././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0486972 mantis_xray-3.2.2/0000775000175000017500000000000014700317134013204 5ustar00wattswatts././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/LICENSE0000644000175000017500000007733214332463747014240 0ustar00wattswatts 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/MANIFEST.in0000644000175000017500000000033414332463747014755 0ustar00wattswattsinclude *.md include LICENSE include mantis_xray/Mantis_batch_settings.txt include mantis_xray/images/* include mantis_xray/henke.xdr include mantis_xray/*.ui include mantis_xray/*.qss recursive-include mantis_xray *.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0446956 mantis_xray-3.2.2/PKG-INFO0000644000175000017500000000524614700317134014306 0ustar00wattswattsMetadata-Version: 2.1 Name: mantis-xray Version: 3.2.2 Summary: MANTiS is a Multivariate ANalysis Tool for x-ray Spectromicroscopy Home-page: https://spectromicroscopy.com/ Author: Mirna Lerotic Author-email: mirna@2ndlookconsulting.com Project-URL: Code, https://github.com/mlerotic/spectromicroscopy Project-URL: Documentation, https://docs.spectromicroscopy.com Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Operating System :: OS Independent Classifier: Topic :: Scientific/Engineering Requires-Python: >=3 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: PyQt5>=5.15.9 Requires-Dist: numpy Requires-Dist: scipy>=1.14.0 Requires-Dist: matplotlib>=3.6.0 Requires-Dist: h5py Requires-Dist: Pillow Requires-Dist: lxml Requires-Dist: pyqtgraph>=0.13.7 Requires-Dist: scikit-image>=0.19.1 Provides-Extra: netcdf Requires-Dist: netcdf4-python; extra == "netcdf" # Spectromicroscopy # [Spectromicroscopy](http://spectromicroscopy.com) combines spectral data with microscopy, where typical datasets consist of a stack of microscopic images taken across an energy range. Due to the data complexity, manual analysis can be time consuming and inefficient, whereas multivariate analysis tools not only reduce the time needed but also can uncover hidden trends in the data. # Mantis # [MANTiS](http://spectromicroscopy.com) is Multivariate ANalysis Tool for Spectromicroscopy developed in Python by [2nd Look Consulting](http://2ndlookconsulting.com). It uses principal component analysis and cluster analysis to classify pixels according to spectral similarity. ## Download ## Mantis package and binaries can be downloaded from [spectromicroscopy.com](http://spectromicroscopy.com). Alternatively, you can install [Python](https://www.python.org/downloads/) and then run the command: `python3 -m pip install mantis-xray` ## Update ## You can upgrade to the latest package release with the command: `pip3 install mantis-xray -U`. It is recommended that you also upgrade the dependencies with: `pip3 install mantis-xray -U --upgrade-strategy "eager"` ## Run ## Installation via pip provides the `mantis-xray` command (alternatively `python3 -m mantis_xray`) to start the Mantis GUI. ## User Guide ## Mantis User Guide can be found at [https://docs.spectromicroscopy.com/](https://docs.spectromicroscopy.com/). ## References ## Please use the following reference when quoting Mantis Lerotic M, Mak R, Wirick S, Meirer F, Jacobsen C. MANTiS: a program for the analysis of X-ray spectromicroscopy data. J. Synchrotron Rad. 2014 Sep; 21(5); 1206–1212 [http://dx.doi.org/10.1107/S1600577514013964] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/README.md0000664000175000017500000000334614700316175014475 0ustar00wattswatts# Spectromicroscopy # [Spectromicroscopy](http://spectromicroscopy.com) combines spectral data with microscopy, where typical datasets consist of a stack of microscopic images taken across an energy range. Due to the data complexity, manual analysis can be time consuming and inefficient, whereas multivariate analysis tools not only reduce the time needed but also can uncover hidden trends in the data. # Mantis # [MANTiS](http://spectromicroscopy.com) is Multivariate ANalysis Tool for Spectromicroscopy developed in Python by [2nd Look Consulting](http://2ndlookconsulting.com). It uses principal component analysis and cluster analysis to classify pixels according to spectral similarity. ## Download ## Mantis package and binaries can be downloaded from [spectromicroscopy.com](http://spectromicroscopy.com). Alternatively, you can install [Python](https://www.python.org/downloads/) and then run the command: `python3 -m pip install mantis-xray` ## Update ## You can upgrade to the latest package release with the command: `pip3 install mantis-xray -U`. It is recommended that you also upgrade the dependencies with: `pip3 install mantis-xray -U --upgrade-strategy "eager"` ## Run ## Installation via pip provides the `mantis-xray` command (alternatively `python3 -m mantis_xray`) to start the Mantis GUI. ## User Guide ## Mantis User Guide can be found at [https://docs.spectromicroscopy.com/](https://docs.spectromicroscopy.com/). ## References ## Please use the following reference when quoting Mantis Lerotic M, Mak R, Wirick S, Meirer F, Jacobsen C. MANTiS: a program for the analysis of X-ray spectromicroscopy data. J. Synchrotron Rad. 2014 Sep; 21(5); 1206–1212 [http://dx.doi.org/10.1107/S1600577514013964] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1728159324.036692 mantis_xray-3.2.2/mantis_xray/0000775000175000017500000000000014700317134015542 5ustar00wattswatts././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/Mantis_batch_settings.txt0000644000175000017500000000066214332463747022636 0ustar00wattswattsVERSION: 2 WORK_DIR: D:\Work\Python\Mantis\Mantis_scr\TestBatch OUTPUT_DIR_NAME: MantisBatchResults FILENAME: luhae.hdf5 SAVE_HDF5: 0 ALIGN_STACK: 0 I0_FILE: '' I0_HISTOGRAM: 0 RUN_PCA: 1 N_SPCA: 4 RUN_CLUSTER_ANALYSIS: 1 N_CLUSTERS: 5 THICKNESS_CORRECTION: 1 RUN_SPECTRAL_ANALYSIS: 1 SA_SPECTRUM: SA_USE_CA_SPECTRA: 1 RUN_KEY_ENGS: 0 KE_THRESHOLD: 0.10 SAVE_PNG: 1 SAVE_PDF: 0 SAVE_SVG: 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/Mrc.py0000644000175000017500000053154214332463747016662 0ustar00wattswatts"""Provide methods for reading and writing files in the MRC format. Requires NumPy. This module has been imported successfully when used with the following combinations of Python and NumPy: Python 2.5.6 ; NumPy 1.3.0.dev6083 Python 2.6.6 ; NumPy 1.4.1 Python 2.7.9 ; NumPy 1.8.2 Python 2.7.10 ; NumPy 1.8.0rc1 Python 3.4.2 ; NumPy 1.8.2 . Other combinations for Python versions greater than or equal to 2.5 and NumPy versions greater than or equal to 1.3.0 likely work. Implements the MRC file format as described at http://msg.ucsf.edu/IVE/IVE4_HTML/IM_ref2.html . The Mrc class is likely easiest to use if you want read-only access to an Mrc file or want read-write access and the modifications that you'll make do not affect the size or format of the extended header or image data. The Mrc class does use memory mapping of the file. Depending on the system, that may make it unusable for large files. An example of how to use the Mrc class is this: import numpy import Mrc a = Mrc.bindFile('somefile.mrc') # a is a NumPy array with the image data memory mapped from # somefile.mrc. You can use it directly with any function # that will take a NumPy array. hist = numpy.histogram(a, bins=200) # Print out key information from the header. a.Mrc.info() # Use a.Mrc.hdr to access the MRC header fields. wavelength0_nm = a.Mrc.hdr.wave[0] If you only want a copy of all the highest resolution data set from a MRC file as a NumPy array, you can use load(): import numpy import Mrc a = Mrc.load('somefile.mrc') The Mrc2 class is what you would use if you wanted to create a MRC file from scratch. An example of that is: import numpy import Mrc a = numpy.reshape(numpy.asarray( numpy.linspace(0, 5999, 6000), dtype='i2'), (10,20,30)) m = Mrc.Mrc2('newfile.mrc', mode='w') # If you want the header to use the MRC 2014 format rather than # the Priism format, insert # m.hdr = Mrc.hdrChangeToMrc2014Format(m.hdr) # Set the header size and pixel type fields. You could also use # m.initHdrForArr(a) instead. m.setHdrForShapeType(a.shape, a.dtype) # Set other fields in the header. m.hdr.setSpacing(0.1, 0.1, 0.3) m.hdr.wave[0] = 540 m.hdr.mmm1 = (0.0, 5999.0, 2999.5) m.hdr.setTitle('Written by Mrc2 of Mrc.py') m.writeHeader() m.writeStack(a) m.close() A short cut for writing a NumPy array as an Mrc file is to use save(). If the only fields that you want to set in the header are the basic size and pixel type information, it is very simple to use: import numpy import Mrc a = numpy.reshape(numpy.asarray( numpy.linspace(0, 5999, 6000), dtype='i2'), (10,20,30)) Mrc.save(a, 'newfile.mrc', ifExists='overwrite') Known limitations: 1) Does not support the 4-bit unsigned integer sample format (mode 101). 2) Does not provide any mechanism for accessing the lower resolution versions of the image data allowed by Priism's version of the MRC format. 3) Uses a NumPy structured array type to represent mode 3 (complex values represented by two signed 16-bit integers) image data. Nothing is provided to make that data act more like standard complex arrays in NumPy. Release notes: Version 0.1.1 corrected Mrc2.makeSymmetryInfo() since it didn't use self when accessing the header information. Also changed the logic in getExtHeaderFormat() to match Priism's when working with a file without the extended header type field but with the map field. In that case assume a non-crystallographic extended header if the space group is 0, 1, or 401. Version 0.1.0 as included with Priism made the following changes which could affect compatibility with client code: 1) Added FROM_PRIISM to the module to distinguish this version from other versions of Mrc.py derived from Priithon. 2) Added HdrBase, HdrPriism, Hdr2014, Hdr2014Priism, ManagedTitleArray, and ReorderedArray classes. The combination of HdrBase and HdrPriism replaced a class defined within implement_hdr(). They do not have the type data attribute (2 byte field immediately after mmm1) that class had and change the interpretation of nspg (absorbing what had been in the type data attribute) and blank (split by adding ntst). The title attribute changed from being backed by a a 10a80 element in the structured array to a (10,80)i1 element. Since interactions with the title attribute are mediated through a ManagedTitleArray, assigning strings to the titles should work as before. 3) Mode 0 image data is interpreted as signed 8-bit integers for compatibility with the MRC 2014 standard. 4) Mode 3 image data (complex values stored as two signed 16-bit integers) was treated as one 4-byte floating-point value. Now it's represented by a NumPy structured array with two 16-bit signed integer fields. The first field is called 'real' and the second is called 'imag'. 5) The extInts and extFloats data attributes of the Mrc and Mrc2 classes are now always set. They may be None, depending on the size and format of the extended header. 6) Removed insertExtHdr() from the Mrc class. 7) Removed extHdrSize, extHdrnint, and extHdrnfloat keywords from __init__() for the Mrc class. 8) Changed the initial values in the header when an instance of Mrc2 is created for a new file. 9) Changed the return value of MrcMode2dtype() from the generic Python type equivalent to the sample representation to a NumPy dtype. 10) As a result of the changes for (2), changed the type returned by implement_hdr() and makeHdrArray(). 11) Changed initHdrArrayFrom() to have a return value. """ __author__ = 'Sebastian Haase ' __license__ = 'BSD license - see PRIITHON_LICENSE.txt' __version__ = '0.1.1' import numpy as N import sys import os # Python 3 renamed __builtin__ to builtins. Work around that without using # from __future__ so that this will work with older versions of Python 2. try: import __builtin__ as builtins except ImportError: import builtins import string import tempfile import weakref # Mark this as a Mrc.py originating from Priism in case a client wants to # try to distinguish between this version and versions from elsewhere. FROM_PRIISM = True def bindFile(fn, writable=0): """Return a NumPy array memory mapped from an existing MRC file. The returned NumPy array will have an attribute, Mrc, that is an instance of the Mrc class. You can use that to access the header or extended header of the file. For instance, if x was returned by bindFile(), x.Mrc.hdr.Num is the number of x samples, number of y samples, and number of sections from the file's header. Positional parameters: fn -- Is the name of the MRC file to bind. Keyword parameters: writable -- If True, the returned array will allow the elements of the array to be modified. The Mrc instance packaged with the array will also allow modification of the header and extended header entries. Changes made to the array elements or the header will affect the file to which the array is bound. """ mode = 'r' if writable: mode = 'r+' a = Mrc(fn, mode) return a.data_withMrc(fn) class Mrc: """Provide memory mapped access to existing MRC files. If x is a Mrc instance, x.data has the highest resolution image data from the file, x.hdr has the header fields, and x.e has the extended header as an array of unsigned 8-bit integers. If the extended header size is greater than zero, the extended header format is Priism's format, and the number of integers or floating-point values per section in the extended header is greater than zero, x.extInts has the integer values from the extended header, x.extFloats has the floating-point values from the extended header, and x.extSym is None. If the extended header has symmetry information, x.extInts is None, x.extFloats is None, and x.extSym is an array of 80 character records for the symmetry information. Any other cases will have x.extInts equal to None, x.extFloats equal to None, and x.extSym equal to None. If used to modify a file, the Mrc class does not provide public methods to change the format or size of the image data or change the size of the extended header data and then remap the image data and extended header data into memory. Because of that, it is best to use the Mrc class for read-only access or for read-write access where the modifications do not change the layout or format of the MRC file. The Mrc2 class can handle more general modifications to an existing MRC file. Depending on the system and the handling of memory mapping in Python, it may not be possible to memory map files which are too large (around 1 gigabyte was a frequent barrier with Python 2.5 and earlier). The Mrc2 class which does not use memory mapping could be useful for those files. If the size of the extended header is not a multiple of the size of the data type used to represent one image sample, the memory mapped image data will be misaligned. Use the Mrc2 class for files like that. Version 0.1.0 of Mrc.py as included with Priism removed the insertExtHdr() function from this class. It also changed the conditions for when the extInts and extFloats attributes are set. """ def __init__(self, path, mode='r'): """Initialize the Mrc object. Maps the entire file into memory. Verion 0.1.0 of Mrc.py as included with Priism removed the extHdrSize, extHdrnint, and extHdrnfloat keywords. Positional parameters: path -- Is the file to map into memory. Keyword parameters: mode -- If mode is 'r', requests read-only access to the file. If mode is 'r+', requests read and write access to the file. """ self.path = os.path.abspath(path) self.filename = os.path.basename(path) self.m = N.memmap(path, mode=mode) self.h = self.m[:1024] self.hdr = makeHdrArray(self.h) if hdrIsByteSwapped(self.hdr): self.hdr._array.dtype = self.hdr._array.dtype.newbyteorder() self.isByteSwapped = True else: self.isByteSwapped = False self.data_offset = 1024 + max(0, self.hdr.next) self.d = self.m[self.data_offset:] self.e = self.m[1024:self.data_offset] self.doDataMap() self.numInts = max(0, self.hdr.NumIntegers) self.numFloats = max(0, self.hdr.NumFloats) if self.hdr.next > 0: fmt = getExtHeaderFormat(self.hdr) else: fmt = -1 if fmt == 0: self.doSymMap() elif fmt == 1 and (self.numInts > 0 or self.numFloats > 0): self.doExtHdrMap() else: self.extHdrArray = None self.extInts = None self.extFloats = None self.extSym = None def doExtHdrMap(self, nz=0): """Map a NumPy structured array to the Priism-style extended header. Creates self.extHdrArray, the structured array to represent the extended header. Also generates self.extInts, a view of self.extHdrArray with the integer entries, and self.extFloats, a view of self.extHdrArray with the floating-point entries. Sets self.extSym to None. Keyword parameters: nz -- Is the number of sections of data to include in self.extHdrArray. If nz is zero, the number of sections included will be the maximum of zero and the number of sections from the header (self.hdr.Num[2]). If nz is less than zero, the number of sections will be the maximum possible given the size of the extended header and the number of integer and floating-point values per section. """ if nz == 0: nz = max(0, self.hdr.Num[-1]) maxnz = len(self.e) // ((self.numInts + self.numFloats) * 4) if nz < 0 or nz > maxnz: nz=maxnz byteorder = '=' type_descr = [('int', '%s%di4' % (byteorder, self.numInts)), ('float', '%s%df4' % (byteorder, self.numFloats))] self.extHdrArray = N.recarray(shape=nz, dtype=type_descr, buf=self.e) if self.isByteSwapped: self.extHdrArray = self.extHdrArray.newbyteorder() self.extInts = self.extHdrArray.field('int') self.extFloats = self.extHdrArray.field('float') self.extSym = None def doSymMap(self): """Map a NumPy structured array to the symmetry information. Creates self.extHdrArray, a structured array to represent the extended header. Also generates self.extSym, an array of 80 character strings mapped to as much of the extended header as possible. Sets self.extInts and self.extFloats to None. """ nrec = self.hdr.next // 80 nrem = self.hdr.next - 80 * nrec type_descr = [('records', '(%d,80)i1' % nrec), ('extra', '%di1' % nrem)] self.extHdrArray = N.recarray(shape=1, dtype=type_descr, buf=self.e) self.extSym = ManagedTitleArray(self.extHdrArray.field('records')[0]) self.extInts = None self.extFloats = None def doDataMap(self): """Map a NumPy array to the highest resolution data set in the file. Creates self.data as the mapped array. """ dtype = MrcMode2dtype(self.hdr.PixelType) shape = shapeFromHdr(self.hdr) self.data = self.d.view() self.data.dtype = dtype n0 = self.data.shape[0] n1 = N.prod(shape) if n0 < n1: # The file is smaller then the space needed for the lowest # resolution data set. Truncate the slowest varying dimension. print('** WARNING **: file truncated - shape from header: %s ' 'expected to get %s but got %s' % (str(shape), str(N.prod(shape)), str(n0))) n1 = n1 // shape[0] s0 = n0 // n1 shape = (s0,) + shape[1:] self.data = self.data[:(s0*n1)] elif n0 > n1: # The file is larger than the space needed for the highest # resolution data set. Ignore the excess. self.data = self.data[:n1] self.data.shape = shape if self.isByteSwapped: self.data = self.data.newbyteorder() def setTitle(self, s, i=-1, push=False, truncate=False): """Set a title in the MRC header. Provided for compatibility with previous versions of Mrc.py. In the version, you can use self.hdr.setTitle(). Positional parameters: s -- Is the character string for the title. If s is longer than 80 characters and truncate is False, a ValueError exception will be raised. Since no byte swapping is done for the titles in the header, s should be encoded in ASCII or another format that does not use multibyte characters. Keyword parameters: i -- Is the index of the title to set. If i is less than zero, the last title not in use will be set. If i is less than zero and all the titles are in use and push is False or i is greater than 9, a ValueError exception will be raised. push -- If True, i is less than zero, and all titles are in use, titles will be pushed down before assigning s to the last title. That will discard the first title and title[k] (for k greater than and equal to 0 and less than 9) will be title[k+1] from before the change. The push keyword was added in version 0.1.0 of Mrc.py as included with Priism truncate -- If True, only use the first 80 characters from s. The truncate keyword was added in version 0.1.0 of Mrc.py as included with Priism. """ self.hdr.setTitle(s, i=i, push=push, truncate=truncate) def axisOrderStr(self, onlyLetters=True): """Return a string indicating the ordering of dimensions. x, y, z, w, and t will appear at most once in the string, and at least three of them will be present. The letters that do appear will be present in order from slowest varying to fastest varying. The values for the axis field in the header do not affect the result. Keyword parameters: onlyLetters -- If True, only the letters for the dimensions will appear in the string. If False, the first character of the string will be '[', the last character of the string will be ']', and a letter for a dimension will be preceded by a comma if it is not the first, slowest-varying, dimension. """ return axisOrderStr(self.hdr, onlyLetters) def looksOK(self, verbose=1): """Perform basic tests on file. Currently tests the file size against what is expected from the header information. Keyword parameters: verbose -- If greater than or equal to one, some diagnostic information will be printed. Larger values generate more diagnostic information. Currently, values larger than three have the same effect as a value of three does. Return value: Returns True if all the tests pass. Returns False if one or more fail. """ shape = self.data.shape b = self.data.dtype.itemsize eb = N.prod(shape) * b ab = len(self.d) secb = N.prod(shape[-2:]) * b if self.hdr.sub > 1: nres = self.hdr.sub if self.hdr.zfac > 1: zfac = self.hdr.zfac if self.hdr.ImgSequence != 1: # Treat an invalid image sequence value as if it was # zero. An image sequence value or zero or two has # z as the fastest varying dimension after y and x. if len(shape) >= 3: resChangesNz = True zind = -3 else: resChangesNz = False else: # WZT order has z changing next fastest after # wavelength, y, and x. if len(shape) >= 4: resChangesNz = True zind = -4 else: resChangesNz = False else: resChangesNz = False else: nres = 1 resChangesNz = False elb = 0 resShape = list(shape) for i in range(1, nres): if resChangesNz: resShape[zind] = (resShape[zind] + zfac - 1) // zfac resShape[-1] = resShape[-1] // 2 resShape[-2] = resShape[-2] // 2 elb += N.prod(resShape) elb *= b # Computing the number of sections of data present only makes # sense if there is no additional resolutions present. if ab < eb or elb == 0: displaySectionInfo = True anSecs = ab / float(secb) enSecs = eb / float(secb) else: displaySectionInfo = False etb = eb + elb if verbose >= 3: print('expected total data bytes: %s' % str(etb)) print('expected number of resolutions: %s' % str(nres)) print('expected bytes in highest resolution: %s' % str(eb)) print('expected bytes in other resolutions: %s' % str(elb)) print('data bytes in file: %s' % str(ab)) if displaySectionInfo: print('expected total secs: %s' % str(enSecs)) print('file has total secs: %s' % str(anSecs)) if etb == ab: if verbose >= 2: print('OK') return 1 elif etb < ab: if verbose >= 1: print('* have %s extra bytes in file' % str(ab - etb)) if displaySectionInfo: print('* have %.2f extra hidden sections in file' % (anSecs - enSecs)) return 0 elif eb <= ab: if verbose >= 1: print('* lower resolution data truncated by %s bytes' % str(etb - ab)) return 0 else: if verbose >= 1: print('* highest resolution data truncated by %s bytes' % str(eb - ab)) print('* file missing %.2f sections of highest resolution' % (enSecs - anSecs)) print('PLEASE SET shape to %s sections !!! ' % str(int(anSecs))) return 0 def info(self): """Print useful information from header.""" hdrInfo(self.hdr) def data_withMrc(self, fn): """Return the image data as a NumPy array. The Mrc attribute of the returned array is this Mrc object. Positional parameters: fn -- Is for compatibility with bindFile() and previous versions of Mrc.py; its value is not used. """ class ndarray_inMrcFile(N.ndarray): def __array_finalize__(self, obj): self.Mrc = getattr(obj, 'Mrc', None) data = self.data data.__class__ = ndarray_inMrcFile ddd = weakref.proxy(data) self.data = ddd data.Mrc = self return data def close(self): """Deletes the memory map of the MRC file. Implicitly commits to disk any changes made. """ # As of NumPy 1.9, memmap no longer has a close method. Instead # use del for all versions. if hasattr(self, 'm'): del self.m ########################################################################### ########################################################################### ########################################################################### ########################################################################### def open(path, mode='r'): """Return a Mrc2 object for a MRC file with the given file name. Positional parameters: path -- Is the name of the file to open. May be None to create a temporary file. For more information, look at the documentation for Mrc2.__init__ . Keyword parameters: mode -- Controls how the file is opened. Use 'r' for read-only access, 'r+' for read and write access to an existing file, 'w' for write access which will ignore the contents of the file if it already exists, and 'w+' for read and write access which will ignore the contents of the file if it already exists. For more information, look at the documentation for Mrc2.__init__ . Return value: Returns a Mrc2 object for the file. When mode is 'r' or 'r+', the header and extended header, if it is present, have been read, and the file is positioned at the start of the image data for the first image. When mode is 'w' or 'w+', the header fields have been set to default values, the file is positioned at the start of the header, and a call to setHdrForShapeType() or initHdrForArr() for the object will be necessary before reading or writing image data. """ return Mrc2(path, mode) def load(fn): """Copy the highest resolution data set from a MRC file to memory. Positional parameters: fn -- Is the name of the MRC file from which to read. Return value: Returns a three-dimensional NumPy array with the data from the file. The array is dimensioned (nsections, ny, nx). The ordering of elements in the first dimension is the same as in the file. The array is not mapped to the file so changes to the array will not affect the file and changes to the file will not affect the array. """ m = open(fn) a = m.readStack(m.hdr.Num[2]) return a def save(a, fn, ifExists='ask', zAxisOrder=None, hdr=None, hdrEval='', calcMMM=True, extInts=None, extFloats=None): """Save the contents of a NumPy array as a MRC-formatted file. Positional parameters: a -- Is the NumPy array to save. a must have one to five dimensions. The type of a must be natively supported by the MRC format. Acceptable types include 32-bit floating point, unsigned and signed 16-bit integer, 64-bit complex, and 8-bit signed integer. fn -- Is the name of the file to be written. Keyword parameters: ifExists -- Controls what happens if there is already a file with the given name. ifExists should be 'ask', 'overwrite', or 'raise'. If the first letter of ifExists is 'o', the existing file will be overwritten. If the first ifExists is 'a', execution will be suspended until there is a response to an interactive prompt about overwriting the file. If the prompt is anwered with 'y' or 'Y' followed by a return, the file will be overwritten. Any other answer followed by a return will generate an exception. If the first letter of ifExists is neither 'o' nor 'a', an exception will be generated if the file already exists. zAxisOrder -- Controls how the dimensions besides the last two of the array are translated to the z, wavelength, and time axes of the file. The ordering of the dimensions in zAxisOrder is from slowest varying (the first dimension of a), to fastest varying. When zAxisOrder is None, it is equivalent to 'z' if the array has three dimensions, 'tz' if the array has four dimensions and to 'tzw' in all other cases. Any ' ', '-', '.', or ',' characters in zAxisOrder are treated as delimiters and are stripped out. The remaining characters are converted to lower case. In the case where the array has three dimensions, only the last character in zAxisOrder after the delimiters have been stripped has an effect. It affects the number of z samples (nz), number of wavelengths (nw), number of time points (nt), and image sequence as follows: nz nw nt Priism sequence 'z' a.shape[0] 1 1 ZTW 'w' 1 a.shape[0] 1 WZT 't' 1 1 a.shape[0] ZTW If the array has four dimensions, only the last two characters of zAxisOrder have an effect: nz nw nt Priism sequence 'zw' a.shape[0] a.shape[1] 1 WZT 'zt' not supported by Priism sequence types 'wz' a.shape[1] a.shape[0] 1 ZTW 'wt' 1 a.shape[0] a.shape[1] ZTW 'tz' a.shape[1] 1 a.shape[0] ZTW 'tw' 1 a.shape[1] a.shape[0] WZT If the array has five dimensions, only the last three characters of zAxisOrder have an effect: nz nw nt Priism sequence 'zwt' not supported by Priism sequence types 'ztw' not supported by Priism sequence types 'wtz' a.shape[2] a.shape[0] a.shape[1] ZTW 'wzt' not supported by Priism sequence types 'tzw' a.shape[1] a.shape[2] a.shape[0] WZT 'twz' a.shape[2] a.shape[1] a.shape[0] ZWT hdr -- If not None, should act like an instance of the HdrPriism or Hdr2014Priism classes, i.e. a value returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. The values of all fields in hdr will be copied to the output file except for the number of samples ('Num' field; bytes 1 - 12), the pixel type ('PixelType' field; bytes 13 - 16), and the number of bytes in the extended header ('next' field; bytes 93 - 96). If both hdr and zAxisOrder are not None, the values for the number of wavelengths ('NumWaves' field; bytes 197 - 198), number of time points ('NumTimes' field; bytes 181 - 182), and image sequence ('ImgSequence' field; bytes 183 - 184) will be overwritten by the values in hdr. calcMMM -- If True, the minimum value for each wavelength, the maximum value for each wavelength, and the median value for first wavelength will be calculated and stored in the header. Those calculated values will overwrite the values set by hdr. hdrEval -- If not an empty string or None, hdrEval will be executed with Python's exec in a context where all global variables are accessible and the local variables are those of the calling function augmented with a variable named 'hdr'. That local variable represents the header to be saved to the file. It is a header as returned by makeHdrArray() or implement_hdr(). hdrEval is executed after any changes to the header made due to the zAxisOrder, hdr, calcMMM, extInts, and extFloats parameters. extInts -- If not None, will be used to initialize the integer fields in the extended header. When not None, extInts must be a NumPy array. The size of the last dimension will be used as the number of integers per section. The product of the sizes for the remaining dimensions will be used as the number of sections of data available. When extInts is None and extFloats is not None, there will be no integer entries in the extended header. extFloats -- If not None, will be used to initialize the floating-point fields in the extended header. When not None, extFloats must be a NumPy array. The size of the last dimension will be used as the number of floating-point values per section. The product of the sizes for the remaining dimensions will be used as the number of sections of data available. When extFloats is None and extInts is not None, there will be no floating-point entries in the extended header. If there are a different number of sections of data available from extInts and extFloats, the size of the extended header will be set based on the larger number of sections and the unspecified values will be filled with zeros. """ if os.path.exists(fn): if ifExists[0] == 'o': pass elif ifExists[0] == 'a': try: # First try the Python 2 way for this. answer = raw_input('overwrite?') except NameError: # Now try the Python 3 one; note that input() has a different # meaning in Python 2. answer = input('overwrite?') yes = answer.lower() == 'y' if not yes: raise RuntimeError('not overwriting existing file "%s"' % fn) else: raise RuntimeError('not overwriting existing file "%s"' % fn) m = Mrc2(fn, mode='w') m.initHdrForArr(a, zAxisOrder) if hdr is not None: m.hdr = initHdrArrayFrom(m.hdr, hdr) if calcMMM: wAxis = axisOrderStr(m.hdr).find('w') if wAxis < 0: m.hdr.mmm1 = computeMinMaxMedian(N.real(a)) else: nw = m.hdr.NumWaves m.hdr.mmm1 = computeMinMaxMedian(N.real(a.take((0,), wAxis))) if nw >=2: m.hdr.mm2 = computeMinMax(N.real(a.take((1,), wAxis))) if nw >=3: m.hdr.mm3 = computeMinMax(N.real(a.take((2,), wAxis))) if nw >=4: m.hdr.mm4 = computeMinMax(N.real(a.take((3,), wAxis))) if nw >=5: m.hdr.mm5 = computeMinMax(N.real(a.take((4,), wAxis))) if extInts is not None: numints = extInts.shape[-1] numextsec_int = extInts.size // numints else: numints = 0 numextsec_int = 0 if extFloats is not None: numfloats = extFloats.shape[-1] numextsec_float = extFloats.size // numfloats else: numfloats = 0 numextsec_float = 0 if ((numints > 0 and numextsec_int > 0) or (numfloats > 0 and numextsec_float > 0)): m.makeExtendedHdr(numints, numfloats, nSecs=max(numextsec_int, numextsec_float)) if numints == 1: m.extInts[0:numextsec_int] = N.ravel(extInts) m.extInts[numextsec_int:] = 0 elif numints > 1: m.extInts[0:numextsec_int, 0:numints] = ( N.reshape(extInts, (numextsec_int, numints))) m.extInts[numextsec_int:, 0:numints] = 0 if numfloats == 1: m.extFloats[0:numextsec_float] = N.ravel(extFloats) m.extFloats[numextsec_float:] = 0.0 elif numfloats > 1: m.extFloats[0:numextsec_float, 0:numfloats] = ( N.reshape(extFloats, (numextsec_float, numfloats))) m.extFloats[numextsec_float:, 0:numfloats] = 0.0 if hdrEval: fr = sys._getframe(1) loc = {'hdr':m.hdr} loc.update(fr.f_locals) glo = fr.f_globals exec(hdrEval, glo, loc) m.writeHeader() m.writeExtHeader(seekTo0=True) m.writeStack(a) m.close() def computeMinMaxMedian(array): """Compute statistics for save(). This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: array -- Is a NumPy array. Return value: Returns a tuple with the minimum, maximum, and median of the array. If array is structured, the values returned are for the first component. """ if array.dtype.fields is None: return (N.min(array), N.max(array), N.median(array)) else: return (N.min(array[array.dtype.names[0]]), N.max(array[array.dtype.names[0]]), N.median(array[array.dtype.names[0]])) def computeMinMax(array): """Compute statistics for save(). This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: array -- Is a NumPy array. Return value: Returns a tuple with the minimum and maximum of the array. If array is structured, the values returned are for the first component. """ if array.dtype.fields is None: return (N.min(array), N.max(array)) else: return (N.min(array[array.dtype.names[0]]), N.max(array[array.dtype.names[0]])) ########################################################################### ########################################################################### ########################################################################### ########################################################################### class Mrc2: """Provide access to MRC files. Unlike the Mrc class, does not use a memory map to access the file. Actively manages the header and extended header data. If x is a Mrc2 instance. x.hdr has the header fields, and x.e has the extended header as an array of unsigned 8-bit integers. If the extended header size is greater than zero, the extended header is in Priism's format, and the number of integers or floating-point values per section in the extended header is greater than zero, x.extInts has the integer values from the extended header, x.extFloats has the floating-point values from the extended header, and x.extSym is None. If the extended header has symmetry information, x.extInts is None, x.extFloats is None, and x.extSym is an array of 80 character records for the symmetry information. Any other cases will have x.extInts equal to None, x.extFloats equal to None, and x.extSym equal to None. Some internal state depends on the values in hdr.Num and hdr.PixelType. To modify those fields, the recommended procedure is to do so indirectly with initHdrForArr() or setHdrForShapeType(). If you do modify those fields directly, call _initWhenHdrArraySet() so that the internal state is consistent with your changes. For the fields related to the extended header, hdr.NumIntegers, hdr.NumFloats, and hdr.next, there's no public way to modify those directly while maintaining a consistent internal state for the extended header. Use makeExtendedHdr() or makeSymmetryInfo() to modify those fields. For the image data, provides the functions seekSec(), readSec(), readStack(), writeSec(), and writeStack() to position the file at a given section, read image data, or write image data. Version 0.1.0 of Mrc.py as included with Priism changed the conditions for when the extInts and extFloats attributes are set. """ def __init__(self, path, mode='r'): """Initialize the Mrc2 object. If mode is 'r' or 'r+', reads the header and, if present, the extended header, and positions the file at the start of the image data for the first section. When mode is 'w' or 'w+', the header fields are set to the default values, the file is positioned at the start of the header, and a call to setHdrForShapeType() or initHdrForArr() will be necessary before reading or writing image data. Positional parameters: path -- Is the name of the file to use. If path is None, a temporary file will be generated, and that file will be deleted when the close() method is called. A value of None for path will only work if the mode parameter is set to 'w' or 'w+'. The _path attribute of the created object will be set to path if path is not None; otherwise, it will be set to the name of the temporary file. Allowing None for path was added in version 0.1.0 of Mrc.py as included with Priism. Keyword parameters: mode -- Specifies how the file should be opened. It has similar semantics to the mode parameter for Python's open() except that all modes implicitly include 'b' for working with binary files. Allowed values for mode are: reading writing notes 'r' allowed forbidden path must exist when __init__ called 'r+' allowed allowed path must exist when __init__ called 'w' forbidden allowed path overwritten if it exists 'w+' allowed allowed path overwritten if it exists """ if path is None: self._f, self._path = tempfile.mkstemp() self._f = os.fdopen(self._f, mode + 'b') self._delete_on_close = True else: self._f = builtins.open(path, mode + 'b') self._path = path self._delete_on_close = False self._name = os.path.basename(self._path) self._mode = mode self._hdrSize = 1024 self._dataOffset = self._hdrSize self._fileIsByteSwapped = False if mode in ('r', 'r+'): self._initFromExistingFile() self.seekSec(0) else: self.hdr = makeHdrArray() self.hdr.Num = (0, 0, 0) self.hdr.PixelType = 1 self.hdr.mst = (0, 0, 0) self.hdr.m = (1, 1, 1) self.hdr.d = (1.0, 1.0, 1.0) self.hdr.angle = (90.0, 90.0, 90.0) self.hdr.axis = (1, 2, 3) self.hdr.mmm1 = (0.0, 0.0, 0.0) self.hdr.nspg = 0 self.hdr.next = 0 self.hdr.dvid = 0xc0a0 self.hdr.nblank = 0 self.hdr.ntst = 0 self.hdr.blank = 0 self.hdr.NumIntegers = 0 self.hdr.NumFloats = 0 self.hdr.sub = 1 self.hdr.zfac = 1 self.hdr.mm2 = (0.0, 0.0) self.hdr.mm3 = (0.0, 0.0) self.hdr.mm4 = (0.0, 0.0) self.hdr.ImageType = 0 self.hdr.LensNum = 0 self.hdr.n1 = 0 self.hdr.n2 = 0 self.hdr.v1 = 0 self.hdr.v2 = 0 self.hdr.mm5 = (0.0, 0.0) self.hdr.NumTimes = 1 self.hdr.ImgSequence = 0 self.hdr.tilt = (0.0, 0.0, 0.0) self.hdr.NumWaves = 1 self.hdr.wave = (0, 0, 0, 0, 0) self.hdr.zxy0 = (0.0, 0.0, 0.0) self.hdr.NumTitles = 0 self.hdr.title = ' ' * 80 self._shape = None self._shape2d = None self._dtype = None # scalar data type of pixels self._secByteSize = 0 self.e = N.zeros(0, dtype='u1') self._extHdrArray = None self.extInts = None self.extFloats = None self.extSym = None def initHdrForArr(self, arr, zAxisOrder=None): """Initialize the MRC header from the shape and type of a NumPy array. Positional parameters: arr -- Is the NumPy array whose shape and type are to be used. zAxisOrder -- Controls how the dimensions besides the last two of the array are translated to the z, wavelength, and time axes of the file. The ordering of the dimensions in zAxisOrder is from slowest varying (the first dimension of a), to fastest varying. When zAxisOrder is None, it is equivalent to 'z' if the array has three dimensions, 'tz' if the array has four dimensions and to 'tzw' in all other cases. Any ' ', '-', '.', or ',' characters in zAxisOrder are treated as delimiters and are stripped out. The remaining characters are converted to lower case. The documentation for save() in this module has the details for how the zAxisOrder will set the header values for the image sequence type and the number of samples in z, wavelength, and time. """ if zAxisOrder is None: if arr.ndim ==3: zAxisOrder = 'z' elif arr.ndim ==4: zAxisOrder = 'tz' else: zAxisOrder = 'tzw' else: # remove delimiter characters '-., ' zAxisOrder = zAxisOrder.translate( string.join([chr(i) for i in range(256)], ''), '-., ').lower() mrcmode = dtype2MrcMode(arr.dtype) init_simple(self.hdr, mrcmode, arr.shape, isByteSwapped=self._fileIsByteSwapped) if arr.ndim == 1 or arr.ndim == 2: pass elif arr.ndim == 3: if zAxisOrder[-1] == 'z': self.hdr.ImgSequence = 0 elif zAxisOrder[-1] == 'w': self.hdr.ImgSequence = 1 self.hdr.NumWaves = arr.shape[-3] elif zAxisOrder[-1] == 't': self.hdr.ImgSequence = 0 self.hdr.NumTimes = arr.shape[-3] else: raise ValueError('unsupported axis order') elif arr.ndim == 4: if zAxisOrder[-2:] == 'zt': raise ValueError('unsupported axis order; time varies ' 'faster than z') elif zAxisOrder[-2:] == 'tz': self.hdr.ImgSequence = 0 self.hdr.NumTimes = arr.shape[-4] elif zAxisOrder[-2:] == 'wz': self.hdr.ImgSequence = 0 self.hdr.NumWaves = arr.shape[-4] elif zAxisOrder[-2:] == 'zw': self.hdr.ImgSequence = 1 self.hdr.NumWaves = arr.shape[-3] elif zAxisOrder[-2:] == 'tw': self.hdr.ImgSequence = 1 self.hdr.NumWaves = arr.shape[-3] self.hdr.NumTimes = arr.shape[-4] elif zAxisOrder[-2:] == 'wt': self.hdr.ImgSequence = 0 self.hdr.NumWaves = arr.shape[-4] self.hdr.NumTimes = arr.shape[-3] else: raise ValueError('unsupported axis order') elif arr.ndim == 5: if zAxisOrder[-3:] == 'wtz': self.hdr.ImgSequence = 0 self.hdr.NumWaves = arr.shape[-5] self.hdr.NumTimes = arr.shape[-4] elif zAxisOrder[-3:] == 'tzw': self.hdr.ImgSequence = 1 self.hdr.NumWaves = arr.shape[-3] self.hdr.NumTimes = arr.shape[-5] elif zAxisOrder[-3:] == 'twz': self.hdr.ImgSequence = 2 self.hdr.NumWaves = arr.shape[-4] self.hdr.NumTimes = arr.shape[-5] else: raise ValueError('unsupported axis order') else: raise ValueError('unsupported array ndim') if self.hdr.NumWaves > 5: print('WARNING: more than 5 wavelengths for MRC file') self._initWhenHdrArraySet() def _initFromExistingFile(self): """Initialize the header for __init__ from the contents of the file.""" self.seekHeader() buffer = N.fromfile(self._f, dtype='u1', count=1024) self.hdr = makeHdrArray(buffer, makeWeak=False) if hdrIsByteSwapped(self.hdr): self.hdr._array.dtype = self.hdr._array.dtype.newbyteorder() self._fileIsByteSwapped = True self._extHdrSize = self.hdr.next self._extHdrNumInts = max(0, self.hdr.NumIntegers) self._extHdrNumFloats = max(0, self.hdr.NumFloats) self._extHdrBytesPerSec = ( (self._extHdrNumInts + self._extHdrNumFloats) * 4) self._dataOffset = self._hdrSize + self._extHdrSize if self._extHdrSize > 0: self.e = N.fromfile(self._f, dtype='u1', count=self._extHdrSize) fmt = getExtHeaderFormat(self.hdr) else: self.e = N.zeros(0, dtype='u1') fmt = -1 if fmt == 0: nrec = self._extHdrSize // 80 nrem = self._extHdrSize - 80 * nrec type_descr = [('records', '(%d,80)i1' % nrec), ('extra', '%di1' % nrem)] self.extHdrArray = N.recarray(shape=1, dtype=type_descr, buf=self.e) self.extSym = ManagedTitleArray( self.extHdrArray.field('records')[0]) self.extInts = None self.extFloats = None elif (fmt == 1 and (self._extHdrNumInts > 0 or self._extHdrNumFloats > 0)): nSecs = self._extHdrSize // self._extHdrBytesPerSec byteorder = '=' type_descr = [ ('int', '%s%di4' % (byteorder, self._extHdrNumInts)), ('float', '%s%df4' % (byteorder, self._extHdrNumFloats))] self._extHdrArray = N.recarray(shape=nSecs, dtype=type_descr, buf=self.e) if self._fileIsByteSwapped: self._extHdrArray = self._extHdrArray.newbyteorder() self.extInts = self._extHdrArray.field('int') self.extFloats = self._extHdrArray.field('float') self.extSym = None else: self._extHdrArray = None self.extInts = None self.extFloats = None self.extSym = None self._initWhenHdrArraySet() def _initWhenHdrArraySet(self): """Reset internal attributes based on size and pixel type in header.""" nx, ny, nsecs = self.hdr.Num if nx < 0: nx = 0 if ny < 0: ny = 0 if nsecs < 0: nsecs = 0 self._shape = (nsecs, ny, nx) # todo: wavelenths , times self._shape2d = self._shape[-2:] self._dtype = MrcMode2dtype(self.hdr.PixelType) if self._fileIsByteSwapped: self._dtype = self._dtype.newbyteorder() self._secByteSize = self._dtype.itemsize * N.prod(self._shape2d) def setHdrForShapeType(self, shape, type): """Set the size and pixel type fields in the header. For a file opened in 'w' or 'w+' mode, this and initHdrForArr() are the two ways to make a Mrc2 object ready to read or write image data. As currently implemented, only uses the last two elements of shape and the product of all the remaining elements of shape to set the size fields in the header. It does not modify the fields for the number of time points, number of wavelengths, or image sequence. Positional parameters: shape -- Is a tuple to specify the shape of the data to be stored in the MRC file. The ith element of the tuple is the number of samples for the ith dimension. The 0th dimension is the slowest varying. The fastest varying dimension, usually called x, is the last element in the tuple. Shape should have at least two elements. type -- Is the NumPy dtype or Python type that will be used to represent each pixel value in the file. If the type is not equivalent to one of the pixel formats supported by MRC, an exception will be raised. """ mrcmode = dtype2MrcMode(type) self.hdr.PixelType = mrcmode self.hdr.Num = shape[-1], shape[-2], N.prod(shape[:-2]) self._initWhenHdrArraySet() def makeExtendedHdr(self, numInts, numFloats, nSecs=None): """Create a Priism extended header or remove the extended header. Will remove the extended header if nSecs is zero or both numInts and numFloats are zero. If header is in Priism's format, sets the space group to zero. Also sets the space group to zero if the header does not claim to support the exttyp field and the space group is different than 0, 1, or 401. The entries for a new header will all be zero. If there already was an extended header, the resources for the previous extended header are released, and no attempt is made to copy the previous values to the new header. When a new extended header is created, the integer values can be accessed with self.extInts. The floating point values can be accessed with self.extFloats. Both are NumPy array views. If numInts is greater than one or is zero, the shape for self.extInts will be (nSecs, numInts). If numInts is one, the shape for self.extFloats will be (nSecs,). If numFloats is greater than one or is zero, the shape for self.extFloats will be (nSecs, numFloats). If numFloats is one, the shape for self.extFloats will be (nSecs,). makeExtendedHdr() does not change the contents of the file. To commit changes made to the shape of the extended header, call writeHeader(). To commit changes made to the values in the extended header, call writeExtHeader(). Positional parameters: numInts -- Is the number of integer values to store per section in the extended header. Must be non-negative. numFloats -- Is the number of floating-point values to store per section in the extended header. Must be non-negative. Keyword parameters: nSecs -- If not None, nSecs is the number of sections of storage to allocate for the extended header. If nSecs is None, the number of sections allocated will be the number of sections from the header. """ if numInts < 0 or numFloats < 0: raise ValueError('Number of integers or floating point ' 'values is negative') if numInts > 32767 or numFloats > 32767: raise ValueError('Number of integers or floating point ' 'values is too large to store in header fields') if nSecs is not None and nSecs < 0: raise ValueError('Number of sections is negative') if nSecs is None: if self._shape is None: nSecs = 0 else: nSecs = self._shape[0] bytesPerSec = (numInts + numFloats) * 4 ntot = minExtHdrSize(nSecs, bytesPerSec) if ntot > 2147483647: raise ValueError('Requested extended header size is too ' 'large for the extended header size field') self._extHdrNumInts = self.hdr.NumIntegers = numInts self._extHdrNumFloats = self.hdr.NumFloats = numFloats if hdrHasExtType(self.hdr): self.hdr.exttyp = N.fromstring('AGAR', dtype='i1') else: if (hdrIsInPriismFormat(self.hdr) or (self.hdr.nspg != 0 and self.hdr.nspg != 1 and self.hdr.nspg != 401)): self.hdr.nspg = 0 self._extHdrBytesPerSec = bytesPerSec self._extHdrSize = self.hdr.next = ntot self._dataOffset = self._hdrSize + self._extHdrSize self.e = N.zeros(self._extHdrSize, dtype='u1') if self._extHdrSize > 0 and self._extHdrBytesPerSec > 0: nSecs = self._extHdrSize // self._extHdrBytesPerSec byteorder = '=' type_descr = [ ('int', '%s%di4' % (byteorder, self._extHdrNumInts)), ('float', '%s%df4' % (byteorder, self._extHdrNumFloats))] self._extHdrArray = N.recarray(nSecs, dtype=type_descr, buf=self.e) self.extInts = self._extHdrArray.field('int') self.extFloats = self._extHdrArray.field('float') else: self._extHdrArray = None self.extInts = None self.extFloats = None self.extSym = None def makeSymmetryInfo(self, nbytes, nspg=None): """Create the extended header for symmetry information. If the header is in Priism's format and the space group, after applying the nspg keyword, is zero, will raise a RuntimeError exception since files with zero for the space group are assumed to use Priism-style extended headers. Sets the NumIntegers and NumFloats fields to zero. The new extended header is filled with spaces. If there already was an extended header, the resources for the previous extended header are released, and no attempt is made to copy the previous values to the new header. When a new extended header is created, the symmetry information, as an array of 80 character records, can be accessed with self.extSym. self.extInts and self.extFloats are set to None. makeSymmetryInfo() does not change the contents of the file. To commit changes made to the shape of the extended header, call writeHeader(). To commit changes made to the values in the extended header, call writeExtHeader(). This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: nbytes -- Is the number of bytes to allocate. A ValueError exception will be raised if nbytes is less than zero. A value of zero will remove the extended header. Note that values of nbytes that are not multiples of eight could lead to misalignment of image data if the file is memory mapped with the Mrc class. Keyword parameters: nspg -- If not None, the space group in the header will be set to the specified value. """ if nbytes < 0: raise ValueError('Negative number of bytes requested for ' 'extended header') if nbytes > 2147483647: raise ValueError('Requested number of bytes is too large ' 'for the extended header size field') if nspg is not None: self.hdr.nspg = nspg if hdrIsInPriismFormat(self.hdr) and self.hdr.nspg == 0: raise RuntimeError('Used makeSymmetryInfo() when the space ' 'group is zero') self.hdr.next = nbytes self.hdr.NumIntegers = 0 self.hdr.NumFloats = 0 if hdrHasExtType(self.hdr): self.hdr.exttyp = N.fromstring('MRC0', dtype='i1') self._extHdrSize = nbytes self._extHdrNumInts = 0 self._extHdrNumFloats = 0 self._extHdrBytesPerSec = 0 self._dataOffset = self._hdrSize + self._extHdrSize if self._extHdrSize > 0: self.e = N.empty(self._extHdrSize, dtype='u1') # ASCII for space. self.e[:] = 32 nrec = self._extHdrSize // 80 nrem = self._extHdrSize - 80 * nrec type_descr = [('records', '(%d,80)i1' % nrec), ('extra', '%di1' % nrem)] self._extHdrArray = N.recarray(shape=1, dtype=type_descr, buf=self.e) self.extSym = ManagedTitleArray( self._extHdrArray.field('records')[0]) else: self.e = N.zeros(0, dtype='u1') self._extHdrArray = None self.extSym = None self.extInts = None self.extFloats = None def makeGenericExtendedHdr(self, nbytes, fmt): """Allocate space for an extended header that is not for symmetry information and does not use Priism's format. The bytes in the new extended header are set to zero. If there already was an extended header, the resources for the previous extended header are released, and no attempt is made to copy the previous values to the new header. The new extended header, as an array of unsigned bytes, can be accessed through self.e. Sets self.extInts, self.extFloats, and self.extSym to None. makeGenericExtendedHdr() does not change the contents of the file. To commit the changes made to the shape or format of the extended header, call writeHeader(). To commit changes made to the values in the extended header, call writeExtHeader(). This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: nbytes -- Is the number of bytes to allocate. If nbytes is zero, the extended header will be removed. A ValueError exception will be raised if nbytes is less than zero. Note that values of nbytes that are not multiples of eight could lead to misalignment of image data if the file is memory mapped with the Mrc class. fmt -- Is a four character ASCII string describing the format of the extended header. A ValueError exception will be raised if fmt is 'AGAR', 'MRC0', of 'CCP4'. Use makeExtendedHdr() or makeSymmetryInfo() to create extended headers for those formats. A RuntimeError exception will be raised if the header format does not store a string describing the extended header format. """ if nbytes < 0: raise ValueError('Negative number of bytes requested for ' 'extended header') if nbytes > 2147483647: raise ValueError('Requested number of bytes is too large ' 'for the extended header size field') if fmt == 'AGAR': raise ValueError('Use makeExtendedHdr() to create an extended ' 'with the Priism format') if fmt == 'MRC0' or fmt == 'CCP4': raise ValueError('Use makeSymmetryInfo() to create an extended ' 'header with symmetry information') if len(fmt) != 4: raise ValueError('fmt is not a four character string') if not hdrHasExtType(self.hdr): raise RuntimeError('Header does not store a string code for the ' 'extended header format') self.hdr.exttyp = N.fromstring(fmt, dtype='i1') self.hdr.next = nbytes self._extHdrSize = nbytes self._extHdrNumInts = 0 self._extHdrNumFloats = 0 self._extHdrBytesPerSec = 0 self._dataOffset = self._hdrSize + self._extHdrSize self.e = N.zeros(nbytes, dtype='u1') self.extInts = None self.extFloats = None self.extSym = None def setTitle(self, s, i=-1, push=False, truncate=False): """Set a title in the MRC header. This function was added in version 0.1.0 of Mrc.py as included with Priism. That version also allows calling setTitle() directly on the header: self.hdr.setTitle(). Positional parameters: s -- Is the character string for the title. If s is longer than 80 characters and truncate is False, a ValueError exception will be raised. Since no byte swapping is done for the titles in the header, s should be encoded in ASCII or another format that does not use multibyte characters. Keyword parameters: i -- Is the index of the title to set. If i is less than zero, the last title not in use will be set. If i is less than zero and all the titles are in use and push is False or i is greater than 9, a ValueError exception will be raised. push -- If True, i is less than zero, and all titles are in use, titles will be pushed down before assigning s to the last title. That will discard the first title and title[k] (for k greater than and equal to 0 and less than 9) will be title[k+1] from before the change. truncate -- If True, only use the first 80 characters from s. """ self.hdr.setTitle(s, i=i, push=push, truncate=truncate) def axisOrderStr(self, onlyLetters=True): """Return a string indicating the ordering of dimensions. x, y, z, w, and t will appear at most once in the string, and at least three of them will be present. The letters that do appear will be present in order from slowest varying to fastest varying. The values for the axis field in the header do not affect the result. This function was added in version 0.1.0 of Mrc.py as included with Priism. Keyword parameters: onlyLetters -- If True, only the letters for the dimensions will appear in the string. If False, the first character of the string will be '[', the last character of the string will be ']', and a letter for a dimension will be preceded by a comma if it is not the first, slowest-varying dimension. """ return axisOrderStr(self.hdr, onlyLetters) def info(self): """Print useful information from header.""" hdrInfo(self.hdr) def close(self): """Close the file associated with the Mrc2 object. Delete that file if it was created as a temporary file. """ self._f.close() if self._delete_on_close: os.remove(self._path) def flush(self): """Flush any changes to the Mrc2 object's file to disk.""" self._f.flush() def seekSec(self, i): """Seek to the start of the image data for a given section. Positional parameters: i -- Is the 0-based section index. """ if self._secByteSize == 0: raise ValueError('not inited yet - unknown shape, type') self._f.seek(self._dataOffset + i * self._secByteSize) def seekHeader(self): """Seek to the start of the MRC header.""" self._f.seek(0) def seekExtHeader(self): """Seek to the start of the MRC extended header.""" self._f.seek(self._hdrSize) def readSec(self, i=None): """Read one image from the MRC file. Keyword parameters: i -- If i is None, starts the read at the current position for the file. If i is not None, seeks to the start of section i and reads from that location. Return value: Returns a two-dimensional NumPy array with the image data. The shape of the array is (self.hdr.Num[1], self.hdr.Num[0]). The format for each image element is the same as in the file. """ if i is not None: self.seekSec(i) a = N.fromfile(self._f, self._dtype, N.prod(self._shape2d)) a.shape = self._shape2d return a def writeSec(self, a, i=None): """Write image data to the MRC file. Positional parameters: a -- Is a NumPy array with the data to write. The format for each sample should match, up to byte order, the format for data values in the file. No checks are done on the dimensions of a, so any amount of data can be written. To best ensure compatibility with future versions of Mrc2, a should be two-dimensional with a shape of (self.Hdr.Num[1], self.hdr.Num[0]). Keyword parameters: i -- If i is None, the write starts at the current position for the file. If i is not None, seeks to the start of section i and starts the write at that location. """ if a.dtype.type != self._dtype.type: raise TypeError('type of data, %s, to write does not match ' 'type, %s, in header' % (a.dtype.name, self._dtype.name)) if self._fileIsByteSwapped != isSwappedDtype(a.dtype): v = a.byteswap() else: v = a if i is not None: self.seekSec(i) return v.tofile(self._f) def readStack(self, nz, i=None): """Read nz images from the MRC file. Positional parameters: nz -- Is the number of images to read. Keyword parameters: i -- If i is None, the read starts at the current position for the file. If i is not None, seeks to the start of section i and starts the read there. Return value: Returns a three-dimensional NumPy array with the image data. The shape of the array is (nz, self.hdr.Num[1], self.hdr.Num[0]). The format for each image element is the same as in the file. """ if i is not None: self.seekSec(i) a = N.fromfile(self._f, self._dtype, nz * N.prod(self._shape2d)) a.shape = (nz,) + self._shape2d return a def writeStack(self, a, i=None): """Write image data to the MRC file. Positional parameters: a -- Is a NumPy array with the data to write. The format for each sample should match, up to byte order, the format for data values in the file. No checks are done on the dimensions of a, so any amount of data can be written. To best ensure compatibility with future versions of Mrc2, a should have at least two dimensions and the shape of the last two dimensions should be (self.Hdr.Num[1], self.Hdr.Num[0]). Keyword parameters: i -- If i is None, the write starts at the current position for the file. If i is not None, seeks to the start of section i and starts the write at that location. """ if a.dtype.type != self._dtype.type: raise TypeError('type of data, %s, to write does not match ' 'type, %s, in header' % (a.dtype.name, self._dtype.name)) if self._fileIsByteSwapped != isSwappedDtype(a.dtype): v = a.byteswap() else: v = a if i is not None: self.seekSec(i) return v.tofile(self._f) def writeHeader(self, seekTo0=False): """Write the 1024 byte MRC header to the file. Keyword parameters: seekTo0 -- If True, the file position will be set after writing the header to the start of the first section's image data. """ self.seekHeader() self.hdr._array.tofile(self._f) if seekTo0: self.seekSec(0) def writeExtHeader(self, seekTo0=False): """Write the extended header to the file. Keyword parameters: seekTo0 -- If True, the file position will be set after writing the extended header to the start of the first section's image data. """ self.seekExtHeader() self.e.tofile(self._f) if seekTo0: self.seekSec(0) ########################################################################### ########################################################################### ########################################################################### ########################################################################### class HdrBase(object): """Represents a MRC header without extensions. Only provides access to the parts of the header that are in the original formulation of the MRC format. Those fields are listed below and can be accessed as x.name_of_field where x is an instance of this class. For example, x.Num would access the number of columns, rows, and sections. This class was added in version 0.1.0 of Mrc.py as included with Priism. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = ('_array',) def __init__(self, hdrArray): """Initialize the HdrBase object. Positional parameters: hdrArray -- Is a MRC header as a NumPy structured array with one element. The dtype of the array will have to compatible with whatever subclass of HdrBase is being instantiated. A NumPy structured array with a dtype of numpy.dtype(mrcHdr_dtype) is compatible with HdrBase and HdrPriism. A NumPy structured array with a dtype of numpy.dtype(mrc2014Hdr_dtype) is compatible with HdrBase, Hdr2014 and Hdr2014Priism. """ self._array = hdrArray def _getNum(self): """Is three integers as a NumPy array. The first is the number of columns; i.e. the number of samples in the fastest varying dimension as stored in the file. The second is the number of rows; i.e. the number of samples in the second-fastest varying dimension as stored in the file. Occupies bytes 1 - 12 (numbered from one) in the header. """ return self._array['Num'][0] def _setNum(self, value): self._array['Num'][0] = value Num = property(_getNum, _setNum) def _getPixelType(self): """Is an integer code for the format of the image data. For supported Python types or NumPy dtypes, Mrc.dtype2MrcMode() can compute the code. Codes recognized by this software are (zero through 4 are part of the original formulation of the format): 0: signed (two's complement) 8-bit integer 1: signed (two's complement) 16-bit integer 2: 32-bit IEEE floating-point value 3: real and imaginary parts of a complex value; both as signed (two's complement) 16-bit integers 4: real and imaginary parts of a complex value; both as 32-bit IEEE floating-point values 5: nothing currently standardized; treated as signed (two's complement) 16-bit integer 6: unsigned 16-bit integer 7: nothing currently standardized; treated as signed (two's complement) 32-bit integer 101: nothing currently standardized; treated as unsigned 4-bit integer; if the number of columns is odd, each row has an extra unused 4 bits at the end so rows start on 8-bit boundaries Occupies bytes 13 - 16 (numbered from one) in the header. """ return self._array['PixelType'][0] def _setPixelType(self, value): self._array['PixelType'][0] = value PixelType = property(_getPixelType, _setPixelType) def _getmst(self): """Is three integers as a NumPy array. For crystallographic data, these are the location of the first column, first row, and first section in the unit cell. For non-crystallographic data, these are usually used to describe the dataset's relationship to another, larger, dataset from which it was drawn; these values might then be the indices in that larger dataset for the first sample in this dataset. Occupies bytes 17 - 28 (numbered from one) in the header. """ return self._array['mst'][0] def _setmst(self, value): self._array['mst'][0] = value mst = property(_getmst, _setmst) def _getm(self): """Is three integers as a NumPy array. For crystallographic data, holds the number of samples along the x, y, and z axes, respectively, of the unit cell. For non-crystallographic data, it is common to use either ones for the values in m or to set the values equal to the values in Num. An exception to that is for MRC 2014 files. Then, m[2] is used to store the number of z slices per volume when the space group is 1 or 401. Occupies bytes 29 - 40 (numbered from one) in the header. """ return self._array['m'][0] def _setm(self, value): self._array['m'][0] = value m = property(_getm, _setm) def _getd(self): """Is three floating-point values as a NumPy array. For crystallographic data, they are the dimensions of the unit cell in Angstroms. For non-crystallographic data, the elements of d are the elements of m times the spacing between samples in that dimension so that d[axis[0]] / m[axis[0]] is the spacing between samples in a column, d[axis[1]] / m[axis[1]] is the spacing between samples in a row, and d[axis[2]] / m[axis[2]] is the spacing between sections. By convention, EM data uses Angstroms for the spacing and optical data uses microns. Occupies bytes 41 - 52 (numbered from one) in the header. """ return self._array['d'][0] def _setd(self, value): self._array['d'][0] = value d = property(_getd, _setd) def _getangle(self): """Is three floating-point values as a NumPy array. They are the angles, in degrees, between the axes of the unit cell. For non-crystallographic data, these are all set to 90. Occupies bytes 53 - 64 (numbered from one) in the header. """ return self._array['angle'][0] def _setangle(self, value): self._array['angle'][0] = value angle = property(_getangle, _setangle) def _getaxis(self): """Is three integers as a NumPy array. The first is the axis (1 for x; 2 for y; 3 for z) that corresponds to columns in the file. The second is the axis that corresponds to rows in the file. The third is the axis that corresponds to sections in the file. Occupies bytes 65 - 76 (numbered from one) in the header. """ return self._array['axis'][0] def _setaxis(self, value): self._array['axis'][0] = value axis = property(_getaxis, _setaxis) def _getmmm1(self): """Is three floating-point values as a NumPy array. The first is the minimum density value. The second is the maximum density value. The third is the mean density value. For files using Priism's format, these statistics are for the data from the first wavelength. Occupies bytes 77 - 88 (numbered from one) in the header. """ return self._array['mmm1'][0] def _setmmm1(self, value): self._array['mmm1'][0] = value mmm1 = property(_getmmm1, _setmmm1) def _getnspg(self): """Is an integer to hold the space group for crystallographic data. For non-crystallographic data, the convention is to set the space group to zero, one, or 401. Priism's version of MRC uses zero. Image2000 and MRC 2014 use zero for single images or image sequences, one for cases where the images represent a volume, and 401 for volume stacks. Occupies bytes 89 - 92 (numbered from one) in the header. """ return self._array['nspg'][0] def _setnspg(self, value): self._array['nspg'][0] = value nspg = property(_getnspg, _setnspg) def _getnext(self): """Is the number of bytes in the extended header. Occupies bytes 93 - 96 (numbered from one) in the header. """ return self._array['next'][0] def _setnext(self, value): self._array['next'][0] = value next = property(_getnext, _setnext) def _getNumTitles(self): """Is an integer for the number of titles used. Up to 10 titles can be stored. Occupies bytes 221 - 224 (numbered from one) in the header. """ return self._array['NumTitles'][0] def _setNumTitles(self, value): self._array['NumTitles'][0] = value NumTitles = property(_getNumTitles, _setNumTitles) def _gettitle(self): """Are the titles as a ten element array where each element is an eighty character string. Occupies bytes 225 - 1024 (numbered from one) in the header. """ return ManagedTitleArray(self._array['title'][0]) def _settitle(self, value): a = ManagedTitleArray(self._array['title'][0]) if isStringLike(value): nper = self._array['title'][0].shape[1] if len(value) >= len(a) * nper: for i in range(0, len(a)): a[i] = value[i*80:(i+1)*80] else: for i in range(0, len(a)): a[i] = value elif hasattr(value, '__len__'): if len(value) == len(a): for i in range(0, len(a)): a[i] = value[i] else: raise ValueError('collection assigned to title does not have ' ' %d elements' % len(a)) else: raise TypeError('invalid type for assignment to title') title = property(_gettitle, _settitle) def getSpacing(self): """Return spacing between samples. By convention, the units for the spacing are microns for optical microscope data and Angstroms for electron microscope data. If the axis values in the header are invalid or a value in m in the header is zero, the spacing values are not well defined. Returns a three element NumPy array. The first value is the spacing in the direction set by axis[0], the second value is the spacing in the direction set by axis[1], and the third value is the spacing in the direction set by axis[2]. """ r = N.empty(3, dtype='f4') m = self.m d = self.d ax = self.axis for i in range(0, 3): j = ax[i] - 1 if (j < 0 or j > 2 or m[j] == 0): r[i] = 0.0 else: r[i] = d[j] / m[j] return r def setSpacing(self, d0, d1, d2): """Set spacing between samples. By convention, the units for the spacing are microns for optical microscope data and Angstroms for electron microscope data. If the axis values in the header are invalid or a value in m in the header is zero, the spacing values are not well defined. Positional parameters: d0 -- Is the spacing between samples in the direction set by axis[0]. d1 -- Is the spacing between samples in the direction set by axis[1]. d2 -- Is the spacing between samples in the direction set by axis[2]. """ m = self.m d = self.d ax = self.axis j = ax[0] - 1 if j >= 0 and j < 3: d[j] = d0 * m[j] j = ax[1] - 1 if j >= 0 and j < 3: d[j] = d1 * m[j] j = ax[2] - 1 if j >= 0 and j < 3: d[j] = d2 * m[j] def clearTitles(self): """Set the number of titles to zero and fill the titles with spaces.""" self.NumTitles = 0 self.title = ' ' * 80 def setTitle(self, s, i=-1, push=False, truncate=False): """Set a title in the MRC header. Positional parameters: s -- Is the character string for the title. If s is longer than 80 characters and truncate is False, a ValueError exception will be raised. Since no byte swapping is done for the titles in the header, s should be encoded in ASCII or another format that does not use multibyte characters. Keyword parameters: i -- Is the index of the title to set. If i is less than zero, the last title not in use will be set. If i is less than zero and all the titles are in use and push is False or i is greater than 9, a ValueError exception will be raised. push -- If True, i is less than zero, and all titles are in use, titles will be pushed down before assigning s to the last title. That will discard the first title and title[k] (for k greater than and equal to 0 and less than 9) will be title[k+1] from before the change. truncate -- If True, only use the first 80 characters from s. """ n = max(0, min(self.NumTitles, 10)) b = N.fromstring(s, dtype='i1') if b.shape[0] > 80: if not truncate: raise ValueError('Mrc only support title up to 80 characters') b = b[0:80] elif b.shape[0] < 80: # Pad with spaces to match what is done by Priism libraries. b = N.concatenate((b, 32 * N.ones(80 - b.shape[0], dtype='i1'))) if i < 0: i = n if n == 10 and push: for i in range(0, 9): self.title[i] = self.title[i + 1] i = 9 n = 9 if i > 9: raise ValueError('Mrc only support up to 10 titles (0<=i<10)') if i >= n: self.NumTitles = i + 1 self.title[i] = b class HdrPriism(HdrBase): """Represents a MRC header with the Priism extensions. Provides access to all parts of the header. The fields that are in common between the basic MRC format and Priism are described in HdrBase. The fields that are Priism extensions listed below. Some properties are provided to make it easier to use instances of this class interchangeably with instances of Hdr2014Priism, especially for retrieving values without modifying them. This class was added in version 0.1.0 of Mrc.py as included with Priism. The dynamically declared class it replaced had data attributes with the same names except that it did not have nblank, ntst, rms, and origin and it had a data attribute called type that was the the first two bytes of the nspg attribute in this class (the nspg attribute in that class class was only two bytes and was immediately after the type attribute). The blank attribute in that class was larger, spanning the nblank, ntst, and blank attributes in this class. That class did not have getSpacing(), setSpacing(), clearTitles(), setTitle(), index2zwt() or zwt2index functions. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = () def index2zwt(self, i, over=False): """Convert section index to 3D index in z, wavelength, and time. Positional parameters: i -- Is the section index. If less than zero, it is treated as a displacement from one past the end (i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). Keyword parameters: over -- If True, a value of i past the end will cause a ValueError exception to be raised. If False, a value of i past the end will lead to a value for the index in the slowest-varying dimension to be past the end. Return value: Returns a three element tuple of the zero-based indices for z, wavelength, and time. """ nw = max(1, self.NumWaves) nt = max(1, self.NumTimes) nz = max(1, self.Num[2] // (nw * nt)) seq = self.ImgSequence if seq < 0 or seq > 2: seq = 0 return index2zwt(i, nz, nw, nt, seq, over=over) def zwt2index(self, z, w, t, over=False): """Convert a 3D index in z, wavelength, and time to a section index. Positional parameters: z -- Is the index in z. If less than zero, it is treated as a displacement from one past the end i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). w -- Is the wavelength index. If less than zero, it is treated as a displacement from one past the end. t -- Is the time index. If less than zero, it is treated as a displacement from one past the end. Keyword parameters: over -- If True, a ValueError exception will be raised if any of z, w, or t are past the end in their respective dimesions. If False, a value for z, w, or t that is past the end will only result in a ValueError exception if that dimension is not the slowest-varying non-singleton dimension. Return value: Returns a zero-based index for the section. """ nw = max(1, self.NumWaves) nt = max(1, self.NumTimes) nz = max(1, self.Num[2] // (nw * nt)) seq = self.ImgSequence if seq < 0 or seq > 2: seq = 0 return zwt2index(z, w, t, nz, nw, nt, seq, over=over) def _getdvid(self): """Is a 16-bit integer that is a code for the originator of the data. Files from Priism and John Sedat's microscopes put 0xc0a0 (-16224 as a signed integer) in this value. Occupies bytes 97 - 98 (numbered from one) in the header. """ return self._array['dvid'][0] def _setdvid(self, value): self._array['dvid'][0] = value dvid = property(_getdvid, _setdvid) def _getnblank(self): """Is a 16-bit integer that is not currently reserved for any specific purpose. Occupies bytes 99 - 100 (numbered from one) in the header. """ return self._array['nblank'][0] def _setnblank(self, value): self._array['nblank'][0] = value nblank = property(_getnblank, _setnblank) def _getntst(self): """Is an integer used to store the starting time index, i.e. the index in a longer time series for the first time point in this dataset. Occupies bytes 101 - 104 (numbered from one) in the header. """ return self._array['ntst'][0] def _setntst(self, value): self._array['ntst'][0] = value ntst = property(_getntst, _setntst) def _getblank(self): """Is 24 bytes that is not currently reserved for any specific purpose. Priism and this implementation do not perform any byte-swapping on this data. If you store multi-byte quantities here, you will need to handle the byte-swapping. Occupies bytes 105 - 128 (numbered from one) in the header. """ return self._array['blank'][0] def _setblank(self, value): self._array['blank'][0] = value blank = property(_getblank, _setblank) def _getNumIntegers(self): """Is the number of 4-byte integers to store per section in the extended header. Occupies bytes 129 - 130 (numbered from one) in the header. """ return self._array['NumIntegers'][0] def _setNumIntegers(self, value): self._array['NumIntegers'][0] = value NumIntegers = property(_getNumIntegers, _setNumIntegers) def _getNumFloats(self): """Is the number of 4-byte floating-point values to store per section in the extended header. Occupies bytes 131 - 132 (numbered from one) in the header. """ return self._array['NumFloats'][0] def _setNumFloats(self, value): self._array['NumFloats'][0] = value NumFloats = property(_getNumFloats, _setNumFloats) def _getsub(self): """Is the number of different resolutions stored in the dataset. Occupies bytes 133 - 134 (numbered from one) in the header. """ return self._array['sub'][0] def _setsub(self, value): self._array['sub'][0] = value sub = property(_getsub, _setsub) def _getzfac(self): """For multiple resolutions, the number of z samples in a resolution will be the number of z samples in the next higher resolution divided by this integer and rounding up any remainder. Occupies bytes 135 - 136 (numbered from one) in the header. """ return self._array['zfac'][0] def _setzfac(self, value): self._array['zfac'][0] = value zfac = property(_getzfac, _setzfac) def _getmm2(self): """Is two floating-point values as a NumPy array. The first is the minimum density for the second wavelength, and the second is the maximum density for the second wavelength. Occupies bytes 137 - 144 (numbered from one) in the header. """ return self._array['mm2'][0] def _setmm2(self, value): self._array['mm2'][0] = value mm2 = property(_getmm2, _setmm2) def _getmm3(self): """Is two floating-point values as a NumPy array. The first is the minimum density for the third wavelength, and the second is the maximum density for the third wavelength. Occupies bytes 145 - 152 (numbered from one) in the header. """ return self._array['mm3'][0] def _setmm3(self, value): self._array['mm3'][0] = value mm3 = property(_getmm3, _setmm3) def _getmm4(self): """Is two floating-point values as a NumPy array. The first is the minimum density for the fourth wavelength, and the second is the maximum density for the fourth wavelength. Occupies bytes 153 - 160 (numbered from one) in the header. """ return self._array['mm4'][0] def _setmm4(self, value): self._array['mm4'][0] = value mm4 = property(_getmm4, _setmm4) def _getImageType(self): """Is a 16-bit integer which is a code for the type of data in the file. http://msg.ucsf.edu/IVE/IVE4_HTML/IM_ref2.html#ImageTypes describes the types that Priism defines and how the n1, n2, v1, and v2 fields are used for each type. Occupies bytes 161 - 162 (numbered from one) in the header. """ return self._array['ImageType'][0] def _setImageType(self, value): self._array['ImageType'][0] = value ImageType = property(_getImageType, _setImageType) def _getLensNum(self): """For optical data, this 16-bit integer is used to indicate which microscope configuration was used when the data was collected. Each microscope system would have its own table of configurations and corresponding integer codes. Occupies bytes 163 - 164 (numbered from one) in the header. """ return self._array['LensNum'][0] def _setLensNum(self, value): self._array['LensNum'][0] = value LensNum = property(_getLensNum, _setLensNum) def _getn1(self): """Is a 16-bit integer whose interpretation depends on the value for ImageType. Occupies bytes 165 - 166 (numbered from one) in the header. """ return self._array['n1'][0] def _setn1(self, value): self._array['n1'][0] = value n1 = property(_getn1, _setn1) def _getn2(self): """Is a 16-bit integer whose interpretation depends on the value for ImageType. Occupies bytes 167 - 168 (numbered from one) in the header. """ return self._array['n2'][0] def _setn2(self, value): self._array['n2'][0] = value n2 = property(_getn2, _setn2) def _getv1(self): """Is a 16-bit integer which is used to store a floating point value, f, as round_to_nearest(f * 100.0). What f is depends on the value for ImageType. This Python implementation leaves the conversion between f and v1 to the caller. Occupies bytes 169 - 170 (numbered from one) in the header. """ return self._array['v1'][0] def _setv1(self, value): self._array['v1'][0] = value v1 = property(_getv1, _setv1) def _getv2(self): """Is a 16-bit integer which is used to store a floating point value, f, as round_to_nearest(f * 100.0). What f is depends on the value for ImageType. This Python implementation leaves the conversion between f and v2 to the caller. Occupies bytes 171 - 172 (numbered from one) in the header. """ return self._array['v2'][0] def _setv2(self, value): self._array['v2'][0] = value v2 = property(_getv2, _setv2) def _getmm5(self): """Is two floating-point values as a NumPy array. The first is the minimum density for the fifth wavelength, and the second is the maximum density for the fifth wavelength. Occupies bytes 173 - 180 (numbered from one) in the header. """ return self._array['mm5'][0] def _setmm5(self, value): self._array['mm5'][0] = value mm5 = property(_getmm5, _setmm5) def _getNumTimes(self): """Is an integer for the number of time points stored in the file. Occupies bytes 181 - 182 (numbered from one) in the header. """ return self._array['NumTimes'][0] def _setNumTimes(self, value): self._array['NumTimes'][0] = value NumTimes = property(_getNumTimes, _setNumTimes) def _getImgSequence(self): """Is an integer code for how the sections are arranged into z, wavelength, and time points. Three values are undersood by Priism: 0: Z varies fastest, followed by time, followed by wavelength 1: wavelength varies fastest, followed by z, followed by time 2: z varies fastest, followed by wavelength, followed by time Occupies bytes 183 - 184 (numbered from one) in the header. """ return self._array['ImgSequence'][0] def _setImgSequence(self, value): self._array['ImgSequence'][0] = value ImgSequence = property(_getImgSequence, _setImgSequence) def _gettilt(self): """Is three floating-point values as a NumPy array. The three values are a trio of rotation angles in degrees. For image windows, Priism uses those angles to rotate the coordinates of overlayed objects to the coordinates aligned to the image axes. The transformation uses the first angle as rotation about the original +x axis of the objects with a positive angle rotating the +y axis towards the +z axis. The second angle rotates about the +y axis from the first rotation with a positive angle rotating the +z axis towards the +x axis. The third angle rotates about +z axis from the second rotation with a positive angle rotating the +x axis towards the +y axis. Occupies bytes 185 - 196 (numbered from one) in the header. """ return self._array['tilt'][0] def _settilt(self, value): self._array['tilt'][0] = value tilt = property(_gettilt, _settilt) def _getNumWaves(self): """Is an integer for the number of wavelengths stored in the file. The wavelength dimension is handled differently than z or time in that other header values store per-wavelength metadata. That storage allows for information about five or less wavelengths. Because of that limitation and because many Priism applications assume a maximum of five wavelengths, you would normally restrict the number of wavelengths stored to be five or less. Occupies bytes 197 - 198 (numbered from one) in the header. """ return self._array['NumWaves'][0] def _setNumWaves(self, value): self._array['NumWaves'][0] = value NumWaves = property(_getNumWaves, _setNumWaves) def _getwave(self): """Is five 16-bit integers as a NumPy array. They are the wavelengths for the emitted or transmitted light in nanometers rounded to the nearest integer. For a broad passband, you would normally use some measure of the center of the passband as the wavelength to store in the header. Occupies bytes 199 - 208 (numbered from one) in the header. """ return self._array['wave'][0] def _setwave(self, value): self._array['wave'][0] = value wave = property(_getwave, _setwave) def _getzxy0(self): """Is three floating-point values as a NumPy array. The three values specify an origin to use in coordinate transformations. The first value is the z coordinate, the second value is the x coordinate, and the third value is the y coordinate. The units for each are, by convention, the same as used for the spacing between samples: Angstroms for EM data and microns for optical data. Occupies bytes 209 - 220 (numbered from one) in the header. """ return self._array['zxy0'][0] def _setzxy0(self, value): self._array['zxy0'][0] = value zxy0 = property(_getzxy0, _setzxy0) def _getrms(self): """Is part of the MRC 2014 format but is not in the Priism format. Accesses will return -1. Attempts to set this to something other than -1 will generate an AttributeError exception. """ return -1.0 def _setrms(self, value): if value != -1.0: raise AttributeError('Priism MRC header does not store RMS') rms = property(_getrms, _setrms) def _getorigin(self): """Is three floating-point values that are part of the MRC 2014 format. The Priism format zxy0 field has the same purpose but is in a different part of the header and has a different ordering for the coordinates. Reorder and return those values when accessed. When set, reorder the values and set the fields used by the Priism format. """ return ReorderedArray(self._array['zxy0'][0], (1, 2, 0)) def _setorigin(self, value): self._array['zxy0'][0] = (value[2], value[0], value[1]) origin = property(_getorigin, _setorigin, ) class Hdr2014(HdrBase): """Represents a MRC 2014 header without extensions. Only provides access to the parts of the header that are in the MRC 2014 specification. The fields that are part of the original MRC formulation are described in HdrBase. The additions for MRC 2014 are listed below. This class was added in version 0.1.0 of Mrc.py as included with Priism. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = () def index2zwt(self, i, over=False): """Convert section index to 3D index in z, wavelength, and time. Positional parameters: i -- Is the section index. If less than zero, it is treated as a displacement from one past the end (i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). Keyword parameters: over -- If True, a value of i past the end will cause a ValueError exception to be raised. If False, a value of i past the end will lead to a value for the index in the slowest-varying dimension to be past the end. Return value: Returns a three element tuple of the zero-based indices for z, wavelength, and time. """ if self.nspg == 401: if self.m[2] > 0: nz = self.m[2] nt = max(1, self.Num[2] // nz) else: nz = 1 nt = max(self.Num[2], 1) else: nz = max(self.Num[2], 1) nt = 1 nw = 1 # All the available image sequence types have z varying faster # than time; use the one with wavelength varying fastest since # any overflow will not go into the wavelength dimension. return index2zwt(i, nz, nw, nt, 1, over=over) def zwt2index(self, z, w, t, over=False): """Convert a 3D index in z, wavelength, and time to a section index. Positional parameters: z -- Is the index in z. If less than zero, it is treated as a displacement from one past the end i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). w -- Is the wavelength index. If less than zero, it is treated as a displacement from one past the end. t -- Is the time index. If less than zero, it is treated as a displacement from one past the end. Keyword parameters: over -- If True, a ValueError exception will be raised if any of z, w, or t are past the end in their respective dimesions. If False, a value for z, w, or t that is past the end will only result in a ValueError exception if that dimension is not the slowest-varying non-singleton dimension. Return value: Returns a zero-based index for the section. """ if self.nspg == 401: if self.m[2] > 0: nz = self.m[2] nt = max(1, self.Num[2] // nz) else: nz = 1 nt = max(self.Num[2], 1) else: nz = max(self.Num[2], 1) nt = 1 nw = 1 return zwt2index(z, w, t, nz, nw, nt, 1, over=over) def _getexttyp(self): """Is four bytes treated a four character string that is a code for the layout of the extended header. This implementation understands three different values, all encoded in ASCII, for this field: 'MRC0', 'CCP4' (treated as a synonym for 'MRC0'), and 'AGAR'. This field was introduced by the MRC 2014 standard and was not part of the earlier Image 2000 standard. Occupies bytes 105 - 108 (numbered from one) in the header. """ return self._array['exttyp'][0] def _setexttyp(self, value): self._array['exttyp'][0] = value exttyp = property(_getexttyp, _setexttyp) def _getnversion(self): """Is a 4-byte integer which stores the version number of the MRC format used by this file. The version number is 10 times the Gregorian year when the specification was issued plus a zero-based (up to 9) version number within a year. Files which use the MRC 2014 format would have 20140 in this field. This field was introduced by the MRC 2014 standard and was not part of the earlier Image 2000 standard. Occupies bytes 109 - 112 (numbered from one) in the header. """ return self._array['nversion'][0] def _setnversion(self, value): self._array['nversion'][0] = value nversion = property(_getnversion, _setnversion) def _getorigin(self): """Is three floating-point values as a NumPy array. The three values specify an origin to use in coordinate transformations. The first value is the x coordinate, the second value is the y coordinate, and the third value is the z coordinate. The units for each are, by convention, the same as used for the spacing between samples: Angstroms for EM data and microns for optical data. Occupies bytes 197 - 208 (numbered from one) in the header. """ return self._array['origin'][0] def _setorigin(self, value): self._array['origin'][0] = value origin = property(_getorigin, _setorigin) def _getmap(self): """Is four bytes treated as a four character string that identifies this file as an MRC file. The MRC 2014 and Image 2000 standards specify that this field should be set to 'MAP ' encoded in ASCII. Occupies bytes 209 - 212 (numbered from one) in the header. """ return self._array['map'][0] def _setmap(self, value): self._array['map'][0] = value map = property(_getmap, _setmap) def _getmachst(self): """Is four bytes that are a code for how floating-point, complex, integer, and character values are stored. In practice, two combinations of values are used: 0x44 and 0x41 in the first two bytes (68 and 65 in decimal; the MRC 2014 documentation has 0x44 in the second byte as well) and unspecified values (typically zero) in the second two bytes for little-endian and 0x11 (17 in decimal) in the first two bytes and unspecified values (typically zero) in the second two bytes for big-endian. Occupies bytes 213 - 216 (numbered from one) in the header. """ return self._array['machst'][0] def _setmachst(self, value): self._array['machst'][0] = value machst = property(_getmachst, _setmachst) def _getrms(self): """Is a floating-point value for the RMS deviation of the densities from the mean density. If the RMS deviation has not been computed, the convention is to put a value less than zero in this field. Occupies bytes 217 - 220 (numbered from one) in the header. """ return self._array['rms'][0] def _setrms(self, value): self._array['rms'][0] = value rms = property(_getrms, _setrms) class Hdr2014Priism(Hdr2014): """Represents a MRC 2014 header with fields from Priism where they do not conflict with the MRC 2014 standard. Provides access to all parts of the header. The fields that are part of the original MRC formulation are described in HdrBase. The fields specific to MRC 2014 are described in Hdr2014. The extensions from Priism are listed below. Some properties are provided to make it easier to use instances of this class interchangeably with instances of HdrPriism, especially for retrieving values without modifying them. This class was added in version 0.1.0 of Mrc.py as included with Priism. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = () def _getextra0(self): """Is 4 bytes that is not currently reserved for any specific purpose. Priism and this implementation do not perform any byte-swapping on this data. If you store multi-byte quantities here, you will need to handle the byte-swapping. Occupies bytes 97 - 100 (numbered from one) in the header. """ return self._array['extra0'][0] def _setextra0(self, value): self._array['extra0'][0] = value extra0 = property(_getextra0, _setextra0) def _getntst(self): """Is an integer used to store the starting time index, i.e. the index in a longer time series for the first time point in this dataset. Occupies bytes 101 - 104 (numbered from one) in the header. """ return self._array['ntst'][0] def _setntst(self, value): self._array['ntst'][0] = value ntst = property(_getntst, _setntst) def _getextra1(self): """Is 16 bytes that is not currently reserved for any specific purpose. Priism and this implementation do not perform any byte-swapping on this data. If you store multi-byte quantities here, you will need to handle the byte-swapping. Occupies bytes 113 - 128 (numbered from one) in the header. """ return self._array['extra1'][0] def _setextra1(self, value): self._array['extra1'][0] = value extra1 = property(_getextra1, _setextra1) def _getNumIntegers(self): """Is the number of 4-byte integers to store per section in the extended header. Occupies bytes 129 - 130 (numbered from one) in the header. """ return self._array['NumIntegers'][0] def _setNumIntegers(self, value): self._array['NumIntegers'][0] = value NumIntegers = property(_getNumIntegers, _setNumIntegers) def _getNumFloats(self): """Is the number of 4-byte floating-point values to store per section in the extended header. Occupies bytes 131 - 132 (numbered from one) in the header. """ return self._array['NumFloats'][0] def _setNumFloats(self, value): self._array['NumFloats'][0] = value NumFloats = property(_getNumFloats, _setNumFloats) def _getsub(self): """Is the number of different resolutions stored in the dataset. Occupies bytes 133 - 134 (numbered from one) in the header. """ return self._array['sub'][0] def _setsub(self, value): self._array['sub'][0] = value sub = property(_getsub, _setsub) def _getzfac(self): """For multiple resolutions, the number of z samples in a resolution will be the number of z samples in the next higher resolution divided by this integer and rounding up any remainder. Occupies bytes 135 - 136 (numbered from one) in the header. """ return self._array['zfac'][0] def _setzfac(self, value): self._array['zfac'][0] = value zfac = property(_getzfac, _setzfac) def _getextra2(self): """Is 24 bytes that is not currently reserved for any specific purpose. Priism and this implementation do not perform any byte-swapping on this data. If you store multi-byte quantities here, you will need to handle the byte-swapping. Occupies bytes 137 - 160 (numbered from one) in the header. """ return self._array['extra2'][0] def _setextra2(self, value): self._array['extra2'][0] = value extra2 = property(_getextra2, _setextra2) def _getImageType(self): """Is a 16-bit integer which is a code for the type of data in the file. http://msg.ucsf.edu/IVE/IVE4_HTML/IM_ref2.html#ImageTypes describes the types that Priism defines and how the n1, n2, v1, and v2 fields are used for each type. Occupies bytes 161 - 162 (numbered from one) in the header. """ return self._array['ImageType'][0] def _setImageType(self, value): self._array['ImageType'][0] = value ImageType = property(_getImageType, _setImageType) def _getLensNum(self): """For optical data, this 16-bit integer is used to indicate which microscope configuration was used when the data was collected. Each microscope system would have its own table of configurations and corresponding integer codes. Occupies bytes 163 - 164 (numbered from one) in the header. """ return self._array['LensNum'][0] def _setLensNum(self, value): self._array['LensNum'][0] = value LensNum = property(_getLensNum, _setLensNum) def _getn1(self): """Is a 16-bit integer whose interpretation depends on the value for ImageType. Occupies bytes 165 - 166 (numbered from one) in the header. """ return self._array['n1'][0] def _setn1(self, value): self._array['n1'][0] = value n1 = property(_getn1, _setn1) def _getn2(self): """Is a 16-bit integer whose interpretation depends on the value for ImageType. Occupies bytes 167 - 168 (numbered from one) in the header. """ return self._array['n2'][0] def _setn2(self, value): self._array['n2'][0] = value n2 = property(_getn2, _setn2) def _getv1(self): """Is a 16-bit integer which is used to store a floating point value, f, as round_to_nearest(f * 100.0). What f is depends on the value for ImageType. This Python implementation leaves the conversion between f and v1 to the caller. Occupies bytes 169 - 170 (numbered from one) in the header. """ return self._array['v1'][0] def _setv1(self, value): self._array['v1'][0] = value v1 = property(_getv1, _setv1) def _getv2(self): """Is a 16-bit integer which is used to store a floating point value, f, as round_to_nearest(f * 100.0). What f is depends on the value for ImageType. This Python implementation leaves the conversion between f and v2 to the caller. Occupies bytes 171 - 172 (numbered from one) in the header. """ return self._array['v2'][0] def _setv2(self, value): self._array['v2'][0] = value v2 = property(_getv2, _setv2) def _getextra3(self): """Is 12 bytes that is not currently reserved for any specific purpose. Priism and this implementation do not perform any byte-swapping on this data. If you store multi-byte quantities here, you will need to handle the byte-swapping. Occupies bytes 173 - 184 (numbered from one) in the header. """ return self._array['extra3'][0] def _setextra3(self, value): self._array['extra3'][0] = value extra3 = property(_getextra3, _setextra3) def _gettilt(self): """Is three floating-point values as a NumPy array. The three values are a trio of rotation angles in degrees. For image windows, Priism uses those angles to rotate the coordinates of overlayed objects to the coordinates aligned to the image axes. The transformation uses the first angle as rotation about the original +x axis of the objects with a positive angle rotating the +y axis towards the +z axis. The second angle rotates about the +y axis from the first rotation with a positive angle rotating the +z axis towards the +x axis. The third angle rotates about +z axis from the second rotation with a positive angle rotating the +x axis towards the +y axis. Occupies bytes 185 - 196 (numbered from one) in the header. """ return self._array['tilt'][0] def _settilt(self, value): self._array['tilt'][0] = value tilt = property(_gettilt, _settilt) def _getmm2(self): """Is not part of the MRC 2014 format with Priism extensions. Is part of the Priism format. On access, will return a NumPy array with two zero values. Any attempt to set to something which is not two zero values will generate an AttributeError exception. """ return N.zeros(2, dtype='f4') def _setmm2(self, value): if value[0] != 0.0 or value[1] != 0.0: raise AttributeError('MRC 2014 header does not store minimum ' 'and maximum values for wavelengths past ' 'the first') mm2 = property(_getmm2, _setmm2) def _getmm3(self): """Is not part of the MRC 2014 format with Priism extensions. Is part of the Priism format. On access, will return a NumPy array with two zero values. Any attempt to set to something which is not two zero values will generate an AttributeError exception. """ return N.zeros(2, dtype='f4') def _setmm3(self, value): if value[0] != 0.0 or value[1] != 0.0: raise AttributeError('MRC 2014 header does not store minimum ' 'and maximum values for wavelengths past ' 'the first') mm3 = property(_getmm3, _setmm3) def _getmm4(self): """Is not part of the MRC 2014 format with Priism extensions. Is part of the Priism format. On access, will return a NumPy array with two zero values. Any attempt to set to something which is not two zero values will generate an AttributeError exception. """ return N.zeros(2, dtype='f4') def _setmm4(self, value): if value[0] != 0.0 or value[1] != 0.0: raise AttributeError('MRC 2014 header does not store minimum ' 'and maximum values for wavelengths past ' 'the first') mm4 = property(_getmm4, _setmm4) def _getmm5(self): """Is not part of the MRC 2014 format with Priism extensions. Is part of the Priism format. On access, will return a NumPy array with two zero values. Any attempt to set to something which is not two zero values will generate an AttributeError exception. """ return N.zeros(2, dtype='f4') def _setmm5(self, value): if value[0] != 0.0 or value[1] != 0.0: raise AttributeError('MRC 2014 header does not store minimum ' 'and maximum values for wavelengths past ' 'the first') mm5 = property(_getmm5, _setmm5) def _getNumTimes(self): """The MRC 2014 format store the number of volumes implicitly and uses a space group of 401 to indicate that multiple volumes may be present. Handle Priism's notion of number of time points with the MRC 2014 mechanism. """ if self._array['nspg'][0] == 401: # The file represents a volume stack. The third component # of m is the number of z slices per volume. if self._array['m'][0][2] > 0: return self._array['Num'][0][2] // self._array['m'][0][2] # The number of z slices per volume is invalid, assume one # slice per volume. return self._array['Num'][0][2] return 1 def _setNumTimes(self, value): if value > 0: if self._array['nspg'][0] == 401: oldnz = self._array['m'][0][2] else: if value > 1: self._array['nspg'][0] = 401 oldnz = self._array['Num'][0][2] newnz = self._array['Num'][0][2] // value # Preserve pixel spacing. if oldnz != 0: self._array['d'][0][2] *= newnz / float(oldnz) else: self._array['d'][0][2] = 0.0 self._array['m'][0][2] = newnz else: raise ValueError('Attempts to set the number of time points to ' 'a nonpositive value') NumTimes = property(_getNumTimes, _setNumTimes) def _getImgSequence(self): """Is not part of the MRC 2014 format with Priism extensions. All MRC 2014 files implicitly have z varying faster than time. Return zero on accesses. Any attempts to set the value to something other than zero will generate an AttributeError exception. """ # This is Priism's code for the ZTW ordering. All of Priism's # defined orderings are the equivalent for this purpose, though, # since the number of wavelengths is one and z varies faster than # time. return 0 def _setImgSequence(self, value): if value != 0: raise AttributeError('The MRC 2014 header does not store the ' 'image sequence code') ImgSequence = property(_getImgSequence, _setImgSequence) def _getNumWaves(self): """Is not part of the MRC 2014 format with Priism extensions. All such files implicitly have a single wavelength. Return one for all accesses. Any attempts to set the value to something other than one will generate an AttributeError exception. """ return 1 def _setNumWaves(self, value): if value != 1: raise AttributeError('MRC 2014 format does not store the ' 'number of wavelengths') NumWaves = property(_getNumWaves, _setNumWaves) def _getwave(self): """The MRC 2014 format with Priism extensions does not store wavelength values. Return a NumPy array with five zeros for any access. Any attempt to set the wavelength values to something other than zeros will generate an AttributeError exception. """ return N.zeros(5, dtype='i2') def _setwave(self, value): if (value[0] != 0 or value[1] != 0 or value[2] != 0 or value[3] != 0 or value[4] != 0): raise AttributeError('MRC 2014 header does not store ' 'wavelength values') wave = property(_getwave, _setwave) def _getzxy0(self): """Is three floating-point values that are part of the Priism format. The MRC 2014 format origin field has the same purpose but is in a different part of the header and has a different ordering for the coordinates. Reorder and return those values when accessed. When set, reorder the values and set the fields used by the MRC 2014 format. """ return ReorderedArray(self._array['origin'][0], (2, 0, 1)) def _setzxy0(self, value): self._array['origin'][0] = (value[1], value[2], value[0]) zxy0 = property(_getzxy0, _setzxy0) ########################################################################### ########################################################################### ########################################################################### ########################################################################### class ManagedTitleArray(object): """Since strings in the header are represented by NumPy arrays of signed one byte integers, provide a more convenient interface so that a caller can work with Python strings. This class represents an array of strings from the header or extended header. This class was added in version 0.1.0 of Mrc.py as included with Priism. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = ('_array',) def __init__(self, array): """Initializes a ManagedTitleArray. Positional parameters: array -- Is a NumPy two-dimensional array of one-byte integers. The first dimension is the number of strings; the second dimension represents the characters in the string. """ self._array = array def __repr__(self): s = 'ManagedTitleArray(' + repr(self._array) + ')' return s def __str__(self): s = '[' for i in range(0, self._array.shape[0] - 1): s = s + "'" + self._array[i].tostring() + "' ," if self._array.shape[0] > 0: s = s + "'" + self._array[-1].tostring() + "']" else: s = s + ']' return s def __len__(self): return self._array.shape[0] def __getitem__(self, key): if isinstance(key, slice): return [self._array[i].tostring() for i in range(*key.indices(self._array.shape[0]))] else: return self._array[key].tostring() def __setitem__(self, key, value): if isinstance(key, slice): if isStringLike(value): for i in range(*key.indices(self._array.shape[0])): self[i] = value else: r = range(*key.indices(self._array.shape[0])) if len(r) != len(value): raise ValueError('Number of elements on right hand side does ' 'not match number on left') j = 0 for i in r: self[i] = value[j] j += 1 else: b = N.fromstring(value, dtype='i1') if b.shape[0] >= self._array.shape[1]: self._array[key][:] = b[0:self._array.shape[1]] else: # Pad with spaces. self._array[key][:] = N.concatenate( (b, 32 * N.ones(self._array.shape[1] - b.shape[0], dtype='i1'))) ########################################################################### ########################################################################### ########################################################################### ########################################################################### class ReorderedArray(object): """Provide an interface that acts like a reordered view for the first dimension of a NumPy array. This class was added in version 0.1.0 of Mrc.py as included with Priism. """ # Use __slots__ so any assignments to unspecified attributes or # properties will give an AttributeError exception. __slots__ = ('_array', '_indices') def __init__(self, array, indices): """Initializes a ReorderedArray. Positional parameters: array -- Is a NumPy array with at least one dimension. indices -- Is an iterable with the new indices (from first to last) to use for first dimension of the array. The length of indices should not exceed the size of the first dimension of array. """ self._array = array self._indices = indices def __repr__(self): s = ('ReorderedArray(' + repr(self._array) + ',' + repr(self._indices) + ')') return s def __str__(self): s = '[' for i in range(0, len(self._indices) - 1): s = s + str(self._array[self._indices[i]]) + ', ' if len(self._indices) > 0: s = s + str(self._array[self._indices[-1]]) + ']' else: s = s + ']' return s def __len__(self): return len(self._indices) def __getitem__(self, key): if isinstance(key, slice): return [self._array[self._indices[i]] for i in range(*key.indices(self._array.shape[0]))] else: return self._array[self._indices[key]] def __setitem__(self, key, value): if isinstance(key, slice): r = range(*key.indices(self._array.shape[0])) if hasattr(value, '__len__'): if len(r) != len(value): raise ValueError('Number of elements on right hand side ' 'does not match number on left') j = 0 for i in r: self[self._indices[i]] = value[j] j += 1 else: for i in r: self[self._indices[i]] = value else: self._array[self._indices[key]] = value ########################################################################### ########################################################################### ########################################################################### ########################################################################### def minExtHdrSize(nSecs, bytesPerSec): """Return the smallest multiple of 1024 capable of holding the extended header data. Positional parameters: nSecs -- Is the number of sections of extended header data. It is assumed to be a non-negative value. bytesPerSec -- Is the number of bytes per section to store. It is assumed to be a non-negative value. """ t = nSecs * bytesPerSec r = t % 1024 if r != 0: t += 1024 - r return t def MrcMode2dtype(mode): """Return a NumPy dtype equivalent to a MRC pixel type code. Raises a RuntimeError exception if the given mode is not known to this implementation. Raises a NotImplementedError is mode is part of the Priism or MRC 2014 specifications but is not handled by this implementation. Version 0.1.0 of Mrc.py as included with Priism changed the return value from a Python type to a NumPy dtype. Positional parameters: mode -- Is the integer code for how sample values are represented, i.e. the value of the 'PixelType' field in the header. """ if mode == 0: dt = N.dtype('i1') elif mode == 1: dt = N.dtype('i2') elif mode == 2: dt = N.dtype('f4') elif mode == 3: dt = N.dtype({'names':['real', 'imag'], 'formats':['i2', 'i2']}) elif mode == 4: dt = N.dtype('c8') elif mode == 5: dt = N.dtype('i2') elif mode == 6: dt = N.dtype('u2') elif mode == 7: dt = N.dtype('i4') elif mode == 101: raise NotImplementedError('Mrc.py does not handle the unsigned ' '4-bit data type') else: raise RuntimeError('Unknown pixel type code, %d' % mode) return dt def dtype2MrcMode(dtype): """Return the MRC sample format code number equivalent to a Python type or NumPy dtype. Positional parameters: dtype -- Is the Python type or NumPy dtype used for each sample. Return value: Returns an integer for the field labeled 'PixelType' (bytes 13 - 16) in the MRC header. If dtype is not equivalent to one of the types supported by the MRC format, a ValueError exception will be raised. """ hastype = hasattr(dtype, 'type') if dtype == N.int8 or (hastype and dtype.type == N.int8): return 0 if dtype == N.int16 or (hastype and dtype.type == N.int16): return 1 if dtype == N.float32 or (hastype and dtype.type == N.float32): return 2 if dtype == N.complex64 or (hastype and dtype.type == N.complex64): return 4 if dtype == N.uint16 or (hastype and dtype.type == N.uint16): return 6 if dtype == N.int32 or (hastype and dtype.type == N.int32): return 7 if (hasattr(dtype, 'fields') and dtype.fields is not None and len(dtype.fields) == 2): fields_ok = 0 for k in dtype.fields: if dtype.fields[k][0] == N.int16: if dtype.fields[k][1] == 0: fields_ok |= 1 elif dtype.fields[k][1] == 2: fields_ok |= 2 if fields_ok == 3: return 3 if hasattr(dtype, 'name'): name = dtype.name else: name = str(dtype) raise ValueError('MRC does not support %s' % name) def shapeFromHdr(hdr, verbose=0): """Return a tuple of array dimensions equivalent to the sizes set in a MRC header. Positional parameters: hdr -- Is the MRC header to use. It is expected to be a header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Non-positive values for the number of wavelengths or time points in the header will be treated as if they were equal to one. Negative values in the header for the number of x samples, number of y samples, or number of sections will be treated as if they were zero. Keyword parameters: verbose -- If true, a string giving the ordering of the dimensions from slowest to fastest will be printed. Each dimension will be represented by a single letter, 'x', 'y', 'z', 'w', or 't', and those letters will be separated by commas. Return value: Returns a tuple of integers which are the size for each dimension, from slowest to fastest varying, specified by the MRC header. """ zOrder = hdr.ImgSequence # , 'Image sequence. 0=ZTW, 1=WZT, 2=ZWT. '), if zOrder < 0 or zOrder > 2: # The value is invalid; use the default, ZTW, instead. zOrder = 0 nt, nw = hdr.NumTimes, hdr.NumWaves nx, ny, nsecs = hdr.Num if nt <= 0: nt=1 if nw <= 0: nw=1 if nx < 0: nx = 0 if ny < 0: ny = 0 if nsecs < 0: nsecs = 0 nz = nsecs // (nt * nw) if nt == nw == 1: shape = (nz, ny, nx) orderLetters = 'zyx' elif nz == 1 == nw: shape = (nt, ny, nx) orderLetters = 'tyx' elif nt == 1 or nw == 1: if zOrder == 0 or zOrder == 2: nn = nt if nt == 1: nn = nw orderLetters = 'wyx' else: orderLetters = 'tyx' shape = (nn, nz, ny, nx) else: # if zOrder == 1: if nt == 1: shape = (nz, nw, ny, nx) orderLetters = 'zwyx' else: shape = (nt, nz, ny, nx) orderLetters = 'tzyx' else: # both nt and nw > 1 if zOrder == 0: shape = (nw, nt, nz, ny, nx) orderLetters = 'wtzyx' elif zOrder == 1: shape = (nt, nz, nw, ny, nx) orderLetters = 'tzwyx' else: # zOrder == 2: shape = (nt, nw, nz, ny, nx) orderLetters = 'twzyx' if verbose: print(','.join(orderLetters)) return shape def implement_hdr(hdrArray, hasMap=False): """Return a HdrPriism or Hdr2014Priism instance to wrap the given NumPy structured array. If h is an object returned by this function, it can be used in statements like h.d = (1,2,3) or h.LensNum = 13 to modify values in the header or in statements like shape = (h.Num[2], h.Num[1], h.Num[0]) or mean = h.mmm1[2] to retrieve values from the header. The documentation for HdrBase and HdrPriism describes the fields available in a HdrPriism instance. The documentation for HdrBase, Hdr2014, and Hdr2014Priism describes the fields available in a Hdr2014Priism instance. The return value also has convenience methods for the spacing between samples and for the titles in the header. Those are described in the documentation for HdrBase. To get the original NumPy structured array from the return value, use h._array Positional parameters: hdrArray -- Is a MRC header as a NumPy structured array with one element. The dtype of the array should be numpy.dtype(mrcHdr_dtype) or numpy.dtype(mrc2014Hdr_dtype). Keyword parameters: hasMap --- If True, return an instance of the Hdr2014Priism class. If False, return an instance of HdrPriism class. The hasMap keyword was added in version 0.1.0 of Mrc.py as included with Priism. Return value: If hasMap is False, the return value is an instance of the HdrPriism class. If hasMap is True, the return value is an instance of the Hdr2014Priism class. Version 0.1.0 of Mrc.py as included with Priism changed the return value from an instance of a class defined within implement_hdr() to an instance of HdrPriism or Hdr2014Priism. """ if hasMap: return Hdr2014Priism(hdrArray) return HdrPriism(hdrArray) def makeHdrArray(buffer=None, makeWeak=True): """Create a NumPy structured array for the header and wrap it for easy access to the header fields. Keyword parameters: buffer -- If not None, the structured array will be overlayed on top of the first 1024 bytes of buffer. Typically, buffer would be a NumPy array of unsigned 8-bit integers read from the start of a MRC file. makeWeak -- Only has an effect if buffer is not None. In that case, the returned object will be marked as a weak reference when makeWeak is True. The makeWeak keyword was added in version 0.1.0 of Mrc.py as included with Priism. Return value: Returns an object representing the header. If the header was not generated from an existing buffer, the header fields have not been initialized. The documentation for the return value of implement_hdr() describes how the returned object may be used. The NumPy structured array embedded as the _array attribute of the returned object either has a NumPy dtype of numpy.dtype(mrcHdr_dtype) or numpy.dtype(mrc2014Hdr_dtype). """ if buffer is not None: h=buffer if hdrHasMapField(buffer): h.dtype = mrc2014Hdr_dtype hasmap = True else: h.dtype = mrcHdr_dtype hasmap = False if makeWeak: h = weakref.proxy(h) else: h = N.recarray(1, mrcHdr_dtype) hasmap = False return implement_hdr(h, hasMap=hasmap) def hdrInfo(hdr): """Print a subset of information from a MRC header. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. """ shape = hdr.Num[::-1] nz = shape[0] numInts = max(0, hdr.NumIntegers) numFloats = max(0, hdr.NumFloats) print('width: %s' % str(shape[2])) print('height: %s' % str(shape[1])) print('# total slices: %s' % str(shape[0])) nt, nw = hdr.NumTimes, hdr.NumWaves if nt <= 0 or nw <= 0: print(' ** ERROR ** : NumTimes or NumWaves is not positive') print('NumTimes: %s' % str(nt)) print('NumWaves: %s' % str(nw)) else: if nt == 1 and nw == 1: print() elif nw == 1: print(' (%d times for %d zsecs)' % (nt, nz//nt)) elif nt == 1: print(' (%d waves in %d zsecs)' % (nw, nz//nw)) else: print(' (%d times for %d waves in %d zsecs)'% (nt, nw, nz // (nw * nt))) if nt != 1 or nw != 1: print('# slice order: %d (0,1,2 = (ZTW or WZT or ZWT)' % hdr.ImgSequence) d = hdr.getSpacing() print('pixel width x (um): %s' % str(d[0])) print('pixel width y (um): %s' % str(d[1])) print('pixel height (um): %s' % str(d[2])) print('# wavelengths: %s' % str(nw)) print(' wavelength 1 (nm): %s' % str(hdr.wave[0])) print(' intensity min/max/mean: %s %s %s' % (str(hdr.mmm1[0]), str(hdr.mmm1[1]), str(hdr.mmm1[2]))) if nw > 1: print(' wavelength 2 (nm): %s' % str(hdr.wave[1])) print(' intensity min/max: %s %s' % (str(hdr.mm2[0]), str(hdr.mm2[1]))) if nw > 2: print(' wavelength 3 (nm): %s' % str(hdr.wave[2])) print(' intensity min/max: %s %s' % (str(hdr.mm3[0]), str(hdr.mm3[1]))) if nw > 3: print(' wavelength 4 (nm): %s' % str(hdr.wave[3])) print(' intensity min/max: %s %s' % (str(hdr.mm4[0]), str(hdr.mm4[1]))) if nw > 4: print(' wavelength 5 (nm): %s' % str(hdr.wave[4])) print(' intensity min/max: %s %s' % (str(hdr.mm5[0]), str(hdr.mm5[1]))) if hdr.LensNum == 12: name = ' (60x)' elif hdr.LensNum == 13: name = ' (100x)' else: name = '(??)' print('lens type: %s %s' % (str(hdr.LensNum), name)) print('origin (um) x/y/z: %s %s %s' % (str(hdr.zxy0[1]), str(hdr.zxy0[2]), str(hdr.zxy0[0]))) if hdr.PixelType == 0: name = '8 bit (signed)' elif hdr.PixelType == 1: name = '16 bit (signed)' elif hdr.PixelType == 2: name = '32 bit (signed real)' elif hdr.PixelType == 3: name = '16 bit (signed complex integer)' elif hdr.PixelType == 4: name = '32 bit (signed complex real)' elif hdr.PixelType == 5: name = '16 bit (signed) IW_EMTOM' elif hdr.PixelType == 6: name = '16 bit (unsigned short)' elif hdr.PixelType == 7: name = '32 bit (signed long)' elif hdr.PixelType == 101: name = 'unsigned 4-bit' else: name = ' ** undefined ** ' print('# pixel data type: %s' % name) if hdr.next > 0: n = numInts + numFloats if n > 0: name = ' (%d secs)' % (hdr.next // (4. * n)) else: name = ' (??? secs)' name2 = ' (%d ints + %d reals per section)' % (numInts, numFloats) else: name = '' name2 = None print('# extended header size: %s %s' % (str(hdr.next), name)) if name2 is not None: print(name2) if hdr.NumTitles < 0: print(' ** ERROR ** : NumTitles less than zero (NumTitles = %s )' % str(hdr.NumTitles)) elif hdr.NumTitles > 0: n = hdr.NumTitles if n > 10: print(' ** ERROR ** : NumTitles larger than 10 (NumTitles = %s )' % hdr.NumTitles) n=10 for i in range(n): print('title %d: %s'%(i, hdr.title[i])) def axisOrderStr(hdr, onlyLetters=True): """Return a string indicating the ordering of dimensions. x, y, z, w, and t will appear at most once in the string, and at least three of them will be present. The letters that do appear will be present in order from slowest varying to fastest varying. The values for the axis field in the header do not affect the result. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Keyword parameters: onlyLetters -- If True, only the letters for the dimensions will appear in the string. If False, the first character of the string will be '[', the last character of the string will be ']', and a letter for a dimension will be preceded by a comma if it is not the first, slowest- varying dimension. """ # 'Image sequence. 0=ZTW, 1=WZT, 2=ZWT.' Given from fastest to slowest. zOrder = hdr.ImgSequence if zOrder < 0 or zOrder > 2: # The value is invalid; use the ZTW instead. zOrder = 0 nt, nw = max(1, hdr.NumTimes), max(1, hdr.NumWaves) if nt == nw == 1: orderLetters= 'zyx' elif nt == 1: orderLetters= ('wzyx', 'zwyx', 'wzyx')[zOrder] elif nw == 1: orderLetters= ('tzyx', 'tzyx', 'tzyx')[zOrder] else: orderLetters= ('wtzyx', 'tzwyx', 'twzyx')[zOrder] if onlyLetters: return orderLetters else: return '[' + ','.join(orderLetters) + ']' def index2zwt(i, nz, nw, nt, seq, over=False): """Convert section index to 3D index in z, wavelength, and time. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: i -- Is the section index. If less than zero, it is treated as a displacement from one past the end (i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). nz -- Is the number of samples in z. Assumed to be positive. nw -- Is the number of wavelengths. Assumed to be positive. nt -- Is the number of time points. Assumed to be positive. seq -- Is the code for the interleaving of z, wavelength, and time. If seq is 0, z varies fastest, followed by time, then followed by wavelength. If seq is 1, wavelength variest fastest, followed by z, then followed by time. If seq is 2, z varies fastest, followed by wavelength, then followed by time. For any other value, a ValueError exception will be raised. Keyword parameters: over -- If True, a value of i past the end will cause a ValueError exception to be raised. If False, a value of i past the end will lead to a value for the index in the slowest-varying dimension to be past the end. Return value: Returns a three element tuple of the zero-based indices for z, wavelength, and time. """ if i < 0: ri = i + nz * nw * nt if ri < 0: raise ValueError('section index, %d, is before first' % i) else: ri = i if over and ri >= nz * nw * nt: raise ValueError('index is greater than or equal to nz * nw * nt') if seq == 0: result = (ri % nz, ri // (nz * nt), (ri // nz) % nt) elif seq == 1: result = ((ri // nw) % nz, ri % nw, ri // (nw * nz)) elif seq == 2: result = (ri % nz, (ri // nz) % nw, ri // (nz * nw)) else: raise ValueError('invalid code, %d, for sequence arrangement' % seq) return result def zwt2index(z, w, t, nz, nw, nt, seq, over=False): """Convert a 3D index in z, wavelength, and time to a section index. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: z -- Is the index in z. If less than zero, it is treated as a displacement from one past the end i.e. -1 is the last valid section index, and -2 is the next-to-last valid section index). w -- Is the wavelength index. If less than zero, it is treated as a displacement from one past the end. t -- Is the time index. If less than zero, it is treated as a displacement from one past the end. nz -- Is the number of samples in z. Assumed to be positive. nw -- Is the number of wavelengths. Assumed to be positive. nt -- Is the number of time points. Assumed to be positive. Keyword parameters: over -- If True, a ValueError exception will be raised if any of z, w, or t are past the end in their respective dimesions. If False, a value for z, w, or t that is past the end will only result in a ValueError exception if that dimension is not the slowest-varying non-singleton dimension. Return value: Returns a zero-based index for the section. """ if z < 0: rz = z + nz if rz < 0: raise ValueError('%d is before first z index' % z) else: rz = z if w < 0: rw = w + nw if rw < 0: raise ValueError('%d is before first wavelength index' % w) else: rw = w if t < 0: rt = t + nt if rt < 0: raise ValueError('%d is before first time point index' % t) else: rt = t if over and (rz >= nz or rw >= nw or rt >= nt): raise ValueError('z, wavelength, or time indices out of bounds') if seq == 0: if (rz >= nz and (nt > 1 or nw > 1 or t > 0 or w > 0)): raise ValueError('z index past end and z is not slowest-varying ' 'non-singleton dimension') if (rt >= nt and (nw > 1 or w > 0)): raise ValueError('time point index past end and time is not ' 'slowest-varying non-singleton dimension') result = rz + nz * (rt + nt * rw) elif seq == 1: if (rw >= nw and (nz > 1 or nt > 1 or z > 0 or t > 0)): raise ValueError('wavelength index past end and wavelength is not ' 'slowest-varying non-singleton dimension') if (rz >= nz and (nt > 1 or t > 0)): raise ValueError('z index past end and z is not slowest-varying ' 'non-singleton dimension') result = rw + nw * (rz + nz * rt) elif seq == 2: if (rz >= nz and (nw > 1 or nt > 1 or w > 0 or t > 0)): raise ValueError('z index past end and z is not slowest-varying ' 'non-singleton dimension') if (rw >= nw and (nt > 1 or t > 0)): raise ValueError('wavelength index past end and wavelength is not ' 'slowest-varying non-singleton dimension') result = rz + nz * (rw + nw * rt) else: raise ValueError('invalid code, %d, for sequence arrangement' % seq) return result def init_simple(hdr, mode, nxOrShape, ny=None, nz=None, isByteSwapped=None): """Initialize a MRC header for a given size and pixel type code. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. mode -- Is the MRC pixel type code to use. nxOrShape -- If ny or nz is not None, this should be a scalar which is the number of samples in x. If ny and nz are None, it must be an iterable. If it has one element, that will be the number of samples in x; ny and nz will be one. If it has two elements, the last will be the number of samples in x, the second to last will be the number of samples in y, and nz will be one. If has more than two elements, the last will be the number of samples in x, the second to last will be the number of samples in y, and the product of the remaining elements will be the number of samples in z. Keyword parameters: ny -- If not None, this will be the number of samples in y. If not None, nxOrShape should be a scalar. nz -- If not None, this will be the number of samples in z. If not None, nxOrShape should be a scalar. isByteSwapped -- If None, the stamps for byte order in the header will be set to match the byte ordering of hdr. If True, the stamps for byte order in the header will be set to be opposite of the native byte ordering. If False, the stamps for byte order in the header will match the native byte ordering. The isByteSwapped keyword was added in version 0.1.0 of Mrc.py as included with Priism. """ if ny is None and nz is None: if len(nxOrShape) == 2: nz, (ny, nx) = 1, nxOrShape elif len(nxOrShape) == 1: nz, ny, (nx,) = 1, 1, nxOrShape elif len(nxOrShape) == 3: nz, ny, nx = nxOrShape else: ny, nx = nxOrShape[-2:] nz = N.prod(nxOrShape[:-2]) else: if ny is None: ny = 1 if nz is None: nz = 1 nx = nxOrShape hdr.Num = (nx, ny, nz) hdr.PixelType = mode hdr.mst = (0, 0, 0) hdr.m = (1, 1, 1) hdr.d = (1, 1, 1) hdr.angle = (90, 90, 90) hdr.axis = (1, 2, 3) hdr.mmm1 = (0, 100000, 5000) hdr.nspg = 0 hdr.next = 0 hdr.ntst = 0 hdr.NumIntegers = 0 hdr.NumFloats = 0 hdr.sub = 1 hdr.zfac = 1 hdr.ImageType = 0 hdr.LensNum = 0 hdr.n1 = 0 hdr.n2 = 0 hdr.v1 = 0 hdr.v2 = 0 hdr.tilt = (0, 0, 0) hdr.zxy0 = (0, 0, 0) hdr.NumTitles = 0 hdr.title = ' ' * 80 if hdrIsInPriismFormat(hdr): hdr.dvid = 0xc0a0 hdr.nblank = 0 hdr.blank = 0 hdr.mm2 = (0, 10000) hdr.mm3 = (0, 10000) hdr.mm4 = (0, 10000) hdr.mm5 = (0, 10000) hdr.NumTimes = 1 # Zero means that z changes fastest, then time, and then wavelength. # One means that wavelength changes fastest, then z, and then time. # Two means that z changes fastest, then wavelength, and then time. hdr.ImgSequence = 0 hdr.NumWaves = 1 hdr.wave = (999, 0, 0, 0, 0) else: hdr.extra0 = 0 hdr.exttyp = N.fromstring('AGAR', dtype='i1') hdr.nversion = 20140 hdr.extra1 = 0 hdr.extra2 = 0 hdr.extra3 = 0 hdr.map = N.fromstring('MAP ', dtype='i1') if isByteSwapped is None: isByteSwapped = isSwappedDtype(hdr._array.dtype) if isByteSwapped: hdr.machst = (_SYSSWAPST0, _SYSSWAPST1, 0, 0) else: hdr.machst = (_SYSMACHST0, _SYSMACHST1, 0, 0) hdr.rms = -1.0 def initHdrArrayFrom(hdrDest, hdrSrc): """Copy all fields from hdrSrc to hdrDest except the number of samples ('Num' field; bytes 1 - 12), pixel type ('PixelType' field; bytes 13 - 16), and the number of bytes in the extended header ('next' field; bytes 93 - 96. Do not change the byte order for hdrDest. Positional parameters: hdrDest -- Is the header to change. Assumed to be a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. hdrSrc -- Is the header from which to copy. Assumed to be a MRC header as returned by makeHdrArray() or implement_hdr(). If hdrIsInPriismFormat(hdrSrc) is different than hdrIsInPriismFormat(hdrDest), the format of the destination will be changed to match the source. Return value: Returns the header, wrapped with a HdrPriism or Hdr2014Priism instance, as appropriate. The return value was added in version 0.1.0 of Mrc.py as included with Priism. """ srcIsPriism = hdrIsInPriismFormat(hdrSrc) destIsPriism = hdrIsInPriismFormat(hdrDest) if srcIsPriism: if not destIsPriism: if isSwappedDtype(hdrDest._array.dtype): hdrDest._array.dtype = N.dtype(mrcHdr_dtype).newbyteorder() else: hdrDest._array.dtype = N.dtype(mrcHdr_dtype) hdrDest = HdrPriism(hdrDest._array) hdrDest.mst = hdrSrc.mst hdrDest.m = hdrSrc.m hdrDest.d = hdrSrc.d hdrDest.angle = hdrSrc.angle hdrDest.axis = hdrSrc.axis hdrDest.mmm1 = hdrSrc.mmm1 hdrDest.nspg = hdrSrc.nspg hdrDest.next = 0 hdrDest.dvid = hdrSrc.dvid hdrDest.nblank = hdrSrc.nblank hdrDest.ntst = hdrSrc.ntst hdrDest.blank = hdrSrc.blank hdrDest.NumIntegers = 0 hdrDest.NumFloats = 0 hdrDest.sub = hdrSrc.sub hdrDest.zfac = hdrSrc.zfac hdrDest.mm2 = hdrSrc.mm2 hdrDest.mm3 = hdrSrc.mm3 hdrDest.mm4 = hdrSrc.mm4 hdrDest.ImageType = hdrSrc.ImageType hdrDest.LensNum = hdrSrc.LensNum hdrDest.n1 = hdrSrc.n1 hdrDest.n2 = hdrSrc.n2 hdrDest.v1 = hdrSrc.v1 hdrDest.v2 = hdrSrc.v2 hdrDest.mm5 = hdrSrc.mm5 hdrDest.NumTimes = hdrSrc.NumTimes hdrDest.ImgSequence = hdrSrc.ImgSequence hdrDest.tilt = hdrSrc.tilt hdrDest.NumWaves = hdrSrc.NumWaves hdrDest.wave = hdrSrc.wave hdrDest.zxy0 = hdrSrc.zxy0 hdrDest.NumTitles = hdrSrc.NumTitles hdrDest.title = hdrSrc.title else: if destIsPriism: if isSwappedDtype(hdrDest._array.dtype): hdrDest._array.dtype = N.dtype(mrc2014Hdr_dtype).newbyteorder() else: hdrDest._array.dtype = N.dtype(mrc2014Hdr_dtype) hdrDest = Hdr2014Priism(hdrDest._array) hdrDest.mst = hdrSrc.mst hdrDest.m = hdrSrc.m hdrDest.d = hdrSrc.d hdrDest.angle = hdrSrc.angle hdrDest.axis = hdrSrc.axis hdrDest.mmm1 = hdrSrc.mmm1 hdrDest.nspg = hdrSrc.nspg hdrDest.next = 0 hdrDest.extra0 = hdrSrc.extra0 hdrDest.ntst = hdrSrc.ntst hdrDest.exttyp = hdrSrc.exttyp hdrDest.nversion = hdrSrc.nversion hdrDest.extra1 = hdrSrc.extra1 hdrDest.NumIntegers = 0 hdrDest.NumFloats = 0 hdrDest.sub = hdrSrc.sub hdrDest.zfac = hdrSrc.zfac hdrDest.extra2 = hdrSrc.extra2 hdrDest.ImageType = hdrSrc.ImageType hdrDest.LensNum = hdrSrc.LensNum hdrDest.n1 = hdrSrc.n1 hdrDest.n2 = hdrSrc.n2 hdrDest.v1 = hdrSrc.v1 hdrDest.v2 = hdrSrc.v2 hdrDest.extra3 = hdrSrc.extra3 hdrDest.tilt = hdrSrc.tilt hdrDest.origin = hdrSrc.origin hdrDest.map = hdrSrc.map hdrDest.machst = hdrSrc.machst hdrDest.rms = hdrSrc.rms hdrDest.NumTitles = hdrSrc.NumTitles hdrDest.title = hdrSrc.title return hdrDest def setTitle(hdr, s, i=-1, push=False, truncate=False): """Set a title in the MRC header. Provided for compatibility with previous versions of Mrc.py. In this version, you can use hdr.setTitle(). Positional parameters: hdr -- Is the MRC header to modify. It is expected to be a header returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. s -- Is the character string for the title. If s is longer than 80 characters and truncate is False, a ValueError exception will be raised. Since no byte swapping is done for the titles in the header, s should be encoded in ASCII or another format that does not use multibyte characters. Keyword parameters: i -- Is the index of the title to set. If i is less than zero, the last title not in use will be set. If i is less than zero and all the titles are in use and push is False or i is greater than 9, a ValueError exception will be raised. push -- If True, i is less than zero, and all titles are in use, titles will be pushed down before assigning s to the last title. That will discard the first title and title[k] (for k greater than and equal to 0 and less than 9) will be title[k+1] from before the change. The push keyword was added in version 0.1.0 of Mrc.py as included with Priism. truncate -- If True, only use the first 80 characters from s. The truncate keyword was added in version 0.1.0 of Mrc.py as included with Priism. """ hdr.setTitle(s, i=i, push=push, truncate=truncate) def hdrChangeToMrc2014Format(hdr): """Change the header to have the format from MRC 2014. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Return value: Returns the header wrapped with a Hdr2014Priism instance. """ if not hdrIsInPriismFormat(hdr): return hdr nw = max(hdr.NumWaves, 1) nt = max(hdr.NumTimes, 1) if nw > 1: if hdr.ImgSequence == 1 or (hdr.ImgSequence == 2 and nt > 1): print('WARNING: MRC 2014 header does not support multiple ' 'wavelengths; any image data present will be out of sequence') else: print('WARNING: MRC 2014 header does not support multiple wavelengths') dx, dy, dz = hdr.getSpacing() origz, origx, origy = hdr.zxy0 if isSwappedDtype(hdr._array.dtype): hdr._array.dtype = N.dtype(mrc2014Hdr_dtype).newbyteorder() isswapped = True else: hdr._array.dtype = N.dtype(mrc2014Hdr_dtype) isswapped = False hdr = Hdr2014Priism(hdr._array) hdr.map = N.fromstring('MAP ', dtype='i1') hdr.nversion = 20140 if nt > 1: hdr.nspg = 401 hdr.m[2] = max(hdr.Num[2], 0) // nt hdr.setSpacing(dx, dy, dz) if hdr.nspg == 0 or hdr.nspg == 401: hdr.exttyp = N.fromstring('AGAR', dtype='i1') else: hdr.exttyp = N.fromstring('MRC0', dtype='i1') hdr.origin = (origx, origy, origz) if isswapped: hdr.machst = (_SYSSWAPST0, _SYSSWAPST1, 0, 0) else: hdr.machst = (_SYSMACHST0, _SYSMACHST1, 0, 0) hdr.rms = -1.0 return hdr def hdrChangeToPriismFormat(hdr): """Change the header to have the format from Priism. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Return value: Returns the header wrapped with a HdrPriism instance. """ if hdrIsInPriismFormat(hdr): return hdr origx, origy, origz = hdr.origin spg = hdr.npsg if isSwappedDtype(hdr._array.dtype): hdr._array.dtype = N.dtype(mrcHdr_dtype).newbyteorder() else: hdr._array.dtype = N.dtype(mrcHdr_dtype) hdr = HdrPriism(hdr._array) if spg == 1 or spg == 401: hdr.nspg = 0 if spg == 401: hdr.NumTimes = hdr.Num[2] // max(hdr.m[2], 1) hdr.dvid = 0xc0a0 hdr.nblank = 0 hdr.blank = 0 hdr.mm2 = (0.0, 0.0) hdr.mm3 = (0.0, 0.0) hdr.mm4 = (0.0, 0.0) hdr.ImgSequence = 0 hdr.NumWaves = 1 hdr.mm5 = (0.0, 0.0) hdr.wave = (0, 0, 0, 0, 0) hdr.zxy0 = (origz, origx, origy) return hdr def hdrHasMapField(hdr): """Return True if the header has a properly formatted map field. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is the header as an array of 1024 unsigned 8-bit integers. """ # The map field is bytes 208 through 211 (numbered from zero). It # has 'MAP ' in ASCII when properly formatted. return (hdr[208] == 77 and hdr[209] == 65 and hdr[210] == 80 and hdr[211] == 32) def hdrIsByteSwapped(hdr): """Return True if the header is swapped from the native byte ordering. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Return value: Returns True if the machine stamp (which is insensitive to whether hdr.dtype has been byte swapped) or the Num field (which is sensitive to whether hdr.dtype has been byte swapped) indicates that the header is not in the machine byte order. Otherwise, returns False. For ease of interpreting the return value, isSwappedDtype(hdr.dtype) should be False. """ if hasattr(hdr, 'machst'): if (hdr.machst[0] == _SYSMACHST0 and hdr.machst[1] >= _SYSMACHST1LO and hdr.machst[1] <= _SYSMACHST1HI): return False elif (hdr.machst[0] == _SYSSWAPST0 and hdr.machst[1] >= _SYSSWAPST1LO and hdr.machst[1] <= _SYSSWAPST1HI): return True # Use the test employed by Priism. Assumes that the actual number of # samples in x and y are both positive and less than 65536. Under those # conditions a byte-swapped header will have both either less than zero or # greater than 65535. nx = hdr.Num[0] ny = hdr.Num[1] return (nx < 0 or nx > 65535) and (ny < 0 or ny > 65535) def hdrIsInPriismFormat(hdr): """Return True if the header uses the format from Priism. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. """ return not hasattr(hdr, 'map') def hdrHasExtType(hdr): """Return True if the header claims to handle the extended header type field. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. """ has = False if hasattr(hdr, 'nversion'): # Check for a version number that looks reasonable. if (hdr.nversion >= 20140 and hdr.nversion <= 20509 and hasattr(hdr, 'exttyp')): has = True return has def getExtHeaderFormat(hdr): """Return a code for the format of the extended header. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: hdr -- Is a MRC header as returned by makeHdrArray() or implement_hdr(). If x is an instance of the Mrc or Mrc2 classes, you can use x.hdr for this parameter. Return value: Returns zero if the extended header is to be interpreted as symmetry information. Returns one if the extended header is to be interpreted as the Priism format. Returns -1 if the extended header format is not understood by this implementation. """ if hdrHasExtType(hdr): if (N.array_equal(hdr.exttyp, N.fromstring('MRC0', dtype='i1')) or N.array_equal(hdr.exttyp, N.fromstring('CCP4', dtype='i1'))): fmt = 0 elif N.array_equal(hdr.exttyp, N.fromstring('AGAR', dtype='i1')): fmt = 1 else: fmt = -1 elif hasattr(hdr, 'map'): if hdr.nspg == 0 or hdr.nspg == 1 or hdr.nspg == 401: fmt = 1 else: fmt = 0 else: if hdr.nspg == 0: fmt = 1 else: fmt = 0 return fmt def isStringLike(value): """Return True if value is a bytearray, bytes, str, or unicode. Otherwise return False. This function was added in version 0.1.0 of Mrc.py as included with Priism. """ if isinstance(value, str): return True # bytes is only defined in Python 3 and newer versions of Python 2. try: if isinstance(value, bytes): return True except: pass # unicode is only defined in Python 2. try: if isinstance(value, unicode): return True except: pass # bytearray was introduced in Python 2.6. try: if isinstance(value, bytearray): return True except: pass return False def isSwappedDtype(dtype): """Return True if the given NumPy dtype is not in machine byte order. Will raise a ValueError exception if dtype is a structured type and has components which have different byte orders. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: dtype -- Is the NumPy dtype to test. """ swappingtype = checkDtypeSwapping(dtype) if swappingtype == -2: raise ValueError('Structured dtype has commponents with different ' 'endianness') return swappingtype < 0 def checkDtypeSwapping(dtype): """Check the byte swapping of the given NumPy dtype. This function was added in version 0.1.0 of Mrc.py as included with Priism. Positional parameters: dtype -- Is the NumPy dtype to check. Return value: The return value will be one of the following: 1 -- The dtype is compatible with the native byte order. It has one or more components that are in the native byte order and all remaining components are not sensitive to the byte order. 0 -- The dtype is compatible with the native byte order. It only has components that are not sensitive to the byte order. -1 -- The dtype requires byte swapping to be compatible with the native byte order. It has one or more components that are not in the native byte order and all remaining components are not sensitive to the byte order. -2 -- The dtype does not have a consistent byte ordering. At least one component is in the native byte order, and at least one component is byte swapped relative to the native byte order. """ if dtype.fields is None: if dtype.byteorder == '=' or dtype.byteorder == _SYSBYTEORDER: rval = 1 elif dtype.byteorder == '|': rval = 0 else: rval = -1 else: rval = 0 for f in dtype.fields: rval_child = checkDtypeSwapping(dtype.fields[f][0]) if rval_child != 0 and rval != rval_child: if rval == 0: rval = rval_child else: rval = -2 break return rval # The second element in each tuple is the name by which that field will be # accessed. If changed, similar changes will be necessary in the # property definitions for the HdrBase or HdrPriism classes. mrcHdrFields = [ ('3i4', 'Num'), ('1i4', 'PixelType'), ('3i4', 'mst'), ('3i4', 'm'), ('3f4', 'd'), ('3f4', 'angle'), ('3i4', 'axis'), ('3f4', 'mmm1'), ('1i4', 'nspg'), ('1i4', 'next'), ('1i2', 'dvid'), ('1i2', 'nblank'), ('1i4', 'ntst'), ('24i1', 'blank'), ('1i2', 'NumIntegers'), ('1i2', 'NumFloats'), ('1i2', 'sub'), ('1i2', 'zfac'), ('2f4', 'mm2'), ('2f4', 'mm3'), ('2f4', 'mm4'), ('1i2', 'ImageType'), ('1i2', 'LensNum'), ('1i2', 'n1'), ('1i2', 'n2'), ('1i2', 'v1'), ('1i2', 'v2'), ('2f4', 'mm5'), ('1i2', 'NumTimes'), ('1i2', 'ImgSequence'), ('3f4', 'tilt'), ('1i2', 'NumWaves'), ('5i2', 'wave'), ('3f4', 'zxy0'), ('1i4', 'NumTitles'), ('(10,80)i1', 'title'), ] mrcHdrNames = [] mrcHdrFormats = [] for ff in mrcHdrFields: mrcHdrFormats.append(ff[0]) mrcHdrNames.append(ff[1]) del ff del mrcHdrFields mrcHdr_dtype = list(zip(mrcHdrNames, mrcHdrFormats)) # This describes the MRC 2014 , http://www.ccpem.ac.uk/mrc_format/mrc2014.php , # format with the addition of fields from Priism that do not conflict with the # MRC 2014 format. The fields that are Priism extensions are ntst, # NumIntegers, NumFloats, sub, zfac, ImageType, LensNum, n1, n2, v1, v2, and # tilt. mrc2014HdrFields = [ ('3i4', 'Num'), ('1i4', 'PixelType'), ('3i4', 'mst'), ('3i4', 'm'), ('3f4', 'd'), ('3f4', 'angle'), ('3i4', 'axis'), ('3f4', 'mmm1'), ('1i4', 'nspg'), ('1i4', 'next'), ('4i1', 'extra0'), ('1i4', 'ntst'), ('4i1', 'exttyp'), ('1i4', 'nversion'), ('16i1', 'extra1'), ('1i2', 'NumIntegers'), ('1i2', 'NumFloats'), ('1i2', 'sub'), ('1i2', 'zfac'), ('24i1', 'extra2'), ('1i2', 'ImageType'), ('1i2', 'LensNum'), ('1i2', 'n1'), ('1i2', 'n2'), ('1i2', 'v1'), ('1i2', 'v2'), ('12i1', 'extra3'), ('3f4', 'tilt'), ('3f4', 'origin'), ('4i1', 'map'), ('4u1', 'machst'), ('1f4', 'rms'), ('1i4', 'NumTitles'), ('(10,80)i1', 'title'), ] mrc2014HdrNames = [] mrc2014HdrFormats = [] for ff in mrc2014HdrFields: mrc2014HdrFormats.append(ff[0]) mrc2014HdrNames.append(ff[1]) del ff del mrc2014HdrFields mrc2014Hdr_dtype = list(zip(mrc2014HdrNames, mrc2014HdrFormats)) # Set character used for NumPy dtypes that corresponds to the native byte # ordering. Also record the values to use in the MRC 2014 machine stamp # field if a file is in the native ordering or if it is byte-swapped relative # to the native ordering. Since the MRC 2014 documentation says that # a machine stamp of 68 and 68 is safe for specifying little-endian, ignore the # least significant 4 bits of the second byte (they specify how characters are # encoded) when testing for a little-endian stamp.. For writing, follow the # CCP4 documentation and use 68 and 65 for a little-endian machine stamp. if sys.byteorder == 'little': _SYSBYTEORDER = '<' _SYSMACHST0 = 68 _SYSMACHST1 = 65 _SYSMACHST1LO = 64 _SYSMACHST1HI = 79 _SYSSWAPST0 = 17 _SYSSWAPST1 = 17 _SYSSWAPST1LO = 17 _SYSSWAPST1HI = 17 else: _SYSBYTEORDER = '>' _SYSMACHST0 = 17 _SYSMACHST1 = 17 _SYSMACHST1LO = 17 _SYSMACHST1HI = 17 _SYSSWAPST0 = 68 _SYSSWAPST1 = 65 _SYSSWAPST1LO = 64 _SYSSWAPST1HI = 79 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0406938 mantis_xray-3.2.2/mantis_xray/TomoCS/0000775000175000017500000000000014700317134016706 5ustar00wattswatts././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/TomoCS/__init__.py0000644000175000017500000000000114332463747021021 0ustar00wattswatts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/TomoCS/_rank_order.py0000644000175000017500000000401714332463747021562 0ustar00wattswatts"""rankorder.py - convert an image of any type to an image of ints whose pixels have an identical rank order compared to the original image Originally part of CellProfiler, code licensed under both GPL and BSD licenses. Website: http://www.cellprofiler.org Copyright (c) 2003-2009 Massachusetts Institute of Technology Copyright (c) 2009-2011 Broad Institute All rights reserved. Original author: Lee Kamentstky """ import numpy def rank_order(image): """Return an image of the same shape where each pixel is the index of the pixel value in the ascending order of the unique values of `image`, aka the rank-order value. Parameters ---------- image: ndarray Returns ------- labels: ndarray of type np.uint32, of shape image.shape New array where each pixel has the rank-order value of the corresponding pixel in `image`. Pixel values are between 0 and n - 1, where n is the number of distinct unique values in `image`. original_values: 1-d ndarray Unique original values of `image` Examples -------- >>> a = np.array([[1, 4, 5], [4, 4, 1], [5, 1, 1]]) >>> a array([[1, 4, 5], [4, 4, 1], [5, 1, 1]]) >>> rank_order(a) (array([[0, 1, 2], [1, 1, 0], [2, 0, 0]], dtype=uint32), array([1, 4, 5])) >>> b = np.array([-1., 2.5, 3.1, 2.5]) >>> rank_order(b) (array([0, 1, 2, 1], dtype=uint32), array([-1. , 2.5, 3.1])) """ flat_image = image.ravel() sort_order = flat_image.argsort().astype(numpy.uint32) flat_image = flat_image[sort_order] sort_rank = numpy.zeros_like(sort_order) is_different = flat_image[:-1] != flat_image[1:] numpy.cumsum(is_different, out=sort_rank[1:]) original_values = numpy.zeros((sort_rank[-1] + 1,), image.dtype) original_values[0] = flat_image[0] original_values[1:] = flat_image[1:][is_different] int_image = numpy.zeros_like(sort_order) int_image[sort_order] = sort_rank return (int_image.reshape(image.shape), original_values) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/TomoCS/forward_backward_tv.py0000644000175000017500000004601014332463747023307 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Originally part of tomotv, code licensed under BSD license. # Website: https://github.com/emmanuelle/tomo-tv # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import absolute_import import numpy as np from scipy import sparse from .tv_denoising import tv_denoise_fista from .projections import back_projection, projection # ------------------ Computing energies --------------------------- def tv_norm(im): """Compute the (isotropic) TV norm of an image""" grad_x1 = np.diff(im, axis=0) grad_x2 = np.diff(im, axis=1) return np.sqrt(grad_x1[:, :-1]**2 + grad_x2[:-1, :]**2).sum() def tv_norm_anisotropic(im): """Compute the anisotropic TV norm of an image""" grad_x1 = np.diff(im, axis=0) grad_x2 = np.diff(im, axis=1) return np.abs(grad_x1[:, :-1]).sum() + np.abs(grad_x2[:-1, :]).sum() # ------------------ Proximal iterators ---------------------------- def fista_tv(y, beta, niter, H, verbose=0, mask=None): """ TV regression using FISTA algorithm (Fast Iterative Shrinkage/Thresholding Algorithm) Parameters ---------- y : ndarray of floats Measures (tomography projection). If H is given, y is a column vector. If H is not given, y is a 2-D array where each line is a projection along a different angle beta : float weight of TV norm niter : number of forward-backward iterations to perform H : sparse matrix tomography design matrix. Should be in csr format. mask : array of bools Returns ------- res : list list of iterates of the reconstructed images energies : list values of the function to be minimized at the different iterations. Its values should be decreasing. Notes ----- This algorithm minimizes iteratively the energy E(x) = 1/2 || H x - y ||^2 + beta TV(x) = f(x) + beta TV(x) by forward - backward iterations: u_n = prox_{gamma beta TV}(x_n - gamma nabla f(x_n))) t_{n+1} = 1/2 * (1 + sqrt(1 + 4 t_n^2)) x_{n+1} = u_n + (t_n - 1)/t_{n+1} * (u_n - u_{n-1}) References ---------- - A. Beck and M. Teboulle (2009). A fast iterative shrinkage-thresholding algorithm for linear inverse problems. SIAM J. Imaging Sci., 2(1):183-202. - Nelly Pustelnik's thesis (in French), http://tel.archives-ouvertes.fr/tel-00559126_v4/ Paragraph 3.3.1-c p. 69 , FISTA """ n_meas, n_pix = H.shape if mask is not None: l = len(mask) else: l = int(np.sqrt(n_pix)) n_angles = n_meas / l Ht = sparse.csr_matrix(H.transpose()) x0 = np.zeros(n_pix)[:, np.newaxis] res, energies = [], [] gamma = .9/ (l * n_angles) x = x0 u_old = np.zeros((l, l)) t_old = 1 for i in range(niter): if verbose: print(i) eps = 1.e-4 err = H * x - y back_proj = Ht * err tmp = x - gamma * back_proj if mask is not None: tmp2d = np.zeros((l, l)) tmp2d[mask] = tmp.ravel() else: tmp2d = tmp.reshape((l, l)) u_n = tv_denoise_fista(tmp2d, weight=beta*gamma, eps=eps) t_new = (1 + np.sqrt(1 + 4 * t_old**2))/2. t_old = t_new x = u_n + (t_old - 1)/t_new * (u_n - u_old) u_old = u_n res.append(x) data_fidelity_err = 1./2 * (err**2).sum() tv_value = beta * tv_norm(x) energy = data_fidelity_err + tv_value energies.append(energy) if mask is not None: x = x[mask][:, np.newaxis] else: x = x.ravel()[:, np.newaxis] return res, energies def ista_tv(y, beta, niter, H=None): """ TV regression using ISTA algorithm (Iterative Shrinkage/Thresholding Algorithm) Parameters ---------- y : ndarray of floats Measures (tomography projection). If H is given, y is a column vector. If H is not given, y is a 2-D array where each line is a projection along a different angle beta : float weight of TV norm niter : number of forward-backward iterations to perform H : sparse matrix or None tomography design matrix. Should be in csr format. If H is none, the projections as well as the back-projection are computed by a direct method, without writing explicitely the design matrix. Returns ------- res : list list of iterates of the reconstructed images energies : list values of the function to be minimized at the different iterations. Its values should be decreasing. Notes ----- This algorithm minimizes iteratively the energy E(x) = 1/2 || H x - y ||^2 + beta TV(x) by simple forward - backward iterations: x_{n + 1} = prox_{gamma beta TV(.)} (x_n - gamma nabla f(x_n)) where f(x) = 1/2 || H x - y ||^2 References ---------- - Proximal Splitting Methods in Signal Processing, P. Combettes and J.-C. Pesquet, Fixed-Point Algorithms for Inverse Problems in Science and Engineering, p. 185 (2011). Algorithm 10.3 with lamba_n = 1. - Nelly Pustelnik's thesis (in French), http://tel.archives-ouvertes.fr/tel-00559126_v4/ Paragraph 3.3.1-c p. 68 , ISTA """ if H is None: method = 'direct' n_angles, l = y.shape n_pix = l ** 2 else: method = 'matrix' n_angles, l = y.shape n_meas, n_pix = H.shape l = int(np.sqrt(n_pix)) n_angles = n_meas / l if method == 'matrix': Ht = sparse.csr_matrix(H.transpose()) x0 = np.zeros((l, l)) res, energies = [], [] # l * n_angles is the Lipschitz constant of Ht H gamma = .9/ (l * n_angles) x = x0 for i in range(niter): eps = 1.e-4 # Forward part if method == 'matrix': x = x.ravel()[:, np.newaxis] err = H * x - y back_proj = Ht * err tmp = x - gamma * back_proj tmp = tmp.reshape((l, l)) else: err = projection(x, n_angles) - y back_proj = back_projection(err) tmp = x - gamma * back_proj # backward: TV prox x = tv_denoise_fista(tmp, weight=beta*gamma, eps=eps) res.append(x) # compute the energy data_fidelity_err = 1./2 * (err**2).sum() tv_value = beta * tv_norm(x) energy = data_fidelity_err + tv_value energies.append(energy) return res, energies def gfb_tv(y, beta, niter, H=None, val_min=0, val_max=1, x0=None, stop_tol=1.e-4, nonnegconst=1): """ TV regression + interval constraint using the generalized forward backward splitting (GFB). Parameters ---------- y : ndarray of floats Measures (tomography projection). If H is given, y is a column vector. If H is not given, y is a 2-D array where each line is a projection along a different angle beta : float weight of TV norm niter : number of forward-backward iterations to perform H : sparse matrix or None tomography design matrix. Should be in csr format. If H is none, the projections as well as the back-projection are computed by a direct method, without writing explicitely the design matrix. val_min, val_max: floats We impose that the image values are in [val_min, val_max] x0 : ndarray of floats, optional (default is None) Initial guess Returns ------- res : list list of iterates of the reconstructed images energies : list values of the function to be minimized at the different iterations. Its values should be decreasing. Notes ----- This algorithm minimizes iteratively the energy E(x) = 1/2 || H x - y ||^2 + beta TV(x) + i_C(x) where TV(.) is the total variation pseudo-norm and i_C is the indicator function of the convex set [val_min, val_max]. The algorithm used the generalized forward-backward scheme z1_{n + 1} = z1_n - x_n + prox_{2 gamma beta TV(.)} (2*x_n - z1_n - gamma nabla f(x_n)) z2_{n+1} = z1_n - x_n + prox_{i_C(.)}(2*x_n - z2_n - gamma nabla f(x_n) where f(x) = 1/2 || H x - y ||^2 This method can in fact be used for other sums of non-smooth functions for which the prox operator is known. References ---------- Hugo Raguet, Jalal M. Fadili and Gabriel Peyre, Generalized Forward-Backward Splitting Algorithm, preprint arXiv:1108.4404v2, 2011. See also http://www.ceremade.dauphine.fr/~peyre/numerical-tour/tours/inverse_9b_gfb/ """ n_angles, l = y.shape n_meas, n_pix = H.shape l = int(np.sqrt(n_pix)) n_angles = n_meas / l Ht = sparse.csr_matrix(H.transpose()) if x0 is None: x0 = np.zeros((l, l)) z_1 = np.zeros((l**2, 1)) z_2 = np.zeros((l**2, 1)) res, energies = [], [] # l * n_angles is the Lipschitz constant of Ht H gamma = 2 * .9/ (l * n_angles) x = x0 energy = np.inf for i in range(niter): eps = 1.e-4 # Forward part x = x.ravel()[:, np.newaxis] err = H * x - y back_proj = Ht * err # backward: TV and i_c proxs # TV part tmp_z_1 = 2 * x - z_1 - gamma * back_proj tmp_z_1 = tmp_z_1.reshape((l, l)) z_1 = z_1 + tv_denoise_fista(tmp_z_1, weight=2 * beta * gamma, eps=eps).ravel()[:, np.newaxis] - x # Projection on the interval tmp_z_2 = 2 * x - z_2 - gamma * back_proj if nonnegconst == 1: tmp_z_2[tmp_z_2 < val_min] = val_min #print 'Non negative constraint imposed.' tmp_z_2[tmp_z_2 > val_max] = val_max z_2 = z_2 - x + tmp_z_2 # update x: average of z_i x = (0.5 * (z_1 + z_2)).reshape(l, l) res.append(x) # compute the energy data_fidelity_err = 1./2 * (err**2).sum() tv_value = beta * tv_norm(x) energy = data_fidelity_err + tv_value energies.append(energy) # stop criterion if i>2 and np.abs(energy - energies[-2]) < stop_tol*energies[1]: break return res, energies def gfb_tv_weng(y, beta, niter, H=None, val_min=0, val_max=1, x0=None, stop_tol=1.e-4, xb=None, xa=None, beta2=None, nonnegconst=1): """ TV regression + interval constraint using the generalized forward backward splitting (GFB) with adjacent energies TV regularization Parameters ---------- y : ndarray of floats Measures (tomography projection). If H is given, y is a column vector. If H is not given, y is a 2-D array where each line is a projection along a different angle beta : float weight of TV norm niter : number of forward-backward iterations to perform H : sparse matrix or None tomography design matrix. Should be in csr format. If H is none, the projections as well as the back-projection are computed by a direct method, without writing explicitely the design matrix. val_min, val_max: floats We impose that the image values are in [val_min, val_max] x0 : ndarray of floats, optional (default is None) Initial guess at energy Em xb : ndarray of floats, optional (default is None) Initial guess at adjacent energy Em-1 xa : ndarray of floats, optional (default is None) Initial guess at adjacent energy Em+1 Returns ------- res : list list of iterates of the reconstructed images CSenergies : list values of the function to be minimized at the different iterations. Its values should be decreasing. Notes ----- This algorithm minimizes iteratively the CSenergy E(x) = 1/2 || H x - y ||^2 + beta TV(x) + +beta2 TV(x,xa,xb) + i_C(x) where TV(.) is the total variation pseudo-norm and i_C is the indicator function of the convex set [val_min, val_max]. The algorithm used the generalized forward-backward scheme z1_{n + 1} = z1_n - x_n + prox_{2 gamma beta TV(.)} (2*x_n - z1_n - gamma nabla f(x_n)) z2_{n+1} = z1_n - x_n + prox_{i_C(.)}(2*x_n - z2_n - gamma nabla f(x_n)) z3_{n + 1} = z1_n - x_n + 0.1 (xa+xb) + prox_{2 gamma beta2 TV(.)} (2*x_n - z1_n - gamma nabla f(x_n)) where f(x) = 1/2 || H x - y ||^2 This method can in fact be used for other sums of non-smooth functions for which the prox operator is known. References ---------- Hugo Raguet, Jalal M. Fadili and Gabriel Peyre, Generalized Forward-Backward Splitting Algorithm, preprint arXiv:1108.4404v2, 2011. See also http://www.ceremade.dauphine.fr/~peyre/numerical-tour/tours/inverse_9b_gfb/ """ n_angles, l = y.shape n_meas, n_pix = H.shape l = int(np.sqrt(n_pix)) n_angles = n_meas / l Ht = sparse.csr_matrix(H.transpose()) if x0 is None: x0 = np.zeros((l, l)) z_1 = np.zeros((l**2, 1)) z_2 = np.zeros((l**2, 1)) z_3 = np.zeros((l**2, 1)) res, energies = [], [] xb = xb.ravel()[:, np.newaxis] xa = xa.ravel()[:, np.newaxis] if beta2 == None: #beta2=beta beta2=0 # l * n_angles is the Lipschitz constant of Ht H gamma = 2 * .9/ (l * n_angles) x = x0 energy = np.inf for i in range(niter): eps = 1.e-4 # Forward part x = x.ravel()[:, np.newaxis] err = H * x - y back_proj = Ht * err # backward: TV and i_c proxs # TV part tmp_z_1 = 2 * x - z_1 - gamma * back_proj tmp_z_1 = tmp_z_1.reshape((l, l)) z_1 = z_1 + tv_denoise_fista(tmp_z_1, weight=2 * beta * gamma, eps=eps).ravel()[:, np.newaxis] - x # Projection on the interval tmp_z_2 = 2 * x - z_2 - gamma * back_proj if nonnegconst: tmp_z_2[tmp_z_2 < val_min] = val_min tmp_z_2[tmp_z_2 > val_max] = val_max z_2 = z_2 - x + tmp_z_2 if beta2 != 0: #Use images taken at energy before and after the current one tmp_z_3 = 2 * x - z_3 - gamma * back_proj tmp_z_3 = (tmp_z_3+(xa+xb)*.1).reshape((l, l)) z_3 = z_3 + tv_denoise_fista(tmp_z_3, weight=2 * beta2 * gamma, eps=eps).ravel()[:, np.newaxis] - x # update x: average of z_i x = (0.3333 * (z_1 + z_2 + z_3)).reshape(l, l) else: x = (0.5 * (z_1 + z_2)).reshape(l, l) res.append(x) # compute the energy data_fidelity_err = 1./2 * (err**2).sum() tv_value = beta * tv_norm(x) energy = data_fidelity_err + tv_value energies.append(energy) # stop criterion if i>2 and np.abs(energy - energies[-2]) < stop_tol*energies[1]: break return res, energies def gfb_tv_local(y, beta, niter, mask_pix, mask_reg, H=None, val_min=0, val_max=1, x0=None): """ TV regression + interval constraint using the generalized forward backward splitting (GFB), in local tomography mode. Parameters ---------- y : ndarray of floats Measures (tomography projection). If H is given, y is a column vector. If H is not given, y is a 2-D array where each line is a projection along a different angle beta : float weight of TV norm niter : number of forward-backward iterations to perform mask_pix: ndarray of bools Domain where pixels are reconstructed (typically, the disk inside a square). mask_reg: ndarray of bools Domain where the spatial regularization is performed H : sparse matrix or None tomography design matrix. Should be in csr format. If H is none, the projections as well as the back-projection are computed by a direct method, without writing explicitely the design matrix. val_min, val_max: floats We impose that the image values are in [val_min, val_max] x0 : ndarray of floats, optional (default is None) Initial guess Returns ------- res : list list of iterates of the reconstructed images energies : list values of the function to be minimized at the different iterations. Its values should be decreasing. Notes ----- This algorithm minimizes iteratively the energy E(x) = 1/2 || H x - y ||^2 + beta TV(x) + i_C(x) where TV(.) is the total variation pseudo-norm and i_C is the indicator function of the convex set [val_min, val_max]. The algorithm used the generalized forward-backward scheme z1_{n + 1} = z1_n - x_n + prox_{2 gamma beta TV(.)} (2*x_n - z1_n - gamma nabla f(x_n)) z2_{n+1} = z1_n - x_n + prox_{i_C(.)}(2*x_n - z2_n - gamma nabla f(x_n) where f(x) = 1/2 || H x - y ||^2 This method can in fact be used for other sums of non-smooth functions for which the prox operator is known. References ---------- Hugo Raguet, Jalal M. Fadili and Gabriel Peyre, Generalized Forward-Backward Splitting Algorithm, preprint arXiv:1108.4404v2, 2011. See also http://www.ceremade.dauphine.fr/~peyre/numerical-tour/tours/inverse_9b_gfb/ """ mask_reg = mask_reg[mask_pix] n_meas, n_pix = H.shape l = len(mask_pix) n_angles = n_meas / l Ht = sparse.csr_matrix(H.transpose()) z_1 = np.zeros((n_pix, 1)) z_2 = np.zeros((n_pix, 1)) res, energies = [], [] # l * n_angles is the Lipschitz constant of Ht H gamma = 2 * .5/ (l * n_angles) x0 = np.zeros(n_pix)[:, np.newaxis] x = x0 for i in range(niter): eps = 1.e-4 # Forward part err = H * x - y back_proj = Ht * err grad_descent = x - gamma * back_proj # backward: TV and i_c proxs # TV part tmp_z_1 = 2 * x - z_1 - gamma * back_proj tmp_z_1_2d = np.zeros((l, l)) tmp_z_1_2d[mask_pix] = tmp_z_1.ravel() z_1 = z_1 + tv_denoise_fista(tmp_z_1_2d, weight=2 * beta * gamma, eps=eps)[mask_pix][:, np.newaxis] - x # Projection on the interval tmp_z_2 = 2 * x - z_2 - gamma * back_proj tmp_z_2[tmp_z_2 < val_min] = val_min tmp_z_2[tmp_z_2 > val_max] = val_max z_2 = z_2 - x + tmp_z_2 # update x: average of z_i x = (0.5 * (z_1 + z_2)) x[~mask_reg] = grad_descent[~mask_reg] tmp = np.zeros((l, l)) tmp[mask_pix] = x.ravel() res.append(tmp) return res, energies ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/TomoCS/projections.py0000664000175000017500000003233514700316175021631 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Originally part of tomotv, code licensed under BSD license. # Website: https://github.com/emmanuelle/tomo-tv # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import absolute_import import numpy as np from scipy import sparse from scipy import ndimage from scipy import fftpack from ._rank_order import rank_order # --------------- Tomo projection operator -------------------- def build_projection_operator(l_x, n_dir=None, angles=None, l_det=None, subpix=1, offset=0, pixels_mask=None): """ Compute the tomography design matrix. Parameters ---------- l_x : int linear size of image array n_dir : int, default l_x number of angles at which projections are acquired. n_dir projection angles are regularly spaced between 0 and 180. l_det : int, default is l_x number of pixels in the detector. If l_det is not specified, we suppose that l_det = l_x. subpix : int, default 1 number of linear subdivisions used to compute the projection of one image pixel onto a detector pixel. For example, if subpix=2, one image pixel is divided into 2x2 subpixels that are projected onto the detector, and the value of the projections is computed from these 4 projections. offset : int, default 0 width of the strip of image pixels not covered by the detector. offset > 0 means that the image is acquired in local tomography (aka ROI) mode, with the image larger than the detector. If the linear size of the array is l_x, the size of the detector is l_x - 2 offset. pixels_mask : 1-d ndarray of size l_x**2 mask of pixels to keep in the matrix (useful if one wishes to remove pixels inside or outside of a circle, for example) Returns ------- p : sparse matrix of shape (n_dir l_x, l_x**2), in csr format Tomography design matrix. The csr (compressed sparse row) allows for efficient subsequent matrix multiplication. The dtype of the elements is float32, in order to save memory. Notes ----- The returned matrix is sparse, but may nevertheless require a lot of memory for large l_x. For example, with l_x=512 and n_dir=512, the operator takes around 3 Gb of memory. The memory cost is of the order of l_x^2 x n_dir x 8 in bytes. For a given angle, the center of the pixels are rotated by the angle, and projected onto the detector. The value of the data pixel is added to the two pixels of the detector in between which the projection is located, with weights determined by a linear interpolation. Using subpix > 1 slows down the computation of the operator, because a histogram in 2-D has to be computed in order to group the projections of subpixels corresponding to a single image pixel. (this should be accelerated by a Cython function... to be written) Examples -------- >>> # Image with 256 pixels, 128 directions >>> op = build_projection_operator(256, n_dir=128) >>> # Image with 128 pixels (to be reconstructed), 256 detector pixels >>> # subpix = 2 is used for a good precision of the projection of the >>> # coarse image pixels >>> op = build_projection_operator(128, n_dir=256, l_det=256, subpix=2) >>> # Image with 256 pixels, that is twice the size of the detector that >>> # has 128 pixels. >>> op = build_projection_operator(256, n_dir=256, l_det=128, offset=64) >>> # Image with 256 pixels, that is twice the size of the detector that >>> # has 256 pixels. We use subpixels for better precision. >>> op = build_projection_operator(256, n_dir=256, l_det=256, offset=64) >>> # Using a mask: projection operator only for pixels inside a >>> # central circle >>> l_x = 128 >>> X, Y = np.ogrid[:l_x, :l_x] >>> mask = (X - l_x/2)**2 + (Y - l_x/2)**2 < (l_x/2)**2 >>> op = build_projection_operator(l_x, pixels_mask=mask) >>> op.shape (16384, 12849) """ if l_det is None: l_det = l_x X, Y = _generate_center_coordinates(subpix*l_x) X *= 1./subpix Y *= 1./subpix Xbig, Ybig = _generate_center_coordinates(l_det) Xbig *= (l_x - 2*offset) / float(l_det) orig = Xbig.min() labels = None if subpix > 1: # Block-group subpixels Xlab, Ylab = np.mgrid[:subpix * l_x, :subpix * l_x] labels = (l_x * (Xlab / subpix) + Ylab / subpix).ravel() if n_dir is None: n_dir = l_x if angles is None: angles = np.linspace(0, np.pi, n_dir, endpoint=False) else: n_dir = len(angles) weights, data_inds, detector_inds = [], [], [] # Indices for data pixels. For each data, one data pixel # will contribute to the value of two detector pixels. for i, angle in enumerate(angles): # rotate data pixels centers Xrot = np.cos(angle) * X - np.sin(angle) * Y # compute linear interpolation weights inds, dat_inds, w = _weights_fast(Xrot, dx=(l_x - 2*offset)/float(l_det), orig=orig, labels=labels) # crop projections outside the detector mask = np.logical_and(inds >= 0, inds < l_det) weights.append(w[mask]) detector_inds.append((inds[mask] + i * l_det).astype(np.int32)) data_inds.append(dat_inds[mask]) weights = np.concatenate(weights) weights /= subpix**2 detector_inds = np.concatenate(detector_inds) data_inds = np.concatenate(data_inds) if pixels_mask is not None: if pixels_mask.ndim > 1: pixels_mask = pixels_mask.ravel() mask = pixels_mask[data_inds] data_inds = data_inds[mask] data_inds = rank_order(data_inds)[0] detector_inds = detector_inds[mask] weights = weights[mask] proj_operator = sparse.coo_matrix((weights, (detector_inds, data_inds))) return sparse.csr_matrix(proj_operator) def _weights_fast(x, dx=1, orig=0, ravel=True, labels=None): """ Compute linear interpolation weights for projection array `x` and regularly spaced detector pixels separated by `dx` and starting at `orig`. """ if ravel: x = np.ravel(x) floor_x = np.floor((x - orig) / dx).astype(np.int32) alpha = ((x - orig - floor_x * dx) / dx).astype("float32") inds = np.hstack((floor_x, floor_x + 1)) weights = np.hstack((1 - alpha, alpha)) data_inds = np.arange(x.size, dtype=np.int32) data_inds = np.hstack((data_inds, data_inds)) if labels is not None: data_inds = np.hstack((labels, labels)) order = np.argsort(inds) inds, data_inds, weights = inds[order], data_inds[order], weights[order] steps = np.nonzero(np.diff(inds) > 0)[0] + 1 steps = np.concatenate(([0], steps)) inds_s, data_inds_s, weights_s = [], [], [] for i in range(len(steps) - 1): d, w = data_inds[steps[i]:steps[i+1]], \ weights[steps[i]:steps[i+1]] count = np.bincount(d, weights=w) mask = count>0 w = count[mask] weights_s.append(w) datind = np.arange(len(mask))[mask] data_inds_s.append(datind) detind = inds[steps[i]]*np.ones(mask.sum()) inds_s.append(detind) #stop inds = np.concatenate(inds_s) data_inds = np.concatenate(data_inds_s) weights = np.concatenate(weights_s) return inds, data_inds, weights def _weights(x, dx=1, orig=0, ravel=True, labels=None): """ Compute linear interpolation weights for projection array `x` and regularly spaced detector pixels separated by `dx` and starting at `orig`. """ if ravel: x = np.ravel(x) floor_x = np.floor((x - orig) / dx).astype(np.int32) alpha = ((x - orig - floor_x * dx) / dx).astype("float32") inds = np.hstack((floor_x, floor_x + 1)) weights = np.hstack((1 - alpha, alpha)) data_inds = np.arange(x.size, dtype=np.int32) data_inds = np.hstack((data_inds, data_inds)) if labels is not None: data_inds = np.hstack((labels, labels)) w = np.histogram2d(data_inds, inds, bins=(np.arange(data_inds.max()+1.5), np.arange(inds.max()+1.5)), weights=weights)[0] data_inds, inds = np.argwhere(w>0).T weights = w[w>0] return inds, data_inds, weights def _weights_nn(x, dx=1, orig=0, ravel=True): """ Nearest-neighbour interpolation """ if ravel: x = np.ravel(x) floor_x = np.floor(x - orig) return floor_x.astype(float32) def _generate_center_coordinates(l_x): """ Compute the coordinates of pixels centers for an image of linear size l_x """ l_x = float(l_x) X, Y = np.mgrid[:l_x, :l_x] center = l_x / 2. X += 0.5 - center Y += 0.5 - center return X, Y # ----------------- Direct projection method ------------------------- # (without computing explicitely the design matrix) def back_projection(projections): """ Back-projection (without filtering) Parameters ---------- projections: ndarray of floats, of shape n_dir x l_x Each line of projections is the projection of a data image acquired at a different angle. The projections angles are supposed to be regularly spaced between 0 and 180. Returns ------- recons: ndarray of shape l_x x l_x Reconstructed array Notes ----- A linear interpolation is used when rotating the back-projection. This function uses ``scipy.ndimage.rotate`` for the rotation. """ n_dir, l_x = projections.shape recons = np.zeros((l_x, l_x), dtype=float) angles = np.linspace(0, 180, n_dir, endpoint=False) for angle, line in zip(angles, projections): # BP: repeat the detector line along the direction of the beam tmp = np.tile(line[:, np.newaxis], (1, l_x)) # Rotate the back-projection of the detector line, and add # it to the reconstructed image recons += ndimage.rotate(tmp, -angle, order=1, \ reshape=False) return recons def projection(im, n_dir=None, interpolation='nearest'): """ Tomography projection of an image along n_dir directions. Parameters ---------- im : ndarray of square shape l_x x l_x Image to be projected n_dir : int Number of projection angles. Projection angles are regularly spaced between 0 and 180. interpolation : str, {'interpolation', 'nearest'} Interpolation method used during the projection. Default is 'nearest'. Returns ------- projections: ndarray of shape n_dir x l_x Array of projections. Notes ----- The centers of the data pixels are projected onto the detector, then the contribution of a data pixel to detector pixels is computed by nearest neighbor or linear interpolation. The function ``np.bincount`` is used to compute the projection, with weights corresponding to the values of data pixels, multiplied by interpolation weights in the case of linear interpolation. """ l_x = len(im) if n_dir is None: n_dir = l_x im = im.ravel() projections = np.empty((n_dir, l_x)) X, Y = _generate_center_coordinates(l_x) angles = np.linspace(0, np.pi, n_dir, endpoint=False) for i, angle in enumerate(angles): Xrot = np.cos(angle) * X - np.sin(angle) * Y if interpolation == 'nearest': inds = _weights_nn(Xrot, dx=1, orig=X.min()) mask = inds>= 0 w = im[mask] elif interpolation == 'linear': inds, _, w = _weights(Xrot, dx=1, orig=X.min()) w[:l_x**2] *= im w[l_x**2:] *= im mask = inds >= 0 w = w[mask] projections[i] = np.bincount(inds[mask].astype(np.int), \ weights=w)[:l_x] return projections # -----------------Filtered back-projection---------------------- def filter_projections(proj_set, reg=False): """ Ramp filter used in the filtered back projection. We use zero padding. Parameters ---------- proj_set: 2-d ndarray each line is one projection (1 line of the detector) to be filtered Returns ------- res: 2-d ndarray filtered projections Notes ----- We use zero padding. However, we do not use any filtering (hanning, etc.) in the FFT yet. """ nb_angles, l_x = proj_set.shape # Assum l is even for now ramp = 1./l_x * np.hstack((np.arange(l_x), np.arange(l_x, 0, -1))) return fftpack.ifft(ramp * fftpack.fft(proj_set, 2*l_x, axis=1), axis=1)[:,:l_x] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/TomoCS/sirt.py0000664000175000017500000002351414700316175020252 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2016 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division import os import numpy as np #from skimage.morphology._greyreconstruct import reconstruction_loop M_PI = 3.14159265358979323846264338327 #----------------------------------------------------------------------- def preprocessing(ry, rz, num_pixels, center, gridx, gridy) : for i in range(ry): gridx[i] = -ry/2.+i for i in range(rz): gridy[i] = -rz/2.+i mov = float(num_pixels)/2.0-center if (mov-np.ceil(mov) < 1e-2): mov += 1e-2 return mov, gridx, gridy #----------------------------------------------------------------------- def calc_quadrant(theta_p) : if ((theta_p >= 0 and theta_p < M_PI/2) or (theta_p >= M_PI and theta_p < 3*M_PI/2)) : quadrant = True else : quadrant = False return quadrant #----------------------------------------------------------------------- def calc_coords(ry, rz, xi, yi, sin_p, cos_p, gridx, gridy, coordx, coordy): srcx = xi*cos_p-yi*sin_p srcy = xi*sin_p+yi*cos_p detx = -xi*cos_p-yi*sin_p dety = -xi*sin_p+yi*cos_p slope = (srcy-dety)/(srcx-detx) islope = 1/slope for n in range(ry+1): coordy[n] = slope*(gridx[n]-srcx)+srcy for n in range(rz+1) : coordx[n] = islope*(gridy[n]-srcy)+srcx return coordx, coordy #----------------------------------------------------------------------- def trim_coords( ry, rz, coordx, coordy, gridx, gridy, ax, ay, bx, by): asize = 0 bsize = 0 for n in range(rz+1): if (coordx[n] > gridx[0]) : if (coordx[n] < gridx[ry]) : ax[asize] = coordx[n] ay[asize] = gridy[n] asize +=1 for n in range(ry+1): if (coordy[n] > gridy[0]) : if (coordy[n] < gridy[rz]) : bx[bsize] = gridx[n] by[bsize] = coordy[n] bsize +=1 return asize, ax, ay, bsize, bx, by #----------------------------------------------------------------------- def sort_intersections( ind_condition, asize, ax, ay, bsize, bx, by, coorx, coory) : i=0 j=0 k=0 while (ix1) indy = i2-(i2>x2) indi[n] = indy+(indx*rz) return indi, dist #----------------------------------------------------------------------- def calc_simdata( p, s, c, ry, rz, num_slices, num_pixels, csize, indi, dist, model, simdata): index_model = s*ry*rz index_data = c+s*num_pixels+p*num_slices*num_pixels for n in range(csize-1): simdata[index_data] += model[indi[n]+index_model]*dist[n] #simdata[index_data] += model[p,s,c]*dist[n] return simdata #----------------------------------------------------------------------- # Calculate sirt # tomo : ndarray # 3D tomographic data. # theta : array # Projection angles in radian. # recon : ndarray, optional # Initial values of the reconstruction object. # num_gridx, num_gridy : int, optional # Number of pixels along x- and y-axes in the reconstruction grid. # num_iter : int, optional # Number of algorithm iterations performed. def calculate_sirt(tomo, theta, num_iter): dx, dy, dz = tomo.shape print ('Calculate SIRT reconstruction') center = np.ones(dy, dtype='float32') * dz / 2. ngridx = dz ngridy = dz #tomo = -np.log(tomo) #recon = 1e-6 * np.ones((dy, ngridx, ngridy), dtype='float32') recon = 1e-6 * np.ones((dy*ngridx*ngridy), dtype='float32') print ('tomoshape', tomo.shape) data = tomo gridx = np.zeros((ngridx+1), dtype='float32') gridy = np.zeros((ngridy+1), dtype='float32') coordx = np.zeros((ngridy+1), dtype='float32') coordy = np.zeros((ngridx+1), dtype='float32') ax = np.zeros((ngridx+ngridy), dtype='float32') ay = np.zeros((ngridx+ngridy), dtype='float32') bx = np.zeros((ngridx+ngridy), dtype='float32') by = np.zeros((ngridx+ngridy), dtype='float32') coorx = np.zeros((ngridx+ngridy), dtype='float32') coory = np.zeros((ngridx+ngridy), dtype='float32') dist = np.zeros((ngridx+ngridy), dtype='float32') indi = np.zeros((ngridx+ngridy), dtype='int') for i in range(num_iter): print ('Iteration ', i) simdata = np.zeros((dx*dy*dz), dtype='float32') #For each slice for s in range(dy): print ('Slice', s) mov, gridx, gridy = preprocessing(ngridx, ngridy, dz, center[s], gridx, gridy) sum_dist = np.zeros((ngridx*ngridy), dtype='float32') update = np.zeros((ngridx*ngridy), dtype='float32') # For each projection angle for p in range(dx): # Calculate the sin and cos values # of the projection angle and find # at which quadrant on the cartesian grid. theta_p = np.fmod(theta[p], 2*M_PI) quadrant = calc_quadrant(theta_p) sin_p = np.sin(theta_p) cos_p = np.cos(theta_p) # For each detector pixel for d in range(dz): # Calculate coordinates xi = -1e6 yi = -(dz-1)/2.0+d+mov coordx, coordy = calc_coords( ngridx, ngridy, xi, yi, sin_p, cos_p, gridx, gridy, coordx, coordy) # Merge the (coordx, gridy) and (gridx, coordy) asize, ax, ay, bsize, bx, by = trim_coords( ngridx, ngridy, coordx, coordy, gridx, gridy, ax, ay, bx, by) # Sort the array of intersection points (ax, ay) and # (bx, by). The new sorted intersection points are # stored in (coorx, coory). Total number of points # are csize. csize = sort_intersections( quadrant, asize, ax, ay, bsize, bx, by, coorx, coory) # Calculate the distances (dist) between the # intersection points (coorx, coory). Find the # indices of the pixels on the reconstruction grid. indi, dist = calc_dist( ngridx, ngridy, csize, coorx, coory, indi, dist) # Calculate simdata simdata = calc_simdata(p, s, d, ngridx, ngridy, dy, dz, csize, indi, dist, recon, simdata) # Calculate dist*dist sum_dist2 = 0.0 for n in range(csize-1): sum_dist2 += dist[n]*dist[n] sum_dist[indi[n]] += dist[n] # Update if (sum_dist2 != 0.0) : ind_data = d+s*dz+p*dy*dz upd = (data[p,s,d]-simdata[ind_data])/sum_dist2 for n in range(csize-1): update[indi[n]] += upd*dist[n] m = 0 for n in range(ngridx*ngridy): if (sum_dist[n] != 0.0) : ind_recon = s*ngridx*ngridy recon[m+ind_recon] += update[m]/sum_dist[n] m+=1 recon = np.reshape(recon,(dy, ngridx, ngridy), order='C') print ('SIRT Reconstruction Done') return recon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677150153.0 mantis_xray-3.2.2/mantis_xray/TomoCS/tv_denoising.py0000664000175000017500000001374114375643711021771 0ustar00wattswattsfrom __future__ import print_function # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Originally part of tomotv, code licensed under BSD license. # Website: https://github.com/emmanuelle/tomo-tv # # Mantis 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 # any later version. # # Mantis 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 . import numpy as np def div(grad): """ Compute divergence of image gradient """ res = np.zeros(grad.shape[1:]) for d in range(grad.shape[0]): this_grad = np.rollaxis(grad[d], d) this_res = np.rollaxis(res, d) this_res[:-1] += this_grad[:-1] this_res[1:-1] -= this_grad[:-2] this_res[-1] -= this_grad[-2] return res def gradient(img): """ Compute gradient of an image Parameters =========== img: ndarray N-dimensional image Returns ======= gradient: ndarray Gradient of the image: the i-th component along the first axis is the gradient along the i-th axis of the original array img """ shape = [img.ndim, ] + list(img.shape) gradient = np.zeros(shape, dtype=img.dtype) # 'Clever' code to have a view of the gradient with dimension i stop # at -1 slice_all = [0, slice(None, -1),] for d in range(img.ndim): gradient[tuple(slice_all)] = np.diff(img, axis=d) slice_all[0] = d + 1 slice_all.insert(1, slice(None)) return gradient def _projector_on_dual(grad): """ modifies in place the gradient to project it on the L2 unit ball """ norm = np.maximum(np.sqrt(np.sum(grad**2, 0)), 1.) for grad_comp in grad: grad_comp /= norm return grad def dual_gap(im, new, gap, weight): """ dual gap of total variation denoising see "Total variation regularization for fMRI-based prediction of behavior", by Michel et al. (2011) for a derivation of the dual gap """ im_norm = (im**2).sum() gx, gy = np.zeros_like(new), np.zeros_like(new) gx[:-1] = np.diff(new, axis=0) gy[:, :-1] = np.diff(new, axis=1) if im.ndim == 3: gz = np.zeros_like(new) gz[..., :-1] = np.diff(new, axis=2) tv_new = 2 * weight * np.sqrt(gx**2 + gy**2 + gz**2).sum() else: tv_new = 2 * weight * np.sqrt(gx**2 + gy**2).sum() dual_gap = (gap**2).sum() + tv_new - im_norm + (new**2).sum() return 0.5 / im_norm * dual_gap def tv_denoise_fista(im, weight=50, eps=5.e-5, n_iter_max=200, check_gap_frequency=3): """ Perform total-variation denoising on 2-d and 3-d images Find the argmin `res` of 1/2 * ||im - res||^2 + weight * TV(res), where TV is the isotropic l1 norm of the gradient. Parameters ---------- im: ndarray of floats (2-d or 3-d) input data to be denoised. `im` can be of any numeric type, but it is cast into an ndarray of floats for the computation of the denoised image. weight: float, optional denoising weight. The greater ``weight``, the more denoising (at the expense of fidelity to ``input``) eps: float, optional precision required. The distance to the exact solution is computed by the dual gap of the optimization problem and rescaled by the l2 norm of the image (for contrast invariance). n_iter_max: int, optional maximal number of iterations used for the optimization. Returns ------- out: ndarray denoised array Notes ----- The principle of total variation denoising is explained in http://en.wikipedia.org/wiki/Total_variation_denoising The principle of total variation denoising is to minimize the total variation of the image, which can be roughly described as the integral of the norm of the image gradient. Total variation denoising tends to produce "cartoon-like" images, that is, piecewise-constant images. This function implements the FISTA (Fast Iterative Shrinkage Thresholding Algorithm) algorithm of Beck et Teboulle, adapted to total variation denoising in "Fast gradient-based algorithms for constrained total variation image denoising and deblurring problems" (2009). """ if not im.dtype.kind == 'f': im = im.astype(float) shape = [im.ndim, ] + list(im.shape) grad_im = np.zeros(shape) grad_aux = np.zeros(shape) t = 1. i = 0 while i < n_iter_max: error = weight * div(grad_aux) - im grad_tmp = gradient(error) grad_tmp *= 1./ (8 * weight) grad_aux += grad_tmp grad_tmp = _projector_on_dual(grad_aux) t_new = 1. / 2 * (1 + np.sqrt(1 + 4 * t**2)) t_factor = (t - 1) / t_new grad_aux = (1 + t_factor) * grad_tmp - t_factor * grad_im grad_im = grad_tmp t = t_new if (i % check_gap_frequency) == 0: gap = weight * div(grad_im) new = im - gap dgap = dual_gap(im, new, gap, weight) if dgap < eps: break i += 1 return new if __name__ == '__main__': from scipy.misc import face import matplotlib.pyplot as plt from time import time l = face().astype(float) # normalize image between 0 and 1 l /= l.max() l += 0.1 * l.std() * np.random.randn(*l.shape) t0 = time() res = tv_denoise_fista(l, weight=0.05, eps=5.e-5) t1 = time() print(t1 - t0) plt.figure() plt.subplot(121) plt.imshow(l, cmap='gray') plt.subplot(122) plt.imshow(res, cmap='gray') plt.show() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/TomoCS/util.py0000664000175000017500000000564714700316175020255 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Originally part of tomotv, code licensed under BSD license. # Website: https://github.com/emmanuelle/tomo-tv # # Mantis 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 # any later version. # # Mantis 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 . import numpy as np from scipy import ndimage def generate_synthetic_data(l_x=128, seed=None, crop=True, n_pts=25): """ Generate synthetic binary data looking like phase separation Parameters ---------- l_x: int, default 128 Linear size of the returned image seed: int, default 0 seed with which to initialize the random number generator. crop: bool, default True If True, non-zero data are found only within a central circle of radius l_x / 2 n_pts: int, default 25 number of seeds used to generate the structures. The larger n_pts, the finer will be the structures. Returns ------- res: ndarray of float32, of shape lxl Output binary image Examples -------- >>> im = generate_synthetic_data(l_x=256, seed=2, n_pts=25) >>> # Finer structures >>> im = generate_synthetic_data(l_x=256, n_pts=100) """ if seed is None: seed = 0 # Fix the seed for reproducible results rs = np.random.RandomState(seed) x, y = np.ogrid[:l_x, :l_x] mask = np.zeros((l_x, l_x)) points = l_x * rs.rand(2, n_pts) mask[(points[0]).astype(np.int), (points[1]).astype(np.int)] = 1 mask = ndimage.gaussian_filter(mask, sigma=l_x / (4. * np.sqrt(n_pts))) # Limit the non-zero data to a central circle if crop: mask_outer = (x - l_x / 2) ** 2 + (y - l_x / 2) ** 2 < (l_x / 2) ** 2 mask = np.logical_and(mask > mask.mean(), mask_outer) else: mask = mask > mask.mean() return mask.astype("float32") def tv_l0_norm(im): """Compute the (isotropic) TV norm of an image""" grad_x1 = np.diff(im, axis=0) grad_x2 = np.diff(im, axis=1) return (grad_x1[:, :-1]**2 + grad_x2[:-1, :]**2 > 0).mean() def compute_sparsity(im): l_x = len(im) X, Y = np.ogrid[:l_x, :l_x] mask = ((X - l_x/2)**2 + (Y - l_x/2)**2 <= (l_x/2)**2) grad1 = ndimage.morphological_gradient(im, footprint=np.ones((3, 3))) grad2 = ndimage.morphological_gradient(im, footprint=ndimage.generate_binary_structure(2, 1)) return (grad1[mask] > 0).mean(), (grad2[mask] > 0).mean() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/__init__.py0000664000175000017500000000002614700316175017655 0ustar00wattswatts__version__ = '3.2.2' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/__main__.py0000644000175000017500000000142114332463747017645 0ustar00wattswattsimport sys, getopt def main(args=None): """The main routine.""" if args is None: args = sys.argv[1:] # Do argument parsing here (eg. with argparse) and anything else # you want your project to do. Return values are exit codes. try: options, extraParams = getopt.getopt(args, '', ['batch', 'nnma']) except: print('Error - wrong command line option used. Available options are --batch and --nnma') return batch_mode = False for opt, arg in options: if opt in '--batch': batch_mode = True if batch_mode: from . import mantis mantis.main() else: from . import mantis_qt mantis_qt.main() # Open the GUI if __name__ == "__main__": sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/analyze.py0000775000175000017500000027374614700316175017611 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division import os import copy import numpy as np import scipy.interpolate import scipy.spatial import scipy.ndimage from scipy.cluster.vq import kmeans2, whiten import scipy.signal #------------------------------------------------------------------------------ def erf(x): # save the sign of x sign = 1 if x < 0: sign = -1 x = abs(x) # constants a1 = 0.254829592 a2 = -0.284496736 a3 = 1.421413741 a4 = -1.453152027 a5 = 1.061405429 p = 0.3275911 # A&S formula 7.1.26 t = 1.0/(1.0 + p*x) y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*np.exp(-x*x) return sign*y # erf(-x) = -erf(x) def stepfunc(p, x): #JS Nexafs book - step function # P - position of the inflection point # H - step height # G - FWHM width of the step # E - independent variable, energy (x) P = p[0] H = p[1] G = p[2] c = 1.665 y = H*(0.5+0.5*erf((x-P)/(G/c))) return y def gaussian(p, x): A = p[0] mu = p[1] sigma = p[2] #offset = p[3] y = A * np.exp(-((x-mu)**2)/(2*sigma**2))#+offset return y def model(p, nsteps, npeaks, x): offset = p[0] pg = nsteps*3+1 istepfitparams = p[1:pg] y = np.zeros((x.size)) if nsteps > 0: for i in range(x.size): y[i] = stepfunc(istepfitparams, x[i]) for i in range(npeaks): pp = [p[pg+i*3],p[pg+1+i*3],p[pg+2+i*3]] y = y + gaussian(pp,x) y = y+offset return y def model_error(p, nsteps, npeaks, x, y): # err = np.zeros(x.size) # for i in range(x.size): # err[i] = (y[i]-model(p, x[i])) # return err err = y-model(p, nsteps, npeaks, x) return err #----------------------------------------------------------------------- class Cfitparams: def __init__(self): self.base = 0.0 self.stepfitparams = np.zeros((8)) self.gauss_fp_a = np.zeros((12)) self.gauss_fp_m = np.zeros((12)) self.gauss_fp_s = np.zeros((12)) #----------------------------------------------------------------------- class analyze: def __init__(self, stkdata): self.stack = stkdata self.pca_calculated = 0 self.clusters_calculated = 0 self.target_spectra = 0 self.tspectrum_loaded = 0 self.n_target_spectra = 0 self.tspec_names = [] self.xrayfitsp_loaded = 0 self.xrayfitspectra = 0 self.n_xrayfitsp = 0 self.xfspec_names = [] self.xfitpars = [] self.pcaimages4D = [] self.eigenvals4D = [] self.eigenvecs4D = [] self.variance4D = [] self.pcaimagebounds4D = [] self.target_svd_maps4D = [] self.original_svd_maps4D = [] self.target_pcafit_maps4D = [] self.original_fit_maps4D = [] self.target_pcafit_coeffs4D = [] self.target_pcafit_spectra4D = [] #----------------------------------------------------------------------- # Calculate pca def delete_data(self): self.target_spectra = 0 self.tspectrum_loaded = 0 self.n_target_spectra = 0 self.tspec_names = [] self.pcaimages = 0 self.pcaimagebounds = 0 self.eigenvals = 0 self.eigenvecs = 0 self.cluster_distances = 0 self.clustersizes = 0 self.cluster_indices = 0 self.clusterspectra = 0 #----------------------------------------------------------------------- # Calculate pca def calculate_pca(self): #covariance matrix n_pix = self.stack.n_cols*self.stack.n_rows od = self.stack.od #normalize od spectra - not used in pca_gui.pro #norms = np.apply_along_axis(np.linalg.norm, 1, od) odn = np.zeros((n_pix, self.stack.n_ev)) for i in range(n_pix): odn[i,:] = od[i,:]/np.linalg.norm(od[i,:]) covmatrix = np.dot(od.T,od) self.pcaimages = np.zeros((self.stack.n_cols, self.stack.n_rows, self.stack.n_ev)) self.pcaimagebounds = np.zeros((self.stack.n_ev)) try: self.eigenvals, self.eigenvecs = np.linalg.eigh(covmatrix) #sort the eigenvals and eigenvecs perm = np.argsort(-np.abs(self.eigenvals)) self.eigenvals = self.eigenvals[perm] self.eigenvecs = self.eigenvecs[:,perm] self.pcaimages = np.dot(od,self.eigenvecs) #calculate eigenimages self.pcaimages = np.reshape(self.pcaimages, (self.stack.n_cols, self.stack.n_rows, self.stack.n_ev), order='F') #Find bounds for displaying color-tables for i in range(self.stack.n_ev): min_val = np.amin(self.pcaimages[:,:,i]) max_val = np.amax(self.pcaimages[:,:,i]) self.pcaimagebounds[i] = np.amax((np.abs(min_val), np.abs(max_val))) #calculate variance captured by the pca components self.variance = self.eigenvals.copy() totalvar = self.variance.sum() self.variance = self.variance/totalvar #Scree plot - find an elbow in the curve - between 1 and 20 components maxpoints = min(25, self.stack.n_ev-1) #Find a line between first (x1, y1) and last point (x2, y2) and calculate distances: y2 = np.log(self.eigenvals[maxpoints]) x2 = maxpoints y1 = np.log(self.eigenvals[0]) x1 = 0 #Calculate distances between all the points and the line x1 and x2 are points on the line and x0 are eigenvals distance = np.zeros((maxpoints)) for i in range(maxpoints): y0 = np.log(self.eigenvals[i]) x0=i distance[i] = np.abs((x2-x1)*(y1-y0)-(x1-x0)*(y2-y1))/np.sqrt((x2-x1)**2+(y2-y1)**2) #Point with the largest distance is the "elbow" sigpca = np.argmax(distance) self.numsigpca = sigpca + 1 except: print( "pca not converging") self.pca_calculated = 1 if self.n_target_spectra > 1: self.fit_target_spectra() return #----------------------------------------------------------------------- # Calculate pca def calculate_pca_4D(self): #covariance matrix n_pix = self.stack.n_cols*self.stack.n_rows self.pcaimages4D = [] self.eigenvals4D = [] self.eigenvecs4D = [] self.variance4D = [] self.pcaimagebounds4D = [] for jth in range(self.stack.n_theta): od3d = self.stack.od4D[:,:,:,jth] od = od3d.copy() od = np.reshape(od, (n_pix, self.stack.n_ev), order='F') #normalize od spectra - not used in pca_gui.pro #norms = np.apply_along_axis(np.linalg.norm, 1, od) odn = np.zeros((n_pix, self.stack.n_ev)) for i in range(n_pix): odn[i,:] = od[i,:]/np.linalg.norm(od[i,:]) covmatrix = np.dot(od.T,od) self.pcaimages = np.zeros((self.stack.n_cols, self.stack.n_rows, self.stack.n_ev)) self.pcaimagebounds = np.zeros((self.stack.n_ev)) try: self.eigenvals, self.eigenvecs = np.linalg.eigh(covmatrix) #sort the eigenvals and eigenvecs perm = np.argsort(-np.abs(self.eigenvals)) self.eigenvals = self.eigenvals[perm] self.eigenvecs = self.eigenvecs[:,perm] self.pcaimages = np.dot(od,self.eigenvecs) #calculate eigenimages self.pcaimages = np.reshape(self.pcaimages, (self.stack.n_cols, self.stack.n_rows, self.stack.n_ev), order='F') #Find bounds for displaying color-tables for i in range(self.stack.n_ev): min_val = np.amin(self.pcaimages[:,:,i]) max_val = np.amax(self.pcaimages[:,:,i]) self.pcaimagebounds[i] = np.amax((np.abs(min_val), np.abs(max_val))) #calculate variance captured by the pca components self.variance = self.eigenvals.copy() totalvar = self.variance.sum() self.variance = self.variance/totalvar #Scree plot - find an elbow in the curve - between 1 and 20 components maxpoints = min(25, self.stack.n_ev-1) #Find a line between first (x1, y1) and last point (x2, y2) and calculate distances: y2 = np.log(self.eigenvals[maxpoints]) x2 = maxpoints y1 = np.log(self.eigenvals[0]) x1 = 0 #Calculate distances between all the points and the line x1 and x2 are points on the line and x0 are eigenvals distance = np.zeros((maxpoints)) for i in range(maxpoints): y0 = np.log(self.eigenvals[i]) x0=i distance[i] = np.abs((x2-x1)*(y1-y0)-(x1-x0)*(y2-y1))/np.sqrt((x2-x1)**2+(y2-y1)**2) #Point with the largest distance is the "elbow" sigpca = np.argmax(distance) self.numsigpca = sigpca + 1 except: print ("pca not converging") self.pca_calculated = 1 if self.n_target_spectra > 1: self.fit_target_spectra() self.pcaimages4D.append(self.pcaimages) self.eigenvals4D.append(self.eigenvals) self.eigenvecs4D.append(self.eigenvecs) self.variance4D.append(self.variance) self.pcaimagebounds4D.append(self.pcaimagebounds) return #----------------------------------------------------------------------- # Move PC up def move_pc_up(self, ipc): if ipc == 0: return if len(self.pcaimages4D) == 0: temp = self.pcaimages.copy() self.pcaimages[:,:, ipc] = temp[:,:, ipc-1] self.pcaimages[:,:, ipc-1] = temp[:,:, ipc] temp = self.pcaimagebounds.copy() self.pcaimagebounds[ipc] = temp[ipc-1] self.pcaimagebounds[ipc-1] = temp[ipc] temp = self.eigenvals.copy() self.eigenvals[ipc] = temp[ipc-1] self.eigenvals[ipc-1] = temp[ipc] temp = self.eigenvecs.copy() self.eigenvecs[:, ipc] = temp[:, ipc-1] self.eigenvecs[:, ipc-1] = temp[:, ipc] temp = self.variance.copy() self.variance[ipc] = temp[ipc-1] self.variance[ipc-1] = temp[ipc] else: for jth in range(self.stack.n_theta): temp = self.pcaimages4D[jth].copy() self.pcaimages4D[jth][:,:, ipc] = temp[:,:, ipc-1] self.pcaimages4D[jth][:,:, ipc-1] = temp[:,:, ipc] temp = self.pcaimagebounds4D[jth].copy() self.pcaimagebounds4D[jth][ipc] = temp[ipc-1] self.pcaimagebounds4D[jth][ipc-1] = temp[ipc] temp = self.eigenvals4D[jth].copy() self.eigenvals4D[jth][ipc] = temp[ipc-1] self.eigenvals4D[jth][ipc-1] = temp[ipc] temp = self.eigenvecs4D[jth].copy() self.eigenvecs4D[jth][:, ipc] = temp[:, ipc-1] self.eigenvecs4D[jth][:, ipc-1] = temp[:, ipc] temp = self.variance4D[jth].copy() self.variance4D[jth][ipc] = temp[ipc-1] self.variance4D[jth][ipc-1] = temp[ipc] if self.n_target_spectra > 1: self.fit_target_spectra() if len(self.target_svd_maps4D) > 0: self.calculate_targetmaps_4D() #----------------------------------------------------------------------- # Find clusters def calculate_clusters(self, nclusters, remove1stpca = 0, sigmasplit = 0, pcscalingfactor = 0.0): #Reduced data matrix od_reduced(n_pixels,n_significant_components) #od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows, self.numsigpca)) self.nclusters = nclusters npixels = self.stack.n_cols * self.stack.n_rows inverse_n_pixels = 1./float(npixels) inverse_n_pixels_less_one = 1./float(npixels-1) dc_offsets = np.zeros((self.numsigpca)) #rms_deviations = np.zeros((self.numsigpca)) od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows,self.numsigpca)) for i in range(self.numsigpca): eimage = self.pcaimages[:,:,i] dc_offsets[i] = np.sum(eimage)*inverse_n_pixels # Since we're looking at deviations from an average, # we divide by (N-1). #rms_deviations[i] = np.sqrt(np.sum((eimage-dc_offsets[i])**2)*inverse_n_pixels_less_one) # The straightforward thing is to do # d_reduced[i,0:(n_pixels-1)] = eimage # However, things work much better if we subtract the # DC offsets from each eigenimage. One could also divide # by rms_deviations, but that seems to overweight # the sensitivity to weaker components too much. rms_gamma = pcscalingfactor od_reduced[:,:,i] = (eimage-dc_offsets[i]) *(self.eigenvals[0]/self.eigenvals[i])**rms_gamma if remove1stpca == 0 : #od_reduced = od_reduced[:,:,0:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca), order='F') else: od_reduced = od_reduced[:,:,1:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca-1), order='F') indx = np.zeros(npixels) clustercentroids, indx = kmeans2(od_reduced, nclusters, iter=200, minit = 'points' ) #calculate cluster distances self.cluster_distances = np.zeros((self.stack.n_cols*self.stack.n_rows)) for i in range(npixels): clind = indx[i] self.cluster_distances[i] = scipy.spatial.distance.euclidean(od_reduced[i,:],clustercentroids[clind,:]) self.cluster_distances = np.reshape(self.cluster_distances, (self.stack.n_cols, self.stack.n_rows), order='F') indx = np.reshape(indx, (self.stack.n_cols, self.stack.n_rows), order='F') self.clustersizes = np.zeros((nclusters,), dtype=int) for i in range(nclusters): clind = np.where(indx == i) self.clustersizes[i] = indx[clind].shape[0] #sort the data with the cluster with the most members first count_indices = np.argsort(self.clustersizes) count_indices = count_indices[::-1] self.cluster_indices = np.zeros((self.stack.n_cols, self.stack.n_rows), dtype=int) self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) for i in range(nclusters): clind = np.where(indx == count_indices[i]) self.cluster_indices[clind] = i self.clustersizes[i] = self.cluster_indices[clind].shape[0] for ie in range(self.stack.n_ev): thiseng_od = self.stack.od3d[:,:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors indx = np.reshape(self.cluster_indices, (npixels), order='F') self.sse = np.zeros((npixels)) for i in range(npixels): clind = indx[i] self.sse[i] = np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:])) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') if (sigmasplit ==1): #Check the validity of cluster analysis and if needed add another cluster new_cluster_indices = self.cluster_indices.copy() new_nclusters = nclusters recalc_clusters = False for i in range(nclusters): clind = np.where(self.cluster_indices == i) cl_sse_mean = np.mean(self.sse[clind]) cl_see_std = np.std(self.sse[clind]) sigma9 = cl_sse_mean+9*cl_see_std maxsse = np.max(self.sse[clind]) if (maxsse > sigma9): recalc_clusters = True sse_helper = np.zeros((self.stack.n_cols, self.stack.n_rows), dtype=int) sse_helper[clind] = self.sse[clind] newcluster_ind = np.where(sse_helper > sigma9) new_cluster_indices[newcluster_ind] = new_nclusters new_nclusters += 1 if recalc_clusters == True: nclusters = new_nclusters self.cluster_indices = new_cluster_indices self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) self.clustersizes = np.zeros((nclusters,), dtype=int) for i in range(nclusters): clind = np.where(self.cluster_indices == i) self.clustersizes[i] = self.cluster_indices[clind].shape[0] if self.clustersizes[i]>0: for ie in range(self.stack.n_ev): thiseng_od = self.stack.od3d[:,:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors indx = np.reshape(self.cluster_indices, (npixels), order='F') self.sse = np.zeros((npixels)) for i in range(npixels): clind = indx[i] self.sse[i] = np.sqrt(np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:]))) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') self.cluster_distances = self.sse self.clusters_calculated = 1 return int(nclusters) #----------------------------------------------------------------------- # Find clusters def calculate_clusters_4D(self, nclusters, remove1stpca = 0, sigmasplit = 0, pcscalingfactor = 0.0): #Reduced data matrix od_reduced(n_pixels,n_significant_components) #od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows, self.numsigpca)) self.nclusters = nclusters npixels = self.stack.n_cols * self.stack.n_rows inverse_n_pixels = 1./float(npixels) inverse_n_pixels_less_one = 1./float(npixels-1) dc_offsets = np.zeros((self.numsigpca)) #rms_deviations = np.zeros((self.numsigpca)) od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows,self.numsigpca)) for i in range(self.numsigpca): eimage = self.pcaimages[:,:,i] dc_offsets[i] = np.sum(eimage)*inverse_n_pixels # Since we're looking at deviations from an average, # we divide by (N-1). #rms_deviations[i] = np.sqrt(np.sum((eimage-dc_offsets[i])**2)*inverse_n_pixels_less_one) # The straightforward thing is to do # d_reduced[i,0:(n_pixels-1)] = eimage # However, things work much better if we subtract the # DC offsets from each eigenimage. One could also divide # by rms_deviations, but that seems to overweight # the sensitivity to weaker components too much. rms_gamma = pcscalingfactor od_reduced[:,:,i] = (eimage-dc_offsets[i]) *(self.eigenvals[0]/self.eigenvals[i])**rms_gamma if remove1stpca == 0 : #od_reduced = od_reduced[:,:,0:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca), order='F') else: od_reduced = od_reduced[:,:,1:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca-1), order='F') indx = np.zeros(npixels) clustercentroids, indx = kmeans2(od_reduced, nclusters, iter=200, minit = 'points' ) #calculate cluster distances self.cluster_distances = np.zeros((self.stack.n_cols*self.stack.n_rows)) for i in range(npixels): clind = indx[i] self.cluster_distances[i] = scipy.spatial.distance.euclidean(od_reduced[i,:],clustercentroids[clind,:]) self.cluster_distances = np.reshape(self.cluster_distances, (self.stack.n_cols, self.stack.n_rows), order='F') indx = np.reshape(indx, (self.stack.n_cols, self.stack.n_rows), order='F') self.clustersizes = np.zeros((nclusters,), dtype=int) for i in range(nclusters): clind = np.where(indx == i) self.clustersizes[i] = indx[clind].shape[0] #sort the data with the cluster with the most members first count_indices = np.argsort(self.clustersizes) count_indices = count_indices[::-1] self.cluster_indices = np.zeros((self.stack.n_cols, self.stack.n_rows), dtype=int) self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) for i in range(nclusters): clind = np.where(indx == count_indices[i]) self.cluster_indices[clind] = i self.clustersizes[i] = self.cluster_indices[clind].shape[0] for ie in range(self.stack.n_ev): thiseng_od = self.stack.od3d[:,:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors indx = np.reshape(self.cluster_indices, (npixels), order='F') self.sse = np.zeros((npixels)) for i in range(npixels): clind = indx[i] self.sse[i] = np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:])) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') if (sigmasplit ==1): #Check the validity of cluster analysis and if needed add another cluster new_cluster_indices = self.cluster_indices.copy() new_nclusters = nclusters recalc_clusters = False for i in range(nclusters): clind = np.where(self.cluster_indices == i) cl_sse_mean = np.mean(self.sse[clind]) cl_see_std = np.std(self.sse[clind]) sigma9 = cl_sse_mean+9*cl_see_std maxsse = np.max(self.sse[clind]) if (maxsse > sigma9): recalc_clusters = True sse_helper = np.zeros((self.stack.n_cols, self.stack.n_rows), dtype=int) sse_helper[clind] = self.sse[clind] newcluster_ind = np.where(sse_helper > sigma9) new_cluster_indices[newcluster_ind] = new_nclusters new_nclusters += 1 if recalc_clusters == True: nclusters = new_nclusters self.cluster_indices = new_cluster_indices self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) self.clustersizes = np.zeros((nclusters,), dtype=int) for i in range(nclusters): clind = np.where(self.cluster_indices == i) self.clustersizes[i] = self.cluster_indices[clind].shape[0] if self.clustersizes[i]>0: for ie in range(self.stack.n_ev): thiseng_od = self.stack.od3d[:,:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors indx = np.reshape(self.cluster_indices, (npixels), order='F') self.sse = np.zeros((npixels)) for i in range(npixels): clind = indx[i] self.sse[i] = np.sqrt(np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:]))) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') self.cluster_distances = self.sse self.clusters_calculated = 1 return int(nclusters) #----------------------------------------------------------------------- # Find clusters def calculate_clusters_kmeansangle(self, nclusters, remove1stpca = 0, sigmasplit = 0, cosinemeasure = False): cosinemeasure = True self.nclusters = nclusters npixels = self.stack.n_cols * self.stack.n_rows inverse_n_pixels = 1./float(npixels) inverse_n_pixels_less_one = 1./float(npixels-1) dc_offsets = np.zeros((self.numsigpca)) #rms_deviations = np.zeros((self.numsigpca)) od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows,self.numsigpca)) for i in range(self.numsigpca): eimage = self.pcaimages[:,:,i] dc_offsets[i] = np.sum(eimage)*inverse_n_pixels # Since we're looking at deviations from an average, # we divide by (N-1). #rms_deviations[i] = np.sqrt(np.sum((eimage-dc_offsets[i])**2)*inverse_n_pixels_less_one) # The straightforward thing is to do # d_reduced[i,0:(n_pixels-1)] = eimage # However, things work much better if we subtract the # DC offsets from each eigenimage. One could also divide # by rms_deviations, but that seems to overweight # the sensitivity to weaker components too much. rms_gamma = 0.0 od_reduced[:,:,i] = (eimage-dc_offsets[i]) *(self.eigenvals[0]/self.eigenvals[i])**rms_gamma if remove1stpca == 0 : #od_reduced = od_reduced[:,:,0:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca), order='F') nsigpca = self.numsigpca else: od_reduced = od_reduced[:,:,1:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca-1), order='F') nsigpca = self.numsigpca-1 n_iterations = 5 # When "Angle distance measure" is used we d_reduced is normalized # so that all spectra have norm equal to 1 (this amounts to # projection of all the pixels to unit sphere in principal # component space. # For angle distance measure we will only include part of the # pixels if cutoff is set. Pixels with lowest optical density are # not included in the calculation since they will be uniformly # distributed over the unit sphere and might obstract finding of # the clusters. angle_cutoff_value = 0 if cosinemeasure: od_reduced_old = od_reduced.copy() for i in range(npixels): od_reduced[i,:] = od_reduced[i,:]/np.linalg.norm(od_reduced[i,:]) if angle_cutoff_value > 0: d_integrated = np.zeros((npixels)) d_integrated = np.apply_along_axis(np.sum, self.od, 0) included_pixels = np.where(d_integrated > angle_cutoff_value) od_reduced_all_pixels = od_reduced.copy() od_reduced = od_reduced[:, included_pixels] # Initial 'learning rate'. LearningRates = 0.3-0.2*np.arange(n_iterations+1)/float(n_iterations-1) # Normal random cluster weights. cluster_weights = np.zeros((nsigpca, nclusters)) randomindices = np.random.uniform(0, npixels-1,size=nclusters) for i in range(nclusters): cluster_weights[:,i] = od_reduced[randomindices[i], :] if cosinemeasure: for i in range(nclusters): cluster_weights[:, i] = cluster_weights[:, i]/np.linalg.norm(cluster_weights[:,i]) Metric = np.zeros((nclusters)) # Start by picking a percentage of the pixels at random, # and using them to start finding our cluster centers n_random_pixels = int(0.50*float(npixels)) random_sample_indices = float(npixels-1)*np.random.uniform(0,1,(n_random_pixels,)) # Make sure we don't do any pixels twice ursi, uindices = np.unique(random_sample_indices, return_index = True) random_sample_indices = random_sample_indices[uindices] n_random_pixels = len(random_sample_indices) this_learning_rate = LearningRates[0] for i_sample in range(n_random_pixels): Sample = random_sample_indices[i_sample] Vector = np.tile(od_reduced[Sample,:], (nclusters,1)).T - cluster_weights #Calculate distances if cosinemeasure == False: for i_cluster in range(nclusters): Metric[i_cluster] = np.sqrt(np.dot(Vector[:, i_cluster].T, Vector[:, i_cluster])) else: #Use angle between vectors instead euclidean distance for i_cluster in range(nclusters): Metric[i_cluster] = scipy.spatial.distance.cosine(od_reduced[ Sample, :], cluster_weights[:, i_cluster].T) MinIndex = np.argmin(Metric) cluster_weights[:,MinIndex] = this_learning_rate* Vector[:,MinIndex] + cluster_weights[:,MinIndex] if cosinemeasure: cluster_weights[:, MinIndex] = cluster_weights[:, MinIndex]/np.linalg.norm(cluster_weights[:,MinIndex]) # Random ordering of sample indices. random_ordered = np.arange(npixels) np.random.shuffle(random_ordered) self.cluster_distances = np.zeros((npixels)) Tempcluster_indices = np.zeros((npixels)) cluster_histogram = np.zeros((nclusters)) cluster_indices = np.zeros((npixels)) New_RMSDistanceIterations = np.zeros((n_iterations)) for i_iteration in range(n_iterations): this_max_distance = 0.0 this_learning_rate = LearningRates[i_iteration] for Sample in range(npixels): # In our case the data array is # d_reduced(n_significant_components,n_pixels), and we have # WorkCol(1,n_clusters). Calculate # Vector(n_significant_components,n_clusters) by multiplying # all the components for this pixel by n_clusters values of # 1 to pick them off, and then subtracting from the result # the current guess of the weights (the cluster centers). Vector = np.tile(od_reduced[random_ordered[Sample],:], (nclusters,1)).T - cluster_weights #Calculate distances if cosinemeasure == False: for i_cluster in range(nclusters): Metric[i_cluster] = np.sqrt(np.dot(Vector[:, i_cluster].T, Vector[:, i_cluster].T)) else: #Use angle between vectors instead euclidean distance for i_cluster in range(nclusters): Metric[i_cluster] = scipy.spatial.distance.cosine(od_reduced[random_ordered[Sample], :], cluster_weights[:, i_cluster].T) MinIndex = np.argmin(Metric) MinMetric = Metric[MinIndex] this_max_distance = max([this_max_distance, MinMetric ]) cluster_weights[:,MinIndex] = this_learning_rate* Vector[:,MinIndex] + cluster_weights[:,MinIndex] if cosinemeasure: cluster_weights[:, MinIndex] = cluster_weights[:, MinIndex]/np.linalg.norm(cluster_weights[:,MinIndex]) self.cluster_distances[random_ordered[Sample]] = MinMetric if (i_iteration == (n_iterations-1)) : Tempcluster_indices[random_ordered[Sample]] = MinIndex cluster_histogram[MinIndex] = cluster_histogram[MinIndex]+1 # Since we're talking about distances from the cluster # center, which is in some ways an average of pixel positions, # we use (npixels-1) in the denominator. New_RMSDistanceIterations[i_iteration] = np.sqrt(np.sum(self.cluster_distances**2)/float(npixels-1)) # Next we sort the data with the cluster with the most members first count_indices = np.argsort(cluster_histogram) count_indices = count_indices[::-1] cluster_histogram = cluster_histogram[count_indices] self.cluster_indices = np.zeros((npixels), dtype=int) ClustersFound = 0 for i_cluster in range(nclusters): i_temp_cluster = count_indices[i_cluster] these_pixels = np.where(Tempcluster_indices == i_temp_cluster)[0] if len(these_pixels) > 0: cluster_indices[these_pixels] = i_cluster ClustersFound = ClustersFound + 1 # Next we sort the cluster_weights with the cluster with the most # members first temp_weights = cluster_weights.copy() for i_cluster in range(ClustersFound): cluster_weights[0:nsigpca, i_cluster] = temp_weights[0:nsigpca, count_indices[i_cluster]] cluster_histogram = cluster_histogram[0:ClustersFound] cluster_weights = cluster_weights[:, 0:ClustersFound] # # Recalculate the cluster centers to be equal to the average of the # # pixel weights. For angle measure will be done later. # for i_cen in range(nclusters): # cluster_members = np.where(cluster_indices == i_cen) # n_mem = len(cluster_members[0]) # if len(cluster_members[0])> 0: # WorkRow2=np.ones((n_mem)) # cluster_weights[:,i_cen]=np.dot(od_reduced[:,cluster_members],WorkRow2)/n_mem self.cluster_distances = np.reshape(self.cluster_distances, (self.stack.n_cols, self.stack.n_rows), order='F') self.cluster_indices = cluster_indices self.clustersizes = cluster_histogram self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) self.sse = np.zeros((npixels)) for i in range(nclusters): clind = np.where(self.cluster_indices == count_indices[i]) self.clustersizes[i] = self.cluster_indices[clind].shape[0] for ie in range(self.stack.n_ev): thiseng_od = self.stack.od[:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors for i in range(npixels): clind = self.cluster_indices[i] self.sse[i] = np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:])) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') if (sigmasplit ==1): #Check the validity of cluster analysis and if needed add another cluster new_cluster_indices = self.cluster_indices.copy() new_nclusters = nclusters recalc_clusters = False for i in range(nclusters): clind = np.where(self.cluster_indices == i) cl_sse_mean = np.mean(self.sse[clind]) cl_see_std = np.std(self.sse[clind]) sigma9 = cl_sse_mean+9*cl_see_std maxsse = np.max(self.sse[clind]) if (maxsse > sigma9): recalc_clusters = True sse_helper = np.zeros((self.stack.n_cols, self.stack.n_rows), dtype=int) sse_helper[clind] = self.sse[clind] newcluster_ind = np.where(sse_helper > sigma9) new_cluster_indices[newcluster_ind] = new_nclusters new_nclusters += 1 if recalc_clusters == True: nclusters = new_nclusters self.cluster_indices = new_cluster_indices self.clusterspectra = np.zeros((nclusters, self.stack.n_ev)) self.clustersizes = np.zeros((nclusters,), dtype=int) for i in range(nclusters): clind = np.where(self.cluster_indices == i) self.clustersizes[i] = self.cluster_indices[clind].shape[0] if self.clustersizes[i]>0: for ie in range(self.stack.n_ev): thiseng_od = self.stack.od3d[:,:,ie] self.clusterspectra[i,ie] = np.sum(thiseng_od[clind])/self.clustersizes[i] #Calculate SSE Sum of Squared errors indx = np.reshape(self.cluster_indices, (npixels), order='F') self.sse = np.zeros((npixels)) for i in range(npixels): clind = indx[i] self.sse[i] = np.sqrt(np.sum(np.square(self.stack.od[i,:]-self.clusterspectra[clind,:]))) self.sse = np.reshape(self.sse, (self.stack.n_cols, self.stack.n_rows), order='F') self.cluster_indices = np.reshape(self.cluster_indices, (self.stack.n_cols, self.stack.n_rows), order='F') self.cluster_distances = self.sse self.clusters_calculated = 1 return int(nclusters) #----------------------------------------------------------------------- # Find clusters using EM clustering def calculate_clusters_em(self, nclusters): #Reduced data matrix od_reduced(n_pixels,n_significant_components) #od_reduced = np.zeros((self.stack.n_cols, self.stack.n_rows, self.numsigpca)) npixels = self.stack.n_cols * self.stack.n_rows inverse_n_pixels = 1./float(npixels) od_reduced = self.pcaimages[:,:,0:self.numsigpca] od_reduced = np.reshape(od_reduced, (npixels,self.numsigpca), order='F') #kmeans(obs,k_or_guess,iter=20,thresh=1e-5) self.indx = np.zeros(npixels) res, self.indx = kmeans2(od_reduced,5) self.indx = np.reshape(self.indx, (self.stack.n_cols, self.stack.n_rows), order='F') #------------------------------------------------------------------------------ # Spectral analysis # This routine reads in a mu spectrum in units of inverse microns. # The spectrum is interpolated onto the energy range of the stack, # and loaded into the matrix target_spectra(pca_gui_par.n_targets,n_ev). # If there is a PCA calculation done, we find the fits to the # target spectra from the components. def read_target_spectrum(self, filename = '', flat = False): # Load spectrum from a file spectrum_evdata = 0 spectrum_data = 0 spectrum_common_name = ' ' if flat == False: fn = os.path.basename(str(filename)) basename, extension = os.path.splitext(fn) if extension == '.csv': spectrum_evdata, spectrum_data, spectrum_common_name = self.stack.read_csv(filename) elif extension == '.xas': spectrum_evdata, spectrum_data, spectrum_common_name = self.stack.read_xas(filename) elif extension == '.txt': spectrum_evdata, spectrum_data, spectrum_common_name = self.stack.read_txt(filename) # Map this spectrum onto our energy range - interpolate to ev ftspec = scipy.interpolate.interp1d(spectrum_evdata, spectrum_data, kind='cubic', bounds_error=False) target_spectrum = np.reshape(ftspec(self.stack.ev), (1,self.stack.n_ev)) #fix the edges if needed if self.stack.ev[0]spectrum_evdata[-1]: indx = np.where(self.stack.ev>spectrum_evdata[-1]) target_spectrum[0,indx] = spectrum_data[-1] else: target_spectrum = np.ones((1,self.stack.n_ev)) spectrum_common_name = 'Flat' if self.tspectrum_loaded == 0: self.target_spectra = target_spectrum self.tspectrum_loaded = 1 self.n_target_spectra += 1 else: self.target_spectra = np.vstack((self.target_spectra,target_spectrum)) self.n_target_spectra += 1 self.tspec_names.append(spectrum_common_name) self.fit_target_spectra() self.calc_svd_maps() #------------------------------------------------------------------------------ def add_cluster_target_spectra(self): # Load spectrum from a file or cluster spectra for i in range(self.nclusters): target_spectrum = self.clusterspectra[i,:] if self.tspectrum_loaded == 0: self.target_spectra = target_spectrum self.tspectrum_loaded = 1 self.n_target_spectra += 1 else: self.target_spectra = np.vstack((self.target_spectra,target_spectrum)) self.n_target_spectra += 1 self.tspec_names.append('Cluster '+str(i+1)) self.fit_target_spectra() self.calc_svd_maps() #------------------------------------------------------------------------------ def remove_spectrum(self, i_spec): if self.n_target_spectra > 1: self.target_spectra = np.delete(self.target_spectra, i_spec, axis=0) del self.tspec_names[i_spec] self.n_target_spectra -= 1 self.fit_target_spectra() self.calc_svd_maps() else: self.target_spectra = [] self.tspec_names = [] self.tspectrum_loaded = 0 self.n_target_spectra = 0 self.target_svd_maps4D = [] self.original_svd_maps4D = [] self.target_pcafit_maps4D = [] self.original_fit_maps4D = [] self.target_pcafit_coeffs4D = [] self.target_pcafit_spectra4D = [] #------------------------------------------------------------------------------ def move_spectrum(self, old_position, new_position): temp_target_spectra = self.target_spectra.copy() temp_target_spectra[old_position,:] = self.target_spectra[new_position,:] temp_target_spectra[new_position,:] = self.target_spectra[old_position,:] self.target_spectra = temp_target_spectra temp_tspec_name = self.tspec_names[new_position] self.tspec_names[new_position] = self.tspec_names[old_position] self.tspec_names[old_position] = temp_tspec_name self.fit_target_spectra() self.calc_svd_maps() #------------------------------------------------------------------------------ # This routine calculates: # - the transformation matrix T as target_spectrumfit_coeffs, and # the fits to the target spectra as target_fittedspectra # - the inverse of T # - the eigenvector matrix C(S_abstract,N) by transposing the matrix # CT(N,S_abstract)=evecs(N,S_abstract) # - the target maps t(P,S_targets) as targetfit_maps def fit_target_spectra(self): # We want to find T(S_physical,S_abstract) which is # CT(N,S_abstract)##mu(S_physical,N). But mu(S_physical,N) is # known here as target_spectra(S_physical,N), and # CT(N,S_abstract) is just a limited version of evecs(N,S). # We will call T(S_physical,S_abstract) by the name # target_spectrumfit_coeffs(S_physical,S_abstract). if self.pca_calculated == 0: return CT = self.eigenvecs[:,0:self.numsigpca] self.target_pcafit_coeffs = np.dot(self.target_spectra, CT ) # Now we get the target spectra as approximated from our # components with # mu(S_physical,N)=C(S_abstract,N)##T(S_physical,S_abstract). self.target_pcafit_spectra = np.dot(self.target_pcafit_coeffs, CT.T) # To get the maps, we need to find R(P,Sbar_abstract) # from ct(N,Sbar_abstract)##d(P,N), and we also # need to invert the transformation matrix. Start by # finding the singular value decomposition of the # transformation matrix. U, s, V = np.linalg.svd(self.target_pcafit_coeffs, full_matrices=False) # This gives T^{-1}(Sbar_abstract,S_physical) t_inverse = np.dot(np.dot(V.T, np.linalg.inv(np.diag(s))), U.T) # This is R(P,Sbar_abstract)=CT(N,Sbar_abstract)##D(P,N) r_matrix = np.dot(self.stack.od, CT) # and this gives us the maps as # t(P,S_physical) = T^{-1}(Sbar_abstract,S_physical)##R(P,Sbar_abstract) # but in fact it is t(P,n_targets)! self.target_pcafit_maps = np.dot(r_matrix, t_inverse) self.target_pcafit_maps = np.reshape(self.target_pcafit_maps, (self.stack.n_cols, self.stack.n_rows, self.n_target_spectra), order='F') self.original_fit_maps = self.target_pcafit_maps.copy() #Find fit errors self.target_rms = (self.target_spectra-self.target_pcafit_spectra)**2 self.target_rms = np.sqrt(np.sum(self.target_rms, axis=1)/self.stack.n_ev) return #------------------------------------------------------------------------------ # This routine calculates the SVD composition maps # 1. The optical density is calculated from the stack, and the # data matrix D of dimensions (pixels,energies) is formed # 2. mu_inverse is calculated using SVD # 3. svd_maps is calculated from multiplying mu_inverse ## d def calc_svd_maps(self, usefittedspectra = False): if usefittedspectra: U, s, V = np.linalg.svd(self.target_pcafit_spectra, full_matrices=False) else: U, s, V = np.linalg.svd(self.target_spectra, full_matrices=False) mu_inverse = t_inverse = np.dot(np.dot(V.T, np.linalg.inv(np.diag(s))), U.T) self.target_svd_maps = np.dot(self.stack.od, mu_inverse) self.target_svd_maps = np.reshape(self.target_svd_maps, (self.stack.n_cols, self.stack.n_rows, self.n_target_spectra), order='F') self.original_svd_maps = self.target_svd_maps.copy() #----------------------------------------------------------------------- # Calculate composition maps for 4D data def calculate_targetmaps_4D(self): n_pix = self.stack.n_cols*self.stack.n_rows self.target_svd_maps4D = [] self.original_svd_maps4D = [] self.target_pcafit_maps4D = [] self.original_fit_maps4D = [] self.target_pcafit_coeffs4D = [] self.target_pcafit_spectra4D = [] tempod = self.stack.od.copy() for jth in range(self.stack.n_theta): od3d = self.stack.od4D[:,:,:,jth] od = od3d.copy() self.stack.od = np.reshape(od, (n_pix, self.stack.n_ev), order='F') if len(self.eigenvecs4D) > 0: self.eigenvecs = self.eigenvecs4D[jth] self.fit_target_spectra() self.calc_svd_maps() self.target_svd_maps4D.append(self.target_svd_maps) self.original_svd_maps4D.append(self.original_svd_maps) if len(self.eigenvecs4D) > 0: self.target_pcafit_maps4D.append(self.target_pcafit_maps) self.original_fit_maps4D.append(self.original_fit_maps) self.target_pcafit_coeffs4D.append(self.target_pcafit_coeffs) self.target_pcafit_spectra4D.append(self.target_pcafit_spectra) self.stack.od = tempod #------------------------------------------------------------------------------ # Apply threshold on SVD or PCA maps def svd_map_threshold(self, cutoff1, cutoff2 = None, svd = False, pca = False): if svd: self.target_svd_maps = self.original_svd_maps.copy() self.target_svd_maps.clip(min=cutoff1, out=self.target_svd_maps) if cutoff2 != None: self.target_svd_maps.clip(max=cutoff2, out=self.target_svd_maps) if len(self.target_svd_maps4D) > 0: self.target_svd_maps4D = copy.deepcopy(self.original_svd_maps4D) if cutoff2 != None: maxclip = cutoff2 else: maxclip = np.amax(self.target_svd_maps4D) self.target_svd_maps4D = np.clip(self.target_svd_maps4D, cutoff1, maxclip) if pca: self.target_pcafit_maps = self.original_fit_maps.copy() self.target_pcafit_maps.clip(min=cutoff1, out=self.target_pcafit_maps) if cutoff2 != None: self.target_pcafit_maps.clip(max=cutoff2, out=self.target_pcafit_maps) if len(self.target_pcafit_maps) > 0: self.target_pcafit_maps4D = copy.deepcopy(self.original_fit_maps4D) if cutoff2 != None: maxclip = cutoff2 else: maxclip = np.amax(self.target_pcafit_maps4D) self.target_pcafit_maps4D = np.clip(self.target_pcafit_maps4D, cutoff1, maxclip) #------------------------------------------------------------------------------ # Find key energies by finding peaks and valleys in significant pca spectra def calc_key_engs(self, threshold): key_engs = [] for i in range(self.numsigpca): pcaspectrum = self.eigenvecs[:,i] pmax,pmin = self.find_peaks(pcaspectrum, threshold, x = self.stack.ev) for i in range(len(pmin)): key_engs.append(pmin[i][0]) for i in range(len(pmax)): key_engs.append(pmax[i][0]) key_engs = np.array(key_engs) #Sort the energies and remove double entries key_engs = np.unique(key_engs) return key_engs #------------------------------------------------------------------------------ #Peakfinder def find_peaks(self, v, delta, x = None): """ Converted from MATLAB script at http://billauer.co.il/peakdet.html by endolith https://gist.github.com/250860 Returns two arrays function [maxtab, mintab]=peakdet(v, delta, x) %PEAKDET Detect peaks in a vector % [MAXTAB, MINTAB] = PEAKDET(V, DELTA) finds the local % maxima and minima ("peaks") in the vector V. % MAXTAB and MINTAB consists of two columns. Column 1 % contains indices in V, and column 2 the found values. % % With [MAXTAB, MINTAB] = PEAKDET(V, DELTA, X) the indices % in MAXTAB and MINTAB are replaced with the corresponding % X-values. % % A point is considered a maximum peak if it has the maximal % value, and was preceded (to the left) by a value lower by % DELTA. % Eli Billauer, 3.4.05 (Explicitly not copyrighted). % This function is released to the public domain; Any use is allowed. """ maxtab = [] mintab = [] if x is None: x = np.arange(len(v)) v = np.asarray(v) if len(v) != len(x): print ('Input vectors v and x must have same length') return -1 if not np.isscalar(delta): print ('Input argument delta must be a scalar') return -1 if delta <= 0: print ('Input argument delta must be positive') return -1 mn, mx = np.Inf, -np.Inf mnpos, mxpos = np.NaN, np.NaN lookformax = True for i in np.arange(len(v)): this = v[i] if this > mx: mx = this mxpos = x[i] if this < mn: mn = this mnpos = x[i] if lookformax: if this < mx-delta: maxtab.append((mxpos, mx)) mn = this mnpos = x[i] lookformax = False else: if this > mn+delta: mintab.append((mnpos, mn)) mx = this mxpos = x[i] lookformax = True return np.array(maxtab), np.array(mintab) #----------------------------------------------------------------------- def load_xraypeakfit_spectrum(self, filename): # Load spectrum from a file spectrum_evdata = 0 spectrum_data = 0 spectrum_common_name = ' ' spectrum_evdata, spectrum_data, spectrum_common_name = self.stack.read_csv(filename) if self.stack.n_ev > 0: # Map this spectrum onto our energy range - interpolate to ev ftspec = scipy.interpolate.interp1d(spectrum_evdata, spectrum_data, kind='cubic', bounds_error=False) xfit_spectrum = np.reshape(ftspec(self.stack.ev), (1,self.stack.n_ev)) else: self.stack.ev = spectrum_evdata self.stack.n_ev = len(self.stack.ev) xfit_spectrum = np.reshape(spectrum_data, (1,self.stack.n_ev)) #fix the edges if needed if self.stack.ev[0]spectrum_evdata[-1]: indx = np.where(self.stack.ev>spectrum_evdata[-1]) xfit_spectrum[0,indx] = spectrum_data[-1] if self.xrayfitsp_loaded == 0: self.xrayfitspectra = xfit_spectrum self.xrayfitsp_loaded = 1 self.n_xrayfitsp = 1 else: self.xrayfitspectra = np.vstack((self.xrayfitspectra,xfit_spectrum)) self.n_xrayfitsp += 1 if spectrum_common_name == ' ': spectrum_common_name = 'Spectrum %d' % (self.n_xrayfitsp) self.xfspec_names.append(spectrum_common_name) self.xfitpars.append(Cfitparams()) #Find peaks: self.init_fit_params(self.n_xrayfitsp-1) #----------------------------------------------------------------------- #Load spectra from cluster analysis def load_xraypeakfit_clusterspectrum(self, i_cluster): xfit_spectrum = self.clusterspectra[i_cluster,:].copy() xfit_spectrum = np.reshape(xfit_spectrum, (1,self.stack.n_ev)) spectrum_common_name = 'Cluster '+str(i_cluster+1) if self.xrayfitsp_loaded == 0: self.xrayfitspectra = xfit_spectrum.copy() self.xrayfitsp_loaded = 1 self.n_xrayfitsp = 1 else: self.xrayfitspectra = np.vstack((self.xrayfitspectra, xfit_spectrum)) self.n_xrayfitsp += 1 self.xfspec_names.append(spectrum_common_name) self.xfitpars.append(Cfitparams()) #Find peaks: self.init_fit_params(self.n_xrayfitsp-1) #----------------------------------------------------------------------- def init_fit_params(self, index): pmax,pmin = self.find_peaks(self.xrayfitspectra[index], 0.03, x = self.stack.ev) fp = self.xfitpars[index] peakengs = [] if len(pmax) > 0: for i in range(12): if i < len(pmax): peakengs.append(pmax[i][0]) else: peakengs.append(0) else: delta = int(self.stack.n_ev/13) for i in range(12): peakengs.append(self.stack.ev[delta*i]) fp.stepfitparams = [peakengs[0], 0.5, 3.0, peakengs[1], 0.5, 3.0] for i in range(12): fp.gauss_fp_a[i] = 1.0 fp.gauss_fp_m[i] = peakengs[i] fp.gauss_fp_s[i] = 0.5 fp.base = np.mean(self.xrayfitspectra[index][0:5]) self.set_init_fit_params(index, fp.base, fp.stepfitparams, fp.gauss_fp_a, fp.gauss_fp_m, fp.gauss_fp_s) return fp.base, fp.stepfitparams, fp.gauss_fp_a, fp.gauss_fp_m, fp.gauss_fp_s #----------------------------------------------------------------------- def set_init_fit_params(self, index, base, stepfitparams, peak_a, peak_m, peak_s): self.xfitpars[index].base = base self.xfitpars[index].stepfitparams = stepfitparams for i in range(12): self.xfitpars[index].gauss_fp_a[i] = peak_a[i] self.xfitpars[index].gauss_fp_m[i] = peak_m[i] self.xfitpars[index].gauss_fp_s[i] = peak_s[i] return #----------------------------------------------------------------------- def fit_spectrum(self, i_spec, nsteps, npeaks): xfit_spectrum = self.xrayfitspectra[i_spec] fp = self.xfitpars[i_spec] p = [] self.nsteps = nsteps self.npeaks = npeaks p.append(fp.base) for i in range(nsteps*3): p.append(fp.stepfitparams[i]) for i in range(npeaks): p.append(fp.gauss_fp_a[i]) p.append(fp.gauss_fp_m[i]) p.append(fp.gauss_fp_s[i]) #p2, success = optimize.leastsq(model_error, p[:], args=(nsteps, npeaks, np.array(self.stack.ev).astype(np.float64), np.array(xfit_spectrum).astype(np.float64))) bounds=[] #base can be a negative number bounds.append((fp.base-0.5,fp.base+0.5)) for i in range(1,len(p)): bmin = 0 bmax = None bounds.append((bmin,bmax)) p2, success = leastsqbound(model_error, p[:], bounds, args=(nsteps, npeaks, np.array(self.stack.ev).astype(np.float64), np.array(xfit_spectrum).astype(np.float64))) fp.stepfitparams = np.zeros((8)) fp.gauss_fp_a = np.zeros((12)) fp.gauss_fp_m = np.zeros((12)) fp.gauss_fp_s = np.zeros((12)) fp.base = p2[0] fp.stepfitparams[0:nsteps*3] = p2[1:nsteps*3+1] for i in range(npeaks): fp.gauss_fp_a[i] = p2[nsteps*3+1+i*3] fp.gauss_fp_m[i] = p2[nsteps*3+2+i*3] fp.gauss_fp_s[i] = p2[nsteps*3+3+i*3] y = model(p2, nsteps, npeaks, self.stack.ev) separate_y = [] #Add base y1 = np.ones((self.stack.ev.size))*fp.base separate_y.append(y1) #Add step for i in range(nsteps): y1 = np.zeros((self.stack.ev.size)) istepfitparams = p2[3*i+1:3*i+4] for i in range(self.stack.ev.size): y1[i] = stepfunc(istepfitparams, self.stack.ev[i]) separate_y.append(y1) #Add peaks pg = nsteps*3+1 for i in range(npeaks): pp = [p2[pg+i*3],p2[pg+1+i*3],p2[pg+2+i*3]] y1 = gaussian(pp,self.stack.ev) separate_y.append(y1) return y, separate_y #----------------------------------------------------------------------- # Calculate Fast Independent Component Analysis; FASTICA uses Hyvarinen's # fixed-point algorithm # A. Hyvarinen. Fast and Robust Fixed-Point Algorithms for Independent # Component Analysis. IEEE Transactions on Neural Networks 10(3):626-634, 1999. def calculate_fastica(self, mixedsig, numOfIC): mixedsig = mixedsig.transpose() print ('msig', mixedsig.shape) # Remove the mean and check the data mixedmean = np.mean(mixedsig, axis=1) mixedsig = mixedsig - mixedmean[:,np.newaxis] Dim = mixedsig.shape[0] NumOfSampl = mixedsig.shape[1] print ('Dim, NumOfSampl',Dim,NumOfSampl) # Default values for optional parameters verbose = True # Default values for 'pcamat' parameters firstEig = 1 lastEig = Dim interactivePCA = 'off' # Default values for 'fpica' parameters approach = 'defl' g = 'pow3' finetune = 'off' a1 = 1 a2 = 1 myy = 1 stabilization = 'off' epsilon = 0.0001 maxNumIterations = 1000 maxFinetune = 5 initState = 'rand' guess = 0 sampleSize = 1 displayMode = 'off' displayInterval = 1 # Parameters for fastICA b_verbose = True # print information about data if b_verbose: print ('Number of signals:', Dim) print ('Number of samples: ', NumOfSampl) # Check if the data has been entered the wrong way, # but warn only... it may be on purpose if (Dim > NumOfSampl): if b_verbose: print ('Warning: ') print ('The signal matrix may be oriented in the wrong way.') print ('In that case transpose the matrix.') # Calculating PCA # We already have the PCA data if b_verbose: print ('Values for PCA calculations supplied.\n') print ('PCA calculations not needed.\n') # PCA was already calculated: D = np.identity(self.numsigpca)*self.eigenvals[0:self.numsigpca] E = self.eigenvecs[:,0:self.numsigpca] # Calculate the whitening # Calculate the whitening and dewhitening matrices (these handle # dimensionality simultaneously). whiteningMatrix = np.dot(np.linalg.inv (np.sqrt(D)), E.transpose()) dewhiteningMatrix = np.dot(E, np.sqrt(D)) print ('wd=', whiteningMatrix.shape, dewhiteningMatrix.shape) # Project to the eigenvectors of the covariance matrix. # Whiten the samples and reduce dimension simultaneously. if b_verbose: print ('Whitening...') whitesig = np.dot(whiteningMatrix,mixedsig) print ('whitesig', whitesig.shape) # Just some security... if np.sum(np.imag(whitesig)) != 0: print ('Whitened vectors have imaginary values.') # Calculating the ICA # Check some parameters # The dimension of the data may have been reduced during PCA calculations. # The original dimension is calculated from the data by default, and the # number of IC is by default set to equal that dimension. Dim = whitesig.shape[0] # The number of IC's must be less or equal to the dimension of data if numOfIC > Dim: numOfIC = Dim # Show warning only if verbose = 'on' and user supplied a value for 'numOfIC' if b_verbose: print( 'Warning: estimating only ,', numOfIC, ' independent components' ) print( '(Cannot estimate more independent components than dimension of data)') # Calculate the ICA with fixed point algorithm. A, W = self.calc_fpica (whitesig, whiteningMatrix, dewhiteningMatrix, approach, numOfIC, g, finetune, a1, a2, myy, stabilization, epsilon, maxNumIterations, maxFinetune, initState, guess, sampleSize, displayMode, displayInterval, verbose) print ('A,W', A.shape, W.shape) # Check for valid return if W.any(): # Add the mean back in. if b_verbose: print ('Adding the mean back to the data.') icasig = np.dot(W, mixedsig) + np.dot(np.dot(W, mixedmean), np.ones((1, NumOfSampl))) else: icasig = [] return icasig #----------------------------------------------------------------------- def getSamples(self, max, percentage): Samples = (np.random.random((max,)) < percentage).nonzero() return Samples #----------------------------------------------------------------------- # Fixed point ICA # This function is adapted from Hyvarinen's fixed point algorithm Matlab version # [A, W] = fpica(whitesig, whiteningMatrix, dewhiteningMatrix, approach, # numOfIC, g, finetune, a1, a2, mu, stabilization, epsilon, # maxNumIterations, maxFinetune, initState, guess, sampleSize, # displayMode, displayInterval, verbose); # # Perform independent component analysis using Hyvarinen's fixed point # algorithm. Outputs an estimate of the mixing matrix A and its inverse W. # # whitesig :the whitened data as row vectors # whiteningMatrix :whitening matrix # dewhiteningMatrix :dewhitening matrix # approach [ 'symm' | 'defl' ] :the approach used (deflation or symmetric) # numOfIC [ 0 - Dim of whitesig ] :number of independent components estimated # g [ 'pow3' | 'tanh' | :the nonlinearity used # 'gaus' | 'skew' ] # finetune [same as g + 'off'] :the nonlinearity used in finetuning. # a1 :parameter for tuning 'tanh' # a2 :parameter for tuning 'gaus' # mu :step size in stabilized algorithm # stabilization [ 'on' | 'off' ] :if mu < 1 then automatically on # epsilon :stopping criterion # maxNumIterations :maximum number of iterations # maxFinetune :maximum number of iterations for finetuning # initState [ 'rand' | 'guess' ] :initial guess or random initial state. See below # guess :initial guess for A. Ignored if initState = 'rand' # sampleSize [ 0 - 1 ] :percentage of the samples used in one iteration # displayMode [ 'signals' | 'basis' | :plot running estimate # 'filters' | 'off' ] # displayInterval :number of iterations we take between plots # verbose [ 'on' | 'off' ] :report progress in text format def calc_fpica(self, X, whiteningMatrix, dewhiteningMatrix, approach, numOfIC, g, finetune, a1, a2, myy, stabilization, epsilon, maxNumIterations, maxFinetune, initState, guess, sampleSize, displayMode, displayInterval, b_verbose): vectorSize = X.shape[0] numSamples = X.shape[1] # Checking the value for approach if approach == 'symm': approachMode = 1 elif approach == 'defl': approachMode = 2 else: print ('Illegal value for parameter approach:', approach) return if b_verbose: print ('Used approach:', approach) #Checking the value for numOfIC if vectorSize < numOfIC: print ('Must have numOfIC <= Dimension!') return # Checking the sampleSize if sampleSize > 1: sampleSize = 1 if b_verbose: print ('Warning: Setting sampleSize to 1.\n') elif sampleSize < 1: if (sampleSize * numSamples) < 1000: sampleSize = np.min(1000/numSamples, 1) if b_verbose: print ('Warning: Setting ampleSize to ',sampleSize,' samples=', np.floor(sampleSize * numSamples)) print ('sample size', sampleSize) if b_verbose and (sampleSize < 1): print ('Using about ',sampleSize*100,' of the samples in random order in every step.') # Checking the value for nonlinearity. if g == 'pow3': gOrig = 10 elif g =='tanh': gOrig = 20 elif g == 'gauss': gOrig = 30 elif g == 'skew': gOrig = 40 else: print ('Illegal value for parameter g: ', g) if sampleSize != 1: gOrig = gOrig + 2 if myy != 1: gOrig = gOrig + 1 if b_verbose: print ('Used nonlinearity: ', g) finetuningEnabled = 1 if finetune == 'pow3': gFine = 10 + 1 elif finetune == 'tanh': gFine = 20 + 1 elif finetune == 'gauss': gFine = 30 + 1 elif finetune == 'skew': gFine = 40 + 1 elif finetune == 'off': if myy != 1: gFine = gOrig else : gFine = gOrig + 1 finetuningEnabled = 0 else: print ('Illegal value for parameter finetune :', finetune) return if b_verbose and finetuningEnabled: print ('Finetuning enabled, nonlinearity: ', finetune) if stabilization == 'on': stabilizationEnabled = 1 elif stabilization == 'off': if myy != 1: stabilizationEnabled = 1 else: stabilizationEnabled = 0 else: print ('Illegal value for parameter stabilization: ', stabilization) if b_verbose and stabilizationEnabled: print ('Using stabilized algorithm.') # Some other parameters myyOrig = myy # When we start fine-tuning we'll set myy = myyK * myy myyK = 0.01 # How many times do we try for convergence until we give up. failureLimit = 5 usedNlinearity = gOrig stroke = 0 notFine = 1 long = 0 # Checking the value for initial state. if initState == 'rand': initialStateMode = 0; elif initState == 'guess': if guess.shape[0] != whiteningMatrix.shape[1]: initialStateMode = 0 if b_verbose: print ('Warning: size of initial guess is incorrect. Using random initial guess.') else: initialStateMode = 1 if guess.shape[0] < numOfIC: if b_verbose: print ('Warning: initial guess only for first ',guess.shape[0],' components. Using random initial guess for others.') guess[:, guess.shape[0] + 1:numOfIC] = np.random.uniform(-0.5,0.5,(vectorSize,numOfIC-guess.shape[0])) elif guess.shape[0]>numOfIC: guess=guess[:,1:numOfIC] print ('Warning: Initial guess too large. The excess column are dropped.') if b_verbose: print( 'Using initial guess.') else: print ('Illegal value for parameter initState:', initState) return # Checking the value for display mode. if (displayMode =='off') or (displayMode == 'none'): usedDisplay = 0 elif (displayMode =='on') or (displayMode == 'signals'): usedDisplay = 1 if (b_verbose and (numSamples > 10000)): print ('Warning: Data vectors are very long. Plotting may take long time.') if (b_verbose and (numOfIC > 25)): print ('Warning: There are too many signals to plot. Plot may not look good.') elif (displayMode =='basis'): usedDisplay = 2 if (b_verbose and (numOfIC > 25)): print( 'Warning: There are too many signals to plot. Plot may not look good.') elif (displayMode =='filters'): usedDisplay = 3 if (b_verbose and (vectorSize > 25)): print ('Warning: There are too many signals to plot. Plot may not look good.') else: print( 'Illegal value for parameter displayMode:', displayMode) return # The displayInterval can't be less than 1... if displayInterval < 1: displayInterval = 1 # Start ICA calculation if b_verbose: print ('Starting ICA calculation...') # SYMMETRIC APPROACH if approachMode == 1: print ('Symmetric approach under construction') return # DEFLATION APPROACH elif approachMode == 2: B = np.zeros((numOfIC, numOfIC)) # The search for a basis vector is repeated numOfIC times. round = 0 numFailures = 0 while (round < numOfIC): myy = myyOrig usedNlinearity = gOrig stroke = 0 notFine = 1 long = 0 endFinetuning = 0 # Show the progress... if b_verbose: print ('IC :', round) # Take a random initial vector of length 1 and orthogonalize it # with respect to the other vectors. if initialStateMode == 0: w = np.random.standard_normal((vectorSize,)) elif initialStateMode == 1: w=np.dot(whiteningMatrix,guess[:,round]) w = w - np.dot(np.dot(B, B.T), w) norm = np.sqrt((w*w).sum()) w = w / norm wOld = np.zeros(w.shape) wOld2 = np.zeros(w.shape) # This is the actual fixed-point iteration loop. # for i = 1 : maxNumIterations + 1 i = 1 gabba = 1 while (i <= (maxNumIterations + gabba)): if (usedDisplay > 0): print ('display') #Project the vector into the space orthogonal to the space # spanned by the earlier found basis vectors. Note that we can do # the projection with matrix B, since the zero entries do not # contribute to the projection. w = w - np.dot(np.dot(B, B.T), w) norm = np.sqrt((w*w).sum()) w = w / norm if notFine: if i == (maxNumIterations + 1): if b_verbose: print ('Component number',round,' did not converge in ',maxNumIterations, 'iterations.') round = round - 1 numFailures = numFailures + 1 if numFailures > failureLimit: if b_verbose: print ('Too many failures to converge ', numFailures,' Giving up.') if round == 0: A=[] W=[] return break else: if i >= endFinetuning: #So the algorithm will stop on the next test... wOld = w.copy() # Show the progress... if b_verbose: print( '.') # Test for termination condition. Note that the algorithm has # converged if the direction of w and wOld is the same, this # is why we test the two cases. normm = np.sqrt(((w - wOld)*(w - wOld)).sum()) normp = np.sqrt(((w + wOld)*(w + wOld)).sum()) conv = min((normm), normp) if (conv < epsilon): if finetuningEnabled and notFine: if b_verbose: print ('Initial convergence, fine-tuning: ') notFine = 0 gabba = maxFinetune wOld = np.zeros(w.shape) wOld2 = np.zeros(w.shape) usedNlinearity = gFine myy = myyK * myyOrig endFinetuning = maxFinetune + i else: numFailures = 0 # Save the vector B[:, round] = w.copy() # Calculate the de-whitened vector. A = np.dot(dewhiteningMatrix, w) # Calculate ICA filter. W = np.dot(w.transpose(), whiteningMatrix) # Show the progress... if b_verbose: print ('computed ( ',i,' steps ) ') break elif stabilizationEnabled: if (not stroke) and (np.linalg.norm(w - wOld2) < epsilon or np.linalg.norm(w + wOld2) < epsilon): stroke = myy if b_verbose: print ('Stroke!') myy = .5*myy if np.mod(usedNlinearity,2) == 0: usedNlinearity = usedNlinearity + 1 elif stroke: myy = stroke stroke = 0 if (myy == 1) and (np.mod(usedNlinearity,2) != 0): usedNlinearity = usedNlinearity - 1 elif (notFine) and (not long) and (i > maxNumIterations / 2): if b_verbose: print( 'Taking long (reducing step size) ') long = 1 myy = .5*myy if np.mod(usedNlinearity,2) == 0: usedNlinearity = usedNlinearity + 1 wOld2 = wOld wOld = w # pow3 if usedNlinearity == 10: u = np.dot(X.T, w) w = np.dot(X, u*u*u)/numSamples - 3.*w elif usedNlinearity == 11: u = np.dot(X.T, w) EXGpow3 = np.dot(X, u*u*u)/numSamples Beta = np.dot(w.T, EXGpow3) w = w - myy * (EXGpow3 - Beta*w)/(3-Beta) elif usedNlinearity == 12: Xsub = self._get_rsamples(X) u = np.dot(Xsub.T, w) w = np.dot(Xsub, u*u*u)/Xsub.shape[1] - 3.*w elif usedNlinearity == 13: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) EXGpow3 = np.dot(Xsub, u*u*u)/Xsub.shape[1] Beta = np.dot(w.T, EXGpow3) w = w - myy * (EXGpow3 - Beta*w)/(3-Beta) # tanh elif usedNlinearity == 20: u = np.dot(X.T, w) tang = np.tanh(a1 * u) temp = np.dot((1. - tang*tang).sum(axis=0), w) w = (np.dot(X, tang) - a1*temp)/numSamples elif usedNlinearity == 21: u = np.dot(X.T, w) tang = np.tanh(a1 * u) Beta = np.dot(u.T, tang) temp = (1. - tang*tang).sum(axis=0) w = w-myy*((np.dot(X, tang)-Beta*w)/(a1*temp-Beta)) elif usedNlinearity == 22: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) tang = np.tanh(a1 * u) temp = np.dot((1. - tang*tang).sum(axis=0), w) w = (np.dot(Xsub, tang) - a1*temp)/Xsub.shape[1] elif usedNlinearity == 23: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) tang = np.tanh(a1 * u) Beta = np.dot(u.T, tang) w = w - myy * ((np.dot(Xsub, tang)-Beta*w) / (a1*(1. - tang*tang).sum(axis=0) - Beta)) # gauss elif usedNlinearity == 30: # This has been split for performance reasons. u = np.dot(X.T, w) u2 = u*u ex = np.exp(-a2*u2*0.5) gauss = u*ex dgauss = (1. - a2 *u2)*ex w = (np.dot(X, gauss)-np.dot(dgauss.sum(axis=0), w))/numSamples elif usedNlinearity == 31: u = np.dot(X.T, w) u2 = u*u ex = np.exp(-a2*u2*0.5) gauss = u*ex dgauss = (1. - a2 *u2)*ex Beta = np.dot(u.T, gauss) w = w - myy*((np.dot(X, gauss)-Beta*w)/ (dgauss.sum(axis=0)-Beta)) elif usedNlinearity == 32: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) u2 = u*u ex = np.exp(-a2*u2*0.5) gauss = u*ex dgauss = (1. - a2 *u2)*ex w = (np.dot(Xsub, gauss)- np.dot(dgauss.sum(axis=0), w))/Xsub.shape[1] elif usedNlinearity == 33: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) u2 = u*u ex = np.exp(-a2*u2*0.5) gauss = u*ex dgauss = (1. - a2 *u2)*ex Beta = np.dot(u.T, gauss) w = w - myy*((np.dot(Xsub, gauss)-Beta*w)/ (dgauss.sum(axis=0)-Beta)) # skew elif usedNlinearity == 40: u = np.dot(X.T, w) w = np.dot(X, u*u)/numSamples elif usedNlinearity == 41: u = np.dot(X.T, w) EXGskew = np.dot(X, u*u) / numSamples Beta = np.dot(w.T, EXGskew) w = w - myy * (EXGskew - np.dot(Beta, w))/(-Beta) elif usedNlinearity == 42: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) w = np.dot(Xsub, u*u)/Xsub.shape[1] elif usedNlinearity == 43: Xsub=X[:,self.getSamples(numSamples, sampleSize)] u = np.dot(Xsub.T, w) EXGskew = np.dot(Xsub, u*u) / Xsub.shape[1] Beta = np.dot(w.T, EXGskew) w = w - myy * (EXGskew - Beta*w)/(-Beta) else: print ('Code for desired nonlinearity not found!') return # Normalize the new w. norm = np.sqrt((w*w).sum()) w = w / norm i = i + 1 round = round + 1 if b_verbose: print ('Done.') # In the end let's check the data for some security if A.imag.any(): if b_verbose: print ('Warning: removing the imaginary part from the result.') A = A.real W = W.imag return A, W """ Constrained multivariate Levenberg-Marquardt optimization """ from scipy.optimize import leastsq def internal2external_grad(xi,bounds): """ Calculate the internal to external gradiant Calculates the partial of external over internal """ ge = np.empty_like(xi) for i,(v,bound) in enumerate(zip(xi,bounds)): a = bound[0] # minimum b = bound[1] # maximum if a == None and b == None: # No constraints ge[i] = 1.0 elif b == None: # only min ge[i] = v/np.sqrt(v**2+1) elif a == None: # only max ge[i] = -v/np.sqrt(v**2+1) else: # both min and max ge[i] = (b-a)*np.cos(v)/2. return ge def i2e_cov_x(xi,bounds,cov_x): grad = internal2external_grad(xi,bounds) grad = np.atleast_2d(grad) return np.dot(grad.T,grad)*cov_x def internal2external(xi,bounds): """ Convert a series of internal variables to external variables""" xe = np.empty_like(xi) for i,(v,bound) in enumerate(zip(xi,bounds)): a = bound[0] # minimum b = bound[1] # maximum if a == None and b == None: # No constraints xe[i] = v elif b == None: # only min xe[i] = a-1.+np.sqrt(v**2.+1.) elif a == None: # only max xe[i] = b+1.-np.sqrt(v**2.+1.) else: # both min and max xe[i] = a+((b-a)/2.)*( np.sin(v)+1.) return xe def external2internal(xe,bounds): """ Convert a series of external variables to internal variables""" xi = np.empty_like(xe) for i,(v,bound) in enumerate(zip(xe,bounds)): a = bound[0] # minimum b = bound[1] # maximum if a == None and b == None: # No constraints xi[i] = v elif b == None: # only min xi[i] = np.sqrt( (v-a+1.)**2.-1 ) elif a == None: # only max xi[i] = np.sqrt( (b-v+1.)**2.-1 ) else: # both min and max xi[i] = np.arcsin( (2.*(v-a)/(b-a))-1.) return xi def err(p,bounds,efunc,args): pe = internal2external(p,bounds) # convert to external variables return efunc(pe,*args) def calc_cov_x(infodic,p): """ Calculate cov_x from fjac, ipvt and p as is done in leastsq """ fjac = infodic['fjac'] ipvt = infodic['ipvt'] n = len(p) # adapted from leastsq function in scipy/optimize/minpack.py perm = np.take(np.eye(n),ipvt-1,0) r = np.triu(np.transpose(fjac)[:n,:]) R = np.dot(r,perm) #try: cov_x = np.linalg.inv(np.dot(np.transpose(R),R)) #except LinAlgError: # cov_x = None return cov_x def leastsqbound(func,x0,bounds,args=(),**kw): """ Constrained multivariant Levenberg-Marquard optimization Minimize the sum of squares of a given function using the Levenberg-Marquard algorithm. Contraints on parameters are inforced using variable transformations as described in the MINUIT User's Guide by Fred James and Matthias Winkler. Parameters: * func functions to call for optimization. * x0 Starting estimate for the minimization. * bounds (min,max) pair for each element of x, defining the bounds on that parameter. Use None for one of min or max when there is no bound in that direction. * args Any extra arguments to func are places in this tuple. Returns: (x,{cov_x,infodict,mesg},ier) Return is described in the scipy.optimize.leastsq function. x and con_v are corrected to take into account the parameter transformation, infodic is not corrected. Additional keyword arguments are passed directly to the scipy.optimize.leastsq algorithm. """ # check for full output if "full_output" in kw and kw["full_output"]: full=True else: full=False # convert x0 to internal variables i0 = external2internal(x0,bounds) # perfrom unconstrained optimization using internal variables r = leastsq(err,i0,args=(bounds,func,args),**kw) # unpack return convert to external variables and return if full: xi,cov_xi,infodic,mesg,ier = r xe = internal2external(xi,bounds) cov_xe = i2e_cov_x(xi,bounds,cov_xi) # XXX correct infodic 'fjac','ipvt', and 'qtf' return xe,cov_xe,infodic,mesg,ier else: xi,ier = r xe = internal2external(xi,bounds) return xe,ier ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/data_stack.py0000775000175000017500000011127314700316175020226 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division from __future__ import print_function import numpy as np import scipy as sp import scipy.interpolate import scipy.ndimage import h5py import datetime import os import csv from .file_plugins import file_stk from .file_plugins import file_sdf from .file_plugins import file_xrm from .file_plugins import file_ncb from .file_plugins import file_dataexch_hdf5 from . import data_struct # ---------------------------------------------------------------------- class data: def __init__(self, data_struct): self.data_struct = data_struct self.i0_dwell = None self.i0data = np.zeros(1) self.n_ev = 0 self.n_theta = 0 self.shifts = [] self.stack4D = None # ---------------------------------------------------------------------- def new_data(self): self.n_cols = 0 self.n_rows = 0 self.n_ev = 0 self.x_dist = 0 self.y_dist = 0 self.x_start = 0 self.x_stop = 0 self.y_start = 0 self.y_stop = 0 self.x_pxsize = 0 self.y_pxsize = 0 self.squarepx = True self.i0_dwell = None self.ev = 0 self.absdata = 0 self.i0data = np.zeros(1) self.evi0 = 0 self.od = 0 self.od3d = 0 self.xshifts = 0 self.yshifts = 0 self.shifts = [] self.stack4D = None self.n_theta = 0 self.theta = 0 self.od4d = 0 self.data_struct.spectromicroscopy.normalization.white_spectrum = None self.data_struct.spectromicroscopy.normalization.white_spectrum_energy = None self.data_struct.spectromicroscopy.normalization.white_spectrum_energy_units = None self.data_struct.spectromicroscopy.optical_density = None # ---------------------------------------------------------------------- def read_stk_i0(self, filename, extension): if extension == '.xas': file_stk.read_stk_i0_xas(self, filename) elif extension == '.csv': file_stk.read_stk_i0_csv(self, filename) self.calculate_optical_density() self.fill_h5_struct_normalization() # ---------------------------------------------------------------------- def read_sdf_i0(self, filename): file_sdf.read_sdf_i0(self, filename) self.calculate_optical_density() self.fill_h5_struct_normalization() # ---------------------------------------------------------------------- def read_xrm_ReferenceImages(self, filenames): self.calculate_optical_density_from_refimgs(filenames) self.fill_h5_struct_normalization() # ---------------------------------------------------------------------- def read_h54D(self, filename): file_dataexch_hdf5.read(filename, self) if self.data_struct.spectromicroscopy.normalization.white_spectrum is not None: self.calculate_optical_density() self.fill_h5_struct_normalization() self.setScale() # ---------------------------------------------------------------------- def read_ncb4D(self, filenames): self.new_data() file_ncb.read_ncb4D(self, filenames) now = datetime.datetime.now() self.data_struct.implements = 'information:exchange:spectromicroscopy' self.data_struct.version = '1.0' self.data_struct.information.file_creation_datetime = now.strftime("%Y-%m-%dT%H:%M") self.data_struct.information.comment = 'Converted in Mantis' self.data_struct.exchange.data = self.stack4D self.data_struct.exchange.data_signal = 1 self.data_struct.exchange.data_axes = 'x:y:energy:theta' self.data_struct.exchange.theta = np.array(self.theta) self.data_struct.exchange.theta_units = 'degrees' self.data_struct.exchange.x = self.x_dist self.data_struct.exchange.y = self.y_dist self.setScale() # ---------------------------------------------------------------------- def read_ncb4Denergy(self, filename): f = open(str(filename), 'rU') elist = [] for line in f: if line.startswith('*'): if 'Common name' in line: spectrum_common_name = line.split(':')[-1].strip() else: e, = [float(x) for x in line.split()] elist.append(e) self.ev = np.array(elist) f.close() self.n_ev = self.ev.size self.data_struct.exchange.energy = self.ev self.data_struct.exchange.energy_units = 'ev' # ---------------------------------------------------------------------- def read_dpt(self, filename): self.new_data() n_rows = 11 n_cols = 8 imgstack = np.zeros((n_rows, n_cols)) f = open(str(filename), 'r') elist = [] for line in f: if line.startswith("*"): pass else: x = line.split(',') e = float(x[0]) x = x[1:] data = [] for i in range(len(x)): data.append(float(x[i])) elist.append(e) data = np.array(data) data = np.reshape(data, (n_rows, n_cols), order='F') imgstack = np.dstack((imgstack, data)) imgstack = imgstack[:, :, 1:] f.close() self.n_cols = imgstack.shape[0] self.n_rows = imgstack.shape[1] self.n_ev = imgstack.shape[2] pixelsize = 1 # Since we do not have a scanning microscope we fill the x_dist and y_dist from pixel_size self.x_dist = np.arange(float(self.n_cols)) * pixelsize self.y_dist = np.arange(float(self.n_rows)) * pixelsize self.ev = np.array(elist) msec = np.ones((self.n_ev)) self.data_dwell = msec self.absdata = imgstack # Check if the energies are consecutive, if they are not sort the data sort = 0 for i in range(self.n_ev - 1): if self.ev[i] > self.ev[i + 1]: sort = 1 break if sort == 1: sortind = np.argsort(self.ev) self.ev = self.ev[sortind] self.absdata = self.absdata[:, :, sortind] # self.original_n_cols = imgstack.shape[0] # self.original_n_rows = imgstack.shape[1] # self.original_n_ev = imgstack.shape[2] # self.original_ev = self.ev.copy() # self.original_absdata = self.absdata.copy() self.fill_h5_struct_from_stk() self.setScale() # Fix the normalization self.evi0 = self.ev.copy() self.i0data = np.ones(self.n_ev) self.i0_dwell = self.data_dwell self.fill_h5_struct_normalization() # Optical density does not have to be calculated - use raw data self.od3d = self.absdata.copy() self.od = np.reshape(self.od3d, (n_rows * n_cols, self.n_ev), order='F') # ---------------------------------------------------------------------- def fill_h5_struct_from_stk(self): now = datetime.datetime.now() self.data_struct.implements = 'information:exchange:spectromicroscopy' self.data_struct.version = '1.0' self.data_struct.information.file_creation_datetime = now.strftime("%Y-%m-%dT%H:%M") self.data_struct.information.comment = 'Converted in Mantis' self.data_struct.exchange.data = self.absdata self.data_struct.exchange.data_signal = 1 self.data_struct.exchange.data_axes = 'x:y:energy' self.data_struct.exchange.energy = self.ev self.data_struct.exchange.energy_units = 'ev' self.data_struct.exchange.x = self.x_dist self.data_struct.exchange.y = self.y_dist # ---------------------------------------------------------------------- def fill_h5_struct_normalization(self): self.data_struct.spectromicroscopy.normalization.white_spectrum = self.i0data self.data_struct.spectromicroscopy.normalization.white_spectrum_energy = self.evi0 self.data_struct.spectromicroscopy.normalization.white_spectrum_energy_units = 'eV' if self.stack4D is None: self.data_struct.spectromicroscopy.optical_density = self.od else: self.data_struct.spectromicroscopy.optical_density = self.od4d # ---------------------------------------------------------------------- def calc_histogram(self): # calculate average flux for each pixel self.averageflux = np.nanmean(self.absdata, axis=2) self.histogram = self.averageflux px = int(self.n_cols * self.n_rows * 0.98) # 98% of total pixels fluxmax_limit = np.mean(np.partition(np.ravel(self.averageflux), px)[ :px]) # average brightness of the 2% of pixels with highest flux self.histmin = fluxmax_limit self.histmax = np.max(self.averageflux) + 1 histogram_data = np.reshape(self.histogram, (self.n_cols * self.n_rows), order='F') histogram_data = histogram_data[~np.isnan(histogram_data)] # remove non-finite values y, self.hist_data_x = np.histogram(histogram_data, bins=100) y[y < 1] = 1 self.hist_data_y = np.log10(y) return # ---------------------------------------------------------------------- def i0_from_histogram(self, i0_indices): self.evi0hist = self.ev.copy() # i0_indices = np.where((fluxmin<=self.averageflux)&(self.averageflux<=fluxmax)) self.evi0 = self.ev.copy() self.i0_dwell = self.data_dwell if self.stack4D is None: self.i0datahist = np.zeros((self.n_ev)) self.i0data = self.i0datahist if np.any(i0_indices): #invnumel = 1. / self.averageflux[i0_indices].shape[0] for ie in range(self.n_ev): thiseng_abs = self.absdata[:, :, ie] #self.i0datahist[ie] = np.sum(thiseng_abs[i0_indices]) * invnumel finite_vals = thiseng_abs[i0_indices][np.isfinite(thiseng_abs[i0_indices])] if len(finite_vals)>0: self.i0datahist[ie] = np.nanmean(finite_vals) else: self.i0datahist[ie] = self.i0datahist[ie-1] #If this fails on the first image then the data is probably completely empty anyway self.calculate_optical_density() else: self.i0datahist = np.zeros((self.n_ev, self.n_theta)) self.i0data = self.i0datahist self.od4d = np.zeros((self.n_cols, self.n_rows, self.n_ev, self.n_theta)) if np.any(i0_indices): invnumel = 1. / self.averageflux[i0_indices].shape[0] else: return for i in range(self.n_theta): for ie in range(self.n_ev): thiseng_abs = self.stack4D[:, :, ie, i] self.i0datahist[ie, i] = np.sum(thiseng_abs[i0_indices]) * invnumel self.calculate_optical_density_4D() self.fill_h5_struct_normalization() return # ---------------------------------------------------------------------- def UsePreNormalizedData(self): self.evi0 = self.ev.copy() self.i0data = np.ones(self.n_ev) self.i0_dwell = self.data_dwell self.od = np.empty((self.n_cols, self.n_rows, self.n_ev)) for i in range(self.n_ev): self.od[:, :, i] = self.absdata[:, :, i] self.od3d = self.od.copy() n_pixels = self.n_cols * self.n_rows # Optical density matrix is rearranged into n_pixelsxn_ev self.od = np.reshape(self.od, (n_pixels, self.n_ev), order='F') if self.stack4D is not None: self.od4d = self.stack4D.copy() self.fill_h5_struct_normalization() return # ---------------------------------------------------------------------- def set_i0(self, i0data, evdata): self.evi0 = evdata self.i0data = i0data self.i0_dwell = self.data_dwell self.calculate_optical_density() self.fill_h5_struct_normalization() return # ---------------------------------------------------------------------- def reset_i0(self): self.i0_dwell = None self.i0data = 0 self.evi0 = 0 self.od = 0 self.od3d = 0 self.data_struct.spectromicroscopy.normalization.white_spectrum = None self.data_struct.spectromicroscopy.normalization.white_spectrum_energy = None self.data_struct.spectromicroscopy.normalization.white_spectrum_energy_units = None self.data_struct.spectromicroscopy.optical_density = None # ---------------------------------------------------------------------- # Normalize the data: calculate optical density matrix D def calculate_optical_density(self): if self.stack4D is not None: self.calculate_optical_density_4D() return n_pixels = self.n_cols * self.n_rows self.od = np.empty((self.n_cols, self.n_rows, self.n_ev)) # little hack to deal with rounding errors self.evi0[self.evi0.size - 1] += 0.001 self.evi0[0] -= 0.001 if len(self.evi0) > 3: # >3 is needed to avoid boundary error! fi0int = scipy.interpolate.interp1d(self.evi0.astype(np.double), self.i0data.astype(np.double), kind='cubic', bounds_error=False, fill_value=0.0) elif len(self.evi0) > 1: # use linear interpolation when there are fewer points fi0int = scipy.interpolate.interp1d(self.evi0.astype(np.double), self.i0data.astype(np.double), bounds_error=False, fill_value=0.0) else: # use constant value when only a single value is available fi0int = lambda x: self.i0data.astype(np.double) i0 = fi0int(self.ev) if (self.data_dwell is not None) and (self.i0_dwell is not None): i0 = i0 * (self.data_dwell / self.i0_dwell) # zero out all negative values in the image stack negative_indices = np.where(self.absdata <= 0) if negative_indices: self.absdata[negative_indices] = 0.01 for i in range(self.n_ev): self.od[:, :, i] = - np.log(self.absdata[:, :, i] / i0[i]) # clean up the result nan_indices = np.where(np.isfinite(self.od) == False) if nan_indices: self.od[nan_indices] = 0 self.od3d = self.od.copy() # Optical density matrix is rearranged into n_pixelsxn_ev self.od = np.reshape(self.od, (n_pixels, self.n_ev), order='F') return # ---------------------------------------------------------------------- # Normalize the data: calculate optical density matrix D def calculate_optical_density_4D(self): n_pixels = self.n_cols * self.n_rows self.od4d = np.zeros((self.n_cols, self.n_rows, self.n_ev, self.n_theta)) # little hack to deal with rounding errors self.evi0[self.evi0.size - 1] += 0.001 self.i0data = np.array(self.i0data) i0dims = self.i0data.shape for ith in range(self.n_theta): self.od = np.empty((self.n_cols, self.n_rows, self.n_ev)) if len(i0dims) == 2: self.i0data = self.i0datahist[:, ith] if len(self.evi0) > 2: fi0int = scipy.interpolate.interp1d(self.evi0, self.i0data, kind='cubic', bounds_error=False, fill_value=0.0) else: fi0int = scipy.interpolate.interp1d(self.evi0, self.i0data, bounds_error=False, fill_value=0.0) i0 = fi0int(self.ev) if (self.data_dwell is not None) and (self.i0_dwell is not None): i0 = i0 * (self.data_dwell / self.i0_dwell) # zero out all negative values in the image stack negative_indices = np.where(self.stack4D <= 0) if negative_indices: self.stack4D[negative_indices] = 0.01 for i in range(self.n_ev): self.od[:, :, i] = - np.log(self.stack4D[:, :, i, ith] / i0[i]) # clean up the result nan_indices = np.where(np.isfinite(self.od) == False) if nan_indices: self.od[nan_indices] = 0 self.od4d[:, :, :, ith] = self.od[:, :, :] self.od3d = self.od.copy() # Optical density matrix is rearranged into n_pixelsxn_ev self.od = np.reshape(self.od, (n_pixels, self.n_ev), order='F') return # ---------------------------------------------------------------------- # Normalize the data: calculate optical density matrix D def calculate_optical_density_from_refimgs(self, files): n_pixels = self.n_cols * self.n_rows self.od = np.empty((self.n_cols, self.n_rows, self.n_ev)) # zero out all negative values in the image stack negative_indices = np.where(self.absdata <= 0) if negative_indices: self.absdata[negative_indices] = 0.01 # Load reference images refimgs = np.empty((self.n_cols, self.n_rows, self.n_ev)) refimgs_ev = [] for i in range(len(files)): ncols, nrows, iev, imgdata = file_xrm.read_xrm_fileinfo(files[i], readimgdata=True) refimgs[:, :, i] = np.reshape(imgdata, (ncols, nrows), order='F') refimgs_ev.append(iev) # Check if the energies are consecutive, if they are not sort the data consec = 0 for i in range(len(refimgs_ev) - 1): if refimgs_ev[i] > refimgs_ev[i + 1]: consec = 1 break if consec == 1: sortind = np.argsort(refimgs_ev) refimgs_ev = refimgs_ev[sortind] refimgs = refimgs[:, :, refimgs_ev] for i in range(self.n_ev): if self.ev[i] != refimgs_ev[i]: print('Error, wrong reference image energy') return self.od[:, :, i] = - np.log(self.absdata[:, :, i] / refimgs[:, :, i]) # clean up the result nan_indices = np.where(np.isfinite(self.od) == False) if nan_indices: self.od[nan_indices] = 0 self.od3d = self.od.copy() # Optical density matrix is rearranged into n_pixelsxn_ev self.od = np.reshape(self.od, (n_pixels, self.n_ev), order='F') self.evi0 = refimgs_ev self.i0data = np.ones((self.n_ev)) self.i0_dwell = self.data_dwell return # ---------------------------------------------------------------------- def calc_px_size(self,distances,n): start = np.min(distances) stop = np.max(distances) diff = stop - start if diff != 0: pxsize = np.round(np.abs(diff) / (n - 1), 5) # um per px, "-1" because stop-start is 1 px shorter than n else: pxsize = np.nan return pxsize, start, stop # ---------------------------------------------------------------------- def setScale(self): self.x_pxsize, self.x_start, self.x_stop = self.calc_px_size(self.x_dist,self.n_cols) self.y_pxsize, self.y_start, self.y_stop = self.calc_px_size(self.y_dist,self.n_rows) if np.isnan(self.x_pxsize) and np.isnan(self.y_pxsize): print("Point spectra are currently not supported.") return else: #In line scans when one dimension is not known, assume square pixels: if np.isnan(self.y_pxsize): # horizontal line self.y_pxsize = self.x_pxsize self.y_start = 0 self.y_stop = self.y_pxsize elif np.isnan(self.x_pxsize): # vertical line self.x_pxsize = self.y_pxsize self.x_start = 0 self.x_stop = self.x_pxsize if self.x_pxsize == self.y_pxsize: self.squarepx = True else: self.squarepx = False # Set scale_bar as well self.scale_bar() def scale_bar(self): bar_microns = 0.2 * self.n_cols * self.x_pxsize if bar_microns >= 10.: bar_microns = 10. * int(0.5 + 0.1 * int(0.5 + bar_microns)) bar_string = str(int(0.01 + bar_microns)).strip() elif bar_microns >= 1.: bar_microns = float(int(0.5 + bar_microns)) if bar_microns == 1.: bar_string = '1' else: bar_string = str(int(0.01 + bar_microns)).strip() else: bar_microns = np.maximum(0.1 * int(0.5 + 10 * bar_microns), 0.1) bar_string = str(bar_microns).strip() self.scale_bar_string = bar_string self.scale_bar_pixels_x = int(0.5 + float(self.n_cols) * float(bar_microns) / float(abs(self.x_stop - self.x_start))) self.scale_bar_pixels_y = int(0.01 * self.n_rows) if self.scale_bar_pixels_y < 2: self.scale_bar_pixels_y = 2 # ---------------------------------------------------------------------- def write_xas(self, filename, evdata, data): f = open(filename, 'w') print('********************* X-ray Absorption Data ********************', file=f) print('*', file=f) print('* Formula: ', file=f) print('* Common name: ', file=f) print('* Edge: ', file=f) print('* Acquisition mode: ', file=f) print('* Source and purity: ', file=f) print('* Comments: Stack list ROI ""', file=f) print('* Delta eV: ', file=f) print('* Min eV: ', file=f) print('* Max eV: ', file=f) print('* Y axis: ', file=f) print('* Contact person: ', file=f) print('* Write date: ', file=f) print('* Journal: ', file=f) print('* Authors: ', file=f) print('* Title: ', file=f) print('* Volume: ', file=f) print('* Issue number: ', file=f) print('* Year: ', file=f) print('* Pages: ', file=f) print('* Booktitle: ', file=f) print('* Editors: ', file=f) print('* Publisher: ', file=f) print('* Address: ', file=f) print('*--------------------------------------------------------------', file=f) for ie in range(self.n_ev): print('\t {0:06.6f}, {1:06f}'.format(evdata[ie], data[ie]), file=f) f.close() return # ---------------------------------------------------------------------- def write_csv(self, filename, evdata, data, cname=''): with open(filename, 'w', ) as f: writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL) header = [['********************* X-ray Absorption Data ********************'], ['*'], ['* Formula: '], ['* Common name: {0}'.format(cname)], ['* Edge: '], ['* Acquisition mode: '], ['* Source and purity: '], ['* Comments: '], ['* Delta eV: '], ['* Min eV: '], ['* Max eV: '], ['* Y axis: '], ['* Contact person: '], ['* Write date: '], ['* Journal: '], ['* Authors: '], ['* Title: '], ['* Volume: '], ['* Issue number: '], ['* Year: '], ['* Pages: '], ['* Booktitle: '], ['* Editors: '], ['* Publisher: '], ['* Address: '], ['*--------------------------------------------------------------']] for line in header: writer.writerows([line]) if cname == "ROI spectra": l = ["photon energy"] for i in range(len(data)): if i == 0: l.append("current ROI") else: l.append("ROI "+ str(i)) writer.writerow(l) data.insert(0,evdata) data = [list(i) for i in zip(*data)] for row in data: writer.writerows([row]) return else: for ie in range(self.n_ev): writer.writerow([f'{evdata[ie]:06.6f}', f'{data[ie]:09.6f}']) return # ---------------------------------------------------------------------- # Read x-ray absorption spectrum def read_xas(self, filename): spectrum_common_name = ' ' f = open(str(filename), 'rU') elist = [] ilist = [] for line in f: if line.startswith('*'): if 'Common name' in line: spectrum_common_name = line.split(':')[-1].strip() else: e, i = [float(x) for x in line.split()] elist.append(e) ilist.append(i) spectrum_evdata = np.array(elist) spectrum_data = np.array(ilist) f.close() if spectrum_evdata[-1] < spectrum_evdata[0]: spectrum_evdata = spectrum_evdata[::-1] spectrum_data = spectrum_data[::-1] if spectrum_common_name == ' ': spectrum_common_name = os.path.splitext(os.path.basename(str(filename)))[0] return spectrum_evdata, spectrum_data, spectrum_common_name # ---------------------------------------------------------------------- # Read x-ray absorption spectrum def read_txt(self, filename): spectrum_common_name = os.path.splitext(os.path.basename(str(filename)))[0] f = open(str(filename), 'rU') elist = [] ilist = [] for line in f: if line.startswith('%'): pass else: e, i = [float(x) for x in line.split()] elist.append(e) ilist.append(i) spectrum_evdata = np.array(elist) spectrum_data = np.array(ilist) f.close() if spectrum_evdata[-1] < spectrum_evdata[0]: spectrum_evdata = spectrum_evdata[::-1] spectrum_data = spectrum_data[::-1] return spectrum_evdata, spectrum_data, spectrum_common_name # ---------------------------------------------------------------------- # Read x-ray absorption spectrum def read_csv(self, filename): spectrum_common_name = ' ' f = open(str(filename), 'rU') elist = [] ilist = [] # Check the first character of the line and skip if not a number allowedchars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.'] for line in f: if line.startswith('*'): if 'Common name' in line: spectrum_common_name = line.split(':')[-1].strip() elif line[0] not in allowedchars: continue else: e, i = [float(x) for x in line.split(',')] elist.append(e) ilist.append(i) spectrum_evdata = np.array(elist) spectrum_data = np.array(ilist) f.close() if spectrum_evdata[-1] < spectrum_evdata[0]: spectrum_evdata = spectrum_evdata[::-1] spectrum_data = spectrum_data[::-1] if spectrum_common_name == ' ': spectrum_common_name = os.path.splitext(os.path.basename(str(filename)))[0] return spectrum_evdata, spectrum_data, spectrum_common_name # ---------------------------------------------------------------------- # Register images using Fourier Shift Theorem # EdgeEnhancement: 0 = no edge enhacement; 1 = sobel; 2 = prewitt def register_images(self, ref_image, image2, have_ref_img_fft=False, edge_enhancement=0): if have_ref_img_fft == False: if edge_enhancement == 1: self.ref_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(scipy.ndimage.filters.sobel(ref_image)))) elif edge_enhancement == 2: self.ref_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(scipy.ndimage.filters.prewitt(ref_image)))) else: self.ref_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(ref_image))) if edge_enhancement == 1: img2_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(scipy.ndimage.filters.sobel(image2)))) if edge_enhancement == 2: img2_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(scipy.ndimage.filters.prewitt(image2)))) else: img2_fft = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(image2))) fr = (self.ref_fft * img2_fft.conjugate()) / (np.abs(self.ref_fft) * np.abs(img2_fft)) fr = np.fft.fftshift(np.fft.ifft2(np.fft.fftshift(fr))) fr = np.abs(fr) shape = ref_image.shape xc, yc = np.unravel_index(np.argmax(fr), shape) # Limit the search to 1 pixel border if xc == 0: xc = 1 if xc == shape[0] - 1: xc = shape[0] - 2 if yc == 0: yc = 1 if yc == shape[1] - 1: yc = shape[1] - 2 # Use peak fit to find the shifts xpts = [xc - 1, xc, xc + 1] ypts = fr[xpts, yc] xf, fit = self.peak_fit(xpts, ypts) xpts = [yc - 1, yc, yc + 1] ypts = fr[xc, xpts] yf, fit = self.peak_fit(xpts, ypts) xshift = xf - float(shape[0]) / 2.0 yshift = yf - float(shape[1]) / 2.0 return xshift, yshift, fr # ---------------------------------------------------------------------- # Apply image registration def apply_image_registration(self, image, xshift, yshift): shape = image.shape nx = shape[0] ny = shape[1] outofboundariesval = np.sum(image) / float(nx * ny) shifted_img = scipy.ndimage.interpolation.shift(image, [xshift, yshift], mode='constant', cval=outofboundariesval) return shifted_img # ---------------------------------------------------------------------- # Apply image registration def crop_registed_images(self, images, min_xshift, max_xshift, min_yshift, max_yshift): # if the image is moved to the right (positive) we need to crop the left side xleft = int(np.ceil(max_xshift)) if xleft < 0: xleft = 0 # if the image is moved to the left (negative) we need to crop the right side xright = int(np.floor(self.n_cols + min_xshift)) if xright > (self.n_cols): xright = int(self.n_cols) ybottom = int(np.ceil(max_yshift)) if ybottom < 0: ybottom = 0 ytop = int(np.floor(self.n_rows + min_yshift)) if ytop > (self.n_rows): ytop = int(self.n_rows) if self.stack4D is not None: cropped_stack = images[xleft:xright, ybottom:ytop, :, :] else: cropped_stack = images[xleft:xright, ybottom:ytop, :] return cropped_stack, xleft, xright, ybottom, ytop # ---------------------------------------------------------------------- # Quadratic peak fit: Fits the 3 data pairs to y=a+bx+cx^2, returning fit=[a,b,c]' # and xpeak at position of inflection' def peak_fit(self, x, y): y1y0 = y[1] - y[0] y2y0 = y[2] - y[0] x1x0 = float(x[1] - x[0]) x2x0 = float(x[2] - x[0]) x1x0sq = float(x[1] * x[1] - x[0] * x[0]) x2x0sq = float(x[2] * x[2] - x[0] * x[0]) c_num = y2y0 * x1x0 - y1y0 * x2x0 c_denom = x2x0sq * x1x0 - x1x0sq * x2x0 if c_denom == 0: print('Divide by zero error') return c = c_num / float(c_denom) if x1x0 == 0: print('Divide by zero error') return b = (y1y0 - c * x1x0sq) / float(x1x0) a = y[0] - b * x[0] - c * x[0] * x[0] fit = [a, b, c] if c == 0: xpeak = 0. print('Cannot find xpeak') return else: # Constrain the fit to be within these three points. xpeak = -b / (2.0 * c) if xpeak < x[0]: xpeak = float(x[0]) if xpeak > x[2]: xpeak = float(x[2]) return xpeak, fit # ----------------------------------------------------------------------------- # Despike image using Enhanced Lee Filter def despike(self, image, leefilt_percent=50.0): fimg = self.lee_filter(image) leefilt_max = np.amax(fimg) threshold = (1. + 0.01 * leefilt_percent) * leefilt_max datadim = np.int32(image.shape) ncols = datadim[0].copy() nrows = datadim[1].copy() spikes = np.where(image > threshold) n_spikes = fimg[spikes].shape[0] result_img = image.copy() if n_spikes > 0: xsp = spikes[0] ysp = spikes[1] for i in range(n_spikes): ix = xsp[i] iy = ysp[i] print(ix, iy) if ix == 0: ix1 = 1 ix2 = 2 elif ix == (ncols - 1): ix1 = ncols - 2 ix2 = ncols - 3 else: ix1 = ix - 1 ix2 = ix + 1 if iy == 0: iy1 = 1 iy2 = 2 elif iy == (nrows - 1): iy1 = nrows - 2 iy2 = nrows - 3 else: iy1 = iy - 1 iy2 = iy + 1 print(result_img[ix, iy]) result_img[ix, iy] = 0.25 * (image[ix1, iy] + image[ix2, iy] + image[ix, iy1] + image[ix, iy2]) print(result_img[ix, iy]) return result_img # ----------------------------------------------------------------------------- # Lee filter def lee_filter(self, image): nbox = 5 # The size of the filter box is 2N+1. The default value is 5. sig = 5.0 # Estimate of the standard deviation. The default is 5. delta = int((nbox - 1) / 2) # width of window datadim = np.int32(image.shape) n_cols = datadim[0].copy() n_rows = datadim[1].copy() Imean = np.zeros((n_cols, n_rows)) scipy.ndimage.filters.uniform_filter(image, size=nbox, output=Imean) Imean2 = Imean ** 2 # variance z = np.empty((n_cols, n_rows)) for l in range(delta, n_cols - delta): for s in range(delta, n_rows - delta): z[l, s] = np.sum((image[l - delta:l + delta, s - delta:s + delta] - Imean[l, s]) ** 2) z = z / float(nbox ** 2 - 1.0) z = (z + Imean2) / float(1.0 + sig ** 2) - Imean2 ind = np.where(z < 0) n_ind = z[ind].shape[0] if n_ind > 0: z[ind] = 0 lf_image = Imean + (image - Imean) * (z / (Imean2 * sig ** 2 + z)) return lf_image ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/data_struct.py0000755000175000017500000001531514332463747020454 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . # This module is used to pass data between routines and is based on # https://confluence.aps.anl.gov/display/NX/Data+Exchange+Basics class Descr(object): def __get__(self, instance, owner): # Check if the value has been set if (not hasattr(self, "_value")): return None # print "Getting value: %s" % self._value return self._value def __set__(self, instance, value): # print "Setting to %s" % value self._value = value def __delete__(self, instance): del (self._value) ### Information HDF5 Group # ---------------------------------------------------------------------- class ids(object): proposal = Descr() activity = Descr() esaf = Descr() # ---------------------------------------------------------------------- class experimenter(object): name = Descr() role = Descr() affiliation = Descr() address = Descr() phone = Descr() email = Descr() facility_user_id = Descr() # ---------------------------------------------------------------------- class sample(object): name = Descr() description = Descr() # preparation_date [string - ISO 8601 format] preparation_datetime = Descr() # chemical_formula [string - abbreviated CIF format] chemical_formula = Descr() environment = Descr() temperature = Descr() temperature_units = Descr() pressure = Descr() pressure_units = Descr() # ---------------------------------------------------------------------- class objective(object): manufacturer = Descr() model = Descr() comment = Descr() magnification = Descr() # ---------------------------------------------------------------------- class scintillator(object): name = Descr() type = Descr() comment = Descr() scintillating_thickness = Descr() scintillating_thickness_units = Descr() substrate_thickness = Descr() substrate_thickness_units = Descr() # ---------------------------------------------------------------------- class facility(object): name = Descr() beamline = Descr() # ---------------------------------------------------------------------- class accelerator(object): ring_current = Descr() ring_current_units = Descr() primary_beam_energy = Descr() primary_beam_energy_units = Descr() monostripe = Descr() # ---------------------------------------------------------------------- class pixel_size(object): horizontal = Descr() horizontal_units = Descr() vertical = Descr() vertical_units = Descr() # ---------------------------------------------------------------------- class dimensions(object): horizontal = Descr() vertical = Descr() # ---------------------------------------------------------------------- class binning(object): horizontal = Descr() vertical = Descr() # ---------------------------------------------------------------------- class axis_directions(object): horizontal = Descr() vertical = Descr() # ---------------------------------------------------------------------- class roi(object): x1 = Descr() y1 = Descr() x2 = Descr() y2 = Descr() # ---------------------------------------------------------------------- class detector(object): manufacturer = Descr() model = Descr() serial_number = Descr() bit_depth = Descr() operating_temperature = Descr() operating_temperature_units = Descr() exposure_time = Descr() exposure_time_units = Descr() frame_rate = Descr() pixel_size = pixel_size() dimensions = dimensions() binning = binning() axis_directions = axis_directions() roi = roi() # ---------------------------------------------------------------------- class information(object): title = Descr() comment = Descr() file_creation_datetime = Descr() ids = ids() experimenter = experimenter() sample = sample() objective = objective() scintillator = scintillator() facility = facility() accelerator = accelerator() detector = detector() # Exchange HDF5 group # ---------------------------------------------------------------------- class exchange(object): title = Descr() comment = Descr() data_collection_datetime = Descr() # n-dimensional dataset data = Descr() data_signal = Descr() data_description = Descr() data_units = Descr() data_axes = Descr() data_detector = Descr() # These are described in data attribute axes 'x:y:z' but can be arbitrary x = Descr() x_units = Descr() y = Descr() y_units = Descr() z = Descr() z_units = Descr() energy = Descr() energy_units = Descr() theta = Descr() theta_units = Descr() white_data = Descr() white_data_units = Descr() dark_data = Descr() dark_data_units = Descr() rotation = Descr() # Spectromicroscopy HDF5 Group # ---------------------------------------------------------------------- class normalization(object): white_spectrum = Descr() white_spectrum_units = Descr() white_spectrum_energy = Descr() white_spectrum_energy_units = Descr() # ---------------------------------------------------------------------- class spectromicroscopy(object): positions = Descr() positions_units = Descr() positions_names = Descr() normalization = normalization() optical_density = Descr() data_dwell = Descr() i0_dwell = Descr() xshifts = Descr() yshifts = Descr() # HDF5 Root Group # ---------------------------------------------------------------------- class h5(object): # implements [string] comma separated string that tells the user which entries file contains implements = Descr() version = Descr() information = information() exchange = exchange() spectromicroscopy = spectromicroscopy() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/dialogalign.ui0000644000175000017500000000376214332463747020376 0ustar00wattswatts Dialog 0 0 400 100 0 0 Alignment Routine Selector Ubuntu 8 Choose an image registration tool: 0 40 Ubuntu 8 FFT cross-correlation 0 40 Ubuntu 8 Manual alignment ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/dialogsave.ui0000664000175000017500000002043614700316175020230 0ustar00wattswatts Save 0 0 400 180 Dialog QLayout::SetDefaultConstraint 0 .png .svg _spectrum .tif (data) .pdf _image .csv (data) batch export Qt::Vertical 20 40 6 0 0 6 0 0 Filename prefix: 0 24 16777215 24 6 0 Path: 0 24 16777215 24 0 24 16777215 24 Browse... true 0 0 Qt::Vertical QDialogButtonBox::Cancel|QDialogButtonBox::Save false ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0406938 mantis_xray-3.2.2/mantis_xray/file_plugins/0000775000175000017500000000000014700317134020222 5ustar00wattswatts././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/__init__.py0000664000175000017500000002263314700316175022345 0ustar00wattswatts# -*- coding: utf-8 -*- # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Benjamin Watts, Paul Scherrer Institute # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . """ The file_plugins system is exposed to general code through the functions defined here in __init__.py: identify(filename) : Returns an instance of the plugin that claims to deal with the file at the URL 'filename'. GetFileStructure(filename) : Returns a structure describing the internal organisation of the file, indicating sets of data available to choose from. load(filename,stack_object,..) : Loads data from the URL 'filename' into the object (data_stack type) 'stack_object'. The plugin used can be stated or determined automatically, using 'identify'. Further functions for writing files via the plugins need to be written yet. To access the system, you should import the module ('import file_plugins') and then access the above functions as attributes of the module (e.g. 'file_plugins.load('data.hdf5',data_stk)' ). Each file plugin should be included here in the 'file_plugins' directory. Each plugin should define the following: title : A short string naming the plugin. extension : A list of strings indicating the file extensions that the plugin handles (e.g. ['*.txt']). read_types : A list of strings indicating the data types that the plugin will read (e.g. ['spectrum','image','stack']). write_types : A list of strings indicating the data types that the plugin will write (e.g. ['spectrum','image','stack']). identify(filename) : Returns boolean indicating if the plugin can read the file at URL 'filename'. GetFileStructure(filename) : Returns a structure describing the internal organisation of the file, indicating sets of data available to choose from. read(filename,stack_object,..) : Loads data from the URL 'filename' into the object (data_stack type) 'stack_object'. """ from __future__ import print_function import pkgutil, importlib, os, sys import numpy from .. import data_stack verbose = True # These variables declare the options that each plugin can claim the ability to handle actions = ['read','write'] #data_types = ['spectrum','image','stack','results'] # Please refer to the individual file plugins for the supported data types. data_types = [] # Go through the directory and try to load each plugin plugins = [] for m in pkgutil.iter_modules(path=__path__): if verbose: print("Loading file plugin:", m[1], ".", end=' ') try: spec = importlib.util.spec_from_file_location(m.name, os.path.join(__path__[0],m.name+'.py')) module = importlib.util.module_from_spec(spec) sys.modules[m.name] = module spec.loader.exec_module(module) # check if there is a read() function in plugin if 'read' in dir(module): plugins.append(module) data_types.extend(plugins[-1].read_types) # pull data types from each file plugin if verbose: print("("+plugins[-1].title+") Success!") else: if verbose: print('Not a valid plugin - skipping.') except ImportError as e: if verbose: print("prerequisites not satisfied:", e) # if getattr(sys, 'frozen', False): # module_names = ['file_dataexch_hdf5', 'file_ncb', 'file_nexus_hdf5', 'file_sdf', 'file_stk', 'file_tif', 'file_xrm'] # for m in module_names: # if verbose: print "Loading file plugin:", m, "...", # try: # # # details = imp.find_module(m) # # check if there is a read() function in plugin # if 'read' in dir(imp.load_module(m,*details)): # plugins.append(imp.load_module(m,*details)) # if verbose: print "("+plugins[-1].title+") Success!" # else: # if verbose: print 'Not a valid plugin - skipping.' # # except ImportError as e: # if verbose: print "prerequisites not satisfied:", e # Go through set of plugins and assemble lists of supported file types for each action and data type data_types = list(dict.fromkeys(data_types)) # remove any duplicates supported_plugins = dict([a,dict([t,[]] for t in data_types)] for a in actions) # dict of supported plugins. Empty! supported_filters = dict([a,dict([t,[]] for t in data_types)] for a in actions) # dict of supported file formats. Empty! filter_list = dict([a,dict([t,[]] for t in data_types)] for a in actions) # separate entries for each file format. Empty! for P in plugins: for action in actions: for data_type in data_types: if data_type in getattr(P,action+'_types'): filter_list[action][data_type].append(P.title+' ('+' '.join(P.extension)+')') # Fill filter_list with file extensions for each scan/data type supported_plugins[action][data_type].append(P) # Fill supported_plugins with plugin for each scan/data type for ext in P.extension: if ext not in supported_filters[action][data_type]: supported_filters[action][data_type].append(ext) # Fill supported_filters with file extensions for each scan/data typeif not already present for data_type in data_types: filter_list['read'][data_type] = ['Supported Formats ('+' '.join(supported_filters['read'][data_type])+')']+filter_list['read'][data_type] filter_list['read'][data_type].append('All files (*.*)') def load(filename, stack_object=None, plugin=None, selection=None, json=None, inorm=None): """ Pass the load command over to the appropriate plugin so that it can import data from the named file. """ if plugin is None: plugin = identify(filename) if plugin is None: return None else: print("load", filename, "with the", plugin.title, "plugin.") if selection is None: plugin.read(filename, stack_object, selection, json, inorm) elif len(selection) == 1: # one region only plugin.read(filename, stack_object, selection[0], json, inorm) else: # multiple regions selected at once. collating absdata plugin.read(filename, stack_object, selection[0], json, inorm) temp_stack = data_stack.data(stack_object.data_struct) full_stack = stack_object.absdata.copy() for s in selection[1:]: plugin.read(filename,temp_stack,s) if full_stack.shape[1] > temp_stack.absdata.shape[1]: temp_stack.absdata = numpy.pad(temp_stack.absdata,((0,0),(0,full_stack.shape[1]-temp_stack.absdata.shape[1]),(0,0)), mode='constant',constant_values=0) elif full_stack.shape[1] < temp_stack.absdata.shape[1]: full_stack = numpy.pad(full_stack,((0,0),(0,temp_stack.absdata.shape[1]-full_stack.shape[1]),(0,0)), mode='constant',constant_values=0) full_stack = numpy.vstack((full_stack,temp_stack.absdata)) stack_object.absdata = full_stack stack_object.x_dist = numpy.arange(full_stack.shape[0]) stack_object.y_dist = numpy.arange(full_stack.shape[1]) stack_object.n_cols = len(stack_object.x_dist) stack_object.n_rows = len(stack_object.y_dist) return def save(filename, data_object, data_type, plugin=None): """ Pass the save command over to the appropriate plugin so that it can write data to the named file. """ print("save", filename, "with the", plugin.title, "plugin.") plugin.write(filename, data_object, data_type) def GetFileStructure(filename, plugin=None): """ Use the plugin to skim-read the file and return the structure of the data. Returns None if there is only a single data array (i.e. no choices to be made). """ if plugin is None: plugin = identify(filename) if plugin is None: return None else: print("get info from", filename, "with the", plugin.title, "plugin.") FileInfo = plugin.GetFileStructure(filename) #if FileInfo is not None: #print len(FileInfo), len(FileInfo[next(iter(FileInfo))]) #print FileInfo return FileInfo def identify(filename): """ Cycle through plugins until finding one that claims to understand the file format. First it tries those claiming corresponding file extensions, followed by all other plugins until an appropriate plugin is found. """ print("Identifying file:", filename, "...", end=' ') ext = os.path.splitext(filename)[1] flag = [True]*len(plugins) for i,P in enumerate(plugins): if '*'+ext in P.extension: if P.identify(filename): return P elif flag[i] == True: #if plugin returns False, e.g. dataexch_hdf5 does not match, try the next plugin and find the same extension flag[i] = False continue else: break print("Error! unknown file type.") return None ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_bim.py0000664000175000017500000001444514700316175022356 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division import numpy as np import os #----------------------------------------------------------------------- def read_bim(self, filename): f = open(str(filename),'rb') data = np.fromfile(f, np.uint32, 6) nmotpos = data[0] ndatatype = data[1] ndate = data[2] naxisnames = data[3] self.n_cols = data[4] self.n_rows = data[5] self.n_ev = 1 angles = np.fromfile(f, np.float64, 1) pixelsize = np.fromfile(f, np.float32, 1) self.x_dist = np.arange(float(self.n_cols))*pixelsize self.y_dist = np.arange(float(self.n_rows))*pixelsize data = np.fromfile(f, np.uint32, 2) hbin = data[0] vbin = data[1] energy = np.fromfile(f, np.float64, 1) motpos = np.fromfile(f, np.float32, nmotpos) axisnames = np.fromfile(f, np.uint8, naxisnames) exposuretime = np.fromfile(f, np.float32, 1) nimages = np.fromfile(f, np.uint32, 1) data = np.fromfile(f, np.uint8, ndatatype) data = np.fromfile(f, np.uint8, ndate) npix = self.n_cols*self.n_rows imagestack = np.fromfile(f, np.float32, npix) self.absdata = np.reshape(imagestack, (self.n_cols, self.n_rows, self.n_ev), order='C') fn = os.path.basename(str(filename)) fnlist = fn.split('_') ind = fnlist.index('eV') self.ev = [float(fnlist[ind-1])] self.data_dwell = np.zeros((self.n_ev))+exposuretime f.close() return #----------------------------------------------------------------------- def read_bim_info(filename): f = open(str(filename),'rb') data = np.fromfile(f, np.uint32, 6) n_cols = data[4] n_rows = data[5] f.close() fn = os.path.basename(str(filename)) fnlist = fn.split('_') ind = fnlist.index('eV') ev = float(fnlist[ind-1]) return n_cols, n_rows, ev #----------------------------------------------------------------------- def read_bim_list(self, filelist, filepath, ds): #Fill the common stack data file1 = os.path.join(filepath, filelist[0]) f = open(str(file1),'rb') data = np.fromfile(f, np.uint32, 6) nmotpos = data[0] ndatatype = data[1] ndate = data[2] naxisnames = data[3] ncols = data[4] nrows = data[5] npix = ncols*nrows nev = len(filelist) absdata = np.zeros((ncols, nrows, nev)) ev = np.zeros((nev)) angles = np.fromfile(f, np.float64, 1) pixelsize = np.fromfile(f, np.float32, 1) x_dist = np.arange(float(ncols))*pixelsize y_dist = np.arange(float(nrows))*pixelsize data = np.fromfile(f, np.uint32, 2) hbin = data[0] vbin = data[1] energy = np.fromfile(f, np.float64, 1) motpos = np.fromfile(f, np.float32, nmotpos) axisnames = np.fromfile(f, np.uint8, naxisnames) exposuretime = np.fromfile(f, np.float32, 1) nimages = np.fromfile(f, np.uint32, 1) data = np.fromfile(f, np.uint8, ndatatype) data = np.fromfile(f, np.uint8, ndate) dwell_msec = exposuretime f.close() #Read the image data for i in range(len(filelist)): fn = filelist[i] filename = os.path.join(filepath, fn) f = open(str(filename),'rb') data = np.fromfile(f, np.uint32, 6) angles = np.fromfile(f, np.float64, 1) pixelsize = np.fromfile(f, np.float32, 1) data = np.fromfile(f, np.uint32, 2) energy = np.fromfile(f, np.float64, 1) motpos = np.fromfile(f, np.float32, nmotpos) axisnames = np.fromfile(f, np.uint8, naxisnames) exposuretime = np.fromfile(f, np.float32, 1) nimages = np.fromfile(f, np.uint32, 1) data = np.fromfile(f, np.uint8, ndatatype) data = np.fromfile(f, np.uint8, ndate) imagestack = np.fromfile(f, np.float32, npix) absdata[:,:,i] = np.reshape(imagestack, (ncols, nrows), order='C') fn = os.path.basename(str(filename)) fnlist = fn.split('_') ind = fnlist.index('eV') ev[i] = float(fnlist[ind-1]) f.close() if ev[-1]. import numpy import os title = 'text table' extension = ['*.csv','*.txt','*.xas'] read_types = ['spectrum','stack'] write_types = ['spectrum','stack'] def identify(filename): try: with open(filename, 'tr') as check_file: # try open file in text mode and read a single line check_file.readline() return True except: return False def GetFileStructure(FileName): return None #---------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): with open(str(filename),'r') as f: Line = f.readline().split() if Line == ['#X', '#Y', '#Wave', '#Intensity']: read_stack(filename, self, **kwargs) else: read_spectrum(filename, self, **kwargs) return def read_stack(filename, self, selection=None, *args, **kwargs): """ This reads the text version of an SPC hyperspectral map from a Raman measurement. """ f = open(str(filename),'r') xlist = [] ylist = [] wlist = [] ilist = [] for line in f: if line.startswith(("*","%","#")): pass else: x, y, w, i = [float (x) for x in line.split()] xlist.append(x) ylist.append(y) wlist.append(w) ilist.append(i) self.x_dist = numpy.unique(xlist) self.y_dist = numpy.unique(ylist) self.ev = numpy.unique(wlist) self.n_cols = len(self.x_dist) self.n_rows = len(self.y_dist) self.n_ev = len(self.ev) self.data_dwell = numpy.ones((self.n_ev))*1.0 self.absdata = numpy.empty((self.n_cols,self.n_rows, self.n_ev)) self.absdata = numpy.flip(numpy.transpose(numpy.reshape(ilist, (self.n_rows, self.n_cols, self.n_ev), order='C'), axes=[1,0,2]), axis=2) self.fill_h5_struct_from_stk() return def read_spectrum(filename, self, selection=None, *args, **kwargs): f = open(str(filename),'r') elist = [] ilist = [] for line in f: if line.startswith(("*","%","#")): pass else: e, i = [float (x) for x in line.split(',')] elist.append(e) ilist.append(i) self.evi0 = numpy.array(elist) self.i0data = numpy.array(ilist) f.close() self.i0_dwell = None return #---------------------------------------------------------------------- def write(filename, data_object, data_type): """Switchyard for writing different types of data.""" if data_type in ['spectrum']: write_spectrum(filename, data_object.absdata, data_object.ev) elif data_type in ['stack']: write_spectrum(filename, numpy.average(data_object.absdata,axis=(0,1)), energies=data_object.ev, title='I0 Spectrum') #---------------------------------------------------------------------- def write_spectrum(filename, data, energies, title=None ): if title is None: title = 'Spectrum' with open(filename, 'w') as f: print('********************* '+title+' ********************', file=f) print('*', file=f) print('* ev, intensity', file=f) for ie in range(len(energies)): print('{0:06.2f}, {1:06f}'.format(energies[ie], data[ie]), file=f) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_dataexch_hdf5.py0000775000175000017500000025201614700316175024277 0ustar00wattswatts# -*- coding: utf-8 -*- # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic - 2nd Look, Nicholas Schwarz, Benjamin Watts # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . # File format details can be found on # https://confluence.aps.anl.gov/display/NX/Data+Exchange+Basics from __future__ import division from __future__ import print_function import os import numpy as np import h5py from mantis_xray import data_struct verbose = 0 title = 'Exchange' extension = ['*.hdf','*.hdf5'] read_types = ['spectrum','image','stack'] write_types = ['spectrum','image','stack','results'] def identify(filename): try: # Open HDF5 file aps_format = False # Open HDF5 file f = h5py.File(filename, 'r') #Check is exchange is in the file if 'exchange' in f: aps_format = True f.close() return aps_format except: return False def read_old( filepath, data_stk): data_stk.read_h5(filepath) return def GetFileStructure(FileName): return None #----------------------------------------------------------------------- def read(filename, stack_object, selection=None, *args, **kwargs): """ToDo: .attrs returns a list in py2 and a set in py3! .attrs and .keys() need to be wrapped in list()""" have4d = 0 #new_stack = data_stack.data(data_struct) # Open HDF5 file f = h5py.File(filename, 'r') # Read basic definitions ds = f['implements'] data_struct.implements = ds[...] ds = f['version'] data_struct.version = ds[...] stack_object.have_dimscale = 0 #Information HDF5 group if 'information' in f: informationGrp = f['information'] if 'title' in informationGrp: title = informationGrp['title'] data_struct.information.title = title[...] if 'comment' in informationGrp: com = informationGrp['comment'] data_struct.information.comment = com[...] if 'file_creation_datetime' in informationGrp: fcdt = informationGrp['file_creation_datetime'] data_struct.information.file_creation_datetime = fcdt[...] # /ids if 'ids' in informationGrp: idsGrp = informationGrp['ids'] if 'proposal' in idsGrp: prop = idsGrp['proposal'] data_struct.information.ids.proposal = prop[...] if 'acitivity' in idsGrp: act = idsGrp['activity'] data_struct.information.ids.activity = act[...] if 'esaf' in idsGrp: esaf = idsGrp['esaf'] data_struct.information.ids.esaf = esaf[...] # /experimenter if 'experimenter' in informationGrp: experimenterGrp = informationGrp['experimenter'] if 'name' in experimenterGrp: name = experimenterGrp['name'] data_struct.information.experimenter.name = name[...] if 'role' in experimenterGrp: role = experimenterGrp['role'] data_struct.information.experimenter.role = role[...] if 'affiliation' in experimenterGrp: affiliation = experimenterGrp['affiliation'] data_struct.information.experimenter.affiliation = affiliation[...] if 'address' in experimenterGrp: address = experimenterGrp['address'] data_struct.information.experimenter.address = address[...] if 'phone' in experimenterGrp: phone = experimenterGrp['phone'] data_struct.information.experimenter.phone = phone[...] if 'email' in experimenterGrp: email = experimenterGrp['email'] data_struct.information.experimenter.email = email[...] if 'facility_user_id' in experimenterGrp: facility_user_id = experimenterGrp['facility_user_id'] data_struct.information.experimenter.facility_user_id = facility_user_id[...] # /sample if 'sample' in informationGrp: sampleGrp = informationGrp['sample'] if 'name' in sampleGrp: name = sampleGrp['name'] data_struct.information.sample.name = name[...] if 'description' in sampleGrp: description = sampleGrp['description'] data_struct.information.sample.description = description[...] if 'preparation_datetime' in sampleGrp: preparation_datetime = sampleGrp['preparation_datetime'] data_struct.information.sample.preparation_datetime = preparation_datetime[...] if 'chemical_formula' in sampleGrp: cf = sampleGrp['chemical_formula'] data_struct.information.sample.chemical_formula = cf[...] if 'environment' in sampleGrp: env = sampleGrp['environment'] data_struct.information.sample.environment = env[...] if 'temperature' in sampleGrp: temp = sampleGrp['temperature'] data_struct.information.sample.temperature = temp[...] data_struct.information.sample.temperature_units = temp.attrs['units'] if 'pressure' in sampleGrp: press = sampleGrp['pressure'] data_struct.information.sample.pressure = press[...] data_struct.information.sample.pressure_units = press.attrs['units'] # /objective if 'objective' in informationGrp: objectiveGrp = informationGrp['objective'] if 'manufacturer' in objectiveGrp: man = objectiveGrp['manufacturer'] data_struct.information.objective.manufacturer = man[...] if 'model' in objectiveGrp: model = objectiveGrp['model'] data_struct.information.objective.model = model[...] if 'comment' in objectiveGrp: com = objectiveGrp['comment'] data_struct.information.objective.comment = com[...] if 'magnification' in objectiveGrp: mag = objectiveGrp['magnification'] data_struct.information.objective.magnification = mag[...] # /scintillator if 'scintillator' in informationGrp: scintGrp = informationGrp['scintillator'] if 'name' in scintGrp: name = scintGrp['name'] data_struct.information.scintillator.name = name[...] if 'type' in scintGrp: type = scintGrp['type'] data_struct.information.scintillator.type = type[...] if 'comment' in scintGrp: com = scintGrp['comment'] data_struct.information.scintillator.comment = com[...] if 'scintillating_thickness' in scintGrp: sct = scintGrp['scintillating_thickness'] data_struct.information.scintillator.scintillating_thickness = sct[...] data_struct.information.scintillator.scintillating_thickness_units = sct.attrs['units'] if 'substrate_thickness' in scintGrp: subt = scintGrp['substrate_thickness'] data_struct.information.scintillator.substrate_thickness = subt[...] data_struct.information.scintillator.substrate_thickness_units = subt.attrs['units'] # /facility if 'facility' in informationGrp: facilityGrp = informationGrp['facility'] if 'name' in facilityGrp: name = facilityGrp['name'] data_struct.information.facility.name = name[...] if 'beamline' in facilityGrp: bl = facilityGrp['beamline'] data_struct.information.facility.beamline = bl[...] # /accelerator if 'accelerator' in informationGrp: acceleratorGrp = informationGrp['accelerator'] if 'ring_current' in acceleratorGrp: rc = acceleratorGrp['ring_current'] data_struct.information.accelerator.ring_current = rc[...] data_struct.information.accelerator.ring_current_units = rc.attrs['units'] if 'primary_beam_energy' in acceleratorGrp: pbe = acceleratorGrp['primary_beam_energy'] data_struct.information.accelerator.primary_beam_energy = pbe[...] data_struct.information.accelerator.primary_beam_energy_units = pbe.attrs['units'] if 'monostripe' in acceleratorGrp: ms = acceleratorGrp['monostripe'] data_struct.information.accelerator.monostripe = ms[...] # /detector if 'detector' in informationGrp: detectorGrp = informationGrp['detector'] if 'manufacturer' in detectorGrp: manf = detectorGrp['manufacturer'] data_struct.information.detector.manufacturer = manf[...] if 'model' in detectorGrp: model = detectorGrp['model'] data_struct.information.detector.model = model[...] if 'serial_number' in detectorGrp: sn = detectorGrp['serial_number'] data_struct.information.detector.serial_number = sn[...] if 'bit_depth' in detectorGrp: bd = detectorGrp['bit_depth'] data_struct.information.detector.bit_depth = bd[...] if 'operating_temperature' in detectorGrp: ot = detectorGrp['operating_temperature'] data_struct.information.detector.operating_temperature = ot[...] data_struct.information.detector.operating_temperature_units = ot.attrs['units'] if 'exposure_time' in detectorGrp: et = detectorGrp['exposure_time'] data_struct.information.detector.exposure_time = et[...] data_struct.information.detector.exposure_time_units = et.attrs['units'] if 'frame_rate' in detectorGrp: fr = detectorGrp['frame_rate'] data_struct.information.detector.frame_rate = fr[...] if 'pixel_size' in detectorGrp: psGrp = detectorGrp['pixel_size'] hor = psGrp['horizontal'] data_struct.information.detector.pixel_size.horizontal = hor[...] data_struct.information.detector.pixel_size.horizontal_units = hor.attrs['units'] ver = psGrp['vertical'] data_struct.information.detector.pixel_size.vertical = ver[...] data_struct.information.detector.pixel_size.vertical_units = ver.attrs['units'] if 'dimensions' in detectorGrp: dimGrp = detectorGrp['dimenstions'] h = dimGrp['horizontal'] data_struct.information.detector.dimensions.horizontal = h[...] v = dimGrp['vertical'] data_struct.information.detector.dimensions.vertical = v[...] if 'binning' in detectorGrp: binningGrp = detectorGrp['binning'] h = binningGrp['horizontal'] data_struct.information.detector.binning.horizontal = h[...] v = binningGrp['vertical'] data_struct.information.detector.binning.vertical = v[...] if 'axis_directions' in detectorGrp: axisdirGrp = detectorGrp['axis_directions'] h = axisdirGrp['horizontal'] data_struct.information.detector.axis_directions.horizontal = h[...] v = axisdirGrp['vertical'] data_struct.information.detector.axis_directions.vertical = v[...] if 'roi' in detectorGrp: roiGrp = detectorGrp['roi'] x1 = roiGrp['x1'] data_struct.information.detector.roi.x1 = x1[...] y1 = roiGrp['y1'] data_struct.information.detector.roi.y1 = y1[...] x2 = roiGrp['x2'] data_struct.information.detector.roi.x2 = x2[...] y2 = roiGrp['x2'] data_struct.information.detector.roi.y2 = y2[...] #Exchange HDF5 Group if 'exchange' in f: exchangeGrp = f['exchange'] if 'title' in exchangeGrp: title = exchangeGrp['title'] data_struct.exchange.title = title[...] if 'comment' in exchangeGrp: com = exchangeGrp['comment'] data_struct.exchange.comment = com[...] if 'data_collection_datetime' in exchangeGrp: dcdt = exchangeGrp['data_collection_datetime'] data_struct.exchange.data_collection_datetime = dcdt[...] if data_struct.version != '1.0': #load old version data # /exchange/detector # detectorGrp = exchangeGrp['detector'] # dsdata = detectorGrp['data'] # data_struct.exchange.data = dsdata[...] #detectorGrp = exchangeGrp['detector'] dsdata = exchangeGrp['data'] data_struct.exchange.data = dsdata[...] if 'axes' in dsdata.attrs: data_struct.exchange.data_axes = dsdata.attrs['axes'] if 'x' in exchangeGrp: dsx = exchangeGrp['x'] data_struct.exchange.x = dsx[...] data_struct.have_dimscale = 1 if 'y' in exchangeGrp: dsy = exchangeGrp['y'] data_struct.exchange.y = dsy[...] # # if 'x' in detectorGrp: # dsx = detectorGrp['x'] # data_struct.exchange.x = dsx[...] # stack_object.have_dimscale = 1 # # if 'y' in detectorGrp: # dsy = detectorGrp['y'] # data_struct.exchange.y = dsy[...] dseng = exchangeGrp['energy'] data_struct.exchange.energy = dseng[...] data_struct.exchange.energy_units = dseng.attrs['units'] else: # Version 1.0 data = exchangeGrp['data'] data_struct.exchange.data = data[...] if 'signal' in data.attrs: data_struct.exchange.data_signal = data.attrs['signal'] if 'description' in data.attrs: data_struct.exchange.data_description = data.attrs['description'] if 'units' in data.attrs: data_struct.exchange.data_units = data.attrs['units'] if 'detector' in data.attrs: data_struct.exchange.data_detector = data.attrs['detector'] if 'axes' in data.attrs: data_struct.exchange.data_axes = data.attrs['axes'] axes_list = data_struct.exchange.data_axes.split(':') #Axes list can be arbitrary but for spectromicroscopy it is always x:y:z for i in axes_list: ax = exchangeGrp[i] if i == 'x': data_struct.exchange.x = ax[...] if 'units' in ax.attrs: data_struct.exchange.x_units = ax.attrs['units'] stack_object.have_dimscale = 1 if i == 'y': data_struct.exchange.y = ax[...] if 'units' in ax.attrs: data_struct.exchange.y_units = ax.attrs['units'] if i == 'z': data_struct.exchange.z = ax[...] if 'units' in ax.attrs: data_struct.exchange.z_units = ax.attrs['units'] energy = exchangeGrp['energy'] data_struct.exchange.energy = energy[...] data_struct.exchange.energy_units= energy.attrs['units'] if 'theta' in exchangeGrp: th = exchangeGrp['theta'] data_struct.exchange.theta = th[...] data_struct.exchange.theta_units = th.attrs['units'] have4d = 1 if 'white_data' in exchangeGrp: wd = exchangeGrp['white_data'] data_struct.exchange.white_data = wd[...] data_struct.exchange.white_data_units = wd.attrs['units'] if 'dark_data' in exchangeGrp: dd = exchangeGrp['dark_data'] data_struct.exchange.dark_data = dd[...] data_struct.exchange.dark_data_units = dd.attrs['units'] if 'rotation' in exchangeGrp: rot = exchangeGrp['rotation'] data_struct.exchange.rotation = rot[...] # Spectromicroscopy HDF5 group if 'spectromicroscopy' in f: spectromicroscopyGrp = f['spectromicroscopy'] if 'positions' in spectromicroscopyGrp: pos = spectromicroscopyGrp['positions'] data_struct.spectromicroscopy.positions = pos[...] data_struct.spectromicroscopy.positions_units = pos.attrs['units'] data_struct.spectromicroscopy.positions_names = pos.attrs['names'] # if 'optical_density' in spectromicroscopyGrp: # od = spectromicroscopyGrp['optical_density'] # stack_object.data_struct.spectromicroscopy.optical_density = od[...] if 'normalization' in spectromicroscopyGrp: normGrp = spectromicroscopyGrp['normalization'] ws = normGrp['white_spectrum'] #Check if white_spectrum is an array wspectrum = ws[...] try: data_struct.spectromicroscopy.normalization.white_spectrum = ws[...] if 'units' in ws.attrs: data_struct.spectromicroscopy.normalization.white_spectrum_units = ws.attrs['units'] wse = normGrp['white_spectrum_energy'] data_struct.spectromicroscopy.normalization.white_spectrum_energy = wse[...] if 'units' in ws.attrs: data_struct.spectromicroscopy.normalization.white_spectrum_energy_units = wse.attrs['units'] except: pass # Close f.close() if have4d == 0: stack_object.absdata = data_struct.exchange.data else: stack_object.stack4D = data_struct.exchange.data stack_object.theta = data_struct.exchange.theta stack_object.n_theta = len(stack_object.theta) stack_object.absdata = stack_object.stack4D[:,:,:,0] datadim = np.int32(stack_object.absdata.shape) stack_object.n_cols = datadim[0].copy() stack_object.n_rows = datadim[1].copy() stack_object.ev = data_struct.exchange.energy stack_object.n_ev = np.int32(stack_object.ev.shape[0]).copy() npixels = stack_object.n_cols*stack_object.n_rows*stack_object.n_ev #Check if the energies are consecutive, if they are not sort the data consec = 0 for i in range(stack_object.n_ev - 1): if stack_object.ev[i] > stack_object.ev[i+1]: consec = 1 break if (consec == 1) and (have4d == 0): if verbose == 1: print("sort the energy data") sortind = np.argsort(stack_object.ev) stack_object.ev = stack_object.ev[sortind] stack_object.absdata = stack_object.absdata[:,:,sortind] #Save sorted energies: stack_object.data_struct.exchange.data = stack_object.absdata stack_object.data_struct.exchange.energy = stack_object.ev if stack_object.have_dimscale == 1: stack_object.x_dist = data_struct.exchange.x stack_object.y_dist = data_struct.exchange.y else: stack_object.x_dist = range(stack_object.n_cols) stack_object.y_dist = range(stack_object.n_rows) stack_object.i0data = data_struct.spectromicroscopy.normalization.white_spectrum stack_object.evi0 = data_struct.spectromicroscopy.normalization.white_spectrum_energy stack_object.data_dwell = np.ones((stack_object.n_ev)) stack_object.i0_dwell = np.ones((stack_object.n_ev)) if stack_object.data_struct.spectromicroscopy.normalization.white_spectrum is not None: stack_object.calculate_optical_density() stack_object.fill_h5_struct_normalization() if verbose == 1: print('filename ', filename) #print 'File creation date ', data_struct.file_creation_datetime print('Data array shape: ', stack_object.absdata.shape) print('n columns ', stack_object.n_cols) print('n_rows ', stack_object.n_rows) print('n_ev ', stack_object.n_ev) print('ev array ', stack_object.ev) print('x dist ', stack_object.x_dist) print('y_dist ', stack_object.y_dist) #print 'type ', type #print 'i0 data ', stack_object.i0data #print 'evi0 ', stack_object.evi0 return #----------------------------------------------------------------------- def write(filename, data_object, data_type): """Switchyard for writing different types of data.""" if data_type in ['stack']: write_h5(filename, data_object.data_struct) #----------------------------------------------------------------------- def write_h5(filename, data_struct): # Open HDF5 file f = h5py.File(filename, 'w') # This is an implementation of version 1.0 data_struct.implements = 'information:exchange:spectromicroscopy' data_struct.version = '1.0' # HDF5 Root Group ds = f.create_dataset('implements', data = data_struct.implements) ds = f.create_dataset('version', data = data_struct.version) #Information HDF5 group if 'information' in dir(data_struct): informationGrp = f.create_group('information') if data_struct.information.title is not None: ds = informationGrp.create_dataset('title', data = data_struct.information.title) if data_struct.information.comment is not None: ds = informationGrp.create_dataset('comment', data = str(data_struct.information.comment)) if data_struct.information.file_creation_datetime is not None: ds = informationGrp.create_dataset('file_creation_datetime', data = str(data_struct.information.file_creation_datetime)) # /ids idsGrp = informationGrp.create_group('ids') have_ids = 0 if data_struct.information.ids.proposal is not None: ds = idsGrp.create_dataset('proposal', data = data_struct.information.ids.proposal) have_ids = 1 if data_struct.information.ids.activity is not None: ds = idsGrp.create_dataset('activity', data = data_struct.information.ids.activity) have_ids = 1 if data_struct.information.ids.esaf is not None: ds = idsGrp.create_dataset('esaf', data = data_struct.information.ids.esaf) have_ids = 1 if have_ids == 0: del informationGrp['ids'] # /experimenter experimenterGrp = informationGrp.create_group('experimenter') have_exp = 0 if data_struct.information.experimenter.name is not None: ds = experimenterGrp.create_dataset('name', data = str(data_struct.information.experimenter.name)) have_exp = 1 if data_struct.information.experimenter.role is not None: ds = experimenterGrp.create_dataset('role', data = data_struct.information.experimenter.role) have_exp = 1 if data_struct.information.experimenter.affiliation is not None: ds = experimenterGrp.create_dataset('affiliation', data = data_struct.information.experimenter.affiliation) have_exp = 1 if data_struct.information.experimenter.address is not None: ds = experimenterGrp.create_dataset('address', data = data_struct.information.experimenter.address) have_exp = 1 if data_struct.information.experimenter.phone is not None: ds = experimenterGrp.create_dataset('phone', data = data_struct.information.experimenter.phone) have_exp = 1 if data_struct.information.experimenter.email is not None: ds = experimenterGrp.create_dataset('email', data = data_struct.information.experimenter.email) have_exp = 1 if data_struct.information.experimenter.facility_user_id is not None: ds = experimenterGrp.create_dataset('facility_user_id', data = data_struct.information.experimenter.facility_user_id) have_exp = 1 if have_exp == 0: del informationGrp['experimenter'] # /sample sampleGrp = informationGrp.create_group('sample') have_samp = 0 if data_struct.information.sample.name is not None: ds = sampleGrp.create_dataset('name', data = str(data_struct.information.sample.name)) have_samp = 1 if data_struct.information.sample.description is not None: ds = sampleGrp.create_dataset('description', data = data_struct.information.sample.description) have_samp = 1 if data_struct.information.sample.preparation_datetime is not None: ds = sampleGrp.create_dataset('preparation_datetime', data = data_struct.information.sample.preparation_datetime) have_samp = 1 if data_struct.information.sample.chemical_formula is not None: ds = sampleGrp.create_dataset('chemical_formula', data = data_struct.information.chemical_formula) have_samp = 1 if data_struct.information.sample.environment is not None: ds = sampleGrp.create_dataset('environment', data = data_struct.information.sample.environment) have_samp = 1 if data_struct.information.sample.temperature is not None: ds = sampleGrp.create_dataset('temperature', data = data_struct.information.sample.temperature) ds.attrs['units'] = data_struct.information.sample.temperature_units have_samp = 1 if data_struct.information.sample.pressure is not None: ds = sampleGrp.create_dataset('pressure', data = data_struct.information.sample.pressure) ds.attrs['units'] = data_struct.information.sample.pressure_units have_samp = 1 if have_samp == 0: del informationGrp['sample'] # /objective objectiveGrp = informationGrp.create_group('objective') have_obj = 0 if data_struct.information.objective.manufacturer is not None: ds = objectiveGrp.create_dataset('manufacturer', data = data_struct.information.objective.manufacturer) have_obj = 1 if data_struct.information.objective.model is not None: ds = objectiveGrp.create_dataset('model', data = data_struct.information.objective.model) have_obj = 1 if data_struct.information.objective.comment is not None: ds = objectiveGrp.create_dataset('comment', data = data_struct.information.objective.comment) have_obj = 1 if data_struct.information.objective.magnification is not None: ds = objectiveGrp.create_dataset('magnification', data = data_struct.information.objective.magnification) have_obj = 1 if have_obj == 0: del informationGrp['objective'] # /scintillator scintGrp = informationGrp.create_group('scintillator') have_scint = 0 if data_struct.information.scintillator.name is not None: ds = scintGrp.create_dataset('name', data = data_struct.information.scintillator.name) have_scint = 1 if data_struct.information.scintillator.type is not None: ds = scintGrp.create_dataset('type', data = data_struct.information.scintillator.type) have_scint = 1 if data_struct.information.scintillator.comment is not None: ds = scintGrp.create_dataset('comment', data = data_struct.information.scintillator.comment) have_scint = 1 if data_struct.information.scintillator.scintillating_thickness is not None: ds = scintGrp.create_dataset('scintillating_thickness', data = data_struct.information.scintillator.scintillating_thickness) ds.attrs['units'] = data_struct.information.scintillator.scintillating_thickness_units have_scint = 1 if data_struct.information.scintillator.substrate_thickness is not None: ds = scintGrp.create_dataset('substrate_thickness', data = data_struct.information.scintillator.substrate_thickness) ds.attrs['units'] = data_struct.information.scintillator.substrate_thickness_units have_scint = 1 if have_scint == 0: del informationGrp['scintillator'] # /facility facilityGrp = informationGrp.create_group('facility') have_fac = 0 if data_struct.information.facility.name is not None: ds = facilityGrp.create_dataset('name', data = data_struct.information.facility.name) have_fac = 1 if data_struct.information.facility.beamline is not None: ds = facilityGrp.create_dataset('beamline', data = data_struct.information.facility.beamline) have_fac = 1 if have_fac == 0: del informationGrp['facility'] # /accelerator acceleratorGrp = informationGrp.create_group('accelerator') have_ac = 0 if data_struct.information.accelerator.ring_current is not None: ds = acceleratorGrp.create_dataset('ring_current', data = data_struct.information.accelerator.ring_current) ds.attrs['units'] = data_struct.information.accelerator.ring_current_units have_ac = 1 if data_struct.information.accelerator.primary_beam_energy is not None: ds = acceleratorGrp.create_dataset('primary_beam_energy', data = data_struct.information.accelerator.primary_beam_energy) ds.attrs['units'] = data_struct.information.accelerator.primary_beam_energy_units have_ac = 1 if data_struct.information.accelerator.monostripe is not None: ds = acceleratorGrp.create_dataset('monostripe', data = data_struct.information.accelerator.monostripe) have_ac = 1 if have_ac == 0: del informationGrp['accelerator'] # /detector detectorGrp = informationGrp.create_group('detector') have_detc = 0 if data_struct.information.detector.manufacturer is not None: ds = detectorGrp.create_dataset('manufacturer', data = data_struct.information.detector.manufacturer) have_detc = 1 if data_struct.information.detector.model is not None: ds = detectorGrp.create_dataset('model', data = data_struct.information.detector.model) have_detc = 1 if data_struct.information.detector.serial_number is not None: ds = detectorGrp.create_dataset('serial_number', data = data_struct.information.detector.serial_number) have_detc = 1 if data_struct.information.detector.bit_depth is not None: ds = detectorGrp.create_dataset('bit_depth', data = data_struct.information.detector.bit_depth) have_detc = 1 if data_struct.information.detector.operating_temperature is not None: ds = detectorGrp.create_dataset('operating_temperature', data = data_struct.information.detector.operating_temperature) ds.attrs['units'] = data_struct.information.detector.operating_temperature_units have_detc = 1 if data_struct.information.detector.exposure_time is not None: ds = detectorGrp.create_dataset('exposure_time', data = data_struct.information.detector.exposure_time) ds.attrs['units'] = data_struct.information.detector.exposure_time_units have_detc = 1 if data_struct.information.detector.frame_rate is not None: ds = detectorGrp.create_dataset('frame_rate', data = data_struct.information.detector.frame_rate) have_detc = 1 if data_struct.information.detector.pixel_size.horizontal is not None: psGrp = detectorGrp.create_group('pixel_size') ds = psGrp.create_dataset('horizontal', data = data_struct.information.detector.pixel_size.horizontal) ds.attrs['units'] = data_struct.information.detector.pixel_size.horizontal_units ds = psGrp.create_dataset('vertical', data = data_struct.information.detector.pixel_size.vertical) ds.attrs['units'] = data_struct.information.detector.pixel_size.vertical_units have_detc = 1 if data_struct.information.detector.dimensions.horizontal is not None: dimGrp = detectorGrp.create_group('dimensions') ds = dimGrp.create_dataset('horizontal', data = data_struct.information.detector.dimensions.horizontal) ds = dimGrp.create_dataset('vertical', data = data_struct.information.detector.dimensions.vertical) have_detc = 1 if data_struct.information.detector.binning.horizontal is not None: binningGrp = detectorGrp.create_group('binning') ds = binningGrp.create_dataset('horizontal', data = data_struct.information.detector.binning.horizontal) ds = binningGrp.create_dataset('vertical', data = data_struct.information.detector.binning.vertical) have_detc = 1 if data_struct.information.detector.axis_directions.horizontal is not None: axisdirGrp = detectorGrp.create_group('axis_directions') ds = axisdirGrp.create_dataset('horizontal', data = data_struct.information.detector.axis_directions.horizontal) ds = axisdirGrp.create_dataset('vertical', data = data_struct.information.detector.axis_directions.vertical) have_detc = 1 if data_struct.information.detector.roi.x1 is not None: roiGrp = detectorGrp.create_group('roi') ds = roiGrp.create_dataset('x1', data = data_struct.information.detector.roi.x1) ds = roiGrp.create_dataset('y1', data = data_struct.information.detector.roi.y1) ds = roiGrp.create_dataset('x2', data = data_struct.information.detector.roi.x2) ds = roiGrp.create_dataset('y2', data = data_struct.information.detector.roi.y2) have_detc = 1 if have_detc == 0: del informationGrp['detector'] # exchange definition # exchange HDF5 group # /exchange if 'exchange' in dir(data_struct): exchangeGrp = f.create_group("exchange") if data_struct.exchange.title is not None: ds = exchangeGrp.create_dataset('title', data = data_struct.exchange.title) if data_struct.exchange.comment is not None: ds = exchangeGrp.create_dataset('comment', data = data_struct.exchange.comment) if data_struct.exchange.data_collection_datetime is not None: ds = exchangeGrp.create_dataset('data_collection_datetime', data = data_struct.exchange.data_collection_datetime) # /exchange/data ds_data = exchangeGrp.create_dataset('data', data = data_struct.exchange.data) if data_struct.exchange.data_signal is not None: ds_data.attrs['signal'] = data_struct.exchange.data_signal if data_struct.exchange.data_description is not None: ds_data.attrs['description'] = data_struct.exchange.data_description if data_struct.exchange.data_units is not None: ds_data.attrs['units'] = data_struct.exchange.data_units if data_struct.exchange.data_detector is not None: ds_data.attrs['detector'] = data_struct.exchange.data_detector if data_struct.exchange.data_axes is not None: ds_data.attrs['axes'] = data_struct.exchange.data_axes if data_struct.exchange.x is not None: ds = exchangeGrp.create_dataset('x', data = data_struct.exchange.x) if data_struct.exchange.x_units is not None: ds.attrs['units'] = data_struct.exchange.x_units if data_struct.exchange.y is not None: ds = exchangeGrp.create_dataset('y', data = data_struct.exchange.y) if data_struct.exchange.y_units is not None: ds.attrs['units'] = data_struct.exchange.y_units if data_struct.exchange.z is not None: ds = exchangeGrp.create_dataset('z', data = data_struct.exchange.z) if data_struct.exchange.z_units is not None: ds.attrs['units'] = data_struct.exchange.z_units if data_struct.exchange.energy is not None: ds = exchangeGrp.create_dataset('energy', data = data_struct.exchange.energy) if data_struct.exchange.energy_units is not None: ds.attrs['units'] = data_struct.exchange.energy_units if data_struct.exchange.theta is not None: ds = exchangeGrp.create_dataset('theta', data = data_struct.exchange.theta) ds.attrs['units'] = data_struct.exchange.theta_units # /exchange/white_data if data_struct.exchange.white_data is not None: ds = exchangeGrp.create_dataset('white_data', data = data_struct.exchange.white_data) ds.attrs['units'] = data_struct.exchange.white_data_units # /exchange/dark_data if data_struct.exchange.dark_data is not None: ds = exchangeGrp.create_dataset('dark_data', data = data_struct.exchange.dark_data) ds.attrs['units'] = data_struct.exchange.dark_data_units if data_struct.exchange.rotation is not None: ds = exchangeGrp.create_dataset('rotation', data = data_struct.exchange.rotation) # Spectromicroscopy HDF5 group if 'spectromicroscopy' in dir(data_struct): spectromicroscopyGrp = f.create_group('spectromicroscopy') if data_struct.spectromicroscopy.positions is not None: ds = spectromicroscopyGrp.create_dataset('positions', data = data_struct.spectromicroscopy.positions) if data_struct.spectromicroscopy.positions_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.positions_units if data_struct.spectromicroscopy.positions_names is not None: ds.attrs['names'] = data_struct.spectromicroscopy.positions_names if data_struct.spectromicroscopy.xshifts is not None: ds = spectromicroscopyGrp.create_dataset('xshifts', data = data_struct.spectromicroscopy.xshifts) if data_struct.spectromicroscopy.yshifts is not None: ds = spectromicroscopyGrp.create_dataset('yshifts', data = data_struct.spectromicroscopy.yshifts) if data_struct.spectromicroscopy.optical_density is not None: ds = spectromicroscopyGrp.create_dataset('optical_density', data = data_struct.spectromicroscopy.optical_density) # /spectromicroscopy/normalization normalizationGrp = spectromicroscopyGrp.create_group('normalization') if data_struct.spectromicroscopy.normalization.white_spectrum is not None: ds = normalizationGrp.create_dataset('white_spectrum', data = data_struct.spectromicroscopy.normalization.white_spectrum) if data_struct.spectromicroscopy.normalization.white_spectrum_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.normalization.white_spectrum_units ds = normalizationGrp.create_dataset('white_spectrum_energy', data = data_struct.spectromicroscopy.normalization.white_spectrum_energy) if data_struct.spectromicroscopy.normalization.white_spectrum_energy_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.normalization.white_spectrum_energy_units else: del spectromicroscopyGrp['normalization'] # Close f.close() #----------------------------------------------------------------------- def write_results_h5(filename, data_struct, anlz): test_file = 0 #Check if file exists try: # Open HDF5 file f = h5py.File(filename, 'r') test_file = 1 f.close() except: pass #Try to save new hdf5 file if test_file == 0: #try: write_h5(filename, data_struct) #except: # print 'Error: Could not open nor create HDF5 file ', filename # return -1 # Open HDF5 file f = h5py.File(filename, 'r+') # Read basic definitions ds = f['implements'] implements = ds[...] if 'Mantis' in f: mantisGrp = f['Mantis'] else: del f['implements'] implements = implements+':Mantis' ds = f.create_dataset('implements', data = implements) mantisGrp = f.create_group('Mantis') if anlz.pca_calculated == 1: if 'pca' in mantisGrp: del mantisGrp['pca'] pcaGrp = mantisGrp.create_group('pca') else: pcaGrp = mantisGrp.create_group('pca') ds_data = pcaGrp.create_dataset('pca_images', data = anlz.pcaimages) ds_data = pcaGrp.create_dataset('pca_eigenvectors', data = anlz.eigenvecs) ds_data = pcaGrp.create_dataset('pca_eigenvalues', data = anlz.eigenvals) if anlz.clusters_calculated == 1: if 'cluster_analysis' in mantisGrp: del mantisGrp['cluster_analysis'] caGrp = mantisGrp.create_group('cluster_analysis') else: caGrp = mantisGrp.create_group('cluster_analysis') ds_data = caGrp.create_dataset('cluster_indices', data = anlz.cluster_indices) ds_data = caGrp.create_dataset('cluster_spectra', data = anlz.clusterspectra) ds_data = caGrp.create_dataset('cluster_distances', data = anlz.cluster_distances) if anlz.tspectrum_loaded == 1: if 'spectral_maps' in mantisGrp: del mantisGrp['spectral_maps'] caGrp = mantisGrp.create_group('spectral_maps') else: caGrp = mantisGrp.create_group('spectral_maps') ds_data = caGrp.create_dataset('raw_maps', data = anlz.target_svd_maps) ds_data = caGrp.create_dataset('fitted_maps', data = anlz.target_pcafit_maps) ds_data = caGrp.create_dataset('raw_spectra', data = anlz.target_spectra) ds_data = caGrp.create_dataset('fitted_spectra', data = anlz.target_pcafit_spectra) f.close() return 0 #----------------------------------------------------------------------- class h5: def __init__(self): pass #----------------------------------------------------------------------- def check_h5_format(self, filename): aps_format = False # Open HDF5 file f = h5py.File(filename, 'r') #Check is exchange is in the file #Exchange HDF5 Group if 'exchange' in f: aps_format = True f.close() return aps_format #----------------------------------------------------------------------- def read_h5(self, filename):#, data_struct): have4d = 0 # Open HDF5 file f = h5py.File(filename, 'r') # Read basic definitions ds = f['implements'] data_struct.implements = ds[...] ds = f['version'] data_struct.version = ds[...] self.have_dimscale = 0 #Information HDF5 group if 'information' in f: informationGrp = f['information'] if 'title' in informationGrp: title = informationGrp['title'] data_struct.information.title = title[...] if 'comment' in informationGrp: com = informationGrp['comment'] data_struct.information.comment = com[...] if 'file_creation_datetime' in informationGrp: fcdt = informationGrp['file_creation_datetime'] data_struct.information.file_creation_datetime = fcdt[...] # /ids if 'ids' in informationGrp: idsGrp = informationGrp['ids'] if 'proposal' in idsGrp: prop = idsGrp['proposal'] data_struct.information.ids.proposal = prop[...] if 'acitivity' in idsGrp: act = idsGrp['activity'] data_struct.information.ids.activity = act[...] if 'esaf' in idsGrp: esaf = idsGrp['esaf'] data_struct.information.ids.esaf = esaf[...] # /experimenter if 'experimenter' in informationGrp: experimenterGrp = informationGrp['experimenter'] if 'name' in experimenterGrp: name = experimenterGrp['name'] data_struct.information.experimenter.name = name[...] if 'role' in experimenterGrp: role = experimenterGrp['role'] data_struct.information.experimenter.role = role[...] if 'affiliation' in experimenterGrp: affiliation = experimenterGrp['affiliation'] data_struct.information.experimenter.affiliation = affiliation[...] if 'address' in experimenterGrp: address = experimenterGrp['address'] data_struct.information.experimenter.address = address[...] if 'phone' in experimenterGrp: phone = experimenterGrp['phone'] data_struct.information.experimenter.phone = phone[...] if 'email' in experimenterGrp: email = experimenterGrp['email'] data_struct.information.experimenter.email = email[...] if 'facility_user_id' in experimenterGrp: facility_user_id = experimenterGrp['facility_user_id'] data_struct.information.experimenter.facility_user_id = facility_user_id[...] # /sample if 'sample' in informationGrp: sampleGrp = informationGrp['sample'] if 'name' in sampleGrp: name = sampleGrp['name'] data_struct.information.sample.name = name[...] if 'description' in sampleGrp: description = sampleGrp['description'] data_struct.information.sample.description = description[...] if 'preparation_datetime' in sampleGrp: preparation_datetime = sampleGrp['preparation_datetime'] data_struct.information.sample.preparation_datetime = preparation_datetime[...] if 'chemical_formula' in sampleGrp: cf = sampleGrp['chemical_formula'] data_struct.information.sample.chemical_formula = cf[...] if 'environment' in sampleGrp: env = sampleGrp['environment'] data_struct.information.sample.environment = env[...] if 'temperature' in sampleGrp: temp = sampleGrp['temperature'] data_struct.information.sample.temperature = temp[...] data_struct.information.sample.temperature_units = temp.attrs['units'] if 'pressure' in sampleGrp: press = sampleGrp['pressure'] data_struct.information.sample.pressure = press[...] data_struct.information.sample.pressure_units = press.attrs['units'] # /objective if 'objective' in informationGrp: objectiveGrp = informationGrp['objective'] if 'manufacturer' in objectiveGrp: man = objectiveGrp['manufacturer'] data_struct.information.objective.manufacturer = man[...] if 'model' in objectiveGrp: model = objectiveGrp['model'] data_struct.information.objective.model = model[...] if 'comment' in objectiveGrp: com = objectiveGrp['comment'] data_struct.information.objective.comment = com[...] if 'magnification' in objectiveGrp: mag = objectiveGrp['magnification'] data_struct.information.objective.magnification = mag[...] # /scintillator if 'scintillator' in informationGrp: scintGrp = informationGrp['scintillator'] if 'name' in scintGrp: name = scintGrp['name'] data_struct.information.scintillator.name = name[...] if 'type' in scintGrp: type = scintGrp['type'] data_struct.information.scintillator.type = type[...] if 'comment' in scintGrp: com = scintGrp['comment'] data_struct.information.scintillator.comment = com[...] if 'scintillating_thickness' in scintGrp: sct = scintGrp['scintillating_thickness'] data_struct.information.scintillator.scintillating_thickness = sct[...] data_struct.information.scintillator.scintillating_thickness_units = sct.attrs['units'] if 'substrate_thickness' in scintGrp: subt = scintGrp['substrate_thickness'] data_struct.information.scintillator.substrate_thickness = subt[...] data_struct.information.scintillator.substrate_thickness_units = subt.attrs['units'] # /facility if 'facility' in informationGrp: facilityGrp = informationGrp['facility'] if 'name' in facilityGrp: name = facilityGrp['name'] data_struct.information.facility.name = name[...] if 'beamline' in facilityGrp: bl = facilityGrp['beamline'] data_struct.information.facility.beamline = bl[...] # /accelerator if 'accelerator' in informationGrp: acceleratorGrp = informationGrp['accelerator'] if 'ring_current' in acceleratorGrp: rc = acceleratorGrp['ring_current'] data_struct.information.accelerator.ring_current = rc[...] data_struct.information.accelerator.ring_current_units = rc.attrs['units'] if 'primary_beam_energy' in acceleratorGrp: pbe = acceleratorGrp['primary_beam_energy'] data_struct.information.accelerator.primary_beam_energy = pbe[...] data_struct.information.accelerator.primary_beam_energy_units = pbe.attrs['units'] if 'monostripe' in acceleratorGrp: ms = acceleratorGrp['monostripe'] data_struct.information.accelerator.monostripe = ms[...] # /detector if 'detector' in informationGrp: detectorGrp = informationGrp['detector'] if 'manufacturer' in detectorGrp: manf = detectorGrp['manufacturer'] data_struct.information.detector.manufacturer = manf[...] if 'model' in detectorGrp: model = detectorGrp['model'] data_struct.information.detector.model = model[...] if 'serial_number' in detectorGrp: sn = detectorGrp['serial_number'] data_struct.information.detector.serial_number = sn[...] if 'bit_depth' in detectorGrp: bd = detectorGrp['bit_depth'] data_struct.information.detector.bit_depth = bd[...] if 'operating_temperature' in detectorGrp: ot = detectorGrp['operating_temperature'] data_struct.information.detector.operating_temperature = ot[...] data_struct.information.detector.operating_temperature_units = ot.attrs['units'] if 'exposure_time' in detectorGrp: et = detectorGrp['exposure_time'] data_struct.information.detector.exposure_time = et[...] data_struct.information.detector.exposure_time_units = et.attrs['units'] if 'frame_rate' in detectorGrp: fr = detectorGrp['frame_rate'] data_struct.information.detector.frame_rate = fr[...] if 'pixel_size' in detectorGrp: psGrp = detectorGrp['pixel_size'] hor = psGrp['horizontal'] data_struct.information.detector.pixel_size.horizontal = hor[...] data_struct.information.detector.pixel_size.horizontal_units = hor.attrs['units'] ver = psGrp['vertical'] data_struct.information.detector.pixel_size.vertical = ver[...] data_struct.information.detector.pixel_size.vertical_units = ver.attrs['units'] if 'dimensions' in detectorGrp: dimGrp = detectorGrp['dimenstions'] h = dimGrp['horizontal'] data_struct.information.detector.dimensions.horizontal = h[...] v = dimGrp['vertical'] data_struct.information.detector.dimensions.vertical = v[...] if 'binning' in detectorGrp: binningGrp = detectorGrp['binning'] h = binningGrp['horizontal'] data_struct.information.detector.binning.horizontal = h[...] v = binningGrp['vertical'] data_struct.information.detector.binning.vertical = v[...] if 'axis_directions' in detectorGrp: axisdirGrp = detectorGrp['axis_directions'] h = axisdirGrp['horizontal'] data_struct.information.detector.axis_directions.horizontal = h[...] v = axisdirGrp['vertical'] data_struct.information.detector.axis_directions.vertical = v[...] if 'roi' in detectorGrp: roiGrp = detectorGrp['roi'] x1 = roiGrp['x1'] data_struct.information.detector.roi.x1 = x1[...] y1 = roiGrp['y1'] data_struct.information.detector.roi.y1 = y1[...] x2 = roiGrp['x2'] data_struct.information.detector.roi.x2 = x2[...] y2 = roiGrp['x2'] data_struct.information.detector.roi.y2 = y2[...] #Exchange HDF5 Group if 'exchange' in f: exchangeGrp = f['exchange'] if 'title' in exchangeGrp: title = exchangeGrp['title'] data_struct.exchange.title = title[...] if 'comment' in exchangeGrp: com = exchangeGrp['comment'] data_struct.exchange.comment = com[...] if 'data_collection_datetime' in exchangeGrp: dcdt = exchangeGrp['data_collection_datetime'] data_struct.exchange.data_collection_datetime = dcdt[...] if data_struct.version != '1.0': #load old version data # /exchange/detector # detectorGrp = exchangeGrp['detector'] # dsdata = detectorGrp['data'] # data_struct.exchange.data = dsdata[...] #detectorGrp = exchangeGrp['detector'] dsdata = exchangeGrp['data'] data_struct.exchange.data = dsdata[...] if 'axes' in dsdata.attrs: data_struct.exchange.data_axes = dsdata.attrs['axes'] # # if 'x' in detectorGrp: # dsx = detectorGrp['x'] # data_struct.exchange.x = dsx[...] # self.have_dimscale = 1 # # if 'y' in detectorGrp: # dsy = detectorGrp['y'] # data_struct.exchange.y = dsy[...] dseng = exchangeGrp['energy'] data_struct.exchange.energy = dseng[...] #data_struct.exchange.energy_units = dseng.attrs['units'] else: # Version 1.0 data = exchangeGrp['data'] data_struct.exchange.data = data[...] if 'signal' in data.attrs: data_struct.exchange.data_signal = data.attrs['signal'] if 'description' in data.attrs: data_struct.exchange.data_description = data.attrs['description'] if 'units' in data.attrs: data_struct.exchange.data_units = data.attrs['units'] if 'detector' in data.attrs: data_struct.exchange.data_detector = data.attrs['detector'] if 'axes' in data.attrs: data_struct.exchange.data_axes = data.attrs['axes'] axes_list = data_struct.exchange.data_axes.split(':') #Axes list can be arbitrary but for spectromicroscopy it is always x:y:z for i in axes_list: ax = exchangeGrp[i] if i == 'x': data_struct.exchange.x = ax[...] if 'units' in ax.attrs: data_struct.exchange.x_units = ax.attrs['units'] self.have_dimscale = 1 if i == 'y': data_struct.exchange.y = ax[...] if 'units' in ax.attrs: data_struct.exchange.y_units = ax.attrs['units'] if i == 'z': data_struct.exchange.z = ax[...] if 'units' in ax.attrs: data_struct.exchange.z_units = ax.attrs['units'] energy = exchangeGrp['energy'] data_struct.exchange.energy = energy[...] data_struct.exchange.energy_units= energy.attrs['units'] if 'theta' in exchangeGrp: th = exchangeGrp['theta'] data_struct.exchange.theta = th[...] data_struct.exchange.theta_units = th.attrs['units'] have4d = 1 if 'white_data' in exchangeGrp: wd = exchangeGrp['white_data'] data_struct.exchange.white_data = wd[...] data_struct.exchange.white_data_units = wd.attrs['units'] if 'dark_data' in exchangeGrp: dd = exchangeGrp['dark_data'] data_struct.exchange.dark_data = dd[...] data_struct.exchange.dark_data_units = dd.attrs['units'] if 'rotation' in exchangeGrp: rot = exchangeGrp['rotation'] data_struct.exchange.rotation = rot[...] # Spectromicroscopy HDF5 group if 'spectromicroscopy' in f: spectromicroscopyGrp = f['spectromicroscopy'] if 'positions' in spectromicroscopyGrp: pos = spectromicroscopyGrp['positions'] data_struct.spectromicroscopy.positions = pos[...] data_struct.spectromicroscopy.positions_units = pos.attrs['units'] data_struct.spectromicroscopy.positions_names = pos.attrs['names'] # if 'optical_density' in spectromicroscopyGrp: # od = spectromicroscopyGrp['optical_density'] # self.data_struct.spectromicroscopy.optical_density = od[...] if 'normalization' in spectromicroscopyGrp: normGrp = spectromicroscopyGrp['normalization'] ws = normGrp['white_spectrum'] #Check if white_spectrum is an array wspectrum = ws[...] try: data_struct.spectromicroscopy.normalization.white_spectrum = ws[...] if 'units' in ws.attrs: data_struct.spectromicroscopy.normalization.white_spectrum_units = ws.attrs['units'] wse = normGrp['white_spectrum_energy'] data_struct.spectromicroscopy.normalization.white_spectrum_energy = wse[...] if 'units' in ws.attrs: data_struct.spectromicroscopy.normalization.white_spectrum_energy_units = wse.attrs['units'] except: pass # Close f.close() if have4d == 0: self.absdata = data_struct.exchange.data else: self.stack4D = data_struct.exchange.data self.theta = data_struct.exchange.theta self.n_theta = len(self.theta) self.absdata = self.stack4D[:,:,:,0] datadim = np.int32(self.absdata.shape) self.n_cols = datadim[0].copy() self.n_rows = datadim[1].copy() self.ev = data_struct.exchange.energy self.n_ev = np.int32(self.ev.shape[0]).copy() npixels = self.n_cols*self.n_rows*self.n_ev #Check if the energies are consecutive, if they are not sort the data consec = 0 for i in range(self.n_ev - 1): if self.ev[i] > self.ev[i+1]: consec = 1 break if consec == 1: if verbose == 1: print("sort the energy data") sortind = np.argsort(self.ev) self.ev = self.ev[sortind] self.absdata = self.absdata[:,:,sortind] #Save sorted energies: self.data_struct.exchange.data = self.absdata self.data_struct.exchange.energy = self.ev if self.have_dimscale == 1: self.x_dist = data_struct.exchange.x self.y_dist = data_struct.exchange.y else: self.x_dist = range(self.n_cols) self.y_dist = range(self.n_rows) self.i0data = data_struct.spectromicroscopy.normalization.white_spectrum self.evi0 = data_struct.spectromicroscopy.normalization.white_spectrum_energy self.data_dwell = np.ones((self.n_ev)) self.i0_dwell = np.ones((self.n_ev)) if verbose == 1: print('filename ', filename) #print 'File creation date ', data_struct.file_creation_datetime print('Data array shape: ', self.absdata.shape) print('n columns ', self.n_cols) print('n_rows ', self.n_rows) print('n_ev ', self.n_ev) print('ev array ', self.ev) print('x dist ', self.x_dist) print('y_dist ', self.y_dist) #print 'type ', type #print 'i0 data ', self.i0data #print 'evi0 ', self.evi0 return #----------------------------------------------------------------------- def write_h5(self, filename, data_struct): # Open HDF5 file f = h5py.File(filename, 'w') # This is an implementation of version 1.0 data_struct.implements = 'information:exchange:spectromicroscopy' data_struct.version = '1.0' # HDF5 Root Group ds = f.create_dataset('implements', data = data_struct.implements) ds = f.create_dataset('version', data = data_struct.version) #Information HDF5 group informationGrp = f.create_group('information') if data_struct.information.title is not None: ds = informationGrp.create_dataset('title', data = data_struct.information.title) if data_struct.information.comment is not None: ds = informationGrp.create_dataset('comment', data = str(data_struct.information.comment)) if data_struct.information.file_creation_datetime is not None: ds = informationGrp.create_dataset('file_creation_datetime', data = str(data_struct.information.file_creation_datetime)) # /ids idsGrp = informationGrp.create_group('ids') have_ids = 0 if data_struct.information.ids.proposal is not None: ds = idsGrp.create_dataset('proposal', data = data_struct.information.ids.proposal) have_ids = 1 if data_struct.information.ids.activity is not None: ds = idsGrp.create_dataset('activity', data = data_struct.information.ids.activity) have_ids = 1 if data_struct.information.ids.esaf is not None: ds = idsGrp.create_dataset('esaf', data = data_struct.information.ids.esaf) have_ids = 1 if have_ids == 0: del informationGrp['ids'] # /experimenter experimenterGrp = informationGrp.create_group('experimenter') have_exp = 0 if data_struct.information.experimenter.name is not None: ds = experimenterGrp.create_dataset('name', data = str(data_struct.information.experimenter.name)) have_exp = 1 if data_struct.information.experimenter.role is not None: ds = experimenterGrp.create_dataset('role', data = data_struct.information.experimenter.role) have_exp = 1 if data_struct.information.experimenter.affiliation is not None: ds = experimenterGrp.create_dataset('affiliation', data = data_struct.information.experimenter.affiliation) have_exp = 1 if data_struct.information.experimenter.address is not None: ds = experimenterGrp.create_dataset('address', data = data_struct.information.experimenter.address) have_exp = 1 if data_struct.information.experimenter.phone is not None: ds = experimenterGrp.create_dataset('phone', data = data_struct.information.experimenter.phone) have_exp = 1 if data_struct.information.experimenter.email is not None: ds = experimenterGrp.create_dataset('email', data = data_struct.information.experimenter.email) have_exp = 1 if data_struct.information.experimenter.facility_user_id is not None: ds = experimenterGrp.create_dataset('facility_user_id', data = data_struct.information.experimenter.facility_user_id) have_exp = 1 if have_exp == 0: del informationGrp['experimenter'] # /sample sampleGrp = informationGrp.create_group('sample') have_samp = 0 if data_struct.information.sample.name is not None: ds = sampleGrp.create_dataset('name', data = str(data_struct.information.sample.name)) have_samp = 1 if data_struct.information.sample.description is not None: ds = sampleGrp.create_dataset('description', data = data_struct.information.sample.description) have_samp = 1 if data_struct.information.sample.preparation_datetime is not None: ds = sampleGrp.create_dataset('preparation_datetime', data = data_struct.information.sample.preparation_datetime) have_samp = 1 if data_struct.information.sample.chemical_formula is not None: ds = sampleGrp.create_dataset('chemical_formula', data = data_struct.information.chemical_formula) have_samp = 1 if data_struct.information.sample.environment is not None: ds = sampleGrp.create_dataset('environment', data = data_struct.information.sample.environment) have_samp = 1 if data_struct.information.sample.temperature is not None: ds = sampleGrp.create_dataset('temperature', data = data_struct.information.sample.temperature) ds.attrs['units'] = data_struct.information.sample.temperature_units have_samp = 1 if data_struct.information.sample.pressure is not None: ds = sampleGrp.create_dataset('pressure', data = data_struct.information.sample.pressure) ds.attrs['units'] = data_struct.information.sample.pressure_units have_samp = 1 if have_samp == 0: del informationGrp['sample'] # /objective objectiveGrp = informationGrp.create_group('objective') have_obj = 0 if data_struct.information.objective.manufacturer is not None: ds = objectiveGrp.create_dataset('manufacturer', data = data_struct.information.objective.manufacturer) have_obj = 1 if data_struct.information.objective.model is not None: ds = objectiveGrp.create_dataset('model', data = data_struct.information.objective.model) have_obj = 1 if data_struct.information.objective.comment is not None: ds = objectiveGrp.create_dataset('comment', data = data_struct.information.objective.comment) have_obj = 1 if data_struct.information.objective.magnification is not None: ds = objectiveGrp.create_dataset('magnification', data = data_struct.information.objective.magnification) have_obj = 1 if have_obj == 0: del informationGrp['objective'] # /scintillator scintGrp = informationGrp.create_group('scintillator') have_scint = 0 if data_struct.information.scintillator.name is not None: ds = scintGrp.create_dataset('name', data = data_struct.information.scintillator.name) have_scint = 1 if data_struct.information.scintillator.type is not None: ds = scintGrp.create_dataset('type', data = data_struct.information.scintillator.type) have_scint = 1 if data_struct.information.scintillator.comment is not None: ds = scintGrp.create_dataset('comment', data = data_struct.information.scintillator.comment) have_scint = 1 if data_struct.information.scintillator.scintillating_thickness is not None: ds = scintGrp.create_dataset('scintillating_thickness', data = data_struct.information.scintillator.scintillating_thickness) ds.attrs['units'] = data_struct.information.scintillator.scintillating_thickness_units have_scint = 1 if data_struct.information.scintillator.substrate_thickness is not None: ds = scintGrp.create_dataset('substrate_thickness', data = data_struct.information.scintillator.substrate_thickness) ds.attrs['units'] = data_struct.information.scintillator.substrate_thickness_units have_scint = 1 if have_scint == 0: del informationGrp['scintillator'] # /facility facilityGrp = informationGrp.create_group('facility') have_fac = 0 if data_struct.information.facility.name is not None: ds = facilityGrp.create_dataset('name', data = data_struct.information.facility.name) have_fac = 1 if data_struct.information.facility.beamline is not None: ds = facilityGrp.create_dataset('beamline', data = data_struct.information.facility.beamline) have_fac = 1 if have_fac == 0: del informationGrp['facility'] # /accelerator acceleratorGrp = informationGrp.create_group('accelerator') have_ac = 0 if data_struct.information.accelerator.ring_current is not None: ds = acceleratorGrp.create_dataset('ring_current', data = data_struct.information.accelerator.ring_current) ds.attrs['units'] = data_struct.information.accelerator.ring_current_units have_ac = 1 if data_struct.information.accelerator.primary_beam_energy is not None: ds = acceleratorGrp.create_dataset('primary_beam_energy', data = data_struct.information.accelerator.primary_beam_energy) ds.attrs['units'] = data_struct.information.accelerator.primary_beam_energy_units have_ac = 1 if data_struct.information.accelerator.monostripe is not None: ds = acceleratorGrp.create_dataset('monostripe', data = data_struct.information.accelerator.monostripe) have_ac = 1 if have_ac == 0: del informationGrp['accelerator'] # /detector detectorGrp = informationGrp.create_group('detector') have_detc = 0 if data_struct.information.detector.manufacturer is not None: ds = detectorGrp.create_dataset('manufacturer', data = data_struct.information.detector.manufacturer) have_detc = 1 if data_struct.information.detector.model is not None: ds = detectorGrp.create_dataset('model', data = data_struct.information.detector.model) have_detc = 1 if data_struct.information.detector.serial_number is not None: ds = detectorGrp.create_dataset('serial_number', data = data_struct.information.detector.serial_number) have_detc = 1 if data_struct.information.detector.bit_depth is not None: ds = detectorGrp.create_dataset('bit_depth', data = data_struct.information.detector.bit_depth) have_detc = 1 if data_struct.information.detector.operating_temperature is not None: ds = detectorGrp.create_dataset('operating_temperature', data = data_struct.information.detector.operating_temperature) ds.attrs['units'] = data_struct.information.detector.operating_temperature_units have_detc = 1 if data_struct.information.detector.exposure_time is not None: ds = detectorGrp.create_dataset('exposure_time', data = data_struct.information.detector.exposure_time) ds.attrs['units'] = data_struct.information.detector.exposure_time_units have_detc = 1 if data_struct.information.detector.frame_rate is not None: ds = detectorGrp.create_dataset('frame_rate', data = data_struct.information.detector.frame_rate) have_detc = 1 if data_struct.information.detector.pixel_size.horizontal is not None: psGrp = detectorGrp.create_group('pixel_size') ds = psGrp.create_dataset('horizontal', data = data_struct.information.detector.pixel_size.horizontal) ds.attrs['units'] = data_struct.information.detector.pixel_size.horizontal_units ds = psGrp.create_dataset('vertical', data = data_struct.information.detector.pixel_size.vertical) ds.attrs['units'] = data_struct.information.detector.pixel_size.vertical_units have_detc = 1 if data_struct.information.detector.dimensions.horizontal is not None: dimGrp = detectorGrp.create_group('dimensions') ds = dimGrp.create_dataset('horizontal', data = data_struct.information.detector.dimensions.horizontal) ds = dimGrp.create_dataset('vertical', data = data_struct.information.detector.dimensions.vertical) have_detc = 1 if data_struct.information.detector.binning.horizontal is not None: binningGrp = detectorGrp.create_group('binning') ds = binningGrp.create_dataset('horizontal', data = data_struct.information.detector.binning.horizontal) ds = binningGrp.create_dataset('vertical', data = data_struct.information.detector.binning.vertical) have_detc = 1 if data_struct.information.detector.axis_directions.horizontal is not None: axisdirGrp = detectorGrp.create_group('axis_directions') ds = axisdirGrp.create_dataset('horizontal', data = data_struct.information.detector.axis_directions.horizontal) ds = axisdirGrp.create_dataset('vertical', data = data_struct.information.detector.axis_directions.vertical) have_detc = 1 if data_struct.information.detector.roi.x1 is not None: roiGrp = detectorGrp.create_group('roi') ds = roiGrp.create_dataset('x1', data = data_struct.information.detector.roi.x1) ds = roiGrp.create_dataset('y1', data = data_struct.information.detector.roi.y1) ds = roiGrp.create_dataset('x2', data = data_struct.information.detector.roi.x2) ds = roiGrp.create_dataset('y2', data = data_struct.information.detector.roi.y2) have_detc = 1 if have_detc == 0: del informationGrp['detector'] # exchange definition # exchange HDF5 group # /exchange exchangeGrp = f.create_group("exchange") if data_struct.exchange.title is not None: ds = exchangeGrp.create_dataset('title', data = data_struct.exchange.title) if data_struct.exchange.comment is not None: ds = exchangeGrp.create_dataset('comment', data = data_struct.exchange.comment) if data_struct.exchange.data_collection_datetime is not None: ds = exchangeGrp.create_dataset('data_collection_datetime', data = data_struct.exchange.data_collection_datetime) # /exchange/data ds_data = exchangeGrp.create_dataset('data', data = data_struct.exchange.data) if data_struct.exchange.data_signal is not None: ds_data.attrs['signal'] = data_struct.exchange.data_signal if data_struct.exchange.data_description is not None: ds_data.attrs['description'] = data_struct.exchange.data_description if data_struct.exchange.data_units is not None: ds_data.attrs['units'] = data_struct.exchange.data_units if data_struct.exchange.data_detector is not None: ds_data.attrs['detector'] = data_struct.exchange.data_detector if data_struct.exchange.data_axes is not None: ds_data.attrs['axes'] = data_struct.exchange.data_axes if data_struct.exchange.x is not None: ds = exchangeGrp.create_dataset('x', data = data_struct.exchange.x) if data_struct.exchange.x_units is not None: ds.attrs['units'] = data_struct.exchange.x_units if data_struct.exchange.y is not None: ds = exchangeGrp.create_dataset('y', data = data_struct.exchange.y) if data_struct.exchange.y_units is not None: ds.attrs['units'] = data_struct.exchange.y_units if data_struct.exchange.z is not None: ds = exchangeGrp.create_dataset('z', data = data_struct.exchange.z) if data_struct.exchange.z_units is not None: ds.attrs['units'] = data_struct.exchange.z_units if data_struct.exchange.energy is not None: ds = exchangeGrp.create_dataset('energy', data = data_struct.exchange.energy) ds.attrs['units'] = data_struct.exchange.energy_units if data_struct.exchange.theta is not None: ds = exchangeGrp.create_dataset('theta', data = data_struct.exchange.theta) ds.attrs['units'] = data_struct.exchange.theta_units # /exchange/white_data if data_struct.exchange.white_data is not None: ds = exchangeGrp.create_dataset('white_data', data = data_struct.exchange.white_data) ds.attrs['units'] = data_struct.exchange.white_data_units # /exchange/dark_data if data_struct.exchange.dark_data is not None: ds = exchangeGrp.create_dataset('dark_data', data = data_struct.exchange.dark_data) ds.attrs['units'] = data_struct.exchange.dark_data_units if data_struct.exchange.rotation is not None: ds = exchangeGrp.create_dataset('rotation', data = data_struct.exchange.rotation) # Spectromicroscopy HDF5 group spectromicroscopyGrp = f.create_group('spectromicroscopy') if data_struct.spectromicroscopy.positions is not None: ds = spectromicroscopyGrp.create_dataset('positions', data = data_struct.spectromicroscopy.positions) if data_struct.spectromicroscopy.positions_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.positions_units if data_struct.spectromicroscopy.positions_names is not None: ds.attrs['names'] = data_struct.spectromicroscopy.positions_names if data_struct.spectromicroscopy.xshifts is not None: ds = spectromicroscopyGrp.create_dataset('xshifts', data = data_struct.spectromicroscopy.xshifts) if data_struct.spectromicroscopy.yshifts is not None: ds = spectromicroscopyGrp.create_dataset('yshifts', data = data_struct.spectromicroscopy.yshifts) if self.data_struct.spectromicroscopy.optical_density is not None: ds = spectromicroscopyGrp.create_dataset('optical_density', data = self.data_struct.spectromicroscopy.optical_density) # /spectromicroscopy/normalization normalizationGrp = spectromicroscopyGrp.create_group('normalization') if data_struct.spectromicroscopy.normalization.white_spectrum is not None: ds = normalizationGrp.create_dataset('white_spectrum', data = data_struct.spectromicroscopy.normalization.white_spectrum) if data_struct.spectromicroscopy.normalization.white_spectrum_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.normalization.white_spectrum_units ds = normalizationGrp.create_dataset('white_spectrum_energy', data = data_struct.spectromicroscopy.normalization.white_spectrum_energy) if data_struct.spectromicroscopy.normalization.white_spectrum_energy_units is not None: ds.attrs['units'] = data_struct.spectromicroscopy.normalization.white_spectrum_energy_units else: del spectromicroscopyGrp['normalization'] # Close f.close() #----------------------------------------------------------------------- def write_results_h5(self, filename, data_struct, anlz): test_file = 0 #Check if file exists try: # Open HDF5 file f = h5py.File(filename, 'r') test_file = 1 f.close() except: pass #Try to save new hdf5 file if test_file == 0: #try: self.write_h5(filename, data_struct) #except: # print 'Error: Could not open nor create HDF5 file ', filename # return -1 # Open HDF5 file f = h5py.File(filename, 'r+') # Read basic definitions ds = f['implements'] implements = ds[...] if 'Mantis' in f: mantisGrp = f['Mantis'] else: del f['implements'] implements = implements+':Mantis' ds = f.create_dataset('implements', data = implements) mantisGrp = f.create_group('Mantis') if anlz.pca_calculated == 1: if 'pca' in mantisGrp: del mantisGrp['pca'] pcaGrp = mantisGrp.create_group('pca') else: pcaGrp = mantisGrp.create_group('pca') ds_data = pcaGrp.create_dataset('pca_images', data = anlz.pcaimages) ds_data = pcaGrp.create_dataset('pca_eigenvectors', data = anlz.eigenvecs) ds_data = pcaGrp.create_dataset('pca_eigenvalues', data = anlz.eigenvals) if anlz.clusters_calculated == 1: if 'cluster_analysis' in mantisGrp: del mantisGrp['cluster_analysis'] caGrp = mantisGrp.create_group('cluster_analysis') else: caGrp = mantisGrp.create_group('cluster_analysis') ds_data = caGrp.create_dataset('cluster_indices', data = anlz.cluster_indices) ds_data = caGrp.create_dataset('cluster_spectra', data = anlz.clusterspectra) ds_data = caGrp.create_dataset('cluster_distances', data = anlz.cluster_distances) if anlz.tspectrum_loaded == 1: if 'spectral_maps' in mantisGrp: del mantisGrp['spectral_maps'] caGrp = mantisGrp.create_group('spectral_maps') else: caGrp = mantisGrp.create_group('spectral_maps') ds_data = caGrp.create_dataset('raw_maps', data = anlz.target_svd_maps) ds_data = caGrp.create_dataset('fitted_maps', data = anlz.target_pcafit_maps) ds_data = caGrp.create_dataset('raw_spectra', data = anlz.target_spectra) ds_data = caGrp.create_dataset('fitted_spectra', data = anlz.target_pcafit_spectra) f.close() return 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_json.py0000664000175000017500000002251714700316175022557 0ustar00wattswatts# -*- coding: utf-8 -*- # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Benjamin Watts, Paul Scherrer Institut # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import print_function import json import re, numpy, sys from os.path import splitext, isfile from collections import OrderedDict title = 'JSON' extension = ['*.json', '*.hdr'] read_types = ['spectrum','image','stack'] write_types = [] def identify(filename): try: if isfile(splitext(filename)[0] + '.json'): JS = JS_FileLoader(splitext(filename)[0] + '.json') return JS.num_regions>0 # return true if file contains at least one region else: return False except: return False def GetFileStructure(FileName): JS = JS_FileLoader(FileName) if JS.num_regions<2 and JS.num_channels<2 and isfile(splitext(FileName)[0] + '.json'): #if json file exists, skip datachoicedialog print("JSON-file found.") return None # exit if only one choice D = OrderedDict() for i,R in enumerate(['Region_'+str(r) for r in range(JS.num_regions)]): D[R] = OrderedDict() D[R].definition = 'JSON' D[R].scan_type = JS.js['ScanDefinition']['Type'] D[R].data_shape = JS.data_size[i] D[R].data_axes = [JS.js['ScanDefinition']['Regions'][1]['PAxis']['Name'],JS.js['ScanDefinition']['Regions'][1]['QAxis']['Name'],JS.js['ScanDefinition']['StackAxis']['Name']] for ch in range(1,JS.num_channels+1): D[R][JS.js['Channels'][ch]['Name']] = OrderedDict() return D #----------------------------------------------------------------------- class JS_FileLoader: """Parse .hdr file for metadata.""" js = [] f = [] def __init__(self, fileName,identify=False): fileName = (splitext(fileName)[0] + '.json') if not JS_FileLoader.js or JS_FileLoader.f != fileName: # prevent class from fetching and parsing the *.hdr file multiple times. Use the class attribute "hdr" instead if available and check if a new file is loaded. # Parse the JSON file with open(fileName) as f: JS_FileLoader.js = json.load(f) JS_FileLoader.f = fileName else: None self.js = JS_FileLoader.js if 'ScanDefinition' in self.js: self.num_regions = int(self.js['ScanDefinition']['Regions'][0]) self.num_channels = int(self.js['Channels'][0]) self.file_path, self.file_ext = splitext(fileName) self.data_size = self.parse_DataSize() self.data_names = self.parseDataNames() else: self.num_regions = 0 self.num_channels = 0 #----------------------------------------------------------------------- def parseDataNames(self): """Figure out names for the .xsp or .xim files that contain the actual data, then check that the files actually exist, printing warnings if they don't.""" DataNames = [] DataFlag = self.js['ScanDefinition']['Flags'] # Only for spectra: if DataFlag in ['Spectra','Multi-Region Spectra']: for num_R in range(self.num_regions): DataNames2 = [] for num_Ch in range(self.num_channels): DataNames2.append([self.file_path+'_'+str(num_R)+'.xsp']) DataNames.append(DataNames2) return DataNames Alphabet = 'abcdefghijklmnopqrstuvwxyz' boollst = [self.data_size[0][2] > 1,self.num_regions > 1] bitfield = sum(val << bool for bool, val in enumerate(boollst[::-1])) # Different detection channels can occur # Four cases have to be distinguished. if bitfield == 3: # multi region stack DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_E).zfill(3) + str(num_R) + '.xim' for num_E in range(self.data_size[0][2])] for num_Ch in range(self.num_channels)] for num_R in range(self.num_regions)] elif bitfield == 2 and not DataFlag in ['Image']: # single region stack excluding line scans! DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_E).zfill(3) + '.xim' for num_E in range(self.data_size[0][2])] for num_Ch in range(self.num_channels)]] elif bitfield == 1: # multi region image DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_R) + '.xim'] for num_Ch in range(self.num_channels)] for num_R in range(self.num_regions)] else: # single region image DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + '.xim'] for num_Ch in range(self.num_channels)]] return DataNames #----------------------------------------------------------------------- def parse_DataSize(self): """Calculate data array size. This is useful for making sure all of the lists of data are the correct length.""" DataSize = [] for R_num in range(self.num_regions): DataSize.append([1,1,1])# [PAxis,QAxis,StackAxis] (switch to [X1,X2,E] later)] DataSize[R_num][0] = int(self.js['ScanDefinition']['Regions'][R_num+1]['PAxis']['Points'][0]) if 'QAxis' in self.js['ScanDefinition']['Regions'][R_num+1] and 'Points' in self.js['ScanDefinition']['Regions'][R_num+1]['QAxis']: DataSize[R_num][1] = int(self.js['ScanDefinition']['Regions'][R_num+1]['QAxis']['Points'][0]) if 'StackAxis' in self.js['ScanDefinition'] and 'Points' in self.js['ScanDefinition']['StackAxis']: DataSize[R_num][2] = int(self.js['ScanDefinition']['StackAxis']['Points'][0]) if self.js['ScanDefinition']['Type'] in ['NEXAFS Point Scan','NEXAFS Line Scan']: DataSize[R_num] = [DataSize[R_num][1],1,DataSize[R_num][0]]#switch to [X1,X2,E] format # DataSize[R_num] = [1,DataSize[R_num][1],DataSize[R_num][0]]#also works, but might be problematic for finding number of spatial points return DataSize #----------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): JS = JS_FileLoader(filename) allowed = ['Image Stack', 'Image', 'Multi-Region Image Stack'] if JS.js['ScanDefinition']['Flags'] in allowed: self.x_dist = numpy.array([float(i) for i in JS.js['ScanDefinition']['Regions'][selection[0]+1]['PAxis']['Points'][1:] ]) self.y_dist = numpy.array([float(i) for i in JS.js['ScanDefinition']['Regions'][selection[0]+1]['QAxis']['Points'][1:] ]) self.ev = numpy.array([float(i) for i in JS.js['ScanDefinition']['StackAxis']['Points'][1:] ]) self.n_cols = len(self.x_dist) self.n_rows = len(self.y_dist) self.n_ev = len(self.ev) msec = float(JS.js['ScanDefinition']['Dwell']) self.data_dwell = numpy.ones((self.n_ev))*msec imagestack = numpy.empty((self.n_cols,self.n_rows,self.n_ev), numpy.int32) for i in range(len(JS.data_names[selection[0]][selection[1]])): try: imagestack[:,:,i] = numpy.loadtxt(JS.data_names[selection[0]][selection[1]][i], numpy.int32).T except ValueError: print("Aborted stack or XIMs with inconsistent dimensions.") imagestack[:,:,i] = numpy.nan except IOError: print("Image file not found.") imagestack[:,:,i] = numpy.nan self.absdata = numpy.empty((self.n_cols,self.n_rows, self.n_ev)) self.absdata = numpy.reshape(imagestack, (self.n_cols,self.n_rows, self.n_ev), order='F') self.fill_h5_struct_from_stk() else: print("Unknown Format") return #----------------------------------------------------------------------- def read_js_i0(self, filename, *args, **kwargs): JS = JS_FileLoader(filename) if 'ScanType' in JS.js['ScanDefinition'] and JS.js['ScanDefinition']['ScanType'] == 'Spectra': Energies = JS.js['ScanDefinition']['Regions'][1]['PAxis']['Points'][1:] tempimage = numpy.loadtxt(JS.data_names[0][0][0], numpy.float32) Data = tempimage[:,1] elif JS.js['ScanDefinition']['Type'] == 'NEXAFS Line Scan': Energies = JS.js['ScanDefinition']['Regions'][1]['PAxis']['Points'][1:] tempimage = numpy.loadtxt(JS.data_names[0][0][0], numpy.int32) Data = numpy.mean(tempimage,axis=0) else:# Image Stack Energies = JS.js['ScanDefinition']['StackAxis']['Points'][1:] tempimage = numpy.empty((JS.data_size[0][0],JS.data_size[0][1]), numpy.int32) Data = numpy.empty((JS.data_size[0][2]), numpy.int32) for i in range(len(JS.data_names[0][0])): tempimage = numpy.loadtxt(JS.data_names[0][0][i], numpy.int32) Data[i] = numpy.mean(tempimage) msec = float(JS.js['ScanDefinition']['Dwell'])#shouldn't this be needed? self.i0_dwell = msec self.evi0 = numpy.array([float(i) for i in Energies]) self.i0data = Data return././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_ncb.py0000664000175000017500000005147014700316175022350 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division from __future__ import print_function import numpy as np import os import re verbose = 0 title = 'Ncb' extension = ['*.ncb'] read_types = ['image', 'stack'] write_types = ['image', 'stack', 'results'] def identify(filename): try: basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' f = open(str(dat_fn), 'r') f.close() return True except: return False def GetFileStructure(FileName): return None # ---------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' f = open(str(dat_fn), 'r') lines = f.readlines() temp = lines[0].split() self.n_cols = int(temp[0]) self.n_rows = int(temp[1]) scale = float(temp[2]) # print self.n_cols, self.n_rows, scale temp = lines[1].split() x_start = float(temp[0]) x_stop = float(temp[1]) temp = lines[2].split() y_start = float(temp[0]) y_stop = float(temp[1]) self.n_ev = int(lines[3]) # print self.n_ev self.ev = np.zeros((self.n_ev)) self.data_dwell = np.zeros((self.n_ev)) filename_list = [] for i in range(self.n_ev): self.ev[i] = float(lines[4 + i]) # print self.ev for i in range(self.n_ev): self.ev[i] = float(lines[4 + i]) try: for i in range(self.n_ev): temp = lines[4 + self.n_ev + i].split() filename_list.append(temp[0]) self.data_dwell[i] = float(temp[2]) except: self.data_dwell = np.ones((self.n_ev)) f.close() if scale < 0: dataformat = np.float32 else: dataformat = np.int16 # print 'data format', dataformat f = open(str(filename), 'rb') big_array = np.fromfile(f, dataformat, self.n_cols * self.n_rows * self.n_ev) f.close() if scale > 0 and scale != 1: image_stack = big_array.astype(float) / scale print("data rescaled by ", 1. / scale) else: image_stack = big_array.astype(float) if (x_start > x_stop): image_stack = image_stack[::-1, :, :] t = x_start x_start = x_stop x_stop = t print('x data reversed') if (y_start > y_stop): image_stack = image_stack[:, ::-1, :] t = y_start y_start = y_stop y_stop = t print('y data reversed') xstep = (x_stop - x_start) / (self.n_cols - 1) self.x_dist = np.arange(x_start, x_stop + xstep, xstep) ystep = (y_stop - y_start) / (self.n_rows - 1) self.y_dist = np.arange(y_start, y_stop + ystep, ystep) self.absdata = np.empty((self.n_cols, self.n_rows, self.n_ev)) self.absdata = np.reshape(image_stack, (self.n_cols, self.n_rows, self.n_ev), order='F') self.fill_h5_struct_from_stk() return # ---------------------------------------------------------------------- def read_ncb4D(self, filenames): if verbose: print('First energy stack:', filenames[0]) data_files = natural_sort(filenames) neng = len(data_files) if verbose: print('Number of energies ', neng) self.stack4D = [] theta = [] for i in range(neng): # Read the data thisstack, thistheta = read_ncb_data(self, data_files[i]) # Check if we have negative angles, if yes convert to 0-360deg for ith in range(len(thistheta)): if thistheta[ith] < 0: thistheta[ith] = thistheta[ith] + 360 self.stack4D.append(thisstack) theta.append(thistheta) dims = thisstack.shape self.stack4D = np.array(self.stack4D) self.stack4D = np.transpose(self.stack4D, axes=(1, 2, 0, 3)) self.theta = theta[0] self.n_theta = len(theta) self.absdata = self.stack4D[:, :, :, 0] self.data_dwell = np.ones((neng)) self.fill_h5_struct_from_stk() return # ---------------------------------------------------------------------- def read_ncb_data(self, filename): basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' f = open(str(dat_fn), 'r') lines = f.readlines() temp = lines[0].split() self.n_cols = int(temp[0]) self.n_rows = int(temp[1]) scale = float(temp[2]) if verbose: print('self.n_cols, self.n_rows, scale', self.n_cols, self.n_rows, scale) temp = lines[1].split() x_start = float(temp[0]) x_stop = float(temp[1]) temp = lines[2].split() y_start = float(temp[0]) y_stop = float(temp[1]) self.n_theta = int(lines[3]) angles = np.zeros((self.n_theta)) self.data_dwell = np.zeros((self.n_theta)) filename_list = [] for i in range(self.n_theta): angles[i] = float(lines[4 + i]) for i in range(self.n_theta): angles[i] = float(lines[4 + i]) try: for i in range(self.n_theta): temp = lines[4 + self.n_ev + i].split() filename_list.append(temp[0]) self.data_dwell[i] = float(temp[2]) except: self.data_dwell = np.ones((self.n_theta)) f.close() if scale < 0: dataformat = np.float32 else: dataformat = np.int16 f = open(str(filename), 'rb') big_array = np.fromfile(f, dataformat, self.n_cols * self.n_rows * self.n_theta) f.close() if scale > 0 and scale != 1: image_stack = big_array.astype(float) / scale if verbose: print("data rescaled by ", 1. / scale) else: image_stack = big_array.astype(float) if (x_start > x_stop): image_stack = image_stack[::-1, :, :] t = x_start x_start = x_stop x_stop = t if verbose: print('x data reversed') if (y_start > y_stop): image_stack = image_stack[:, ::-1, :] t = y_start y_start = y_stop y_stop = t if verbose: print('y data reversed') xstep = (x_stop - x_start) / (self.n_cols - 1) self.x_dist = np.arange(x_start, x_stop + xstep, xstep) ystep = (y_stop - y_start) / (self.n_rows - 1) self.y_dist = np.arange(y_start, y_stop + ystep, ystep) image_stack = np.reshape(image_stack, (self.n_cols, self.n_rows, self.n_theta), order='F') return image_stack, angles # ---------------------------------------------------------------------- # This procedure writes a whole stack (3d (E,x,y) array) to a binary file # with associated *.dat file to track paramaters def write(filename, stack, data_type): # ,norm): # # def operate_on_Narray(A, B, function): # try: # return [operate_on_Narray(a, b, function) for a, b in zip(A, B)] # except TypeError as e: # # Not iterable # return function(A, B) # import matplotlib.pyplot as plt # def export_figure_matplotlib(name,arr, dpi=100, resize_fact=1, plt_show=False): # fig = plt.figure(frameon=False) # fig.set_size_inches(arr.shape[1] / dpi, arr.shape[0] / dpi) # ax = plt.Axes(fig, [0., 0., 1., 1.]) # ax.set_axis_off() # fig.add_axes(ax) # ax.imshow(arr, cmap='gray', interpolation='none') # if plt_show: # plt.show() # else: # plt.savefig(name+'.tif', dpi=(dpi * resize_fact)) # plt.close() print('Writing .ncb stack:', filename) basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' filename = basename + '.ncb' image_stack = np.transpose(stack.absdata, axes=(1, 0, 2)) '''Experimental OD filtering and edge substraction''' # if norm: # # image_stack = np.transpose(stack.od3d, axes=(1,0,2)) # #image_stack = stack.od3d.copy() # OD_filter = np.amax(image_stack, 2) # #returning the ODmask as tif # mask = np.where((OD_filter > 2.5), 0, np.ones(np.shape(image_stack[:,:,0]))) # export_figure_matplotlib(basename+"od_mask",mask) # # for i in range(np.size(image_stack, 2)): # image_stack[:,:,i] = np.where((OD_filter > 2.5),0, image_stack[:,:,i]) # #image_stack = np.where(image_stack > 2,0, image_stack) #OD > 2 filter # e0 = 0 # num = 5 # ##pre-edge # ave = image_stack[:,:,e0] # for i in range(num-1): # ave = ave + image_stack[:,:,e0+i+1] # ave = ave / num # # #export pre-edge_average for contour plot. # pre = np.transpose(stack.absdata, axes=(1,0,2))[:,:,e0] # for i in range(num-1): # pre = pre + np.transpose(stack.absdata, axes=(1,0,2))[:,:,e0+i+1] # pre = pre / num # export_figure_matplotlib(basename,pre) # # for i in range(np.size(image_stack, 2)): # image_stack[:,:,i] = image_stack[:,:,i]-ave # # #post-edge # ave = image_stack[:,:,-1] # for i in range(num-1): # ave = ave + image_stack[:,:,e0-i-2] # ave = (ave / num) + 0.0001 # #print(ave) # for i in range(np.size(image_stack, 2)): # image_stack[:,:,i] = image_stack[:,:,i] - operate_on_Narray(ave, image_stack[:,:,i], lambda a, b: a/((1+ np.exp(-b+0.5*a)**(25*1/a)))) image_stack = np.reshape(image_stack, (stack.n_cols * stack.n_rows * stack.n_ev), order='F') tmax = np.amax(image_stack) tmin = np.amin(image_stack) test = np.amax([abs(tmin), abs(tmax)]) scale = 1. if test > 3e4 or test < 1e3: scale = 10. ** (3 - np.fix(np.log10(test))) # Save image stack to a binary .dat file f = open(str(filename), 'wb') if scale != 1.0: print('Scaling the data by ', scale) saveddata = image_stack * scale saveddata.astype(np.int16).tofile(f) else: scale = -1.0 image_stack.astype(np.float32).tofile(f) f.close() # print 'imagedims', image_stack.shape f = open(str(dat_fn), 'w') print('\t%d\t%d\t%.6f' % (stack.n_rows, stack.n_cols, scale), file=f) x_start = stack.y_dist[0] x_stop = stack.y_dist[-1] if x_start != 0.: x_stop = x_stop - x_start x_start = 0. print('\t%.6f\t%.6f' % (x_start, x_stop), file=f) y_start = stack.x_dist[0] y_stop = stack.x_dist[-1] if y_start != 0.: y_stop = y_stop - y_start y_start = 0. print('\t%.6f\t%.6f' % (y_start, y_stop), file=f) print('\t%d' % (stack.n_ev), file=f) for i in range(stack.n_ev): print('\t%.6f' % (stack.ev[i]), file=f) for i in range(stack.n_ev): thisstr = 'image' + str(i + 1) print('%s\t%.6f\t%.6f' % (thisstr, stack.ev[i], stack.data_dwell[i]), file=f) f.close() return # ---------------------------------------------------------------------- def natural_sort(l): convert = lambda text: int(text) if text.isdigit() else text.lower() alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] return sorted(l, key=alphanum_key) # ---------------------------------------------------------------------- class Cncb: def __init__(self): pass # ---------------------------------------------------------------------- def read_ncb(self, filename): print('Reading stack:', filename) basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' f = open(str(dat_fn), 'r') lines = f.readlines() temp = lines[0].split() self.n_cols = int(temp[0]) self.n_rows = int(temp[1]) scale = float(temp[2]) # print self.n_cols, self.n_rows, scale temp = lines[1].split() x_start = float(temp[0]) x_stop = float(temp[1]) temp = lines[2].split() y_start = float(temp[0]) y_stop = float(temp[1]) self.n_ev = int(lines[3]) # print self.n_ev self.ev = np.zeros((self.n_ev)) self.data_dwell = np.zeros((self.n_ev)) filename_list = [] for i in range(self.n_ev): self.ev[i] = float(lines[4 + i]) # print self.ev for i in range(self.n_ev): self.ev[i] = float(lines[4 + i]) try: for i in range(self.n_ev): temp = lines[4 + self.n_ev + i].split() filename_list.append(temp[0]) self.data_dwell[i] = float(temp[2]) except: self.data_dwell = np.ones((self.n_ev)) f.close() if scale < 0: dataformat = np.float32 else: dataformat = np.int16 # print 'data format', dataformat f = open(str(filename), 'rb') big_array = np.fromfile(f, dataformat, self.n_cols * self.n_rows * self.n_ev) f.close() if scale > 0 and scale != 1: image_stack = big_array.astype(float) / scale print("data rescaled by ", 1. / scale) else: image_stack = big_array.astype(float) if (x_start > x_stop): image_stack = image_stack[::-1, :, :] t = x_start x_start = x_stop x_stop = t print('x data reversed') if (y_start > y_stop): image_stack = image_stack[:, ::-1, :] t = y_start y_start = y_stop y_stop = t print('y data reversed') xstep = (x_stop - x_start) / (self.n_cols - 1) self.x_dist = np.arange(x_start, x_stop + xstep, xstep) ystep = (y_stop - y_start) / (self.n_rows - 1) self.y_dist = np.arange(y_start, y_stop + ystep, ystep) self.absdata = np.empty((self.n_cols, self.n_rows, self.n_ev)) self.absdata = np.reshape(image_stack, (self.n_cols, self.n_rows, self.n_ev), order='F') return # ---------------------------------------------------------------------- # This procedure writes a whole stack (3d (E,x,y) array) to a binary file # with associated *.dat file to track paramaters def write_ncb(self, filename, data_struct): print('Writing .ncb stack:', filename) basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' image_stack = np.transpose(self.absdata, axes=(1, 0, 2)) # image_stack = self.absdata.copy() image_stack = np.reshape(image_stack, (self.n_cols * self.n_rows * self.n_ev), order='F') tmax = np.amax(image_stack) tmin = np.amin(image_stack) test = np.amax([abs(tmin), abs(tmax)]) scale = 1. if test > 3e4 or test < 1e3: scale = 10. ** (3 - np.fix(np.log10(test))) # Save image stack to a binary .dat file f = open(str(filename), 'wb') if scale != 1.0: print('Scaling the data by ', scale) saveddata = image_stack * scale saveddata.astype(np.int16).tofile(f) else: scale = -1.0 image_stack.astype(np.float32).tofile(f) f.close() # print 'imagedims', image_stack.shape f = open(str(dat_fn), 'w') print('\t%d\t%d\t%.6f' % (self.n_rows, self.n_cols, scale), file=f) # print('\t%d\t%d\t%.6f' %(self.n_cols, self.n_rows, scale) x_start = self.y_dist[0] x_stop = self.y_dist[-1] if x_start != 0.: x_stop = x_stop - x_start x_start = 0. print('\t%.6f\t%.6f' % (x_start, x_stop), file=f) y_start = self.x_dist[0] y_stop = self.x_dist[-1] if y_start != 0.: y_stop = y_stop - y_start y_start = 0. print('\t%.6f\t%.6f' % (y_start, y_stop), file=f) print('\t%d' % (self.n_ev), file=f) for i in range(self.n_ev): print('\t%.6f' % (self.ev[i]), file=f) for i in range(self.n_ev): thisstr = 'image' + str(i + 1) print('%s\t%.6f\t%.6f' % (thisstr, self.ev[i], self.data_dwell[i]), file=f) f.close() return # ---------------------------------------------------------------------- def read_ncb_data(self, filename): # print 'Reading stack:', filename basename, extension = os.path.splitext(filename) dat_fn = basename + '.dat' f = open(str(dat_fn), 'r') lines = f.readlines() temp = lines[0].split() self.n_cols = int(temp[0]) self.n_rows = int(temp[1]) scale = float(temp[2]) if verbose: print('self.n_cols, self.n_rows, scale', self.n_cols, self.n_rows, scale) temp = lines[1].split() x_start = float(temp[0]) x_stop = float(temp[1]) temp = lines[2].split() y_start = float(temp[0]) y_stop = float(temp[1]) self.n_theta = int(lines[3]) # print self.n_ev angles = np.zeros((self.n_theta)) self.data_dwell = np.zeros((self.n_theta)) filename_list = [] for i in range(self.n_theta): angles[i] = float(lines[4 + i]) # print self.ev for i in range(self.n_theta): angles[i] = float(lines[4 + i]) try: for i in range(self.n_theta): temp = lines[4 + self.n_ev + i].split() filename_list.append(temp[0]) self.data_dwell[i] = float(temp[2]) except: self.data_dwell = np.ones((self.n_theta)) f.close() if scale < 0: dataformat = np.float32 else: dataformat = np.int16 # print 'data format', dataformat f = open(str(filename), 'rb') big_array = np.fromfile(f, dataformat, self.n_cols * self.n_rows * self.n_theta) f.close() if scale > 0 and scale != 1: image_stack = big_array.astype(float) / scale if verbose: print("data rescaled by ", 1. / scale) else: image_stack = big_array.astype(float) if (x_start > x_stop): image_stack = image_stack[::-1, :, :] t = x_start x_start = x_stop x_stop = t if verbose: print('x data reversed') if (y_start > y_stop): image_stack = image_stack[:, ::-1, :] t = y_start y_start = y_stop y_stop = t if verbose: print('y data reversed') xstep = (x_stop - x_start) / (self.n_cols - 1) self.x_dist = np.arange(x_start, x_stop + xstep, xstep) ystep = (y_stop - y_start) / (self.n_rows - 1) self.y_dist = np.arange(y_start, y_stop + ystep, ystep) image_stack = np.reshape(image_stack, (self.n_cols, self.n_rows, self.n_theta), order='F') return image_stack, angles # ---------------------------------------------------------------------- def read_ncb4D(self, filenames): if verbose: print('First energy stack:', filenames[0]) data_files = natural_sort(filenames) neng = len(data_files) if verbose: print('Number of energies ', neng) self.stack4D = [] theta = [] for i in range(neng): # Read the data thisstack, thistheta = self.read_ncb_data(data_files[i]) # Check if we have negative angles, if yes convert to 0-360deg for ith in range(len(thistheta)): if thistheta[ith] < 0: thistheta[ith] = thistheta[ith] + 360 self.stack4D.append(thisstack) theta.append(thistheta) dims = thisstack.shape self.stack4D = np.array(self.stack4D) self.stack4D = np.transpose(self.stack4D, axes=(1, 2, 0, 3)) self.theta = theta[0] self.n_theta = len(theta) self.absdata = self.stack4D[:, :, :, 0] self.data_dwell = np.ones((neng)) return ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_nexus_hdf5.py0000775000175000017500000001661414700316175023662 0ustar00wattswatts# -*- coding: utf-8 -*- # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Benjamin Watts, Paul Scherrer Institute # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import print_function import sys, os, numpy, h5py from collections import OrderedDict #from PyQt5 import QtGui title = 'NXstxm' extension = ['*.hdf','*.hdf5','*.nxs'] read_types = ['spectrum','image','stack','sample line spectrum'] write_types = []#'spectrum','image','stack'] def perhaps_decode(value): try: return value.decode("utf-8") # h5py <3.0 except AttributeError: return value # h5py >=3.0 def identify(filename): try: # Open HDF5 file f = h5py.File(filename, 'r') # Count valid entries n_regions = 0 for entry in f: if 'NX_class' in list(f[entry].attrs) and f[entry].attrs['NX_class'] in [b'NXentry', u'NXentry']: if 'definition' in list(f[entry]) and f[entry]['definition'][0] in [b'NXstxm', u'NXstxm']: n_regions += 1 f.close() return n_regions>0 # return true if file contains at least one NXstxm entry except: return False def read(FileName,stack_object,selection=(0,0), json=None, inorm=None, *args, **kwargs): #print(inorm) D = GetFileStructure(FileName) entry = list(D.keys())[selection[0]] detector = list(D[entry].keys())[selection[1]] #[counter0, ...] F = h5py.File(FileName, 'r') if 'energy' in list(F[entry][detector]): stack_object.ev = numpy.array(F[entry][detector]['energy']) elif 'photon_energy' in list(F[entry][detector]): stack_object.ev = numpy.array(F[entry][detector]['photon_energy']) else: print("Can't find photon energy!") stack_object.x_dist = numpy.array(F[entry][detector]['sample_x']) stack_object.y_dist = numpy.array(F[entry][detector]['sample_y']) # if line scan, 1 dimenson has only one pixel/position! if numpy.all(stack_object.x_dist == stack_object.x_dist[0]): stack_object.x_dist = numpy.array([stack_object.x_dist[0]]) if numpy.all(stack_object.y_dist == stack_object.y_dist[0]): stack_object.y_dist = numpy.array([stack_object.y_dist[0]]) stack_object.data_dwell = numpy.array(F[entry][detector]['count_time']) stack_object.n_cols = len(stack_object.x_dist) stack_object.n_rows = len(stack_object.y_dist) stack_object.n_ev = len(stack_object.ev) if 'axes' in list(F[entry][detector].attrs): # Specification correct axes_list = [item.decode('UTF-8') for item in F[entry][detector].attrs['axes']] if 'line_position' in axes_list: axes_order = [axes_list.index('line_position'),axes_list.index('line_position'),axes_list.index('energy')] #linescan elif 'energy' in axes_list: axes_order = [axes_list.index('sample_x'),axes_list.index('sample_y'),axes_list.index('energy')] #stack else: axes_order = [axes_list.index('sample_x'),axes_list.index('sample_y')] #image else: # Old version from before the specification was finalised if 'energy' in list(F[entry][detector]): try: energy_axis = F[entry][detector]['energy'].attrs['axis'] except: print("Only stacks are supported!") elif 'photon_energy' in list(F[entry][detector]): energy_axis = F[entry][detector]['photon_energy'].attrs['axis'] else: print("Can't find photon energy!") axes_order = [F[entry][detector]['sample_x'].attrs['axis']-1,F[entry][detector]['sample_y'].attrs['axis']-1,energy_axis-1] signal_name = 'data' if 'signal' in list(F[entry][detector].attrs): signal_name = perhaps_decode(F[entry][detector].attrs['signal']) if axes_order[0] == axes_order[1]: #i.e. if linescan temp = numpy.transpose(numpy.array(F[entry][detector][signal_name]),axes=axes_order[1:]) temp = numpy.expand_dims(temp, axis=0) if stack_object.n_rows == 1: # if horizontal line scan stack_object.absdata = numpy.transpose(temp,axes=[1,0,2]) else: stack_object.absdata = numpy.transpose(numpy.array(F[entry][detector][signal_name]),axes=axes_order) if len(axes_order) < 3: # for single images add one more dimension stack_object.absdata = numpy.expand_dims(stack_object.absdata, axis=2) if inorm and ('ringcurrent' in list(F[entry])): ringcurrent = numpy.transpose(numpy.array(F[entry]['ringcurrent']['data']),axes=axes_order) if len(axes_order) < 3: # for single images add one more dimension ringcurrent = numpy.expand_dims(ringcurrent, axis=2) ringcurrent_median = numpy.nanmedian(ringcurrent, keepdims=False) stack_object.absdata = stack_object.absdata / (ringcurrent/ringcurrent_median) F.close() stack_object.fill_h5_struct_from_stk() def GetFileStructure(FileName): """ToDo: Currently, the file will be opened two times. Maybe a solution like in the sdf-plugin would be better.""" F = h5py.File(FileName, 'r') D = OrderedDict() for entry in F: if 'NX_class' in list(F[entry].attrs) and F[entry].attrs['NX_class'] in [b'NXentry', u'NXentry']: D[entry] = OrderedDict() D[entry].norm_data = OrderedDict() D[entry].definition = None D[entry].scan_type = None D[entry].data_shape = None D[entry].data_axes = None for data in F[entry]: if 'NX_class' in list(F[entry][data].attrs) and F[entry][data].attrs['NX_class'] in [b'NXdata', u'NXdata']: D[entry][data] = OrderedDict() #print "should collect more info in each NXdata group" elif 'NX_class' in list(F[entry][data].attrs) and F[entry][data].attrs['NX_class'] in [b'NXmonitor', u'NXmonitor']: D[entry].norm_data[data] = OrderedDict() if len(D[entry].norm_data) == 0: D[entry].norm_data = None if 'definition' in list(F[entry]): D[entry].definition = F[entry]['definition'][0].decode("utf-8") if len(D[entry].keys()) > 0: channel_zero = list(D[entry].keys())[0] if 'stxm_scan_type' in list(F[entry][channel_zero]): D[entry].scan_type = F[entry][channel_zero]['stxm_scan_type'][0].decode("utf-8") signal_name = 'data' if 'signal' in list(F[entry][channel_zero].attrs): signal_name = perhaps_decode(F[entry][channel_zero].attrs['signal']) if signal_name in list(F[entry][channel_zero]): D[entry].data_shape = F[entry][channel_zero][signal_name].shape if 'axes' in list(F[entry][channel_zero].attrs): D[entry].data_axes = [perhaps_decode(item) for item in F[entry][channel_zero].attrs['axes']] F.close() if len(D) == 0: return None else: return D ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_sdf.py0000664000175000017500000003436314700316175022364 0ustar00wattswatts# -*- coding: utf-8 -*- # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Benjamin Watts, Paul Scherrer Institut # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import print_function import json import re, numpy, sys from os.path import splitext, join, dirname, isfile from collections import OrderedDict title = 'SDF' extension = ['*.hdr'] read_types = ['spectrum','image','stack'] write_types = [] def identify(filename): try: if isfile(splitext(filename)[0] + '.json'): print("JSON-file found. No need to fetch again.") return False else: HDR = HDR_FileParser(filename) return HDR.num_regions > 0 # return true if file contains at least one region except: return False def GetFileStructure(FileName): HDR = HDR_FileParser(FileName) if HDR.num_regions<2 and HDR.num_channels<2 and isfile(splitext(FileName)[0] + '.json'): #if json file exists, skip datachoicedialog return None # exit if only one choice D = OrderedDict() for i,R in enumerate(['Region_'+str(r) for r in range(HDR.num_regions)]): D[R] = OrderedDict() D[R].definition = 'SDF' D[R].scan_type = HDR.hdr['ScanDefinition']['Type'] D[R].data_shape = HDR.data_size[i] D[R].data_axes = [HDR.hdr['ScanDefinition']['Regions'][1]['PAxis']['Name'],HDR.hdr['ScanDefinition']['Regions'][1]['QAxis']['Name'],HDR.hdr['ScanDefinition']['StackAxis']['Name']] for ch in range(1,HDR.num_channels+1): D[R][HDR.hdr['Channels'][ch]['Name']] = OrderedDict() return D #----------------------------------------------------------------------- class HDR_FileParser: """Parse .hdr file for metadata.""" hdr = [] f = [] def __init__(self, fileName,identify=False): if not HDR_FileParser.hdr or HDR_FileParser.f != fileName: # prevent class from fetching and parsing the *.hdr file multiple times. Use the class attribute "hdr" instead if available and check if a new file (not "f") is loaded. # compile some regular expressions self.MatchReStruct = re.compile(r'[\s\{\}\(\)=";]') self.MatchReArray = re.compile(r'[,\s\{\(\);]') self.__file = open(fileName) # Parse the HDR file HDR_FileParser.hdr = self.parseStructure() HDR_FileParser.f = fileName self.__file.close() else: None self.hdr = HDR_FileParser.hdr if 'ScanDefinition' in self.hdr: self.num_regions = int(self.hdr['ScanDefinition']['Regions'][0]) self.num_channels = int(self.hdr['Channels'][0]) self.file_path, self.file_ext = splitext(fileName) self.data_size = self.parse_DataSize() self.data_names = self.parseDataNames() else: self.num_regions = 0 self.num_channels = 0 #----------------------------------------------------------------------- def parseStructure(self): """.hdr files consist of structures and arrays. This routine sorts through the structure parts.""" Structure = {} BuildWord='' BeforeEq=True QuotedWord=False raw = self.__file.read(1) while len(raw) > 0:#until we reach the end of the file matched = self.MatchReStruct.match(raw) if matched == None: BuildWord+=raw elif matched.group() == '"': QuotedWord= not QuotedWord elif QuotedWord==True: BuildWord+=raw elif matched.group() == '=': FieldName=BuildWord BuildWord='' BeforeEq=False elif matched.group() == ';': try: # convert numbers into ints or floats or end up with a string Structure[FieldName] = int(BuildWord) except ValueError: try: Structure[FieldName] = float(BuildWord) except ValueError: Structure[FieldName] = BuildWord except TypeError: Structure[FieldName] = BuildWord BuildWord='' BeforeEq=True elif matched.group() == '{': #Must be after = BuildWord = self.parseStructure() elif matched.group() == '}': #break loop and return dictionary break elif matched.group() == '(': #Must be after = BuildWord= self.parseArray() elif matched.group() == ')': #This should not happen print(') in structure') raw = self.__file.read(1) return Structure #----------------------------------------------------------------------- def parseArray(self): """.hdr files consist of structures and arrays. This rountine sorts through the array parts.""" Array = [] BuildWord='' raw = self.__file.read(1) while len(raw) > 0:#until we reach the end of the file matched = self.MatchReArray.match(raw) if matched == None: BuildWord+=raw elif matched.group() == ',': if len(BuildWord) > 0: try: #convert numbers in array into ints or floats Array.append(int(BuildWord)) except ValueError: Array.append(float(BuildWord)) BuildWord='' elif matched.group() == ';': print('; in array') elif matched.group() == '{': Array.append(self.parseStructure()) elif matched.group() == '(': Array.append(self.parseArray()) elif matched.group() == ')': if len(BuildWord) > 0: try: #convert numbers in array into ints or floats Array.append(int(BuildWord)) except ValueError: try: Array.append(float(BuildWord)) except ValueError: # There is an error in the HDF5toSDF conversion script at the SLS/PolLux which appends arrays with a superfluous "}". Obviously, this char cannot be converted to int or float. We therefore just skip it here. pass break raw = self.__file.read(1) return Array #----------------------------------------------------------------------- def parseDataNames(self): """Figure out names for the .xsp or .xim files that contain the actual data, then check that the files actually exist, printing warnings if they don't.""" DataNames = [] DataFlag = self.hdr['ScanDefinition']['Flags'] # Only for spectra: if DataFlag in ['Spectra','Multi-Region Spectra']: for num_R in range(self.num_regions): DataNames2 = [] for num_Ch in range(self.num_channels): DataNames2.append([self.file_path+'_'+str(num_R)+'.xsp']) DataNames.append(DataNames2) return DataNames Alphabet = 'abcdefghijklmnopqrstuvwxyz' boollst = [self.data_size[0][2] > 1,self.num_regions > 1] bitfield = sum(val << bool for bool, val in enumerate(boollst[::-1])) # Different detection channels can occur # Four cases have to be distinguished. if bitfield == 3: # multi region stack DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_E).zfill(3) + str(num_R) + '.xim' for num_E in range(self.data_size[0][2])] for num_Ch in range(self.num_channels)] for num_R in range(self.num_regions)] elif bitfield == 2 and not DataFlag in ['Image']: # single region stack excluding line scans! DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_E).zfill(3) + '.xim' for num_E in range(self.data_size[0][2])] for num_Ch in range(self.num_channels)]] elif bitfield == 1: # multi region image DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + str(num_R) + '.xim'] for num_Ch in range(self.num_channels)] for num_R in range(self.num_regions)] else: # single region image DataNames = [[[self.file_path + '_' + Alphabet[num_Ch] + '.xim'] for num_Ch in range(self.num_channels)]] #ToDo: File exist check return DataNames #----------------------------------------------------------------------- def parse_DataSize(self): """Calculate data array size. This is useful for making sure all of the lists of data are the correct length.""" DataSize = [] for R_num in range(self.num_regions): DataSize.append([1,1,1])# [PAxis,QAxis,StackAxis] (switch to [X1,X2,E] later)] DataSize[R_num][0] = int(self.hdr['ScanDefinition']['Regions'][R_num+1]['PAxis']['Points'][0]) if 'QAxis' in self.hdr['ScanDefinition']['Regions'][R_num+1] and 'Points' in self.hdr['ScanDefinition']['Regions'][R_num+1]['QAxis']: DataSize[R_num][1] = int(self.hdr['ScanDefinition']['Regions'][R_num+1]['QAxis']['Points'][0]) if 'StackAxis' in self.hdr['ScanDefinition'] and 'Points' in self.hdr['ScanDefinition']['StackAxis']: DataSize[R_num][2] = int(self.hdr['ScanDefinition']['StackAxis']['Points'][0]) if self.hdr['ScanDefinition']['Type'] in ['NEXAFS Point Scan','NEXAFS Line Scan']: DataSize[R_num] = [DataSize[R_num][1],1,DataSize[R_num][0]]#switch to [X1,X2,E] format # DataSize[R_num] = [1,DataSize[R_num][1],DataSize[R_num][0]]#also works, but might be problematic for finding number of spatial points return DataSize #----------------------------------------------------------------------- def read(filename, self, selection=None, JSONstatus=None, *args, **kwargs): HDR = HDR_FileParser(filename) if JSONstatus: with open(splitext(filename)[0] + '.json', 'w') as outfile: json.dump(HDR.hdr, outfile, indent=4, sort_keys=True, ensure_ascii=True) print("JSON-file written at "+ splitext(filename)[0] + '.json') allowed_flag =['Image Stack','Image','Multi-Region Image Stack','Multi-Region Image'] allowed_type =['NEXAFS Image Scan','NEXAFS Line Scan','Image Scan', 'Line Scan'] flag = HDR.hdr['ScanDefinition']['Flags'] type = HDR.hdr['ScanDefinition']['Type'] region, channel = selection if not (flag in allowed_flag and type in allowed_type): print("Unknown Format") return linescan = False if type in ['NEXAFS Line Scan', 'Line Scan']: linescan = True p_axis = HDR.hdr['ScanDefinition']['Regions'][region+1]['PAxis'] q_axis = HDR.hdr['ScanDefinition']['Regions'][region+1]['QAxis'] stack_axis = HDR.hdr['ScanDefinition']['StackAxis'] if linescan: # if line scan if p_axis['Name'] == "Energy": # vertical self.ev = numpy.array([float(i) for i in p_axis['Points'][1:] ]) self.y_dist = numpy.array([float(i) for i in q_axis['Points'][1:] ]) self.x_dist = numpy.array([0]) # set x-pos to 0 if q_axis['Name'] == "Energy": # horizontal self.ev = numpy.array([float(i) for i in q_axis['Points'][1:]]) self.x_dist = numpy.array([float(i) for i in p_axis['Points'][1:]]) self.y_dist = numpy.array([0]) # set y-pos to 0 else: # if image stacks or single images assert p_axis['Name'] == "Sample X" self.x_dist = numpy.array([float(i) for i in p_axis['Points'][1:] ]) assert q_axis['Name'] == "Sample Y" self.y_dist = numpy.array([float(i) for i in q_axis['Points'][1:] ]) assert stack_axis['Name'] == "Energy" self.ev = numpy.array([float(i) for i in stack_axis['Points'][1:] ]) #print(self.x_dist,self.y_dist,self.ev) self.n_cols = len(self.x_dist) self.n_rows = len(self.y_dist) self.n_ev = len(self.ev) #print(self.n_cols,self.n_rows,self.n_ev) msec = float(HDR.hdr['ScanDefinition']['Dwell']) self.data_dwell = numpy.ones((self.n_ev))*msec imagestack = numpy.empty((self.n_cols,self.n_rows,self.n_ev), numpy.int32) if linescan: # if linescan load only first existing image and iterate over each row. line_img = (numpy.loadtxt(HDR.data_names[region][channel][0], numpy.int32).T) if q_axis['Name'] == "Energy": # if horizontal, transpose matrix line_img = line_img.T for i,row in enumerate(line_img): imagestack[:, :, i] = row else: # no linescan for i in range(len(HDR.data_names[region][channel])): try: imagestack[:,:,i] = numpy.loadtxt(HDR.data_names[region][channel][i], numpy.int32).T except ValueError: print("Aborted stack or XIMs with inconsistent dimensions.") #imagestack[:,:,i] = numpy.nan except IOError: print("Image file no. "+str(i)+" not found.") #imagestack[:,:,i] = numpy.nan self.absdata = numpy.empty((self.n_cols,self.n_rows, self.n_ev)) self.absdata = numpy.reshape(imagestack, (self.n_cols,self.n_rows, self.n_ev), order='F') self.fill_h5_struct_from_stk() return #----------------------------------------------------------------------- def read_sdf_i0(self, filename): HDR = HDR_FileParser(filename) if 'ScanType' in HDR.hdr['ScanDefinition'] and HDR.hdr['ScanDefinition']['ScanType'] == 'Spectra': Energies = HDR.hdr['ScanDefinition']['Regions'][1]['PAxis']['Points'][1:] tempimage = numpy.loadtxt(HDR.data_names[0][0][0], numpy.float32) Data = tempimage[:,1] elif HDR.hdr['ScanDefinition']['Type'] == 'NEXAFS Line Scan': Energies = HDR.hdr['ScanDefinition']['Regions'][1]['PAxis']['Points'][1:] tempimage = numpy.loadtxt(HDR.data_names[0][0][0], numpy.int32) Data = numpy.mean(tempimage,axis=0) else:# Image Stack Energies = HDR.hdr['ScanDefinition']['StackAxis']['Points'][1:] tempimage = numpy.empty((HDR.data_size[0][0],HDR.data_size[0][1]), numpy.int32) Data = numpy.empty((HDR.data_size[0][2]), numpy.int32) for i in range(len(HDR.data_names[0][0])): tempimage = numpy.loadtxt(HDR.data_names[0][0][i], numpy.int32) Data[i] = numpy.mean(tempimage) msec = float(HDR.hdr['ScanDefinition']['Dwell'])#shouldn't this be needed? self.i0_dwell = msec self.evi0 = numpy.array([float(i) for i in Energies]) self.i0data = Data return ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_sm_netcdf.py0000664000175000017500000002243714700316175023551 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division import os.path import numpy as np import scipy as scy import netCDF4 from mantis_xray import data_struct #----------------------------------------------------------------------- def read_sm_header(filename): try: print (filename) f = netCDF4.Dataset(filename, 'r', format='NETCDF4') ncols = len(f.dimensions['n_fast_pixels']) nrows = len(f.dimensions['n_slow_pixels']) ev = getattr(f,'start_ev') x_center_um = getattr(f,'x_center_um') y_center_um = getattr(f,'y_center_um') f.close() return 1, ncols, nrows, ev except: return -1, 0,0,0 #----------------------------------------------------------------------- def read_sm_list(self, filelist, filepath, ds): #Fill the common stack data file1 = os.path.join(filepath, filelist[0]) fcdf = netCDF4.Dataset(file1, 'r', format='NETCDF4') ncols = len(fcdf.dimensions['n_fast_pixels']) nrows = len(fcdf.dimensions['n_slow_pixels']) npix = ncols*nrows nev = len(filelist) absdata = np.zeros((ncols, nrows, nev)) ev = np.zeros((nev)) #fdevicen = netCDF4.chartostring(getattr(fcdf,'fast_device_name')) #sdevicen = netCDF4.chartostring(getattr(fcdf,'slow_device_name')) fdevicenl = getattr(fcdf,'fast_device_name') sdevicenl = getattr(fcdf,'slow_device_name') fdevicen = chr(fdevicenl[0]) + chr(fdevicenl[1]) + chr(fdevicenl[2]) + chr(fdevicenl[3]) sdevicen = chr(sdevicenl[0]) + chr(sdevicenl[1]) + chr(sdevicenl[2]) + chr(sdevicenl[3]) x_center_um = -1 y_center_um = -1 if fdevicen == 'XPZT': x_center_um = getattr(fcdf,'x_center_um') if sdevicen == 'YPZT': y_center_um = getattr(fcdf,'y_center_um') if (x_center_um == -1) or (y_center_um == -1): print ('Error in stack build: Not piezo scan') return x_pixel_um = getattr(fcdf,'fast_pixel_um') y_pixel_um = getattr(fcdf,'slow_pixel_um') x_direction = getattr(fcdf,'fast_direction') y_direction = getattr(fcdf,'slow_direction') dwell_msec = getattr(fcdf,'dwell_msec') x_dist = (np.arange(npix)-np.fix(npix/2.0)) * x_direction * x_pixel_um + x_center_um y_dist = (np.arange(npix)-np.fix(npix/2.0)) * y_direction * y_pixel_um + y_center_um ds.implements = 'information:exchange:spectromicroscopy' ds.version = '1.0' ds.information.file_creation_datetime = ' '#netCDF4.chartostring(getattr(fcdf,'systime')) ds.information.comment = ' '#netCDF4.chartostring(getattr(fcdf,'comments1')) ds.information.experimenter.name = ' '#netCDF4.chartostring(getattr(fcdf,'scientist')) ds.information.sample.name = ' '#netCDF4.chartostring(getattr(fcdf,'sample')) fcdf.close() #Read the image data for i in range(len(filelist)): fn = filelist[i] filename = os.path.join(filepath, fn) fcdf = netCDF4.Dataset(filename, 'r', format='NETCDF4') image = fcdf.variables['image_data'][:] absdata[:,:,i] = image[:,:,0] eng = getattr(fcdf,'start_ev') ev[i] = eng fcdf.close() if ev[-1]. from __future__ import division import numpy as np import scipy.interpolate import os title = 'STK' extension = ['*.stk'] read_types = ['spectrum','image','stack'] write_types = [] def identify(filename): try: #Binary file - the only way to check if the file is ok to check the extension bn, extension = os.path.splitext(filename) if extension.lower() == '.stk': return True else: return False except: return False def read_old( filepath, data_stk): data_stk.read_h5(filepath) return def GetFileStructure(FileName): return None #----------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): f = open(str(filename),'rb') data = np.fromfile(f, np.int32, 3) data.byteswap(True) self.n_cols = data[0] self.n_rows = data[1] self.n_ev = data[2] #print 'self.n_cols, self.n_rows, self.n_ev', self.n_cols, self.n_rows, self.n_ev self.x_dist = np.fromfile(f, np.float32, self.n_cols) self.x_dist.byteswap(True) self.y_dist = np.fromfile(f, np.float32, self.n_rows) self.y_dist.byteswap(True) self.ev = np.fromfile(f, np.float32, self.n_ev) self.ev.byteswap(True) msec = np.fromfile(f, np.float32, self.n_ev) msec.byteswap(True) self.data_dwell = msec imagestack = np.fromfile(f, np.float32, self.n_cols*self.n_rows*self.n_ev) imagestack.byteswap(True) self.absdata = np.empty((self.n_cols, self.n_rows, self.n_ev)) self.absdata = np.reshape(imagestack, (self.n_cols, self.n_rows, self.n_ev), order='F') f.close() self.fill_h5_struct_from_stk() return #----------------------------------------------------------------------- def read_stk_i0_xas(self, filename): f = open(str(filename),'r') elist = [] ilist = [] for line in f: if line.startswith("*"): pass else: try: e, i = [float(x) for x in line.split()] elist.append(e) ilist.append(i) except ValueError: pass self.evi0 = np.array(elist) self.i0data = np.array(ilist) f.close() self.i0_dwell = None return #----------------------------------------------------------------------- def read_stk_i0_csv(self, filename): f = open(str(filename),'r') elist = [] ilist = [] for line in f: if line.startswith("*"): pass else: try: e, i = [float(x) for x in line.split(',')] elist.append(e) ilist.append(i) except ValueError: pass self.evi0 = np.array(elist) self.i0data = np.array(ilist) f.close() self.i0_dwell = None return #----------------------------------------------------------------------- class x1astk: def __init__(self): pass #----------------------------------------------------------------------- def read_stk(self, filename): f = open(str(filename),'rb') data = np.fromfile(f, np.int32, 3) data.byteswap(True) self.n_cols = data[0] self.n_rows = data[1] self.n_ev = data[2] #print 'self.n_cols, self.n_rows, self.n_ev', self.n_cols, self.n_rows, self.n_ev self.x_dist = np.fromfile(f, np.float32, self.n_cols) self.x_dist.byteswap(True) self.y_dist = np.fromfile(f, np.float32, self.n_rows) self.y_dist.byteswap(True) self.ev = np.fromfile(f, np.float32, self.n_ev) self.ev.byteswap(True) msec = np.fromfile(f, np.float32, self.n_ev) msec.byteswap(True) self.data_dwell = msec imagestack = np.fromfile(f, np.float32, self.n_cols*self.n_rows*self.n_ev) imagestack.byteswap(True) self.absdata = np.empty((self.n_cols, self.n_rows, self.n_ev)) self.absdata = np.reshape(imagestack, (self.n_cols, self.n_rows, self.n_ev), order='F') f.close() # self.original_n_cols = self.n_cols.copy() # self.original_n_rows = self.n_rows.copy() # self.original_n_ev = self.n_ev.copy() # self.original_ev = self.ev.copy() # self.original_absdata = self.absdata.copy() return #----------------------------------------------------------------------- def read_stk_i0_xas(self, filename): f = open(str(filename),'r') elist = [] ilist = [] for line in f: if line.startswith("*"): pass else: e, i = [float(x) for x in line.split()] elist.append(e) ilist.append(i) self.evi0 = np.array(elist) self.i0data = np.array(ilist) f.close() self.i0_dwell = None return #----------------------------------------------------------------------- def read_stk_i0_csv(self, filename): f = open(str(filename),'r') elist = [] ilist = [] for line in f: print(line) if line.startswith("*"): pass else: e, i = [float(x) for x in line.split(',')] elist.append(e) ilist.append(i) self.evi0 = np.array(elist) self.i0data = np.array(ilist) f.close() self.i0_dwell = None return ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_tif.py0000664000175000017500000001356014700316175022366 0ustar00wattswattsfrom __future__ import print_function # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . import numpy as np from PIL import Image import os title = 'Tiff' extension = ['*.tif','*.tiff'] read_types = ['image','stack'] write_types = ['image','stack','results'] def identify(filename): try: # Open Tiff file img = Image.open(filename) return True except: return False def GetFileStructure(FileName): return None #----------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): img = Image.open(filename) imgstack = [] i = 0 while True: try: img.seek(i) i += 1 imgstack.append(np.array((img))) except EOFError: break imgstack = np.array((imgstack)) imgstack = np.transpose(imgstack, axes=(1,2,0)) self.n_cols = imgstack.shape[0] self.n_rows = imgstack.shape[1] self.n_ev = imgstack.shape[2] print('File {0} dims: [{1},{2},{3}]'.format(filename, self.n_cols, self.n_rows, self.n_ev)) pixelsize = 1 #Since we do not have a scanning microscope we fill the x_dist and y_dist from pixel_size self.x_dist = np.arange(float(self.n_cols))*pixelsize self.y_dist = np.arange(float(self.n_rows))*pixelsize #Read energies from file basename, extension = os.path.splitext(filename) engfilename = basename+'.txt' f = open(str(engfilename),'r') elist = [] for line in f: if line.startswith("*"): pass else: e = float(line) elist.append(e) self.ev = np.array(elist) f.close() msec = np.ones((self.n_ev)) self.data_dwell = msec self.absdata = imgstack #Check if the energies are consecutive, if they are not sort the data sort = 0 for i in range(self.n_ev - 1): if self.ev[i] > self.ev[i+1]: sort = 1 break if sort == 1: sortind = np.argsort(self.ev) self.ev = self.ev[sortind] self.absdata = self.absdata[:,:,sortind] self.fill_h5_struct_from_stk() def read_tif_info(filename): img = Image.open(filename) arr = np.array(img) n_cols = arr.shape[1] n_rows = arr.shape[0] return n_cols, n_rows #----------------------------------------------------------------------- def read_tif_list(self, filelist, filepath, ds): from PyQt5.QtCore import Qt n_ev = len(filelist) ev = [] cols = [] rows =[] for i in range(n_ev): row = self.filelist.findItems(filelist[i], Qt.MatchContains)[0].row() cols.append(int(self.filelist.item(row,1).text())) rows.append(int(self.filelist.item(row,2).text())) ev.append(float(self.filelist.item(row,3).text())) assert rows.count(rows[0]) == len(rows), 'x dimension not equally sized' assert cols.count(cols[0]) == len(cols), 'y dimension not equally sized' n_rows = rows[0] n_cols = cols[0] absdata = np.zeros((n_cols, n_rows, n_ev)) for i in range(n_ev): fn = filelist[i] filename = os.path.join(filepath, fn) img = Image.open(filename) imagestack = np.rot90(np.array(img),3) absdata[:, :, i] = np.reshape(imagestack, (n_cols, n_rows), order='C') # Since we do not know the pixel size, x_dist and y_dist is derived from pixel number pixelsize = 1 x_dist = np.arange(float(n_cols))*pixelsize y_dist = np.arange(float(n_rows))*pixelsize # Since unknown, set dwell time to 1 msec = 1 data_dwell = np.ones((n_ev))*msec # # Fill the data structure with data: ds.implements = 'information:exchange:spectromicroscopy' ds.version = '1.0' ds.information.comment = 'Converted from .tif file list in MANTiS', import datetime now = datetime.datetime.now() ds.information.file_creation_datetime = now.strftime("%Y-%m-%dT%H:%M") ds.information.experimenter.name = '' ds.information.sample.name = '' ds.exchange.data = absdata ds.exchange.data_signal = 1 ds.exchange.data_axes = 'x:y' ds.exchange.energy = np.array(ev) ds.exchange.energy_units = 'eV' ds.exchange.x = x_dist ds.exchange.x_units = 'um' ds.exchange.y = y_dist ds.exchange.y_units = 'um' ds.spectromicroscopy.data_dwell = data_dwell return def write(filename, data_object, data_type): """Switchyard for writing different types of data.""" if data_type in ['stack']: write_tif(filename, data_object.absdata, data_object.ev) #----------------------------------------------------------------------- def write_tif(filename, data, energies = []): dims = data.shape for i in range(dims[2]): basename, extension = os.path.splitext(filename) thisfn = basename + '_' + str(i+1) + extension img1 = Image.fromarray(data[:,:,i]) img1.save(thisfn) if len(energies) > 0: thisfn = basename + '.txt' f = open(thisfn, 'w') for i in range(len(energies)): print('%.6f' %(energies[i]), file=f) f.close() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/file_plugins/file_xrm.py0000664000175000017500000040652014700316175022414 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . import numpy as np import scipy as scy import struct import os from mantis_xray import data_struct as ds title = 'XRM' extension = ['*.xrm','*.txrm'] read_types = ['spectrum','image','stack'] write_types = [] def identify(filename): try: xrm_format = False if isOleFile(filename): ole = OleFileIO(filename) if ole.exists('ImageInfo/ImageWidth'): xrm_format = True ole.close() else: xrm_format = False return xrm_format except: return False def GetFileStructure(FileName): return None #----------------------------------------------------------------------- def read(filename, self, selection=None, *args, **kwargs): basename, extension = os.path.splitext(filename) if extension == '.xrm': read_xrm(filename, self) elif extension == '.txrm': read_txrm(filename, self) self.fill_h5_struct_from_stk() return #----------------------------------------------------------------------- def read_xrm_fileinfo(filename, readimgdata = False): if not isOleFile(filename): print ("File not valid OLE type.") return # Open OLE file: ole = OleFileIO(filename) verbose = False if ole.exists('ImageInfo/ImagesTaken'): stream = ole.openstream('ImageInfo/ImagesTaken') data = stream.read() nev = struct.unpack(' 1: print ('This file has more than one image and cannot be used to make a stack.') return 0, 0, 0 if ole.exists('ImageInfo/ImageWidth'): stream = ole.openstream('ImageInfo/ImageWidth') data = stream.read() nev = struct.unpack('0: this_ev = float(this_ev) else: # In this case energy is stored like this: _0250.52_eV_ this_ev = str_list[i-1].strip() this_ev = float(this_ev) if verbose: print ("Successfully read energy value from file name.", this_ev) eV = this_ev break except: eV = eV[0] print ('Using energy stored in the file.', eV) ole.close() if readimgdata == False: return n_cols, n_rows, eV else: return n_cols, n_rows, eV, imgdata #----------------------------------------------------------------------- def read_xrm_list(self, filelist, filepath, ds): #Fill the common stack data file1 = os.path.join(filepath, filelist[0]) if not isOleFile(file1): print ("File not valid OLE type.") return # Open OLE file: ole = OleFileIO(file1) verbose = False if ole.exists('Version'): stream = ole.openstream('Version') data = stream.read() version = struct.unpack('0: this_ev = float(this_ev) else: # In this case energy is stored like this: _0250.52_eV_ this_ev = str_list[i-1] this_ev = float(this_ev) if verbose: print ("Successfully read energy value from file name.", this_ev) eV = this_ev break except: eV = eV[0] print ('Using energy stored in the file.', eV) ev[j] = this_ev if ev[-1]512 bytes # - getsect: added conformity checks # - DEBUG_MODE constant to activate debug display # 2007-09-04 v0.13 PL: - improved/translated (lots of) comments # - updated license # - converted tabs to 4 spaces # 2007-11-19 v0.14 PL: - added OleFileIO._raise_defect() to adapt sensitivity # - improved _unicode() to use Python 2.x unicode support # - fixed bug in _OleDirectoryEntry # 2007-11-25 v0.15 PL: - added safety checks to detect FAT loops # - fixed _OleStream which didn't check stream size # - added/improved many docstrings and comments # - moved helper functions _unicode and _clsid out of # OleFileIO class # - improved OleFileIO._find() to add Unix path syntax # - OleFileIO._find() is now case-insensitive # - added get_type() and get_rootentry_name() # - rewritten loaddirectory and _OleDirectoryEntry # 2007-11-27 v0.16 PL: - added _OleDirectoryEntry.kids_dict # - added detection of duplicate filenames in storages # - added detection of duplicate references to streams # - added get_size() and exists() to _OleDirectoryEntry # - added isOleFile to check header before parsing # - added __all__ list to control public keywords in pydoc # 2007-12-04 v0.17 PL: - added _load_direntry to fix a bug in loaddirectory # - improved _unicode(), added workarounds for Python <2.3 # - added set_debug_mode and -d option to set debug mode # - fixed bugs in OleFileIO.open and _OleDirectoryEntry # - added safety check in main for large or binary # properties # - allow size>0 for storages for some implementations # 2007-12-05 v0.18 PL: - fixed several bugs in handling of FAT, MiniFAT and # streams # - added option '-c' in main to check all streams # 2009-12-10 v0.19 PL: - bugfix for 32 bit arrays on 64 bits platforms # (thanks to Ben G. and Martijn for reporting the bug) # 2009-12-11 v0.20 PL: - bugfix in OleFileIO.open when filename is not plain str # 2010-01-22 v0.21 PL: - added support for big-endian CPUs such as PowerPC Macs # 2012-02-16 v0.22 PL: - fixed bug in getproperties, patch by chuckleberryfinn # (https://bitbucket.org/decalage/olefileio_pl/issue/7) # - added close method to OleFileIO (fixed issue #2) # 2012-07-25 v0.23 PL: - added support for file-like objects (patch by mete0r_kr) # 2013-05-05 v0.24 PL: - getproperties: added conversion from filetime to python # datetime # - main: displays properties with date format # - new class OleMetadata to parse standard properties # - added get_metadata method # 2013-05-07 v0.24 PL: - a few improvements in OleMetadata # 2013-05-24 v0.25 PL: - getproperties: option to not convert some timestamps # - OleMetaData: total_edit_time is now a number of seconds, # not a timestamp # - getproperties: added support for VT_BOOL, VT_INT, V_UINT # - getproperties: filter out null chars from strings # - getproperties: raise non-fatal defects instead of # exceptions when properties cannot be parsed properly # 2013-05-27 PL: - getproperties: improved exception handling # - _raise_defect: added option to set exception type # - all non-fatal issues are now recorded, and displayed # when run as a script # 2013-07-11 v0.26 PL: - added methods to get modification and creation times # of a directory entry or a storage/stream # - fixed parsing of direntry timestamps # 2013-07-24 PL: - new options in listdir to list storages and/or streams # 2014-02-04 v0.30 PL: - upgraded code to support Python 3.x by Martin Panter # - several fixes for Python 2.6 (xrange, MAGIC) # - reused i32 from Pillow's _binary # 2014-07-18 v0.31 - preliminary support for 4K sectors # 2014-07-27 v0.31 PL: - a few improvements in OleFileIO.open (header parsing) # - Fixed loadfat for large files with 4K sectors (issue #3) # 2014-07-30 v0.32 PL: - added write_sect to write sectors to disk # - added write_mode option to OleFileIO.__init__ and open # 2014-07-31 PL: - fixed padding in write_sect for Python 3, added checks # - added write_stream to write a stream to disk # 2014-09-26 v0.40 PL: - renamed OleFileIO_PL to olefile # 2014-11-09 NE: - added support for Jython (Niko Ehrenfeuchter) # 2014-11-13 v0.41 PL: - improved isOleFile and OleFileIO.open to support OLE # data in a string buffer and file-like objects. # 2014-11-21 PL: - updated comments according to Pillow's commits # 2015-01-24 v0.42 PL: - changed the default path name encoding from Latin-1 # to UTF-8 on Python 2.x (Unicode on Python 3.x) # - added path_encoding option to override the default # - fixed a bug in _list when a storage is empty #------------------------------------------------------------------------------ # TODO (for version 1.0): # + get rid of print statements, to simplify Python 2.x and 3.x support # + add is_stream and is_storage # + remove leading and trailing slashes where a path is used # + add functions path_list2str and path_str2list # + fix how all the methods handle unicode str and/or bytes as arguments # + add path attrib to _OleDirEntry, set it once and for all in init or # append_kids (then listdir/_list can be simplified) # - TESTS with Linux, MacOSX, Python 1.5.2, various files, PIL, ... # - add underscore to each private method, to avoid their display in # pydoc/epydoc documentation - Remove it for classes to be documented # - replace all raised exceptions with _raise_defect (at least in OleFileIO) # - merge code from _OleStream and OleFileIO.getsect to read sectors # (maybe add a class for FAT and MiniFAT ?) # - add method to check all streams (follow sectors chains without storing all # stream in memory, and report anomalies) # - use _OleDirectoryEntry.kids_dict to improve _find and _list ? # - fix Unicode names handling (find some way to stay compatible with Py1.5.2) # => if possible avoid converting names to Latin-1 # - review DIFAT code: fix handling of DIFSECT blocks in FAT (not stop) # - rewrite OleFileIO.getproperties # - improve docstrings to show more sample uses # - see also original notes and FIXME below # - remove all obsolete FIXMEs # - OleMetadata: fix version attrib according to # http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx # IDEAS: # - in OleFileIO._open and _OleStream, use size=None instead of 0x7FFFFFFF for # streams with unknown size # - use arrays of int instead of long integers for FAT/MiniFAT, to improve # performance and reduce memory usage ? (possible issue with values >2^31) # - provide tests with unittest (may need write support to create samples) # - move all debug code (and maybe dump methods) to a separate module, with # a class which inherits OleFileIO ? # - fix docstrings to follow epydoc format # - add support for big endian byte order ? # - create a simple OLE explorer with wxPython # FUTURE EVOLUTIONS to add write support: # see issue #6 on Bitbucket: # https://bitbucket.org/decalage/olefileio_pl/issue/6/improve-olefileio_pl-to-write-ole-files #------------------------------------------------------------------------------ # NOTES from PIL 1.1.6: # History: # 1997-01-20 fl Created # 1997-01-22 fl Fixed 64-bit portability quirk # 2003-09-09 fl Fixed typo in OleFileIO.loadfat (noted by Daniel Haertle) # 2004-02-29 fl Changed long hex constants to signed integers # # Notes: # FIXME: sort out sign problem (eliminate long hex constants) # FIXME: change filename to use "a/b/c" instead of ["a", "b", "c"] # FIXME: provide a glob mechanism function (using fnmatchcase) # # Literature: # # "FlashPix Format Specification, Appendix A", Kodak and Microsoft, # September 1996. # # Quotes: # # "If this document and functionality of the Software conflict, # the actual functionality of the Software represents the correct # functionality" -- Microsoft, in the OLE format specification #------------------------------------------------------------------------------- import io import sys import struct, array, os.path, datetime #=== COMPATIBILITY WORKAROUNDS ================================================ #[PL] Define explicitly the public API to avoid private objects in pydoc: #TODO: add more # __all__ = ['OleFileIO', 'isOleFile', 'MAGIC'] # For Python 3.x, need to redefine long as int: if str is not bytes: long = int # Need to make sure we use xrange both on Python 2 and 3.x: try: # on Python 2 we need xrange: iterrange = xrange except: # no xrange, for Python 3 it was renamed as range: iterrange = range #[PL] workaround to fix an issue with array item size on 64 bits systems: if array.array('L').itemsize == 4: # on 32 bits platforms, long integers in an array are 32 bits: UINT32 = 'L' elif array.array('I').itemsize == 4: # on 64 bits platforms, integers in an array are 32 bits: UINT32 = 'I' elif array.array('i').itemsize == 4: # On 64 bit Jython, signed integers ('i') are the only way to store our 32 # bit values in an array in a *somewhat* reasonable way, as the otherwise # perfectly suited 'H' (unsigned int, 32 bits) results in a completely # unusable behaviour. This is most likely caused by the fact that Java # doesn't have unsigned values, and thus Jython's "array" implementation, # which is based on "jarray", doesn't have them either. # NOTE: to trick Jython into converting the values it would normally # interpret as "signed" into "unsigned", a binary-and operation with # 0xFFFFFFFF can be used. This way it is possible to use the same comparing # operations on all platforms / implementations. The corresponding code # lines are flagged with a 'JYTHON-WORKAROUND' tag below. UINT32 = 'i' else: raise ValueError('Need to fix a bug with 32 bit arrays, please contact author...') #[PL] These workarounds were inspired from the Path module # (see http://www.jorendorff.com/articles/python/path/) #TODO: test with old Python versions # Pre-2.3 workaround for basestring. try: basestring except NameError: try: # is Unicode supported (Python >2.0 or >1.6 ?) basestring = (str, unicode) except NameError: basestring = str #[PL] Experimental setting: if True, OLE filenames will be kept in Unicode # if False (default PIL behaviour), all filenames are converted to Latin-1. KEEP_UNICODE_NAMES = True if sys.version_info[0] < 3: # On Python 2.x, the default encoding for path names is UTF-8: DEFAULT_PATH_ENCODING = 'utf-8' else: # On Python 3.x, the default encoding for path names is Unicode (None): DEFAULT_PATH_ENCODING = None #=== DEBUGGING =============================================================== #TODO: replace this by proper logging #[PL] DEBUG display mode: False by default, use set_debug_mode() or "-d" on # command line to change it. DEBUG_MODE = False def debug_print(msg): print(msg) def debug_pass(msg): pass debug = debug_pass def set_debug_mode(debug_mode): """ Set debug mode on or off, to control display of debugging messages. :param mode: True or False """ global DEBUG_MODE, debug DEBUG_MODE = debug_mode if debug_mode: debug = debug_print else: debug = debug_pass #=== CONSTANTS =============================================================== # magic bytes that should be at the beginning of every OLE file: MAGIC = b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1' #[PL]: added constants for Sector IDs (from AAF specifications) MAXREGSECT = 0xFFFFFFFA # (-6) maximum SECT DIFSECT = 0xFFFFFFFC # (-4) denotes a DIFAT sector in a FAT FATSECT = 0xFFFFFFFD # (-3) denotes a FAT sector in a FAT ENDOFCHAIN = 0xFFFFFFFE # (-2) end of a virtual stream chain FREESECT = 0xFFFFFFFF # (-1) unallocated sector #[PL]: added constants for Directory Entry IDs (from AAF specifications) MAXREGSID = 0xFFFFFFFA # (-6) maximum directory entry ID NOSTREAM = 0xFFFFFFFF # (-1) unallocated directory entry #[PL] object types in storage (from AAF specifications) STGTY_EMPTY = 0 # empty directory entry (according to OpenOffice.org doc) STGTY_STORAGE = 1 # element is a storage object STGTY_STREAM = 2 # element is a stream object STGTY_LOCKBYTES = 3 # element is an ILockBytes object STGTY_PROPERTY = 4 # element is an IPropertyStorage object STGTY_ROOT = 5 # element is a root storage # # -------------------------------------------------------------------- # property types VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6; VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11; VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17; VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23; VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28; VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64; VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68; VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72; VT_VECTOR=0x1000; # map property id to name (for debugging purposes) VT = {} for keyword, var in list(vars().items()): if keyword[:3] == "VT_": VT[var] = keyword # # -------------------------------------------------------------------- # Some common document types (root.clsid fields) WORD_CLSID = "00020900-0000-0000-C000-000000000046" #TODO: check Excel, PPT, ... #[PL]: Defect levels to classify parsing errors - see OleFileIO._raise_defect() DEFECT_UNSURE = 10 # a case which looks weird, but not sure it's a defect DEFECT_POTENTIAL = 20 # a potential defect DEFECT_INCORRECT = 30 # an error according to specifications, but parsing # can go on DEFECT_FATAL = 40 # an error which cannot be ignored, parsing is # impossible # Minimal size of an empty OLE file, with 512-bytes sectors = 1536 bytes # (this is used in isOleFile and OleFile.open) MINIMAL_OLEFILE_SIZE = 1536 #[PL] add useful constants to __all__: # for key in list(vars().keys()): # if key.startswith('STGTY_') or key.startswith('DEFECT_'): # __all__.append(key) #=== FUNCTIONS =============================================================== def isOleFile (filename): """ Test if a file is an OLE container (according to the magic bytes in its header). :param filename: string-like or file-like object, OLE file to parse - if filename is a string smaller than 1536 bytes, it is the path of the file to open. (bytes or unicode string) - if filename is a string longer than 1535 bytes, it is parsed as the content of an OLE file in memory. (bytes type only) - if filename is a file-like object (with read and seek methods), it is parsed as-is. :returns: True if OLE, False otherwise. """ # check if filename is a string-like or file-like object: if hasattr(filename, 'read'): # file-like object: use it directly header = filename.read(len(MAGIC)) # just in case, seek back to start of file: filename.seek(0) elif isinstance(filename, bytes) and len(filename) >= MINIMAL_OLEFILE_SIZE: # filename is a bytes string containing the OLE file to be parsed: header = filename[:len(MAGIC)] else: # string-like object: filename of file on disk header = open(filename, 'rb').read(len(MAGIC)) if header == MAGIC: return True else: return False if bytes is str: # version for Python 2.x def i8(c): return ord(c) else: # version for Python 3.x def i8(c): return c if c.__class__ is int else c[0] #TODO: replace i16 and i32 with more readable struct.unpack equivalent? def i16(c, o = 0): """ Converts a 2-bytes (16 bits) string to an integer. :param c: string containing bytes to convert :param o: offset of bytes to convert in string """ return i8(c[o]) | (i8(c[o+1])<<8) def i32(c, o = 0): """ Converts a 4-bytes (32 bits) string to an integer. :param c: string containing bytes to convert :param o: offset of bytes to convert in string """ ## return int(ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)) ## # [PL]: added int() because "<<" gives long int since Python 2.4 # copied from Pillow's _binary: return i8(c[o]) | (i8(c[o+1])<<8) | (i8(c[o+2])<<16) | (i8(c[o+3])<<24) def _clsid(clsid): """ Converts a CLSID to a human-readable string. :param clsid: string of length 16. """ assert len(clsid) == 16 # if clsid is only made of null bytes, return an empty string: # (PL: why not simply return the string with zeroes?) if not clsid.strip(b"\0"): return "" return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) % ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) + tuple(map(i8, clsid[8:16])))) def filetime2datetime(filetime): """ convert FILETIME (64 bits int) to Python datetime.datetime """ # TODO: manage exception when microseconds is too large # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/ _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0) #debug('timedelta days=%d' % (filetime//(10*1000000*3600*24))) return _FILETIME_null_date + datetime.timedelta(microseconds=filetime//10) #=== CLASSES ================================================================== class OleMetadata: """ class to parse and store metadata from standard properties of OLE files. Available attributes: codepage, title, subject, author, keywords, comments, template, last_saved_by, revision_number, total_edit_time, last_printed, create_time, last_saved_time, num_pages, num_words, num_chars, thumbnail, creating_application, security, codepage_doc, category, presentation_target, bytes, lines, paragraphs, slides, notes, hidden_slides, mm_clips, scale_crop, heading_pairs, titles_of_parts, manager, company, links_dirty, chars_with_spaces, unused, shared_doc, link_base, hlinks, hlinks_changed, version, dig_sig, content_type, content_status, language, doc_version Note: an attribute is set to None when not present in the properties of the OLE file. References for SummaryInformation stream: - http://msdn.microsoft.com/en-us/library/dd942545.aspx - http://msdn.microsoft.com/en-us/library/dd925819%28v=office.12%29.aspx - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380376%28v=vs.85%29.aspx - http://msdn.microsoft.com/en-us/library/aa372045.aspx - http://sedna-soft.de/summary-information-stream/ - http://poi.apache.org/apidocs/org/apache/poi/hpsf/SummaryInformation.html References for DocumentSummaryInformation stream: - http://msdn.microsoft.com/en-us/library/dd945671%28v=office.12%29.aspx - http://msdn.microsoft.com/en-us/library/windows/desktop/aa380374%28v=vs.85%29.aspx - http://poi.apache.org/apidocs/org/apache/poi/hpsf/DocumentSummaryInformation.html new in version 0.25 """ # attribute names for SummaryInformation stream properties: # (ordered by property id, starting at 1) SUMMARY_ATTRIBS = ['codepage', 'title', 'subject', 'author', 'keywords', 'comments', 'template', 'last_saved_by', 'revision_number', 'total_edit_time', 'last_printed', 'create_time', 'last_saved_time', 'num_pages', 'num_words', 'num_chars', 'thumbnail', 'creating_application', 'security'] # attribute names for DocumentSummaryInformation stream properties: # (ordered by property id, starting at 1) DOCSUM_ATTRIBS = ['codepage_doc', 'category', 'presentation_target', 'bytes', 'lines', 'paragraphs', 'slides', 'notes', 'hidden_slides', 'mm_clips', 'scale_crop', 'heading_pairs', 'titles_of_parts', 'manager', 'company', 'links_dirty', 'chars_with_spaces', 'unused', 'shared_doc', 'link_base', 'hlinks', 'hlinks_changed', 'version', 'dig_sig', 'content_type', 'content_status', 'language', 'doc_version'] def __init__(self): """ Constructor for OleMetadata All attributes are set to None by default """ # properties from SummaryInformation stream self.codepage = None self.title = None self.subject = None self.author = None self.keywords = None self.comments = None self.template = None self.last_saved_by = None self.revision_number = None self.total_edit_time = None self.last_printed = None self.create_time = None self.last_saved_time = None self.num_pages = None self.num_words = None self.num_chars = None self.thumbnail = None self.creating_application = None self.security = None # properties from DocumentSummaryInformation stream self.codepage_doc = None self.category = None self.presentation_target = None self.bytes = None self.lines = None self.paragraphs = None self.slides = None self.notes = None self.hidden_slides = None self.mm_clips = None self.scale_crop = None self.heading_pairs = None self.titles_of_parts = None self.manager = None self.company = None self.links_dirty = None self.chars_with_spaces = None self.unused = None self.shared_doc = None self.link_base = None self.hlinks = None self.hlinks_changed = None self.version = None self.dig_sig = None self.content_type = None self.content_status = None self.language = None self.doc_version = None def parse_properties(self, olefile): """ Parse standard properties of an OLE file, from the streams "\x05SummaryInformation" and "\x05DocumentSummaryInformation", if present. Properties are converted to strings, integers or python datetime objects. If a property is not present, its value is set to None. """ # first set all attributes to None: for attrib in (self.SUMMARY_ATTRIBS + self.DOCSUM_ATTRIBS): setattr(self, attrib, None) if olefile.exists("\x05SummaryInformation"): # get properties from the stream: # (converting timestamps to python datetime, except total_edit_time, # which is property #10) props = olefile.getproperties("\x05SummaryInformation", convert_time=True, no_conversion=[10]) # store them into this object's attributes: for i in range(len(self.SUMMARY_ATTRIBS)): # ids for standards properties start at 0x01, until 0x13 value = props.get(i+1, None) setattr(self, self.SUMMARY_ATTRIBS[i], value) if olefile.exists("\x05DocumentSummaryInformation"): # get properties from the stream: props = olefile.getproperties("\x05DocumentSummaryInformation", convert_time=True) # store them into this object's attributes: for i in range(len(self.DOCSUM_ATTRIBS)): # ids for standards properties start at 0x01, until 0x13 value = props.get(i+1, None) setattr(self, self.DOCSUM_ATTRIBS[i], value) def dump(self): """ Dump all metadata, for debugging purposes. """ print('Properties from SummaryInformation stream:') for prop in self.SUMMARY_ATTRIBS: value = getattr(self, prop) print('- %s: %s' % (prop, repr(value))) print('Properties from DocumentSummaryInformation stream:') for prop in self.DOCSUM_ATTRIBS: value = getattr(self, prop) print('- %s: %s' % (prop, repr(value))) #--- _OleStream --------------------------------------------------------------- class _OleStream(io.BytesIO): """ OLE2 Stream Returns a read-only file object which can be used to read the contents of a OLE stream (instance of the BytesIO class). To open a stream, use the openstream method in the OleFile class. This function can be used with either ordinary streams, or ministreams, depending on the offset, sectorsize, and fat table arguments. Attributes: - size: actual size of data stream, after it was opened. """ # FIXME: should store the list of sects obtained by following # the fat chain, and load new sectors on demand instead of # loading it all in one go. def __init__(self, fp, sect, size, offset, sectorsize, fat, filesize): """ Constructor for _OleStream class. :param fp: file object, the OLE container or the MiniFAT stream :param sect: sector index of first sector in the stream :param size: total size of the stream :param offset: offset in bytes for the first FAT or MiniFAT sector :param sectorsize: size of one sector :param fat: array/list of sector indexes (FAT or MiniFAT) :param filesize: size of OLE file (for debugging) :returns: a BytesIO instance containing the OLE stream """ debug('_OleStream.__init__:') debug(' sect=%d (%X), size=%d, offset=%d, sectorsize=%d, len(fat)=%d, fp=%s' %(sect,sect,size,offset,sectorsize,len(fat), repr(fp))) #[PL] To detect malformed documents with FAT loops, we compute the # expected number of sectors in the stream: unknown_size = False if size==0x7FFFFFFF: # this is the case when called from OleFileIO._open(), and stream # size is not known in advance (for example when reading the # Directory stream). Then we can only guess maximum size: size = len(fat)*sectorsize # and we keep a record that size was unknown: unknown_size = True debug(' stream with UNKNOWN SIZE') nb_sectors = (size + (sectorsize-1)) // sectorsize debug('nb_sectors = %d' % nb_sectors) # This number should (at least) be less than the total number of # sectors in the given FAT: if nb_sectors > len(fat): raise IOError('malformed OLE document, stream too large') # optimization(?): data is first a list of strings, and join() is called # at the end to concatenate all in one string. # (this may not be really useful with recent Python versions) data = [] # if size is zero, then first sector index should be ENDOFCHAIN: if size == 0 and sect != ENDOFCHAIN: debug('size == 0 and sect != ENDOFCHAIN:') raise IOError('incorrect OLE sector index for empty stream') #[PL] A fixed-length for loop is used instead of an undefined while # loop to avoid DoS attacks: for i in range(nb_sectors): # Sector index may be ENDOFCHAIN, but only if size was unknown if sect == ENDOFCHAIN: if unknown_size: break else: # else this means that the stream is smaller than declared: debug('sect=ENDOFCHAIN before expected size') raise IOError('incomplete OLE stream') # sector index should be within FAT: if sect<0 or sect>=len(fat): debug('sect=%d (%X) / len(fat)=%d' % (sect, sect, len(fat))) debug('i=%d / nb_sectors=%d' %(i, nb_sectors)) ## tmp_data = b"".join(data) ## f = open('test_debug.bin', 'wb') ## f.write(tmp_data) ## f.close() ## debug('data read so far: %d bytes' % len(tmp_data)) raise IOError('incorrect OLE FAT, sector index out of range') #TODO: merge this code with OleFileIO.getsect() ? #TODO: check if this works with 4K sectors: try: fp.seek(offset + sectorsize * sect) except: debug('sect=%d, seek=%d, filesize=%d' % (sect, offset+sectorsize*sect, filesize)) raise IOError('OLE sector index out of range') sector_data = fp.read(sectorsize) # [PL] check if there was enough data: # Note: if sector is the last of the file, sometimes it is not a # complete sector (of 512 or 4K), so we may read less than # sectorsize. if len(sector_data)!=sectorsize and sect!=(len(fat)-1): debug('sect=%d / len(fat)=%d, seek=%d / filesize=%d, len read=%d' % (sect, len(fat), offset+sectorsize*sect, filesize, len(sector_data))) debug('seek+len(read)=%d' % (offset+sectorsize*sect+len(sector_data))) raise IOError('incomplete OLE sector') data.append(sector_data) # jump to next sector in the FAT: try: sect = fat[sect] & 0xFFFFFFFF # JYTHON-WORKAROUND except IndexError: # [PL] if pointer is out of the FAT an exception is raised raise IOError('incorrect OLE FAT, sector index out of range') #[PL] Last sector should be a "end of chain" marker: if sect != ENDOFCHAIN: raise IOError('incorrect last sector index in OLE stream') data = b"".join(data) # Data is truncated to the actual stream size: if len(data) >= size: data = data[:size] # actual stream size is stored for future use: self.size = size elif unknown_size: # actual stream size was not known, now we know the size of read # data: self.size = len(data) else: # read data is less than expected: debug('len(data)=%d, size=%d' % (len(data), size)) raise IOError('OLE stream size is less than declared') # when all data is read in memory, BytesIO constructor is called io.BytesIO.__init__(self, data) # Then the _OleStream object can be used as a read-only file object. #--- _OleDirectoryEntry ------------------------------------------------------- class _OleDirectoryEntry: """ OLE2 Directory Entry """ #[PL] parsing code moved from OleFileIO.loaddirectory # struct to parse directory entries: # <: little-endian byte order, standard sizes # (note: this should guarantee that Q returns a 64 bits int) # 64s: string containing entry name in unicode (max 31 chars) + null char # H: uint16, number of bytes used in name buffer, including null = (len+1)*2 # B: uint8, dir entry type (between 0 and 5) # B: uint8, color: 0=black, 1=red # I: uint32, index of left child node in the red-black tree, NOSTREAM if none # I: uint32, index of right child node in the red-black tree, NOSTREAM if none # I: uint32, index of child root node if it is a storage, else NOSTREAM # 16s: CLSID, unique identifier (only used if it is a storage) # I: uint32, user flags # Q (was 8s): uint64, creation timestamp or zero # Q (was 8s): uint64, modification timestamp or zero # I: uint32, SID of first sector if stream or ministream, SID of 1st sector # of stream containing ministreams if root entry, 0 otherwise # I: uint32, total stream size in bytes if stream (low 32 bits), 0 otherwise # I: uint32, total stream size in bytes if stream (high 32 bits), 0 otherwise STRUCT_DIRENTRY = '<64sHBBIII16sIQQIII' # size of a directory entry: 128 bytes DIRENTRY_SIZE = 128 assert struct.calcsize(STRUCT_DIRENTRY) == DIRENTRY_SIZE def __init__(self, entry, sid, olefile): """ Constructor for an _OleDirectoryEntry object. Parses a 128-bytes entry from the OLE Directory stream. :param entry : string (must be 128 bytes long) :param sid : index of this directory entry in the OLE file directory :param olefile: OleFileIO containing this directory entry """ self.sid = sid # ref to olefile is stored for future use self.olefile = olefile # kids is a list of children entries, if this entry is a storage: # (list of _OleDirectoryEntry objects) self.kids = [] # kids_dict is a dictionary of children entries, indexed by their # name in lowercase: used to quickly find an entry, and to detect # duplicates self.kids_dict = {} # flag used to detect if the entry is referenced more than once in # directory: self.used = False # decode DirEntry ( name, namelength, self.entry_type, self.color, self.sid_left, self.sid_right, self.sid_child, clsid, self.dwUserFlags, self.createTime, self.modifyTime, self.isectStart, sizeLow, sizeHigh ) = struct.unpack(_OleDirectoryEntry.STRUCT_DIRENTRY, entry) if self.entry_type not in [STGTY_ROOT, STGTY_STORAGE, STGTY_STREAM, STGTY_EMPTY]: olefile._raise_defect(DEFECT_INCORRECT, 'unhandled OLE storage type') # only first directory entry can (and should) be root: if self.entry_type == STGTY_ROOT and sid != 0: olefile._raise_defect(DEFECT_INCORRECT, 'duplicate OLE root entry') if sid == 0 and self.entry_type != STGTY_ROOT: olefile._raise_defect(DEFECT_INCORRECT, 'incorrect OLE root entry') #debug (struct.unpack(fmt_entry, entry[:len_entry])) # name should be at most 31 unicode characters + null character, # so 64 bytes in total (31*2 + 2): if namelength>64: olefile._raise_defect(DEFECT_INCORRECT, 'incorrect DirEntry name length') # if exception not raised, namelength is set to the maximum value: namelength = 64 # only characters without ending null char are kept: name = name[:(namelength-2)] #TODO: check if the name is actually followed by a null unicode character ([MS-CFB] 2.6.1) #TODO: check if the name does not contain forbidden characters: # [MS-CFB] 2.6.1: "The following characters are illegal and MUST NOT be part of the name: '/', '\', ':', '!'." # name is converted from UTF-16LE to the path encoding specified in the OleFileIO: self.name = olefile._decode_utf16_str(name) debug('DirEntry SID=%d: %s' % (self.sid, repr(self.name))) debug(' - type: %d' % self.entry_type) debug(' - sect: %d' % self.isectStart) debug(' - SID left: %d, right: %d, child: %d' % (self.sid_left, self.sid_right, self.sid_child)) # sizeHigh is only used for 4K sectors, it should be zero for 512 bytes # sectors, BUT apparently some implementations set it as 0xFFFFFFFF, 1 # or some other value so it cannot be raised as a defect in general: if olefile.sectorsize == 512: if sizeHigh != 0 and sizeHigh != 0xFFFFFFFF: debug('sectorsize=%d, sizeLow=%d, sizeHigh=%d (%X)' % (olefile.sectorsize, sizeLow, sizeHigh, sizeHigh)) olefile._raise_defect(DEFECT_UNSURE, 'incorrect OLE stream size') self.size = sizeLow else: self.size = sizeLow + (long(sizeHigh)<<32) debug(' - size: %d (sizeLow=%d, sizeHigh=%d)' % (self.size, sizeLow, sizeHigh)) self.clsid = _clsid(clsid) # a storage should have a null size, BUT some implementations such as # Word 8 for Mac seem to allow non-null values => Potential defect: if self.entry_type == STGTY_STORAGE and self.size != 0: olefile._raise_defect(DEFECT_POTENTIAL, 'OLE storage with size>0') # check if stream is not already referenced elsewhere: if self.entry_type in (STGTY_ROOT, STGTY_STREAM) and self.size>0: if self.size < olefile.minisectorcutoff \ and self.entry_type==STGTY_STREAM: # only streams can be in MiniFAT # ministream object minifat = True else: minifat = False olefile._check_duplicate_stream(self.isectStart, minifat) def build_storage_tree(self): """ Read and build the red-black tree attached to this _OleDirectoryEntry object, if it is a storage. Note that this method builds a tree of all subentries, so it should only be called for the root object once. """ debug('build_storage_tree: SID=%d - %s - sid_child=%d' % (self.sid, repr(self.name), self.sid_child)) if self.sid_child != NOSTREAM: # if child SID is not NOSTREAM, then this entry is a storage. # Let's walk through the tree of children to fill the kids list: self.append_kids(self.sid_child) # Note from OpenOffice documentation: the safest way is to # recreate the tree because some implementations may store broken # red-black trees... # in the OLE file, entries are sorted on (length, name). # for convenience, we sort them on name instead: # (see rich comparison methods in this class) self.kids.sort() def append_kids(self, child_sid): """ Walk through red-black tree of children of this directory entry to add all of them to the kids list. (recursive method) :param child_sid : index of child directory entry to use, or None when called first time for the root. (only used during recursion) """ #[PL] this method was added to use simple recursion instead of a complex # algorithm. # if this is not a storage or a leaf of the tree, nothing to do: if child_sid == NOSTREAM: return # check if child SID is in the proper range: if child_sid<0 or child_sid>=len(self.olefile.direntries): self.olefile._raise_defect(DEFECT_FATAL, 'OLE DirEntry index out of range') # get child direntry: child = self.olefile._load_direntry(child_sid) #direntries[child_sid] debug('append_kids: child_sid=%d - %s - sid_left=%d, sid_right=%d, sid_child=%d' % (child.sid, repr(child.name), child.sid_left, child.sid_right, child.sid_child)) # the directory entries are organized as a red-black tree. # (cf. Wikipedia for details) # First walk through left side of the tree: self.append_kids(child.sid_left) # Check if its name is not already used (case-insensitive): name_lower = child.name.lower() if name_lower in self.kids_dict: self.olefile._raise_defect(DEFECT_INCORRECT, "Duplicate filename in OLE storage") # Then the child_sid _OleDirectoryEntry object is appended to the # kids list and dictionary: self.kids.append(child) self.kids_dict[name_lower] = child # Check if kid was not already referenced in a storage: if child.used: self.olefile._raise_defect(DEFECT_INCORRECT, 'OLE Entry referenced more than once') child.used = True # Finally walk through right side of the tree: self.append_kids(child.sid_right) # Afterwards build kid's own tree if it's also a storage: child.build_storage_tree() def __eq__(self, other): "Compare entries by name" return self.name == other.name def __lt__(self, other): "Compare entries by name" return self.name < other.name def __ne__(self, other): return not self.__eq__(other) def __le__(self, other): return self.__eq__(other) or self.__lt__(other) # Reflected __lt__() and __le__() will be used for __gt__() and __ge__() #TODO: replace by the same function as MS implementation ? # (order by name length first, then case-insensitive order) def dump(self, tab = 0): "Dump this entry, and all its subentries (for debug purposes only)" TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)", "(property)", "(root)"] #print(" "*tab + repr(self.name), TYPES[self.entry_type], end=' ') #if self.entry_type in (STGTY_STREAM, STGTY_ROOT): # print(self.size, "bytes", end=' ') #print() #if self.entry_type in (STGTY_STORAGE, STGTY_ROOT) and self.clsid: # print(" "*tab + "{%s}" % self.clsid) for kid in self.kids: kid.dump(tab + 2) def getmtime(self): """ Return modification time of a directory entry. :returns: None if modification time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 """ if self.modifyTime == 0: return None return filetime2datetime(self.modifyTime) def getctime(self): """ Return creation time of a directory entry. :returns: None if modification time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 """ if self.createTime == 0: return None return filetime2datetime(self.createTime) #--- OleFileIO ---------------------------------------------------------------- class OleFileIO: """ OLE container object This class encapsulates the interface to an OLE 2 structured storage file. Use the listdir and openstream methods to access the contents of this file. Object names are given as a list of strings, one for each subentry level. The root entry should be omitted. For example, the following code extracts all image streams from a Microsoft Image Composer file:: ole = OleFileIO("fan.mic") for entry in ole.listdir(): if entry[1:2] == "Image": fin = ole.openstream(entry) fout = open(entry[0:1], "wb") while True: s = fin.read(8192) if not s: break fout.write(s) You can use the viewer application provided with the Python Imaging Library to view the resulting files (which happens to be standard TIFF files). """ def __init__(self, filename=None, raise_defects=DEFECT_FATAL, write_mode=False, debug=False, path_encoding=DEFAULT_PATH_ENCODING): """ Constructor for the OleFileIO class. :param filename: file to open. - if filename is a string smaller than 1536 bytes, it is the path of the file to open. (bytes or unicode string) - if filename is a string longer than 1535 bytes, it is parsed as the content of an OLE file in memory. (bytes type only) - if filename is a file-like object (with read, seek and tell methods), it is parsed as-is. :param raise_defects: minimal level for defects to be raised as exceptions. (use DEFECT_FATAL for a typical application, DEFECT_INCORRECT for a security-oriented application, see source code for details) :param write_mode: bool, if True the file is opened in read/write mode instead of read-only by default. :param debug: bool, set debug mode :param path_encoding: None or str, name of the codec to use for path names (streams and storages), or None for Unicode. Unicode by default on Python 3+, UTF-8 on Python 2.x. (new in olefile 0.42, was hardcoded to Latin-1 until olefile v0.41) """ set_debug_mode(debug) # minimal level for defects to be raised as exceptions: self._raise_defects_level = raise_defects # list of defects/issues not raised as exceptions: # tuples of (exception type, message) self.parsing_issues = [] self.write_mode = write_mode self.path_encoding = path_encoding self._filesize = None self.fp = None if filename: self.open(filename, write_mode=write_mode) def _raise_defect(self, defect_level, message, exception_type=IOError): """ This method should be called for any defect found during file parsing. It may raise an IOError exception according to the minimal level chosen for the OleFileIO object. :param defect_level: defect level, possible values are: - DEFECT_UNSURE : a case which looks weird, but not sure it's a defect - DEFECT_POTENTIAL : a potential defect - DEFECT_INCORRECT : an error according to specifications, but parsing can go on - DEFECT_FATAL : an error which cannot be ignored, parsing is impossible :param message: string describing the defect, used with raised exception. :param exception_type: exception class to be raised, IOError by default """ # added by [PL] if defect_level >= self._raise_defects_level: raise exception_type(message) else: # just record the issue, no exception raised: self.parsing_issues.append((exception_type, message)) def _decode_utf16_str(self, utf16_str, errors='replace'): """ Decode a string encoded in UTF-16 LE format, as found in the OLE directory or in property streams. Return a string encoded according to the path_encoding specified for the OleFileIO object. :param utf16_str: bytes string encoded in UTF-16 LE format :param errors: str, see python documentation for str.decode() :return: str, encoded according to path_encoding """ unicode_str = utf16_str.decode('UTF-16LE', errors) if self.path_encoding: # an encoding has been specified for path names: return unicode_str.encode(self.path_encoding, errors) else: # path_encoding=None, return the Unicode string as-is: return unicode_str def open(self, filename, write_mode=False): """ Open an OLE2 file in read-only or read/write mode. Read and parse the header, FAT and directory. :param filename: string-like or file-like object, OLE file to parse - if filename is a string smaller than 1536 bytes, it is the path of the file to open. (bytes or unicode string) - if filename is a string longer than 1535 bytes, it is parsed as the content of an OLE file in memory. (bytes type only) - if filename is a file-like object (with read, seek and tell methods), it is parsed as-is. :param write_mode: bool, if True the file is opened in read/write mode instead of read-only by default. (ignored if filename is not a path) """ self.write_mode = write_mode #[PL] check if filename is a string-like or file-like object: # (it is better to check for a read() method) if hasattr(filename, 'read'): #TODO: also check seek and tell methods? # file-like object: use it directly self.fp = filename elif isinstance(filename, bytes) and len(filename) >= MINIMAL_OLEFILE_SIZE: # filename is a bytes string containing the OLE file to be parsed: # convert it to BytesIO self.fp = io.BytesIO(filename) else: # string-like object: filename of file on disk if self.write_mode: # open file in mode 'read with update, binary' # According to https://docs.python.org/2/library/functions.html#open # 'w' would truncate the file, 'a' may only append on some Unixes mode = 'r+b' else: # read-only mode by default mode = 'rb' self.fp = open(filename, mode) # obtain the filesize by using seek and tell, which should work on most # file-like objects: #TODO: do it above, using getsize with filename when possible? #TODO: fix code to fail with clear exception when filesize cannot be obtained filesize=0 self.fp.seek(0, os.SEEK_END) try: filesize = self.fp.tell() finally: self.fp.seek(0) self._filesize = filesize # lists of streams in FAT and MiniFAT, to detect duplicate references # (list of indexes of first sectors of each stream) self._used_streams_fat = [] self._used_streams_minifat = [] header = self.fp.read(512) if len(header) != 512 or header[:8] != MAGIC: self._raise_defect(DEFECT_FATAL, "not an OLE2 structured storage file") # [PL] header structure according to AAF specifications: ##Header ##struct StructuredStorageHeader { // [offset from start (bytes), length (bytes)] ##BYTE _abSig[8]; // [00H,08] {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, ## // 0x1a, 0xe1} for current version ##CLSID _clsid; // [08H,16] reserved must be zero (WriteClassStg/ ## // GetClassFile uses root directory class id) ##USHORT _uMinorVersion; // [18H,02] minor version of the format: 33 is ## // written by reference implementation ##USHORT _uDllVersion; // [1AH,02] major version of the dll/format: 3 for ## // 512-byte sectors, 4 for 4 KB sectors ##USHORT _uByteOrder; // [1CH,02] 0xFFFE: indicates Intel byte-ordering ##USHORT _uSectorShift; // [1EH,02] size of sectors in power-of-two; ## // typically 9 indicating 512-byte sectors ##USHORT _uMiniSectorShift; // [20H,02] size of mini-sectors in power-of-two; ## // typically 6 indicating 64-byte mini-sectors ##USHORT _usReserved; // [22H,02] reserved, must be zero ##ULONG _ulReserved1; // [24H,04] reserved, must be zero ##FSINDEX _csectDir; // [28H,04] must be zero for 512-byte sectors, ## // number of SECTs in directory chain for 4 KB ## // sectors ##FSINDEX _csectFat; // [2CH,04] number of SECTs in the FAT chain ##SECT _sectDirStart; // [30H,04] first SECT in the directory chain ##DFSIGNATURE _signature; // [34H,04] signature used for transactions; must ## // be zero. The reference implementation ## // does not support transactions ##ULONG _ulMiniSectorCutoff; // [38H,04] maximum size for a mini stream; ## // typically 4096 bytes ##SECT _sectMiniFatStart; // [3CH,04] first SECT in the MiniFAT chain ##FSINDEX _csectMiniFat; // [40H,04] number of SECTs in the MiniFAT chain ##SECT _sectDifStart; // [44H,04] first SECT in the DIFAT chain ##FSINDEX _csectDif; // [48H,04] number of SECTs in the DIFAT chain ##SECT _sectFat[109]; // [4CH,436] the SECTs of first 109 FAT sectors ##}; # [PL] header decoding: # '<' indicates little-endian byte ordering for Intel (cf. struct module help) fmt_header = '<8s16sHHHHHHLLLLLLLLLL' header_size = struct.calcsize(fmt_header) debug( "fmt_header size = %d, +FAT = %d" % (header_size, header_size + 109*4) ) header1 = header[:header_size] ( self.Sig, self.clsid, self.MinorVersion, self.DllVersion, self.ByteOrder, self.SectorShift, self.MiniSectorShift, self.Reserved, self.Reserved1, self.csectDir, self.csectFat, self.sectDirStart, self.signature, self.MiniSectorCutoff, self.MiniFatStart, self.csectMiniFat, self.sectDifStart, self.csectDif ) = struct.unpack(fmt_header, header1) debug( struct.unpack(fmt_header, header1)) if self.Sig != MAGIC: # OLE signature should always be present self._raise_defect(DEFECT_FATAL, "incorrect OLE signature") if self.clsid != bytearray(16): # according to AAF specs, CLSID should always be zero self._raise_defect(DEFECT_INCORRECT, "incorrect CLSID in OLE header") debug( "MinorVersion = %d" % self.MinorVersion ) debug( "DllVersion = %d" % self.DllVersion ) if self.DllVersion not in [3, 4]: # version 3: usual format, 512 bytes per sector # version 4: large format, 4K per sector self._raise_defect(DEFECT_INCORRECT, "incorrect DllVersion in OLE header") debug( "ByteOrder = %X" % self.ByteOrder ) if self.ByteOrder != 0xFFFE: # For now only common little-endian documents are handled correctly self._raise_defect(DEFECT_FATAL, "incorrect ByteOrder in OLE header") # TODO: add big-endian support for documents created on Mac ? # But according to [MS-CFB] ? v20140502, ByteOrder MUST be 0xFFFE. self.SectorSize = 2**self.SectorShift debug( "SectorSize = %d" % self.SectorSize ) if self.SectorSize not in [512, 4096]: self._raise_defect(DEFECT_INCORRECT, "incorrect SectorSize in OLE header") if (self.DllVersion==3 and self.SectorSize!=512) \ or (self.DllVersion==4 and self.SectorSize!=4096): self._raise_defect(DEFECT_INCORRECT, "SectorSize does not match DllVersion in OLE header") self.MiniSectorSize = 2**self.MiniSectorShift debug( "MiniSectorSize = %d" % self.MiniSectorSize ) if self.MiniSectorSize not in [64]: self._raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorSize in OLE header") if self.Reserved != 0 or self.Reserved1 != 0: self._raise_defect(DEFECT_INCORRECT, "incorrect OLE header (non-null reserved bytes)") debug( "csectDir = %d" % self.csectDir ) # Number of directory sectors (only allowed if DllVersion != 3) if self.SectorSize==512 and self.csectDir!=0: self._raise_defect(DEFECT_INCORRECT, "incorrect csectDir in OLE header") debug( "csectFat = %d" % self.csectFat ) # csectFat = number of FAT sectors in the file debug( "sectDirStart = %X" % self.sectDirStart ) # sectDirStart = 1st sector containing the directory debug( "signature = %d" % self.signature ) # Signature should be zero, BUT some implementations do not follow this # rule => only a potential defect: # (according to MS-CFB, may be != 0 for applications supporting file # transactions) if self.signature != 0: self._raise_defect(DEFECT_POTENTIAL, "incorrect OLE header (signature>0)") debug( "MiniSectorCutoff = %d" % self.MiniSectorCutoff ) # MS-CFB: This integer field MUST be set to 0x00001000. This field # specifies the maximum size of a user-defined data stream allocated # from the mini FAT and mini stream, and that cutoff is 4096 bytes. # Any user-defined data stream larger than or equal to this cutoff size # must be allocated as normal sectors from the FAT. if self.MiniSectorCutoff != 0x1000: self._raise_defect(DEFECT_INCORRECT, "incorrect MiniSectorCutoff in OLE header") debug( "MiniFatStart = %X" % self.MiniFatStart ) debug( "csectMiniFat = %d" % self.csectMiniFat ) debug( "sectDifStart = %X" % self.sectDifStart ) debug( "csectDif = %d" % self.csectDif ) # calculate the number of sectors in the file # (-1 because header doesn't count) self.nb_sect = ( (filesize + self.SectorSize-1) // self.SectorSize) - 1 debug( "Number of sectors in the file: %d" % self.nb_sect ) #TODO: change this test, because an OLE file MAY contain other data # after the last sector. # file clsid self.clsid = _clsid(header[8:24]) #TODO: remove redundant attributes, and fix the code which uses them? self.sectorsize = self.SectorSize #1 << i16(header, 30) self.minisectorsize = self.MiniSectorSize #1 << i16(header, 32) self.minisectorcutoff = self.MiniSectorCutoff # i32(header, 56) # check known streams for duplicate references (these are always in FAT, # never in MiniFAT): self._check_duplicate_stream(self.sectDirStart) # check MiniFAT only if it is not empty: if self.csectMiniFat: self._check_duplicate_stream(self.MiniFatStart) # check DIFAT only if it is not empty: if self.csectDif: self._check_duplicate_stream(self.sectDifStart) # Load file allocation tables self.loadfat(header) # Load direcory. This sets both the direntries list (ordered by sid) # and the root (ordered by hierarchy) members. self.loaddirectory(self.sectDirStart)#i32(header, 48)) self.ministream = None self.minifatsect = self.MiniFatStart #i32(header, 60) def close(self): """ close the OLE file, to release the file object """ self.fp.close() def _check_duplicate_stream(self, first_sect, minifat=False): """ Checks if a stream has not been already referenced elsewhere. This method should only be called once for each known stream, and only if stream size is not null. :param first_sect: int, index of first sector of the stream in FAT :param minifat: bool, if True, stream is located in the MiniFAT, else in the FAT """ if minifat: debug('_check_duplicate_stream: sect=%d in MiniFAT' % first_sect) used_streams = self._used_streams_minifat else: debug('_check_duplicate_stream: sect=%d in FAT' % first_sect) # some values can be safely ignored (not a real stream): if first_sect in (DIFSECT,FATSECT,ENDOFCHAIN,FREESECT): return used_streams = self._used_streams_fat #TODO: would it be more efficient using a dict or hash values, instead # of a list of long ? if first_sect in used_streams: self._raise_defect(DEFECT_INCORRECT, 'Stream referenced twice') else: used_streams.append(first_sect) def dumpfat(self, fat, firstindex=0): "Displays a part of FAT in human-readable form for debugging purpose" # [PL] added only for debug if not DEBUG_MODE: return # dictionary to convert special FAT values in human-readable strings VPL = 8 # values per line (8+1 * 8+1 = 81) fatnames = { FREESECT: "..free..", ENDOFCHAIN: "[ END. ]", FATSECT: "FATSECT ", DIFSECT: "DIFSECT " } nbsect = len(fat) nlines = (nbsect+VPL-1)//VPL #print("index", end=" ") #for i in range(VPL): # print("%8X" % i, end=" ") #print() for l in range(nlines): index = l*VPL #print("%8X:" % (firstindex+index), end=" ") for i in range(index, index+VPL): if i>=nbsect: break sect = fat[i] aux = sect & 0xFFFFFFFF # JYTHON-WORKAROUND if aux in fatnames: name = fatnames[aux] else: if sect == i+1: name = " --->" else: name = "%8X" % sect #print(name, end=" ") #print() def dumpsect(self, sector, firstindex=0): "Displays a sector in a human-readable form, for debugging purpose." if not DEBUG_MODE: return VPL=8 # number of values per line (8+1 * 8+1 = 81) tab = array.array(UINT32, sector) if sys.byteorder == 'big': tab.byteswap() nbsect = len(tab) nlines = (nbsect+VPL-1)//VPL #print("index", end=" ") # for i in range(VPL): # print("%8X" % i, end=" ") # print() for l in range(nlines): index = l*VPL #print("%8X:" % (firstindex+index), end=" ") for i in range(index, index+VPL): if i>=nbsect: break sect = tab[i] name = "%8X" % sect # print(name, end=" ") # print() def sect2array(self, sect): """ convert a sector to an array of 32 bits unsigned integers, swapping bytes on big endian CPUs such as PowerPC (old Macs) """ a = array.array(UINT32, sect) # if CPU is big endian, swap bytes: if sys.byteorder == 'big': a.byteswap() return a def loadfat_sect(self, sect): """ Adds the indexes of the given sector to the FAT :param sect: string containing the first FAT sector, or array of long integers :returns: index of last FAT sector. """ # a FAT sector is an array of ulong integers. if isinstance(sect, array.array): # if sect is already an array it is directly used fat1 = sect else: # if it's a raw sector, it is parsed in an array fat1 = self.sect2array(sect) self.dumpsect(sect) # The FAT is a sector chain starting at the first index of itself. for isect in fat1: isect = isect & 0xFFFFFFFF # JYTHON-WORKAROUND debug("isect = %X" % isect) if isect == ENDOFCHAIN or isect == FREESECT: # the end of the sector chain has been reached debug("found end of sector chain") break # read the FAT sector s = self.getsect(isect) # parse it as an array of 32 bits integers, and add it to the # global FAT array nextfat = self.sect2array(s) self.fat = self.fat + nextfat return isect def loadfat(self, header): """ Load the FAT table. """ # The 1st sector of the file contains sector numbers for the first 109 # FAT sectors, right after the header which is 76 bytes long. # (always 109, whatever the sector size: 512 bytes = 76+4*109) # Additional sectors are described by DIF blocks sect = header[76:512] debug( "len(sect)=%d, so %d integers" % (len(sect), len(sect)//4) ) #fat = [] # [PL] FAT is an array of 32 bits unsigned ints, it's more effective # to use an array than a list in Python. # It's initialized as empty first: self.fat = array.array(UINT32) self.loadfat_sect(sect) #self.dumpfat(self.fat) ## for i in range(0, len(sect), 4): ## ix = i32(sect, i) ## #[PL] if ix == -2 or ix == -1: # ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: ## if ix == 0xFFFFFFFE or ix == 0xFFFFFFFF: ## break ## s = self.getsect(ix) ## #fat = fat + [i32(s, i) for i in range(0, len(s), 4)] ## fat = fat + array.array(UINT32, s) if self.csectDif != 0: # [PL] There's a DIFAT because file is larger than 6.8MB # some checks just in case: if self.csectFat <= 109: # there must be at least 109 blocks in header and the rest in # DIFAT, so number of sectors must be >109. self._raise_defect(DEFECT_INCORRECT, 'incorrect DIFAT, not enough sectors') if self.sectDifStart >= self.nb_sect: # initial DIFAT block index must be valid self._raise_defect(DEFECT_FATAL, 'incorrect DIFAT, first index out of range') debug( "DIFAT analysis..." ) # We compute the necessary number of DIFAT sectors : # Number of pointers per DIFAT sector = (sectorsize/4)-1 # (-1 because the last pointer is the next DIFAT sector number) nb_difat_sectors = (self.sectorsize//4)-1 # (if 512 bytes: each DIFAT sector = 127 pointers + 1 towards next DIFAT sector) nb_difat = (self.csectFat-109 + nb_difat_sectors-1)//nb_difat_sectors debug( "nb_difat = %d" % nb_difat ) if self.csectDif != nb_difat: raise IOError('incorrect DIFAT') isect_difat = self.sectDifStart for i in iterrange(nb_difat): debug( "DIFAT block %d, sector %X" % (i, isect_difat) ) #TODO: check if corresponding FAT SID = DIFSECT sector_difat = self.getsect(isect_difat) difat = self.sect2array(sector_difat) self.dumpsect(sector_difat) self.loadfat_sect(difat[:nb_difat_sectors]) # last DIFAT pointer is next DIFAT sector: isect_difat = difat[nb_difat_sectors] debug( "next DIFAT sector: %X" % isect_difat ) # checks: if isect_difat not in [ENDOFCHAIN, FREESECT]: # last DIFAT pointer value must be ENDOFCHAIN or FREESECT raise IOError('incorrect end of DIFAT') ## if len(self.fat) != self.csectFat: ## # FAT should contain csectFat blocks ## print("FAT length: %d instead of %d" % (len(self.fat), self.csectFat)) ## raise IOError('incorrect DIFAT') # since FAT is read from fixed-size sectors, it may contain more values # than the actual number of sectors in the file. # Keep only the relevant sector indexes: if len(self.fat) > self.nb_sect: debug('len(fat)=%d, shrunk to nb_sect=%d' % (len(self.fat), self.nb_sect)) self.fat = self.fat[:self.nb_sect] debug('\nFAT:') self.dumpfat(self.fat) def loadminifat(self): """ Load the MiniFAT table. """ # MiniFAT is stored in a standard sub-stream, pointed to by a header # field. # NOTE: there are two sizes to take into account for this stream: # 1) Stream size is calculated according to the number of sectors # declared in the OLE header. This allocated stream may be more than # needed to store the actual sector indexes. # (self.csectMiniFat is the number of sectors of size self.SectorSize) stream_size = self.csectMiniFat * self.SectorSize # 2) Actually used size is calculated by dividing the MiniStream size # (given by root entry size) by the size of mini sectors, *4 for # 32 bits indexes: nb_minisectors = (self.root.size + self.MiniSectorSize-1) // self.MiniSectorSize used_size = nb_minisectors * 4 debug('loadminifat(): minifatsect=%d, nb FAT sectors=%d, used_size=%d, stream_size=%d, nb MiniSectors=%d' % (self.minifatsect, self.csectMiniFat, used_size, stream_size, nb_minisectors)) if used_size > stream_size: # This is not really a problem, but may indicate a wrong implementation: self._raise_defect(DEFECT_INCORRECT, 'OLE MiniStream is larger than MiniFAT') # In any case, first read stream_size: s = self._open(self.minifatsect, stream_size, force_FAT=True).read() #[PL] Old code replaced by an array: #self.minifat = [i32(s, i) for i in range(0, len(s), 4)] self.minifat = self.sect2array(s) # Then shrink the array to used size, to avoid indexes out of MiniStream: debug('MiniFAT shrunk from %d to %d sectors' % (len(self.minifat), nb_minisectors)) self.minifat = self.minifat[:nb_minisectors] debug('loadminifat(): len=%d' % len(self.minifat)) debug('\nMiniFAT:') self.dumpfat(self.minifat) def getsect(self, sect): """ Read given sector from file on disk. :param sect: int, sector index :returns: a string containing the sector data. """ # From [MS-CFB]: A sector number can be converted into a byte offset # into the file by using the following formula: # (sector number + 1) x Sector Size. # This implies that sector #0 of the file begins at byte offset Sector # Size, not at 0. # [PL] the original code in PIL was wrong when sectors are 4KB instead of # 512 bytes: #self.fp.seek(512 + self.sectorsize * sect) #[PL]: added safety checks: #print("getsect(%X)" % sect) try: self.fp.seek(self.sectorsize * (sect+1)) except: debug('getsect(): sect=%X, seek=%d, filesize=%d' % (sect, self.sectorsize*(sect+1), self._filesize)) self._raise_defect(DEFECT_FATAL, 'OLE sector index out of range') sector = self.fp.read(self.sectorsize) if len(sector) != self.sectorsize: debug('getsect(): sect=%X, read=%d, sectorsize=%d' % (sect, len(sector), self.sectorsize)) self._raise_defect(DEFECT_FATAL, 'incomplete OLE sector') return sector def write_sect(self, sect, data, padding=b'\x00'): """ Write given sector to file on disk. :param sect: int, sector index :param data: bytes, sector data :param padding: single byte, padding character if data < sector size """ if not isinstance(data, bytes): raise TypeError("write_sect: data must be a bytes string") if not isinstance(padding, bytes) or len(padding)!=1: raise TypeError("write_sect: padding must be a bytes string of 1 char") #TODO: we could allow padding=None for no padding at all try: self.fp.seek(self.sectorsize * (sect+1)) except: debug('write_sect(): sect=%X, seek=%d, filesize=%d' % (sect, self.sectorsize*(sect+1), self._filesize)) self._raise_defect(DEFECT_FATAL, 'OLE sector index out of range') if len(data) < self.sectorsize: # add padding data += padding * (self.sectorsize - len(data)) elif len(data) < self.sectorsize: raise ValueError("Data is larger than sector size") self.fp.write(data) def loaddirectory(self, sect): """ Load the directory. :param sect: sector index of directory stream. """ # The directory is stored in a standard # substream, independent of its size. # open directory stream as a read-only file: # (stream size is not known in advance) self.directory_fp = self._open(sect) #[PL] to detect malformed documents and avoid DoS attacks, the maximum # number of directory entries can be calculated: max_entries = self.directory_fp.size // 128 debug('loaddirectory: size=%d, max_entries=%d' % (self.directory_fp.size, max_entries)) # Create list of directory entries #self.direntries = [] # We start with a list of "None" object self.direntries = [None] * max_entries ## for sid in iterrange(max_entries): ## entry = fp.read(128) ## if not entry: ## break ## self.direntries.append(_OleDirectoryEntry(entry, sid, self)) # load root entry: root_entry = self._load_direntry(0) # Root entry is the first entry: self.root = self.direntries[0] # read and build all storage trees, starting from the root: self.root.build_storage_tree() def _load_direntry (self, sid): """ Load a directory entry from the directory. This method should only be called once for each storage/stream when loading the directory. :param sid: index of storage/stream in the directory. :returns: a _OleDirectoryEntry object :exception IOError: if the entry has always been referenced. """ # check if SID is OK: if sid<0 or sid>=len(self.direntries): self._raise_defect(DEFECT_FATAL, "OLE directory index out of range") # check if entry was already referenced: if self.direntries[sid] is not None: self._raise_defect(DEFECT_INCORRECT, "double reference for OLE stream/storage") # if exception not raised, return the object return self.direntries[sid] self.directory_fp.seek(sid * 128) entry = self.directory_fp.read(128) self.direntries[sid] = _OleDirectoryEntry(entry, sid, self) return self.direntries[sid] def dumpdirectory(self): """ Dump directory (for debugging only) """ self.root.dump() def _open(self, start, size = 0x7FFFFFFF, force_FAT=False): """ Open a stream, either in FAT or MiniFAT according to its size. (openstream helper) :param start: index of first sector :param size: size of stream (or nothing if size is unknown) :param force_FAT: if False (default), stream will be opened in FAT or MiniFAT according to size. If True, it will always be opened in FAT. """ debug('OleFileIO.open(): sect=%d, size=%d, force_FAT=%s' % (start, size, str(force_FAT))) # stream size is compared to the MiniSectorCutoff threshold: if size < self.minisectorcutoff and not force_FAT: # ministream object if not self.ministream: # load MiniFAT if it wasn't already done: self.loadminifat() # The first sector index of the miniFAT stream is stored in the # root directory entry: size_ministream = self.root.size debug('Opening MiniStream: sect=%d, size=%d' % (self.root.isectStart, size_ministream)) self.ministream = self._open(self.root.isectStart, size_ministream, force_FAT=True) return _OleStream(fp=self.ministream, sect=start, size=size, offset=0, sectorsize=self.minisectorsize, fat=self.minifat, filesize=self.ministream.size) else: # standard stream return _OleStream(fp=self.fp, sect=start, size=size, offset=self.sectorsize, sectorsize=self.sectorsize, fat=self.fat, filesize=self._filesize) def _list(self, files, prefix, node, streams=True, storages=False): """ listdir helper :param files: list of files to fill in :param prefix: current location in storage tree (list of names) :param node: current node (_OleDirectoryEntry object) :param streams: bool, include streams if True (True by default) - new in v0.26 :param storages: bool, include storages if True (False by default) - new in v0.26 (note: the root storage is never included) """ prefix = prefix + [node.name] for entry in node.kids: if entry.entry_type == STGTY_STORAGE: # this is a storage if storages: # add it to the list files.append(prefix[1:] + [entry.name]) # check its kids self._list(files, prefix, entry, streams, storages) elif entry.entry_type == STGTY_STREAM: # this is a stream if streams: # add it to the list files.append(prefix[1:] + [entry.name]) else: self._raise_defect(DEFECT_INCORRECT, 'The directory tree contains an entry which is not a stream nor a storage.') def listdir(self, streams=True, storages=False): """ Return a list of streams and/or storages stored in this file :param streams: bool, include streams if True (True by default) - new in v0.26 :param storages: bool, include storages if True (False by default) - new in v0.26 (note: the root storage is never included) :returns: list of stream and/or storage paths """ files = [] self._list(files, [], self.root, streams, storages) return files def _find(self, filename): """ Returns directory entry of given filename. (openstream helper) Note: this method is case-insensitive. :param filename: path of stream in storage tree (except root entry), either: - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - or a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] :returns: sid of requested filename :exception IOError: if file not found """ # if filename is a string instead of a list, split it on slashes to # convert to a list: if isinstance(filename, basestring): filename = filename.split('/') # walk across storage tree, following given path: node = self.root for name in filename: for kid in node.kids: if kid.name.lower() == name.lower(): break else: raise IOError("file not found") node = kid return node.sid def openstream(self, filename): """ Open a stream as a read-only file object (BytesIO). Note: filename is case-insensitive. :param filename: path of stream in storage tree (except root entry), either: - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - or a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] :returns: file object (read-only) :exception IOError: if filename not found, or if this is not a stream. """ sid = self._find(filename) entry = self.direntries[sid] if entry.entry_type != STGTY_STREAM: raise IOError("this file is not a stream") return self._open(entry.isectStart, entry.size) def write_stream(self, stream_name, data): """ Write a stream to disk. For now, it is only possible to replace an existing stream by data of the same size. :param stream_name: path of stream in storage tree (except root entry), either: - a string using Unix path syntax, for example: 'storage_1/storage_1.2/stream' - or a list of storage filenames, path to the desired stream/storage. Example: ['storage_1', 'storage_1.2', 'stream'] :param data: bytes, data to be written, must be the same size as the original stream. """ if not isinstance(data, bytes): raise TypeError("write_stream: data must be a bytes string") sid = self._find(stream_name) entry = self.direntries[sid] if entry.entry_type != STGTY_STREAM: raise IOError("this is not a stream") size = entry.size if size != len(data): raise ValueError("write_stream: data must be the same size as the existing stream") if size < self.minisectorcutoff: raise NotImplementedError("Writing a stream in MiniFAT is not implemented yet") sect = entry.isectStart # number of sectors to write nb_sectors = (size + (self.sectorsize-1)) // self.sectorsize debug('nb_sectors = %d' % nb_sectors) for i in range(nb_sectors): ## try: ## self.fp.seek(offset + self.sectorsize * sect) ## except: ## debug('sect=%d, seek=%d' % ## (sect, offset+self.sectorsize*sect)) ## raise IOError('OLE sector index out of range') # extract one sector from data, the last one being smaller: if i<(nb_sectors-1): data_sector = data [i*self.sectorsize : (i+1)*self.sectorsize] #TODO: comment this if it works assert(len(data_sector)==self.sectorsize) else: data_sector = data [i*self.sectorsize:] #TODO: comment this if it works debug('write_stream: size=%d sectorsize=%d data_sector=%d size%%sectorsize=%d' % (size, self.sectorsize, len(data_sector), size % self.sectorsize)) assert(len(data_sector) % self.sectorsize==size % self.sectorsize) self.write_sect(sect, data_sector) ## self.fp.write(data_sector) # jump to next sector in the FAT: try: sect = self.fat[sect] except IndexError: # [PL] if pointer is out of the FAT an exception is raised raise IOError('incorrect OLE FAT, sector index out of range') #[PL] Last sector should be a "end of chain" marker: if sect != ENDOFCHAIN: raise IOError('incorrect last sector index in OLE stream') def get_type(self, filename): """ Test if given filename exists as a stream or a storage in the OLE container, and return its type. :param filename: path of stream in storage tree. (see openstream for syntax) :returns: False if object does not exist, its entry type (>0) otherwise: - STGTY_STREAM: a stream - STGTY_STORAGE: a storage - STGTY_ROOT: the root entry """ try: sid = self._find(filename) entry = self.direntries[sid] return entry.entry_type except: return False def getmtime(self, filename): """ Return modification time of a stream/storage. :param filename: path of stream/storage in storage tree. (see openstream for syntax) :returns: None if modification time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 """ sid = self._find(filename) entry = self.direntries[sid] return entry.getmtime() def getctime(self, filename): """ Return creation time of a stream/storage. :param filename: path of stream/storage in storage tree. (see openstream for syntax) :returns: None if creation time is null, a python datetime object otherwise (UTC timezone) new in version 0.26 """ sid = self._find(filename) entry = self.direntries[sid] return entry.getctime() def exists(self, filename): """ Test if given filename exists as a stream or a storage in the OLE container. Note: filename is case-insensitive. :param filename: path of stream in storage tree. (see openstream for syntax) :returns: True if object exist, else False. """ try: sid = self._find(filename) return True except: return False def get_size(self, filename): """ Return size of a stream in the OLE container, in bytes. :param filename: path of stream in storage tree (see openstream for syntax) :returns: size in bytes (long integer) :exception IOError: if file not found :exception TypeError: if this is not a stream. """ sid = self._find(filename) entry = self.direntries[sid] if entry.entry_type != STGTY_STREAM: #TODO: Should it return zero instead of raising an exception ? raise TypeError('object is not an OLE stream') return entry.size def get_rootentry_name(self): """ Return root entry name. Should usually be 'Root Entry' or 'R' in most implementations. """ return self.root.name def getproperties(self, filename, convert_time=False, no_conversion=None): """ Return properties described in substream. :param filename: path of stream in storage tree (see openstream for syntax) :param convert_time: bool, if True timestamps will be converted to Python datetime :param no_conversion: None or list of int, timestamps not to be converted (for example total editing time is not a real timestamp) :returns: a dictionary of values indexed by id (integer) """ #REFERENCE: [MS-OLEPS] https://msdn.microsoft.com/en-us/library/dd942421.aspx # make sure no_conversion is a list, just to simplify code below: if no_conversion == None: no_conversion = [] # stream path as a string to report exceptions: streampath = filename if not isinstance(streampath, str): streampath = '/'.join(streampath) fp = self.openstream(filename) data = {} try: # header s = fp.read(28) clsid = _clsid(s[8:24]) # format id s = fp.read(20) fmtid = _clsid(s[:16]) fp.seek(i32(s, 16)) # get section s = b"****" + fp.read(i32(fp.read(4))-4) # number of properties: num_props = i32(s, 4) except BaseException as exc: # catch exception while parsing property header, and only raise # a DEFECT_INCORRECT then return an empty dict, because this is not # a fatal error when parsing the whole file msg = 'Error while parsing properties header in stream %s: %s' % ( repr(streampath), exc) self._raise_defect(DEFECT_INCORRECT, msg, type(exc)) return data for i in range(num_props): try: id = 0 # just in case of an exception id = i32(s, 8+i*8) offset = i32(s, 12+i*8) type = i32(s, offset) debug ('property id=%d: type=%d offset=%X' % (id, type, offset)) # test for common types first (should perhaps use # a dictionary instead?) if type == VT_I2: # 16-bit signed integer value = i16(s, offset+4) if value >= 32768: value = value - 65536 elif type == VT_UI2: # 2-byte unsigned integer value = i16(s, offset+4) elif type in (VT_I4, VT_INT, VT_ERROR): # VT_I4: 32-bit signed integer # VT_ERROR: HRESULT, similar to 32-bit signed integer, # see http://msdn.microsoft.com/en-us/library/cc230330.aspx value = i32(s, offset+4) elif type in (VT_UI4, VT_UINT): # 4-byte unsigned integer value = i32(s, offset+4) # FIXME elif type in (VT_BSTR, VT_LPSTR): # CodePageString, see http://msdn.microsoft.com/en-us/library/dd942354.aspx # size is a 32 bits integer, including the null terminator, and # possibly trailing or embedded null chars #TODO: if codepage is unicode, the string should be converted as such count = i32(s, offset+4) value = s[offset+8:offset+8+count-1] # remove all null chars: value = value.replace(b'\x00', b'') elif type == VT_BLOB: # binary large object (BLOB) # see http://msdn.microsoft.com/en-us/library/dd942282.aspx count = i32(s, offset+4) value = s[offset+8:offset+8+count] elif type == VT_LPWSTR: # UnicodeString # see http://msdn.microsoft.com/en-us/library/dd942313.aspx # "the string should NOT contain embedded or additional trailing # null characters." count = i32(s, offset+4) value = self._decode_utf16_str(s[offset+8:offset+8+count*2]) elif type == VT_FILETIME: value = long(i32(s, offset+4)) + (long(i32(s, offset+8))<<32) # FILETIME is a 64-bit int: "number of 100ns periods # since Jan 1,1601". if convert_time and id not in no_conversion: debug('Converting property #%d to python datetime, value=%d=%fs' %(id, value, float(value)/10000000)) # convert FILETIME to Python datetime.datetime # inspired from http://code.activestate.com/recipes/511425-filetime-to-datetime/ _FILETIME_null_date = datetime.datetime(1601, 1, 1, 0, 0, 0) debug('timedelta days=%d' % (value//(10*1000000*3600*24))) value = _FILETIME_null_date + datetime.timedelta(microseconds=value//10) else: # legacy code kept for backward compatibility: returns a # number of seconds since Jan 1,1601 value = value // 10000000 # seconds elif type == VT_UI1: # 1-byte unsigned integer value = i8(s[offset+4]) elif type == VT_CLSID: value = _clsid(s[offset+4:offset+20]) elif type == VT_CF: # PropertyIdentifier or ClipboardData?? # see http://msdn.microsoft.com/en-us/library/dd941945.aspx count = i32(s, offset+4) value = s[offset+8:offset+8+count] elif type == VT_BOOL: # VARIANT_BOOL, 16 bits bool, 0x0000=Fals, 0xFFFF=True # see http://msdn.microsoft.com/en-us/library/cc237864.aspx value = bool(i16(s, offset+4)) else: value = None # everything else yields "None" debug ('property id=%d: type=%d not implemented in parser yet' % (id, type)) # missing: VT_EMPTY, VT_NULL, VT_R4, VT_R8, VT_CY, VT_DATE, # VT_DECIMAL, VT_I1, VT_I8, VT_UI8, # see http://msdn.microsoft.com/en-us/library/dd942033.aspx # FIXME: add support for VT_VECTOR # VT_VECTOR is a 32 uint giving the number of items, followed by # the items in sequence. The VT_VECTOR value is combined with the # type of items, e.g. VT_VECTOR|VT_BSTR # see http://msdn.microsoft.com/en-us/library/dd942011.aspx #print("%08x" % id, repr(value), end=" ") #print("(%s)" % VT[i32(s, offset) & 0xFFF]) data[id] = value except BaseException as exc: # catch exception while parsing each property, and only raise # a DEFECT_INCORRECT, because parsing can go on msg = 'Error while parsing property id %d in stream %s: %s' % ( id, repr(streampath), exc) self._raise_defect(DEFECT_INCORRECT, msg, type(exc)) return data def get_metadata(self): """ Parse standard properties streams, return an OleMetadata object containing all the available metadata. (also stored in the metadata attribute of the OleFileIO object) new in version 0.25 """ self.metadata = OleMetadata() self.metadata.parse_properties(self) return self.metadata # # -------------------------------------------------------------------- # This script can be used to dump the directory of any OLE2 structured # storage file. if __name__ == "__main__": import sys # [PL] display quick usage info if launched from command-line if len(sys.argv) <= 1: # print('olefile version %s %s - %s' % (__version__, __date__, __author__)) # print( # """ # Launched from the command line, this script parses OLE files and prints info. # # Usage: olefile.py [-d] [-c] [file2 ...] # # Options: # -d : debug mode (displays a lot of debug information, for developers only) # -c : check all streams (for debugging purposes) # # For more information, see http://www.decalage.info/olefile # """) sys.exit() check_streams = False for filename in sys.argv[1:]: ## try: # OPTIONS: if filename == '-d': # option to switch debug mode on: set_debug_mode(True) continue if filename == '-c': # option to switch check streams mode on: check_streams = True continue ole = OleFileIO(filename)#, raise_defects=DEFECT_INCORRECT) print("-" * 68) print(filename) print("-" * 68) ole.dumpdirectory() for streamname in ole.listdir(): if streamname[-1][0] == "\005": print(streamname, ": properties") props = ole.getproperties(streamname, convert_time=True) props = sorted(props.items()) for k, v in props: #[PL]: avoid to display too large or binary values: if isinstance(v, (basestring, bytes)): if len(v) > 50: v = v[:50] if isinstance(v, bytes): # quick and dirty binary check: for c in (1,2,3,4,5,6,7,11,12,14,15,16,17,18,19,20, 21,22,23,24,25,26,27,28,29,30,31): if c in bytearray(v): v = '(binary data)' break print(" ", k, v) if check_streams: # Read all streams to check if there are errors: #print('\nChecking streams...') for streamname in ole.listdir(): # print name using repr() to convert binary chars to \xNN: #print('-', repr('/'.join(streamname)),'-', end=' ') st_type = ole.get_type(streamname) if st_type == STGTY_STREAM: print('size %d' % ole.get_size(streamname)) # just try to read stream in memory: ole.openstream(streamname) # else: # print('NOT a stream : type=%d' % st_type) # print() ## for streamname in ole.listdir(): ## # print name using repr() to convert binary chars to \xNN: ## print('-', repr('/'.join(streamname)),'-', end=' ') ## print(ole.getmtime(streamname)) ## print() print('Modification/Creation times of all directory entries:') for entry in ole.direntries: if entry is not None: print('- %s: mtime=%s ctime=%s' % (entry.name, entry.getmtime(), entry.getctime())) print() # parse and display metadata: meta = ole.get_metadata() meta.dump() print() #[PL] Test a few new methods: root = ole.get_rootentry_name() print('Root entry name: "%s"' % root) if ole.exists('worddocument'): print("This is a Word document.") print("type of stream 'WordDocument':", ole.get_type('worddocument')) print("size :", ole.get_size('worddocument')) if ole.exists('macros/vba'): print("This document may contain VBA macros.") # print parsing issues: print('\nNon-fatal issues raised during parsing:') if ole.parsing_issues: for exctype, msg in ole.parsing_issues: print('- %s: %s' % (exctype.__name__, msg)) else: print('None') ## except IOError as v: ## print("***", "cannot read", file, "-", v) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/helpers.py0000664000175000017500000001216214700316175017564 0ustar00wattswattsdef resource_path(relative_path): import os, sys """ Get absolute path to resource, works for dev and for PyInstaller """ base_path = getattr(sys, '_MEIPASS', os.path.dirname(__file__)) return os.path.join(base_path, relative_path) def check_for_updates(current_version): import urllib.request, re from urllib.error import URLError from importlib.metadata import version as version_check from packaging.version import parse as parse_version timeout=5 message = 'Socket timed out. Check your internet connection.\nVersion checks skipped.' # Scrape the version string from the PyPI:mantis-xray RSS feed try: with urllib.request.urlopen("https://pypi.org/rss/project/mantis_xray/releases.xml", timeout=timeout) as init_file: pypi_rss = init_file.read() #print(pypi_rss) pypi_list = re.findall(r"(?:\\s*)([\d\.]+)(?:\s*\)", pypi_rss.decode())[0] print("Latest package on PyPI is version {0}".format(pypi_list)) except URLError: print(message) return except: pass # Scrape version string from the code in the github repository try: with urllib.request.urlopen("https://raw.githubusercontent.com/mlerotic/spectromicroscopy/master/mantis_xray/__init__.py", timeout=timeout) as init_file: github_init = init_file.read() #print(github_init) github_latest = re.search(r"(?:__version__*\s=*\s)['|\"]+([\d\.]+)", github_init.decode()).group(1) print("Current default (master) code is version {0}".format(github_latest)) except URLError: print(message) return except: pass try: with urllib.request.urlopen( "https://raw.githubusercontent.com/mlerotic/spectromicroscopy/development/mantis_xray/__init__.py", timeout=timeout) as init_file: github_init = init_file.read() # print(github_init) github_latest = re.search(r"(?:__version__*\s=*\s)['|\"]+([\d\.]+)", github_init.decode()).group(1) print("Current development code is version {0}".format(github_latest)) except URLError: print(message) return except: pass # PyQt5 & pyqtgraph version check if parse_version(version_check('pyqt5')) >= parse_version('5.15.6'): print("PyQt version in use is {0}".format(version_check("pyqt5"))) else: print("PyQt version in use is {0}. Please consider updating to > 5.15.6 for full functionality.".format(version_check("pyqt5"))) if parse_version(version_check('pyqtgraph')) >= parse_version('0.12.2'): print("PyQtGraph version in use is {0}".format(version_check("pyqtgraph"))) else: print("PyQtGraph version in use is {0}. Please consider updating to > 0.12.2 for full functionality.".format(version_check("pyqtgraph"))) # PDF Exporter adopted from Orange https://orangedatamining.com/ # https://github.com/biolab/orange-widget-base/blob/master/orangewidget/utils/PDFExporter.py # Potential integration into pyqtgraph in the future is under discussion: https://github.com/pyqtgraph/pyqtgraph/issues/1455#issuecomment-734299674 from pyqtgraph.exporters.Exporter import Exporter from PyQt5 import QtCore from PyQt5.QtWidgets import QGraphicsItem, QApplication from PyQt5.QtGui import QPainter, QPdfWriter, QPageSize from PyQt5.QtCore import QMarginsF, Qt, QSizeF, QRectF class PDFExporter(Exporter): """A pdf exporter for pyqtgraph graphs. Based on pyqtgraph's ImageExporter. There is a bug in Qt<5.12 that makes Qt wrongly use a cosmetic pen (QTBUG-68537). Workaround: do not use completely opaque colors. There is also a bug in Qt<5.12 with bold fonts that then remain bold. To see it, save the OWNomogram output.""" def __init__(self, item): Exporter.__init__(self, item) if isinstance(item, QGraphicsItem): scene = item.scene() else: scene = item bgbrush = scene.views()[0].backgroundBrush() bg = bgbrush.color() if bgbrush.style() == Qt.NoBrush: bg.setAlpha(0) self.background = bg def export(self, filename=None): pw = QPdfWriter(filename) dpi = int(QApplication.primaryScreen().logicalDotsPerInch()) pw.setResolution(dpi) pw.setPageMargins(QMarginsF(0, 0, 0, 0)) pw.setPageSize( # Tested with pyqt5-15.5.6 QPageSize(QSizeF(self.getTargetRect().size()) / dpi * 25.4, QPageSize.Millimeter)) painter = QPainter(pw) try: self.setExportMode(True, {'antialias': True, 'background': self.background, 'painter': painter}) painter.setRenderHint(QPainter.Antialiasing, True) if QtCore.QT_VERSION >= 0x050D00: painter.setRenderHint(QPainter.LosslessImageRendering, True) self.getScene().render(painter, QRectF(self.getTargetRect()), QRectF(self.getSourceRect())) finally: self.setExportMode(False) painter.end() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/henke.py0000664000175000017500000006722214700316175017223 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2011 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . ''' ! This is from file COMPOUND.DAT for HENKE. Entries are: ! compound formula density (g/cc) water H2O 1.0 protein H48.6C32.9N8.9O8.9S0.6 1.35 lipid H62.5C31.5O6.3 1.0 nucleosome H42.1C31.9N10.3O13.9P1.6S0.3 1.5 dna H35.5C30.8N11.7O18.9P3.1 1.7 helium He 0.1663e-3 chromatin H49.95C24.64N8.66O15.57P1.07S0.03 1.527 air N78.08O20.95Ar0.93 1.2047e-3 pmma C5H8O2 1.18 nitride Si3N4 3.44 graphite C 2.26 nickel Ni 8.876 beryl Be 1.845 copper Cu 8.96 quartz SiO2 2.20 aluminum Al 2.70 gold Au 19.3 ice H2O 0.92 carbon C 1.0 polystyrene C8H8 1.06 silicon Si 2.33 germanium Ge 5.323 ''' from __future__ import division import numpy as np import scipy.interpolate from xdrlib import * import string #------------------------------------------------------------------------------ class henke: def __init__(self): self.compound_name = [ 'water' , 'protein', 'lipid', 'nucleosome', 'dna', 'helium', 'chromatin', 'air', 'pmma', 'nitride', 'graphite', 'nickel', 'beryl', 'copper', 'quartz', 'aluminum', 'gold', 'ice', 'carbon', 'polystyrene', 'silicon', 'germanium'] self.compound_forumula = ['H2O', 'H48.6C32.9N8.9O8.9S0.6', 'H62.5C31.5O6.3', 'H42.1C31.9N10.3O13.9P1.6S0.3', 'H35.5C30.8N11.7O18.9P3.1', 'He' , 'H49.95C24.64N8.66O15.57P1.07S0.03', 'N78.08O20.95Ar0.93', 'C5H8O2', 'Si3N4', 'C', 'Ni', 'Be', 'Cu', 'SiO2', 'Al', 'Au', 'H2O', 'C', 'C8H8', 'Si', 'Ge'] self.compound_density = [ 1.0, 1.35, 1.0, 1.5, 1.7, 1.66E-04, 1.527, 1.20E-03, 1.18, 3.44, 2.26, 8.876, 1.845, 8.96, 2.2, 2.7, 19.3, 0.92, 1, 1.06, 2.33, 5.323 ] #------------------------------------------------------------------------------ def compound(self, compound_string, density): # Type in a chemical formula and the density. The routine # then figures out the atomic weight in g/mole of "molecules" # and how many of what Z atoms you have. z_array = [] if compound_string in self.compound_name: compound_string = self.compound_forumula[self.compound_name.index(compound_string)] if compound_string in self.compound_forumula: z_array = self.zcompound(compound_string, z_array) atwt = self.zatwt(z_array) return z_array, atwt #------------------------------------------------------------------------------ def zcompound(self, compound_string, z_array, paren_multiplier=False): verbose = False if verbose: print ('compound_string', compound_string) if paren_multiplier == False: z_array = np.zeros(92) paren_multiplier=1. max_z_index=93 last_char_index = len(compound_string) - 1 # If we don't start off with a parenthesis, all we have to do # is strip off the first element and process it. We then # call the routine over again to handle the next part of # the string... if (compound_string[0] != '(') : # Look to see if the string has an element # like "C" or like "He". first_char=compound_string[0] if len(compound_string) > 1: second_char = compound_string[1] else: second_char = '' this_element_name = first_char if ((second_char >= 'a') and (second_char <= 'z')) : this_element_name = this_element_name + second_char num_start_index = 2 else: this_element_name = this_element_name + ' ' num_start_index = 1 if verbose: print ('this_element_name:',this_element_name,', num_start_index:', num_start_index) this_z=0 if this_element_name == 'H ': this_z=1 elif this_element_name == 'He': this_z=2 elif this_element_name == 'Li': this_z=3 elif this_element_name == 'Be': this_z=4 elif this_element_name == 'B ': this_z=5 elif this_element_name == 'C ': this_z=6 elif this_element_name == 'N ': this_z=7 elif this_element_name == 'O ': this_z=8 elif this_element_name == 'F ': this_z=9 elif this_element_name == 'Ne': this_z=10 elif this_element_name == 'Na': this_z=11 elif this_element_name == 'Mg': this_z=12 elif this_element_name == 'Al': this_z=13 elif this_element_name == 'Si': this_z=14 elif this_element_name == 'P ': this_z=15 elif this_element_name == 'S ': this_z=16 elif this_element_name == 'Cl': this_z=17 elif this_element_name == 'Ar': this_z=18 elif this_element_name == 'K ': this_z=19 elif this_element_name == 'Ca': this_z=20 elif this_element_name == 'Sc': this_z=21 elif this_element_name == 'Ti': this_z=22 elif this_element_name == 'V ': this_z=23 elif this_element_name == 'Cr': this_z=24 elif this_element_name == 'Mn': this_z=25 elif this_element_name == 'Fe': this_z=26 elif this_element_name == 'Co': this_z=27 elif this_element_name == 'Ni': this_z=28 elif this_element_name == 'Cu': this_z=29 elif this_element_name == 'Zn': this_z=30 elif this_element_name == 'Ga': this_z=31 elif this_element_name == 'Ge': this_z=32 elif this_element_name == 'As': this_z=33 elif this_element_name == 'Se': this_z=34 elif this_element_name == 'Br': this_z=35 elif this_element_name == 'Kr': this_z=36 elif this_element_name == 'Rb': this_z=37 elif this_element_name == 'Sr': this_z=38 elif this_element_name == 'Y ': this_z=39 elif this_element_name == 'Zr': this_z=40 elif this_element_name == 'Nb': this_z=41 elif this_element_name == 'Mo': this_z=42 elif this_element_name == 'Tc': this_z=43 elif this_element_name == 'Ru': this_z=44 elif this_element_name == 'Rh': this_z=45 elif this_element_name == 'Pd': this_z=46 elif this_element_name == 'Ag': this_z=47 elif this_element_name == 'Cd': this_z=48 elif this_element_name == 'In': this_z=49 elif this_element_name == 'Sn': this_z=50 elif this_element_name == 'Sb': this_z=51 elif this_element_name == 'Te': this_z=52 elif this_element_name == 'I ': this_z=53 elif this_element_name == 'Xe': this_z=54 elif this_element_name == 'Cs': this_z=55 elif this_element_name == 'Ba': this_z=56 elif this_element_name == 'La': this_z=57 elif this_element_name == 'Ce': this_z=58 elif this_element_name == 'Pr': this_z=59 elif this_element_name == 'Nd': this_z=60 elif this_element_name == 'Pm': this_z=61 elif this_element_name == 'Sm': this_z=62 elif this_element_name == 'Eu': this_z=63 elif this_element_name == 'Gd': this_z=64 elif this_element_name == 'Tb': this_z=65 elif this_element_name == 'Dy': this_z=66 elif this_element_name == 'Ho': this_z=67 elif this_element_name == 'Er': this_z=68 elif this_element_name == 'Tm': this_z=69 elif this_element_name == 'Yb': this_z=70 elif this_element_name == 'Lu': this_z=71 elif this_element_name == 'Hf': this_z=72 elif this_element_name == 'Ta': this_z=73 elif this_element_name == 'W ': this_z=74 elif this_element_name == 'Re': this_z=75 elif this_element_name == 'Os': this_z=76 elif this_element_name == 'Ir': this_z=77 elif this_element_name == 'Pt': this_z=78 elif this_element_name == 'Au': this_z=79 elif this_element_name == 'Hg': this_z=80 elif this_element_name == 'Tl': this_z=81 elif this_element_name == 'Pb': this_z=82 elif this_element_name == 'Bi': this_z=83 elif this_element_name == 'Po': this_z=84 elif this_element_name == 'At': this_z=85 elif this_element_name == 'Rn': this_z=86 elif this_element_name == 'Fr': this_z=87 elif this_element_name == 'Ra': this_z=88 elif this_element_name == 'Ac': this_z=89 elif this_element_name == 'Th': this_z=90 elif this_element_name == 'Pa': this_z=91 elif this_element_name == 'U ': this_z=92 else: this_z=0 if (this_z == 0) : print ('zcompound is confused: ',compound_string) compound_string='' z_array = 0 return # Find the next element or parenthesis, as # anything before it must be a number. postnum_index = num_start_index if len(compound_string) > num_start_index+1 : test_char = compound_string[postnum_index] else : test_char = '' while ( ((test_char == '0') or (test_char == '1') or \ (test_char == '2') or (test_char == '2') or \ (test_char == '3') or (test_char == '4') or \ (test_char == '5') or (test_char == '6') or \ (test_char == '7') or (test_char == '8') or \ (test_char == '9') or (test_char == '.')) and \ (postnum_index <= last_char_index) ) : postnum_index=postnum_index+1 if (postnum_index <= last_char_index) : test_char=compound_string[postnum_index] else: test_char='' # is there more? if (num_start_index != postnum_index) : number_string=compound_string[num_start_index:postnum_index] num_multiplier=1. if verbose: print( 'Trying to interpret ',number_string,' as a number.') if (len(number_string) != 0) : num_multiplier = float(number_string) else: num_multiplier=1. # We've handled this element, so pop it into the # matrix and continue. if (this_z <= max_z_index) : z_array[this_z-1] = z_array[this_z-1] + num_multiplier else: print ('zcompound: z_array smaller than ',max_z_index) z_array = 0 return # And deal with what's left remaining_string=compound_string[postnum_index:last_char_index+1] if len(remaining_string) > 0: z_array = self.zcompound(remaining_string,z_array,paren_multiplier=True) return z_array #------------------------------------------------------------------------------ def zatwt(self, z_array): maxz=z_array.size atwt=0. for i in range(maxz): if (z_array[i] != 0.): if i+1 == 1: this_atwt=1.00794 elif i+1 == 2: this_atwt=4.0026 elif i+1 == 3: this_atwt=6.941 elif i+1 == 4: this_atwt=9.01218 elif i+1 == 5: this_atwt=10.81 elif i+1 == 6: this_atwt=12.011 elif i+1 == 7: this_atwt=14.0067 elif i+1 == 8: this_atwt=15.9994 elif i+1 == 9: this_atwt=18.9984 elif i+1 == 10: this_atwt=21.179 elif i+1 == 11: this_atwt=22.98977 elif i+1 == 12: this_atwt=24.305 elif i+1 == 13: this_atwt=26.98154 elif i+1 == 14: this_atwt=28.0855 elif i+1 == 15: this_atwt=30.97376 elif i+1 == 16: this_atwt=32.06 elif i+1 == 17: this_atwt=35.453 elif i+1 == 18: this_atwt=39.948 elif i+1 == 19: this_atwt=39.0983 elif i+1 == 20: this_atwt=40.08 elif i+1 == 21: this_atwt=44.9559 elif i+1 == 22: this_atwt=47.88 elif i+1 == 23: this_atwt=50.9415 elif i+1 == 24: this_atwt=51.996 elif i+1 == 25: this_atwt=54.9380 elif i+1 == 26: this_atwt=55.847 elif i+1 == 27: this_atwt=58.9332 elif i+1 == 28: this_atwt=58.69 elif i+1 == 29: this_atwt=63.546 elif i+1 == 30: this_atwt=65.38 elif i+1 == 31: this_atwt=69.72 elif i+1 == 32: this_atwt=72.59 elif i+1 == 33: this_atwt=74.9216 elif i+1 == 34: this_atwt=78.96 elif i+1 == 35: this_atwt=79.904 elif i+1 == 36: this_atwt=83.80 elif i+1 == 37: this_atwt=85.4678 elif i+1 == 38: this_atwt=87.62 elif i+1 == 39: this_atwt=88.9059 elif i+1 == 40: this_atwt=91.22 elif i+1 == 41: this_atwt=92.9064 elif i+1 == 42: this_atwt=95.94 elif i+1 == 43: this_atwt=98. elif i+1 == 44: this_atwt=101.07 elif i+1 == 45: this_atwt=102.9055 elif i+1 == 46: this_atwt=106.42 elif i+1 == 47: this_atwt=107.8682 elif i+1 == 48: this_atwt=112.41 elif i+1 == 49: this_atwt=114.82 elif i+1 == 50: this_atwt=118.69 elif i+1 == 51: this_atwt=121.75 elif i+1 == 52: this_atwt=127.60 elif i+1 == 53: this_atwt=126.9054 elif i+1 == 54: this_atwt=131.29 elif i+1 == 55: this_atwt=132.9054 elif i+1 == 56: this_atwt=137.33 elif i+1 == 57: this_atwt=138.9055 elif i+1 == 58: this_atwt=140.12 elif i+1 == 59: this_atwt=140.9077 elif i+1 == 60: this_atwt=144.24 elif i+1 == 61: this_atwt=145. elif i+1 == 62: this_atwt=150.36 elif i+1 == 63: this_atwt=151.96 elif i+1 == 64: this_atwt=157.25 elif i+1 == 65: this_atwt=158.9254 elif i+1 == 66: this_atwt=162.5 elif i+1 == 67: this_atwt=164.9304 elif i+1 == 68: this_atwt=167.26 elif i+1 == 69: this_atwt=168.9342 elif i+1 == 70: this_atwt=173.04 elif i+1 == 71: this_atwt=174.967 elif i+1 == 72: this_atwt=178.49 elif i+1 == 73: this_atwt=180.9479 elif i+1 == 74: this_atwt=183.85 elif i+1 == 75: this_atwt=186.207 elif i+1 == 76: this_atwt=190.2 elif i+1 == 77: this_atwt=192.22 elif i+1 == 78: this_atwt=195.08 elif i+1 == 79: this_atwt=196.9665 elif i+1 == 80: this_atwt=200.59 elif i+1 == 81: this_atwt=204.383 elif i+1 == 82: this_atwt=207.2 elif i+1 == 83: this_atwt=208.9804 elif i+1 == 84: this_atwt=209. elif i+1 == 85: this_atwt=210. elif i+1 == 86: this_atwt=222. elif i+1 == 87: this_atwt=223. elif i+1 == 88: this_atwt=226.0254 elif i+1 == 89: this_atwt=227.0278 elif i+1 == 90: this_atwt=232.0381 elif i+1 == 91: this_atwt=231.0359 elif i+1 == 92: this_atwt=238.0289 else: this_atwt=0. atwt=atwt+z_array[i]*this_atwt return atwt #------------------------------------------------------------------------------ def extra(self, ielement = -1): energies, f1, f2, n_extra, energies_extra, f1_extra, f2_extra = self.read(ielement, all = False) if n_extra != 0: energies_all=np.concatenate((energies,energies_extra), axis=0) f1_all=np.concatenate((f1,f1_extra), axis=0) f2_all=np.concatenate((f2,f2_extra), axis=0) sort_order=energies_all.argsort() energies_all=energies_all[sort_order] f1_all=f1_all[sort_order] f2_all=f2_all[sort_order] else: energies_all=energies f1_all=f1 f2_all=f2 return energies, f1, f2, energies_extra, f1_extra, f2_extra #------------------------------------------------------------------------------ def read(self, ielement = -1, all = True): # If we don't specifiy element return all energies if ielement == -1 : all = True verbose = False expected_pos = 0 filename = 'henke.xdr' file = open(str(filename),'rb') # try: # file = open(str(filename),'rb') # except: # print ('Could not open file ', filename) # return -1 if verbose: print ('File: ', filename) buf = file.read() u = Unpacker(buf) if all: n_elements = u.unpack_int() n_energies = u.unpack_int() if verbose: print ('n_energies: ', n_energies) print ('n_elements: ', n_elements) expected_pos = expected_pos+2*4 print ('Actual, expected file position before reading energies: ' ,u.get_position(), expected_pos) energies = u.unpack_farray(n_energies, u.unpack_float) energies = np.array(energies) if verbose: print ('energies: ', energies) f1 = np.zeros((n_elements, n_energies)) f2 = np.zeros((n_elements, n_energies)) this_f1 = np.zeros((n_energies)) this_f2 = np.zeros((n_energies)) if verbose: expected_pos = expected_pos+4*n_energies print ('Actual, expected file position before reading elements: ',u.get_position(), expected_pos) for i_element in range(n_elements): this_f1 = u.unpack_farray(n_energies, u.unpack_float) this_f2 = u.unpack_farray(n_energies, u.unpack_float) f1[i_element, :] = this_f1 f2[i_element, :] = this_f2 if verbose: expected_pos =expected_pos+n_elements*n_energies*2*4 print ('Actual, expected file position before reading n_extra_energies: ', u.get_position(), expected_pos) n_extra_energies = u.unpack_int() if verbose: print ('n_extra_energies: ', n_extra_energies) if verbose: expected_pos = expected_pos+4 print ('Actual, expected file position before reading extras: ',u.get_position(), expected_pos) n_extra = np.zeros((n_elements), dtype = np.int) extra_energies = np.zeros((n_elements, n_extra_energies)) extra_f1 = np.zeros((n_elements, n_extra_energies)) extra_f2 = np.zeros((n_elements, n_extra_energies)) this_n_extra = 0 this_extra_energies = np.zeros((n_extra_energies)) this_extra_f1 = np.zeros((n_extra_energies)) this_extra_f2 = np.zeros((n_extra_energies)) for i_element in range(n_elements): this_n_extra = u.unpack_int() this_extra_energies = u.unpack_farray(n_extra_energies, u.unpack_float) this_extra_f1 = u.unpack_farray(n_extra_energies, u.unpack_float) this_extra_f2 = u.unpack_farray(n_extra_energies, u.unpack_float) n_extra[i_element] = this_n_extra extra_energies[i_element, :] = this_extra_energies extra_f1[i_element, :] = this_extra_f1 extra_f2[i_element, :] = this_extra_f2 else: n_elements = u.unpack_int() n_energies = u.unpack_int() energies = u.unpack_farray(n_energies, u.unpack_float) energies = np.array(energies) if verbose: print ('energies: ', energies) byte_offset = 4+4+4*n_energies + 8*ielement*n_energies u.set_position(byte_offset) f1 = u.unpack_farray(n_energies, u.unpack_float) f2 = u.unpack_farray(n_energies, u.unpack_float) byte_offset = 4+4+4*n_energies + 8*n_elements*n_energies u.set_position(byte_offset) n_extra_energies = u.unpack_int() if verbose: print ('n_extra_energies ', n_extra_energies) # Now we have the above plus i_element times the quantity: # (2 for n_extra, and n_extra_energies each of three floats) byte_offset = long(4)+long(4)+long(4)*n_energies + long(8)*n_elements*n_energies + long(4) + ielement*(4+12*n_extra_energies) u.set_position(byte_offset) n_extra = u.unpack_int() this_extra_energies = u.unpack_farray(n_extra_energies, u.unpack_float) this_extra_f1 = u.unpack_farray(n_extra_energies, u.unpack_float) this_extra_f2 = u.unpack_farray(n_extra_energies, u.unpack_float) extra_energies = this_extra_energies[0:n_extra] extra_f1 = this_extra_f1[0:n_extra] extra_f2 = this_extra_f2[0:n_extra] file.close() return energies, f1, f2, n_extra, extra_energies, extra_f1, extra_f2 #------------------------------------------------------------------------------ def array(self, compound_name, density, graze_mrad = 0): z_array, atwt = self.compound(compound_name,density) maxz = 92 first_time = 1 for i in range(maxz): if (z_array[i] != 0.): energies, this_f1, this_f2, n_extra, extra_energies, extra_f1, extra_f2 = self.read(ielement=i) if (first_time == 1) : f1 = z_array[i]*this_f1 f2 = z_array[i]*this_f2 first_time = 0 else: f1 = f1+z_array[i]*this_f1 f2 = f2+z_array[i]*this_f2 num_energies = len(energies) AVOGADRO=6.02204531e23 HC_ANGSTROMS=12398.52 RE=2.817938070e-13 # in cm if (atwt != 0.0) : molecules_per_cc = density * AVOGADRO / atwt else: molecules_per_cc = 0.0 wavelength_angstroms = HC_ANGSTROMS/energies # This constant has wavelength in angstroms and then # they are converted to centimeters. constant = RE * (1.0e-16 * wavelength_angstroms * wavelength_angstroms) * molecules_per_cc / (2.0 * np.pi) delta = constant * f1 beta = constant * f2 # Alpha is in inverse meters squared alpha = 1.e4 * density * AVOGADRO * RE / (2.*np.pi*atwt) #alpha = alpha[0] if (graze_mrad == 0.): reflect=np.ones((num_energies)) else: theta = 1.0e-3 * graze_mrad sinth = np.sin(theta) sinth2 = sinth * sinth coscot = np.cos(theta) coscot = coscot * coscot / sinth alpha = 2.0 * delta - delta * delta + beta * beta gamma = 2.0 * (1.0 - delta) * beta rhosq = 0.5 * (sinth2 - alpha + np.sqrt((sinth2 - alpha)*(sinth2-alpha) + gamma*gamma) ) rho = np.sqrt(rhosq) i_sigma = (4.0 * rhosq * (sinth - rho) * (sinth - rho) + \ gamma * gamma) / \ (4.0 * rhosq * (sinth + rho) * (sinth + rho) + \ gamma * gamma) piosig = (4.0 * rhosq * (rho - coscot) * (rho - coscot) + \ gamma * gamma) / \ (4.0 * rhosq * (rho + coscot) * (rho + coscot) + \ gamma * gamma) reflect= 50.0 * i_sigma * (1 + piosig) denom = energies*4.*np.pi*beta zeroes = np.where(denom == 0.) nonzeroes = np.where(denom != 0.) denom[zeroes] = 1e-8 inverse_mu = np.array((len(energies))) inverse_mu = 1.239852/denom if (len(zeroes) > 0) : inverse_mu[zeroes] = np.inf return energies, f1, f2, delta, beta, graze_mrad, reflect, inverse_mu, atwt, alpha #------------------------------------------------------------------------------ def dose_calc(self, stack, i_composition, od_spectrum, i0_signal, dosecalc_detector_eff): pix_nm_squared = 1.e6*(stack.x_dist[1]-stack.x_dist[0])*(stack.y_dist[1]-stack.y_dist[0]) dose = 0. #rho doesn't matter here rho = 1. henke_energies, f1, f2, delta, beta, graze_mrad, reflect, inverse_mu, atwt, alpha = self.array(i_composition,rho) func_f2_array = scipy.interpolate.interp1d(henke_energies, f2, bounds_error=False, fill_value=0.0) f2_array = func_f2_array(stack.ev) # This is the scaling of f2_array as described in pca_gui_man.tex i_max_ev = np.argmax(stack.ev) if stack.data_dwell == None: stack.data_dwell = 1. if (od_spectrum[i_max_ev] != 0.) : f2_array = f2_array[0,i_max_ev]*(stack.ev/stack.ev[i_max_ev])*(od_spectrum/od_spectrum[i_max_ev]) # See the documentation file pca_gui_man.tex. Is i0_signal # normalized to 1 sec? Does not seem to be the case! Might # have to change it at some point. dose = 6.74e5*(1./(dosecalc_detector_eff* pix_nm_squared*atwt))*np.sum(i0_signal*1e-3*stack.data_dwell *f2_array) return dose ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/henke.xdr0000644000175000017500000137140014332463747017374 0ustar00wattswatts\A A"SA%7A'A*A-]dA0+kA3A5A8#A;خA>AAAEAHL0AK7AN,AR,AUAYA\A` AcAgiAk($AnArAvAzA~ffAAuAA}AA rAF AAA0A:AArA|A|PAAAYAAΥA7AqAAT,ABA>AE9AYAyAǧAA)AA ATA,AbAcAkAffA0A DAAAB )B!|B@Bh>B B ңBBaBB?BBBq[B B!^B$(>B&B)B,@B/ B1ބB4B7kB:B=B@BCBGBJ:BMOBP&BT3BWB[VB^BbDBeBiBmlBqDBu+By"4B})BBBBB'B_BBB>BBBu2BBvFBfB BFB$BByBL"B*=B9B KB BdB6B_!Bɔ{B B'mBӅB'BkB3B=B0!B`ByB}qBaHBTBYC7 CICeCCC C ,CtCƨC"NCCCpbCC C#^C%C(lC+%`C-=C0C3C6}C9q'CBDAVwDDvDG{DJDN'DQ|DT!DXQD[7D__mDbZDf1DjcDDn-DrDuDy{D} D HDD>DffD DФDD`DDD~fDDo D DHD&DDRD>D\D)D{DDHDD\DqDRD8RD}qDRD0DןD{DާDAD=D=DiD@D'DD&fE3EEEE %E ]E EE3FA3FEfFHFfFKFNfFR&FUFY3F\F`FcfFgcFk!Fn3FrfFvFz3F~ŚFr3F3FF3FFB3FF3F,F3FfFnF3FxF 3F3FUF FFfFlfFO3F>F8F@FSFtfFǡFfF$3FyF3FNF3F\fF3FF`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?e?????)?J8?e?}V???????R??.???n?~R?n?\>?G?1? ??????t??V?7?+?b?ם???t?S?2v? ?E?ρ???o~?Oa?.? ??̣?2??k?L0?-? ?t??)??y)?\?A ?%F? ??[??? ?k{?R?:?"? (???X?d???o?[?G?4/?!-?????ǹ?????v!?f?W?H?:~?,g???l??1???z???e????{?q?hs?^?U?L?D?;?3]?+,?#O???u?r\?oi?l"?i/?f??>?????C?C?????G?G?G??????K?K?K?K?????????P?P?P?P?????????????T?T?T?T?T?T?T?T?T????????????????z?i?i?X?X?G?7?7?&?&??????????????????????????????~?~?~?~?~?~?m?m?m?m?m?m?]?]?]?]?]?]?]?]?L?L?L?L?L?L?L?L?L?L?L?L?L?;?;?;?;?;?;?;?;?;?;?;?;?;?;?;?;?;?;?*?*?*?*?*?*?*?*?*###$##딢#dF#CC#2#2#B8$$ʘ$C$$ JH$ $$$t$n$C$b$;$$"[$$$'$*].$-$/'w2*w4F=?uё?nm?gC?`O]?YG?S?L ?F?@?:T?4?.?)13?#?w`?\t?k?? ?j?2>>>R>%>ܞ>Wy>M>>%> >f>s?> >!>>>U>>:>\l>GA>~>vo>o>hE>a=2>Zj>S>Map>G'>Am>;@>5>0>*>%t.> X>dZ>>6> dR> X>>3=SX====ۭ==&N=dz=tY=g=N=t=\R=H=ި=y="=$e=u=]=q=~=va6=nn=f=_K=XN=Q=J_=Cv===7h1=1|=+=&-= =i=JZ=Vj= ==p}<7<׾r<8<1<+λ<&< e<<<m < l<<0;;1;P;';;Z;; o;?|;;];A;];;.;;;Ў; ;o;w;ocP;g;_;W];O ;H;A;; Y;4;.P;(G\;"p;;3;=; k;;::d:-:Ƅ:٭::XS:i::X:C:Y:{:Q:::l5:a^::{:r:i:a:Yt:Qb:J l:Bߗ:;:5$(:. :(_:"U:::y: A:8:W9#9790_9*^ 9$!f99T9'9 a94988ԟ88ꨢ88ʕ88 8‹B8M8U^88(8l8p8/:88H8# 8.8vb8m^8dܹ8\b8T98L[8D8=~86r{8/8)8"̥888B]8 8J8/77?7<7ޏ717#7ai777~7717v77787A7ä7y7p$7fB7^0f7U7M7E7>!%76ُ7/ٳ7)7"7i7kc7r7 N77w6o6666Ӱ6˛6$6M+6 6>Ə>%??m?/~?Ad?R|l?cI?s)?p?tT? ?G?2?զ?7?^t?P???6;?¬?L?z?s.??P3?؄b?ە?ކ/?Wi? ?|? c?o~?Ѣ?'?t*??I?-#?fQ? ?@*@ @c @Y @#:@r@@y@@@@A@n@a(@@@ P3@ @ +@ K@ @ @ p@ J@ U\@ @ o@ @ '@ ?@ 6@ e@ ud@@<@_1@zc@@@@@@@@{5@n@a(@Xy@NQ@9@@@ @ @ @ @ c@ :@ @ @ i@ s@ 82@ @ @ }@ I{@ @ @ @ @ g@ ;O@ @ @ t@ 7@ ]O@ 1@ @ @@@`@7@@ @]@@|@Z@9@@ @}@@L@b@=@@+@o@@C@o~@O@0@@s@w@@$@@@f@K@1@@T@h@@c@s@@u@_@Jw@5@!-@ @@\@Ӯ@U@y@@@|[@l"@\>@M@>B@/@ @@@@@@ @*@ @@w@@@@v`@la@b@Y6@O@F@>-@5~@,@$@@@ @@r@f@@!@@Y@s@э@@?@@o@[@F@2@r@@E@@l@*@@"@4@o@}@zc@v@s@pe@mH@jU@gb@do@a@^@\>@Y@W@T@R@O@M@K^@I@F@D@B@@@?@=G@;y@9@8 @6;@4@3@1@0+@.@-8@+@*@)J@'@&@%@$_@#:@">@!@ @!@&@*@.@\@a@@@@@r@@@Q@@@0@@ @b@@g@@A@ @ E@ @ J@ @ x@ $@ @ R@ @ @ -@@@1@@@5@@@d@@@@h@@@@l@C@@@@q@G@@@@@u@K@!@@@@@y@P@P@&@@@@@@~@~@T@T@*@*@@????X?X???????]?]?]? ? ? ? ?????a?a?a?a?a? ? ? ? ? ???????e?e?e?e?e?e?e?e?e?????????????$V$$}$'$$3$9$i/$a$i%%֑%u%% J6% % đ%R%c% %*%%%'%!/'%#{%&p%)!%+%1y%4W%7BZ%:9-%=-@Oa@f@@@>@D@@@ #y@ J@ < @5~@6z@>@N?O? B?T???쉠? c?k?j?|?FJ?֮}?%?Ϭ?@??ŕ?U?")? ?&?1?U?^?D??W*????? ?`?J?Q?t??1?xB?{?w.?r?m?iz?ej?`"?\?Xb?TOn?PP?Lc?H?DK??t?:?6?1?-??(?$_?"a? Q?8?0?d?? ??*?a>>>/>>c>AB>I>>>J>>f>^>>>>;>>>)>>#>}>vb>o`>h>bX >[>U@>O%>Id>CGc>=Y>7k>2>,>'c>"NY>a,>F>>z> h>>̴==R={>=<=7 =g==˓=3===6=~=H==l=j===[T=?=yyi=qGP=iY{=a;=ZC=S=L(=Er=>#=8 =2=,'=&= d=2=0== =T=A<7q<1q<+]|<%~<<@~<.<< <p<%;;;M;-l;ٛ;U;O;Ĉ0;w;{;o;5";<{;;;;;;2;;yJ;pt;hTh;`]";Xx;Q>;JA;C;=/;6v;0;*V;%;y;;;0; ";;2: 2:,n:匌:>4:{::0:Ś:DI:3:d::2:u::@:z:,Z::z<:p/:g:^-:V':M`:F:>_:7:/L:):":3:W:<_: :(|9ۘ9*9!9979I999v9"99Y9o999p9{999xa9n9e|9\9T.9L89D.9<95k9.y>9'9!b]99 9M/909 %988 838㯊88\8ъ8(K8˪88&8Z8881886z88 8v8mJ8dk8[E8S58Kř8D+8<۲85,8/ 8(8"A888i"8 8 o8A8E7'7!7ι7=I7b77j77q77&77B7+7M7777$7I7|)7r7ī7a7XD7P7H7Ao7:P 73c7,7&K37 B7%7hK77 7m66uC6K.6w6V6866LW666'6s6T6<6664V6{6Y6z{Y6p6gA$6^5x6U]6M-c6E)6=w6606. 6(%6!6K6?X6o6 ڋ6}[555@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?.Nj?, ?)?&?$9?!S?Cx??d??? 4?Q? >]B>N>tK>@1>#O>>>p7>K>tՕ>J9><=G4=O4?)~qLep]>BWT{%Ŀ#E׊]zxLzbB ic,V!>V>?>:\?z???-M?Y?n????? ?ȣ?Ӕ?߷A?T?@@u@@" @){@.[l@3@6k@7h@;6@>@>K4@?@?l7@?0@@U2@ART@B@D]:@E'@F@G*@Hl@I@J۶@Kq@Ls@M@Nl@Oj@O@P!@P@PX@Q#O@Q0@R@R{5@R@S@SXd@Su@S@S@T@TO@T5@T3@U@UO@TY@T@T@T}@T@T>@T@T@T3@T@TL@T@T_1@T77@T @S @S@S@SP@S6@Rg@R@R@R_[@R+A@Q@Q@Q{@Q>W@Q@P+@P@PB1@Pu@O¹@O@OC@OW@N@N@NJ8@N @M@M@Ma@M&l@Lp@Lu@Lu@L;y@L@KG@K@K[-@K$@J@J@J@JI@JQ@I@Iz@Iv`@ICB@Is@Hr@H@H@HT@H&@G@G͊@Gs@Gx@GP@G(@Fm@F@FQ@F{@FU\@F2M@F@E[@EK@E@E@Eb@EBF@E")@E@D@Dm@D@D@Dn@DR@D8 @D@D@C@CѢ@C9@C#@C@Cs@C]%@CGZ@C28@C@C l@B@Bm@Bϖ@B<@B@B,@BL@Bx@Bh^@BXy@BH@B9@B*@BX@BA@B~@A8@A@AU@A̸@An@Ax@A@A@Ad@A@A}A@Ar@Ah@A^@AUG@AK@AB@A9@A1<@A(@A @A@A@A l@A @@@@@@g@@@@v@@f@@Ԁ@@@@@@L@@@@|@@>@@)@@?@@U@@@@@@@@%@@@@@@@@@@}@@z:@@v@@s@@o@@lL@@i@@e@@b@@_@@\@@Y@@W*@@Ta@@Q@@OL@@L@@Jb@@H@@E@@C@@A_@@?h@@=G@@;O@@9@@7@@5@@4@@2M@@0@@/@@-@@,@@*@@) @@'@@&W@@%@@#@@"@@!B@@ @@@@@@@@@@@@@@@@@@@@H@@M@@{@@@@@@0@@@@@@@@g@@@@A@@ @@ @@ t@@ @@ N@@ @@ R@@ @@ W@@@@[@@@@@@ @@@@d@@@@@@>@@@@@@C@@@@@@G@@@@@@u@@!@@@@@@y@@&@@@@@@~@@T@@*@@@?@?@?X@?.@?@?@?@?@?]@?3@?3@? @?@?@?@?@?a@?7@?7@? @?@?@?@?@?@?e@?e@?;@?;@?@?@?@?@?@?@?=l=}=K===A==E=f>u>>5>5>i>>@><> >w>r> >N>>> > > [> #> > ׌> x>?>h><>> > x> > `> 5"> > x> > i> 6> > > > k+> > 3> 6>x>N@>>o>o>=>3>1>7>>=*c=)=92=X=r===㺺===4=o=ճ=X=V?=ʹ===ü = o=j=p=_]=-==L=!=0.=K=t=N==9=(==j==6j=ws=a=V=pv@ @@%w@2m@<@E@Me@Uv @\@`@`@\@X@MW@Dv@=@9pz@5/@3 @2:@2@3@6j@9.@;S@==@>S@@\>@A@@@=C@8@1ݘ@+7@&W@B@/o@@@ c@@9m@I=?ƨ?k?33? ?Ɠ??ޔ?٦?Ԫ?&?ˁ??1Q?hs?f?/??j@?v?ܜ?:?|1???e??q7? ??\?{E?u ?oO/?hn?bO?\k'?Vˆ?Qh?L&?G?B ?>>>>@->ܔ>>њ>ɷ>> >xu>^>>>>k>E>>Y>j> >>>z>ri>j>cC>\>V>Ov>I3>B><=>6X>1 >+u!>&> j>3>g>J> V>F>>n=0=ֲ=.=d={=Ӂ=®=7R====HP=<=====P@==(=^=y!=p=hn=aKZ=Y=R=K,=E[=>=8l=2cz=,yU=&T=!.=έ=.== N=@&=<:7:1:*2:$Y:]4:h: ]: ::99H9c99ڏ9Қ99Ê?9j9999kO9t9ʡ9K[98999zm^9qG9hw9_9Wa9O9HE9@99#939,~+9&*)9 9/9w99 r98'8A8,8m8L88 Z8}88\868v88_888<18I888y!8pO8g\Z8^b8Vp8Nq\8F8?T88281S8*8#O88o88 88r77b71P7Z77̰7F7I77 f7Vj7[7777u7'97 7vJ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?W~???[??\?{ ?j?6???G???T"?鱅??>?W?R?덤???=? ??w?m?6?~?|?G0??'?t~?$?~?V??N?,?*?v??8?ۡ?"?ᑼ?F??a?Ŭ? N?1?6??F?e?%????_??e?X%?"?5??!-?U? \?? 5?i?QZ?69P?>>|< ɽ xQ`vɿ%x>D@)?W?f۔??-@#O@?w\@@^@=0j@:8@:ѷ@=7@@yh@C|@FJ@Ie@M@RL@Vi@Z@^?@at@dr@g@j@m@peA@s@ud@w4@yS@{@}n@I@{5@G@ @ƽ@{@,R@Q@@R@\@@ Z@w@g@(N@q@W@F@*@H@m@@@<@@@ީ@@@@4@E@@M@@R@@@.@X@@@̎@@3@S@y@a|@G@,@@8@(@[@@wG@V@6;@@U@خ@@@{@Z@9@;@]@@<@@},@V@-@@y@1@@O@s@T@6z@@@@@@@A@e@Hk@,@s@<@Y@õ@@@wG@^5@E@-@@@)@@@L@*@|p@h@T7@@@-@@[@j@@ә@¹@B@@y@Q@t~@e@W?@H@;@-M@@@@3@@@@\@#@)@m@@@@ @v`@l@c@Z@Q@H@@@8G@0+@(9@ @@@ |@q@@@t@%@@@ @c@@T@@@@@@@@@@S@%@"@@D@@~@{J@w@ti@q"@m@j@g@d@a@^@\)@Yu@V@TL@Q@Oa@M@J@H@F_@D=@B1@@%@>-@8>ܺ>>EJ>>2>롐>e>9>>>/?a?T? ?Gg??p? R? ? ? b0? -{? ? Vm? z?}?#????˰??;u?t? ? .? ? u? ? ;? ?k?.??l?g?Z?#?>>> >'>I>n>t>>>&>]_>뗚>>>Y>䡌>J>O>w>]>E_>.0>P>>>>Ζ>ֿ>ձ>ԥY>Ӛ9>Ґi>ш >V>-> >[>Ո>7>ög>>>W>>>>>$>1b>Xy>>?>>0>tm>o>o>w>->>|B>*>Y6>f>bx>I>>@>>>>y_>|}>xJ>t>o>kQ>g،>cW>` ->\:>X|>Tύ>Q1>MH>J%>F>A~5>7>2Š>..>)>%+> >>>>L> > S@8r@C@Pd@\@j@y)_@M@A@@N'@@~$@_@N/@H?h@D@@@=yS@:@8W~@6#@3@1΅@.@+zx@(D(@%?@"h@z@!@@!B@@]@ ڐ@ &@v@&@^?L?w?+? ?"?j??FJ?՞??ʋn?9? ?i??N??&??f?7v?#?+?N??N?x??{?u7?n?h?b?]1?WO?Rw?L?G?B`?=5?7?2}?-?(?#.?8??J?P? c? ?}?>q>3>)>U>຦>[>1>3'>!>#>c>>s><>+>=.>O{>R>[>>R&>U>>~#>vNH>n>g`9>`@>YW>R4>L>E>?)>9|>3>.`>)>#>>2>O{>> >>]====n=5z=5=n=&=ŀ/=V/=]\=?=m=G=H=/=@=w=8=W===}e=u}[=m=f:=^=W=Pr=I=Cl==*=7=1=+h~=%q= =Z=W=~= ͤ=C0=F<<w<8<1ɧ<+<%3< 3Q<<t0<Z< m<<+;E;;W ;8;`;[;ˁ;sw;;;;;;;xL;&;;; ;u;w;o8;fՍ;^;V;Oe;H @;A;:<|;3X;-Bn;';;!/;xA;;; ;;:]T:|:;:L:ذ::ɏ:U{:]::+:D:::6:::: :z*:q6:i!:`}:X:P]:Il=:B/:;3a:4w:-:'':!:Ҧ:2:ž: :}:9y9̺99ߤ 9ׄ69Ϯv9 @99Ђ9 %995p9"9G99199+969v9m:9dـ9\o9TTM9Lu9Do9=9690 9)b9#X9X999 9{9[8s8;F8Y8˻8؍8Н8r88K8O8{8V88<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@,z@-d0@-@.`@.)@/pP@/@0A@1 @1)@2!@2h@3C@3̎@4P@4 @5QY@5@6K@6@7H@7@85@8'@9@9~=@9@:U2@:@;@;~@;@<8\@<@<@=J@=J@=3@>&@>g@>@>@?@?P@?f@?@?|@@j@@:@@Xy@@q@@@@@@@@@@/@@à@@4@@@@@@;@@sX@@T@@0@@5@?@?c@?[@?@>@>t*@>e@=@=I{@<Ԁ@?F?&?ُ?ŭ?B?'(?H<\>#D4v ?[ ?@?꒣@ @s@,%@:T@FD@Q@Z@cUq@kA@r@y2@a@@:@g@@A @T@P@9@.@X@*o@m@@@7@H@GZ@6z@@ @K@j@L@@\@@{@T@~@@@I@U@@@H@e@l@ @B@p@c@@@@@j@*o@8q@>@>B@9.@8@:T@8@3r@,g@$@:@@@ @ @O@@s@c@M@4n@@@@X@@@i@J@+@ @@m@@w@`@=@@Y@@@@{@[l@:@@3@ؙ@(@@x@X@9X@@;@@D@@@i@L@0@@@@@C@@z@b$@I@1@@@)@@@@S@o@l@X@D@1@K@ @@@׈@i@@<@@I@u@f@W@I=@;@-@`@@@#@@P@[@Ǐ@@@@(@@Y@|[@r@h@_@Vm@M@D@<6@3@+@#@@Q@ @@@@@t@@@ײ@ѷ@@?@@Z@@@@@^@@F@@@i@f@b@@|@y@u@r@n@kf@h@e@a@_@\@YK@V@S@QD@N@LD@I@G@E9@C@@@>@<@:@8@6@5@3H@1@/@.4@,@+@)@(@&@%F@#@"@!B@ @@@@\@L@;@+@/@3@8@Q@j@@@@@R@@ @ @ t@ @ @ |@ @ B@@@@@y@@}@@@@@2@@`@ @@;@@@?@@@C@@@r@@@@a?^?*?w?d?Se?C?5i?(??? ?S?*??n????T?? ??~?(c?4D?B?:??8?D??^t?o????Y?z?s?K^??Z?YB?Wo?UD?TI?R4?Q/?O?Mq?L ?Ja?H?F?E8G?Cj?Aڭ?@?>>)?S?*vd?( ?&?$ɸ?"ӕ? i?? ?5;?\t?a??`?!?? ? .f? C5?^??? ?>:>\>>0>K>X*>>>x>> >Հ>>oP>>DŽ>>>>>W>r>p>>p>>>N8>i>>H>>>0>>>T>g>>T">} >xP>s)=>nX>i*>dSL>_>[X>W`9>R<>Mr>H7>C >>>9]>4O>/(>,>(Ի>%>"l>ؐ@ł@U@@S@}@xs@tu@p@k @g]@bH@]`@X@Tt~@P @KF@Gz%@CT"@?Dg@;JM@7e@3[@/0@,0+@(~@$@ @Ln@@.4@@a@ @ @¹@h?k?d??H?Ԫ?4?K4?4Y?;?e?ʷ?ϫ???k??jU????m?Sz?Uq?q?X??`-?Y?~э?w?q4&?j?d?^?X?R~?M0'?G#?Bd>A1>$W><>>>Ӂ>.0> >>N>>>aV>fE>~>9>,>n> >>N>2+>|lU>t1>m4Y>e>^>X>Qu>K>D;>>>8>3>-R>(@>#> >)$>j>> <>۔> ==A=Y=H=ަ=ו=йV==Þ=`-=T{={m=>=V=.====i==iy==w=oH=hn=`=Y0S=Rf=K8=D=>d=7=1=+=&L= Ϲ=~=X=\I= ==Wn<::J:ż7:r:kg:::ͥ::ބ:8:y::zC:{6(:qҸ:h!:`]:W:O:G :@M:9:2#(:+r:%:г:ڵ:: C:K:19y9 99Ap9~9͍9 9Y9g9d99x$9R,9g99<9999v9m9d\9\K9Tp}9Lt9E2<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@h@k6@m@o׈@q@s@u@wG@x$@zI@{@|@}@F@@/@0@@Z@@@@:?@y@@ƨ@@@7@"@u@@@Ŭ@@~@1@@Ɇ@̎@@@/@do@@@b9@/@@9@iY@@,@^@@ @#:@6@Go@U@a@k@sX@y@|@~@~@|@yS@t@m@do@Z@N@@y@1'@ 2@ @]@i@@@x@z@\)@;@@@@m@~@S@&@@̎@N@e,@(@@@Y @ N@9@b@J@@A@^@g8@A@u@@~5@}@|b@{V@z @x@wy@uQ/@syS@q|@oW@mO@j@g@ḍ@a@^l@Z@U̎@P]@K@Ela@>D|@5@+@@g8?C?4>"`?G@ +,@-@Fb@Y@j*o@xL@W?@@@G@b$@*Z@@@ 5@ @@"@o@@W@@j@@n@.@@>@[@f<@b9@S@4@y@¾@nD@@Ĩm@4@ŷk@0@ơ#@ @jj@H@@d@ȫ@@*@a=@ɑ}@ɺ@:@.@}@ʴ$@@Y@@#@0@9@>@@O@?S@@@@E@@F@ʸ@ʡ@ʉ@r@[@A_@%@@1@̎@ɭm@ɍ@n@NQ@.@g@@ϖ@Ȯ>@Ȍ@j@H@&l@l@@@ǟ@~g@\@;@@@@Ƹ@Ƙ@y}@Z@;@*@@@@ťQ@ň<@k@Oa@4@@@i@G@į@Ė>@},@d@Ln@4@?@:@@U@Ë@î@Ù@ÄM@o@[@Hk@5+@"S@@@@{@\@¸@¨@—@ˆ'@x@i@Z@L@=@/@")@@@@9@@@@@>@/@^@@{@~R@t~@j@aR@X@O@FJ@=@5+@,@$@@b@ @@m@w@@@z@+@@@@T@@0@@@@@@ @t@@@D@+@@$@{J@w@s@p;@l@iY@f@b@_@\@Y@V@S@Q/@N{@K@IR@F@Dg@B@?@=@;O@9.@7"@5@33@1<@/o@-@+@*@(x@&@%F@#@">@ @`@@@\@"@@@@b@Q@@@0@I@M@R@k@ @ @ @ @ (@ l@@@5@y@@)@@@G@@!@@@@?N?YY?f |?rz?7?]??Go??N??5?{?B??c?@d@ @@@@$@+J@1Z\@6%@;@@ @ER@O@Rj@Ua@Xb@[n@^@aNf@b@d-@e@g @h@j@k@m@ly@k@i@h@f@e@O@cg@b@^@[:@W@T@QG@N@J2@G@Db@@t@<@9nY@6C@2;@/`@,%[@(,@%Y@" E@ѷ@@@@@F@ @ @@@@8\??]? ?Z?2?d?^_?H?Q?w2?ڐ?ř?f?@?(N?m??*?C-?hs??`?s??$ ?nY?{??h?o?m??N'??`???1?Mj??}M?x?tU?oj?k?gw}?cT?_D?[GA?W\)?S?Ob?L ?H_?D?AF?=ү?:nT?7r?3?0(?-u:?*[?'P]?$R?!cE????(F?yd??@!? l? 6P?‹?Z?>>b>v>x>>>6a>ܤL>)>Ɔ>z->D>#>?>#>Bh>uS>,>'>E>>Yu> >\>N>">˸>E>>>>6i>xV>r >k>ew>`%9>Zy>T>O>GZ>?>8ة>1">+d>% >p>@@~@@z3@v@qW?@l@h@c[@_+@ZE@V@Rq@N]@J^5@Fr2@B@>,@:@6Hk@2@-׈@)@%@!h@T@i@@B@W@ p@ &@@}?AJ?? ?'?i?ί?V??˼?̶??p?2??&?1?n?A?.?4n?z?L???]?&??:?c? V?J?[l?>>x>|W>䲖>?>פ@>N>> >3f>>>>x >n>>*>">>>;>>D>ܺ>y>rrC>k'F>dK>]>VM<>O>Ig>C@>=IR>7>1;>,s>'6>">>7.>>x> o>6> =r=$_=="d===D=ɼ=b=:=B={=ۆ=k=$===>\=f= ==hc=x&=p=hФ=aL=Z=RY=L<=Et=?=8=2e=,#='B=!B=oR=Fn=G= r_=É=:<<+;;;;r;ۃt;Ӱ;;;s;;a;;;;B;;i5;D;N);});sɼ;jކ;bC;YV;QT;J;";B;;;4;-+;';!Y,;];1; '; ;;::]k:~: :Ի.::4R::Ծ: N:O:C:==:n ::d:C:R:z):qB:gT:_F:Vz:N^:F:>O:7^:0:*:#d: :,:&: f:!:9c9 9<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@F0U@K*@Q0@V#@Z]@_ @b@fg@i@mJM@pV@sć@v@y~g@{>@*@Ɇ@q"@@j@˧@:@do@@@z@ @Q@x@@s@J8@ @J@AJ@@Q@K@t@\S@At@&@@֌@y@V@K@@9@^ @@@E@@@@@#@@@@C@m3@o@;@@@?>@@@!@e@@H@3@W*@@@@@5@S@n@D@(@X@Q@)@@@@A@@*@~@7@@@@ם@\@@@@@N@v@b@C@@@@@S@@@S@I@"@@ZG@G@@J#@@@@T@2#@@1@@@~@e@7@@ @@9m@]@uO@~R@wp@_[@4@@@,@@@_@y@@#O@@) @}@r@e@T@|>>o>n+>fE>>˧>6>Ɨ>y>O>GN>_>5>>e>>2>w>_>b >>+>:>>7>y[6>pN>h$ >_>X;>P>IU.>Bb>;q>51>.>(>#+@@l@~3@xe@s@n|@i@d4@_w@[Ks@V,@R;y@Mײ@I@EVC@A7v@=.@9;d@5]O@1@-{J@)6@% @ "@@*@e,@=@">@ @9@@H???ƽ??+?ݕ??]???T?[?"?q?L?xl??q?G?.?E??}??-#?"?M?|զ?u?o3?h?bK?\T?V ?Pk>5>>}>ۥ>@>o>->>>a>y>>w>zc>>>V>>>F>*>~Z>v >o>g>`>Y>S>L>FA->@">:->4i>.>)^[>$>U>>-#>$> >>E=C=5a=h=,=߁=c9={S==J==L==: =Z====G= =Kj==;==ww=o]=g=_d=X5=Qp=Jh=Cܙ==eQ=7!j=1=+2=%b= 2==O=3= >= =K*<7<18<+x<%<6F<<2|<< < t<R;;{;A};(;# ;};4;;;wa;;߹;;%;g;;1;;;C1;w?;nL;eB;][;UV;M+;F)E;>;8);1gl;*;$);؞;)\;5;Q; 0;@:x::r:}n:J:d::r:`x:P:::::#::M :9:U::;#:v"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?B? ?????,R?.?q@F @ /@*@0@!-#@*3@1@8/Z@>z@D~(@JK^@O@Uj@@ZX@`@eG@jn@o&@tD@yǤ@~@">@o@@@v @a@9@@@@ @^@ @&@:@ @77@+@y@^@C@@.@b$@@@G@@r@ @@ \@%F@&@$@ G@P@w@O@@A@}@@i@@b@t@ś@Ƒ@gM@(@ٔ@|@Z@ʝ@!@˗x@@o@$@+k@@O@@R@΅I@ζ@@"@W@ό@Ͽ @@!@R@Ё@бF@3@0@E@{ @Ѷ@@T@\@=@h@|@}@q"@X@6;@ @֌@Қ@W@@ѽ'@e@@Мx@@Ϝ@0@Ǥ@_@@͎@%@̺I@N@ީ@k<@@w@<@r\@@Y@@*Z@Ɖ"@ @1Q@yh@ø|@@@7@J@N@D@'@@@K@ @-#@g8@t@N<@O@9X@,@\@@(@@7@@j9@3aә@I@^@@]@D|@D@@ǚ@͆@@ׯ@o@oT@+@@}@f@@Y@ @@@M@@a@@T@@Ap&A}A^ ATAD(AAARAAیA5ARAAAAA&AEA`Ay AAA A9AAAAnArAAA AA AAwAAKA}AAuApAAvAAAqAbASACA3A#yA6A\AA9ADA/AAA_AzAiAXAFA5JA#AAoAPA;AFAqAA$AA{_Ak1A[-AKSA;A,AA zAgAAA_AAAAAtAAqAdAWsAJA=A1[A%AA A0AA@AA A=AA/AAA A_AAyApoAgwA^AVAMAEcA=GA5TA-A%AyA/AA A6AAAA>A$AACAЇAAmAAAAAAASAAPAAAAAAA|Ay>AuArAnAk[AhAdAaA^A[AXAV$ASeAPANAKAIAFADRABA?A=A;yA9mA7aA5iA3}A1A/A. A,RA*A)A'gA%A$UA"A!lA AA^AAAAtANA)AAAAA AA(AAA eA A A A (A lAA?4 (?5 ?5?5 ?5*c?54Y?5>-?5H?5Q?5[?5e?5ͣ?6a9?6E?7?8?8[?9JM?9?;?>.?B:K?E?H?M(?U9?]?fR?y7 ?|??|?nD?\h?@ 6@v@.@C@T/@^u@iF@qײ@u7@yI(@}@z@C@@ W@q@@M@@@@7 @@@@@_@@P@u@ @h@@_@@?@f@6@i@$_@@$@Y`@@֌@}@@7v@]d@@@b@@,@Y@@A@@;@K@@@@!@A@Ta@@@F@@!@g@@~A@{j@x\@uW@r\@oj@l@is@f˧@c@a8q@^{@[@Y@V'@S@Q~@OS@L@J)J@G@Ej@Cb@@Ǐ@>@<@@:@7g@5@3w@1e@/M@-;@+/@)*Z@&@#@ @/@ @)@ @/@s@ r@ ">@@@@w?`?2??퇔?/?.?2??6?jj?ѯ:??j??f?e??T?n?}?Ǥ????B?;??]?;d? ?? ?~o?w?o?h.?a?[d(?T?NØ?H?D]d??,?;k?7]?2T?.3?*Q?&)?"t?s?G?B??o? X? ?Y?%?>>+>)t>{>Z>݇>?p>H>>$>L>xW>j> >R>9>N>à>>A>E>^>h>ә>U>>>^>zi8>t@>n>@@)@@z@@{N@u @pDR@j@e@`U@Z@U*@O@JF@F}@BA@>=@96@5A @0@,@(@$J@ @@@@PH@ Ԫ@ @R@.@??T??v?j??G?Ͽ?H?j?Ǥ??J? ?`?ײ?l??}?????$ ?9? ?x ?rE?kR?eL?^$?Xo?R?L?G?A~?<+c?6?1?,?'?#$ ?$??M?^? >k>ap>M>>`>>><>)g>>>>}>p>>>e>>3>>">>}>ua>n>gZu>`U>Y>R&>Loz>F+k>@f>:&_>4cI>.>)T>$ l>>>>G> >?>==k=\==ޒ=k<=|=}=A=d== =*==7=== W=K==?]=}=uS=mm=eg=^-=V=O=Hj=B=;=5N=/A=)g7=#=@= === =^=v<<;j;bPm;ZL;R;Ky;C/;=;6T:;/;) ;#;/;S;; ;\;8:h::<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>hq>?Z?"?=/?Wщ?of?do??3??;?)?U??˸??8??P@@@ @/o@0@#O@ )@%;@*U@/y@4@9T@?+@D@I@OfQ@T@Z@`]@f@j/@sŬ@{A@@k@K@]@`B@@@@ @@Dg@@@@F@r@@@e@;@-@/@Ĕ@ @ @,R@Ri@d@{@j+@=\@H@س@j@2M@ݽ@(9@{@@@ $@T@#d@d@=@'@s@@Y@Z@=@ @@f<@r@@b@T@@o@ײ@AМAAAA;AAjA A A{AhAA+A?AAAۡAAAOAAApAYAwAhAYKAIA9mA) AjAAAVA AšAAAA|;AjAXAGPA5A$ AA;AAޞAAuAAAAyrAhAXAHuA8gA(xAAArA+AAAAtAAy]ApAgA^ AUgALAD|A8>;S >>6???- }?B ?Y?k ?|R?Oa?\? ?j?/?]?ƃuX>.>~>^>#[>۝>HE>!>(>[p>>@}>9>&>>>O>:>&>>J>a>y>{~N>r`>i>a >X@΅@@@|f@vy@q@lI@g&@b!@]4/@Xd@S@O8@J@F,=@A@={@8@4@0J8@,e@(@#n@@ @8@{@s@ HV@ 6@g@??b?'g???$J?p&??1??w?L??;?f?(? ???J>->>>#>> k>>ʧY>Ĵ>g>M>>>S>H>c>#>H>zF>a>>d>>yn>q>j]l>c&>\#>UT3>NE>HP>Bj>< >6+>0z>*>%$> e>[->u>> ;>H>P'>==.f=腐=r===%=ŕ=9Y=s=(=L==@=2=w==#==]=\=x<=p=hF=a0=Y=Ry/=K>=Dr=>;=7=1o=+B=& [= =2=+== 0=_=<ndb; 7=>>Y>>>?J?,?I?b?s^?~Ё?s????C???U??8\?1?0j??,@"@ M@{@۶@"y@)Ŭ@1@5@:@@N@F|@MQ@Tu@\U@e@oz@x@ͳ@_@)@X@X@|[@@@~@^5@S@t@@)@Xd@@I@@E@ƺs@˘@Ϯ}@|@@j@ޚ@@p;@+@@j@@@@!@@3@o@ڐ@A:AAnAKA>A?A&A6zAAA7A,AOAAnA A A A A "A AA vA A A !bA _PA A A +A [A A A A mA A NA A OA vA A %pA (A ,A 2A 5TA 1A 'A  A BA A A A A ^A sbA SA 2vA A A iA A xA P}A )iA A ؙA A v`A AA rA /A A XyA ;AAAMAAAkAAAp0AARAW*A=AAAFA+kAA%AjAfAe@zc@ $@zN@ı@0@@@@|1@@߹@|@咣@@b$@l"@P@s@do@f6@5@D@k@@҉@SA6AnAA PA A+AAAkAT"A̘A%AaHA8AAAnADA AtAjA A A!A!_A" A"xA"/A#A%iA%hA%hA%NA%A&;A&{A&$A&2A&=A&HA&QA&WA&[WA&]dA&]A&\A&YA&U2A&OvA&HA&@OA&7LA&-A&!A&A& A%PA%cA%ߤA%}A%A%A%'A%\A%~(A%lA%ZA%HA%6A%$tA%A$A$A$A$A$9A$bA$A${A$hA$VmA$CA$1'A$OA$ A# A#A#ԕA#A#A#A#PA#{A#jA#YKA#HA#7A#&A#A#A"A"A" A"A"A"A"A"~A"~(A"p;A"bNA"TA"GEA":*A"-A" \A"A"_A!A!A!A!EA!6A!'A!A!qA!4A!A!A!A!zA!pA!gA!^5A!UA!LA!CA!;A!3A!+kA!#A!A!{A! A!A A A A KA A ݘA sA NA )A mA A ^A A A eA zA A A $A :A A A A A |A xA u%A qvA mA jA ffA c A _A \A YKA VA S&A OA MA JA GA DA B[A ?A =qA :A 8A 6A 4A 1A /A -A +A )A 'A &A $ A "hA A !A ~A A 7& :w& v&k& s&d&L&2z&&)&&"I&$&'&*I&- &/K&2Z&56&8&;&>h&AV&D&G&K(u&Nq&Qs&U-&X&_'&cNT&f&j&n&r_e&vJ&zF&~S &8&O&n&P&ȸ&>&F&&?lL@ @@ @)j@2N@9@@]@FB@L@Q@W2@\}@a@dv@hQ@l]:@nm@pJb@r+k@s{@s@sć@wa@{&@@5@m3@Y@&l@r@Ҟ@-@ @1@@\@ @(x@O@y>@@@o@@@&@_@@'=@@#@@k'@@sC@@Oa@q@@@@@H@@@_@:@o*@@@@pP@ʬ@'@Ɔ@=@Ɖ@.@ŧ@ř[@ŋ@|@l@Zq@HA@6&@$ @@8@/@(@"@@@&@S&@N@@v@پ@"h@u:@J@9C@@$@g@@U@@:@'@I@7@@@[@@@b9@@@@@{@wh@r&@o@k@h>@d@a@^N'@[]@U@K@H@d@Dy@@@=@9@6 @24@/Ln@,:@(B@%8@!@@@@c@pe@ :i@ @@>@?pe?Ԁ?U?n??q?۩*?T ?? ??A ??B?nn? ?????m?u?4?????j?}C?wVT?p?j?deb?^i'?X?R?ML?Gރ?B?=n?8k?3?.C?*,g?%d?!P"???٥?? ? ]?ʟ?OD>#>9*>G>>>܌.>>Ѷ> >u>Y>>"u>u>ܾ>BF>>7>߇>>H>@9C@>@x@@{t@|E@w@q[@k@f*@`_@Zf@UD@O@Jo@E@@V@;K@72@2@.%1@)@%@!V@< @;@U@@ @ :*@@H,??JM??U??5?O?^ ??O7???J??*??C?@?^J?&?;?F?? ?B??{V?t?n;?gt?`?Z?T#?N?H9?C ?=?8%[?2%?-Ц?(?$?T*??M+?+? ? ?e?>>Y>0>扇>Ó>.>>̊>y>>>Dx>֕>>g>k>\>>K>0>U>S>A>z>r>kp&>d/>]%h>VO>O*>IB]><>7O>1N>+¹>&`>!'>>,>g> Ǭ> K{>^>=B=R=韡=)=W=-==ƅ=!====z==A====%==?=z==q=i=b$=Z=St=Lu=El=? <=8Ɔ=2=,=&5=!V===%= ğ==s< G>jT>>?f??5(?Q$?m0?.?t?%??B?0j?@c@N@D@%W@/2@8\@Bj@L'@VZ@`A@j8@v.@@@4@{ @4@@@w@?>@@T"@@x@@=G@JM@,@!@ڇ@@qv@@ @G@e@k@_[@Dg@T@R@A@yA$A"AAEAA A A A A AZAIAAALAAAAJXAAAAQxASAqAZAeA~A8=AA,AEAANA+AsmAA A]AA)A|AA,AAARA^5AfAkAmrAlAidAd&A]OAUAQDAKIA=V===o=k=@c@#@#@(z@-s@2@7+@=G@B@H@N@TQ@Z$@_H@eY@j@p~R@vD(@|-@@7 @c@@@`@j@R*@H@L@`@@@@i@N@@y@nY@oi@}@N@ľ@@@@@Sz@ה@m@=\@Q@?@a|@@穨@@@ @K^@w@'@,@@6@c@T7@@!@'@@WT@@'@@@R@ם@c @Z@ߎ"@-@[@@2#@@Ѫ&@o?@,@@ȝ@`@(@@@@@q@_p@S@r2@@o@@@@;y@U@j@@@S@&@~@6@ί@p@m@ @h@8@Go@}R@x1@s+@n?h@il@d@`@[f@W@R@Nw@JI@F1Q@B.4@>?@9@5@1f{@-@@)3@%?)@!b9@c@@S@>@a@ _@U@@oi???H?슜?敖???՘?7?3?r???H??+?!?Z?Vm?F ?O?s?&?7??Z2? ??{I{?u ?o?iT?cP?]?X+?R?N?I~?E?@T&?; ?5?0"?+j@?'2?#1f>>>P>:>ݼ>׹Z>߹>.>V>>>o>>=>4>>k'@lv@<@^@@@|"@v@pqa@jt@d@^@Ya@S@N@IY@DU@?@:݃@67 @1@-A@(@$4@ @@@@BF@ @ @X@ ?1?x?)?P?Xd?M?la?}??ȇ???y???c??k???O?(c?C?+?SP?),?w/?pà?i ?c?\?V,?OO"?8?3Re?. x?(?#??b??Y? ?Љ??>Ȩ>G>>>)>W>>Ɋ>k >x>1>D>>`><>CJ>n>/>0@>~>{>R>|>t4>m#>e">^>W>P,>Jj>D>=N>7j>24e>,5>'+>!>}>،> >cc> >>C|=Mn=W==!="=غ=l=o`===Ӊ==^==)=|=k== =z=*G={ܻ=s*=k=d=\=U]=Na=G=AB=:+=4=.e=(u=#R=x=z=E=Ŗ= =u=.<׽<<<O>ϧ>>}l>T=-Խ玾k8 ¿bVa׿~țAVm_,zoֿt6">vؿaLL9@&( 0%Ce[~>&>-??L?}?F?H??@ r@֡@%q@2@?I(@K@X+A@dy@p@|@E@'@@@j@@@O@mH@@@'R@C-@I=@8@+@ٔ@ދ@'@@$ @@@@qAsAAyA\A4A 0A A y>A%AAZGAAeAAABAAqA9AvArAA A!PA#uA#]A$oA%EA&A'fA(RA)@A)A*uA+A+A,FA,}A-QA-˒A.=A.LA/ A/dA/A0SA0KA0A0A0rA1'RA1OvA1rA1A1*A1jA1)A1A1A1A1ݘA1خA1ϫA1A1-A1A1A1m]A1QA12aA1bA0WA0mA0xA0pA0B[A0A/A/A/uA/?A/SA.A.A.K^A. A-ƨA-oA-:*A,A,zA,VA,A+A+bA+ A*9A*XA)A)A)4nA(A(`A'A'|A'GA&+A&uA%zA$A$OA#}A#A"HA!sA AA@A\AAAwAiAVAm3A/AA /AAM@@@y@N'A AAAA!qA%#A(&A*A--wA/NA1<6A2A41A6{A7sA8A9A; A<A1A?QA?A@AA2aAAABHABAC.ACFACADGADADAE!AEZAEAEUAE)AFAF6AFVAFq AFfAFAFAFAFƨAFBAFAFAF#AF#AFAFgAFϫAFȴAFAFAFAF'AF AFAFv`AFfAFVmAFE9AF3AF!AFAEAE>AEԕAEAEAE$AEAAElAEXAEBAE-wAE+AEuAD)ADADADCADADADmADYKADDgAD/ADqAD_ACMAC;AC˒ACRACACACcAClACZACHAC6zAC$ACACuABABAB}ABABABABABABsABe,ABVABHAB:AB-wAB \AB@AB%AArAA)AAHAAgAAɆAAAAAAAAAAAAAA}AAsAAjAA`AAW?AANB?lϯ?K%?.9?pr?->\> >*>Y>n >X">N>E>4>,>%A>>>> 1b>&x=b==y==Y=N=E=m==j=m==c=yW==l1==`g==,==/=me=T*=N=]=Ӂ=׺==m==zh=#===> >a>&>k>>> > > Qr>Z>>HR>>>k>M>ݥ>?>">Et>h>>> >>>>F >>Ȱ> > ~>&>>>===b==>?8?͊?@'R@ i@%@ @+S@7v@C@Q0@^P@i)@sR@0@@|1@@j@@G@@y@@~@.^@@@@@׊@)@9@R@l@@@@ $@@y@@8@[A%AoAHA&LAPrAllAxApZAYA4AAhA]/@@Z@@@@@#@@@x@@#@@-#@+@v@@ȴ@@=@@zx@O@ֆ@a@i@ͳ@&l@{ @.@@C@x@~@Ԫ@@%@H@k@@@q@@ @-#@P]@w@@@@6z@r\@@@H@{:?@u@p@kn@fn@a]O@\c@W|@R@M0@I%@D<@?@;sC@7@2@.j@*6@&*@"^@@<@l@@6@ i@!@q@???S?袇?(?ܰ??E??j??+?i?">?I?B?*??_?")? B?3??4???x?q?k7?dD?^I=?W?Q?KL?F,?@?;?5 ?0ǟ?+?&?!ڌ?;??.U?b? ?m?w?>h>l>>>u>*> >v>RD>Ųp>:>>Ĩ>ɸ>@U\@ @̎@@S@{@u>@o@i@dyh@_'@Y@Tm@O@K@F\@A@=<@8@4@/@+@&ϖ@"k@H@2@.@@0j@ w@@DR@ ????2?b?2??) ?Ɂ???W?5i?4?Se?h?9?h??F?j?t?{??l?x`?q>O?jK?ca?\?V?PQv?JB?D\?>?9 ?3f?.X?)6r?$9X?`[???? K8? a?q?>X>>뎰>{>ݝV>t>t>&>> >>XX>>O>>%>o>>n+>>y1>0> B>~.>v0f>nW>g:>`>Y'N>Rm>K>E>?o>9z>3>.>(P>#g>K>V>@>H> Z>>=9=>p=퀡=*=޹=׬=ֲ=6==[===O=S=4= U= =4=3===I=xM=pI>=h=`=Yr=R=K(=E,=>=8=2=,X='=!3=@=n=$Z= Sp=l=%<<<>C>Sw=о{i~i4d%D.R~ſ%o+k@:^:Κ>Jk|f{{=>p>?)?6?^ ?+A?1???d@s@>@*@C|@XH@i&@zz@TL@b@@'@A@@>@V@@s@@@M@V@@@CAA F5A;:AA!AQAA?hA0A!OA#A%fA'SA(zxA)A*!A+A,A.A/XA0A1A3,=A4~(A5A7hsA9(A:˒A<A= A=ZA>UA?v`A@A@AA&AAAAABOABABAC =AC/ACGEACEAC2aAC6zACFACZACqvAC7ACACAC֡ACADAD>BADoADADADADADtADFAD ADADOAD4AD.AD{JADeADQAD>AD%AD AC2ACACACkACA ACABABABbAB(AAAAAAOAAA@҉A@\A@J#A@A?A?qvA?$ A>ԕA>A>/A=A=oA=&A<ɆASA@'ABvAD!AEXAG AH\AIAJFAKALCAMANYAOAOAPiDAPAQ7AR AR~ARASOASAS.ATLAT AT[AU AUBAUrAUAU3AUAVAVAV6zAVJAV[WAViDAVtTAV|AVAV+AV7AV7AVAVAV~AVxlAVpAVgAV]dAVQAVEAV8AV*0AVqAV AUAUAUAUzAUtAUAUAU|AUhAUTAUA AU-AUAUATATیATATATATATu%ATaATLAT8AT$ATAS"ASAS9ASaAS!ASIASASxlASeASTaASB[AS0ASASVARAR)ARARdARARARARARcARq ARbNARTaARFtAR8AR+ARARARAQfAQAQ;AQ[AQzAQAQAQAQ=AQAQYAQ|AQqAQhsAQ^AQU2AQL0AQC-AQ:AQ1AQ)_AQ!-AQAQ4AQ lAQAPDAPAPAPAPAPsAPAPXAP3APAPAP-APqAPAPbAPAPAPAP~APAPAP~(APyAPuAPq APlAPhAPdAP`AP\APYKAPUAPQ@Gq@C@?M@<b@8X@4@1!W@.5?@,A5@*R@(i@&Y@$D@"ϖ@ &@@z@)@T@@ @X????f1?>>{>C5>}#O>Z>I=>9p>,m>$>B>>.>l>\%>=f#=5=Ũ=Y6===@==G=n=9=d>X>>> I> >>F>>p> >#9>&>)>- J>0RP>3>7 >:V>>_>As>D>G;>I}J>Ke>N>Pl>RɎ>U->W>Z >\>_>a>d>fx:>g>iY`>j[>lC>mf>o7v>p>r4e>s>q>m>i>>f3H>bC>^>[mD>XZ>Vj>T2>Rn>Q c>O#>M@h>M>Mj>Mk>N:;>U6P>].>e>mqm>uB>}8>>@Fw@Q@]@jn@w@+@u:@r@2@r@@v @7@n@@]@@1@Ԝ@,=@@@@@nD@ApA޾ATA A !A\AA AA-Ae`AIA=A ;AEXA}AA$A&A_A8AA!AALANAxbAAzA@@@'@mH@!W@@o~@)@@õ@ͣ@ʎa@Dž@ĈQ@@Z@r@J@B@@@p@z:@c@M@'@@ @w@JM@@j@{}A@un@p?@kgM@f@bP@]rq@XU@T(@PQ@L&@HT@C@@C@;@7O@3@.I@*/@&@"8@@>@s@p@{@ \@ #%@̸@oi?1?G???%?ސ?س?5?@??~?K?u???C?]?#?"?r?P?C-?Q?z? ??{%?t=?m?g&?`?Zs??Tk8?NP?Hؙ?CQ?=?9P?4%?.?*?%ap? ŗ?J???[? ?F??Zq>>#>쉏>,>>>">b5>U>*>F>@{@e@}@x2@r,@m@ho@c")@]ۡ@X@S@N@I@D@@P@;q@7|F@3@. @*C-@&@!@;@@@@5@ Y@@a?Q?~?}?1? ?ߍ?ه?ӥ?C?K?5?{t?E$?/?:~?d?S????O?Iw?C?>?8v?3(?-?( ?#?Y?e(??m? ???w>:K>ȣ>?>'>ݲ> >Оw>Z>F0>^>>>C>i>Pn>]>>>^>>>>>wMY>o>hbo>aA>ZU>Sw>M~>FG>@>:>4>/V>)2>$>>S> >> >.>F=$=-=њ=I=/=2= B=`==c=s===w=====<=2=1r=={b=sF=ki=c=\h=U?G=NN=G=A=:=4=.=(x=#\v====A= =k_=i@<ם@:@8m@5*@3Mj@0ӄ@.d@+@)^@'y}@%(@"x@U@Z@@G@G@@ @??n/??.??4?[?k?s^?=?>n;ͽ‡rA5 dk~п˞B?P8 eT 9B>1[W??W?d0?i/@+@;.I@< o@>@H @Rh @f@sw@c@@[@/@@@y@@v@-@_@+@K@A|&A/AA gA A4AA0AA)AAn/A!@A#zA%ȴA(A*0A,ffA.A0.A2eA4$tA5A7fA8A:jA;ںA=A A>A@ AA[AB ACADAE2AFAGAHAIAJBAKXAL҉AM+ANیAO'APOAPAQ|AQARxARASMjASATATXATCAUAUMjAUAUAUAVAVAV5AVFAVTaAV]AVdZAVhsAVjAVsAV{JAVxlAVjAVTaAV9XAVAUAU)AUAUkQAU1ATAATAT4ATMAT ASAS0ASASlASEAS@ARݘARARl"AR1'AQPAQzAQAQAAPZAP@APOvAOAOAO:AN֡ANn/AMAMAMALAL+kAKFAK?AJ3AJEAIAI:AHAHAG}VAFAF+AEsADOACABABuA@ZA?˒A>~A=1A;]dA9nA7&A4`A0A,:A%ffA@A3>A+A4FtA:\A?|ACAGYAJeALAOAQ.ASATAVXyAWAYoAZDA[^5A\`A]OvA^+A^fA_A`_AaAa AbqAb1Ac AcvAcخAd2aAdAdAeAeS&AeAeHAeAfYAf<6Af\AfzAfuAfAfAf˒AfخAfAfAfAfMAfAfAfAfAfAfAfAf}AfAf^AfAfAf AfMAfu%Afe,AfTAfCAf2Af!-AfAeAeAeخAe?AehAeAe"Ae{AeiDAeVAeDAe2Ae!-AeAdAdAdAdAdAd-AdAd1AdAdYAd4Ad}VAdcAdAdAdAeAdAdAcAcPAcYKAc,=AcAb AbHAbAbAbjAbRAb;dAb%Ab4AaAaAaAaAa$AaAaAaAacAarGAaeAaYAaNjU>L>끶>->>ӂ>\p>V;>oz>D>>>>>5> >T>i>v>>y>>>Ô1>ř>Ǥ>ɵc>>>>>.^>Z>֌>Ĝ>O>F >\>>>u>I>>Z>d>8>w>>i>Ϣ>6e>k>q>k>g>>새>e8>J>3>!>@>W>g???d? b? ??%9?4j?E?? "? ??7~>l>->Ή>`>D>Ӛ|>ђC>>g>>v>l>@>ժ>>?@@}S@k@'@&@e@]y@I@@@A=AfA AA}AjAA!AeA?A|AA)A5AA A0AA"A)[WA,|A.A,aA#A$ A)JAAAuA\A|ALAA fA DA A A _AAA,ANfA{@@d@@V@|1@1@o@H@⪹@\@ۚA@'=@@j@@oi@@Áo@!B@@@Y`@3r@\@@@$_@4@v@G@+@@do@&@@@@e@a@zR@t`@oV@i@d:@^@Y>@T J@N@J:@E0@@yh@;ޔ@7`@2@.@*@&A@!X@Q@z@q@{@ @ @T@O?V?^_?|?f?!??O?Y?.??x?($?U?? ?D??%?:?M?@??F ??л???y?rX?j'?d c?^?XL?R;?M%?G~?B?=n?8t?3?.?*??%?!Rn????&? ? ?DV?>)>>>R>e> k>>ͧ>lj>z@@@~bN@x¤@sBp@m]@hH@crG@^2#@YS@S1@N6@I4@E@@Q@;@7@2y@.:@)@%c @!28@@@@@O@ w@ @($@I?O???z?*?y? ??`W?e?Q?a???2??E9?p?;?T? ?@?Q???|)|?t^?m?g?`hU?Y?S?M}?G'?A?<?6t?1> ?, ?'?"?Q??-? ? /?e?`>9>W>+>>>>>~>>>~> N>`>L>4>Rz>F>a>^>>>04> >߹>{>tn>l_>eT>^G>Wp>Pͅ>J\>D>> >8+>2w>,[>'>"Z>M@>f>> > >:>==b=Mf=ܩ=ݥ=֦=#=I==== =E=ι=<=_=d3=0==Xl=h=]=w=o=gR:=_=X}:=Ql=J=C==F=7U=1S_=+=%= lQ=%U= == Nr==0<hiл%.s7L h/>?_Z2??t@w@l@1@G=@^p@z77@/@@DR@@O@.@@@OL@ˏ@E@5@<6@AmAxA AAAAAA_A!A$hA'A)HA,[WA.A1SA4zxA7oA9fA;\A=$A?{AAXACYADAF^5AGAIeAJԕAL7AM.AN;AP%AQeARAS/AU#AVpAWAX˒AYAZA[A\lA]8A^%A^ȴA_}VA` A`Aa<6Aa0Ab3AbLAcAcAcAdHAdAeAe1'AePAen/AeAeAeAfAf8Afe,Afq Af]dAf1Af(AfAfAeAeAe˒AeAeAeMAec Ae;AeAdvAdAdvAd?AdAcAcAcQAc4AbBAbAbFAbiAaAarAa(A`#A`A`:*A_A_hA_9A^ A^A^%FA]3A]`BA\A\A\%FA[A[C-AZAZPAY}AYJAXwAX,=AWAVAVJAU_ATATAS4ARIAQJ#AP0UANAMALVAJHAH6AEABA>dA9nA1AD3AA3A?AFALAQ&AUAXL0A[)_A]OA_cAaAcaAeiAfAhHAiAjAkwAlAmAnnAo1Ao`ApAq"AqOAr0ArAsMAsyAsAt*0Atw2At{>P>ˈD> >;>[>z)>>rE>dX>V{>Sj@>O>Q(9>V~>\>b4>k8>to>~>"%>>>f>>>>\:>m>>9>>$>a>*>z>->݅Y>A>$)>->^>;?z?h'?f? v? ???m?#?\? ?#a?$:?&&?'iD?(d?)?+@-?,?-?/-?0!?0B?0c?0^?0?0G?0?1 R?1,?1Nn?1p ?1?1[?1 ?1?1*?0?0EV?/?/`?.,?.}?. ?-?-+ ?,?,J?+E?)su?'?%?$?"]? L???[??,(??&x?}?J? t? ?M? ?g?~>@ (@@y@f'AAwAA F A rAA>AAA]A/EA]A!ANpAaAA*AA$6A*/A( A'xA& A$dA#7LA AAAoTAPrA~AͳA!Az/A׈A9A fA A {AAhAAA@@@@@'@[-@o@@R@ @B@ϖ@k'@@˼@ƐX@bN@4@@VC@@n@u@ @H@o@)_@$@~(@,R@@@@@o@F@@}]@x@ra|@lZ@fJ@`1@[@V@Q@LM@HA_@C^t@>+@8@3@/w@*b@'@#@t@@@1@rG@ @@N@v???v6?.I???R~?ϥ??Ļ?~=?e?o?E?ޔ?/??3??$?'?T? ? ?c?}?vF?p'?i?c\[?]8?W??Qo?K;?FG?@?;?6$?2?-WX?(?$[? ?E?I??? ?-?%?{>>>.>>j>>>̥z@B@^@}@z@@tD@nS@iX@c @^{@Y-M@SZ@N@I@D@@?@;5+@6Y@1 @-oT@)@$@ w@U2@Ex@L0@jU@ @@E@?k?V?:??X?/?-?O?ʗ9???B[?? ??S?X???\?'??Z?0?i?yy)?rO?kW?d`?]?We?QM?K;?ER???9?4?/F?*#?%#`? I?? ??0w? ?? >>>Q><>[`>ج>/>>L>Ѝ> 1>o7>>>>$> >>>>G>>>yG>q>jA>c>\>UW>N>Hi>B;><=%>6k>0>+MI>%H> զ>ՙ>>G> _> H>>===$==s=ە=ԤL=}=b= ===/=='== =[= =fs=o==|u=tm=l=d=]a=V-=O3=HrE=Aa=;q=5n=/~==)=$+=dž===U= ߍ=Iv=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A7VA'AA9@*@b@<@C@`@@h@f@ @@@c@)@@@خ@΅@ǿr@Īz@IR@T@7@@@@~@n@q@pP@u@@<@@P@@A@f@@@_[@@@@@z@'@@ڥ@@@@d@D@:@B1@k@@A5@@V@M@@@x@@`W@@@h4@6@^@@@c@n@ί@@>@[@|@zN@Q@;@@~kQ@x@r(@l@@e@]=\@TH@K7"@@@5?@(E@@?{?? ?sZBy\E3=4?r?׏@]@79@V@s@O@i@@@X@@r@@@Ƃ@f@~@@@!W@4@N@sAA}A 7A AA3AOA VA%lA)خA-A1KA4FA7A:uA=W?A?AB4ADAGAAIAKOAMdAO#AQASsAU˒AWAYA[A]A_FAa-AbAdYAeq AfA`A_XyA]A\ AYAWATq APvAJyABA,A4MjAHAR`AYA_=qAc~Ag&Aj<6AlWAoJAqfAsKAuiAvAwAy=qAziA{|A|zA}bA~8A~"A-A,qAy>AAAwA49A)AAA AAA/AAںANAAwA?AAAA4AAAwfAoAfA^AVANAFA?HA7A0UA(A!AAA AYAA>AAAfAvAچAAARAAwA$AAAAAA=AAAA7AAA|AxAtAq Am)AiyAeAbA_A[@ @@@ =@@[@@@@/@D@#@L@~@AbAɆA#AjA A qVAQAAbA AJAsA]A*AASzAAAAVA A%A pA eA+AAj@A@O@@j@@@<@z@Ȳ@@ǹ@U2@]@o@@3r@@\@xl@f @V@@F@6@&2@@ ?l??u????{?{*Z?g&?T^?C~?3?%?e ? ?>>>8m>+(>|_>œ>i>f>ij>J>]5>Y`>>?? ?}?Q?+n?2X??9?@?F?L|?ST?[?de?i ?l ?q?w?}?-??WT?R?+??+??z?????=?q?{?la?\?L??kP?i?hE?f%?e`?d7A'A'zA'WA' A&A&nA&!-A%A%A A$A$1A#0A"KA![WA l"A}A}AxwAAAA5?AmA"AމA A OA A UA DA!33A!l"A!zA!A]AeA2aAAiAǤA AAA A eAAлAAA<@@Ln@@Ri@@@H@+@H@~@:@6@@(@%@b@ @@1<@c@;@@s@@@;@@@ 9@@n@A @&@@#@~6@w@qh@k^@e@`@\ @V@QJ@K @FR@Ay)@>><>> 5>,>r@@}@w|@r5@lB@g B@a?@\0@V@Qx@L,@G}@B@=ԕ@9/@4r2@/.@+k@' Z@"H@@w@r@8@\@ K@=@@$5?m]?E?1{??ߓ??ӏ?? ?žo??)?K??)?@?Y!?w?A???{ ?r2?>L>Y>Z>ݏ>>Ѝ>T?>H>h^>>)=>Ǭ>>z>>>>>:d>>>>wd>pW>i>a>Z>TE>M>Gq>AOH>;[p>5l>//>*'>%@F> >>'>S>{> >>h>A=t=D= ==ڊ@=Ӝ==\H====&3=='2==5=="== =^=z=rۥ=j#=cU=[=T=M%=G=@$=:L}=41=.H=(P<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AAA HA YA A";A"MA oA̸AIAnAArAA?)A A SA{JAlaAC@n@;@@ޔ@台@K4@=@٣@R?@@4@̈́@O@ȶ@Ɩh@ıp@C@,g@@]@4@+@@@X@2@wG@õ@ @@"h@ӄ@F@v@V@{ @@@@$@@YK@@.@@]@@@@@@@\@(@@P@a@@T@'@I=@@W@t@@-@H@L@9@@2@i@@W?@@@@@!W@Z@H@@.@@@2@~B@sV@g]%@Y@J@8FJ@#J@ [?c?p?$BBvK7ſ=^5?IR?@)@PM@s@@mH@<@\S@@.@#y@@@AW^A wfAA A[A"AA&EA+.A/N-AAADAH!AKGANcAQwATAW4AZA^AaTAdAgAioAk0AmAoAqĜAsAu \AvAx{AysAzA{A}A~AAAqAAEA6AA A+ApA[AWA"ATAAqAҽAA A.}AF?AYAjAxAAAFAAAeA$AAAGAzxAo5AaATAF A4A A ~A8AAXAA+A}Ac AGEA*A AAAAAlAJXA'RAGAjAA:AkQAC-A7A;AĜA+AkA>AAzAiA A~*A~FtA}HA}xA} A|A|(A{A{4AzhAz=qAyAyuAxXAwAv)Av&AuRTAtlAssArbAq5AoAnjAl$AjAhgAe+AaA\AU&AE A8dAUAA`AhAnMArAv$AyɆA|A~]AADAjA3AAA)A?A5?AASAyrAA(AuAA(A:*Aq AnAѷAA"AEAeAuAxAAAیA"AA_A:AqA#:A)A.}A2-A4A6A6zA6A4A2aA/OA+A'RA"4AAA.A 7AAAAAAAAŢA6AaAAAAAAv`Al"AaAWAM6ABA8A.}A$tA7A.A%AAGAsAA2A˒A'AAALAIA{AAAzDAqAiyAaAYAQAIAAUA9A2-A*A#nACAA"A_AAAAAAAچAԕAΥAA-AARAAAAAAAAbAA_AA~AzAvAr|??ؚ?rG@@ |@.c@=&@Lm@_H@xU@@@@s.@Nf@0@^@N'@@@6z@͞@;@=@]@@>B@@z@AӄAA;A y}A A4AkAA>bAA: AǤAɐA@AA/ADA!AVAAAAA8=Aj+AYA A AA@<@@@۶@2@@&-@$@*@V@ܱ@@@{@j>@Nr@5P@I@ Xy?M?٭?'R?,??,?a=?=Vm??>>;>gQ>̗>C> V>γ>>ߛ5>>?I? r?+?=C?!|?(?0Y?8F?@?I6??RAN?[p?eK?o?ya??`?g?2?G??4??S???q??i? ?U?WT?'?#???p;?D?????a=??$?4?{_?'??O???? ?'g?B?^?|???w?L? ?C?L??J???r\?Ln?(?!????9?s?W??>?i??5~??A?V? ?,?A(UA+A/;A2p;A5oA7A8IRA8A9A9cA9'A: A:4A:;A;>BA;A;A<]dA<э@:"@5@1@,H@(NQ@$@@@5@+@k@ "@ 1@=@Q??t?????-w?ӈ??Ȯ?o*?R?X?s?T?(?;?J8??@?=????}?a?E??zj?t+?m9?g?a?[?V)?Pq?K?Eo?>$y?9 1?4!\?/U7?*a?&V?!N?8X???? ?)??G>I>,>;>n>ʆ>L#@@v`@ya@st@m@hS;@b@]N<@Wo@Re@M{J@Hf<@Cjj@>U@9@5 @0o@+@'@#0@E@ @m@>@V@ )_@w@Q@S;???v? ?˒?ٱ?Ӽ?|?C-?½?Z??&??#O?g??L????wp?{??}?vJ?oj?h?bj?[?U=S?On?IH?CG0?=?8?2?-n?(y-?#????1? ???>>>h'>j>ݠ{>}>О>e(>Y>yh>>:i>ض>>H>s>0>.Q>>I>>>T>wv>pl>i8>a>[ >TR?>M>Gx>AT>;]F>5`>/>*>%5> Z>>>>> > >D>= =G==KZ=$=5L=|`=?="==c==D=ݰ=Y=z==S=B=ɾ=t=z=ra=j=b޽=[z*<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@a@kA_A A{_ALOANA$A)?A.jA3eA7OA<ACuAI AJAL/AMAJADA>~A8A1A+YA$A9cAsA[AGA mAAFAG@|@P@ @]@(@@7"@ޮS@h @U@f@ҩ@/@@C@ 2@Ȳ@ǧ@ƥ@Ő@Ĕ@÷@@¡@f'@.@<@\@+A@;y@u@d@@@*@@i@@i@q@@W~@7@R?@Ô@@@~@@"@/@<6@N'@m@ļj@"S@9C@@Į@3r@Ö@پ@+@!@}@k@%@@cI@P@@@@*@`@@>@@+@f@t@+@}V@v @@Z2@&@"@xk@i;@Wә@D@-F_@]?Tv?C?<oEgPHX>*?@Ou@@TL@8\@Ԕ@!@@ @AAA pA{JAHA~A&A0A:AA@AEAJ.ANAR0AVSAZ/A]~A`:AcjAf lAhkQAjzAl?An!ApArAtAuAwdAyA{oiA}6zA~rA^ANpABAAA?Ay AAFA/A9A4nAaA2aAXA%AKA}AA =AdAAA.AiDAA|A A/OAIA\)AhAqvAxA~(AAAYAA~(AuZAjA]/APAC-A2AAAA A6AAuAdAEA'A+A,AACAuANA%FAA}AnAtAEAAAHAoAe`A8A rAAAcAOAAAAAMAAAAgA)ADALA`vAYAAzDA%FA^AkAAA 'AA4AtA~A~uA|?Az2AwAtAp>AkAc}VARoAJu%Ae*ApsAxA~AAkAS[AGAYAAAXAAUAAAPA/A^AA@OAAcAS[AAA(AdA=AjAA%AJAmAA_AAٴA/A4A.AA)A49A:A@33AAACAE+kAF#AHAH;AGAGAGAGAFsADeAAPA?kA=?}A9͟A3OvA- A&.A!)_AjApA{@A@AO@@ o@/@@s@@b+@9*@HV?28??8G?p?k^c?JZ?:?*?&1?$?"?!l?>?r??\)??@? ?????U?ȟ?-#?/?@?`AA>wA_A}.Ax9ArAmVAfA`pAZbAT@ATAU~AUZAUAUAVbAVMjAVrAV =APAK%FAFABA>?A:zxA6KA37A/~A+A(4A%qA!KAAA=fAfA_pAXAA \>A AAAAfAPr@y@CB@@@a@z:@޸@U@Ց}@")@?@U@ú@^@@x@@S&@X@@<@ @{@݃@#d@X@@ @@&W@e@yp@sF@mC@gg#@aL@[o@V77@P]@Kla@F=q@A9@<>@7K@2z:@-q@)7@$@ q7@8@&@@:@ e@/@Z@U??R?2?r?y?\?sC?j?n?H?C-?]%?9??i???q?J?:?E$?j?e??r?@?}O?vQ?p$O?iʰ?c?]z?WV?Qٲ?L7r?F?AU?<H?60?1{?-?(J?#??s?J? (? u? ?~? W>3>'>Z>`@@~@c@z@t@n_@i@cT@]@XE@R`@M@Ht@Cd0@>mr@9@4F@0%@+@' @"v@x@Se@?@C@_@ @@:?ag?w??$ ?$?v?Zq?ff?̘ ??j? ?S???ߏ?&l???g?y)?Xd?Q?h??{G?t?m3?f?`Z?Yg?ST~?M4?GA?Ay)?;?6ak?1,?+?&D?!P?<?#?!l?? ?iD?h{> S>z >!>>>S >ǰ>l+>?->?h>k>‹>C9>F>>)>>>q>>b>d >I>|>tT>m]>f>^P>X>Qq>J >D>>>8/>2i>-J>'>"(>>>M>> q>,>[===q=`="= |=6i=ȕ=)=I===eq=?==ou=q>=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@ h@9@fB@#@ @Q@ڙ@AtA*A#.IA2'A?!AIAR*AZA`pAcAdoAdmAd)AcAaeA]XA[AXAWNAUPHATRAOAIvADA?a|A:DgA4cA/A*MjA% A A|&AzcAAe`A.A CBAA'AbA#:@}@6@ @@*o@@S@@n@o@@,@6@6e@C@@E@Y @@@qv@:*@rq@@ܷoP ;.I!`k>ܭX@W@j@m@,@C@Sz@AtA A@AAA&QA,A3qA:!AA.IAG}VAMMARVAWTaA\A`aAeXyAjQAnAr"AvAy\)A|n/AQA%ARAMAA&AAܒAAAcA#AA{A&LAUAUAZAnAA.A AAACAA AA#ADA]AmAsAnA;ACAIA'A9$AQApAA AA2AE9ARA\)AaAdAdAbA^AXAR AJAEmA<6A.AIA =AA;AzA}AAxA\]A?HA!-AAAAAAA`BA@A!ADAA=A{AYA.IA AAAvAFtAAACAwA@AfAAASAAA1A>AAVAIAAVA'AABA[A5tAAiA/AUAYA0UAǮAA}F AvSAiAn*0A}_A?HASAhA$ANA_AAAAAA=AA}AfAYA>AbNA[A7AjAnA8A6zAo5AAAA#AGEAgAA!AAdAA AAAA&A0A;AFtANAQASASAR AOAJAEA=A5?A,=A#AA A.A AAAAA,AAרAAǮAAAAnAAAA|AqAgA]AS[AIA>A4nA*0A 'AA AAAAAAHAƨAAA0AAAAA|AtAkAbAZARTAIAAA9A1A*eA"A AA ~ASA(AfAoAA AAmAHA#AABARA= X=s=y=[=d="b='=,x=1=7=; =?L=CI{=G =J=Nܧ=S=WBr=[=`=d΁=ii=nn=s=y1=k==+==Wd===,==^=>>->nD>M><:?2a?@|@=@@U2@@<`A_A "A!BAdA"DA+jA4+A=_pAG AQ5AZq A_#:Ac5AhgAm֡ArMAwAxaAziA{?A|A}A}Az3AwjAtAqoAoeAcAYiAL4A@A4E9A$A)~A S@[@7@η@@@e@>@@eL@E'g@ w@?+?ſH??Bp?e~?A?. %?" _?R?@?0n?")??x?!t?*f?7?F?XtT?fxK?tD?n?? ?+?j???hs??H??k?.4???1f? ?@@@@b9@f<@l7@tT@~=@@D@߹@-@{@ɰ@ +@ f@ J@ C@ Se@ @ @ @ /@ @ Bp@@W~@@n/@@Y@@@-w@E@If@@>@ @??z??)t?_?5?Ԫ?f??Q?:?9?x? ???;??@?O?H?A?T?R~??Ȁ?"?:?|?4/?|?V?f?UArAoCAlAiNAfAd A`hA\[AZmAYuAXAV_AU-ASmAR_pAPAOkAN;dALAJ!AG)ADAB!-A?[WA<'A9A7GEA4XA03A-AA)ѷA&rA#$AA:AAAnAOA BA DAWiAzA=@8@{@9C@U@ B@@F@ڌ@@eV@H@'=@&l@E@@@]@E@@y@e@jj@a@&@a@@N<@@i@ @@~?@wS@q9@l&@fO@`@[VC@V @P@K@F9@B"@=sm@8@4V@/#@+C@&\@"z@s@vK@@@@ N@@L@g?H???莊?b??C?N?˩*?*? ?Z???I?-??9??/?%??L?!-?{?W?x?qܥ?k-?du?^\?X8*?R>?Lo?Fɰ?AK?;f?6?1N?,B?(?#b?:?ʛ???ɰ? \?N? ?7>>@,R@y)@z@|@uW@oJM@h@ba@\q@V@P@K@E=@@F@;@6 $@1")@,[@'@#.@6@@T@F@T@ z@@ ???P3??`W?+?"?B?̋???Q?3?9.?a=? ??n??#??HV??? g?zfE?r?k)?e ?^u?XR?Qբ?K*?E?D!??e?9?4&?.?)ͣ?$? ?U?u?Y? ? r?X?י>u>L>H>;G>a>ع>A>@>7>>,>B>">8>9>>>/{>>-E>݃>X>>yQ@>q>j0>b=>[[>U>Nu>Hl>A>;I>5>0%>*o>%A> k>>>`> Ǐ>Q>&==z==o=!=b=m=˯F=&=ъ=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<QA:T/ρYjg>*e?Y@f.@@oARA.AO|Aj1'AyuAAAAADgAAaA>AncAoAWA|v`AxuAuAqAAkSAeA_͟AYAT ANHAHAB A=A8A3mA/aA+ =A'A#]dA̎AA5AAw2AkAAkA r\A +A A AxAAAoA{AAW^A\AAgAAAA$A\AAAAOWAACAALANA:A AAdAA…A%A3AzDAVA A ~A FA lA -CA RA A:AAAN@@@@@@ߙ1@@@a@;%@@3@.s@[ @h?I6ӿ?I@ ̸@{@@]@ @a(A:AA.AXA%A-cA5 lA<ƨAEkAMmAU#A\AbAhAmqAqdAv0AzWA~CA"AAncA A4A/A'AbAbA-AAAAںA:A0!AAA}A=A|Ay>AAA:A%A)AJAA)A4AuAAA!AOBA{JAAɺAAA0!AIA^5Am]AxAAAA_AA4AyAq AgA[ANA@A2-A"hAA;AAA,AgAAAzA]A>A!A(A)A$AAoiAHA!bA AϫAAzxANA"AAAAiDA8A+AԕAAlWA6zAcAAAQAFAAAPHA =AUAuZA%A AzAAAOA5Ac A/AJ#ARAA!A2aA_A-ASA)AA}4A3 AYA_A҉ADARAK^AAbAhAjALA+AbAAAXAA`vAA=AAAIAAKA_AR AAATA xA/AQAoA AhAA˒AAAAMA AAxA!A%A(A*0A*A*eA)*A'A$@A ACAA:A JAAAAA AߤA?A'_>d> V >>>>!>>#;>'7T>*f>/>4s>9>?>E>Kg>QS7>Wj>]W>d$>kG>r>zP >>'>N{>ka><>>>i>s>>??KQ?`A)AZcA~[AADANAvAzAAAnA AA.}AqAAOANAAJXAyAcWAN/A<:A)ATA֡@I@9@`@2@@@}(@X@9W@U@? ?DR???zӦ?]G?C6i?09?&? <\?;?!%?%m?0!?AP ANAKAGԕAD/A@xA=7A9A6DA2oA/A,_A)$A% A" ApAJA9cA:sAKIAjA rA !AEAA@@3@@U@M@M@e@@@.4@JM@ǃ@@L@@?@X@>@;@P@zc@@(@y@@/@ @Y@G@*@}@w^@qI{@k[@e@_|@Zk@U 5@Oρ@J9@E@@Y@< @7@2H@.@*M@&~@!3@@>@@@}A@ "@ @@d??ߤ?'?9?< ??r?`?iD?w?t?>??$?LY??i?n?6;?*??? ?K4??~+?wD ?p?j?c?]?W?QÜ?Ly?F?AP"?<$p?7?2?h?-?(?$r;? ?? ?0?? >B???e>!>=>>@iY@|@vZ@pJ@jX@d@^@Y5T@S9@NZG@IU@C@>%@9@5*Z@0tT@+؄@'W@",@U@q7@W*@V.@l@ R@@>?e?z?c?#??t?[l?g?̛?I?s??b?ć?l??E??#>><>>>\>H>Ǜ>>>3>G>J>o>>>C>V>f>:>f>?d>~lL>v>o:>gI>`t>Y}>R#>L+>EΞ>?^>9>3_>./>(>#h>C$>Et>n >> .>Ă>|===Ӟ=Cl=x<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<V@%j= jV,(YUV}MQ+)%$ei?@bK @@콺A 7A!]A5TANA\wAb'RAgAmAtNoAFAMPAZAcPAk AqxAw~(A}A{AkA7ALAAiyA3AyASAA"4A,AAAlWA@AHAAAAbA2-AAA6FAȀAPABADA[AAvAA!bAkAAA'A]A.AjAAAVA7AMA^AmAyAAA:AAAAAA{AqAAeAYALdA=A,AAGAAԕAAA_AlWAOA1AAAѷAAPAiAF?A"AAEAAAbA9AbAAAAaA4ASAgAtArGA>A AAAbNA&ADA_AgA!AAVA>AQAA4AHAdAoArGAsAPHAAsAAAAtAAABA^A|HAA<6AAAA[ARAA;AcAAAoA5 AAncAfAtA AQAA JA^AAiA/AiAVAHA"A%AK)AlAARAA9AAJA A+A#nA-CA5?A;A@ADAGEAHAHAHAFtACA@A?>Ű??y?@,?@?Ah?C?EG?Gy?Ie?KV?MM?O?P}=?Q ?SUX?T"?V7i?VZ?;*?94?6b5?3?1'?.?+f?(?%?#?"?!~?#?&l?*6?1j?@?[?@ AA3hArAA1A A?AA A xAAUAqAרAsMAr0AaFtAQrABA24A!4A&-A{_@/@I@J@@@@;@ql@bK@R G@86@'@!@I@s@{@$@ @@@e@*@@ c@!@+@0@@@!@"2#@$[@&@(L@+@-M@/@2:@3@3 Z@4p@5ֶ@7?)@8@8@@8y@8@8@9C@9@:K@:Ta@:A@:@:@:@;@;3@;W?@;z@;0@;@;@<r@<>@ڶ>hf@`@~;@y@sc@m@h4@bE@]W?@X @R @M@H@C@>զ@:@5a=@0Ɇ@,I=@'e@#P@QY@,@@%p@Bp@ v`@D@O@O?4?o~?Ӯ?]???? E?V??S&?1???>?#?{J??+?:~??b???|t?u_W?n>?gO?``?Z\?S8?Mh?G__?Ao?;.?6>}?0b?+?&?!?U???P? ??>P>>A > V> >A>Ҩ>@>>z>#:>s>>>^->R>l<>\>>:>=>F>a>w>pC>hͧ>a>Z>SZ>M# >F>@>:I>4>.>)>$/>p>>+>w> >}R<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@R@ d@?M?A5?(?ǚ?H?k??l[K?F,?(?f?->卽>9>H? '?4g?m*c?T ?@ V@7I{@ao@}@@@0@N{A\]AAA,?A5L0A<OAAy>AEvAIYKAK^AMiAN[AOAOtANALHAJdZAH2AE AC,=A?_A;YKA8A6SA3A1A/oA-bA+A(ĜA)qA)A)5A*\A*A+?A+A,6A,A-MjA. =A.A/]A/A0[WA0dA15?A1A1vA2.IA2A3A3A4eA4A5VA5A6A6sA6BA7"hA7J#A7XA7QA7A6EA6A6|A6L0A6CA5)A5A5IA4#A4\A3A3*A2sA1A0ȴA/[A.?A-A,YKA*A)nA'aA%AA#A!gAAAJAAAcA -AA@ @j@ά@2@@--@}@@ٌ~AYjA`BA!YKA-{A7 lA?{JAFAL6AMںAMAS=qA\AjAu9A}%AA 'AMAAA AܒAAA{A?AAwAfAJAAvAKA.AAOA(A1'A7A;A>AB[ACA>A5?A)ACA JAA A,AHA$AAsAWA:*AqAAںA$A_AuAR A-A1AAA@AjAAAA)AAAiA;A PA5AA}VAKAAAAyrAAAAA_A}VA+AUA AAAAAbAA!bA'A,=A/A1A2A2A1A0!A-wA)A%A A AAVA+AAA5AfAdAAXAAzAqAAAAAAwAlAaAVAKAA A6FA+6A \AA AAZAABAA^A AAAA1A\AA|PArAiA`vAWsANAEA=-@1Mj@5t@;@C&@J`@S@]@g@ro@}@`@@ @@?}@j@@N@k@g@@+@F@F@*E@n/@@@@B@P@>W@@(@#@$t@ 9@;d@@S@^@@)@AA dA[Ak1A(MA3 A=tACAJCAN ARw2AV'RAUrGAT}ASjAP:*AMADA9SA/@<ѷ@;@9:@7"@4 @2@0F@. @+C@)@'Y @%+@#)@ @X@@@|@pe@k@m@v@@ @@@@EN??@?? ?u??q7?`?ߡM?MAyZAtAp?}Ak҉AgzAc6A_AZAVARAO AK8AG{JAJAOWAUAU^AQAMAICAE6AAߤA=EA9ȴA5A2L0A/*0A,"A(HA%%FA!VA,A^AyA/AA A AAA\@@b@q"@@@I@@\@P@P@E@Ē@@\@@@Z@7@/@>@e@j@0@H@2@&B@1@[B@@}@wq@q}A@k @e[@_@Z $@To@N@I@Ds@?e@:yh@5@0@,N@'@#c^@@@ی@@ @ U@@@*?P?u?1???ڽ??7?ɮ>?Hk?J????V???h?]?w?|?t?>.@@{R@v @pg@j"@e5+@_@Z\@UE@O@J¤@E#@@@;j@7=@2@.@)@%1@ y@@@@@ (@ @g@Ѣ??????6??t??@?Ğ?")?q?y?|F??$??lv??Z?c?E?C?\?!-?w8?p??i&?b?\?U?Ok?ITv?ChI?=/?8 ?2?-Q?(-8?#-?R???.? <??>>?>n>~x>oa>۔>>v>0>D>/>ru>e>x%>8> >/>c>s>8e>֮>d>vy>zJ>s(>k+>dS >]<>V]>O1>I<>B>6>1E>+>&YK>!"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@9@~@A5@4@@Ri@(@ @@(x@o@@ye@p@g@[@N@B@6($@'H@)@@q@'@8@J}V@_K@t5@@Z@\@õ@I@Xd@Ԍ@ޅ@:*@@A@AA SAuA|;AAA/AAA$AA]dAZA{5AAE$AA*AALA0UAA@AA\AAAg.AHA.AAAPHAA ADAAAAAOAAsA"AA A!A"TA$"A%~A&A&A'TA(zA)A*A+pA,PA-0A.A.MA/֡A0/A?1A@'RA@AAABMjABAC~AD AD~AE_AE|AEAFXAFAG7AGfAGAGAH$ AH@OAHQAHXAHVmAHJAH8AH&AHAGںAGAG!AF4AFbAEiADACABMAAA@A?A>W?Ac(@@~ANASA'QA2A:0AB9AKATA]Ag}ArA}CA AAAAaA+6A&A`vAAAAAcA AbAAXEA!AAAAAVAKA-AAGAA=A7A͟AW AҽADAIAAjAA AS[AAA@AHAuAA[ANAAA.IACaAWAiArGAwAyAxAu%AoiAgA^jATaAGA7A$AAA A#AAAzDA]A?A AApAAOAAYAVAAZA"hA[AAHAAA6AWsAu%AAAAHAAAA_AA+AOA#A&A(A)A)A(A&A$@A AxAAA AAA`AcAA/A,AAUAAwA:AAVAAy AncAcAXAMABA7A-A!A$A JAAA"A|A A̘A'AAAAAAA|PArAiyA`BAW ANAE9A=A0!?D|?C?K????|??JM?ѣn?U?c ?c??sm@^@@ @0@.@%F@H@@#¹@%;@(@*-8@.@3@>@A;@D}@G@J@M@Pc@Q^J@P}@O@N4@M]@M@L(@JW*@F[@BtT@>@:L@6@1@,Κ@(u@$9X@ @@T@@@2@@ @ k@{5@??X:?m????慈?%?u?_??ί@"@r@M@ e@'(@;:@"@.@@;@8@5@2۶@0 -@-A@*Zq@'[@$j@!@@u@'@v@x@8G@/0@ -M@;@Z@??p&?>?????AzAuAqGAlZAgAcbA^˒A]QA]ƨA^A A^jA_7A_A^jA[_AVA AQAMyAI˒AFC-AB{JA>A:A6AA3>BA/=A+A(OvA$wA!AAA|A-8AAA A AzAq7As@@V@@J@@y)@4@g@ @!@V@ç@@@B[@@@"@r@uO@m@@@o@}@rq@ @@H@| @u @o}A@is@c@] @X:T@R@MH@G @B,@=@8\@4 q@/zx@*@&5@"D@ @ @P@M@$@ j+@e@;??h??h?Z?嚕?߅?٘? ?0?ȴ?\?'??"?Q?? ??Cl? ?B?S??2?}k??|?uU?o3?h?b]?\2?V2 ?PV?J?E???:lL?5I?0K ?+k?&?"??0??@? W?]??,> @]@z@td@nH@i8@c@^^5@YM@Sخ@N@I@D@?@;@6aR@1&@-D@(o@$@ DR@@ @@*@ Z@<@U@k??@?n??ᝈ?o~?f{?σ??-?V?c?1?!?2?c?9?#??]%?%? ? 9?'?~?w[?p.?i3?bi?[͚?U`B?O?I ?C"?=b|?7ˣ?2\?-^?'?"k??dA??\? ?s?>[>*>h>%x>>>>Ԙ=>#)>r>C>>#>>+V>>>T>]>s>N>`>O>0>za>r>k>cK>\>U^>O0>HA>Bvu<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@'(@@$ @X@V@Q@@j@`@@@c@@Z@@_@4@N@n@A@~O@oy@\ @I@@3 =@@K?? ?@@y@-Q@B!@U?S@g\}@z"@@gb@%@,R@ @$@M@ܯ@;@@`-AA8=AԕA 1'AAAp;A'AAAA2lA}AzAVAAAA.AMAACBAAAIAA SA A BA!L0A?wA@AAACrGAEAFl"AGAH.AIxlAJHAJAK}AKAL4AM#AMϫANoAO8AOAPAQARyAS/ASEATuAU AU_AV!AVAW AWAX AXsAXsAY8AYAZAZy>AZA[GA['RA[=A[HA[CA[/A[ AZ]AZnAZg8AZ>AYAYPAXAWAVgAU AT{AS*AQAP ANVmALZAJHAH+AEAC($A@VmA==A9A5A1A,A&A _AAA@A@*@huۡ>r!@ANAA/iA>+kAH APAXUA]AcQAkiArAzKA)AASA7AATAAWAAAAyAA^5AɺA-CAA]A:*A^ANpAVAAƨArA@OAAfAxAA($AbAA~]AZAD3A7A A/OApoA=A ASAFtAuZAA)AAoAA!A+A2aA6zA:*A;0A7A/A$@AAAAAA}AYA|A`vABA#:AAA AA_ArALdA$AAӏAA~ASA'AAjAApAAAA|AA~ALAAfAA|AGEAbAA'AffA+kA5AAr|A1AAAbNA_A^A{A&ApAq A A A2AA2A'AAGEAwAAhsA AIAA&A$AS[AgAsMA0APAS[AAm)AAAAMA\]AbAAHAHAL0AA%ASAA-wAw2AA>A2-AffAAUAA A-AK^Af2A~(A“uA¦A·A?AAAmA5AZADAA4AAAAAAAAAAAAߤAAуAAA¹XA°A§AžA•A‹DAoAwAm]Ac AXANpACA9XA.A$@AA(AAAiAAںAHA AAAAAA A4AvAm]AcAZAS&ADg?K?Cl?~=??ʂ??I{??s.?F?lL??߼?9??w@Y@.@7@@@ x@'@/K@76&@?|@EX@J|@P>@[@a@f@jT7@n@s @w@{]@}c@@!@5@P@ֶ@-@T@w@4@a@@Y6@ @@9m@@@}nY@y(@ts@p@l"@g\@_ԕ@X@R @K@ECl@?\@9@4@/{@*}@%D@!L@ӄ@@x@@ ʂ@m@N@A?q ?W?s??2@`k@@ 6z@@,@5@Uz@yI@M@@@@͜9@s@@w@I@qa@лAA6A2A\AA5iAAAY@s@Ex@) @@辡@@ݛg@U2@$ @@m@R?@@x@@@@k@@@f@NQ@/@ @@õ@Y@@{ @{5@{_@{t@{@{@[@Oa@D=@:T@1@*@@ @@%@@@@q@-M@`@B@ʂ@@7v@oT@@,(@4@@@#@e@U@w@@@ʬ@@@M@@c @q@@@@Z@9@}@ @jU@&@}@*0@i@f@ @y@Y @@@1@$@}@)@Z@~{5@z@wI@sı@pL@l@iV@e@bo@_#@['@Xv@T@PJ@K@G@C@?D@;@8 t@4.@1$@-@*$@'x@%v`@##@ ٔ@@]@+@P@@bc@.I@@ @ L@ @@?)@? ?AAA{AtAmAfUAaXA^PA[SAYkAZA[A\A]A_YA^AZW?AVAR]AOiAKAHoAEAA33A<)_A7AA2zA-A)A%KA".A{A1AGAAo*A ,AAA@@%@QD@@@^@T@&@ @_@@9@^@@c @r@@n@i@\S@<@1@At@j@X@@|@@R@x@r |@kg@eWi@_=@YLn@S@M@HjU@C+@=@8ʂ@3@.@*-@%~@! g@"@^ @3@#@ /@U@@??٩??搬?0????^_?Б?i?'? ??>?????8q????!?e?^?xq.?qy?j?di?^?X?R?LI#?F?A8?; ?6:?15?,j?(@l?#?>%??Q?u?? B?2j??>\!>@s1@n@h@c9@]@X@Sv@N_@I_@DtT@?0@:P@67a@1@-(@(@$s@ :@@ @@4n@ g@@@??LD?2?6e??۸?ճ}???Ās? 5?^?H?{? ?D???b??s?j?kn?8~?3 ?-?(?#0?ē? ?u\? ? ?u?^?e>M>h>M>;>]>ղ>9m> >տ>>(A>>' >;> >g>>ZB>>m>*> >|>t<>l>eY>^=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@б@0@@юL@˧@@@ѵ@ag@0@5@π@ΰ!@@̶1@˫`@f@ȍ@:T@o?@&@-@@ @UG@ @@_@{w@R@1A@3?G?j@r@QH@.@QD@Н@Ǐ@mA8|AA =AA&AA.A!AAAAA خA!A"bNA"A#A"A"A"A#A#L0A#tTA#_pA#q A#A#A$A$FtA$rGA$A$_A$zA#oA#g8A#GA#ZA#"A#A$9XA$A%'A%A&FtA&WA'A(dA)5A*{A+ A,iA-A.K^A/oA09A1A3,=A4y>A5 A7LA8A:yA~A?rA@AB?AC\ADAF!AGhAHAJAK;dALqvAMnANAPuAQ2ARcASATAUAWAXAAYtTAZA[oA]AA^A`RTAb1AcFAdcAfAgAg`AhRAizxAj.Aj Akv`AlMAlqAm&Am+AmAnAnB[AnbNAnsAnv`AnkQAnRAn,Am8Am AmaAmAl"Al Ak{JAjAj($AihsAh!AgXAfAeuAdAbfA`A^A\AZy>AWAU<6AR=AN)AK=qAG7ABq A=&A7A/A'dZAAf@@ɂ@`H~@dO@CAzA/\ABzARA^WAiApAsA{!A A-CA??Y@ @@q@>@ M@ Y@$@{@P@@@@ 1@#ֶ@&@* @-9C@0xB@3@7%F@:@>@A@EC@H@L@Tx@Xt*@\L@_I(@bPH@ea@h}@k@n@r@u[@v@w׈@x@z N@{'=@|D(@}b@~@@b@u@~FJ@|@z@yC@wi@u@t @r q@n1@kX:@hd@d@an@^X%@[5T@X@U@R#@O4@LP@Iw\@F@C@A'@>@<:@9@7@5@3T@1,R@-{@*@'?S@$ @ e@t@8@+A@k<@[W@)@{@x@/*@Pi@wE@@ƷAAAA\)AA?A ϠAaA:@[@@K4@F_@i@ٲ@ @γh@i@@d@@@ @^@@M@6&@\>@@@+@9@Go@W?@h@{5@2@@o@ә@)@@H@ @Ec@l"@@@g@@_1@@@@O@@T@8@@@6@-@'@!@~@@z@@@y}@d@Ë@h@k@@Y@@"@L@u@]@w@Ov@@B@'@7@<@@wp@H@K@@!@@/@f@V@B@@8@@K@p@=@@5@F@@@@mr@"@@@y@S@6@~D@z-8@v&W@r0+@nJ8@jnD@f@b@_1@[:@XP@Tx@Q @M @JP@FƓ@B@>@:@7M@3Uq@/@,@( @%@"@a@"@p@@1@4@W~@ {@ Z\@@"@3@h?O??A"A'RA{AvArkQAmAidZAdA_xA_y>AcOAg5AfeAaA\xAWc ARkAMƨAIqAEcAAkQA=A9A5NA1A.&A*kA&aA#-A A -AA rAJA[A !AnA!A@@@p@@Ft@@܍@a|@S@d@ȓK@&@A@k@&-@@O@|@:@@@(@M@@%@#@K@@t@x@rr\@l0U@f6e@`+@ZC@T|@N܇@I^@DT@>@9@4\@/ϫ@+o@&s@!@P@Ec@@ @ @ 8G@v@?|?&?̣?:?ә?ۙ?Պ?ϥ??U2?}??~|?4???V?z?L?R?9?=G?_???z?s?m4?f?`\?Z7?T>?Ns?H?C\?> R?8?3v?/B?*L?%?!ER??r??U? ? /? ?>T>k@r6@m"@g4@b3H@\U@W@Rs@M^@H^_@Cud@>/@9@5@O@0F@,8\@'ֶ@#3@S@4@+@8G@Y@ @j@B@?+?P?/?澌?t?Q?T7?{?Ǐ?7"??$?V?N?h??r?p&?S???r2?y?(?}`?vZ%?o8a?hG?a?ZE?T:?NY?HN@?Bm&?߃>5>~>&>>ڳ>+>ͧ>j>Z>y>Y>8\>>>>M>/>9?>>]>"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@~@@|@_@N@e,@.@@E@p@sm@Q@@@#@xl@@@@?S@ؙ@"h@>@w@V@9@~4@u@k@^V@O@>0@(x@ X?ͅ?>TRp_?@@b@<@@3]@ѹ@@燩@|F@+@k@@*@@^@C@л@@?@@u@}AAKAjAAqAPA2AotAA A A AiAAqVAPAAAAAAUAA A IA"sA$XA&RTA(rA*KA,A.cA0mA2A4KA6A8A:AEA@ ABADAFAHaAJ0AKAMXAOAQg8AS;dAUAWAX]A[$ A]A`AbiAcAeF Af AhAi<6AjSAkEAkAlIAmw2AnjAog8Apl"AqwAr7AsbAtUAuWAw&AxxlAzAA{A|A}A~vAAEAA;AXAIAAzxAdA_A=APHASAIA/A2AA7AmAAAAApAMAA?AA~{A}U2A|{AzAy>BAw4Au AsAqAoSAmAjXyAgRAcDA`@A\4AWU2AR lAL lAECAA+ARTAUA'AUAAeA=AyA(XA?AbAAAAA)A AYAAA˛ AHAAvArAqvA;ACAϠ'AABAЉA)AA=ApAѝAzAAA/AK^AdA{AҏAҡAұ[AҿAAAݘAAA/A AAAAAAAAApAٴAӏAAŢAҾAҶAҭAҤAқAҒA҉7AAuAkAa|AW?ALAB[A7A-A"hAA AuAA)AAAjAAѸAѮAѣAљ?sX?v?ł???ө?ۂ,???Ԫ?@ @ @ud@@ mH@ @@:@>@q@x@R*@ 9@#-@&0+@)@@,`-@.@2@3@5S@6@8i@:Y@<k@=g@>K@?ƨ@@ީ@AM@Cj@D0+@EN@Fn@Gm@H@I@Jm@L'@L@M#y@M@M@NYu@N+@O)5@O?@O@Pb$@P@Q3@Q @Rd@R @P@O@N@Mh@LD|@K!@J&@H@G3@F@Ez@D]@C@A @@zc@?33@=@< @;j@:+@8@7x@6f@4l@2Ȋ@1i@/<@-}@+@* @(Z@&X@%@#^@!x@@@@$ǹ@AAA'GEA+RTA. A*A'*0A!˒AwAbAA A{@ x@@6P@n@@N@Y@<@@[@-@|@ @@0@@T@p@Ĝ@@yh@T@1<@V@-@@P@@k@0@@@n@.@@p@s@6;@A@@@u@6@@@z%@;d@@yS@@@@@@n@@,=@Ft@a|@}V@@@@@r@@@P@)@[@ݕ-@0@#@x@@@i@{@·@@˰u@H@@@`@@@e@ 5@X@@@@J@#@y@k@ߏ@]@@t?@ @@@]@s@:i@+@@H@@@J@{w@vs@q@l@g@c]@^@Zb@V@Q@M`@Iy}@Eq"@As@=@9F@5Z@2G@.@+%@'`@$L@ @@@M@X@D=@@ @ x@@H@@AgmAxAA{kAvApĜAkAfWAh AiK^Aj|Ak!AivAd>BA_$AZ'AUFtAP6AL5?AGACA?~A;lA7c A3^A/]A+sA'A#A 8AQA&"AAcAdA AAv+AV@@@@ @@!B@Ц@ח@]y@?h@A@b@I@@y)@=@D@n@-#@.@@@Y@R@@o@@/E@}4@w@p@jD|@du@^ q@XF @Re@L@G@B?$[:?Ӏ?l?#??? ?+E?s>>@ro@m'=@g @b77@\z@W@Rn@MW@HU\@Ci@>>@9@5/@0o@,&@'@#ti@>-@@@"}@D=@ {@@*@?Z?k???H?%?(?Pr?Ȝ? ?{?V?.I?'|?At?{5??L??B?e?Qn?YK?|?}q?v}?nm?hs?aQ?Z?T_9?N)?H?B@!?<&?6?1?,W:?'=K?"G?vG?L?:C?!? ,?U?Gc>>>>_>\>ڍ> >ͅ>J><>]>>>>>x>6<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@~r@<@@@j@0@@@X@V@^@@d@t@ x@c@-@z@@@+@@BF@@ @'R@@@ q@5@@@@@=@y/@m@`J@N@;+@% @:?>Kf .^>3!?@C9C@N@O@@t~@ɤ@0@܄8@K@Pr@@ @@/@[@A|AuOAOA/ZAA A A pAAA+AڐAAAP}AuA cA"A%C-A'{A)FA+A.iA0A233A4GEA6XA8hA:sAAACACAEOAGzAIAKݘAMAOAQHASTAUAXAZ4A\[WA^[WA`DgAbAcAeAgsAi1'AjAlAnApB[AqAsxAuAvAxAyAzA|QA}A~HAAAJAAr|AA}APAwAAlWAܒAEmAAAaAXA A[WAhAZA#A_;AA?AoAA8AUAlA}A7AAoA"AAr|A[A?HAAAAA@AAAHAAnA;AdZA^A%ArGAAAA"ABA}q AzخAwAtEAqZAmoAhAc6A]sAVANAE6A9 A)MA@I?@yA A2GEAMCAaȴArPAA{A"A#Am]A+AAADAjAAjAA=AXAAAA?Ae,ADAvAA7LA{AʦA˹A̺^AͩAΊ A\)A!A)AьA6AAcAA` AбA9XA՛qA8ASA֨AA5?AmAןVAAA:A-AIAcAwA؆AؐbAؖAؙAؙ1AؕMA؎VA؄AxAiAWAD3A.}AAVAA9AצA׆AeAC-AAAA֭Aֆ%A]dA3A lAjAղAՅAXA+AA͟AԝAmAwA($A>AӵA` AxAՆA_A|A AJXAפtAABA؈1AA A7LAh Aٔ{AٽAAGA!A@ q@I@4@@5@![@$@'@+1@2M@5w@9?@nA:v`A6_A2`A.xA*A&mA#?A`AAsAAA4A GAAA@AJ@eV@+@P@)@y@Vm@&@:@ @1@G0@mH@@e@x@ @@@r@@L@2@ݭ@%@@@k@z@d@sD@m0@f@`@Z@T@O/@I@D0@>@9@4@/@+@&_1@!@jj@@F@@ ܇@@8@z?+???h??B????P?ú?L?????[?N? ?-???? j?4a?>7>@n@ii@c{@^1@YOa@T*@Nr@I@E@@)@;f'@6@2 G@-@)4Y@$C@ @~R@m@s@a@ ]@ @ag@???=??k?o?9?3d?.D?)?$?>W??B?t ? 6??d?>>h>>S>'>y>>ɰ>Ó>>i>H0<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@e@ @@@v@ܱ@@F@@82@wG@y@@~@d@@=@.@@P@5@[@UG@2@@5@~@{@y@vk@s@o8q@kD@h5@d@a@^-@ZA@V@R(@Nj@I@E;@?@7A @,j@Κ@ ?&?t?b?6?1ȗ??@2@-{@J.@_ł@t @V@z@0@m@@¹@4@@@&@ѣY@ץe@X@@LD@P@T"@S;@L@BA A A$A A &7A2A֡A!AhAAA!A$A xA! A#[A$JA%RA&A('A*ϫA- A/sA2 A4A7A<A@4AC:*AF8AIAK`ANAQQASrAVIAY?}A[A^Aa=AcAf+Aj8AmAp_pArPAupAwƨAzA|,=A~BA$A!-AA AAAAAPAr|AXA^jAlAmACAAAXyAAcAAAAiAA8AAA]/A*A 7AHKA}"AAADA]A AFAAA%AAvAtAA"Ay AW?AuAAW AA]AXA'AsAAAخAA$AAdAAA*AiAAA}VAQAWA-CASAWA?AVARArGA AݗYAAދDAAW?A߰AAMAAѷA xA@OApAxAĜAA A'ABAZAp;AGAFAA⯸AAA˒AѷAmAA)AdAdAܒAںAAA}AA?AOAAA6AnA A⒣A≠AiAvAm]AcAYAOvAE9A:A0UA%A AAAAAޞ?Jb??'=?S?h?t?-M?t?X?y?;:?(?T"?ۺ4?Q???8?@@@ ܇@پ@J@x@m@ 2@ y@$@*@.H@1@5j@8%@;D@>&@@>@C@F@@IJ@GW?@E@Fd@F@Gk<@G@J@M@P@V1{@\o?@bی@iw@pE$@s[@uR@w@xw@z@z @x@w5T@u@t@q@o@mZ\@k$@h@g;@e?@c=\@aag@_7@]@[z@Z@XwG@W @U@T?>@R@Qz@P.@P@P@O@OQ@O@O@P@R@T#@U@W]%@X@Z@\@` o@cr@f@je@m@q @u@y @}&@@@}@@x@@,=@i@\}@~@e@4A¤AA AKA;A2A/A2AbCAIA2vAA'AA}LAAAK)AA'A{AAQcAAAGA(A]AAA:AAA~AwAqkA>AAA}A={AvAAAAԪAsA A"A#/A$J#A%gA&]dA%jA%_A$A$cA#A#iA")A"pA!A!y>A ]A {A AAWAA;AA/oAA2BACACA5AdAAA /A A rqA 1AaApA!AզAA@-@2a@G@k{@V@@4@l@@܂@V@է@O"@@Ŭ@Ȕ@ @x@s@@@Ȋ@@N@($@M@@ @:@>l@l @@c@B@oT@@ҳ@I@4@z@u%@o:?@i@d N@^L@Yb@T=@PU@L7@Hnn@D,@As@=}@9@6@3$@/t@*c@#@/@)@@@9@t@ 9@ @/A\A=AA{ȴAvAAqAk AfVAd_pAh AtAoe,Aj"AdA_KAZƨAUHAQ+ALjAGAC_pA?iA:^A6MA2\A.BA*@A&VA"AAXAEAA8AA AA [Av A.@@@@?@H@4Y@ܔ@@ѩ@e@C@A_@`@@@+@@ @z@V@H@V@{@@ @w@Y@S@z{@tK@mm@g@a~@[@Ud@PV@J@EF@@nD@;d@6|@1@-@({@$ @@@k<@k@@ c@:@j@??I?!?r?Ec?A?d0?ϯ?O?İ?iD?B?>?Zq???m??g?3?v6?|p?4?9?*?}' ?v,4?o_ ?h?bC?[?UJ?O?Jm?DO?>r?9\?4 ?.?)?%? O@p@kR@eЦ@`d@[ @U̸@Pf@KY@F@A@<@8y@3{@.@*|@&O@!׈@]@@]@ @ Ŭ@ @W@?}?D??g?>??״?ł?j?U\??s?77??"h?H???x?v?ٔ?5????zj?s-R> o>A>6>ި>>hQ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?ƸR?RT???D@t?@+@[@w@&0@.I@3c@7#@9z@:Q@9@5 @4~@3Z@3m@2@2;y@0@-@,@,@-1f@.@/>@0@28@5@8G@:@=t@?x-@A@D@G(@I@L@OG@R@V J@Y@]QY@asm@eN@i@n*@r@w]@{'@yS@`@d@@4@3@y@@(@,@VX@@6@ɰ@[@_F@o@@%@@M@@0@p@ŗ@(N@@ʼn@ɗ@ @Ҁs@M@@෕@@^t@Ec@y@uAAA A AAAAAe`AxA#iA(!A,QA0PHA45A8(A;A?KACAGALAQW?AUyAYFtA\ߤA`SAcAfAj9XAmAq AtAv]AyxA{]A~)A:^AZAuAAAeA AvAʌAAA}ATA%zA;AAwfA5?A;A0ArA+ATAoAAAxAAA+AA%ATA\AUA#A{AARAӏA.AkAACA9AAeA7A$A+AA PA$ A'A@ATAA AbNA"4AAA>AAPA[A'RAAADAAACANAAh>A̘A AAAbAA|AtKAkA_AAPnA:A@X@ AnAQ֡Ar@A'AA.AtA8A*AA1AܒAA AxAA[A8AƐAɜA:*AΐbAЮAҞAdAA׌AACAzAܙeAݤ@AޜCA߃GAZA#A A╁A?AAdAAV9AA \AxA?A{A]/A=ATAGA-ARTAqvAJAAAAApAAΥA#AARA諟ACAAw2AaAJ#A0AArAیA6A盦AyAVA2A A AUAAqAAHAOAAȴAApoACAAAAAZA)AlAA㔯AaA.ArAgAVAW?A!A2AAp;A33AA9AqA-AAߝAQNAAޮ}AVAxAݘA0!AOAGAĜA5 AږAAA2aAA&A.}A>AέAcAPAAAmAoA(AAVmAߘAAqAARA+AeA>AAA AAAwfAAIAeAXA`AAPA~AABAGAA.IAGA^AsMASAMAAA$AAȀAA AAmA A֡A2AAAAzA[AjAAOAA'AAꏑAꆎA}AtAjA`AVALdAB'A7A-CA"AA ?@@H@@(@"y@&I@-@1w@5[@9T@<@>d@@@C @Ei@F@H @I~@Jz@LB@Lם@L@LL@L5@K@M@N@Pj@RL@U%p@Yc^@]@b G@f<@k7"@qa@y@eA@[W@p@L@7@@@u@Qn@:i@*@@@R~@@@@d@~|@q@k@(@@j@'g@@@e@g@)@|@n@@mH@(@@o@@j@p@b@C@@7@.@uO@)@@ͯO@Ѐ@[@>B@@ۏ@E$@@ɰ@@q7@Ri@sC@@@@@ @@K^@BA|AHAA=ADrAA ^A Y`A AAAA%AAAAAA!A#!A%&A'4nA)HA+dZA-A/A1gA3BA4hA6&A7IA9A: A<A=A? A@ABABbABVAB AB AB lABABABABuABA@A?LA=A`@9U@4j@/{@*E@&h@!r@]@q@Wi@X@ s@ @'@]?k?C?:?龡?m?HV?J?r??;?֡?)?xl?}?/??M?ӄ?u%?3H????C?r?}?v?o۱?i&R?b?\>?V B?PT?Je?Da?>?9Z?4 ?.?)Q?$? /Q?D?~?!?G$? ?.?)@u@o{@jC@d@_R@Y@T@O?@J},@E4@@V@;˒@7<@2t@-@)y)@%@ ں@@c@@@ <@ m@t@ ???I(?;?l?9?,?Ex?ʃ??_?U??M?+???^?A? ?xs?qCJ?jDc?cv?\?Vff?P"?J -?D?>[?8?3O?.?(z?#??Io?O?> R<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<FPED{BQ@Կ=94-ҿ$6.{ X]tLsb'9߾4riS.ev=6t>d> _>ʘ>8?Y?-/?C|?Yz?oR???????Ɯ$?;?߯?q?c@6@ @2@@ 7@&@,@3I@9t@@*@F\@MA@S@[@b@hE@oTL@uә@|82@=@?S@@e@@R@@@K@@@@N@Ta@%@@@Ö@*@Ό@C@A @޴@IR@R@'@@2#A-AABZAGAK{APtTATAYA]$tAaAeAiAmAqIRAvSA{A]dArAYAW AAA)AhAABAAA1AEAT,A]AbAdZAcAbAgAuAoAVmA3A 7AAAfA($A8AFAdA'AAA͟AsMAAAAhsA#A$ AxAKAAuAAAAfAAA?A$A[#AAABAAuZA AA8AATaAAXAA@ANA+AAA$ A:^A?HA2AApAA8ABA[WA9$AAYAAQAAw AjAYABh AY @q:i@A8@A_A{NA^5A6ApAAAxATAkAjA9A_pAA;A/AA`vAԬAAA=AMA AlAAgAAEAAAF AAAiA1[A[AA\AAбAAAfAA(AA2A5AA?AEA?AArArAWA<6AAApAA Aw2AR A,AA/AnAA`vA5tA AdAUA킪ATaA%A+A?A앵AdA2AiAjAAeA0AAAJASA7AߤA飣AffA'A8AA`AkAуAA7AA掿A4AAn/AoA䌳AVAㄶA]AE9AA௸A߳AއAA5?AؖAXAɗAZA:AܜA߻dACA_A-AcA/OA:A'AA^AffAAAAAYAASAA4AK^AbABAA=qAm]AAAAA"hAwA&@ ;@'@?&???ؑ}???&??Y ?)?B?D?zc??Jb?n?ti??K?%?Z?T???7 ?e???o?-?̡8?.?ԕ?ڕ?qv?i/?}k?)??i?@@^@ @ @@T@ @@ @$@(O@,@1 @5:@:8 @>P@C߹@H@N.@S3@Y B@^@du@je@p|@vj@}%p@ܜ@A?XA@[AB JABABAACAA?A@A@c AD|AJuAO!AT^5ATATAU =AUCAU|AUAUoAV*0AVcAVAVsAW4AWK^AW%AV!AU=qATZASxARAQAPخAODAOAL;AJ AG>BAD|AAĜA?A=A;{A9oA7iA5iDA3n/A1xA/A-A+$A)A']A&(A$XA"PA AջAŢAVAAAAAAA 49A ^A RAAAA0@@Cl@j@m@S@@y)@+@@@gw@v`@ǣ@@S&@R@r@*Z@@@K@@|@@@U@@Q/@@@J8@@u@}r@y@t @pݭ@l!@hi@d6e@`n@\ -@W@Sa@N%@Jb@E0@AQ@=e@9D=@5:@1H@-la@)<@&m@#eV@ /@@@@:AaA5AAAsA8A~A{RAx0AuzAr]AoAl_AhuAbA]AXDASAN}AJAEAAA<4A8S&A4A/>A+ A'ݘA#A 1'A|AtAUqAËA=A 0AeA&A@zN@Z@a@@r@-@؟k@1@X@Ȯ@Û@@>@@mH@@}@-@@@ٔ@x@@a@@2M@@z@t @m@go@aS@[_F@U@OC@Jb@E`@?]@:@5@0@,@'~g@#;@@^@8@-@<@ f@;@?I? ?Q?m?cI?-? ?4?9bJ?4?.?)L?$? .?'??S?Hb? ? ? @uJ#@opP@i@d@^@Y@S@N@IZ@DM@?Y6@:}@5@1@,@(@#@`k@0@@@]@,(@ X@@0@dE???b?:?ߗ?n?j?͎"?ֶ?DR?զ?]?a?[?uO?`? ???F?a??#??|?t?mW?f6?`Q?Yz?S4?MO?G?A1?;s?6y?0?+n?&^5?!r&???k?? ز<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?j?????;??eV?B?Tv?f?/? J?Go?}c?|b?~ ?lv??0?g??e??1?Ɇ?p??P?p?F5???k?I?S&?!? ?&???Y ?e?+?Ƞ?Ύ??wG?倲??U2@a@@@ @@l@@!Ks@&>@*@/&@4 t@8r@<@@@DE@Hs.@L@Q^@V@[@`@eć@j@p@v@}@V@'@O@خ@@@ -@sC@_@R@+@º@@Yu@ۡ@ܐ.@ㆭ@$@@AcA']A ;AAA*AA9$A A$/A'A+(A.A2EA5A9A=#AA~AEiAHoAKAO&ARuAUHAYA\HA_AbAfAidAl$Ap+AsAw lAzA~AxAoA7A)AA7AAgAAA(AA{Aa|AXA AAA.}AffA(AAAA A$AAAA~AqvA2AAA(AADgAmA>AAAA`AB[AxAA_pAAA5?A]dA{AAAnA4AAA}"AjAYA^AeA>BAyAzA%AYAAA AAfAΥA;A2aAApAKABAuAyAGA[A AA1ATAYAAq AW A(2?}A@?@>l@>X@>Q@>0@=F@=>@:&W@7@4@1*@*@#s@@@@ @bc??ڐ?;?]??k?E?&?o"? I?>C9>.>N >qP>t>%5>>-? Y?B?+|?JƬ?itq?h?Bp? ???z?S?H?F?h??؄??r?CW?????,?¾?Ǯh?~?Պ?Ԫ?]?(?8 ?@@@ 0@@@R@ @%J@+Q@1<6@7[@=[@D?@I˒@OD@Te@Z@`[@f@l#@s`@y@ag@@?}@8@B[@]:@@@+@{J@k@{ @@D@9@@@r\@X%@R@b@ʈ@r@V@*@D@}@k@@@@c^@@@%@R?@AA='AA|A A $A JCAxWAAnACaAʬAyA4A A#ϫA&!A)A,A/'A2A5A7ĜA9A;xA=XA?>BAA(AC+AE AG%AIAKAMAOAP7APAAPAQS&AQAR$AR"ARfASaASAT4ATAU lAUsAUAV1AUAUAUZAUoATAT{AT;ASASCASeAS!ARARARJ#ARAQ_Ad?,? ?Q7?9;@ue@o@i@d^@^@Yx@T)@N@I@DK@?@; B@6K@17@-@(@$FJ@ T@ @@@x@ @A@@ ??M??-??ڭ.?ԧr?e? _?v ????|? ?Ɇ?u??(c?پ?D?K???}?v?o{?h.?a ?[W>b >&v>^_>Ti?.?a??1?l?ܱ?w?L?7??<`???[??2?Z?/?n.M?SQ?>3?,?ؐ?l>羘>i>>>>*>5>9>|j<>_r.>AB->UX=AN==)=t =<=#= =-=@=*=k~>$>o;>;>ܔ>?1?0?,i?:?In?X??g?wo*?M?;:? ??>?j?) ?Q/??,?,?@@"@k'@[@@#@+@3ł@<8 @E@No?@XW@b@n{ @{>@B@@R@X@_@/Z@A @e@q@@<@J@*E@P@)5@ںAA A aAA-AfAPA"MA' A,HA21A8A>ADSAIbAN=qARAWA\SA`qAdAi&Amu%Aq'AvAzeA~AAyAOBAAAAUA͟AXAAFtAAAAAJA`vAAAZA-AA&AzAAQA|AoA A DA rA 7A DA0UA\AQA!AچA;A_AA!AAAYKA!AAHAAdAAW?AAAqAqAAAo5AHAAHAbAZA AAbAA{AA_;AA\AS&AAAtAԕA AAFtATA.AqAAuAYAAA8ATA A AtA ~AlOvAD@*A&Ahp;ABAA{AS[AAߤA$AAA!A(XAѸAիkA ACA!AmA7A~(AA A쑝AFA?AIAAA AIA+A+A:AA6A`A1AA/AA(XAMA8AQAhAB9B3MBLBcBvFBtB,BBXBBBtBBBB)B8BB$BPB[BvFBi*B[#BLJBW@J@JK@I@I@HuO@GO7@A@; =@51@/@* @$@:@@@@ {@ؙ@d?(?U?g?פ??&?V?*?? (??v??? ??{?lM?]M?Py?C?7?,?"C?r? e?'(>i> >=>+>>zF>u>>+? ?2n?gi?R?6?@+@@ P@! @@W@@V@+@ >@ t@L@+@@@C-@ *@ @ j@E@ǹ@@[-@">@(@0z@9@B@I@P@V@]r@diY@k@r@zZ@w@n@@@r@p@M@@@d@^ @@0@@CB@QD@s@ثu@@[l@ԕ@d@ @X@8@A,A1AIA8A %ABAEjAHAK_pANAPAS$AVg8AYA A\$tA_4AbAe AhMAj{JAkEAm7LAnAoAqaArKAt1AuIAw AxzxAyWA{`BA|֡A~OAɆA9AA~A~SA}7LA|jA{IAzѷAz%Ay;AxqvAwAv;AvYAuOAtAsAr"Ar8AqsAoxlAlAiAf;Ad@AaOA^A[AY8AV$ASAQl"ANALaAIAGtAE =ABA@K^A=A;-wA8A5A3oA0A.tTA,A)xA'=qA$A" A޾AA?AМAAA FA AA99AA@B@d@@Z@@@@@@GZ@ʪ@(N@@o@9@@o@!@G@>@H@kf@@@_@@q@@@@|y@v9.@o@i_@c3 @]w2@X@T=@On@K@F@B@>@:@6y@2@/7v@+@'@$l"@ ]A/AqARTAA!A|h AyRAw(Atm]Aq,Ao@AlmAi8AdQA_HAZ[AUDAP9AL:AG^ACS&A?A:IA6?A1A-RA)=A%$A!qAAAAAMuA AYKAtA@@@d@W@m@ޢ@@s@ @C@Ýs@@a@ @&@@\@@z@Se@E@Q@u@@5@sm@@y`@r~R@l@e@_@Y=@S@N@Ha@C*0@=@8X@3@.@*6@%@!@ @}@XO@P@ bN@a@V@4Y?X?v?~?CB??ڼ?Ժ?ߤ?.?â?>???? ?V?U?J???x?-???1'?y1?r0?k`?ds?^J?X*?Q ?KE?F%1?@?;?5?0xu?+h?&z?!q??v? _?? ?r.?x@s@m@h!l@bz@\g@Wy@R G@LL@G4@B@=Z@8C@4"@/|@*@&{_@"@@@@@ @ g@;@?"?9?z??y?6??%?V?Ŭ?'??7?n?ti???J?c?u?8\?D??)?[l?yO'?r?k?dG^?]?W2?Pp?J,?D:??"[?9?4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@Ҷ@ 5@~@f@q"@<@u%@@4@)@{@@|@V@@@4@w\@@ 0@e@@$t@ @@J8@@R@ @@[@d@@ @}xW@w@p@j@eo*@`@Zz@U[@PV.@K@GB@CK@@@=)_@:F @7^@5X@3@3a@3@3@3j@36@3%@37@4ݭ@5n@6~@6u@7@8@:CW@AwA^5A#AAjATAqvAAHAAAEArARAA8AjAeAAVA?HAVAsAAAAߤAwAAh>A2-AvAAIAAzDAAbASAAAA|AYKAIA`A6ArA§AA AA \AA$A%AyAA™eAhA1'AAkA>A?Ae`A>A^5AA"4Ap;AAٴA AAܒA=A`BATAaHAAIA}AA-CAAAbAA,AAiYAA' AxAtAzAAFAߤA$AbAAS[AA,AAA߅SAAAWAm]AAA AASAAAAqA=AA5AخB`vBB/OBBB-BuBBB+B\BXBBؓBrBB0BGzBZBkByBBBB1BB]B=B_BB"BB~]BtBiB]BPBBB3B$ZBBB BޞB˒BBB\BzDBdBNB8B"B )BB)B3BBuBzBa|BGB.IBFBB;B3BBBpBTFB72BBdBܒB"BB|BZB72B@AAA>wAAA6FAAlA]AAAzAA5?AsAA{A^AޞAsAAjAܑA AAF?A9$A.AWAA6B.BB$ B)BB;BEB0B BDBxRBBӏBBBAUB_B{BB"B B,BzB BBBB$B-B5%B;BABFYBJ=BMBOBQBRBSBSBS&BR:BPBO(BMBJBGBDBAUB=@@m@W@@^@s@0@@s@@X@/@ @7@@@@@@w@W@3H@P@m@@W@!@@@G@U@@@F@A@@~@}@0@@@q@U@{s@r@i@a}@Y6&@Q<@I@B'|@8Se@-@#@=@W@@ں?X?ud?ձ1?P??I?;?U?1?]?w?j?]?Q?F?;?/>?"o? ?bZ? ې?k?V?0?K?\h? ??$S?D%?it??o??h?N?1?̣??B[?u?8??n? c?z?d?Ĝ?@@ 9@Z@(@ cI@)|@23@;ֶ@EV@N@X@c@m@y @"@b9@Jw@`@v@@@@@KI@1@B@@5@ @[@I@?@ޢ@7@ꗷ@B@0@#AAtA+A A 3AA AArAn$AxA!:A$0A'A*A-3A0A3A6xA9xlA!A9^A5kQA16zA-qA)0A%($A!/AKSA{jAAA{A AA%Aأ@;d@y@x@C@7@@@X@Ҟ@n@'@@r@@J@@=@g@C-@@@<@1@@W*@@"@}V@vu@o @i?@cN@]5@WD@Q{J@K׈@FXy@@@;z@6@1@,@(>@#C@:@@}@K@@ S@e@4?:?:?la?̣?Y??w???|7?u?n'?h?p?a?[Wq?U%F?O?I>?C?=?8?3M?.-?)0?$U??O??,V? F? ?@tXO@n@h@c@]@XD@R@M{@HT@CF@>R@9x-@4@0@+@' l@"@ff@9.@#@&-@?@ o@@?A?K?gw????_?`?ٔ?zC?s%?k?e&?^~A?X?Q?K=<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@x@b@r@@F@bc@P@m@ 5@) @:@@@=G@2@Q@@۩@\@@Ԑ@S@L@̞0@@C@x@@"@~@@K@@ @m@@@@u@<@@}$@q+@zQ@@@^@:?@@vN@n @f_@_@YM@R@Lp@F@Au@<>@6@0 @*l@%@.@]@U@ U@g8@u@f? ?̸??7?b?E?P?D@@ ,g@ @d@@f<@!@'e@.ff@7@Ay@I&@R@ZL@d@m@x@@e@D@6@l@A @sX@@˧@Ĩm@̊@ԕ@@堐@X@yAA@A [#AGA]/AA \)A'U2A-[A3nA9A>AD{AJ>BAOATAYA^AcAhzxAm2aAqAvA{L0AAlAAmA'AJA9$AخAA?A"AaA̒oAGAA˔{A,AʾACAɼ6A$A~]AȀAuA+AC-ADgA-wAPAA>AAsAAĜAK)AwfA2-AYAAAASA~$ A3HA??AzAA2AAA 'AA\)AȅAQAקAAh A`AWA!bA AjA5A{JAAAA\AlBBl=BByB7LBB4BzBBԕBOBBmBBjBT,B=VB&BpBBjBBBBzB`BFB,BBBB4B@BBkBMB/BBB BBBoBLB(BBBB)B_VB1[BBPBmB[qBBSB B5?BBhAҽAA($A;0AAAAACAsB0BQB;B}BB8BB*BHBB=BB0B BABu?B BBBB:DBX+BsMB0BB2BBڠBBB B B EB B (>B .B 4B 8B B <@z%@@@*Z@0@8@@DR@%@ @-@%@j@ @@@`@@@*@p&@@@D(@T@f@@W@S@@S@@e@-@@@@i@|[@@ud@@V@g@W@ݘ@f@Z2@i@@N@ @ @ag@xD@l -@`*@U@KF @Ae@7@/(@&@u@¤@oT@w@ջ? 0? 5?]%? o?P3?:?p?@:???S?bc???{?n?b?VX`?K1n?@?6?-?&? ???ZW? F?ac? -?? >\O> >5?>I>!??4]? ?6A<(A9gA6A4iA1XA.A+A)4A&A#A!TAɐAIAAZAUAdA A pzA ApEAAPAX:@!@w@@@@:*@@(@څ@֔F@u@j@s@Ə@¹@L@@D=@@@gM@u@q@@@@7@@"@<@@@@@g@zX@t@oA@jG@dm@_@Z@V(@Qx@L@HjU@D@?@;9@7I@3@/@+AߤA~Ay_AtbNAoAjAf_AaoiA\cAuApںAk[Af-Aa=A\FAWARAN(AIhAEA@A@8܇@3@.@*,=@%@! @|@R@'R@@ "@J@@.?V?΅???.???j?e??rG?/?s??@?D?+??*Z??s??@?.??w0?p?j??c?]?V?P=?J_?E-???:(x?4?/4?* ?%ڌ?!????i? H^?CR@r@@lh@f6@at@[}@Vk@Px@K~@F]@AWi@@t@h@@'@g@$@G@y@q@iJ#@a1@Y<@Qn@I$@Bf@;6z@4a@.@)M@%$ @"@"@@&@(a(@(E@)Ln@)7@+,(@-"@0@5@9@>">@B@HPH@N@VΚ@a@o @zW~@2v@-8@;:@~R@P]@Ex@@@o@j@")@@ȱ@Дp@D|@U@@tARtA dA5ArAA#ѷA+$A1HA7A=AC+AI+ANATfAZA_Ae]AkXAqzAwg8A}DAAޞAl"AA{JA&AqAAcAA3AAAیA#AADA`A0UAAAeAy AxAtA1[AAJ#AzAAbNA®IA)AIA:AHAHA?}A,AoAAAͥFAn/A4ATAПVA:^A0AWsAA^jAAeAA7AoiAՋDAղAAOA֎"AֽqAAAA0!A6A1A"A AA A֑AYKAAAՅSA-AAbAAuZAAWsAѰ!A8A/ASAe`AaAFAAɿAM6AƴAoA2A}AB ӏB ^B IB b$@2D@'p@J@Cl@  @M??x?p?dZ?D?u?a?ۡ?@?J?zR?k?]R?P?D?9?/L?%ω? ??Um? j? u???c?`?? ?&?+??0?è? ? M??'?.I?*0AB AEDAIANAR@AVA[A_AdgAiیAnAt=AyAA{sA~2aAIA|AaAQAAKA:AARAAA[A9AfA)_AJXAkAJAApA}AAiDA)A)AA{AGA~+A{BAyvAw%FAtArApQAnAkAiqAgAeZAc6Aa xA^A[ݘAX^AUARAOALAIAFACA@;A=2A:A8+A5C-A2xlA/^A-A*LA'hA$QA"/AiA6AAEAA"ASA *0A ~AA/A~3A$5@e@{@X@@m3@@@1@h@m3@@:@`@ÿ@ί@@,(@x-@@?@@?@@@9@ߤ@@M@@@@0@@'@z@u?)@o@jf@es@`[@[b@V@QD@M@H@D@?@;@7@4e@1GA@A|AwƨArHAmNAhAdC-A_'A[MAvAq4nAkAf AabNA\XyAWkQAR^AM/AI@AD]dA?A;1'A6^A2YA. A)A%A!AAƝAA;AA ~AA@A@@@?@`@&@O7@֚@@˛@J@q@ t@@J@@@f@,@@Ĝ@9@ǹ@@0@@@{!@tD(@m@gEc@a@Z@U @O;d@Ig@D!@>K@9@4@/Q@*@&+@!@5@S@@@ @@ @d????o*? ??)??0?ß?6;??)?1?????0+? ??9?~?p?? q?y?r?kQY?d?^JZ?X l?Q?L x?FJ?@?;=?5?0?+]?&?""?A???at? BA8VA7A7>A6`A5]A4=A3A1A0_A.A-A,'A*A)'RA'A%A$B[A"A HA*eAoAAAAQnA'A4AARA A A tAA!ABeAYuAQ@l7@`@a@X@@n@m3@AJ@@WT@ԍe@x@z@@x@6@@z%@@@n@->T7u#?rq@@@n@h@@c@{t@b@@z@Z@8@t"@c@U@I@>&@5@1 g@/N@,:*@)G@(@*B@2u%@::~@>@C@I&@P@\C@i~@s@~L@@Y@J@O@I(@p@@_@@@c@@Z@/@@jA0A A9AAABADA"hA%AdABuBB B/B="B6+BBGBlB oB B B JB oB OB 3B /B B BCGBBEBBTBBBB}B4TBT,Bp;BBBUBBBB~BBBBB B޸BBhBȚBwB3BB1BB{JBkBYBHB5B"NBB*BFBBBBBuB^BFB.B9BBB^BBB}BcBIB./B B B B B B %B hB KB ,B B }B VB wB B i*B EB B B B B ~wB QB !-B }B RB ~B ?.B B B ZBBBpBsBBB4BAB&B}BBB ׍B B _B B B tB ^B B4B]~BB|BBNB~B_BB%BB49BOBhBBFBBB_B2BBB`BBB 6BBBBB!bB#nB( Bq'?'j'/-''f'}'' '#('%L'(z'+4'-s'0'3'6'9{'<'?'BL'E<@@#d@Y@RAA tAAA h A#8A&A(]A)A*A*QA*1A+QA, =A,3A-oA-A._A.A/1A/%A/7A/VA/A.zxA- A+xA*1A(A$֡A!AnNA\A[-AZApA ?)AAAA@@^@C@׭C@͕@'@˼@A@x@`@@@@@r@d@Wi@JI@?7L@4($@)@@@ g@B? _?t?P?ί?!?W?73?0?*?$O ? d????o*?%B?+???G??????_x?%?\A9A5[A1uA-JA)1A%+A!5AS;A{AłA@A~A A'AA̘@@@@h@s@ܞ@@[@@ƙ@gM@T @_F@{@ϫ@3]@h@O@5@'@@͊@@'R@x@@z@s@mO7@f@`@Ze@TjU@N@Hp@CeV@>@8\@3@.@)C@%5@ @/@ڥ@@Y@ q@@C@!?9a?4=?/>?*d^?%?!?+?P??? ?3@m+@gnY@ah@\B1@V@Q}k@LB@G @B@=+,@8V@39@.@*r@&@!@n@H@:@D@ d@ 9@6@N{?h???n? =??ֺ?!??c5?u??T"??>?K?yh??4??i?0??<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AQ JAZ&AbbAjAt@AR AA49AA-wAA@OASA-AAaA}Az-Av}VAr}AoTaAkAhAeHAbSA^dA[VAX~(AUiDARa|AOgAL|AILAFADAAIRA>oA;HA9A6GEA37A0OA-A+QA(HA&A$0A!&AA&AAAmAU(ABA@/ACA AA >ABPA?A@A;A-@H@:@'@@K@4@⃺@qv@1@ҽ@@]@c@@@K@a|@^_@Z@@Y@C@Z@|@hI@WE@F@5@&@~@@ @o??B???[? @@$5@ @@@7@#j@.t@:g@H&-@Xƽ@k>-@}@.@@J@@@a@f@ג@E9@ﴍ@7LA6&AA/A"W?A(sA.?A3A9A?zAEAKAR#:AX0A_AgSAn˒AuA|ANA.AJXAJA2AE9AzAAAAiAרAAlAYAA=AAA&AlAAAAAAAAφA-AAIAmA8AبAAx8AaAA@OAOvAJXA=B6B5BWBBB+B˒B͹BBJBBBqBtB=BBBB{0BlWB\BLB:B(BBuBcBBĶB5BBBkBTB=B%B BnBیB[BB(Bu?BZB@iB%B rBB@BBB}B`BBBuB$ BBBSB@B[B_B;BB;B1BBrBEBBBBnB.B sB B DMB B pUB B FYB uB YBZBMB{dBB gRB PB *B#BNBxBBBBsBBBi*BBB BRoBBBBB,B1vBL0BdBzBB-BBBBBBIBBBgB BBSB ==~=0=f=\=mk====xU=;==- =}=;=j=+>]>(>m>>$w>0<>K)>`>X?c?A AyA$5?A,A6AA@AJJAS^AZ%Aa$ Ah\)AoBAwA~AYANAAuAAA.}AAAARAAÖAAGzAA AAPHAAvAApAAKAAyBAp{JAcxARmACA5N? ??}?rL?g-?\?Rp?H???6~?3!?1?/rq?-P?,Mw?+ ?+ (?*j?*?+q?-"d?/ 1?0o?4"?7?;ҷ??S?C.j?F?Iv?Mw?QR?WF?]e?c?j+?wJ??.?F??4D@@'@1G@#@@@.\@>@P@e @|Zq@<@ @@)_@`@0+@ܨ/@T@lA4nA A:A:A3A$A+A2}A:6AAeAHCAP:AW;A]^AdAj7AoAt1Ay A}sA\AٴAbAA AJA+AjKAmAq AAAA.ArAffA\)ASALAADAAAhAUAkAA AA#:AhAFAAxlAAA2ASAuZA+AtAA+AYAVAA<6A%A{AxdZAtAqaAmyAj4Ag#Ac҉A`A]LAZ AV=AS-wAOdALEAHAE\)ABA>A;A8uA5oA2WA/SA+'A(K^A%cA"A QAAAGA ATkAOA ݷA A`AA@[@@@ @@V@@@I=@X@\S@ @@`@@@@o@ @X@(@,=@@N@@T@,@@@@n@k@x@{0@u}A@o@iw@cT@]^@XH@Rz@M`@H"@C@> @8`@3oA8A0AmdZAhAcA_#AZAV+AQ˒Ah$tAcRA^AYϫAUOAP{JAK`AG^ABA>}VA:$A5/A1A-~A)jA%eA!sAAAA\rAËA W~@9@3@/@*(N@%q"@ ۶@ek@ @Կ@@ @@ x@^ ?`?|? ?B?ܱ?أ?Җ?̴??l?>??X??@?.I?k?1Q?T??n?6???|m?uV?o]?ht2?aC?[t?UR?O?I ?DXh?>?9?4?/?*?%?!k?/??}p?lv? y>?@i@d$ @^Y@Y:@S@N@I@D@?@:ʬ@6@1i@,@(jU@$@@@@@@ @@uy@??Z?r?&?X?څ?Ԅ?Ω??bc??e?,?{_??л?*?@?< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@o@/A UA\A'A1xA:ACAJARAZAb-AgMAjAmHAo<6ApAqfArArJ#ArF ArAq#AqخAqcApAodZAmAkg8AiAfAd0AaA^rA\JAY:AVTAT:AQANALZAIaAG0UADVABA?A=A:FA8A5"A2TA07A-ɆA+zxA)1A&A$A"OA &AAA7AA\}AAđAAF AA %A A LOAA_AhAI(Aqk@@$ @ @@L@鑧@@S@٫@v@̪&@Cl@#@@x@Ѣ@W*@%@@zN@n'@UA@>@'t@<@M??B?X?Y?u?&?F??@0@j@@`@+¹@8@G7L@Wg@f@va@\@@@M@I@ϫ@ x@@K@}@s@CAA;OA rA7LAK>A!A*A5A?yAIsASA`Aj?}ArzAzA7AsAAsAA5AOvAcAuAAAwAרAAjAAA_A"A|AAAAɊ A˸AɺA[AѨXA}"ACA.AطAiAAݞA 'AAA=/_N>3>6>:Ƌ>>M>C0Q>Gѻ>Lu>Qi>Va>[w>`>fS>m>s>z*>},>i>e>b>D>Q>>~>{x>8>;%>8>>v>Ls>>>?^?_9??7??'3?/V?9O?E?Q~?^U?lր?{?;O?}? -@%<@}AEcAVAhA|sA+AYA]dA֡A2AAA AA3A?HAAtAAAEA7AXEAyMApA AgA^AR6AGcA<_A2bA(^A+AiA`A @{@@%@2v@!@r@_@L@?@ 9@@:@tF@cP@Sf@Dؙ@7]O@*}@LD@y@ c@?sX?????.??6?do?8?xG?oF?fU?^?W2?RW?QC?O?Q?R?T\?V6?X?[?]?`\W?cM?g,?k??o?s28?wW??{;?E|?r?I?'=? ??%? ?M?J??K4??#?g?iD?2?h?W?@ @F @ѷ@$$@7C@M@g'@@U@33@f@)@\@@@AOAOA^AA'A.A5A= ADVAK AQ~AXAA^]AeAlAs Ax/A}5AA1[AA$@AAAbNAAncAAUA|A.AGEAaA}AA0AuACAiAAn/AAAtAA|AAA A4AAA#A AA4A6zAaAAɺASAGAAUAiAxAQAA}Az"hAvAsYAoAlhsAhAeZAa3A^+kAZAW$ASAP\AM AIƨAFbABA? A;A8]A5A2 A/A,.A)bNA&CA#4A ,A"AAA1ATkAA iAHAOaAA@@֌@@@K@&@߈@@O@͊@c@@@p@@K@`@@1{@k{@@@Z\@@a@@@4@Z2@G@HV@[l@|@wiD@q@l@g_p@bA@]A@X@T@O:@K@Gh@CW@?[A7eA0=Ao*Aj Ae lA`!A[U2AVbAq?AkAf|Aa:*A\ AVAQMAM1AH1ACqvA>A:6zA5RA1PHA,.A(A$A \AAA-A%zA ~A LAnYA@\>@=@rq@8@#%@1Q@b@Է@.I@@Ā@[l@U@oi@@]@rG@@y@xl@[@Z2@r@n@@P3@@x@r @k,@e(@^@XM@S@M_F@Gί@Bc@=@7@2@.@)]@$@ A@@{@{t@q@ @@n@\>?K??%??T?.?28?_F?Ǵ?1Q?[??/??ŗ??a??S?1?8?n?)?p?~U?wfb?px?i?c*?\ț?V>?P?J?D5??Y?9)?4?/#?*?%? S?Q?E?x%?8? ]@lT@fU@`à@[I@U@P@Kqv@F[B@A]@?@L*@@@}A A$5?A3A@AKwAT͟A\Ad$AjhApdZAuAzA5 AAAPHAAϫAA|A0AGEA?HAA/AA~A|eAyȴAvPAtAq#An(Ak)_Ah$AeeAaA^A[=AXAVIASBAQeAO1ALOAJJAGAEACeAA8A>A@AFVANAV{A^"AeĜAmoAutTA}AA5AAAqAA#nAخA$ AAAAVAA^ABAA҉AvAAAACAXEA\)APA5?A AHA㢜ANADAuZAAhA[A5AAAB'AA>wAOAAAQAA=AIAA'A8AƨAJAmBBQhBGB-BB B;dBjeBBBvB0BcBwBB]B BBqvBYB>B BBBB7BZB(sAApAAnAߤAFtA4AA4AeAMAAA$AzxAFAA AfA9AGAAOAALdAA׵ AЎA]AArAAбAA۴A,AAAAA>wB9>BsMBkB3B B ZB B72BBՁBBуByBm)BBBTaBBQBB$BB~B0B|B3BKBHB BBרBRB B vB7BNBeBz^BBBTBBqB BB BBBBaBB BfBBBBBqBϫBBB>BBXBzBjeBYBHB7fB%BaBBBBBBBB'BiBP.B5BkBBB-B[BNBBcBDB%BBBBnBB]B9BB]BB BoB@BBCBBiB)*BBgB>BBkBBE9BxBdBĶB BPBw2B\BBBBBfLB6BHBBZ7BB BUBKB?BBCBrBBBBB&fBABZkBq BB+BBBżBBBf?x-?@W@$@o@C???K?VX???\>?)?rq? ??EN?? ??4?4??.?&?d?{|x?td$?qJ?o?n?mD?kg?l?o?ss?w;C?|?SP?i/??\?ł??@??*E?e@=@(1@S=@@KA%AdAtAAAAAnAԕAAhA0A6ARA!A/AiAtOAihA_^AV.IAM =ADJ#A;A3A+C-ANA0At@@@r\@ɖh@m@A5@0@X@ @b@mv@Zh@I0@9R@*[@@y@f@n?Ҟ?p? ?Ԁ?#d??sX?dZ?҉?[? ?x-?y?q@?hޠ?ai?`e?_?^?]?\V?[9?]?:?`Ӟ?dv?h)9?k?o ?s ?xX?}с?x?.?{?`??ɰ? c?VC?-?C???uADAIAOAU'A[rAa`AfKAlSAreAwffA|A[A A0!AcAAF@9 A/A)A`CA\AYAVȴAR\AM,=AaA A\fAXAT4APSALAHADAA&A=yA9jA5A1oA,A(A#?AIAwAyAA A [A$A3A'@[@Z@@@#@J8@qa@ӿ@4D@1@Ì*@@q@ @@@},@9@F@@U@/Z@=@f{@@@y@@yP@rĜ@ldo@f/0@`#@Z@@T^@N@IZ@D6@?R@:C@5@0Se@+@'#@"@nn@?@-@6@Z@ @0@ 2?Ri??,R??? ?Ӈ?"?c??b?'?? ?Б?%?9?3 ???? ??0j?{#?t?m@?f?`"%?Y?So?M˼?H?Bh?@L(@G{@Bb@=@9&B@4pe@/@+M@@&@"H@LY@&@@@=@ r@<@:?)_?@?V???:?*?%?S?Ʀ??4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?LD?ν?\? ?;y?M?o?7?^?A??@ /@/4@ZV.@@s@o@@6AAKA3cAF8AYBAjJAv_A}AAA|AuA!-AlArA?}AܒARTAAA PA AIA rA֡A}A{&Ax0AvFAs˒AqJ#AnaAl6zAiAgAd*Aa=qA^A\F AZ1AWjAUASAQAOTaAMHAKRAIdZAGv`AEh ACHAANA?uA=*A;;A9A8A6QA4A2A1bA/A.?A,A+0UA)A(CA&A%m]A#)A"dZA ѷA5AAA A+An@O2@i'@}@@{@JM@@@¨m@F@<@"@@HAA.A/AғAAA%A,UA3A:AB3AJARwAY[AaAhB[AoMAvA}HAA0A_AcAbAnA2A AΥAAA(AA$AmALA/AA AACA[A]AA(A AعXAcTA+AxA/A^AA`vA1'A2A%zA(A AXAT,A9ABAAB ^B4B8BvBhB B=BƨBZBBBB!|BBB BCBBBB?BB?B3hB@BFYBGEBCaB;B0oB!BBBBɠBBBUMB%B[BlBzB8BvBtBPBBB+BB?}AuZAV9AAEANAAAAAAsAYA A߭wAؔAJXAAAA:A$@AC-A9XAUA{ABIBBJB lqB ^B kBBBcBAUBhXBw2BpBUB)BWBBFB-BoBBkBqBBuBHBRBH1BhBaBNBHB{BsBNBB SB 2B L0B aB tB 3B B WB B B B CB B KB LB B B B EB |PB p;B cB TB EB 5B %B B UB}B BBaBIBBqB[=BDB-CBBBzBBBB;BgmBMPB2BBBBżByBBoBR:B4BB+B9BtBBq[BMB)*B-BۦBBBZkB*BlBªBBJBBBiB BNB&B4B_BLBB@B `'BB/B%B_ByBvFB/BBXBoB>B5BnBB BfBBB6BfBTBDBޞBB B 9B RB iyB ~B B B B w@@n@@@p@@H@@@s@.@1@X@p@0j@0@ @p@0@E@@@@Ft@ީ@z@@@@n@nY@t@@x@ J@@~@z @v@sy@o@l7@h@e@b\@_+V@\?@[@Z>@Y@X@YO@Y)@Z&W@Z@\L@`C@dL@h@t[l@,@w\@I@YK@=G@@@1'@ɡw@D@@_pAA dAS[ArA*B[A56AB \AOIRAWvA`AiAsaAzA~AcTAv+AA}Av[AhuAXAJoA=1'A0A%!AFA")AD@@ @ۜ@,|@:@@K@@@@}m @lk{@\7@Mj@?@3@' @@b@M???9?h?L?*?k?u?L??M?O?k?*o????m?|?p?T ??3?|??w\?d?a?n?Q?)??P?6?2?Tv??a?s?A ?pP?ĭX?w?QY?.??x-?w?ߓ?l?*?AoAkAAg&AbߤA^AZAVARANAK6zAG:AC.A@|A;A5]A0IA*A%LA A=ACAظAAA VA A ?>AŬAA1@@@&@~@]@@@8@Ջ@&@U@@Å@Bp@r@;@@@F@~@\@(@@%@ @r@4/@ @@o@@}@y|@tI@n@ibc@d@_ Z@Y0@T@P@KE9@FA6TA1A-(AhAc>A^cAZAUMjAnTAi xAcA^0AY}AT AOϫAJAFBAA1A=_A8fA4!A/ɆA+rA'^5A#HAEAYAVAA A o AwAqA@@ @A@ꈹ@~@ޗx@ә@1@Ͳ-@Sz@@M@@v@Y`@@/@Ɠ@x@F @/@1@N{@#@@8@}m@v@o@ir@c!@\b@V@Q"@Kq@E@@8@;C@6'R@1-@,T@'@#y@a@4@@M@ ײ@ @&@sX?x??m?G?|?܏?vu?ЇU??"??Z?/o?'?B???^J??????h?v?zD?s?m#?f_?` p?Y?S?M?G?BH?<ϯ?7|?2OD?-E?(_[?#??u6??? @k1@e @` @Z@U&@O@J}@EW@@M+@;\@6@1h@-)@(@$3r@@@@~=@r@@ @@@0U@?/?W?쫴?) ??ٟ,?Ӗ)?ͳ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@i@R0@<;y@%UG@ ?<`?\??Xjn?->-Z<.܊z>??{@p@a3@#%@Y@@wGAA|AA&A2mA@ xANAZAeApSA}wA ApANAPA(AvA A;A)A;AA A:AfA ARAx8AA|AgAIA)AA AA[A8AAAA~>BA{bAxYAu=ArxAo`AlAi"AgffAdAb"A_kQA\AZGEAWAUAS2AQAPANaALAK,AIAH?AFzAESACTABvAA A?A>MAB$-B$B$B#B#ߤB#B#ZB#B#7B#raB#[#B#CB#+B#&B"B"B"KB"B"B"zB"` B"E9B"*B"B!B!֡B!B!/B!B!aB!CB!$B!SB FB gB B OB \B 88B oB6BuBBkQB E@Ev@Lj@TT@[@`Ĝ@f_@l@r@xm@s@ @Pr@w@u@L@]@@\}@Z@Q@<6@H@@@@@@@^@@'(@P@{5@@+,@@g@@ @HA@U@&@O@@y\@r'R@k@e{@_q@Y@T@Q;@M@J@G @F@EN@EK@D@E@JD@N@V@c@q@@@q@{@W@Ri@A-ANA!A4AFAY>Ak'At͟AzyA{;A{A{jA{0UAzAzAzoAzGEAz AuAp8AjAeK^A^]ASy>AH~A>0UA4$A)SAAJA |A1Q@*@@>@7@!@Y@~@+@5~@.@@p=@_@P֡@@@1'@$3@0@ @Y?|F?ްu??? ?|?0?u?? J?5T??WT???;O?{?Б?9?a???bN???$??-?? R?ơ?S?_p?j?׊?۾?1?f?8?@%?Ɠ?Zq??"?c @RT@j@@L@@ A5@ @+@'@}@ !@)`@3u@Bg@P@`ϖ@C@EN@x@&@>ArAA A-3A9zAFVAOmAW?A^Ad5AkAqHAw9A|AADAKA#AAAcAoA_;AZAATaA AԕAA` AAAFtAA_A@A!A6AA,A A"AA|AAAf2AtAAVmA$AAMAlA'AiACAA#A.}AAAGA}A AJA~AzAwAuArAo,=AlOAhyAeAb&A^خA[AX`AU7LARANAAI1ADA A?oiA:TA6~A26A/#A+A(F A$5A!AbAAaA8A#AA A#AxAMA:*@w@ŗ@@F_@(@'@B@x@ɛ@4n@ɷ@H,@v@C-@@Hk@U@w@A@@C@f@0@@.I@@@O"@+@@z8\@t[@n͟@ii@d/0@_@Z1'@Ul@P@LQ@GA5S&A0yA,AhMAcjA^n/AYATȴAm=AgAb6A]9AX}AS^ANAJ JAERA@CA<CA7A38A.A*A&~A"iAg8Az:AAA0A A AA5_@@VC@@֌@O@G@#%@тA@@ƥ@i/@Ln@O7@pz@@@@!@9@@@@N@@=@@zM@s|@lؙ@fbx@`@Y^@S@N+k@HA@B@=#@8gb@3S@.`@)m@$@ Q@@@[@D@ Jw@l@@??^?7v??J8?D?8?3r????Ri?9?D?s?v?5??&?x?H?6?At?hs??|?t?n?gn?`?Z?Tz?N?H5?C?=?8@?3A?.O?)!W?$^-??BA4A<ACmAK,ATiDA^SAh9AriA}AAAA,A%zAAZQA AA}VAPAm)AAANAjADAAjAAȀA=ApAQAsABB/BBCBB!IB" B"ۦB#B$yB$_B% B%m]B%B&?B&_B&-B&ܒB'B'CB'p;B'B'B'ߊB'BB(7B(3@~@M@C@ @ 5+@@T@j@-@@$@*@1Q@8V@?n@G9@O?@W+@_5+@g@p1@y*@<@@@D@z@@82@o@˒@@{t@4@L@@Ѕ@K^@,@*@&@K@!@$@)t@/0@6@c@r@ @~@@KI@ƨ@Bp@r@̢@Dž@@@@@*@I@@w@h]@a̎@a@a @a~@cd0@l@v@u%@@ć@@7@UG@˚k@߬@cAAtIA#8A3uABzAJASrA\zxAe`Ak_Am8Ap^5ApyAmAj33AfA_9XAWaAOϫAHAAAv`A:eA4 A+???@@]@A@ @ :~@@U@@gb@R@6@K@@(@@E@M@ @#L@&@+x@2l@9U@CD@V @wa=@@ @@\AAvA(jA9ADAOAXwA` AiYKAp{JAuUA{$AA'AAAh>AEAAtAASAAAo5A(AAAخAAȀAYAAV9AA6A$@AAAA ~AAhAA!bA.AAOAAoAA}A1AAAAAMAW AeAzASARAA\AD3A.AA+A|AyMAuhAq%AlAgAbGEA] AWARAN]AK4AHAB|A>`A:ZA76A5]dA1A.ZA*͟A'B[A#XA dA^A"AZA~AoA peA EAAAr@ @7@J@ @@@@(@f@Иt@@D@@Z@ @M@@@@`@@5~@@@x@@=@A@K@&@@z!B@t>B@n|@h@cZ@]H@X@Sh@N@I'A1jA-XA)AcA^AYATAP33AgcAbOvA]J#AXXyAStANAI>AE>A@XAW@9X@4;@/@+@&\h@!*@N<@@ @@ @@@B?X:?^?W???Pr?=G?T?ȕl???E?!?!?Ec??=?y? ??ʬ?˧?%?!?~?w"?p?j*0A=A;یA:A9A8%FA6A4A32A1NA/JA-($A*A(RTA%A"A61AA9AmA tAA@N@@g@Q@V@y@w@E@@?n?>?@@@7t@W@w@k@@@S@+@A)AAhA)A3A=sAFjAPbNAZ'AdXApoiA|"AATA?AAAAAoA*eAAϫANAA=Al"Ah>AÜxAA͎A$AaA?HACaA ArGAAAA AxlAAABBBB߾B|BBOB BBLB B B vB !-B B _!B B B BBhBBBSB BBbNBQBqB&B[qB=BBؓBBaB+B?BNBZBcBiBkkBjBfB`BX+BN q@DFt@Jk@Q,@W@^@hB1@mq@r@x@~h@,@@@@@@@[@ @@΅@@`@~@o@g8@H@(c@ @@л@@d@@A@N@@G@?@J@\)@s.@@@@@@u@}@O@i@\)@w>@r@n@pE$@u3@~!@@@h@@F@͟@Y!A $JA&A)`BA5`A@AKB[ARJ#AYA_MAbAfiDAiAgیAdMA`^A\AVkQAJ7A?A7 A0 A(tA!K^A4AmA CAA|@w@@ي@@@@ @O@@K@@rG@f@[@P@Au@3@'n@(@@֌?N?/??m? ?ȟ?o?'?:i?V?x?ı?O????,??l?;?+?P?ı?n?P3?k{?>??p??@^@@/@ x@ @0@$@@O@@N@^@@r@?@Q@ @!A@"@#G@$}V@%{t@&e@'#@(I@*K@-fQ@0@:o@E>W@P82@b|1@uU@ @@Y@@A+A)A?VANHA_AnffA|A:AA"4AANAYAuAlA'AAA{AAkAAAA~ALAAZAAkAAAA$ A7ALdAa|AxAnAjA AA1'AaAMAAA|AAAAAA9AAEA?AJ#AbA=AT,AAOA4A:A2A{kQAwArAng8Aj#:AeAaA]6AY*AU{AQdZAMMjAIK^AEZAAxlA=A9A5A2 A.XA*^A'/A#A RTA$A{AEAnAb9A fA zA:AAZ@@A@[-@G0@B@%@ @4@g#@Ѵx@\@ț@4@@@h@CW@6e@A@e@@t@P]@@W@@@B@ l@@!@}^@w@q@l @fu@`H@[@Vek@QB@L:A."A*qvA&lAa2A]bAYzAUARYKAb]A]TAXAS>AO1AJ<6AEAA@ AW?r?0?xQ?qT?j?c?]}?W@9ł@4@0M@+g@'<6@"@@]@C@A@W@ @@#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AXoA A )A BAA=A}oiAzFAxMAuvArϫApLAm^AkFtAhیAfDAdAb?A`jA_6A]A\OAZAY֡AX(BB BTBDBB33B3BLBB@BBBS B(BB+B xB =B -CB BBXBRA)_AAJAzAzABpBxB >wB 0BYBB7BB:B BɺB YB!aB# dB$8B%KDB&GB'/B(9B(ʌB)B*(B*B+S@B+רB,QB,B-*eB- B-B.2B.|B.4B.B/6B/iB/$B/B/B0B0#B0=qB0SB0fB0vB0MB0B0 B0B0B0B0B0B0B0mB0B0_B0}B0rB0gB0ZB0KB0@@D@@L@@o?@ @d@m@Nf@=@@BF@c@@w@1f@+@s@ǹ@3@p@O@@ԌT@@ɠ@a(@D@J8@r@@&@m@@@@@@@R@@5@@z@Ń@3H@ݔ@{@+AAAeA"0UA,HA7ABe,AItTANfATAZrGA`lAev`AgAhhAjVmAkPAmzAkpAhAeAYHALcA@uA5!A*xA pAAA@4@$@ܣ@#@0j@?@ @C@]@v@a@NA5@W@ @N@@!@@ Ɇ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@%@.@!@(@s@[@O@ @}@Ƕ@M@HV@KI@@µ @{5@,@@P3@@I@o@@r@o7L@^b@Sł@OH@NN@Nqa@P@UD@]@i2@{V@|1@4@N@0@}@ښ@+AA AqA%hA1˒A>ANA_GAkAv8AnAAAVAAAAzDAAAAAAAFAaAA-AAȴAAABA AAA"AP}A@AAAAbNA'RAA֡AuAtA}AA}cAzAw^5AtIRAq{An,AlGEAizAg)_AdAbPA`zA^YA\FAZfAYMjAW^AVB[ATKASwASuARJ#AQVAPMAO:AN"AM AK|AJAIیAHAG~AFDgAE%ACɆABXAAu%A@A>A=!A;~A9A7/A4A1A.A+<6A'>A"AbXA^AA:@LY@9@@@K@Bz@ X??ٍ&? @ U@3&@o{@8@@PH@唚@UA)AAA*6A6ȴACߤAOAZ6Ae33Aoy>AyA,AmA=B+,B*qB)B(_B&+QBoB&B)B+zxB,ðB-ðB.FB/CaB/B0\B0B14B1B1bB2+kB2u%B2?r?ƽ???E??b?7?bc??Y@@@ @@@@ >@(Q@0+k@8c@@@I@R@ZF@c@mM@w!W@@@@>@׈@p@v@x@@ B@@;@@!@H@< @ڠ@(x@@+@@@A|&A/A1AA A A#AAIAAAAt4A0A3AAfAAA(AAA4AA .AAA@g@@}@{@v@e@鮧@@b@&@@@A AAXA AA'MA/zA8 AA JAJ{JASA\iDAeSAo%AxJ#A}'AqAArAnA)A=ARTAiAGAA&AeAA AuAzAt+kAmAg/A]fAMC-A>LA0m]A#A_A hA@̸@۩@@j@@F @@.@xE@f@U@Ee@6P@(*o@*@ I@?'?H?̉?"???0??#?)J?k??eA?0?r?5?N?e?9?=?@2#@ _@U@X@(@ 2@&y@,r@0@4yh@8K4@<1@@-8@D>l@He@L_1@O@SG@Wk@[<6@_@cI@g@ix@lK^@n@qq@rp@s'@s@ti@uȟ@v@v?@v@vc@vA_@w@yk@|E@F@"@@M@@ AATA2ALAfA}A AA#nAiAu%AA$Aa|AA%AkAA.A6FAAzAAAĊAm)ATaA@OAAF AȸA AJAcAŇ_A'RA AľAĖAlA0ASAAAdAA;AA8AaAAҽAAiyA*AvA#AsMAA/AAAoiAiAzAA~A|AwC-ArMAm֡Ai xAdMjA_A[AVARAM˒AIhAEm]AA_A=eA9AA5A1A.XA*zA'J#A#;A A=ADAkAŗAA iA rAkpAuA@p@@w@(x@ѷ@z@_1@M@Y@΄@˧@-@@Vm@@@]@2M@@7@+@O@/@l@+@@@r@H@}^@wyS@qm@jߤ@d@^'@XL@S"@MrqA/A+A'A`A[/AUAP}AKAf7A`֡A[AV"AQ ALIAGƨACA>YA9'A5CA0#A,YA(IA$!A (A:A*AXAA A ]nAAp@/@@@%@ @@=@ 2@q@G@|1@3@ @l@@R@@7@@VX@@@@Y@H@@@|i@v@oW?@h@bx@\Ta@VQ@Pxl@J@E9@?@:@5qv@0v@+4@&@"N@֌@|@AJ@"@ @ :@oT@M?M?P??m?x?6z? ?4/?q?^?do???? ?M@??3?ֶ?t?x?ti????x?q?k)?d?^!?W ?QJ?K2?F%?@i?;m?5ԕ?0?+!?&э?"?{?\?e?i@d@_7@Y@TA5@Nc@I@D@?@:!@5@1/@,@(@#@c@/o<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<? E?H?|?@@ @@&@ @#>W@)7@0(@7]O@>Q@DSe@I@Pw@W@_6@hS@r1@|a(@z@@@@4@@8@E@(@@ @/@W@V@8q@e@%1@vAZQA A>lAAA A'pA.]A5A>AH7APAYAbAmFtAw5AALAGAAA0AA[AvAA1AmA1'A+AAPHAAAA#AuAAnATA:*AAZAWsA AfA lAzAAUAʌA8A?AkQA AApAC-AGAJA|AyѷAv)AtCAqEAoAmAk4AiAhAgxAfeAeAcAbAarGA`[A_@A^*A]OA\A[2aAZgAYAX AX!-AWDgAVh AUATATE9ASAR@AQzAPuAO~ANSAMdAKjAJ,=AH?}AE2AC&A?AB!,=B!B B ּB B RB \]B ,BqB%BBEBBBT{BB(BBB!BBhBEB B[BBlBB*KBHBFBFB 4BA(A%ABZkB 3MBBUB;JBhBB,=B ٴB#QB%B&BB(_B)#B+B,@B-UMB.S@B/="B0B0یB1uB2=AYAu"AAxAgA!AAAAAr|AA{AA.Aw2AtAAuZAɌA˩A0ATAA,ARAxAўAкAUAA̒A4ApAɻAfAخA+A9Aq AAlAPAB'A\AAqAAIAgAA A=A|AǮAAsAA7A0A!AA"A~~Ay1AtAnAioAeA`L0A['AW ARAN.IAIAEAA$A=A9@A5XA2A.RTA*9A'(A#A GAAAn$AGA2WA -mA 9ATAIA@U@@_@@@⑧@݇@ؚ@@|@D@Ő@^@i@@@82@@˒@@D@F@)@U@2@@ܱ@I@Ɠ@zH@s@m3@f@`9.@Y@S`A/A+{JA'QA[AVWAQ8AL:A`A\@AWASANu%AIAEAAAcn>TG??;ס?h?O?ܱ?gM?ԏ2? @@v@3@+'@9@GM@@V=@e@u@gb@q7@!@*E@@@G0@@.4@@9@K@ƨACA}A A8AA!A)A0A9($AC ALOAVSA_tTAiAtXyA~qvAAAmA AfAAA%AA1A6A0A AeAA0UAA?A \A+kAȯAĺ*AAAAA0AAAGA&AdAzA'AAOAAZAHAfA=qAAɺAjAIA{AAxAuJAsAq7ApoAo+AnPAm͟AmAlkQAkAk}VAj)AjRAiAh&Ah$Age,AfAeAe9Ad.AcZAc:AceAb,Ab'AaiDA`4A_ݘA^A]A\ A[AZAY0AWOAU)ARaAP9XAMuAIȴAESA@A;KA4?A,OA#AA KA@ J@@oT@Y=@o?!-?(9?Q@#:@p@9@H@l@`A AA*ɆA;,=AKAYAf7As8A8A"AAeAAQAAtTAoAV9A/A6zAg8AyAxAjAZASAgAA>AtABBB!BB $B $B B9BrBxB"B&BOB˒BB+B[BBtBIB"B{BjKB B B!B!B"B"3B"qB#MB#XB#VB$MB$B$ބB% B%_VB%B%B& B&AB&}B&B& B&B'1B'B'!B''B')B''B'#B'1B'B&B&B&B&B&pB&EB& B%B%B%uB%5B$AB$B$XB$B#B#JXB"nB"utB!B!UB B g8BBBaBBBBsBSBdB#B BfLB [BVAA\]BBB[=B B|6BBqBMB3B!tTB$uB&$B(.B*{B,u%B-B/=B0q B17B2B3tB4LdB5B5ɆB6qB7 B7WB8B8B9B9mwB9DB:!HB:pB:B:B;5?B;k6B;B;B;B<B<0B@U9@^@f@ok<@xXy@͊@@B@@@%1@|@@L@v@L0@@N<@@@t@,|@ͤU@Ո'@ݹ@;y@@S@:A)AaA \A A +A>AAAA}AjA\AV9AV$AA |A#A&A)UA,+A/AA2A5A8A;A>QAAADzAF&AITAK6ANMjAPgASeAUAXA[F A]A`AcAeAhTaAj"AmApg8As'AuAxA{@A}cAQAUAoAxlAAOA A6zAAAOABAjAA}AAARAAA"A!A2-AB'ARTAbA~Ax8AU2A4nA6ACAA@P@ ?˧?"h?Ta?2??'?|?L?u?F???ܱ@M@ @@ ?@+3@7^@@$@I0@Re@Y@_xW@f @kS@ps@u @x@|H@@@h@@;@5~@r@x@ l@@@9@"@Z@Q@)@G@@@:?@@?@;@C@ŗ@8@h@o@+@@Wi@@@@V@G@OL@^@ɱ@AA5SAW7LAx`BAA̘AA:A;A@AqAAAu%AǧAUAAAAA\A2A֔A5AٴAA rA+ANAZAAAA!AQA_AAA|AwArAm`AibAdTaA_A['AVAR_pANAIAEAAA=MA:CA6XA2IA.A*A'{A#IAAAlAAAGZA 1ABA"Ar@@z@L@s@5@@N@L@@ @2M@v@g@PH@q@c@b@Hk@GE@_F@@o@82@y@=@@V@iD@L@|"@v<@p@kB@e˼@`x@[I@V.4@8@3ϫ@.|@)a@%F@ @:T@@@@ @@@6;?Ec?P?? ?ޓ?[?P?o?Ʒ?)?¹??h?r?V?_?`?b?e?v?e?rG?? ?|?us?n=?g?az?[1?U?O'?Icg?C%?>Wd?9 9?3d?.?*{?%Y`? z?P??Ɋ@ciD@])@XQ@R@M@Hy>@Cf@>n@9@4@0$_<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<??@8@ć@p@@ϫ@*@G@"r@'@,b@2!l@8/@?@Fuy@L2@Sh@Z @`@g@oR@w@@@@@@@R@@@3r@@@`@@4@@x@ @;@=A AUAIA AAA7 A%oA*@A0OA5A;5?AA AFALFtAQAWHA]^AdAjApAwm]A~A4A>BAAAAǮAsAE9Ae,AYKAAAYAA4AANA6AAOBAXA=AYASAsAhAnA8A>AwfA.IAAg8Av`AjAAAEAA?AMA+6AeAGzAzA^5ASA~]AAzAAAPHA-A%AA~BA}]A|xlA{bAz/Az33AyAycAyuAy0AxHAx?AwAw/AvAv6Av>BAuAu At/AsoAqAp\An?AmAk/Ai+AfAdAa A]FAYAU AQAAL+kAFpA?TA7A/wA&BAA˝@K^@ϖ@\@@Ck@A?B?*>?e@ @Mj@5~A"VA=YAWAnAAA AvA͟A:AAAA<6AvAAPA–AǪA]AҁA9AAA%AAf2A7ABBBB +B DgB ,BBBV9BUBx8BBH1BBxB# BR BdB[B ="B! B!̈́B"B#%zB#B$MB$ѷB%KB%B&$B&B&;B'1B'|B'B'}B(6B(ffB(B(B(_B(B)EB)'B)F%B)cB)B)IB)iB)bB)*B* B*B*B*B*KB*B*B*B*B)aB)jB)B)B)B)YB)*eB( B(B(wB(.cB'B'mB'$ B&rB&DB%ĜB%8B$B#B#8B"h$B!4B |PBVBBMBB_BBlB ]B/AߤBzB AbNBHBÖB;BؓBңB#fB&|B)mB+9B-xB/B1(B2gB3B5 B6B7B7+B8ƎB9B:5tB:B;kB;BB>UB>;B>B?B?UB?B?hB?#B?wB@~B@8B@P.B@dtB@uB@{B@B@$B@IB@B@B@NB@B@B@SB@B@%B@|6B@q B@dZB@VB@HfB@8B@($B@B@B?B?OB?#B?ZB? B?XB?tB?]IB?F%B?.B?B>BB>B>̘B>MB>B>B>e`B>JB>/B>B=$B=IB=B=ZB=EB=iB=KB=-CB=B;уB;$B;|B;OvB;B:B:B:|B:=B9rB9B9ZB80B8B8 B7r-B6B5B4B1,B,B3MB6<B7yB9B9B:B;`>>;>>^x>? o?1?)?>=?\{5?~f#?j?i/???o?u?v?}??w?]%? ?Б?ʥ?Β?q"?l?݆???) ?e?'@a|@t?@@ Ѣ@?@j@@G@#>-@(i@.s@4m@;@D!@L@U@^f@h@rݭ@}x@P@"@@@@gb@8@)@>@@a@Z@x@D@<@b@ٷ@< @GZ@o@1@ @@CAAvA(A A K A1AsAA_eAFA7A!6A$B[A']A*fA-'A1 xA4dA7xA:AAGAB4nA=|A8A4SA/A++A'B[A#A(AAA=2A}A ӤA=AAPS@0@hI@@ȟ@1@j@@I{@ @_1@@D@/@@U@W@+@@w@F@2v@7"@X@O@@N@y@r@l9X@e@_V@YfQ@Sm@M@GX@Bk@=*@7{@2@-@)/@$@@@D@3@@ R@5~@v`?????i?8?~?? |?[?R?ud?;y?%?4n?eV?R?,??r?D?2?>-?e??|?t?n^?gb0?`?Z?Td?Ng?HJ?B?=jH?8V?2?-O?(3?$?h?*?w@hu%@b@\/@Wh@Q@L@Gd@BF<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<‹ʪ;tѿPH4׿sQ"+Կu>-ƿqۇDg/ĿG 꿁9S6iP㾀%aO> O>"?+ ?gd??K?=?@@!t@6I@LH@f&@F@k@ @@Jw@@<@̓@޺@f@AAOAGeA ;A aAaA^JA-A@ASAfAjA4Ac A/OAQA8AA­A>BAњA<6AײaAMA?AگAڹAڡ-A7A5tAAAy>AٴAATAτA;AkA]AIRAȂAA33AAAAAwA1AqAAӏAOAAWAAiA,=AcAAmA AAlAjA[#A]/Ar|A~AAQNAAbAAvAAzACAqAAmAZAWsA{A'RA1A$`A@@}|@!@ t??@-m@s@uAAB0AB2mB4_!B5B7CB8}B9B:B;BPB?'B?B@5tB@WBAKBA}]BDT,BDfBDv+BDBDBDBDKBD)BDBDBDBD,BDBDYBD}VBDsBDgRBDZBDMBD>wBD.BDBD PBCdBCBCՁBCBC)BCEBCBClBCVBC?BC(BC4BBrBBHBBBBBBBB}BBcBBIBB/BBBA*BABAaBAmBABAnIBAQBA3hBAB@FB@ּB@zB@gB@sB@P}B@,qB@B?\B?B?B?abB?2B?B>B>&B>UB>TB=1B=uZB=B<6B<+6B;B:B9B84B4 B36B8}B:B>>> >x->u>D>G>S>>ş,>X>o>"p>(>?1?,?`?a?????_?Z??Z\? E?=???3 ?e?z???+@e@ B@^@ @ #d@(@16@<@H@U!@`$@j@u@"S@~R@$t@%1@e@!@@|p@@ɥz@a@@9@@@$AgA jjAoAA#YAMA#0UA(u%A-A3{A9OvA?K^AD`BAIa|AN{ASAY,A^Ac(Ag/AleAqhAvA{A7AACAAAAA҉AA%A rAApA6ArAAA?}AΥAhA&AR AAYKA_A–AAǫAIARAB'A2A@A{JALAFAAPHAHAuqAe/AWAI;A@@X@@|@@p@@H@@"@X@v@&@ײ@@A@@|1@@Ex@@@M@ϫ@@{@ W@P@@¤@bAiAHLAu!-AAWA\AAfAAǪApAȐ.AuAřeA"4A,=ABA”AHKA A Aq AGAӠ'AހA)_AӏA⃰A9AܩAADAѾBA@A[AEAǮAqAAYAAwA8RAÖA^5AAAAoAcTAGzAmATA+AAncADgA+kA"A)AAUAh AANA|jAw-wAr AmAhqAcJ#A^:AYMAUm]AP.ALXAHh AD=qA@(A<*A8A A4lA0qA-A)fA%XA"8A$ACA{A.AcA=A (A1A0A<@q@@e@6@臾@@@ @a@@@P]@ś(@"@v6@@@dZ@3@+@@i@=\@p@@@{ @#@@&B@}@w/@p @j@du@^uy@XA';A"AVATAOkAJ\)AEBA@KAXXASK^ANSAIsADA?|A;S&A6A2WA-A)A%A!h AbcAqA9AѢA 'A AAA'(@@A@@K@X@@/@ϗ9@ @˧@x@@@@t@o@E@@Vm@0+@#@4/@\)@@@~@w@q0@j@d9@^G@Wb@R @LL@Fc@AA@;@6)@1@,H@(@#}A@@@T7@-@"@ 4n@aR@???.?鄡??ܺs?֘5?Р?1?.??Y?(??28?k?*?A???oi?d?v???z?sp?là?f ?_D?Y^?SA:?MOj?G{?A-??+??ޫ@-#@7@[@P@n/@u@@}@fAA+A5A)TA6A4A&AAbAA%ASArAAARA0AbNAjA\A>A$A>A)AlA A9$ApAfAAo5AAA0AW?Az Au_AobAhpA`AX lAMABE9A3A$Aw\@j@@M?f?ox@ACAjA1AAA*A#nA AFAZAAAbNA AAA٪eAAAAA^5BwBlWB B @B BBBGBIBBi_BByB MB"B#L0B$kQB%xB&vFB'd@B(CB)2B)B*B+?B+B,zxB- B-bB.VB.MB.|B/Y1B/B0B0bNB0}B0B1:B1z*B1B1]B2!HB2R:B2B2B2ؓB2XB3,B3*B3B3)B3hB2B2ӏB2cB2B2W B2$ZB1B1;B1nIB1&B0B0B0)B/_B/\B.B.k6B-B-MPB,B+FB+/B*S B)[B(CB'B%B#ּB!B}BlBB-B!-B,BIRBBB#KB&JB&>(B*B.^B1WB3zB5B7_!B8]B:SB;yB<B=pB> B?DB@zDBA:BA=BBBC&fBCvBD1BDmBEBEwBEӏBF'BFtTBFBFBG4BGi_BGBGÖBGBH BH)_BHCaBHZBHmBH}uB=HB;]~B6AB9B=A @~L@|8@z(N@x@v#@t@r@p@n!B@if{@d@_@[L@S?@I/@@6@75+@.h4@@@?.?!?d?s??UG?Y? ?u?d?Yv%?N݋?D2?@V.@mr@@2@w@*Z@@|F@J@bx@@z@Ƒ@@۽@'@)@AA {A?A۫A%.A-|A7-wA@AK!ATRTA]AgAr?A}"AAdARAA 7AAAsA33AA9A^A$AGAAA`vA(XAAxA<Aa|AnjAȅAwfAjA]ARA>A*AAAAFA[A|PAAAAjAAAhAB'A 7A]dA6FAqAbCAUAHaA:F A+AAkA@9@@E@@B@8@J8@Z@c@E B@*@@:?@ 7@ @@;@ 6e@Q@@}@(@3JM@=Vm@EDg@M@@V"@_@hr@r.@|S@r@@@>@/@@a@<@|@6P@@OL@@C@@%@@+@@f@@@X@@@f@@(@t@@ @T@@@@[@ײ@@@;%@f'@@\@@ݘ@6@a@QY@p@f@@@V@>A(AGgA%A)AdA[ADASA/AAרAAEAAMAAAmA+A5?AA{AvAqcAlAgqAbA^F AY3AU[AQ ALpAHXAD$A@AASC-AN6AJ!AEbAA1ABA0 A+A'ݘA#;AAcAMA-AA ZI?9ZK?4='7|8A񪿲࠿( ( r*u(0K67.58)AXA;A"AAAQAPHAoiAzAkQAAAA{A[#A;AAA(A-AD3A\]A+AʌA9AA8RARA=AAp;AAtAiyA AAOAAqADgAAOAOASAAuAS&AE9A'AAAF AچAbNAAJAAA?}ArGA(A}GA{?}Ay JAv6AtAqRTAnVAk,=AgخAd%A_0AZAT/ANQAFA>xA4A)3AA %@E@@7@@~R,#<"@`AxWAJAr1AzATAYA[#AD3A%A lAAFAtAAA6A>ABA풣AAAyAaA A'AB4B:BB {B BBc:B2B+BbB-BUB]B!wfB#PbB$B&yB'xB)$ B*SB+nB,vzB-lB.S B/*B/B0|B1eB2pB2]B3CGB3B4W?B4YB5RB5B68B6qB6tB7GB7B71B8eB8TB8jB8oB8B9B96zB9RTB9iB9|B9B9B9B9B9B9B9tB9qB9B9}B9iyB9QB96FB9B89B8PB8B8sB8AB8 B7ΥB7B7IB6}B6B6ZQB5B5B51B4.B4CB3B3,B2B1B1#nB0PB/eB.]dB-0;B+B*9B(HB%֡B"B[BBWBwBp;B-B#WB(WB+kB,B.B3ZB6TFB8TB:tBHA6A!AJAAaA[@@8@\h@צv@&@do@(@V@@J@I@΅@E$@@q@\@@{@s @g$@[@OKI@DT @0U@'`@iY@)@ w@I=??-??C?T ?ę?(??]?^?i?q?&?o?~?n˧?fh?^ٌ?S?Eo??3?9?.6?&?"W?a?΅? ? ? g? P? 8z? 9?? ?? ?.|?Oq?@y@ @'N@+@)V@C@@@ "S@F@@͟@@w@,c@?@T @n"@'@HV@@@K@Z2@@?AVA A(AA!خA,W?A5A>FAHeAQrA[҉Af"hAqW?A}AAHAԕA1AA3A4AMAAA4AAA A.AiDAόJA+A ADABAէA_A$tAEA*A1[A.AA#AA$A[#ABAAxSAav`ALA9A'QA@z%@@=@,@@e@Dr@-@3@u@s@3]@ "@@1f@ s@X@y@%@//o@9\@D!@M@V $@[@a@fզ@li@s"@yW@~Y@M@@<@?>@*@d@!@S@z@. @@@iD@1@*@Ӯ@@D@o@Y@I(@1@!@h^@;@@%@@P@X@~g@@@F @P@x@@@H@P@s@T@ @&@@%@3@ (@9@i@@Ǐ@]@ @@ی@@@ީ@@h@tB$BBB3BeBPB VBrBBKB AA ANAAAAܰAjA'Aс;AA{AA>BA\]AюAA7A%AC-AAԒoAOAA.AʘAKA;A}"Aļ6AAAkAVAA lAcAAANA xAAAkAAAHA4AOAA5AzUAuApAk7AfAaA\jAX/ASAO~AJ$AFlAB#A=A9A5tA1Ac5A?A A OAAR@u@LD@@@g@f@ڈ@W@8@7@q"@?@.@=@j@@"S@@L@ @!@3@R@@b@@~i@w@p`@j;@c#@]@W@QJ@K@Fd@@@;u@64@1,@,b@'@#CB@@c@!@7@ Z@ -@82@??z??Ec??܁?b$?m?ʢ????0?;?+??I??"?]?z?S?J?]d??R?zqX?sk ?l?e?_z?Y1?S?M"?GZ?AI?@ W?z?d?j?~\?C(O{>O߿ 1<1RGe]zre#d>یE.5~`U\vR:bkMUD(mS0r\L>b#?\S?ϖ@<@V@@zAA\A.AJ AgAA~A.}A)_A~ABAAA4AqBB BKBBFB-Ae`A(XAAAAA&AQAnAAUAAA;A?AMAAAޞAA)Ap;AAZAAAoA&LAGAAA\AA{A1AA49AA{A AAW?AAA"4A)A,=A5ANAAAVA+AtA)A\A0UAR AwA@AAVmAŢAAVA AAAhAMAVAAuA5A AA_pAIRAzAvTAqYKAkyAeA_+AWtTANĜADϫA8BA+PHA*A b@@:@@J:5lq?︑@A2A_cAAߤAABAAGADA"AA%AAܥAuAvAAAA{AAIRAB[#BtB W$B |BG+B7B#BfB&BZ7B8BNB!?B#[B%GB&QB(O(B)0B*B,B-8B.C-B/=VB0(B1zB1B2B3]B4B40B5]B5^B6[B7)B7B81AB84B9 #B9jKB9{B:B:dtB:3B:jB;;B;q[B;5B;MB;&B;B<B<"B<4B]B<1'B<pB< 7B;B;B;}B;1B;]~B;/B:jB:ňB:B:K^B:B9B9rB9!HB8rB8nIB8 dB7tB75B6B6BuB56B5,qB4B3B37LB2shB1B0B/B.mB-8lB+B)zB'B%,B!5BFBGBaBBBkB!TB()yB+B-B/B4/B7{B:BIB@ BAdBBBD2BEWsBFcTBGXBH9XBIBIŢBJtBK{BK$BL0BLBMBM7BMBNB BNBNܒBOBO\CBO@BOBOABPBPW@5@b@F1@1g@#$@ @@@,@u@h @&@@ @g@"@)"@/@7@@L@IC@S]%@]d@gu@q,@z@S@}@ڥ@n@\)@Q@@@(@>@,@K@̣@@`@G@C@)@C@c@Ԉ@ֳ}@@ @U@ߗ@e@,@怈@5@%[@[@@K@@5+@k@4@@`@L@ߏ@۰@؍P@uy@u@@v@+@̂@ t@ə1@(@ƺs@ŏ@0@Ҟ@t@@@[@@EA/B?}BB!B|B wLB pB_B.B"B@AMAnA(A_AABAAIRAA܊=A>AAtA~]AA8A氊A/AoAޏ\A۸RAA׋xAzA'RA_ACA+A=AVA7AdZAA8A^5A?AbA]AwAnAAUA$@A$AA+ALA{AAiAMAAyAtN@@@u@ @@l??\?k??C?ܻo?ֈ'?Ё?ʥQ?w?j? ???X??T"?%?d???[??.?z:?y?r‹?k?e\?^ ?X?RL?L?G?A?w@?o??:?L6~ӕޔFv6Ju.KgȌtvƓ">3 f=#ӿ¿,>?@?F @6&@@FA;A5ASAp`ApAKA1[AFA=A0Ax8A7LAAƨA7A1'AA'A9A>AdAAl"AAbA AAAA`A۩AaAAЌ~A˧AAAAAVA+A>A+A$ AXEAA~AAANA'A=A-wAAOA3A'A/AAZA-AQNAAA PAAAA8AA]/AA_A APA;AIA'A>A` AAuA^AޞAWA2AΥAArA3AAAA#AqAA A'AA8AA8A4AnAA?A~,=AvZAnAe7LAZ;AMgA>:A+rAs@Ë@$?;OБ@kAAS*AAMB>B>B? \B?YB?B?B?B@VB@4B@SuB@m]B@B@B@B@B@wB@B@B@nB@_B@lB@vB@`B@FtB@(>B@YB?B?B?lB?WsB?!B>2B>B>aB>B=fB=rB=BBSqBS BSɆBS/BTVBT*BTCGBTXEBTiBTxBTMBTjBTBTBTBTBTBTBTA։AA}AARAAo5A} AjAYAI~A7QA(oAFA uAf@¹@֜c@0@Y@@KI@{@[@?Z@-@$ݭ@B@{J@Z@3]@v@`-@ @&@-")@4@AAZAYAGA mRA ?4AAA@?]? R?=?????| ?uO?n߃?h"6?a?[3?T?N?Ii?Cd(?=?8p"?3/4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AGAvA AN1A A RA A oAA؄AA@'R@@#@G@S@@ض@w@̐C@@I@@@w@z@I{@x@@i/@@|NQ@k7@[G@I@7;@#Y@F?s??@O?">neؿ6UW,IE0DU^ͼ2MwP>3$_()S&,#*QQ8GEc"yQd?@g~@^ AL%A'AR!AzA1A=A_AJAwfA1'BߊBeB .BFBHB@BBxBdtB tB NByBBA=AJAc ATAvAA;AqvA,A͛ AȍA AV9A=AkAAAAAEAZA DAAfA AXAHAiAAAAAhAJ#AALAA}AϫA_pAA'AAAArAc AbAAA^AAACaAAAA-AiAXAYA3AFAHA<6A!ADAAACAAAAMA@A]AA4nAyrA~]AB>;B?( B?B@!B@pB@-BANBA:BAGBB:BB|BBBCBC6BCgBC BC$BCkBCBDBD"BD1'BD;BDABDCBDAoBD;dBD0BD"NBDBCXBC޸BCBCBCuBCIBCBBBBkBBlBB(BAߊBAHBA:B@jB@zB@vB?WB?B>gB>UB=_B]BO>BP'BPBQBRqBSBSBT3MBTBU#nBUdBU"BVCBV@BVۦBWIBWXyBWBWBWBX(BX0BXNVBXgBX}BXHBXBX"BXBXBXUBXÖBXðBXBXBXBX[BXBXBX@BXBXxBXiBXZBXIRBX7BX%,BXBWBW_BW,BWwBW$BWNBWzBWbhBWJ=BW1BWBVBVBVJBVBVBV|BVa|BVF BV*KBV"BUBUBUBUBU|BU]~BU>wBUBTBTݲBTBTBTvBTQBT+BTBSCBSBSBSW BS%BRvBRBR}? ?҆/??^?GZ?dE?8?????#y??#:?N?P?}9?|??z ?y ?xw?zO?}&?n?{ ?b?W@@#@j+@@V@@@@@@@@BdBBWBB,B RB XBVBA+ArAzxA7AEAA߬AAGAAߺAAvA|AAYAA-AvADAAAAAAA A~ABArAYAAVAcTAA%FAA0!AHAADgA+A]AAAA4AgAA`AS&A}YKAxArAmTAh5AdYA_W?AZAV"AQ?AMU2AHZADA@qAA1A-A(A$ =AY ATZANAIAbA]AX9XAS AMAH|AD JA?>BA:A5A1fA,8A(A$dA =A.A4xAQAwA QA ,AA)?@@A@|1@-@@@)@qL@@k'@@9@@U@*@|@@|@'=@@J@л@@@h@u@z@s@m@f@`HA@Z"@T&@NS@HD@C#y@=]@8H@3s@.@)d@$C@ o@@>@z@d@ j@@ɛ@ ?"S?5??x?F?ދ?X?Qn?tT??4?ρ??v?s?m?&?k??K?x?d0?l ??W?|Q?u8?nQ]?g?ab?Z?Tm?N7?HF<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AJSAIjAHAGAG =AFVAFAEAD!AB*0A@;dA>;dA<&A9A7A5q A3VA0A-A+ZA(3A&"hA#iDA 'A AALDAW4ANpANA4AsAR AٴA{AGA?Ag8AA0AѷAAAMAĜAAAqAaAAeASAZAsAA֡AApA)A-CAA4AAAfAArA AAiAvAANB{B 2-BWsBBBBBBB :B"pB$nB&AB'B)kB*ňB,'B-B.B0:^B1B2B4B5\B6gB7B9B:OBB;BB>XB?_B@kBApoBB$BBBC\BCBDd BDEBECBEBFBF_;BFBFBG8BGraBGBGҽBGBH BH7BHMBH_BHl"BHsBHw2BHv+BHpUBHeBHWBHE9BH.}BHBGBGуBG0BG~BGNBGBFBFBFbBFBEBE|BE$BDYBD`BCBC~BCiBBwBABABB@ B?B>jB>B=-B;B:shB8gB6\B4iDB1B,B!IRB"MB+dB.VB..B-B5EB9NB;RBR>l>??BF?Qrz?o?ek?ƙ1?Ft@*@:@j@ @A`A(A?6AJqAU"A^}A`l"Ab_AdVAfQAhQAe{AbA^}A[AW!ARAN4nAI[AC:A=A7A0A*A#q AA)AłA EAA@G@݃@R@Ζ@Ž@Ex@@L@|@|1@~@<@@33@} \@lb@\s@O/@C@7V@-O@"Z@p@~@@#??)5?C?;?J?n?B?r??Կ??X?N? ?^5??|?5?D?}'?{m?z1?x?wp7?yD?~C?&?S?3?ʗ? ??x???lv?W?v?Ĥ+?@@)@@iD@@HA@S@]@h[B@sN@~ek@@a@=@@@5@~@b@ $@@3@A@7@ȏ@̩@Ж@F_@Ѣ@&W@1@@ڥ@Ft@@@씯@:@T@*E@N@]@cs@}@@@@J8@@@@tT@82@w@:@&@@&B@<@Se@`@J@@t@e@b$@㶅@@7@x@ܼV@@K@in@HV@@D@֡@/@˜@|@BB~]B "BqBBAAAAhsASAZA|AڹA֖A&LAAԧRA49AAAAwA5A\]AڃA AAn/AAA^5A|AÕAGEA+A&LAA)A^AA]dAKApArAxA&AAwAXAA AwA}A AAAA~!AxAsDgAnAhvAc#A_AZ#:AUTaAP=AKAGq ABA>A:ZA6(A2A.1A*A&8A"q A6A(ADACAmA R A ;AfAA9@PH@q@-@@@/0@@@϶[@ƽ@@9C@@@@TL@U@@q@@@E@@@ag@@@0@}@w@qL@k7LA/XA*?A%MA 4AWAAQخALuAGoiA\ɆAWzARAMAI1AD{A?A;L0A6ѷA2kQA.A)A%9A!'AAA݃AAjA ϠAGzA҉AqV@F@@|p@O@F@a@՝@@@"@C@S@@@2@@@@]d@.s@@!W@A@{@@~s@wy@p@j@c@]V@W:@QG@K}k@EQ@@]@;@5ԕ@0@+{@'@"h@@x@.I@@ I@ 6@+@p??*????>??'(?[?ºI?@??z???;?{?m??gb?I?I?f??}?vƛ?o{?i?b?\6"?V<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AAK^ATA`AEAA`A6A}A| =AzAyAwAu7LArxAoAlAiAf AceA_ɆA\uAYOAV:AS.IAP&AM!AJAG1ACA@A=@A:^A6A3}VA0 =A,ffA(NA%S&A!vAtsA AxAAFA AA@@m3@g#@@l@ȟ@8@p@p@@0U@@i @G@#S?v?iY?ehRTvzVɡ# dMqiDQh1+@%@]yA1zAa)_ABa-BB&rB,QB0ںB3B5 B3B1,qB,B(B&zB"{Bu?BS BxB0B @B +BmBAuAVA/AcA㖇A9AA!bAoiA>wAǓuA?}A'A@AAA\AAoAA+A{AWAAAB'A@AZAAAuAgAFAA>AT,AAsA=AA5 AAAQAAAvA*AAA2AAR A\AAJXAуANAÖA1A A A{AуAAD3Ah A~A ArAAsA`AAA$tA|A!AAEAÖAAAA-wA^A0AaA.AAAAgAo_ASn/A,@QR@xWACVmAuFAAĜA5 AAA[WAOA܊ A,ATAA|PAB6BB0B _B BlBbB#BhBBѷB^BB!CB#kB%B'"B*B-B.B0VB2B4B5B7B8{dB9B;GB|B?B@BAqBBA BCBCBDuBE 'BEjBFOBF?BG_!BGBHYeBH+BI,BIBIBJ1BJ{BJBJ^BK0BKabBKBKBK BKBLBLBL"4BL)BL-)BL+BL%,BLkBL )BK2BK޸BKBKBKy>BKMBKIBJBJBJlBJ%BIBIBI+QBHlBH_;BGBGoBFBFRBE;BDBD9>BC^BBjKBAVmB@=B>B<h> V>X>U>!>)m >1>;>Fo>R>_lj>li>}>ʧ>q>>> >0??|N?*?; 2?1q? ?>O?Y@>@xlATA1 ABAV/A,AAPAiA֢AǮAՓAAEA+6AA4A AAhAA!#@@L@@dZ@łV@1@kQ@'g@w@?@Zq@u@oy@^j@O@B@4@)a@ m@@w2@"@K??u:??1? ?9??s??p&?1?Z????Z\??m?`?S?n?a?e,?y???{???J? ?3?ć?R?ւA?DR?V?q@e@ @L@ @v@'1@+V@AO@]@@%Ar=AB VB@pBe rBhBcfB[BX1[BcxRBl)B[uBDiB,BuBgA+Aђ:AAAAp=qARA5A~AbN@q@ŕ@R@@x\h@T@@X@-ԕ@)@׈@@i??B?d@k@ 0@@R@d@%C@-1@7@A-@K>@V@bo@ni@{W@%1@!@b@>@l@k@@ @_@Ģ@ʿ@J@a@ژJ@@hI@2@+@I@+@;@5AfQA8A(A`AuAyA#dAiAAAJAޞAAFA6AT A=AA=AAJAA5AAYAAAtA=At?@E@ @(@OL@~(@;@h@@@陯@瓊@@h^@o@ݣ@@@Ԍ~@>@@@Ǣ@Ĉ'BuAhsAΥA7AAAoATAiAAAXAA2AA_AA2AGAqAGA;0AtAٲAA8AԀA̘AAnAAA{AAƃA,=AA_A|A^5ALAHAQAgA9A%AAxAAAAAAuACAA͟A'A}OAx)As AnuAi Ad.A_cAZ6AV1AQxlALAHAD?A?]A;ѷA7 A3A/ A+ A'A$=qA "AA`AAx#AA AAd0AE@l@jU@^@@|@~@_@֪@h@@@0@8@YK@@@Hk@X@X@;@@@y@s@@@ؙ@@|@uz@nA1lA,oA'A"jAYwAS9AN+AHbA_YAZ7AU.APSAKAFԕAB/A=A9 \A4tA0]dA,eA'yA#AÖA{AgA AfqA A ,AwAAJ@@?@@%@q@s@ؚA@@N<@@ˆf@V@D@R?@~@G@1@,@Y`@@A@@@@_@@z[B@sp@lc@f%@_¹@Y/@S}@M@G@BI@<۶@76@2oi@-o*@(@#9@< @@gb@+@ @ @(@`?d?>?Jw?舤?Q?ە-?a=?Y?~g?6?D?`???G??;?A?S?w?l???3??w?p?j. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ADRTAPAYA`HAf&AkApfAv$AzaA|jA~)_A~A A~wA~ xA}uA{!Az~AxRAvVmAt-AqAoAAlAjAgAdjAb*A_iA\AYAVEASAP/AMAJAGq AD(A@)A=U2A9A6 A2*0A-|A)4A$fA AwAAרATA DgAA@K@@{5@ۘt@-b@@e@n@@q@uw@PZ@*-@w?,>Ǿ˿dZ,5zV֗ g4oNe`kCGIBhu@A(B BTB/}BB[ AlA֡ABA8AбAAkA1AABA[AXAIAQA2-A>AĮAYA)A&AUAǘ_AǷLAǷAǞOA~(A:AƵ?A5AAú*AA Au%AiAaHAASAMAAA`AAuAHAAxKAUAם@_UG<)_AAh@OAIA1ApoAǚAѻ0Ah>AAEAFAA$A#ABsBBB ^B )B BDBUB6+BJ BtBDB ϫB#4B%HB'B)B,B.2aB0vB2kB5.B6B8IB:=B;iB=B>LdB?}BAfBBgRBErBJBM8BP%BRAoBTmBUnBW%zBXq'BYBZB[B\B]SB^HB^B_[ B_B`pB`BaYBaBb]BbpBbBcBcC-Bc|PBcBcBdBd+BdKBdfBd~Bd&Bd&BdBdBdBdrBd͟BdpBd6BdBdBdBdBdBd\BdBdBdvBdfBdUBdCBd0BdIBdBcBcBcBcBcBcBciDBcP}Bc7LBcBcBbyBbBbBbBb|BbaBbDBb(>Bb ^BaBa}Ba|BaBau%BaUBa5BaB`nB`ңB`!B`B`hXB`BB`)B_B_XB_B_qA????>???^?(?V?T?zN?R???%e?;??n~? 6 ?B1?? >v)??&?"?N~?O?@ N@?d@dE@Ɇ@Ay3AWl"AA<AVAIA AjA ASAWA)APAAȴAAhAgAAwPAlSAaxAWaAMAEa|A<A10A&Ad0AVXA A@]%@@ܜx@@@@@ @ @Wi@d@a@w7@jn@]`@QZ@E@;:@1|@'qv@YK@@ ^@W?L???:*?ʗ?}? ?fAAAA>AA8A~[Ay Atv`AoxAjhAeA`A\TAWASv@%0@ ?X??=v>HzΆ@ڿײ#`kZ˼Fn/!خJaxyMjRo1FggWӏAD`AL0BWB/B;ԕBEZBQ&BU3BTpoBMBEuB?B8AB2B+/iB%B BdBBdB B @B,B{JAA%FA]ASA{AܽAAB4j0B5B7bB9)DB;B=KB?cBABBBD BExBFBGWBIBJ)BK6BL1 BMBMBNiBOh>BPBPBQNBQtBRtBRBSpBSBTESBTTBTXBUH1BUBBUϑBV BV=qBVjBVTBVBVhBVBVBWBWBWgBWBW.BW+BVBVXBVBVrBVBVzBVUMBV,BUBUjBUBU^jBU BT5BT$BTKxBSBS@BSHfBRfBR}BR BQBQBPBOBOUBNBMBMMBL,qBK(XBJABHABG'BEQ4BC 7B@ ^B;9B3B)XB8gB+6B::B?}BEXBHBGVBL!BPBS5ZBUgBWJBXBZjB[B\cB^B_4B_B`Ba{Bb-BbBceBc5Bdm]Bd-BeK^BeBfmBfVBfBfBg BgWBg7BgBgBhBhBh9BhPBhd BhtTBhBhBh[Bh_Bh BhqBhBhBhBhBhBhvzBhjBh]BhOBh@iBh0BhBh dBgXBg`BgбBgJBgFBgBgwBg_BgGBg.BgBfPBfhBf1BfBfBfwLBf[Bf?Bf#BfBeBe̳BeBeBerBeSBe4BeBdBdөBdBdBdlBdHBd#BcBcBc?O?&?H??h?5?:?`k?v?ݭ??~?[?A?/?%?݃? t?~yh?|w?{B5?t?m!?e?^?Xv?Sw:?N0?I ?@l?>@;@٩@ @B8RTB5mB1ǔB.B+x8B(f2B%b4B"lB{B9B}l@r@С@~@v@@@@g#@@y>@&@l@ȴ@j@@@@b@@1@l@x@q8A1$tA+A&WA" AKARAM%AGAB-AV lAQHALAGACa|A> A:p;A6A1A- A)a|A%K^A!HAWTAy)AAAOWA A;A6Aq@O@H@[@l @f@ۃ@"@$ @ʦ"@H@ @@@4@O@q@%@@n@;@$_@'|@D|@z@@~cI@wb@p@i@cr@]%p@W\@Q @K;:@E6@@0@:@5@0n@+ @&@" @@@.@@ @@@ ?j???M+?ջ?ٍ&?q?͂?ǿr?%?5?l7?J8?M?u?j?/?/?q7?B?1?@?kQ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A4NA$A/A8NAA?AHAO"hATAYE9A]ZA`AcZAfAhԕAjRAlIRAmAnAoDAo0AoAo AoAoAo.AnAnOAm_AlwAkXyAiAhtAg/AeAdSAc1'AaA` A^A[AZ5AXDAVAUL0AS6AR APaANhALAJzAH{AEAC~A@A>4A;[A8kA59A2A.A+A(MjA$wA!ANA ArqAXA A @8@@DR@@@F@Se@E ??$Ijm.]?J _pIzOMAA"hA+BmwB BBG_BɆB&BABYeB IB!xB!ѝB 'B5BB1B4BB~B<B ּB MjBB&BkAA)A5AEAIA@AAܺ*AـA֪A#nAAAPAKAʿATA_A,AŷAĦAôAA#Aw2AtA;AAAiApA5tAKA­CAÚAAQAtAsAkAbAaHA̋DA͟!AΜAϖSAГ@Aј_AҰUASAF AU2A@OAAߤAٚkAIAAۋA Aܮ}A6AݺAAQAXAAoAc A8^@5ABAAABAU2A׺AABeBK^B =B <BraB72BBiBBBNB&BB/BB B"B%CB'uB*B,B0'8B4B8[B;B>9rB@}qBBrBDkQBF0BGBISuBJBKzBM!HBN6BO8BP)*BQBQBRBSiDBTBT%BUHBU}BVMjBVBW*BWBWBX2-BXzBXxBXBY&BYRBYy>BYeBYBYȴBYBYBYBYBYBY'BYFBYBYBYBY{BY[=BY72BYBBXTBXBXBXGBX BWBWBW<6BVBVBV@iBUBU{0BUA iA A5@@#%@@,R@ة@g8@c5@Û=@ @@@#@d@@@o@X@]@}@@^@N<@E@EN@~[@zV@v@rO@n`@j&@e@a@_@]d@[E@Y,(@W@U@Ty@Su@Sq@Ssm@S:@S@R@R.@Ql7@O@M@Le@JZ@H@H@Hh@H@G @Gu@GE@FL@F @Fl@F@M@}@U2@%AJA ѷAAAv`B0rBfBxB_HBbqBIB3nB" B-BA AF A'AyAA/OAc AoAg\AR6A>`A.~(AA˝AY`@뙯@l@Ë@@P@@a(@r@K@&@@C@ @M@z@@@@@U@@l@-@n@@^ @I@S@|F@ć@0+@@C@[AA{@AA DrA AAƈA)AAAGAr2A AQAAA/A܇AA7AA SA %FA A;OAARAAkAAAOAKA,A*AH,AmAe AAAAA~AtsA rA vA AA{AALA`A0@T"@@2@@K@a@#@ݚ@X:@*o@4@G@ɾ@C@@}V@0jBB,B@BU2BkB3BBA+AAA]dAQNA`AߊAA-CAѤtAΧAKA"A΍A.}AAqA{AѷA[WA҆YA8AAӺA\AA,AXAAmAAA8AjAA rAyA"AAGA9A5L0A1MA,A(A$A!!AYKAA2AAA 4ApEA9xAW@@ @*@pz@׈@_p@1@)@Ѹ@̼@ @`@zc@V@@0U@U@|@@@@B@x@@o@@ @|@LY@|^ @vI=@pXA+g8A&=qA!:A^AXALAG=AAAU;dAP+AKHAGJABA>NA9yA5A1RTA-!A) A$A AA;:AvAA&A eAyA;A`7@6P@o@@qv@t?@ۙ@߹@Go@@x@A @) @/@T@x@@t~@ @z@@}V@V@@خ@) @"@x#@qQ@j@d4@]@W@Q˒@K@FQ@@@;p@68 @1#O@,1@'b@"@&W@g@in@8@ %p@ .@T"@????4/?ౚ?]?7a?=?m?`?K??`??8??|??U?h?K<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A-AWA%A+@A/bA2;A4&A6A8A:A<`BA>iA@ABADjAFAHWAJAKQAM$tAM`AN(AMAM}VALFAKCAJAJ'AK AKAKALOALAMAMSAMAMAN%FANlAN!ANAO!AONAOrGAOAOAOAOAO~AOYAO&ANANAN$AMAM+ALrGAK AJߤAI5AHTAGAFrGAE ACFAB!-A@8A=A;QA8YKA5;A1IRA-PA(A"{AݍAqA gA@@Y@ @(@5Q?[=*-PhI#Tez~:_>çA A49AAAAVBĜBxB ]B ,B +B oB B uB B GB"BmBVmBBDAAAqvAAA/AzZ>y>@>B>i>>F>>L>>>ޠ>>>|>=>Y>>1 >n+><>m><>-{>b9>>> J?? ? V???B??q?%?+ǂ?5X?B?Y47??&?\?X??N@u@B@_]@@4@(@@@%A A,gA0A;AEbNALASخAUAV^AUxANOAGbNA@PHA9n/A2خA,A&kA"`BAA*A%AarAA~A/dA A AAAk@@S@L@!-@U@/@@Q@߶[@@@ܼA@w@4@@״x@֐@Ն@~@x-@qL@h @`-@Y@T7@PH@M@L@K@L@O7@R@W@]@d@mr@z@@@@f@@@j@Z@*o@k@H@,@`@@@@ @Q@@!@^@A @k{@@@k@@Б@2@&@1@A xA"AB;B!ɠB75 B=eB8tB/XB#B B A9$ABAe,AqvA֡Ay>AKAf2AAt1AbARxlAE'A9A.=qA$AAAA#A AnA%A@OAAAoA*oAVA\A[AAJAAIA@IAyAXAEArAAғAA =A A AAaA.A;AA3A$AciAA4A .IA fA ZA MA AA 5A A A A!&A!KA"OA#uA$<6A%A%A%A%ffA%6zA%A$ A$A$xlA$HA$eA#A#kA"A"uA!A!A!1A A A@dAAہA0 A"AAHAyAAA }A A AAA~AA @ @~@@@C@*@B@a|@@A>BA?A2AA8AA;AEAYA|AܒA7AZA՗$APAAA@AԌJAٴA)*AzxAA#A{JA\A݇+AگAv+AHA{A2aAнAa|AxAAwAA.AAtAA1'AAAAAAbNAAAŢAAAA{XAuApnAjAeqA`u%A[ZAVZAQw2ALAGACgA>A:+A6kA9A5DA16zA,ZA(ȴA$A LAAAyALdA A  A!A' @@[@@U@1f@1@S@Ԙ@m@ɇ@0@D@@B@@Y!@V@<@ٔ@d@f@U@_p@@r@@}@v@oH@h@bA5@[@UC@O@J2@D@?@9@4(@/@*@%@!@@@Y@@@ &@'@%@o???x?0?޾?z?do?y?ƹ?#O?J?n?NA>#:A=uAA!AAAcAAuAbAxAAATaAAAɺAAApAAA*A^AAMAZAA AiAhAATAA8AXEA JA;AAМAwA AԕAAABA'@AujAA)_A;AiAؼ6A AALdA/BMBjBB ABBWB"NBoOB~B H1B$B(/B,RB1)_B4bB7]R>eG>n x>v>>A>S>S>>>s`>8>3>fA>8>->DR>Z>7>d>>td>B>,g>a>>>?? k@??^c?*V?C?"@?+|?4?>'?L?b՝?|y??z?ݘ@@'d@N?@tr@LD@@ͽ{@0A49A%&A>AO?}AYAdAjAorGAt&AweArAncAjuAeAa}A\AVw2APuAJAD|A?nA8A0dA)XA#ACBAADA'A ɥA:AҞA @@HA@@@)@2@2a@6@?}@L@V@/@,@@@@ϼ@ͧ@ˌ@w@g@\)@ @@@^@@I(@ @@a|@@ @~@޾@]@4@@!@@O@@$@/@O@G@v@@o@{@Z@;@@@y@Ѣ@@3@@@@@@@@@'@ @@~@ A#eAԓBdq Bcw2BZBHٴB9 ^B*]B FBB B6A[An/AڮIA;BAoAAXA5tAAAAA~!AsAj?A`AWAOPHAG A?A7zA09A-c A*!A&A#͟A ^AAAEAA4AACAA\AAAAAdA"AAA {JA"MA$%FA&uA'`A)6A+0A-A/A1A3A4A5A7-A8fA9A:A9A8ںA7ȴA6RA5A4xA3hA3E9A2A2qA2`BA2{A1KA1|A11'A0`A0JA/{JA.A-A,fA+HA*bA(6A'A&GA%1A#˒A"A!VmA iAAKAVAAfAAWAA8AA*eAA -A bXA A#A/AARAWAN@2@X@Q@_@">@@ı@榋AALA~AA>A\A A0A A[A$AvAAeAjAA(A`vAAAWAeAv`Au%AAh>AACAAAՐ.A>BAAUAČAfAWA_A{AAAMAHA:AA>AAA:AA]AAdAA}AwAr[WAljAg|Ab6zA] AWAS AN2aAIuADA@BA;ѷA7w2A33A/ lA*+A&rA#@ABzAjAAPSA[A jKA {A AhA@@@,@|1@@~|@.@@@@`@c^@ @>@1@@Jw@+A@#O@3@[@@@VX@ԕ@f'@ @P@}(@v@pA)QA$2A AjAxAX:*AMoACOAYCASANJAIACfA>rA:A5U2A0A,"A'A#]dA"AoAA ]A4A uA qaAXAR*@Ȋ@U@@7L@@@]@Y @@M@@$@T@@@j@7@t@%[@w@ݘ@@\@=@?@@{W@t@9@4@/@*զ@&/@!/@@%@j@@@ @@-@???XO?͊?o?=\?5?VC?ɟ???`???A?e?<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@kQ@@@9.@+@x@=@+@@f{@l @@*A A Ah}A[AAA A A A xlA A ˧A "A A A A BA ǏA h^AAA4AaAAwAsAwAAvAAA'AAA)A ]A"EA#dA$VA%"A%6A&XA&A'A'EA'XA'KA'qA&A&?A%A%'RA$|A#$A"A"A! A2A"}ARAMA"A AlAPA EAuA@u@I=@@@'@Y?),'>ԕ]g&A5AVdAR=A-A9AٴAgA9AȯA6AqA]A;A#A؛=ANAڳA0UA{AIAA#AٍPA1[AإApA)AA,AbA҉lAAϪ0ArA?AIA]ĄXArGACaAhA˼AˈAuZAtTA|AˆYAˌAˀiA7AAAAˀA(A̿A͔FAΛqA˒AiAA]/AAqAAA٢Aۮ}AݝA)A1AAAAGAcAA痍AjA8AAAA:AAnA[#AAÖA`vA2AXEA6AA`BAAA$A<6A:^AAAoiA1[AcAA A@ArAfA{A;dAAA@OA"4A6AAߤAPHA@OAЎA AĖAΥAEmA3AApA-9XrA/]A_AAAAaHA0A܋AAAB BB^B BdtBBB_;BbNB#B&}B')yB, B2B5B9B;gB;B?=BChBFsBI?BKNBM`BOFBQBRBT)BUBWBXKBYz^BZB[B\B]rB^FB_ B_gB`nBa BaBb%BbhBcBc}BcݘBd4BdBdBe #BeBBetBeBeĜBeBeBfvBf~Bf&fBf*eBf)yBf#BfBf BeBeBemBeBe BeZBe./Bd(BdBdhBdTBd@BcPBcuBc2BbݘBbBb!BaBaL0B`B`WsB_\B_BBxRoBxc:BxpBx{BxBxBxBxBx)BxBx[Bx{JBxrBxhsBx\BxOBxABx2GBx!Bx}BwBwBwBwBwqBwSBw}Bwh$BwP.Bw7BwBwBvBvѷBv2BvCBvBve`BvIBv-)BvBuBuSBuBuBu|6Bu]~Bu>Bu# BtA>@%}>H)>P9>X>a >jC>t>~>~>>->x>> >W>f>3>ȣ>>rP>7>̳_>Ҽ>9>WO>>쳢>>Q]?H?Š? #:? S?~?k'??B?́?"j?'/#?-?35?9/?H?b" ????B?&?c@,@2A@;@0`@P-@C@xATAAwA' A1HA6A:A=tA@ABiABmACDA= A6!A2:*A. lA)DA&A#A 1A AeAOAaAAC"A MA A 6AAoAA4AT@%1@e@@[W@_[@@\@ۡ@Z@f@Z2@<@Zq@@[@A/AoAAA2At*AAA A@k@@c@ -@D(@V@@[@P @@@9@@@yh@=@@˧@@औ@ޱF@;@s@@@޾@٩@@ l@=@@G@X@@@@z@xh@lu@gs@fл@A AB"&BvBA6BDBBB 8BcA@AA?AA DA(Ah>APHAAAAAdA JAA =A|lAtAmAfA`A\AXRAT^APBALAJ.IAIA=mA<,A;A:MA9-A7RA67A4A3MA1A0sA/ A-A,!A*rGA(A&WA%pA$)_A"WA!lAAAxA+A77AxA,AEA7AmAêA A >A AAuANA"A@ @@+@Q@!@점@,|ASAAwA AwASA0UA AAAAfAh>AGA'AA2AtA%FA}A*0AAIAAK^A1[A3hAVAZAA DAiAvAo5AA,A©_AAOAAAA%AHAAAsAAA8AAXAA|Au%A~Ay,=As|AmAhuAcCA]AXAS'ANخAJ AE[WA@A@Y@@6@L@A69XA0-A+U2A&!A!A2AR AKAEA\h AWoARYAM[AHAD?A?A;A6A2?}A-oA)A%.A!~(AmAAAAN'A A+ApAT@ @@A@U@@!@]d@ϻ@;@@V@@@'@@B@@U@ @@@ʂ@6@#O@ud@}@vH@o@iXd@b@\@V@P@Jm@E"@?@:P@5`@0@+'@&_@!@3 @̣@4@[@ O@`@;@?n?f??-?w?0?]?(?e?d?[l?<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@:@r5@< @l"@y@@@eAA0A #A3A6AA##:A'rGA*A-*A0GEA39A6͟A9K^A:/A9AA9A:OvA;J#A<$AA!A` A䔯AAAjAjA֡AȴALA쩓AAAZQA!AAAAAFA{AA,=AzAFAAAaAAAAC-AA@ANAnAlA6zA̘A.AZAJAAOANpAAbAӭA΢AAPAAYKAYAmADAMP@oA/e,AAtTAԸRACABVBB jBB B%gRB)B-.B0yXB3bB5bB8 B99B;eB;B9BA~]BE'mBG@BJBKrBL{BM|BQ?cBS%BUBWBY8BZB\ B]u?B^B_\B`)BagBb>KE>G?>QJ>[}>f/>p>{q>>M>s>!>r>#>j>ޭ>w>(>y>>> >׭>ޅ>>>>"??\? ?~?H=?v?'? ʂ?%)?*?/?4u?:ܱ?A/?I' ?S?bE?x??5??@x@$t@H@j@@@@AڐA$6zA80ALA] AgArA| Aq AAA9XAAA4AHAzAthAnAi_AcA^AYfAT~AOE9AIAD'A>oA:A6A2L0A,B[A&lA"E9Ao AAAѢAgAA A ŬA@AwA AfAA:@)J@@F@@>@C@#@@@@w@,@ @E@덏@_@2a@@O@4@c^@Hk@in@\@P]@C@77@*@&@@@@R@z%@Z\@;@@@@wG@R@`@r@=q@R@Κ@e@&@@O@J@@M@w\@?@s @c}A@Tn@ApƨB%qBYBg5B(?}A;AAAťAeAw2AAcALdAAA AjA5AjAxA7AAAjKA~AwAq6AkuAdAaJA^FA[MAX^AUy>ARAOAMAJFAI-AHAG"AGe,AFAF6AEAEAEOAEAEAEAE:AEAFXAGѷAINAJALPHAMgAO]APAR1ASEATAUAW AXAXAXhsAWAVAVAUMATASARrARAQ APANAMALAKQAJAIAHAGTAEADACAAYA?A>kABA"+A AԠAA 2AMAwA9AAA8ABA aA EA4AȕAA#AI@P@8@k@@B8B4)B0?B,B(B%=B!oBBOBBB{B IB *B]B BB4A՛AAKAA AgmAAAYA/A9zA5wA1?}A-A)A%#:A!GEAA}A4AA:A wA(AVA/0@5@/@L@@@kf@ @q@ѧ@̢ @ǹ@@<@@+@}@{@Tv@=2@<@Sz@<@@ @e@@v@P @ 0@yg@s|A*fA%+A A+uAgMAJADA? A9]dAOL0AJzAEjAAoA<|A7A3A/6A*MA&A"AAAbAjA_A A3AAU@@@0@z@@@7@ё}@ Z@ƪ@i@G@F5@c@'@n@r@@@#@k@n/@r@/@@u@y@s8@ln@e@_a@YV@S@Mt@H @B@=(c@7@2@-@)@$e@b@f@3@i@@ (@@5???/?^5?k?y)?J?HV?p&??;?<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?r@P@4@WL@y@E@@:?@L0@;@^A}A +ANwAAtAOA AL@v`@k'@\@3@">T xY dt; AᡖBb4B}A~AVAȗA8AsMA8AbA,AAA'AΥAjAQACaA AbA¹XA)_AïAAUAA[#AACAfAǏA>AAɛ ADgAWA˒A2aAAYAQAHAΕAΦAAqAzDAA =A2AнAѕAҝ~AӒ:AyrA_;AF?A0UAAA JAA1'A(A=A_AAjAFAXAiAAATAAyA rA@AbNAtA{A{AxAyA A_AyADAAwAUgAAAJA5?AGEA\AAA3A6AxA.IAAZAATA 7AAHAYAAAWADAAlAMAݹXAQACaAeA}ACaAIA`AACADC-@OL*0A"KADAAρA?}A lBB LB2BWBAUBxBBjKByrBB"GB)UB-XB/B/ܒB7XBBBCZBEWBGBHy>BKBO^OBR(BTBV]BXBZn/B\oB]B^]B`LBa'Bb4BcIBdBebBfh$Bg1BgQBhyBi8lBiBjU2BjBkGBkGBl@BllBlBmBmFBmUBmnBmBnBn(BnCBnY1BniBntBn{Bn|BnyBnrBnfBnUBnABn)DBn Bm=BmǔBmBmraBmABm BlBlBlTBlVBkBkrBkBjoBj_BiLBiBibBhBh?Bgq BfϫBfBe_BdBc\BbBam]B`{B^~B\,BZ)BVBQBF+BIBS&2BV9BX4BWBLBZ՛B_B`BbңBgv+BjBl/BnLBpBrF?BsBtBv BwBwBxŢByBz;Bz;B{tB{qB|zB|B}UB}B~ B~ZB~B~BBRB'B=BѝB|BBBjB'EB.}B4,B8yB;B=AAƶJ:CAcAA AA =A^AyA%AHAAyA@A|ACA:A`BA_pAsAr|A7AA"AAȁoAAȯOAMA_AѷAA}A̸AͷAΘ_AgmAqAЎ"AМAAA AMAIAA5tAق A3AA/AK^Ad&AA A8AA7AA-wA-A$A픯A-AA'AAAMAFATAB[AAAGA*B:BNBABBhBxB_BnBBPBj0B2aBhB B jB B tB iyB ªB B \]B B B xB B 4B EB P.B QNB BB %B B OB yB $tB B F B B B `vBBBBW?B+BfBkA ~A9AeA`AAOBAAAAK@@bNA:AɹATA]dB#BgBqBxB B$lB'B*>B+B,B*FB'7B2^B8=B;B>B>өBB3BG?BK$ZBMBPHBQBQBU BXB[9B]UgB_8lB`UBbBcBeQ4BfBgBhVBiΥBjBkBliDBm(sBmBn~BoBoBp%Bp/Bq BqoBqxBrjBriBrBreBsBsOBsxBsBs8BsBsBs;BsBsBsBsBsBsBsЗBsBsBsYBsf2BsBABsBrBrBr#h>*1*>8`W>?>G}>OҦ>XH|>af>i>rn>{|>p>P]>=>B>uX>א>k>1>>Q;>>>É>ɣ>Ϻg>ռ#>->>e>H>"?)?? z?3??،???$?(e?,p?1Uq?9Z?F?UG|?d?z?L??S?7?'?G@ \@@0~(@LnD@l9@nn@@@@@@d@@ A AAoA )A&A*A.A/VA.A.A. A-AA,DA,rA+{A)EA(8A&A$ߤA#+kA!{JA AAAR5AFJAfAfAVA=AAAABAA rA8RAgXAAAUA.APA\A iA wA A A AA؄A,AA*AAeAJ8A.AA:A0KAN[AlvAAAɺAAW?A A YA /A A M6A A#A&AuoAGAdEAA?AlAA A TA A$ADApA_[@w@=G@@ܬ@Ѥ@9@@|@)5AN#:BWB:PB*DBKAFtAϪeAVmAdAAsMAiyAAA A)A^AA A4AA`AA;dAeAAA^AFAcTAAxAA~A}4A{Az33AxAwp;AvAwAx?AxAyDAz2aAzPAy]AyqAyZAy AxAxeAx|AxqAxAy xAy:AyB[AxAxAxiDAx!-AwAw}AvAv AuUAtAs>As2aAr|AqjAqCApApbAovAnAmAlAkAkiAiAhAgRTAfAdvAceAbuA`.A]A[eAY xAVATjAR"AOAMAK[AI7AF;ADeAB{A@S&A>1'A;KA9A7YKA5 A2A09A.A,OA*qA'cA%A#A!uACAAARAAcA.SAA A MA AAAFA@kf@ByrB`Bf2BBB aB1BB AAOAAAA]/A/A5A`BAA쿱AxA╵A A\AA/A AAA}Ax7ArjAlAgfAbA\gAW^ARAMAI AD_pA?A;PHA6cA2zA.u%A*\A&[WA"pAAJA5A)A#A A aA0A@k@@@@ @Q@@Ɠ@җ$@͇@ȓ!@üj@@e@C@y@*o@+@@@@@Qn@@@@)@9@@z@tA"AAA~rAAW@@@~@w@p@jl@d[@]1@W@Qջ@LM@F{@A@;@67@1@,8@'n@#:?@@R@ @@ +@ @@\h?z%?nD???mH?!??5<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<t~)Z}Y#sSr=_?sE?:T@$3H@[@;@b@Ɨ$@ݴ@AWTAT A yAAAA3A"3A%u%A'A)NhA?AAxACADADADAD ABA@RA=#:A9bA4A-KA&LAgAfA\@@@VmZC\@peAd*AAe`AA[AٴA'A$@AϫA_;AAXEAQA2AgAAA$AA&AޞAĤA9XAǣ:AAAAy AAA[A.IAtA|AGzAAJAѷA~A` AhsAӉ7AԽAGAXyAؼjA/A۰ABAfAVAAApoAGEA AA˒A﯃AAATAkAbACAwfBBBȀBBYeBBևB(BDB2BBWsB B B ZB B iB B GB ݲBeBbBRB^BBoOBAAAVAOA{AA@OAaE۩_A~/A0UAGEABB"B0BhB"B&tB*xB-OB/lB0B0B'LB3CB:$ B>BBABCB@BIBMBQBSBUvBVZ7BW(B[bB^1vB`l"BbhBd6zBeݘBgcBḧ́BjdBkU2BlwBmBnBojBpDBqBqrBrxBsBscBt7BtzBu*BuBuBvNBvBvBw'Bw_BwBw"Bw4Bx;BxBx.IBxzB[B`TBcBdoBc BRXyBfoiBiBikBmBqBtgBvBxTByB{AB|yB}hB~PBqAB B~5BB"ABiBBB"BNB|)BzBBB B'B@BWLBk^B}"BBBtBB$B4BB=B]BOB;B!BB)BeBBªBBEB-BBtBBBJBnBzDBpBfB\BRGBGB >%H>,;>2n>8>?θ>F>N/{>U>]>ft>o>x>Z>p>`>Q>3>Q>,>wK>p>>O>If>>D>Լ'>>>ꃲ>L>9>??? ?2??\? ?!r?&S?,$?2N?:"?F?S?at?p'??gb?Կ?T?'=?咸@s.@i@,@M @tQ@@q@˴9@'A A"A:AH$tAWZAcAl^At A{AhAAgATAyAoAAAAy@ArDAl]Af`AaMA[jAUAPAKAAF$ AA&APA;ںA9c A6A4{A2{A/A-F A*A(oA&VA#A!\)AAAAA]AAȴAA WA ,(A AAA{@BtB!BB6zB pB ]Bl=BO\BEA'AA3hA뮲AJXAApA&AAAݘAA3A뷵A|A^5A]dAxAҽqAVAҒoA5AyAūA|A^APHAS&AeAA($A9A5DA0A,A(A$LA AA$_A{5AAgA A|AdA5@2@@0+@e@V@5@@Ԋ3@e@^5@w2@@@n@@V@_p@9@-@8@]O@@@T @_@fQ@@@}:?@w`A(oiA#+A-A#/AAI\ADA>ƨA90A3AIv`ADeA?A;TA6)A2YA-A)FA%A!kQAfAwGATA؄A(A PAdAA4@҉@b9@@@@@]@6@T@@ѷ@+@>@@K4@@=@@s@~R@u%@@@@P@3@x@q@kY @d@^ @X@R˧@M @Gv@B@7[<>i>m?_V2?<@@,U@T5?@u@K@F_@@@@@@<@9@@X@ AA/A A HA )AkAkACAAAA_AA$tAGA0`AA.AޞA^A VA!A!xA"eA"a|A"($A":A"PA"A#e,A#A$K^A$FA%{A%]A%A%?A%6zA%kA%3A&1A&}A'7LA'XA(ffA) A)A*lA+.IA,A- xA.qA.A/A0A0A0oA1J#A1A1A1A1A16A2[A2A0#A-A)ںA$~AMAA @ @͠@?ixz0BAC-A[AҽAAA0AAP}AAAǮAAJAl"AAA-AҽAAe`AGA8AAAAAAXADATAA'A/AA lA=A1AEmAOBA A AA8AkAʨAQA1[AkAϦA]AiDAAjAAءAT,A1AA AAA噚AvAZAJ#AKA}AAAAA(AwAc AVB6BBB8B5BxBBABBBsB?Br|BBBABB3BPHBiBBBBBBB]BB0BBB$BBB=BBB{BBBtBiBB|BBuBBBBBBB}/BqBftBZBNBBB6B)yBBBBlB>+>/>3>8JQ>A;>FNH>M>UT3>]C>e2>l>tO>}$>>h>?>>Pa>E(>c>u>!>>.>z>z>Ƙ>>ժ><>> >|>F=>}t??3? ? ?_g?+??*?#2?)gw?/t%?6-??|?Jj?ar?x},?Կ?T??u%?!-@@$-8@>G@^3@?)@@VC@@1@@A,|A0Ag#A'oA0nA79XA>VAB;AG\AK,AM-AP?}AOtANKANAMVALAK AH AFeAD~AA]A?bA=\A;A89A6A4l"A2A A0A-A+mA)֡A( =A&GA$A"}A!AkAYAPAAAAGA=Al A$AUAAsAsAAmAAsAAAAA#DA`ADHA)AAuAFA{AAAAiAqAdoA XyA!NwAn9AAAA pA@n@@ݙ@8A6lBEB$+B ^Aԩ*AɒA(AA\AzAkAA;A{AAzA9AA$tAkA4A1AAwAoAfA6AAAaADAfAD3Af2AAAoiAAAzxA(AGAh>AAA#AA A,qAq AgAAkAAAh AAhAcAgAl"ApoAtAy A}AA%ArA(XA$AJ#AیAm]AcAA$AAK^A;A$A3AAwA!A#A~AA|TAyHAwAAt'AqAo-wAlQAjNAhAfXAc.A`NA^ A[l"AXHAVASFAPAMAKDA;3A9)A6.A38A1hA.A,YA)A' A$jA!NACBAAEAApA@AyAxA :A AAA`B("B$bB!)BmB[BOBBBB BAB BoB:AΥAAHKABjBBTAA>AAA~AA1AȴAsA:A!A{AA\AAh>AAƲAsABA%FA+AA2aA[#AA)A7ARA AAQNA;AAAwfAh>A|҉AvrAq;dAkAfA`A[w2AVMAQAALQAG|ABA>%FA9A55A0&A,6A(rA$oA A AABeAA&A A\>AA@{@gw@n@險@t@\)@@Ӟ@pz@d0@sC@@@P@@q@)5@@@`@@:@@u@\S@2@D@>WA.IA)pA$iDAvAͩA6[AK^5AE@¾M@@@@z@4/@a@5T@@@@@^@͟@@xl@{@uS@nT@gS@aw@[I@UEc@Oi@IJ@D'@>/@9}@4^@/c @*@%Ѣ@!:?@¤@j@/@@ Z@-@d@1?C<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<.¤0>>&g??@&`@UT@}@a@>@@#@ù@h4@à@x@q@셲@B[@Y@'AtA^ANpA A ?HA N{A CA A MUAAZfAAAAAA A[AENAAAAɛAdAnNA(NA%AI=AjAA+6ATA $tA A A EA!VA!]A"mA#A$A%A&A(cA*zA,"A-\)A.\A/,A/XA0.IA04A/vA/DA/iA/A/A0F A0SA0gA0UA08A1vA2&A36A4VmA>|A=8A;"A7A/*A&A۫@AJAMjBXB pB9rBB#uB(TB,ԯB0B3B5B7(sB6oB('B<BBBGTaBJBLlBLeFBPBUdBXB[6+B] JB]B`yXBcCBfoBhFBj!BljeBnBoBpBrGEBs|PBt=BuBvBwBx[WBy"ByںBzYB{%`B{LB|>B|dB}-B}SB}B~MB~qB~B"BZB0BBBB BBJB B"uB"B OBBZB BBBBBB_pB3BB~οB~B~ZB~QB}B}B}?}B|B|2B|7B{aB{jBzXBz4By]Byr|BxqBx7LBw3BvHBuBtBsBrdBq&BoaBm#nBj_BeVBYB_BgBk;BlBmSuBkBgȚBppBrȀBoGBxBABz'B}:B]B^BB>BKBoBHBSB^BuBBB]VBBBBABoBXB}B'BBOB8BOOBd&BvBBtBBBBBðBȀBBcBϑBBB6BʙB8BB(BB|BBBB{BBBy1Bo\Be,BZBOBDB9rB-B" BB BBB>;>>A>A>D>H>KC>N>T]B>[h>bY>iJo>p>x_>8>fM>>+N>>>jb>r>>c>x:>l>>q>]>VX>=>\t>>>U>???7? b?M??8? ?! ?%n?+?08?6?BA]~A]ںA^)_A^A=A;KA8AA5E9A2TA0qA/0A--wA)EA%tA"SA!A!.IA xAAAAkA AAvAi:A9A]A0`AAAAiA'3AAAbmA!AAb$AwA AwAA2ABA\HAxAALAAgA %FA!MA"+kA"-A"/A"1'A"33A"5?A"6A"8A"9A$$ A%tA&A(A)v`A*ѷA,/A-A/A1}A3A5MA7p;A8A:rA; A=AA?A@AB1'ACzAAA?KAAOvA%AAAAݘAA'ANBArZAo~AleAi AfAcAa A^+kA[QAXzAUAR)AOAM;dAJAGAD҉AAPA?1AAAArAkAvAuAÖAAYAA7AOA\]A+AAAp;A_;A]dAzAuMAop;AiWAd{A_6AZ_ATZAO.AK$tAFe,AAA=4A8A4hsA0($A+A'A#A bACWAxAA[WA A |A+AA@M+@=2@Q@+@@V@N@ӥ@{5@o@Ā@@b@^J@@w@+@8@@Z@1@@W*@>@;@@-@@y 2A(@A#AԕA$@AA+KADA>A93ANJAEAAJ#A8?3{?s?*@@@5ρ@P8 @l:@@0@@bc@@h@M@@р@=q@ߩ@@p&@@&@@@zAAAA A A OA v6AAhAAAAȊAAAT"AA6AA%AY!AМATAAtAPAqAيAmrAAiAAA2AA A rA!+kA!gA"e,A"KA#A%A'N A@>BAAAClAF!AHAJ-AK~AJ AM6APASAU֡AVAWMAW$ ANLA?cA&B[@F ;>\A1qAA+ApA2AAqA"A(A=7zp>;>@>EGZ>J>O >T>YN>^>dI>i8>ns>t.>zCx>C>>w>R> >>g0>>\>>> =>lL>>~=>8>&|>NY>p>_ >>}??%?5L? &?5L??\??"a?&T?*_?.|(?2?6?;c^?E?Q+?`f?y ?1?f? ?ۧ@!@9I@h@@@J@RT@;@A-A xA@AہA(mA!6A#NA&v`A(ںA+6A-A.A/A/!A0A1VmA0A.یA,A*A(A&A#A"OA!A!A KA 4AA:ACA^AAeA*AnAAAA'A?ApAA&AyHA5AA!AOAAxAAAJAAA|AAUAA}A]:A>A GA%AA!A"v`A#)A%A( A*2aA+xA,A. A/[WA0A1A3SA4A6A84A<A@ AAAB1ACbADmAEAGoAHAJ+ALfAL:ALW?ALANAQ0UAPAP AMAJ}VAGYKA=MA1|A'AA RAH!A6A肪A̛ A rAA%A7AYAkAmAAAVAAAA%A A%AɺAA_pA%FAA3AzAAA 7AvAAAARA"AGAAA[#AAуAA:A|A!A9AAAAiDAAAwAbAIA!AѷAdZAAArA6ATAA!AA`BAAeAQAAAUAAAlA,AZA~A5AB'AA+A}_AyAfAMjARAEmAEAAMAKAЙAAxA xAAsAFA2-A2AHArAAAhATApoAVAAAXEA>A4A|zxAvApAkhAeA`A[hsAVMAQOvALlAGzAB8A>eA9WA5A1DA-A(A$"A!A>AAםACWAaA W A$AcA@6@ @@@) @ߓu@T@Ǐ@Ϗq@v!@x@t@,@+@@(@@@c5@QD@V@rq@j@@H@@C-@A,A'MA"AA AA;ACA=A8.IAJ(AEnAA.IA<ɆA8tA41A/.A+jA'BA#ѷA\A AEA8A A [A ܜAoTA@@#d@զ@P@#@๶@@N{@@e,@ @/@@ @>W@@r@@/0@o@d@z@ @@@U@@@y@@“!@Ĥ@ƨ@Ǥ@+V@́@i@=@Ҿ@]y@9@/@ݸ(@@>@@㐂@@@\h@Ҟ@@6@U@@@@q@X@b@bx@AA"AyA &A RA FA A /A {AQAuARA:?A_A]A+ApAtiA >8A A A FA 2vA]AJAA>A$A A=AHAAA:Ah^A!\A#cA%~(A'qA)A,HA.A1A A3A6A9AB`OB!BBBEBBBBBTB_B BKBFBBBOBBFBB?BBqBuB[B@vB#BBBBBoBCBBB!Bx+BBB33B'BjB>^>_>a7*>bP>c>eB>f>gX>j[>oeV>te>y>,>x>H>">2>ߋ>J>%>k>$l>Io>kL>>$>ý{>Ɂ>h8> >>V>>R?>5L?-?n?? M??a???R?!E?$Y!?'9?,?0?5?;*?@H?F?M%$?[?qT?S??b?@@T@:@UAjAàA.HA7A@AJ4ARAZA`+kAedAg+AfAe8AaA_}A]ZA[sAYsAVAQAMAH\AE?ABںA@}A=A:.IA6A4A84ANAIOADPHA?A:rA6pA1]A-A)]A%/A!AA'3AOAA A HA.AU@ @c@@@@V@@@ψ@@@e@@@@6P@a@3@@@'@~|@@¹@ |@j@@xi@r/0@k3@eJ@_U@YR@S.s@Mu@G@BtT@=+,@8@3@.!@)b@$P@ B@a@c@u<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<0Ub)oiou&dݿcKǾA>b?/?@&E@V@G@V@@F@w@@m@C@@AAALYA A jA A AA!DA"A#یA$A%A&A'BA(!A),=A) A(A(yA)ffA* A*A+OA,|A-VA.c A/A0@A0JA/ȴA.JA*8A)1A)A+&A,A,ݘA-FA.2A/tA0(A0DA/A/yA/J#A/A.FA-A-hA-A-A-A-A.XyA.vA/}A07A0A1A3A4rGA5A7A9:A;A>{AA|AD*0AFAIRTAK.AN,AQATAVCAY6A[A^!-A`AcSAeAh`BAj-wAlWAn"ArAvA|g8AAAAAdA"AtANAAOvAuA\AeAAuA\AAVA7LAoA3AAALAtAAA&Ae,A°A AvAGÁA(AATA5AffA=AOAtA|ASAuAxAAAA ABABBB\B$BBB IB {B)BBKBraBBB,WBYBBBBBBoBh>B_B aB!FB"EB"B#B$UB%B%B&8B&B'88B'*B(HB(lqB(wB)B)IB)uB)B)B)B)B*B)HB)B)MB)mB)B(B(&B'VB&pB&YB%3B$2B#pB!B QB"BpBB9XBaB xBB 6BWAAq AA*MA.A!A1B|B;B B(PB/B6aB;,B?+BC_BFBIBIVBM}BOBQeBRBQBNB=?BT[B[BRB`BϞBB۳BB~BںBյBIBĶBBB BBtB^BF?B+BBBBϞBJBB^wB3BB%B BiB.B/BB_pBBYBW2BBv`B-B~B}HB{ByBv}BrBjvBfSBq3Bvn}BxvByByBwjBuhB|~B~BzDB+BB;BBiBBBOBҖBIyBBBsBB&BY$BrBnB B<BiB BBqBB?B0BH B\Bo(BpBB7BBBtBBBBðBBB+B?BgBB5BBB\BBNBBBBBxBoBeBZBPBEB9B.p?d?ѱ?"?ț?7 ????M?i?|F?e??j+???bc?Jb?At?F?_p?m?3?`??Y?Yu?`-?nn??y?sh?mQ?gb?a?\}?X?S\?Ṇ?JU?FJk?Bl?>?:?7H,?4!\?1_?-?*?'?$?">:?!l? ?،?D??*o?+???$.?+\?4X?Il?r@@W,|@\AGzA,A+ A6 =A<+AAɆAEAIAOZAUAXIAX7AX AWvATe,AMzAJAGɆAEABQA?OA=A9͟A6(A2A/A-?}A*WA)OA)kA)(A(8A&\A$YA"A!C-A +ArAAAcAAA!WAYAjAiABAA:A.AͩA9ASAfAz/AoAAA8AAAAWAA.AsAQAGAA[AAySALA A!A#_pA$zA&2aA(dZA*A->BA/RA2qA5XyA8K^A;J#A>UAAcADAHUAKAOiAS AVAZu%A^CAb"Ae2Ah AjAmAp͟AsAv1AxQA{ A~A AA+AzxAA}VAAAAYA ~A0AAuAA)AAAQA2aA=AA:A AAF AAlARAAAPAK^A.AA AA]ARA$AAAAAcAvAoiAjAg8AeAf2Ag8AJXAyAA:A|AAPAdA&LAA=AzA A^jA'AAYAqANA[AAhAYAA=AQAlAA A"A3hA8AA&AAuAhsAAAe`AsAoiAA'Aϡ-A,=AA}ACAA A 7A=AA AyrAAAA AOA@AAAAsAjA|ݘAw AqS&Ak0AfA A`A[AVAQ{JALAGHAC A>mA9WA5A14A,A(/A$gA A AENA]AAxBA AAbA-@@@ @a@`@ʬ@T@@!@ɱ1@ķk@ۡ@@w@@@0@U@զ@@@@CW@@T@~=@A-A(A# AQAWAWAhAF]dA@wA:wALqAGXACB[A>KA:A6eA2:*A.A*A&A"-wASAAA,\AA AA=AH@[B@@P@@@֡@@Ӈ@@R@ȶ@}A@b9@eA@@@y@W@'@@@@~|@@@ N@j@}@v+@p&@i@c:@]\@V@Q@KR@E@@I@:@5@0͟@+@''g@"@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>]> 3¿Tǿ_Π *Giaxbl334}\Sb˧D(翶B[4[?A?@#@X@3r@ @ @&@LDArA AlAA"A)A.A3A8ȴA=oABE9AG?AJAM;AN˒AOLAO AO AM8AK-wAIAH=AG=AEACFA@ѷA=A9A7iDA5.A3A2]dA0OA.)A-lA,XA+kQA*A)VA(A(\)A(uA'A'A'1A'VA' A'9A'A(A(FtA(LA) A)A*YA+A+`A,A-A.A/A1A2jA3 A5ZA6A8.A:9XA;A=0A?$AAYACrAELAGیAJ$ALAOAQATZAW>BAZVA]jAahAdAh]AkTAo}As4AwoA{)AA A/OANpAqAAAAtAAAAA{AAkAGA{AA4A'AP}A($AQAnAAIA…AIJAWA-AnAͯOAcA+AdA֙AԕAAZAߢ4AA=qAAAZA6AOAA˒AgAAPAB8BB B/iBpoBB/5B B :B TB4BhBBxlBMBBB_BBB)B4TBb4BB B!B"B#|jB$OB%B%өB&B'"B'6B(LB(@B)OB)B*+B*MB*B+EB+KB+BB+B,NB,+B,+QB,EB+'B+B+kB+ B*B* B)tTB(B'B'3B%B$B#zDB!B K)B`'B0oBcBBeBbhB BVmA-AlAVAqZA A A&B bBB#B+B2T,B7B< B@tBCVBE`BFBC~BBGeBPBVtBZoB]B_B`BaCBgBjBmBopoBpBqʌBv*By9B{YKB}\B$tB_VBBB[B7BkBBOB,BBf BnBB=Bz*B4BBB:B`OBBBBBfBBBBB#B(B+B,dB*B'zB"BBBtBBBQBBBB BlJBPB2B&B4BBYB}"BQ4B"ABHBBHBCBB>BkDB+BBQABB[BƛBBHB~B{BxBpKBhBv/Bz9B}YKB~B~dB}#TBnHBtnB!B{B4BrBBBUBNBBBBBBVBBB\BJ#BjBʳBB5gBcBBBzBBB+BABUBgBw2BBBBBB(B@BBBRBBBFB4B]BBBBLBUBBBxBoBfB]BS@BI*B?B8EB@'u@-@3@9@>@D@Io@MT@Q@U@Y@\e@_@a@cg@e@g@iZ@kܜ@jm@ie@gN@f`@e\@dZG@cX@a@_@]n@YV@V7@R(@Oc@L@H?@Ex@B N@>@;]O@8@4!@2@/Oa@,v@*@'sC@$@"@ v@H@t@@E@@@*@@@$@N@ ~@ @ t@ e@ @@@@@(D@1ڥ@[[smۿ(c8ֿƨ77?>@yZIJBY޿O(h"쾉$=ܖ>?ib?H@=2@,@U5+@@@C@Mj@|@#%A AA!!A,ZA9AANAH3ANiDARݘAVAYNA\A_AaAbAcsAdAdrAdAdF AbAa@A_aA^fA]A[AYvAW.ATARAQDgAOANAMALSAKAI2AH*AGOAEAC)A@A>XAWA@AA9XAB~(ACAEXAFKAH1AJsALnANzxAOAQ#:AR,ATAW:AYA\HA`|Ad1AgDAkAobAs?AwxA{AfAA"A/A@A[#AiAAMADAA-AAAAiAAAA AAxAAAAAMAĠ'AA*eAaA͐bAϴ9A)AΥAշLAw2AؽA]A۹XAAq ABvzB*B$BB%BGBdgB~BTB+BBðBwB{BBڭBBBѝB BOB{BBBBq'B[qBCB*0BB4BBHBBfB>BFBEBBBNBtB+BBOBB4BWBB B B}BڭBRB(sBB~PBwBmB|vB^]BjBF2BnpB=B}!B 7BBBBXBOB~B1B8BgBaHB;BQNBBfBmBBBGBB=B/BBGRBn@a@Y!@!@ǃ@&B@э@s@@j@c@ۑ@z@ڡw@)@ٲW@ל9@< @m@Џ@C-@܇@t@@Ļo@in@d@'@@ff@6@7@H@@o@~@@u@ @@p@+@ @,@h4@C@@P@@,@@ 2@@ć@?@`@@@UqAA AWAcA'hsA0GEA9ACsAMpAV6A_hsAh[AqAzAzAA^AiAUAA{A5AD3AoA1[AxAAAA{|AsAkAdrA^h AXAQԕAKAEfA@IA:A5jA06zA+($A&LA#0UA #:A@AhAAU\AuAQxAAAHkAAAM A8A$ AA@AAtA0A#AàAAA5AjA9A!A$]A(qA+kQA.˒A2=A5A9A<1A>PAAAE AH!-AKF ANoAQCAT"hAW<6AZxA]AaMAdvAgAkaAnxAqYAtAxiA|AuAFA/AOAXAFA!-AQAAAmAAADAvA2A{AAhA!-AAK^AmAA+AA~AAjAAiAE9AAHAAZA'A,A+6AqAAAABA AбAA\]A@AMAQAA^AAAAsACAAr|AAA©*AA|AALAAAUgAQA.AAAxAbAAǮA ~ATA!A&AAA2A4AAJAAAAAA!AAAAA}AA/AAAAA,A'A \AASAA"A|6zAx(AtAp!Al2aAhUAdfA`XA]AYzxAUAReANAKDAGbAD A@A=A9OA6YKA3A/خA,6A)A&eA#MA DAIA\>A|;A&AA*EAfAꮲBBPBy>B BB gB3BB #A1A@AB XEBBBNA@AAAAA6A8AF A෵ANpASAٴA9AЫ6A+AǾwAcAANAAA_AAߤAAaAA+A6Aްw2bZ.N\ƯͿbe{q>?7@ @=@o[@@V@5~@H,AA&JA5AD;dAP]dAYxAaUAg Al;AoApԕAr%As \AsxArApwAoAAoPHAnAmvAltAkBAjAh!AgAdAb;dA`hA^'A]$ A[lAYfAWAUOASAQANOAKAI(AF=ABA@OvA>.A<4A:(A8^5A7A66zA5eA56A4A4A4RTA4E9A4\A4A5SA5A6+A7A9A:A;A<uA;}A<AMA?AAACK^AE4AGsAIAL1ANAO]AQBASAV/AXA[$ A]A`KAcخAfAiAm,ApAtGAw*A{|AA AAAAAOAAAzA4AAAzAaAXA0UA;AALAAA6zAmADAƴAA!bAIAϰA8RAخA׏\AZA49A'ARAnAcA(AAAXA]/BwB5%B]BBjKB 0B 2B B?Bc BtBBBBBGB:^B[WBB vB" B#RB$B&%B'kB)B*K^B+B,B-B.XB0'B0RB1B2B3B4ZB5gB5gB6j0B7 7B7B8)DB8B9B9B9B:F?B: B:B:B;B;$&B;&fB;=B;B:dB:B:m)B:!B9ɠB9TFB8B8IB7[#B6|B5OB4bNB3;B1B0B.B[B,(sB)VB&B#BBBB 6B/Aޒo@K^AA}VB'B9B$B.NB6B=BBBBGqBJ`BMp!BNaBIBPvBZTB`TBd\BgBj+BkxBeZBpBtyBw/By-BzCB{jBVBOBBB BLJBB2BBhKBBpBBSB8B%BkBBBEBBBBBEBk6BBKB@BBBB 6BgBB!B#B#B!UBBB ~BUBBBBBBB|BaoBDB&2BBBBBmBABBoBBtB8BBBi7BBiB_B9B{BBTB2BBcBRB}yBrBBFBBBBB҉Bj#BqBBBFBBBBiB BBBBjB6mB.B%B B?zc@I@9@ 6@!@@i@"`@(@.C@51f@:C@?y@D@JX%@P@U@[@a@hI@n@ux@|W~@@T@8@2@ם@@{_@@6@QD@+@ (@@خ@6@<@K@@@@3@@@'@@@@@y@@@f{@;@i@^_@@΅@K@{@@@܇@i@c@e@m@@n@u@{b@wHV@t@rs@q#@plv@sֶ@{@f@@@@2@b@*Z@@ AAA gA1=A>ɆAM^5A\OAkA{A:^AAqAIAGAAe,AA]dAADAgmAA%zA{ApAf?}A\;ASIRAKBACA<~A4A/A+CA&"A"NAnA{AmA.AA$AD8AFAIMALAPATAYA]y>AafAeU2AiU2Amg8ApAtOAwdA{VA1AAAAAA =AA+AGAl"A_AA lANpAAGAQNA$A)A A'RAAAkA>BAoAAAAVA|A\A,AAiyAfA1A!AzA,A3AD3A_A{AA¹A#A]A#:AlWAȼ6AVAbA̹$A8AA̤A1[A˽AK)AںAhADA;A5ASAQAĕMAe,AAA!A5AKATaA¸A+6A AA AzxAAA A_A%A lAAAAA\]A;AAA[ALAAuAyAbAMA@A7A3hAAAvAA{Aw{AsAoAkAg Ad>A` A\AYAU3AQhsAMCAJ;AFeABA?]A;A8A5;A1`A.A+cA(7A%A" A vA8A/AG&AiA*eA?AoBByBBB }B NB0B$B*KAA҉AB'B&BKBW A3AAﻙAAxlAA"AxlAuAAJA}VAgA Aɓ@AAA_A!AA,AA>A"A\A=AASA AXAVmAAAȀA}wAwAqAkAf]dA`A["AVTaAQ8AL8AGTABPA=A9QA4A0|A,8A( A#A|AAM6ASA%A iA A#ABA@@@@S@V@VX@M@т@H@/@4@Xy@@w@lL@X@m@v@Z@S@ff@@Ѣ@*Z@@\@A!A7AAOA A AAA3A.A*C-A>gA9ѷA5QA0A,A(YA$3A #:A(xACaAsAA !A AA@@ W@@t@a@r@٧@T@{J@@$@@S@9@ @h@@u@&@@@+@@0@~R@@~@w@qG@j@dyh@^Q@XS;@R|@L!@GC@Ae@<@7@2@-`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@@4@@@y@CB@~g@W*@X@@@?)@@^@d@uq"@W_F@0C@????΅??F@@@E@`0@y[W@@@@a@_@@@@4@@@!WA\AA$q A0c A;AGAU>A`^AlAwC-AXA A3AA~A;0AOAA ~AAAA A2A`AAA!A{AxHAumAr8AmAiAfFAc_A`SA\+kAXAUARAPiDAMbAKlAIAHAF]AEZAE=ADADAC_ACNAC+AC4ACtTADADAEHAF AGAH7AJOAKJALAAM(AMAMANZAO:APE9AQzARݘATbAVcAXAYȴA[rA][A_=Aa.Ac($Ae.Agg8AidAl[WAoArAu'AxrA|/OB>aB?#B?'B?бB@NB@DB@kB@ B@,B@9B@B@yB@\]B@1AB?cB?B?%zB>B>B=S B<%B;B:B9kB8B6B4B2B0ȚB.="B+FB'B#YBlBxBB A($AYA&AcTBs3B$LB/B8S[B?a|BE-)BI;BMBP=BR+6BPBO B[BbkBgBjǮBmBoBo BrS@BwBz+B||jB~ B{BBB ByB%B^BNBBBAB\BRBB<BBB\BBB=B|BBBBFBmBoBBBXBBBBB}B'B,B/B/B-B(B"4BKBBBMBzB*BӏBsB BB BnB&BB5BBBxJ BBBUB\B=VBBBnBBkkBXBe9BBBOB6BBdB{BUBBkBoBBBDZBVB2BYB&B<Ba;BBBMABXADqAEl"AEaAHAALAQA\AhjAr+AzAAATAEA&A{ANAA~AxXApAeAYsAMAFA>A8(A1A+HA%/AMA?A|AA rA@YAˈ@@ @~@@<@XO@ @@2a@M@g@@v@@"@Q@8@NAEAxAbA A>-AnDAAAmAA"yA&~A)A-A0A4 =A7bA;JA>A@zACPAFSAHALoAO ASAWYA[{A_{Ac"AgAAkAn֡Ar$AvAz}A~'AsMAAA#AuANA7A$A#nAA'AF AkAAA rAOAAAAAAJAA7A.AAQAA/A4A AAAAxAADAAĩA lAiAA(AAAA{AیAzDAA̹AYAAΜA=ABAЃA&LA#A>ASAAˈ1AˆAAeAA|PAA͍AAL0AAĦLAA.AAAAA|A9A5cA0.A,hA(A$gA eA{A*AAGA A <6AAAF@9m@ 5@@@_@@F @@̳@ǘ @ž0@"@.@bc@@u:@'|@@@U@A@!@i/@~@e@@|A A>AAVA'A AAA2@OA-A(A;A7CA3%A.QA*A&A"UAA AJAIAA x8A AAGE@ =@@j@@M@S@z@@+@ε5@^5@&@ @j@6@w@g@Oa@K@}@b@H@I@b@@e@A@v@xc@q@k]@e@^l@X@R@M)_@G@B%[@6B?B@CB@fBABBFBBרBCUgBCBD"NBDtBD*BDMBE BBEAUBEW$BEb4BEbNBEVBE7BE_BD%BDsBD(BCBC BBlqBAB@B?B>B=>B<]B:B9oB7:B4żB2B/oB+HB&]B! BjBA_A=mACAB[B eB"%B/EB9@Yu@@@@U\@ط@@@QY@Ë@VX@ @AQAzAAApA ¯A A A κA A Y@A /A 'A A *AWAvAL:A`ARAr@?}@@@@!@U@@ٗ@h@Y@i@lv@@`B@@B[@;@tmr@pj@r@z @@t@k@M@ǭ@A&AA'OA=APhAe6zAm4nAuyAyA}uAjAuAA}AzXAuAoAiwAb4AY4AQc AJ`BAEoA?>A:A6oA2WA/hA+fA(A%HA" lA8A}AA4A AA8(AI@|@g@E@ @>@x@ܻ@@e@@@׎"@@@v@ @+@\>@뫊@7@@XdAAaA+A `AA\AK>AUAzA"A'A+A0 A4A9A>jABAGjALAPAUAXqA\HA_MAcAgxlAkSAo>As:AwA|oAOAAPAbA ArAQAVmAAL0A?Am]AAdA:AA@AffA =A\AFAA;0ArA/A1[AAA=A;A83A4A1A.A+W?A((A% A!AaAqAAݘA~]A5tB4BBzB yXB _!BW B`'BzAKAAYAhB@B xAAAɆAAU2A[AA$@AAAAAPHAϡAAƂAA6Aw2AGA,A'RA6AXEAAsA5?ALA(AAdZAAAAcAy`BAs}VAmAh+AbA]/AWARUAMAHzACA?=A:4A6!-A1A-l"A)7LA%qA!+A+AVA~AzA]yA AwA"A@k@6@'@>l@v@V@R@@ͱ1@ȐX@Î@2@@?>@@AJ@@@@@ @@@@Q@@/0A"AAAAKAA ײAA+A5 A0.A+sA?DA;dZA6&A2zA.%A)A%A!eA\AlAA0jAA AmA9At@ @Q/@ @ @@:@j@ٔ@jU@C@@r@@$J@sC@߹@iD@(@л@@@p@]@%1@Z@E@{@tD@mJ@gP@a@[O@U:@O]y@I @DNQ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@%@߳@@u%@A@AjAmA]AAXAAA;:@@@KI@8@@@@@@#@3@׸|@h@~@@Se@@?@X@@7@?>@@g#@@AA&A %FA h>A A A AA]A"-A1`BA?CAP~AaAnEA}{JA|AAvA[A`BA5AAqAYA%FA^AAAoAAAAtADA 'ASAA}TA{vAyXAvAt*0ApAmNkB>B?YB@GBAB BB.IBCpBCBDBE}qBF?.BFXBGBHBH BH]BIM6BI|BIBJ'RBJZBJaBJ BJBJBJCBJBJc BJ&BIQBI}BIBHBGBGPBFBEBDBCBB>B@~B?)B=O\B;1[B8B5B2nB.RB)BB"rB{B AmA\ArAABVB-B9NBABH̘BN~BSVBVBYQNBZJrBVxB]_BfbBlBpWsBs BvSBxyBxyBz0BXB\B2BS[BhsBBBބBhBBR BBB>BBMPB?B6BBjBWByBB:ByBhBBBBBiDBBB BڠBB~B BuBBBB/B$BB BB[BBoBB_BBvFBZB="BBB+B:BB_B2|BBB*BaB$BBBQB'BEBEBBaBLB69BuB8BLB~jBBT{BB/BdBBcnBD BmBBBrBBwBShBaB?VB]BB2GBB(BBBF?BBPBBYBvBBIBB=B_!B~BB3BB(B;BBBVBB%,B./B5B;B@BDtBFBHsBHBHBG8BEBBNB>B:B5B0B/B@@@@V@#@)@0~@6[@<@@U@DZ@I6@M@Pm@S{ @V:@Y@[@^@aAt@d@g[@l@rv!@x6;@~;@K@B[@X@mr@@A@@H@1Q@@P@X@$@@82@@Q@1@A{AUAgA A wA7kAA11ABpAUAjAxAt^AqApeApAĦA'AAeAU|A:AרA!AnAFA&A JAA8\@@#d@!@@@q@@@@@@@ @L@~=@A@,AAAC9ADĜAFS&AGAJAPSAVQA]!Af4ApDAwB[A{)A~?Ax[ArAjA^YKASAAHRA?%A6{A-qvA%6A`A6A"4A*EA MAtA @@à@@@^_@@@ҳ@پ#@l@1@ @@`@@C@v@@@ @6@͟@$@in@@\>@ @@i@w@AA%A :AeA7A2A XA%*A+A/A4)A8A=˒AB͟AHzANtAU!A[AaAgcAneAu A{HAsAAw2A0AoAJXARA:AҽA8AA0AFAA=A.AbAK^A9XASAA8A AyAARAqAABAƠAYAsMAAϼA[#AAԡAJ#AA٤tAVA ~AmAA[AԕAMjAtA?AXA33ACA'A4AA_AA\A DA_AAoAtAAc A5A\)AܒA_A,Am]AAՅAAҦA;A҉Al"AA˦LAGAAǏA7LAHAÍAAA[#A5AYA \A4AA{APAAAA[A =AAAAJAuAیAA|ݘAxAtKApAkAg|Ac+A`A\"hAX:*ATaAPALߤAI6AEAB{A>kA;-wA7A4:*A0A-jA*7A&یA#6A Av!AACAWBߊBjeB BB uBZBG+BE9A*AWAQAsBBB AA>A(A}"AA췀AV9ASAAfA/OAѣnA($AȽAd&AxATAAAXA AA lANSB?/B@BB1BC?BDSBEZBFXBGS[BH=BIBIoBJ BK!BKBL7BLUBM/BM~BMBN#BNhBNBNBNZBNBN_BN֡BNBNBNFBM$BMxBM+BLBLBKLBJzBI^BH}BGKBE2BDtBB2B@`B>e,B;B8B4YB00!B*mB"BBM6A)B!AYBnB+yrB8BBvFBJ%BP[#BUeBYi*B\_B]B\|PB\jBhBn=BrBvByyB{B}!HBxwB0BOBFBB BPBD BNB:B B\jB"BBBBB+DBBB|BۦB3BBBBQ'BBVBBB;B\)ByKBBXBBɓBgBBB&B_BB BBBBƵBXBB_BBo\BWB>B# BBYBBbB{BSB)DB]B̳BBdB+xB}BwBgB]BBroBBB.A ApAA AiDAAzAoZAf4nA^.IAVoAK=A?)_A3zA)A"֡AAAAA d0AʌAX@@x@F_@H@ۮ>@w@Ί@VC@@@\@3@@@@@O@@ܜ@ǘ @@3 @׶1@޷@昳@@4AClAĻA AxAAA#A*c A1OA8A=ACAIAO AUUA\Ad?AlAt A|AA|AdAiAAzAAAFtAxAbNAQAGzABATAA ACAn/AAcA_A[AWASߤAPALRAHzADAA(A=sA9ѷA6AA2'A/RTA+MA(A%~A"7LA3AA>BAѣBiBBB DB BBҽBAƨAVAwfB9BǔBASApA!AA枸AGzA鯃AgA?A7LAPAفAԷAAAcAsAcTAAAA_ARA[#AtTAhAA9AnAAALAcAaAAiAxoAsAm4AgAaDA\~AW=qAR ALAHAC+kA>oiA9pA5HA0A,~A(UA$6A 1AC7AmHAAOA tA AAA:A6.A2PA.A*A&}A# AA@AAAW*AA [A 0A&AVA@R@,@;@V@i@.4@֗x@ @ć@Ƈ@h@f<@@@ R@x@!@a@e@=@/@:@^J@@@X@{N@t@n@@gǤ@ax<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@2 @G[@mb@@c@4@ @͊@z@8@@y@m]@g@@&@J@@A.ACvA¹A {A aA|eAA]A!ADA!A#A%A'OA(A'FA(`A)A+lA- xA-+A0VA3wA84A=AEQALASA\yAh.IApAxVA~AAtTAAA<A|PAFAbAA{ApAxA]AFAAAA0AA{AAAsMA3hAAAAg8AAA@AkA ATAA@AbA0A>AUAATA_pAASAIAAzxAA-AA~AyAtAny>Ai@AdA^3AXJ#ARAMAIADA@jAB[fB\ \B\l=B\B])yB]B]tB]B]B]ҽB]3B]B^B^88B]B] \B\QhB[B[BZ=BYBXsBVBUzBTS BRBQHBOtBNBKңBI%,BF6BBtB>_B9?B1B&BB BDBR:B'GB5abB=)BDBJFBO}BQ%BU BSqBSB]%,BepoBjBoBsBvοByBBzvB||BpUBB _BBBjB[BBBB{WBTBB9BpHB BhBB/BBpBBB(XBlBBCB _B4BqBBB\BtBBByB֡BpBBdBBBBBBBBBBBuBYB;B=B_B B6BBShB#-BB>BBABBLBjXBBBXB7BlBB9BsB|B2:B:BgB,BBBBYXBtBBBraBpB3B B|B B,BFBdB%BCBBKBBYB>BBBBWLBB BBB>Ba|BBBSBBoBBnB[B~B'B0B7B=BB'BEmBGBHBHBGBEBCnB@vBAB'R?C?9X??c@m@ B1@U@X@?)@#@+y@2@@8@?@F|@L+@R@Y<@`G@g<`@n9@v)@~u@@@dZ@`@x@%@@ q@@@@e@Ų-@ @q@v@a@@AAA AA2AAA TA"*0A \A*AA,A0AA tA URA أA\hAASeA>bA$A A,8A6%FA?#:AF%AIAMAN$tAJAHMjAGAG AFjAEzAE"AD~AC#AD1AD0AEnAF"AFsAGAIGAJ8AKp;AL*AMANANAMAMALl"AKAJAIDAJALݘAO(AQGEASAV A[A_4Ad<6AhcAlAnAqiAs+kArlAn9AjAeA`9A[AW8ATAS4nAQ APTaAOKAOwAA;AAܚ7A A׮}AC-AA*eAQAʃAǾAAbAT,A[Ay AAvAJAA7AXA@AAEA!A&A.A0ABAdZAAIRA~-AyNArN@zx@ם@U@M@O@7@4@@@ @q@@jA AAKSA%AA /A^AA $A/3A*A&@A:jA6oA16A-kA)zxA%lA!rAAzAAEA~A AACAv@a@@@@⿱@g@<@ѭ@>B@t@Y@u@ @@6@s@!B@@|[@R@C@M@q7@@@o*@{@uq@n},@h <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;R4.h(R#]G~ }丿N{-˿ϫ.{ŬFƋ.#$w> 9>D?%'?j#?\?H?!W@ M@'@@@Xf@pAAA-AA?AGA|"AvApEAjhAcA]AWAQ!AL\AGAB˒A>AA9`A5OA1A-A)A& A"ANAzAAA9AAAA?hAAFAڛA4AMAAA"8A%cA*{A/JA3dA8bA=ADAKdATdA\A1'AæAȱÁA<6AA{AfAAAA|A>BAAA BBX+BBBB)*B ͹B `B ޞBdZB6B'BخBpB B B#nB&,=B(B*kB,uB.B/XB0tB1oB3?B5B8 B:9B=xRB?BAȚBCBEPBFBHoiBIBKIBLBNBOZBP~wBQBR{BS]BT.}BTBUhBVFBVݲBWh$BWBXWsBX"BYBYe,BYsBY\BZ BZ.BZEmBZQBZUBZQBZB@vBAB@B?BCnB)R@DU@C@B|@B @ADg@@n@A@B@D9X@E@F@HGE@I @KJ@Lj@M@O9C@P@T @Ww\@Z @^u%@b @e@i@n@t#@yX@~@@@@g@.@g@ g@ \@R@Q@ @q@@@@<@Ϋ@O@@Ɠ@颱@@@{A(A>A [A;AeAAAA#\)A'wA+"A.7LA1YA4A7XA;eA>w2ABAF&AKE9AOHATRAXA]pAa2AeSAh>Al]ApHAtAy \A}&AA}AȴAA$AKAlAmAA AA|AFAAA=AAA#A AOvA:A2A+AKAsAAAKAAcA@AAARAAAA2-AAAAiApAAPA|zxAwAq֡Al!AgzAboA\AWrARJAM,=AH-ACMjA>&A8A3K^A.A)A$)_AgAAj!A A+A@;@l"@V@ڇU@v@˶[@>@@~(@@@@@@@@,R@@L@h@X@@ o@ŗ@@EN@)J@չc@߼,@6@/ZAUAXyA 3AANA#A+6zA3AA䍄A)A6A ApAArGA@A/Ae,AܒASAA"hA^AAרA@APAAA\AΥAAzAA}AAAA/OAsAsAcAqAAaAAAnAqA6AFA;AAAAGAA駻AWsA-AA#AǮAܾAپAgA[AA JA,=AJ#A AEAkAzAeA^AbArA"AA>A%zAmAAAA>AuAAA&AʌAtA AA]A~@Ay4AuApAlA AgAcA_A[BAW lARANAJEAFAC A?WA;'A81A4W?A0A-A)SA&7A"eAjA;A0!BBʦB B 4B_BoB6AHA/AAbBByAAZAAAMjA2A3A烰AFA*A-wAJ#A9AҚAAɎA$AAAJA%AoAA#AHA|A9AA=AAA7AAAA|Av0ApAjAeFA_tAZCATAAOAJLAEA@҉A<A7q A2yA.~(A*-A%A!EAӤAmAAWIAA %1AAKA3@D@Ex@'@,@[@ݰ6@&@L@́@a(@b9@@@@aR@@@F@&@%@@@x@@;@Ŭ@i/A SA*EADAA^A A>AAoA.u%A*A%NA9A5VA11A-A)A%"hA!>AkAAAYASA NFAmAA>-@ 9@@@o@@@ 2@ҕl@*E@+@°@@K@]@%[@@ @@`@3]@@%@D@|@:@3@}d0@va<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<fe}dsb2`1Q\4WOrHAT9Q0o&3 ʿؿaaR>R-=:>?<?[?%?L@-@*e@E@`ŗ@w@/@!@m@E@@d@AwA AƨAA%=A/IRA9oAHwAWEAbEAl AuA}[AAjKAA=AGA%zA4AzAfA AIA@AAAA4AAXA(AlWA"AAAAA AAA-A4AIAKAQAzAAkAA7APAAm)A)Aw2AAnAdAcAAARAuA6AAA,qAAASAA{AzRAtTAmKAgAbA^xlAZ$ AUAQ!AMAIHAFF ABA?sA=A::A80A6bA4GA2)A2^A2:A2oA26A2A4hA7 A9A< =A>}VAA?ADzxAHAM_pAQ AVA[AaoiAh-ApYAwA}A;A AYAAǮAAAAXAPHAA^AEAAAłAA}AyA2AAVAAAFA=(BgB#B8BBGBrBBBcnB!B$B'4nB)ZB+9 B,B-fB/B1uB3B6B9uB;B>poB@\BBBD|BF;0BG BIBKlBMBżBHBBpBcBFYB&BFBBBBi7BA gAvAHAA=A#HA)RA/3A5A;#:A@AF ALARAYjA_HAdAi Ao\)AtAzAEAуAjAAgAAU2A2AAcA?AAAgAIA/AtAA!ASAiAA=A6AAAAAAA|AAqAQNA]AAq A6zA{AyAAQA?AqA \AAyAA~DgAxAs6AmvAg6Aa!-A[4nAUoiAONAJXAC^5A<A5`A/{A)ZA#iDA8AšAGA A@'@Z@.@ @ۘ@V@@@@xl@>@6@`@Ӯ@w@FJ@>@@dE@4@ @@d@I@D(@Y@m@y@@L@T@O@@@GAA AASAA%AaA0UA AvARAA,ANA9A|PA2AkAoAÚA3AdAΘA,AA 7A,A[ASAALACaA AcAAA\AAAEAkAAuAYA%A_AVAA$AwAzAIAAtARA2-A5 A;AdAAAQAAK^A'A^5AAAZAh>A=A\AAYAkAAߤA\AFtASAAHAAzDA A׳3A/AѦA)*AʸATAAA|AOA.IAAAA&ABAjAAA&A{JAWAFtAA7A0A6AAK)AA7A~oAyAuh AqAlyAgAc{JA_AZĜAVrARcANQAJQAFAB A>_A:hA6/A3oA/U2A+A(iA$hA AA JBB2GB ؓB BbBF B<AA-AOABeBfA A6AhA,A#A㦁AޓAAKA۽AA AgALAAdžYA;A(AΥAAAAABAA[AaA7A AAAbNAOAAȀA{kAufAo{AiHAd7A^FAY-wASANAICADA?A;33A6A2+A-hA)hA%7LA!A!A;%AmrAAA mA*AAy@z@P@;@O@U@@i@@׈@;@@Ë@@<@n@@@>@@ @@M@@;@{ @+AӮAZA,AȟAMA oA'A@jA-A)dA%A9$tA4)A0A,RA(4A$(A 0ALA|AAA }A AA*@<@I@@@i@@@z@/@ʚ@Y@9m@8 @U@@S@]y@@@d@G@D@[W@@F@3@6<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<?Jwc޿8TmJ!Vuײ">}gZa=cܿdP>Ͽ '̾ҦΔ>&j>,s?++?zT.?n?Nf@&l@$ @/@HQ@aJ@}@3@5~@|@ʂ@Ҍ@X@AIfAAA'A1qvA=AI1ASA]Ag$tAp4AyAA\AԕAiA-wA|AAUAA*AwA+AVAA ~A^5A@ApAe,A6zA`vA/AFA(AkAAiyAצLAJAӲANAAA@A@!-A?A?|A@L0AA5?AB֡AEMAK \APDAUTaAY7A]AbNBABD BGxBIezBK|BMz*BOsBQHBRDBTeBUĶBW =BX9BYUBZ^OB[UB\BzBBnpBBB'B9$BªBDB,B ~B,B9KBjBBB˺Bv+B kBBBoBσB&2BtBjBB8BnB BBBB<BZ^BuBIBZBBlBB?BBB3B BBBBBB@o@Ft@"@>@@@bc@$@>@N@`@$@@CB@L@@)@/@%@xr@h@ZW~@K^@AGAADAmA_AA;A~AKAAƨAA*AoAAAVAA \AAzDAZQAyAo Af A\3ASݘAKS&A@A40A)qA:AWAAA@@&@.@ߝ@X@2@+,@B@w@@@KI@.@Zq@4/@@ @@<@F@@@?@m@@@@[@"h@@ŚR> #ʊpWw' 37J+bv2B[T#7 w]3-My8mZ\\I^ 7%S&PrϿ q&+r>?P>d??S@k@Bc@kqL@1@Ec@@j@`A RAx8A,;A=6AMuA].Apw2A-CAVAAiAQAA'AA AAGAơbAL0A>A1AAAy AAkAf2A)AAA롖A>A&AMA}A姇A4A=qAޥA۰AA?A6AQA6A֡A!bAqAxAHAA}AAACAoAA4AA|A3A}VAMjAAAJA AAAdAOA~"Ax=Aq]AktTAfAbGA^MAZp;AX)_AV%ASAQ`AO,AM@OAKAJAJ?AIKAJ AJݘALAPhsAT5AXA\ZA`!AdffAiȴAoAuy>AzAFA{A0UAAAEA]AmA AbAAAAKA@AAAcTATAʑAЂAcTA#AA =ARA=A1BTBBDB ,B BAB?BB]B=B,BcB}BBPBBBgB!B$MB(eFB,B/FB2յB5rB7B:DgB<B>B@` BB BCXyBDBFBGBIBLBNЗBQXBSsBUDgBVBXoBYB[)B\dB]B^B_B`BaBbcBc2|BcBdkBeWsBeBfBgBgBh,qBhBiBiz^BiBirBj!BjBzBBoBBJB:BWLBq B>BBBBVBBB(BBIB[B%B@1@Y @\@!@@4n@@i@;%@@Ë@uy@'@@*@+@},@@,g@@@#@;d@i@@@v@z@9@&@G@wՑ@k@`?@UZ@J@A @7?S@-@$@ @s@ @N?B?bc??}?F?v?? ?՗??@KP@U@@S@Ʀa@/@@j@e@@l @@@E@:@΅@@4@K@G@+@@c@ͳ@v@^@77@ @'@J8A' AARA!A+MA4mA=IRAF4AOrAXAaAkXyAuAOAAaA AFAh AdAAA8AAAhA8Ax8AбA lAA>AyAA2AP}AeAA!ACAA+AOA PAzAl"A"A։A#AA@OAȞA AAAXAcA|AAA-AEA AA~/ApAcAWALAAqA6 JA+1A!HAvAiAuo@]y@a@@s@ݝ@&@ЍP@O@@@@@F@r@;y@@@ 0@$t@mr@)J@2@b@Z@4@@@@a@.4@@@@82@]@@A}AuyAA}A!h A)-A1SA9jAB AL4nAUTA_Ai~At JAA=qA:AA$AIA[WA-A$tAHA>ALArGAA|A·A4AW A̼6A0!AAםAnAPADAJXA"A튦AAAAAABBdBBB(BBtB BBBBBB AA4nAA6AAVAAkA BncB_BtBBpoBKDAQAAA-AAARA[AvAꂪAWA4ASA4nA6AJA&Aي=AA:AA6|A2fA.'A*mA&fA#:AԔ{A^BBIB BBGBCA AuA"hAeAB(B A A A\]AjAB'A"AصtA\A,=AOA-A\]AzAAƦLAS[AATA-AAXA6AA*AtAA9AAE9AZA{AUA($A AyAt;An($AhoiAbԕA]ZAWARAMAHACA>)A:@OA5}A17LA,QA(A$p;A `AjA AAA }"A AZA6@@ @\}@Ov@j@@@@֛@@˼A@ƅ@s@@@U@@'@r@@@@@J@t@:@A"A_AALAA A A^A@+A,bA(A$LA6qA2RTA.B[A*B[A&QA"qA^AsA55AA NA A $AA@@C@@@@L@ڟ@o@Ϥ+@T@#@@H@C@U@@do@&@@{@b@b@|1<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<NE CF<< >¿]势N^Y BpIF&K7(H]rk5;9ȟ6I33veN&W3f<۶a >I?7v?L?1@ @B*E@n@&@@M@6AnA !#A@A%oA2 A?AM+A\}AkU2Ay_AAA8RA,qA AAxlAAAuA$AsAVABAճA?HA6ArAܨA%zAAߛqAy>A4AA AՅAԑAyAR AAAӫkAרAԲA֡AҋDAAAAAoAAŨAAiA{A JAA_AAHA=A#AAbAKA|PAAA A33A"AKAF AvAn6Af]A_AWAPoAIbABA<;A5CA/A* A'K^A$hA!AAA A A!_A#'A&A)A A+OA.JA1VA5jA:A>ACcAH!-AMOvASAYAbAjAs.A|AAAA~A5A#ANAAAĴAmAAlWAAA3AB/OBhB BBBraBB}BBWBdZBMBtTBBNB}B!NB#4B&+B*q[B.B2UgB5QB8B:B=9B?SBAmwBCWBEBFgRBG?BI.IBJdBMBOBR}VBU7BW BYy B[@B\ B^gmB_ѝBa$BbcBcBdBe`BfaBgBBhBiTBjBjBk6Bl5BlgBmmBn BnBoBoBoڠBp&BpdBpBpjBpSBpBpBpBpBpBpL0BoBo BoBnm)BmlBm(Bl1Bk3hBjBh{BgGBeBcBa`BB^B[BWBSW$BMGBEmB9B!B՛B.\)BB@}BRrB^VBfBmBrBBvFtBy vBzkBwB|<6BɆBBhBBBKB>jB̋BB@BGBWfBEB]B2BhBjrBLdBBHBy$BBUB$ BxB QBqhBiB%BtBqBB<BrB@BBB6B;dBVBnBB_BTB#BB@BrB^BB8BΥB2BBBaB BaB)BBBqBBBg_BBKB#BWBBBBOB!|B?BZBsBBBBBB̥BؓBBBB@%@@@@@g#@@@ @1@g@\@Sz@K@D@@ @@j@y@<`@e@ȟ@:@]@@2@^@@_[@@@3 @#@$@@@@N'@@@!@}@z4@r&@iͳ@a]@Ypz@K@>@2@(@ @@@N@?͊??M?(9?i?!?F?%? $?,?ްu?1?@@W@;:@3@(N@U@T@@˔@e@u:@І@і@ҩ*@Ӽ@؋@ Z@㲖@ @@?AEA AAA(A3A?qAKEAX6AbcAlAw!AA~AAAAAtTA9AAEA}AAOA"A/ANjxAiAV9AА.AA 7AHAՊ AA:AYKAڢ4A.AUAr|A#A2AنA~A/AABAxAArA A$tA$AD3ACA \ANAA$AyAl7A_LASAGrA@A;A7cA2A.A*A&hsA"jA#AvANAf@ί@@ o@ևj@3@@r@@;@'@@@*@4@@sC@5@ @|@x@uH@uײ@xbN@}e@@@@ @@f@At@n@]@@և@LD@9AA3ARA$A1g8A=AI-wAVAcAoA{HAɺA<6AdZAjAjA=qAHKAAA:A\ACAYAПAAݲaAW A \A\A%AbB@BFB:B7B B DBKBGzBEBEBGzBBɠB~B4BB+BuB XB _pB B #:B~BB-BP}BSBBFBQNBBBBB 3B 0B{B?cBBBrB3BjAmA1AcTAAAAAAKAOvACA1ApAAhsAAה{A>AA͵AʃA\AAUA2-A.IA5tAGAcAAAlA7LAuAAAA AfAAA{AAMAAA+AMAAzDgAu{ApAlbAg0AcA^.AZ AUAQ9XALAHxADcA@9XA<A8 A4VA0CA,8A(c A$xA7A#A0B%B BYBuBNAA~]ASAA4BAmAA+AQAHAOAAAAXEA-A"hA5tAε?A(XAŬABAA'AAlWAeAsAAɺAhAkAyAWsAAJAAA1AvA}AwAqAkgAf,=A`@A[:*AU5APAKAF3AAA=6A8A47A/A+hsA'6A#AA5JAeAA BA }A AaAY@C@@#@z@ @Z\@H@ek@ή@#y@L@@@@ܱ@D@҉@@Z2@R@l@@G@}@@A#AkAAAAA ܒA{Af@ ɾeP+R=ٿk e+$Llw>]-b!ШݰK!vGt6Zbև+pROgw䏿Ρ>ץ ?Ԫ@@V@@ 5@؞A-AA7FANAcAw.IAA8AFAoAeA AMAHAʓAХzAbAܮIA^jAzAPA3A;AcAaAAKAAC-A2AAy>AA=AA풣AQAgAA]AA)_AAA%AA㻙A$@ApA٥zAtA3ApA.IA/A4AA|AMAȀA$A_AA)AAc A/A}"A-wAҽAJAAAOvAE9A}MjAr*0AhA^AUqvALjACxA:bA1OA*!A&jA'MA&=A%FA&bA&A' \A'hA)>BA+uA/&A6+AMB@PBCncBEBH3BJX+BL\CBNF%BPBQ BS>(BT]BV@BW]BY5BZmB\B]{B^B`HBaBc;0BdJBf.BgB Bh]IBiffBj_BkKBl,BmBmyBnyBogBp BpVBq BqBqXBrR BrBr*Bs%BsMBsaBsaBsOBs,"BrBrBrSuBqvBq}BpyBp:BomBnBmpBl7BjBi5tBg\Be9Bb6B_ΊB\OBX BRMBKXBAB00UBB.sB/;B6bBOsMB\6BfraBm(Bs|jBwB{OB}CB}JXByBW?BhBBB|)B-B+BpB_.B BNByBBBqvB BBBBs@B&?BBbNBBnBzBRBBBiBB;B@BB{=BBB B1BS3BpoBBBbBOBBЗBBmB%BABBBBBKBmBsuB]BEB,=BBBԕBaB&BrBQvB&BBBwBQBB̋B4B1BB{BB4BBBVBBBBLBB9B~BxyBPBuBB*0BBPBlWB0B|BBBzB3BBuB%BpBGBB/(BBB="BEBBfB@BsBBBBB6BShBmPBBrBB]BBLBBDBA A AtsAA]yAA"AAl7AtAxAnDAA0AwQA A eA >A }AA A LA >A~A)AACaAAAAxAԿA6AR@&@@u@@x@C@@@@@{t@8@@@8 @@C-@"@$@a@@@@@MU@ G@vv@gl7@WY@Gd@9ͳ@,@#%F@@@@@ @Mj@???Κ?M??^?5+???@@^t@ P@[@_[@$@.@: x@FXy@SP@h@9@-@) @@+@֯d@AXAABA.RTA@y>ATAjAzAzAیAA^AASAɺAخAƉ7AuZAԟVA^ARAA겖AAgA;AB"hBtTBBBKBvFAAA:A}"AA` A"A\A%AbAΏ\AM6AL0ArASAAAuAA7LAR AwAoHAh.IA`QAYUARAL:*AEA? A9~(A0eA'bA~gA\AAT@@@Ԯ@@˧@@#@7@~@f@w@j@ew@`B@[d@VT@T+@R@P@P@R5@S@UD=@W @]@cQ@i< @o@wk@R@V@~=@R@@Z@[@¨@Y`@y@ȊA LdA A-.ABB_pB B{BOBA1AA{ATBBAA4A-wAOvAAArA;dA ~AAAƨAATAy A rAŦAWAAAAAAaA2AkA_AAAA#AOAmA,qAA}AwAqAkĜAf =A`oAZZAU1AP]dAK?AF?AA\AWA{AӮA0ZA,!A']A;RTA7A2A.hA*uA&kA"tTAgAAAOAYA +AAPA @z@@r@@҉@@J@Ӹ(@F @ @U@@R@@&@@ B@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A qaA yA [A %/?Ex@Xi@֌@b@jAIA($A3&AKeAiDAAANAAgmAAJA A9AUARAA+A9A49AASA*eAȀAMAiAMA1A2-ArABA+kA\AAiyAxlAF AA A虚A~AAAހAfA<AxAӖAеtAKAʰ!AǖSAĄAZAAjAl"AںAACAAA9AA"A7A AA7A5 AdAHAvVAm3AcAYrAORAEXA:A0.A+A)CA%NBOBjB^BB!B!'B!B#:^B%|B(}B,+B1B71B;ZB>бBABDu%BF]BIBKN"BMfLBOiBQ\BS_BTHBV4nBW(BYgBZB\B^B_Ba+BblBdUBf Bg;Bi-)Bj[Bk(BlBmBoBpOBpBqOBr+BsBtZBuBuBv'BvBwBwxlBwBx-Bxn/BxBxfBxªBxBxyBxmBxN"BxoBw+BwaBvBvQNBuBtٚBsBrBqBpY1Bn)BmBjBhBe~BbB^BYoBS0BJZkBjBBBBBFBB}B\BB@BBgB(B.BBBpB.B}BBTFB!BnBOBBB B@BBoBJBBBEB$B?pBWfBlBB4B4BjBB@5@M=@ia=@f@@)@҉@@~@@@P]A AASA6&AB BiBB9BBF?BBBfBmB%BB B&B DB GB WB ϫB )yB ߊB BBBsBBYBQBPB W B d@B 2BXB[B-B+BAʌAA,qAAA{AAwAADAA9AA AڹAq A4AAAADZAīA$A:ArAGA)AAAYA)*AGEApAAVAAAA`vAA rApoATA\A̘ACA{AvAqAlAhMAc^5A^AZ$AUAQ'RALAHhAD!-A?A;A7A3A/A+AבA<6BgB`B ncB wB B`\B2A3hA(ADB :BEB8BA AAjAA(AAm)A1A AAA rAS&AdzA)*AnAVA AچAjAhAwAjA A[#ALA'RAA@AA@AqA~AxArrAlAfDAacA[cAV_AQaALHAGOABsA=A94A4A0A+dA'A#xAsAAAAQxA PA I\AqA@@@{@X@X@@ @>@ͪ;@<@I@Ц@@@9X@<@'@B@@{ @}@@@1@AAA\rA=A<A VA5Aܒ@@[A(A$A!A2A.PA*\A&bA"aAA8AAAdA +A iA'gA@N@i@@*@٩@ Z@`@ә@e@ @@ѷ@ @G@G0@H@#@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A9cA%AAeAvA!A>AuA*A@{5@$@^@n@@@@x@B@ӡ@j@ń@@@q@K@~R@?@@zZ@ce@L$@3@=2??HA?1? k8;ԿwAAۺ^AXAwAd&AOA]ABB BzB&B_BB)BxlB5?BHKB;BBsB5BBBBBB|BBgB)*AAADgAuZA}AhAB4NBMbNB`A BkBtGBz1B?B BBaBzB[B Bk)BtB'mBBXBdBYB ByBd&ByBB#BOBBBABNcBfBBFBѐBQABYB1BTBpB@BBбBBGRBz7BBФBBB0HBH1B\jBmBzQBBBbB BABBfBBvBjB\BLB;=B'BBxB,B͹B%BBwBTB.B BBBwB@BBżBOB4BBB!BB.BXBKBB+BgBBB6B PBu B[BTBKBmPBQ4BjBB|BtBBMB<)BXB8BBhBBBBh BB'BZB)BRBB@BnIBBBBBeB2oBHB\BnB~BqB=N`=x*= n=S=/>&>M>>r2?#9K? ?b@@W@@Ċ@t@@Б@?)AAA ;AޞA7AJApAAAA DA!A#DA$ѷA&A(A+A-<6A.,A/A0A2MA3.IA3A4CA4A4A5E9A5=A4UA2AA04A.,=A,*0A*.IA&@OA".A6AWAAAIA AAI(@@ud@@8@i@M@@@@U@X@O@¹@qa@@zZG@lZ@_#y@Ri@F@; @1:?@&@2@ @ n@QY?Y??eV?Πf??? ??R? ? =??iY???e?Q? ??&B?h@ d@5 @g @k@ @VAAPUAA@A!Ao5AA쒣A B6FBB MB zBByBmByBBBkB B"2B#nB"B!-BB=BJrBMB B#BHAA)AA}VAFA#ABAVAA)ApAAAAgA˒Ay4nAk=A^HARAGeA;ZA0,A&[A6A=A AR@5T@J@A@ʳh@+@jU@#@@bx@5@U@qL@x@pD@h@`_@Xf@RK4@MU@Mj@O@R @Sj@U@ap&@kC@u@&-@@8@@@\h@F@`@c@%@GAAAAXA-_A<خALA]YAnxA~A@OALAAuA AK^AA6FAFA A^5AA;dA"A2aAl"AбAA'BIB:*B1B1AB8RB B LB lB'BKBBbBFB(XBP}BxB4TBBKB2BtB4BBBB)yB BBB$B JB wB %B `B C-B TB (BBOB~BVB DB "B #BB-)B'BMBB5AA:^AA,AA5 AAGAAAYAA~(A PAԩ*AQA%AOA|AAAyrA;dA rAApAAĜA$AaA-AwAA]A0UAo5A*A!AsAjA.AA JA}AxAs8Ang8AinAdA`QA[mAWGAR#ANAJ5AEAA҉A=A9A5A1[A-AAҷA͒oBB 88B ԕBBMB)DA5?A<Al"BvBB7A7A&AYAaA.}AAٍAmArAbAoiA̘AТhAA@OA°!A6AAAOA,AA'RADgAuAdAAAMA_A@AAzA}LAw.IAq4AkZAezA`AZAUHAPAJAF AA0UAorud 2iHk);ihy6zAQ=Y 2S 4J2%@:@PA A)IAXA-A`AAAANAAAƨAhA6FB1'BBB B B BB BOvBBuBEB%B BFtB:BBB 1'B AB KDB UMB }Aު0A2A+A9AZBMBB wB BBz*B \B B#B'pB*B,ǔB/SB1PB4UB6B7B9y B:~B:āB:*B;B=@B?BCBGBKuBNBQ~(BT xBV|BXsB[!-B]^B_BaBd BfZkBhBBkIlBmnBoNBqBrBtBusMBv]BwByBz5tB{;dB|-B}\B}bB~,B[BBQBBB#BXlBZBB%B܅B=BMBBBBݲBoBBlB2BBB?HB2B~B}B|P.BzߤBy:^BwW Bu)*BrBo BlBgBb%BZBOB:B-BDB@FBMjBbBo+6BwB~BB3B4BB-B=BBOBd@B5BBBýBtTB@B XBRBGBB/vB?BRBcBMBxB+B~5BBB"BBBdBwBbB[0B;BBBGRBt{BBvBBBaB&B6zBC{BM]BT9BX8BYBXBU%BOvBG_B=/B14B#aBBhBB6B˺BBB~wB[B5B ~BBB}BGmB BϫBBFBBBOOBB'B BBB)BB'BBKBB-B8RBJrBBZ7BnB3[BMwB*KBPBfBB;B}BiRBB@BB%zBBtBFBhBBdBBBOBXB0BךB}B@OBmBBBݥB)BB0HBFfBZBkBz=*u=7t=ErK=T=d=v&=v==oJ==nF=pW=,=#:=m> ] >V># [>6>K`>_a>s>ֿ>o>N]>>"???/E?i@@D @us@@?@@@kA.AAAA#DA#lA'A*eA,LA.XA/h A/A0HA0A1BA1A2A3?}A3KA4?A2A0A/^A-A+~A%hsA0AAA A@@@6@M@Ԫ@@@֌@@\>@ @1@{@Mj@خ@wd0@kG@`@U7@K@B @8Q@.@%@ \@ @ c5@?s????Ǧ?g??p?|?=?? ??Y?]d?'|?[??¤U?>@ @(MU@M@|O@Uq@A-33AS[AAYB="B BMByB qB$d&B(XB+JB,B-B.bhB.3B*B'SB$_B!9 B!BBaB UBBA;AfA詓AAA~AA)A33AAAAŢAA}dZAorAbrAVAJ^5A?Nu@B@GW@K@RX@[@e:@sh@M@$@Y@a@ E@@@g@A R5AEA&^5A5HAEAW?Aj~A~;dAAA'AA-CA$ AvAAγhAպAACAK)AAA}AKA(AcB+B[BBEBfB "B >B BBA BFB2BWBBzBmBB&BBBtnBcBTaB;0B6BBDBBB:B AVA \AmA{A3@@)A&4A"VmAA1A-_A)qA%_pA!aAvAGA݃A.hAA ]AA2A @K@@õ@J@@@Y@,@oi@*@%@@@P<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<AFAN,AU AZA_AeRAjMAnApArDgAr`AsAu%FAvrAwAx6AwیAv=AtArTAp+AmAky>Ah]Afm]AcUA`"A^"hA[4AXFtAU+AQjAN_AI.AEAAK^A=A8EA4W?A/ȴA+.IA&n/A!|A[AAUA TA@@H@s@@@Y@@{a@Jz@J? >l5 ]ira7LُkQwef}(ῷL?@}d@ϰA#A9ߤAan/AQAHA AJAVAAABBlB uB)BBhBBBBDBB1BB(BiB,BlBgB7B;BYBBBl"BrBsB B 9rB BB?BB-B AuAA&AAqvAyrA놎A聣A|AℶA߃A9AAOA \AA)AɮAƑ4AQAMAATA?A8A0ADAAArAFAÖA^5A$@A{Ao \Ac($AW ALAA?A7A.A)A%CA$2A+sA1A8ABwAKdAT A]HAi8As A}BA]ACA}"A7AbA AA=AdA=qAGAaA \A^A AAg8AƨBBBrBC{B `BBBXBjBּB( B"oB%B("B,B0NB2,B5wfB7B9B;7B;"B<_B=uB?vFBBCBEtBJBOwBSBV1BX?B[}VB]ևB`BbBdBeBgBiBjy Bk Bm.BnBpBqBsBt{BuBw^By6B{B|B~OB`\BC:BʳBHsBB+BBBVmBB)B(1B]BB@BѷBB "BB!HBBBBNBByBLBBBLBBVzBBhBN/B~B|ˬBzkQBwBtLBp@Bk+Bd}BZBJ2BBI3BMWsBHBeBsB|BBB2oB*Bn/BByBB4,BǻBּBBoB:*B:B*BKBIB=IBB_BB-BFBSB9B`BIBdBBB BBBQBBBMBuBB ~B@\BoBBBB*BaB&B7BDBOBVBZ7B[BZBW BQBHB>B2oB$BtB9BBeBB9BBp;BIlBBBBBYB iBnBB]BBBp.BBB@BB8lB%BvBBBGBJeB BBBBB BUBiyBBB B|BB\]BUBB($B9B&BRBfLBBj0B׀B9BBB-BoBBZB BEFBoiBBBEBBB%B:??g?VC??V? ?{z?rZ6?iu?a?[?VO?P?K'?F]?@Љ?;?6?0 q?)c?#:??h?cN????x?#?f ??QY?ɛ?w@H@7Ln@OLn@nS@@e@@@$ @@]AU\A AAeA$7A-A1A5A9A>2aABkAEԕAI@AJAJRAJwAJAI)AHAGkAFMAAoA:HA4}VA.خA)aA$MAǮA' A kA(AO@_@<@r@[@k@@Q@@@3@@@@~@q1@e@Y8@M@CD@9#y@.@#D@@@ @?Cl? ??f?rq?Ե?ΕB?!?.I?ԕ????S????K???BABȴA>dA:A5A1A-xlAAϭAʛB 4B BBz*BNAoAjAAѷBPBpA%AA AEA䣣A'AAԖA}A^5AxlA̲A AÁAvAARAxA^5ABA8A@OAYAAUAAoAAeAAAZA$tA}AwAqAkAf&A`MAZAUxAPWAK0AF)_AA>AAq_Ax4AtAAL0A1ArGAAA=AjAA@AsAAޞAdAxAAAAAAEmAA!ArAtAAAoAcA[AzA AIAA%AOAVAq A[A.A#A,AMA{AvAqAlq Af=A`J#AYkAQAIzA@|A6>BA*AnAf@7@@?ݢs$_@D@*9X\'UO!<6@jAAUBB)FYB,r-B*TB*B(B(B)AB*@B+RB+B+ B)B&B% B#\B#IB"]BgAAA rAAABASA>AAJA;A儁AㄶA~AjA+AA)AAA?AABA̸Aa|AEAAA AAAAAg8AA AcAAAyAAmAAdAXA_AچAAA AA7AhA*Aw2A}AGAA?HAAfAkAFA,AAsA5A(AdAcAA BBuB {dB;B^OBBKBBB!fB#[B&B'B(ZB*mB+HB-HfB.B0CB21AB4lB6B9B>.B>B?F BEBKHBOBBRBUBXm]BZrB]WB_Ba)Bc{Be0BfrBhxlBj4BkBmBoBqM6Bs#nBu 6BwpBy_B{u B}=B~^BBoB\BWBs[BBoB(BAB BB4FBuZBB߱BB;B[BsB:BBBiBmBQB,BB4B{B(BXBXyBRBEBB@Is@Fq@C{@@@=@: @80@5R@2@/`@*^_@%@ e@0+@@@@@ @@5@4??{?U?u??6?o?@Y@ L@s@$$@C8@m @b@@W?@A AgAA 9AASA A$A(A-1A1=A533A8ĜAbA9>A5MjA0A,iDA(!-A#AUA?ATAbA9XA zAADA@@Bp@@隿@ㆭ@ݜ@@?@̎@c@W~@T"@ti@@@@J@@{@@~@\)@@-AcA5AwA-A ,A ݢAAZ@@V@}@A )A8=A;A,m]A(J#A$;dA AA\AAWA&A EA SAAI=A@[@T"@3 @5@[@ۣ@ @И5@D(@8@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<@LD@ @I@ۉ @ @#dA?A A\AA'IA2A<ĜAEMALAR\)AXA]<6AbbAfAjAnAr`AwGA{یAAAAAQA4A1AAAAAbAF?AHAJAӏAXAAZAbNA1[AAQA`A{AwɆAsAn AiAdA^AWqAPAIEAA A7A.A#>Au:A x@p@ֿ@Y`@@$ >xB_ i V@o?V3N0<6Fؑh v?>AB"(XB%B) 7B,UB,2GB'k6B"KB 0B 7B4ByXBqBBB$tBBBBtB.IB׍BiDBB jB B B PB dBUBraBxB̳B>B%BBAA@AuA>AA$AlAAA&A芦AA?AS[A|AoiAגoAAUgARAFtAʿA33AŗAEA{A1[AWAfAUgAA"AaAA-CAAA]AA_AAz6AqAgh A[AOC-AJAGCACA@nA=pABAIJANAR]AW A\AfAqAzA1AAAdAAzA%zA_AwfAfAA^5A`vA>BA]AASAAAABB+B BBqBBrBjB" B%B(tB,zxB/B2x8B4ݘB78RB9gB;ߊB>B@,qBABD BF/BGBI BJ"BLBNYBPBRmBTBVɆBXwB[YB]B`lqBbBdBgBi2Bk>BmHBoU2BqpBsBvBxU2BzG+B{B}[B B6BBzBzBB0;BBB~jB=B0B}VB|BfBJ#B.B^BOBB(BsB yBBBՎBBBCB0B`BAB̋BFLBB=B0BGB:B~B{'8BwBsVBn{BgJB^mBNBGBG]BMh>B#yB_cBrK^B~?HBXB|BBB1BaBPBjKBBB8BIBЊBnBgB*eBBHB,BBxB|BoB?BB{0B91BSBNBB2B B|B-B=BTBcB%zBdB(BѝBB(XBLJBkBBBB B"BBBBބBB1BB͟BBBBsBBzQBSB(BBǡBBY B/BwBBXBABByB(BB{B@t~@?@@:T@D@v@{@[B@xB@n@e@^@V%@P@M#@J-@K6P@L@@Ma@O:@Q+@Sy@U@W:@Y\@]V@a7@fFt@j@o'@tt@y@,g@|@@,@y@@@@ѝ@8@A ASA&A>jANTA`h Asg8AR AAGEA'RAnA#AL0AğAMA[AۮAA A3hAAhApoBvBBEBDB BߤB,BkB BBhBB(BB/BBpB:BKBOBVBaBpB}ByBYeB BBۦB_B)B aB }B B B B aB BBA;BBB.BBBZBBaBB{dBgBTBD3AhAM6A5tAVAAZAAAF?AbA AcAA֑A,AϣAAȑA$AAMAA^AAy>A>A rAޞAAAA~]AzAoAAAAAAW AkA}Axu%As0AmAhAcA^ AYmAU(APGAKAFABYKA=֡A9cA5 A0FA־ACAˡbB2GB3BQB xBBPbBخA`Am)BBiDAɆAA(A뎊A$AAۈAnAsAAAeANA̯A%AïAJAAhAAcAVA[AsAAچA)AAAAIAzAA~AxcArFAlKAfrGA`A[%AU[AP\AK'AF@AA~A@ѽ@́@Dž@@N<@@ =@?@X@Q@+A@8q@w@@@T @Mj@qA,A'A#;AArA;A"A%A E.AA7@A/A*A&oA7PA3MA/A+1A'A#A AjA"A+AYA*A AhAVA@xB@Dg@0+@;:@ek@ݮ<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<A)33A.A2A7+A<A@\ADbAHALAQAU,AY?}A]QAah AeAiAmAr?}AvA{a|A-wA A[AAYKASA{A\A}"AjArAzA7AACaAAAVAAWAxA?A(AAAAVAAAwAѷArAeAwAAMA AIAAgA.AAAZQAAAxFtAe|AQFA:A!oA]@{@:h45T❲T"@D@@>zK^*V†: @XeAAbB4B22-BDBPyBV BWBBBBYeBApB@^B?2B>xB>4TB=^jBB7BBdtBBBeFB'BEBBIB_BB"ABBB@BBB)BBB}BfBBBRBR?A?p?̤??aR??í?^?]?R???R????m]??׈?U?A ?b?v???i?X??-8???|[?b?:i?C?z???ž?@m @a@@Q@@n@AA uA[AAA$}A*-A/RA5q A:A=MAA ADbAHIALAOAR|A;ݘA9GA5 A0OA+A+A՛A"A]AQNAAbABB Bm]BrBB-B{BB!B 0B FB ]BBB bB!B"BOBXBB >B 8B iB -B%BcBJB[B./BGBB%A#AAAZAA?AŢAYA>AߦLA_A%FAAAϾBA8AAIAMAA,ALA?AQNAAAXEA&LAAvAAAA=AsAAA|A=A|KAww2ArB[Am)Ah,AcJA^AYԕAU?}APAL_AHoAC/A?A;A8$ A4g8A0MA+~A&҉A"FAAA^AjAAAmAۍAQAA^AҢAϑǍJAGA䝲A AېAiAkA͸RAmA0UA4AdAEAT,A˒AS[AAAIA\AA9AA#AAZQAsAȀA)AA\AA#AqAbA AzoiAsAmSAfSA`SAZJAUAPALyAHACA=BA7A3$A/A9A6ZA19A-_pA)A$A rArqAq7AA#A A JAA7@@@hI@ @9@ݽ@l@@]:@0@t@2@@@,(@g@@7 @ɰ@xB@A@&BA'kA"AAMA*A"A7,A fA+AK@y@FA+hsA'1A96zA5A1SA-A)bA%-A!ZANAABAA/A )A_1AA@D@@@k@ A~dAAř<<<%.%XB@1*A~dBZffB[33<M~Q#O> 7a=}?qvA~dB33Bߙ<kmC?<>@2*A~dC;fC<<%[?>@ A~dC C&f<J8@Jr>@ A~dBBCfC<@|&@}.V:U@@f o@i> `@ \A~dDD<>X=?0>h@ A~dD.&fD.33<N?z>U@IRA~dAADYfDY3<<<̆&\ &?LD>,M@_A~dA33ADfD<\[l<=@>$d@SA~dBEBFffDD3<*a.i>F!u>I=Q>?^5>@MA~dB33BDDf<?mH?>!>^[@CsC>{@R?A~dBffBDٚD<¤3@5c^@5~@<?@o>1@A~dCfCEfE<y|@b@c @UzN>O@V>@!A~dC"ffC"E~fE<] \A@i@sA?c>6A(>Ϊ@A~dCGfCHE0dE0h<]]T@@A?yA'}>@A{33A~dCxLCxEH\EH`<<̈́M@Ǿ@e&{@ڥ?A1>@A~dC@CYEaEa<*0o@ؕ-@؟V=?;As >쑰@ A~dC C&fE|ffE|i<CAAv>l?Alq>ɓ@A~dCLCffEeEg3<pAA?Vd^?vA{/> @a|A~dCٚC3E2fE4<N'A'?A'ȴ@?̸AzTa>@1fA~dDD EEɚ<0eA7A7]@9I?uA}|>ߋ@P]A~dDDE(E*f<qvANANh@U1?A>@vA~dDfD3EW3EX<uW?A\A\@P.?dN@xA~dD0D0E?3E@< AlSAl_@0p@TA1[>@veA~dDBDBEfE<[B[A~oA~@.@v6A >@rБA~dDU&fDU33F2F3<w"AVA\@'@AJ#>VT@uA~dDiDi&fF K3F L<q AAY@)@ A,>@v_A~dDlDyFF<vvAFAGz?Y@LAܒ?@yA~dD3DD\Dc3F!F!<**OAQAhAA>@RiAz_Ac3A~)?"@v A~dDD#3DDfF-|F-|<FQF>AA AcAdZ?Q@ .AB[ArAr?F5@yjjA~dDpDvfDD3F9jfF9k3<%>B% AIAW AAs@}@%ArA]{JA|?@y&A~dD9D@D|D΃3FEFEǚ<uAA AA@(@)A>wA[$Ay?g@vFA~dDDD޼D3FRfFR3<&vYAA[AAA=@0GAAZA|/?@w+ A`Ac33A~dBffBDɚDDD#3F_F_<<<AEAX"SANa@>(A<9XA-?ArASAsR? )@px A~dEfEEfEEFfEIFF<@^@{tAvAAݫ6Aݼ6AیA?L@6A1A$1A`v`AH6Aa,? %@n< A~dE 3E fE)E,E8E;3FFf<AAASA~AOvA_;AoA@v@B|A7A, AmAQAn? @p A~dE&fE)E E E(E(FSFSf<A AALAnA椩A=AA@@DA6'RA+$AlsAP$Am? 8@oh A~dEEE$E$3E3fE3F>F?3<A433A4:*A/OA<6ARAYA}A}@L@FA3CA)uAgAMHAiG? @mȟ A~dE'LE'PE.E.E>&fE>)FgFh3<AMOvAMVmAgAqAB}B{A@A@c@@JR?A1A&GAdAQ AfFt? @jP A~dE1\E1`E9lE9pEI~fEIF3Fʚ<AgaAgȴAv`ABoBrBB @@K3A.@A#A`XyAG/A`u? @f $ A~dE;3E;fEDED3EU(BCBB-?4@@U@3!A0VA#oA`LAFAgf?0/@i A~dCfCEi Ei#3EvfEv!Ek3ElFGFH<A*?A)AlApA{A+BIBB@B@4>mL@b@ez@X3A*ݘAH,AXA@:A]n?*@`M A~dCffCEu3EufEEEEfF F f<@3]@AAA)_A/BBB4BO?t@@@TA-bAECAYhsABLA[Z?+@c A~dDfD3E E"fEfEEњE3<h/gA8A;0BB DB B >@:mH@VB!~@QݘA%}VAPAP`A:AV_ A~dD9DFfEfEE3E EXEZf<Q>PAɺA6Bu ByB!fB!@A B!G@T#A53A%8AbIAK8Ag/ A~dDDfEhEiEEE E!<aaAĭCAİB [#B _B%^B%A@B$7@^oA.QAzAVsA@tA_e A@AC33A~dD)3D) EEfEEfEeEg3<<<TTNVA`AB*B/B)cB)'89AY@ɨB -]@dA3A!2A[gAEAd\ A~dD5D5EfEEzfE|EE3<0:0kA|AрiBjBnB0cB0gA3r@ǹ$B@haA2ApAY&AD+A^ A~dDCfDC3E3EEEE%E'3</'AևA֊ B1B/B3hB3lW?'@B^@lxA4,AA[~AEAa= A~dDPDQfETEVfEEEњE3<PEAضFAظRB4BB5vB5 ? a@XB @mA8A"+kA_ AHA Aeh A~dD\D\EfEEEfE̥Ȩ3<ŠbŠAXA[B *B ,B=B=?D4@B:[@mA2uAAVGAAiDAZ- A~dDh,Dh9EaEc3EBfEDEՕE՗3<Ld@AkAo5B$F?B$GBAbBAc?W@8B,@n|A/;A,AR!A=ZAV! A~dDu3Du EfEE 3E Eޯ3Eް<ΞOΊ AVAB(B(bBD6BD9?@d@WAt@qxWA0AARA=AXw A~dDYD`EٚE3E%E'3EfE <}0BªBðB$MB$PBF&BF ?y@䗷AN@oA-vAA^ƨA9eAY \ A~dDiDpEEfE}E3EŚE3<-By Bz*B/JB/KBKxlBK{d?|@'gA'@}hsA76A!AXcABA^HA~dDD3DDfDɹDDD3EfEEE E3E<seFsAoB4OB4B/L0B/RBF7BF~B cB B6B6 BPBPb?~@˧B:AA4A'RA5 A9XA^@rBA/IA`AMA9:AT A~dDDfDD3DfD DfDEUEW3EњE3FF<KKB!B9rB4?B4\]BA5BAB B &B6}B6~BBSgBS?̎@B)\ATBB2BA᳜AL@?A8AxAZ1AD(A`A~dD D&fDffDlD3DDD3E3EFFFFf<v+ZBB7B5;B5OBLt9BLwB}BB@B@mBZVBZ>F@BXyAeA<AuAAAא@p)JA#8A UAFA3+AKfA~dDDfDpDvfD6fD\@mBL~AAm]ATAAϮA~(@q*0A(xA RAD1A2IRALA~dDDD٣3D٩DVfD\E3EfE8E9F VF WFfF3<88~BɠBB;xB;PBQKBQMB\)B]BFTBFW$B`=B`>B>1AgB)A뎊BAkAAܫ6A@`kA3GAv+AO1A;xAVeA~dD&fD,DvfD|DfDE fE F3FFFF\F]<|BSBeFBB|BB}NAn/B"ZAIB[WAANAA_@vA++A AF*A3ANzA~dDs3DyDD3E3EfE+3E.fFF fFC3FDFfF3<0x0żBqBy$BD[BDBbBcMB&PB&QBP͂AB+MAB A=A,=A,qA]@|A/U2AAI3A6 AM4A~dDfDDfDEfEњEE3F F FfF3F#3F#<D?D6BBBHBHfBbBb%B+#nB+$BTb4BTbBm)Bm*>%A-A߽A1B X+A6A%A]dA@~tA.AoJAG=qA5ݘAPA~dDƌDƓ3DDfE vfE yEEFpFpF!F!F)3F)<0\0BG$BG!B'BB'BifBih>B.n/B.oBW$BWBsżBs??A .IAޝA.A5A\AA#Aم@+A/AAHaA6CAMRA~dDϳ3DϹEE3EEE"E"FbfFc3F'3F'F0fF03<B&A B&GBT BTBgBgjB4TB4V9B]B]BtraBts@[;A }B(aANB ;AmAcAAff@}-A,A ғACA3+AOA~dDDfE fE !EIELE)>fE)AFdFdF.F.F6F6f<@}@QB'RB'BYBYBo@iBoIB=B=Bd3Bd5?B~B~p@A1AA B ^A%FAAwA J@}A&0ATA<$ A+oAATaA~dD#3D)DDfEfEE E E08E0;3FzF{F4_F4`fF=F=< @@B.żB.ٚB_B_Br߾BrBE BECBj^Bj_B B C@8A6A@A+B9$AuB #AAAԘA@|hA"XA*A6CA(eAA+A~dDYD`DDE3EfE'E'3E79E7fE>F)3F)FAFAfFJFJf<xտA6VA6ںB5B5BiIRBiRoB{1B{4BK0oBK1 Br2BrBBm@A5AA~(BAбB\)AHAA[Aی@}SA$5?AA5A(+AA?}A~dD DE@EC3EiElE5ɚE5EFYEF\F/lA~dEE3E E E%TE%XE=&fE=)EMfENF4fF43FObFObFX3FX<? ? &AQARMjB=^B=BrBr"hB B*BPBPByBy$BB@LA 2Am]AЭB.}ADB AA5AӛAܣ@~2A' =ARA6 A)QA>;A~dF::fF:;3FVFVF`BF`C<BYBY+B~B~VB$B@%@A#Q@A24A%QA>4A~dElEpE EE1E13ELfELE^E^3F?3F?F^fF^3FgFgݚ<@P@qaAOANBE/BE49B|UB|XB:B;B]kB]l=BBBr-Bq4@A A~AB'BA7BAA켟AgmA@B1A#@A1 lA%HA=K^A~dES3EVfEPES3E8E83EUyEU|EgEg3FEŚFEfFe3FeFofFo3<@!@dAHA~BJخBJdBBB:B:Ba Ba DBsBBRBS@OAAز-AQNBy AvByA۳AT,AdAoi@YA#j@A/A% \A<$A~dE>fEAE!E!3E?E?E^!E^$EpEpFKfFK3FmFmfFwFwӚ<@ł@?AAݘBPoBPBFBfBB`BeBeBOBBOBemBe?UA! AAvBAIBAAA=A9$@q"A#}@A.ZA#dA;FA~dE!8E!;3E'E'3EFEFEg3EgfEyEy3FQFQFu|Fu|FF3<A:AGAdABS BSNB.B4BB;Bi'Bi($B"[B"B#Bd@A"AxA!BAy>B#A)AAqAA)_@[A#~@!A-A#oiA9TA~dE'fE'E.fE.EN\EN`EpEp3EfEFWFWךF}F}њFVfFV<A8oA8"A1A$@BZxBZBwBzBvBgBiBi`BB=BB@0A"A֡A|B$AnB4AMA4AA\@zA%h @LA.֡A$A9A~dE.)E.,E5ɚE5EVfEV!Ez~fEzE3EF^F^fF!3F!FF3<A9A:AںAB^B^(>Be,BiBBqBk BkBZB[=B/BcAXOA'AA}BuABAHAGA!-A3h@5A(U@%A1/A&qvA> A~dE4E4EBACAHBV9$BVGB`BeSBB`BvBvBBB2B2:AJA*A A`BAB nA7A:AZA`@IA"@A)AnA5\A~dE;|E;EDEDEdfEdE73E8E_3E`FjfFj3FF3FF3<Al$ Al($A0!AGBdBdB"NB#BBiBzcBzcTBBBOBOARA,$AںAFBVmAVB&2AgA+AȩAp@{A@˧A%A A1A~dEB EBEKEKEl3ElfEK3ELE3EFqQ3FqRFhfFhFI3FI<AhAFAAyBidBin}B BBBOB}+B}B{B|BB?F+A,a|A5AʶBAn/B2-AA܌ARA@eA"!@쩓A&AnxA4[A~dEI.fEI1EREREtNfEtQE3EEO3EPFwFwfF3FFF3<AkAm)B B+kBlBlBBBBB B B$B2B>B>B?fEPAEZ+3EZ.fE|E|3EfEEE3F~F~F3FښFFf<AKARB8lBJBopBoBBBBB&B[B BB BgBi?0A,YATA-CBpAqAB;AAAoAA@A@#OA!AA/A~dEW3EWfEaEaEmEo3EFfEHEfEFFfF3FFFf<AAAԆAԜBB5B=BBMBPBMBB%BBB?A0~A6AbNBEABl=AAA~]A;@PA._@A0^A&VA;XyA~dE]E]EhEh3EzfE|EEfFfFFFfFF<ApAB7_B7bhBzBzBLJBKBB9B&B&fBYB?ASAW?AsAAɤtAa|AFA@PA+@*EA-(A#-A9M././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0446956 mantis_xray-3.2.2/mantis_xray/images/0000775000175000017500000000000014700317134017007 5ustar00wattswatts././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/Mantis_logo.icns0000644000175000017500000035112614332463747022163 0ustar00wattswattsicnsVis32 _  D_yd: %Jg{7@J :)SW!lw~>EW|6bmyk9:eHl}wLQr\]B$*+xJEmXW\<_ UTQo@.=*# yUtX87=9=rW_Fbd:,#, Hdd"!5 knFI:63 lo^fgRU0 !hTuZ26X_:S(%%9%; 1)., 3 P  <9|B &>'*( *1 ǟ6,  "OBMO 8 SH q >[.j\1BMX/#Gj6 C>mwi7 RoSBPG9  &GGRiF%'  7qy`12 =QZa~J``ڿwjj`AhW9Y (K@IE N+ ^$Xd :a>'A0 " N&  < F% #K&'# UJ :.$D$.5( Um13$+.rQ-045 "L(/ (5 sk  * ?Hs8mk]ܔMSs 7,qil32 &    J  fZ@pH#@~Y  R${a#B0i8L{`  LQ;Xcwk2YC˴dWppud(1g>laA{aoS^VF2k "5tobYB=Pi]@=ڶg@Y~{Q6$$Ho_*NFk?;@Q]Q7,>Z5BBEcU/GaA(3I[.$' (ܗ\X1E kg@2  ! _dUlvtopiC9 !JAώYyq$fnqj1:1Bgnpd,Cl9;/άfLP]_^\E@2'2S`JFaZI.('(9 (2 B# \8( @? 28G%/ 4.",52 0CN) 0;.+ >YL# 3+ "  AS."   5 0   8C     $(    &* 0!""+] Lr/ Y5EE$ 6E O1"ak"; M X#8 oyn.%UM$OK&L"!fT0Y䷇}[%XEyiYK79 zn; ܷdW*,Enq^omJ:/uJb/ W6bwdczsadQ -Q H bԃGXk|gY['K$M72  #z/\ÂP[1F+ ˩^$ɂq/L{hS.  7pz]ѾYnn@Sw21w !y\K` T3:kjyg,BEY $&BO) d62AX$> bd1QYh, b?# !!#{V~Q >k $  #1qZxJ,+/ "  9DrH>EAUDz X o)@oih32A   /45  I *      TuI  $ $&[E  ^ vV'%\@ +|h.'dR Z[- m!_yh*Pm2-Pc1Qm !r]faAnOgd3%$$Ȏhxy: (nXwqRq`4ZP!(6GqNGvvanO]kT iQ#ʋ:wSRrecehSxVH7$}_X])9oe6A~NY&8l>6r4Pk=XA6MvA& (^ 缀jwJ})pQ1?RC5R_/*() )S 2>lD%Z1=qN1CO<1?Y_ # h܉ -DQM>GwH0<4Sk8,*) ,;0 7ƃ@[sF5?RN2TB!.4,!*8A"0xE{nK4AQG)eO=4PH8+./" ! Sr<@p6{~kD(.+ w-%YH.%6JJ8,+%8 ׶r9D"%\|}y6"(XE(#8LF2+:JF3    %ܳi006~Uv{~|uK=(BLA+ *>LC2(.3 w _ڕ3~1huxvk@Iq>"'CO?.-;A'0( ##F~>#5Qgmoo^& vyeA"*DJ4 )D! xH쩎uW/5QcgE'Ȃuub:0(NP<"0\KhӚP+4G$UryyuqF9&0*GP?$f>Dу7$Hckllc4D  .n{?澊ZQ]1&KcgS 3F"*& %堕3@QfX0(G+8Z #%%$! (LV8>x 5jmjS);"!,   +kӃBNt[6F%Umnlkd20K  ! + <. RExDoāhkZ/%GVjmpqpldAG* oVؾNF}y2yoplieDO9<_jlopna>$OrB& Y7ֹFD8P]^ilkkidN:L0NbhjiV%by^Z?" &¾dPSO\ZYYXV?<:D<(5QWW<)MOVRPE7 //:>GJ  " !% P0M&GNB& 9,> 3"8896P"T@)#-88# BS"XD% .A=*8G H][I% 1<4&] "  &D]_F A9 +   (G]\9  4     1B#  2L E    !n+#     D'       '  :< "eE *w~J *( xS ,HE A-LM) X8vX+  Px: BS)$. -Yof)  !OmaA^9S-PQ,w=l3at`#  dI`@ObR&A%*]Me 8…bx@Am*[kNTX@%t]V%  lܽ|KRV KWCbkNLD;a8pxR$[  fDT!|xJ?g[Mj\51&!jk/ 8 1ьz;8EszNLtYFT]/ )gM3g qx=xy@HyRQpyGa&0W2"$ >ΰH!վy@O_=O(x55Q\Q3&   HdžGָo28[T?3wpD 5K\@  ]5 N4}ԅ2nThE6OsE8H? yU ў|H &9\cH:Zn<& +P  g( `̈F&*#6: Ǭr5 ;˾]^*ej8 K"  "Ũk+"6}V4(GsiU3~e0@   OǾ3otϺvL}C+Lt{&cm" {p3)[ȥG'ļx=0JEWkl ) Ywum~sL:frOh<[vZnrn[] [hm,  9.'5,+; uK+$y; i{j;0[F` Q5XYYS(}3hD. CWX7/h7g; Fe`BMZ (fv9"K_O<;&"7kq2 h[v :oX Q! '/Lg! * >[&p "* %~; +0   #U6  )  $%## 3  #' &!"" tT  %- (!!b%# 4  , *i/!$ .   A!:J%!$#! $   ,b-Q !""#$$31   R*Ԕz ""\u$"  ) qɐ9 !o}%&'! 1 =i(b@"'&&% .$ #4-I+E#%$&Q%$1z@w+""! $#"$#!"4  NxE 8&)!q"  !! A~*  B}|N@!## %  U0#&  " %!Jc  !  jA  VQ%Yqo7"Qyb  b# " "#$# E;Wv6!8 5^)u6 !"  !"!Xˤ]9dp3B+! ' Q  ةX;i6 Re!  (# &٠I&T? +<=&#+   [^ Z:x"Jdp % pv!" ַc>D7U[ %p* f>! !  8ئX6v5  76!" !eцU̢:L  9H ! ); o?ͮ .   0;%ml/Ϊ    Sh8mk |r$lEr8Ąhz; ڦk @'zB'ӿ]HAh"Liit32m}    $   51  4           #    *Nfk^E%   )clz}yqR-  +muvM'    GksF# & !+)GrpA  ":HF%&Ixm<  X8 'Jzj6 o]B'L~c1   vmN/(Q}V$     xK*",Vg7  !CR+  zyG)! .ZyeF NlqplT- ]ouD'"-UjnbLEL  P}y{qY2 J:`r@$# *@t9';&Cfyw^7 >u?Aql;$ ! +Tptq`@"3Llzb< ?yCK|d5  ZP aroF<@Nv|d> AŽ~CR}O  e)RorD;@T{}f? BȸzAXz\$ 7fwyiN15QunC=AV}f? C˴qYsQ% ־`vU&>ZxA#55Lul Zn7-3>N[^NIID9+-<\mG ЪQfK>սw[tNBBWm}wcC$*:CJMMNMJD6).=eyc9 DŽX* s=ԴlW{x`?_svM,$.ѧQFMPQRQOME7934'AdttnW@A /OI-8St{lH?<5%!-9<84*m+fX) 8#"(Ai͕U|* JdzxwupgN*1AFLOPOLIC3(:;^7G:$ )7CJKJEDB?6&".9:71 Z'H<%" 27-"B==H|őB L]{|}||{ywtm`?'.0,!" &5GSafMCA=0! #1?EIIHHGE?3&#/40& 1HtC=HE<=Lʳe$ 6RcŮ~}~<}}|zyvqfO !31 ,eA7$)8@FHGFEC=0!!%4AFIJHGEC<0$ "+// qnżnB?IAE! );HF@8**9@FIIHFD=.!$)7AGKIGEB?9,+7$*$Ǽk@?G@?ASc!ATR]o~}:~~}zundS) 2ND 07X{eF@;+/=BHJIHFD=-"%)8BFIGDB?90 u  >ȼe=AH@?46;0$(HR\g}~{|~}|wofW3 GR%18BMUMFDB=,!3?DIJIHFC<,#%,9@DFDA=5-2 iȼ^=AB%1E:ONXbow{|~~+~}zwqhV+9KB -9?DGHGFC=+%7@EIJHGDA:*$18;94)  2 ƸU)9GI6RPIVdnuy|}~~}|}}|zxtodN(?J2=5 3îs-?5.MVWO<#7>CGJJIGC<+"!):@DFFDB=7'1?%   w'tǵJ8'І .HS]fmqrrsttuvxxwurohX.3>=\mc^U;&8?DHJJHFC;,$#*8=?AB>6*"IE,$e^ƨQ,dԹ7,"?QYbhlnopprsuvurolcO&6% JnqifaU9(:@DHJIGEB9)!"/44/$  ?P0;SO) !]e̱j@f03!"0EU\cgjklnpqrqnlh]>)/ +Ssnkf`T7,;@EGHGEA=/ /.F2 !!  ,2L6?y̯d͚0B3,+>4,2IV]cehikmnmkhdW-. >\Ƶspmjf_R4-;@CDDC?9.=B'5=80"  ;RZE),cʧXMui==F:0(5KV\acddb^V:  -SkȽ}yvsqnlhd\K+ ",.($HC /7LVaZECA:)ەL.^jgջnr0r¹c;?D7,'6MV[^^\XN'&CZrũzyzzwspmjgbXC  $7*"+ .;BGJIIJD:'mEA՟B6QϱU:)ʽ^:AC4(&8LSUUPB $KPazĵyxxyz{zwrnkhbZK DD&% /P8 <[]92>DJL&LLKE:%ڱsXY<)A29gսsfg!gʹS:BB-!('"I}KW`rxsrstuvxyzywurphaN'OQ#' !5@EIJKKJE9$୺ӜvHH@1;oΰPe*ȶO;C+$3>.-hRVZdkoopqrsttvwxxwusqlbL$BO6  %8@EGIJIID6" ֻUqEF=0?uZĔH2@ưE+7IB&gQRX_dilnpqqrstuvwwtqni]@-BH ':ADGIqF?1 Ӟ`XżiDG;.A{˶m1piDF<VrKLU\bgjmnopqqrstvuspleU13C)  ,<@CEGFB=4̅)sɾdDF8/Fƿ@_ŬqF,5ѿC+FOX_cgjlmnopqqrssqojaO'79 0I' `.:=>><61&ɫ,(ɾ_DF6/Ix)!PοD*~$@NW_cfhjlmnopqpmg\B)8 F|O'4)342+ DCɽXEE4,C`^<-5#æVcϯ+-HT[adfiklmnoVmjdU1.)"T<%  1"ajȺSE8+:?G%#!ãVW2KV\adgikljf_O%.%]f0%$"!  4<= Ƶ<;ZH7#p—zb*?E=(6NW]adgijihfcZA% 'i|<)''&%#"  2J. " <ϾR[R-KͰ~0Һst3$!;PX]`a`^XJ 6V.$%&("'%$#"  '( ƫg,԰.#&d0șYv Lrieb[R<""=PVYZXQ: Dl5%#$&((&#!" ;ɭl`Q 3qYտug'0Ujggd[Q;!#>LOOF#9 J}s?'""##$$%''$""$" "!@ȜNJJb÷liijjgbYM4%I!$# !"!    $}4iL:I]\XS=!#EϻrI /Pfĵolkjhf`WJ0 NN !s    1}Ҹb|k%=U|ga]U< %KūJ AVnÿypomlkijigd^TC%>ZT !%    :(OL̨V.)Fexjd`^T9 &R·# 2IYzsqponmlkjigd`XL')[\B U /='  E.))6[ĉb80.Mqsieb`]R7! +YW ::M[ztqpponmkihea\T?Q[W  W  Gt<$ BP9+XO;8cѹdlE(=T|·nifdb`\P3!!+K_K# ,?CPZl}sqpqpponlifb\TD;V[1  X !Wk+wiM[^M9;kʨMN(#F[Ļqmjgecb`[M0 % O9JQYagknoppqpnkhd]RA+HUI Y $b>!hN\\K7=pνB&.)Karomkigeb`]WF'#LU:INV]dhlnoopqpomid^Q62GP#%@@5 Z (oA!ĽbP\[H3>wĬZ"+:Qgtrqpnljhfc`\WO4*<\Y9BJRZ`eilmnoopWomie]O*8H1EOSQK8! /xj.!ſ^P\YD2B{2(#+GWi|rpoomljgc`[UN0)Z^S !@KT[`dgjlmnoponlid[G(:=Jv`ZWN65  3ph6$ Ǿ[Q_WA0DtW1/ACLYepponmmligc_XPAP]]46IRY_behjlmmnVmkgbV7-; .Q]\\YM3+,;-$ ĺWU_U>*59?3/|HOYafikmlkigd`ZQB;U\I $;MU[`cfhikl*khe^P)3' =Sza]\\ZUI/ 8 &ƽ|SSC6IM=*%vTOX`fhijiyhgfc_YP>)GVU!'>NV\`cefghhgeaXF%+CRghda_^\YWSOC,  ?& »q:HgqD$sÓTLU]begffedctba_[VL12IS5&CB3&#*ANV[_`bccb_[Q5).DNUZ\^^]\YVTQLH>( -Dÿ\jM.QѷHEOVZ\]\\[JZYWTOE&;KBPIA>E9))# +BMSWYZYXVRG$ @FKOSVWXXVUSPNKHDD>+ QC+ūL83٠20FJKLMNNONNONNMLJHB6(AK!([OCBHVW:#'!/CJLKHD6 (QIEFHJKLJIHFCB HSG! ! DXM /  W@'  SQ>5 ! Nd_C=6%## LNeKDA>6% ECFHGFD@=0 04>CHHEA?:% 9%,:??9., BM6 U?!/BGFHI8# !WBF *Z[3 .=AB<- #[I3Th_ 5( %+;BBA5" GXW+bO3 ".3   *;EED<* '|u6 4=B9'   ,>FGE@2e/AP[NGC5   /BGHFB7% g47CRl||_PNL5  !2DHHFA:+    +?MVZZUUTSM3  #5DFFD@<3  '.@OTWXXYWUL.   -<@@?@># &',/ESVYZ XUI,  "-/0A:  S%&*.HUXZ[[ZXTF(   &W\    ''1JVYZ[[ZWSC%  PfH     &'3LVYZXUO=  $c`  7,  &'4NVX WTQH-0'    $((8MSVTROJ?(      $$4DHHC5' !?A    ,*    '#3E $('    'b1   DVB        fh(  2 -bd& 40     8Vx[ OZI  *72     ?xx? )     #)r|`!       _zc$1   =O6      EoM    *0-     -R0     % 1     ,     =B  !  '  ,=(  9d/ -    KffjgX:    Iuc" "   "xaxrb=     S|yC "PZgzc7  ^d"  &   Cgy|_1     jh)/  (<*,Lo{Z.     (s|M!:   ,:B*,QszV*    4vb* #:2  -SvuO'  *    NauzrX2  4\yu_4  T+"'$  =%   3Zn{Y. ";av~|vl[@ #GZ5 {_?$!% ! ,]  88/@bw}U, "8Zkppi^C,  HcniZG pk^U9"+#gk&mZ1ElzR) !,CLB9M5  ;[iohaW7 jlaO)+(Ozr: G 8[2LtxL&   Csn =RE/&D`jmj`L# jrhZ.'6x}o F Z¿V4RxqF#  #QC_q|ogZ;/3Mgnng[: j{{rhG k}|@  F{ŹP5X{h<  \!We|~vqbD17PhnkbO# j{ynVPu{c[ 3ƸL8]|wV& pF!I_p}~yfD08Uhlg]9 mzrS6att) Z   XŶF;b||oZ2 :\gj_J0-HevzeD1:TgidM! nx~oGEapF F zij?=atwxtmdN1 /]nrjX905Pq~zdB2;UfdW4 nvz||k7La[   1=s32IWZO=@(  L`q|ztY819Zwy`A1;U_XF  mtx{~x^5N`& 59    UڂǸ7 >ca/F7"4Ibr}uX95?a{tX<1;NT[4 lruy{sK;Q: Ln2   wف ŵ! %}B4VjoldN,%.KkyvV98Gh|ylO.3TnI losvwl:BE Vzf"  /ځ?ǻNd]c~{vmP/&3VsuT9:Hi|~vlX1!" kmpst`0B]}E Oـ܀?ɽ`@K 3Ybp~|rQ.*:`xrO9:Miw}xoaR, iilpnL3# eb#nـۀ@ʽOr4TiutQ2,Ag{~oL89@[ilh_K3 hehle6)  $o\% VkȸK{?"`rc;8_s|tQ52Gj{vd>*:@5BY 1 fadeY$ 1tq9  >ԁEŰsW}jXq7?gvtO65Jmz|tgH `}H 37+! f]^]D =tvJ  TNTc{(so4!GkzqN;9LhrwzxqdO- C 7G[TG@6'.bTTP) :dQ#s 8sǺGjI-l0&Mp|}kI4%>Xa`VA;~]GEl`OIE8) #7:*  +m   A}ñtN\Qſg-*Qq|}sW)#.9[Z;a79HV[[XVRLD1   c  IĽRX) uƿb*,Uqy~~wkV/ %x; H^idYB'*:JSWXYXSLB, s   OĿ?; +ŽY%.Rjprqi_F%\EcxtmfM,$+=NTWYXWSH5  y "Vo7 OĽQ"$>RUI9D$?[bx}ynM,#-APTXYXTK8   4 F)&[HvـA¹:  DjS FZD<*;RkwoK)$/DOUYWSK9   5  bb$*a"+ր8ļ< 50 :KWWLE8%#4Wr|nG)&2EOTTPH9   Y "m{O  ,`MMú~ |oCEjrTPK;&"7^wmF*'2ELMKE<    )st1  &80mրA;N2$       3x}J+  (ôEl !$;HPVZ^_^ZP9%&Dk{~f?)$'58      A|zF\  7­zV+?G:'AMTZ^ab`[P8'+Jp}uY-  6S    Rc*\ !}Ƚ\cREaed]@-GPV\_ba`[O8)1Rs{{p_9  FnJ   ]|q9V  FƸIlpYwztpe>0IRW\_``_ZO8,5Vntvuof[G  Pvo/!   QfC8   'D{°}Mn6*c|vd<5KRW[^]YM5',M_a]P:7  XzxS  '*T '!(VǾ]XQ CmƜwb87LRWYZ\[XR>*-;^A  a}|`!  v  5)*13^÷DXYuмxa5:KQTUUTPI9 +wy   $k~xM   V  &qrK//606gĽ}D0 .c{ʨx\0 :HKKJF?*d[>  .q|d*  ,  czH/16.8l¾X9 Ljɩ%wW+/9:0%/+% P 5nl:   U ywE-03,4" (J8 & +  6˺t@,03+?to CPetã'w](XV8IPJA:-('  &- - [o>,22+AvB 'vVgvũ!qJYiB BFifG@?%\   !G8")*P {i:-50(@aiO mf`hw~qW 9jf ,?FLKFA33#0  [þ[,#/<.*[\hs}{lH;Y\8?/ );=:t*  lnƽo;52"  0~E;8&zQYepzzh6EY/E_bbU.&3|I   ,žj;83" 1 +b¼J5P!C]ju}%x^.HHXuyslU* _   M¾f<;1 qoւ v*27 !<[ku}rK5J +a~zsjL$ 8  p_<=1 WzЃHý8tb0&Ieqy}k5>2 Dk¶{uj[D   . (Z<;#'8)=nЃǿ|Q(.1&,*-Ois{Zx^+;Wrǜzrg[ #    JI,7E6"'sЃygaEHyye<+23(1Tkt|qH. 3azɢ~wl\%`1   l¾c;Q1unσWNnc(j<,22'4Vlu|yh0&#Kg̾zo] ef&   %ρ^Ŀ[/#Q<>{ǿKrz&=g8,30&6Xltz}~}yrZ!E^l|q^Ki;<   Gþ|,4g$.3è:ĻiVv7Wa6,30&9[jqttqg?VLbo}q^/ ǵZĿ7w'8#!#DqƿMbLu¶Y3-50';YcfdW# 5sTboz}q^ ~ǁȁ[tNA37+ +;;IwÿES4P0.6/'8HI3 }V]dmwzo\  vW\afH|b:JC5;9N|cL$WtJ106#(#YLYckszztjX  DNGs4srnNOD6;9P~B0"{wS,-48+6[V_ekptusoiaQ    QSdK8H~{nDK?587We. N~|~@>@Ta[Z\_abcb`^ZRJ N&  e<   tbT2,ukbV81. utvpleT8$%lipuutoh_M  FOblstpjbZ< $,&X5(D\ddYFG-j|V.  b4&1Jlr 0srnX7 !ip, AS  E]de]D% #vP$$ )@2#3?ZhgeS4 %q C~Q +9>,!->\mmk^A! SR`gZ?!+BanpldM. Ɯ& JcuwqjS+,GgqrngV8 RYjw~wT(-LkssofZA#  & C`vzO$1Qkookb[O0 5Bd~xI  .G_dc_cb7 0)4;DltC3DHJg\*^029Fqp>;. 2%+36Kvk8 s  !37Nx`.  :3!@5 +"36R{qE L?(   ,#58Wy~s`>       # #$0,PlrqiR;/ *IK'  87# ! 2<7Qn*  '-11  4o=%  nl   7+tv6#   H= "=:!  :'ei-"   s )4A>! "#NN(!   (D)    /7p0"!   "    %,pt4"!!  "GZA   '&V_-""!! *4;(     #>dA'#""!         $, !.B-$##"!!    !    'DJ"  %,%#"! &.$       "#""!   Fr>% $    !!"!! "Xr1$!!    6UV+ !!"!  &bS("!%     &00   !   )ls2" #"!       !  .wv8#""#2"!     0C2  7\0#""#<"   5BK3  % "Cs:'$#$$##""       C/  %#LH)$#$"##"!      6B<*  S"@aK,#$)#"!   +P`:  D5=8$_: '-($"##$#"!  ).3%     c93: &24F-!"#$#"  "   '!Z,*6D7&?!"#$#!    .     ! {?DA*x\ ""#$H#"!  .,$   лG= Tì1  !!"#L$$##"! $\h0$!     &.  μp*2ÿe  !!"#"!  *nY+$$"  -    /PWF!<û0   !""##"! -y<(%%$"  )    ""  1VB  !""!  3],&% $"  ,    2nno   !!   =h1&&%$" >G( ſUy$  !! $KR-&%7$!     $.9=# úQ~=@E&  (\j6(&%%$"      u_]"Wz@%   +g{B+'&%%$"  !    Yiq %ct1"     *ZnK.&)%$#!  4V^=  .Kk+ (jS'!!  #25,'&+%$#! %+! ,wQ7 *tp1""!!    #%&%$#!   T@1}j3#"!!    "$%&&%%$#"       6>H*"!!  !"$%$##!      $   +PWD& j!LZ-#"!"!     !"##$B#""!(T0$   'Ti}zofR0~>   Ita3$!"!!     !"#Y"!!  8{a,#!    rh|ukS/8V[A -;.#! !""!    !!!"!!   EJ)#"!6    (Ul~xlM%     !"#"   ! B $Ou3%$#"!  7XtxhE   !?"##!      'ZF(%$$#"! 4   ,J?$:^|rS    ! !!"#$#!      +hG(%D$$#"    *090$?guX   !"#$#   !Q5#   /wa2&%$$#"    !CP>##(Clu[!   !"#" (no1#     8v;&$$%$$#"!  Bcv{tmW.>+Hn}qZ&  !"! -z]*!!     !;xzI(""##$%$$#"! khz[.Y ,Hmxzvk\     5>%"!!   .L@)"!!""#$% $$#"   9]jzY)"0Iekh`  @X)"!   !##! !)""##$%%$##"  9]sT'$,:QW "&    !OT)"!    !)""#$$#""!  !cp\1>h}R& , A_+!  $_q9%! !   !\"##""! jf, Fp}O# R|X&    'kG)"! !!   !"$!    ¹b*#Lt{K#0  !\~<"   '`uQ,#"!     !  C¸]&&RwtD  !%eb(   !59+$"!   :!  iŹX$*Xz^&  #(po0  !   <  ŸQ".[zrZ( *# /z],         :  BŶJ1\tz{wocA  %d", ;t9"        : kĴB.J[\J9J 43 B{{I& !   +W,"   %݀$u-$NrM =F4$1*(6YG)   .   &$   .  HB%!!    k܄®siihmec:4      #Ql, !   'ۀ܁%ó1[)G`hrsnh_E      &\}<" !   Fۀ݀ ±Cc%@^imh]̶k  )$   *kv;" !! \ۂ%­q](!XcJ!A^bYɴB . $g@&   1yU+ !   Aڂ$TmLlH|EC?..ٞ=:VĖ(qjtFI^xeB =ЊD e#l}VqW?9G}`SC4_i`̞WA []MG썂1}ǒy1/m$0/P7q嫮Zۇ X־?aco:QL>('e@泠<|μR#t#?Z DvWE?YP@WW3?Y>ΏbGD1U; {S%\&a3>CIΓYDR¦?)g}b GG1x*k=tžad /}2*7O;ɤI6$9g^*ƒ9JR4VU),.F KܙzPqgVR[iYrߒԤ#u#I7%BږQq+B@R,\|ʳH}JiapH6tvmհ"Ԟ*Js$ :uﵚ30|7W*^ n9ocBx3I 0l:ZܙKi egn7C nNI; h4W ]juKe+ pE7#2p͙GhOMqJJwdVҙBgTon#.ӄZ֤59_0D+e ā_nM `>]$ayQA  3}Ҥ.g }jg؈ѶM3$E1s"&=+3WR$x ͂J0?qAP?™U\$ qʮYgY!-9Zīsš X//J2:Y/}= ?@W#@ ӕIxQ+Qz]0H+\ч?!0̰GeHpx"uΖP.I( EJ_O7/>'Ǜאo".kfןw~nԢ5=rlm>rU"&骓t?J5E _p95 ^Wl f70ZJuۘ'wY#I>}L /tˍQU߹ީ E(p2{3*d4.-KG>Zju V7y[O@Ƿ8~K;I+B"};ڞe_Qh!\s3&YEX/B"UkX-ʋ7?+($6WU1'5 mT @y7 [Z1u%XQ22oh”ALb_ʩ&`EfT4ofm֔jR$"MPm)BJuL ҹZ8e_A @diƺ4իcyrwV.x7]F=1np!EsQ4W뜐Me~!,Rm!w1+g #rbI7[=ɖLJe.l A2}8Nz5zj %YC/`DV!_fZ@}gh4Jk5zK*7K@e7~B *R7 KGm]kH{4 ^cjR'_z/SQq~V@稸|ם$4 ZƳMYV t$߇Q 犆\0upI\0E#_ûgӓX(ԻP9x@K:]4 ;׶_ڕZ қO-YڇHWhY?9̣R  0̬w>bi2E)0 F|[amqf^9Onecqd`u2bNRsBxKJA"+p?B=yAMo0j99]Ҡ%J#Md:*DwW-3.w}-6_7b"u윣 5|60[y184\ZxJx^i =fv. _.C6eeI'Fe耚/zg o--^`d!FXzL,C/~pdw;Gøxf c/Y>4kK<ߡ0I~֣̫])c" 9fL"↕vö>v9P ᭄z!Ƿc. Wڿ7ZZc623AU20E uИIyn*L9 $CJ@\+lԀ~Ú,!È16A0٦"zHKfݳ,Z KfzkEbZ˜' r5*Ȉ>Q+\: g/y*6͉) e]E.14#ְ7WaB~Z|Q-kuz Nnu@H36eHJeKhN{B$̈́lv_.Z~7x?DT1Exj@iʚ ,)qmTw)Y8kowYB=Q-0hRO&JBj]f%K?szGQ}ґ054X"AJ#rJoCi{Xї0~ $?M./mZjtQf*|21n&6hў*ƫcM.:{ҞthjFڄ4IdF|2xe>NCfxty~xZȞl^ⓀAç]b W!ȳ}NյʩAjC[|!`W_z";/`XC3ooǗd {,+S^>;FUd}9)>(ELqPO;@ΘĈ I ?6P5W "g Ub!Sn a4'F!jKzKQ Bmi𢻐s砧(mѴ%;ZC[ ?c[vG< mÇiԂlu@G]uӦb&@*{,NkP BXw'҃_0--*U}Mn/Ji? b'N!&9k+mӛm|jN&C0SSQ:2]B`UjtiӮqY5o}rVթb|^/spp鋣GKPr9#V™Z~|_Iju:˚ 1dAL>1,|LUS5~5U"&q%ޗIn-TڀC53WN!KwBE| }x-yQp<5 t8V<ƟEt B~=s2ͧ.1W^>;I9'k \^ AH)5s!Io8*HNhG|lr 37TƇŌi dRۉ77SB("˙ ]YbDK!4)#? 4?nh}M'qJNt|!6CTTu&P /&}᤼?|VޯF1a͔ mȭN=kPfFV!Y&]Yv/'l|o/_ƕ# Gy}{=r%R{ ( "J0"1)ӫS _ #|2K¹[$Hø#g |4Ag(sg~BAOw61Ʀs30|.9R&#edlp84'# AHJ=~B]2ЦU?)M3,1Ә?u#M{qpd€wqR~snR4eB-4$_)e:DJp#HPuꨃlס 9+۵/Ŋɂ*s*li>sMe c/"kH:|~ ;̠4 l 2vgTrjU}Nv4ǣS]!ZP5cL*lbJx^=eq8E Z_s!| v|(@2A:#7SyI|>J&zPpO:[1(瘾UxdEkVi.[XT*4WbX-3(96]qϴxELOb_ :S aԃ-msr듙;&x!1^>(-%PŵB^DұeS<,Z[̺{gVdi/6l ,,r.p8f2śZ(֕Vі^DbdQ.Vώ;10A/^n/aZW#tz\Asst˫IȎLi{6*1_5J@K&Q)-xDIefV1A[ u]a Ã6o)Pni>qR"21eI5f8C_t0 Rٔ.Sy&9|&+wځZd+- |G%G #.z㕗E dSqROz;ȓgg)k1/ =ZőjebA-h`Dr\R~ϛ b{jTB61h:콪=`>A =7U"5)1Ba3OU}\[ƖjEG~!#k2qc*cb jvLJĖlBaDPl(k&= g#@&-{%~#,E˃r"9,&8ݴ~P636yc.>LAE;S{\:L%PUWԤT13{y~&,z_d ;iCtX#kȎ{m~~ȅM}\Di| -`yhAn'ٹ8Z?hYLwg: x)$fΌa^}z'NK8^|L0g½6+N1z.(oW~ίάu1Xg$"AԻ;vNX (%|0!kݷj}2pv3ۤ3 P/?J'oO{KC~{q4-藸2Ќ3_IC(h-nwk"> "E|&"-K8D=iepU"p[W2(&aZh&$kpX[7ƟÛ)ufM:.GLP? k;|H4Ie1(ΩoG3q )v8k%OC! }o]Ǽ?wi' Eoی9~~ QS F `f]%G&5w/fTX"i _&0Y:[;s?'S4{Z=t ;BWQ_'Gfh?>\QM~tNw Q &x ]}۽gӂ&xR2~1lgKƫUVyɳvչpn,y|R YpAD4"^N܁@#3gx#ݑxkRpQx Thb\yg0_# 9s+IpD>sE+<ʚa" CcpЇK7?[@޳ƟqE|BI~| "Ҷrٟ- [51Дc07y3h;Nq)qO˄m}:FT_qKd@meU1AE*ެ'vNA;㐨T1z2䭸%%4 Yڭy6bq[|)4MqF(KMxa7CN>8?37GG^& zc"*;~5SYofUQ ^p_5Ԣt F8(`h΋i T@ASr7'sKg]=ƥD,p 8vbOA , 60G$TUiSz"(w`,-0Iӓpb"" }tez:fi mb%׽`B6Q} w|th}g1W@u@JIg \Vs>BO1M/?dpC/Fu+2;-%&$G#h}rJcߖ>^ҁEkgLȈ  A0ozV@. CŭjKϿ" Y*k!y^ -:#(DQi~|a5 R4QjW#`7cOL|yN:k %;[DJ7"UfV{fԊbQ?ill#F=5]e@ޜ6{ Q8[3R5_G |&_?l!^W~ݚJdB`oDž“n_U.ù^7qz$#W+Щ"b=5zR3*Eͼې=m0d<1B%x+ieæ`* KtBB/TL+X)9ջwꞏ9/8ɸq1!HGKJxF8.:!0یc28ۮlꋪQNMljt`e`xݘMyJ* wEɸQ MȈ~-]n܏=ԸذXc+)EA>wJ7~ǫܢ|`S^_^ԉ 4Zq5|$Qy~۩.&^{7p(pe 7 LТ@@تXLeG7sk|tmDF݄iU^hiBjɒ;]~";)h⽝촹7<)婆ܰa=eh5q*K$. bx&2L&}";a{\ 1;DZ#y;5LUE*Wؠ@x/@"ʐV݉9ܻ$f%[l(u`H9etښy=$3! dz,nO9YkI8/ljWR; =x<}O+"5eI?9҆sܧ HZjsݘ&~e3c (nn.,> N_.3)1#`gPC(OaQ*i6:gc{^ eSP 7KI&dߛcw2d0*zˎ05: KO]gMe~SXݝ8ueF36Eѝ ]r<|~# %FnK8rN _bM9ϋt3ӷ-[UPį[ uVDAE͠I۳IP2Eq?KZdo\;T;6,>ھ.:K1 M=#ȃnir QPdl퓬  47j!/s%Xh )oqv%Y+Tޟo=MMmt>R? ,)c%܎T$b?Շ|J}a 7t),5L-\k9݀X 2@8Ĺl 77YSWnP ' 1. +Y}~RW9[H Q^= =ۂF4?IUFfu#fj1T}E]V9{4H%|MmK-JoJ ckK%fZ8^_%RP hajug(1)-wC%Zy#@:D0'nyK7.\EWI9yՠN\/L؛.dq0Ĩy"C_0 ^jt&_#CGZ䡬vޙ_'fJ~K4]jt?jv ECC~F2n1W 좆bҶ}"ŤZvo+{‰\gwUte:82zɴWbȚb!ar7pEF~:l$:4\zZX6RZ/Qϔjݱg@ [PNpa?ko+QrARz BsQi[".A(񾯊@]xHHhxq 4+|4?|?x9q,M8,ŝPGqJ)Qץyuչk83є^h},YRM0̗L+Jh߸ő"NhʬÁ p[Վzv/dMDq-ə(|۠fftGͥXXAmd$ΝZ95.Xd/K FbY9(7`sxmXwߩ߹xz2a^e{J)Xg3}ޱB>DұB9ݱ7+Oѧ=z66,,rٟW,xbggOJ) jPvH/O96Gx̢wrA7]BW, <מ~1{$7{T^ؑc=N DGʠHKGt.^orN0cuwǒ#驫ԍfਈj,MJ} l30uC ݛc'M+9H"+>G<$|>}Xl$_}&uBF%OE/\<LjPB _LI+䛍4<~S9n$3^p6KK= @jL .m}d.۷rRS@SGY&dE--w8qA zh#;(IZ0іGmj7v^[vHܑѰjX@zeՒs`j]wR$抍)o5K>-] S/W zvfqIԴ2BLPNH2/g@ @>"{5w9K1#.Wqi-;zc`oOp>R%RakC~'  ;^'? 8[+ѧAukv]ӔDA`rue{dtR`Ȣ uۂ@/oO>}k'|{Vw8s:I,ъw)Q[ GCMhPΞ4ڡDx5ors}!kqq0_.4g [ʨVS%J t \Sg,܄]</ p5F=;&`UML]QqhX~E^&30zHN,+Գtz#v()~2bnY{t6O18w?=mo?纾Q[3KҺBš1 @ᨨIF5A嗢`m9F'qew8V ʀ]~Xu!.}SNJBYK # `Gx_Q{z*bG z' PAi .W[HTg6 BkJl: ZMbg,GWrLLEF>IG&p{2--4.|y;2߿iLV쀫&lRn#r+& p;aS涺}۱Y`? m#(%Ph\񇱋 "l$႐#ewdoՉysG%=6i8B=' %qMDielz{nH!w@ǻҠNьx8 gU=D`.*lig)L[QLW+PG _́+?H2~B33vz/ټbnfv<TT4pM KH Wk%+eEI D-銰>1SjQa9 wI*&d^ 93v5=.V%7c78E)g'T{4;spN=S)baYh١7+x^^^; ߴnHφ.Oξ.QJmQbQBHx$Ppz׳ h9TXv'ǃv pƾH6i0E4 #kd£a5Z Қ>!d'T9k|u CNr oD@H|c DMO8Rl]H6qm}ʎpϝv 䘯㬌49Emq8+͋vwUޜL ­D+[Fy  HW0cvdU:_yl⢅۬o|#)_L+hVnQ:|~'eN>6ѻz].8S<@U \$Tlx.F;a4g4 |٠(,R鴣LKĮ!>sRG)=0ᐁ:!$r5ZsSq(V籄C:NUD)ra<]Ux„ˮc?13Z }g>>h"jψSJq\pqٯ)W>u;/f y1@}5ϩfMqURsM$ҵ"gy.>A)!%)$ Ե_M)3^(?5{D"`SgG p\T{WrCl`dzȼmeJ alLrWtv2iUht)Eƍ!}|8Y?pU*y>gO *h̛y&*WvcBFO{w8Z8pe r'7,PFoQEYj|Y\k[ he5Ⱔm nQZ"釉R؍4)DzxaPps &‚rv~ۊN- nݸ9;ߒjwʶ0]Cꐌ5&Ĵ" ]Y<;\%1& [9lAf}-3hI1AQ14?w%UUh,²s," J`NЈW_5)P2 xyU "mgbw;REbpC)^ʿpu).gs"*RMI}b=276c[q[Q%JQY0]i!,l<:fR=u̽(ɕ?/GHc0L&>")yYQh}t̰lϹwg-{9Gt0  ?9>۳ 2wtddU17ogUۛIIH`|~=HU?"Hn@.ON̈́lf- qB 6XRoù:d $+!^cQ[vk䙱n:W{;S/R+JttBT^sЮ@|lYlTN8cpR>؁j!tdz \^Sp=l3&_ \Lv81=Jම ]$@7*!\J|,E+u4D:B{&mTEu:w[Ulg+݇eMLy ]sWsZuK*qE8ÜN[.f_T9 w3a Qe88!3(Q_ycsr&=|YaVKj=y>8%FXi\ѐ {zSshS/t)lɼ8(B)]Ϙ]4-*7 ÂBmHtTW]L$N..Y,T}?yPnidc!뛚- ]W'G` U~ :-w>evmŽut~#B)Lz-pы%<އ"j{hǘo[cz.{ DNiM#և!{)4RLhC$E#3}}FIC܈AFRgv@, 9!;e0(|$#DWW~Hd iE:v,펱Xw;6Tp^}]71 tṥKljf*g6gj[sU#=l bjgw=Ŀ\g|Hډs Hg@KSzGLDa1>5/h q^PF:ޕuhM,yrz@dRu鷈4xp[: PIuwMbru5wMx٭0&Dqy9;/7'y6 [Y8SUwG1\nSW_uľY pAۗ (WQNΞlڀ3&|hӳv[;Vxjd !7XFC Mc~~6]M8iÎ{ d);#ګ2^MT+6yBb3m۵]ȿKz8@t5_sE`#/RĵR&yZjsTq :T:/Bj9,Ĉ+&9{;1_J>f=6QmFx4}LB +v$PӀV37MA #V|/ 6HnBjW]d.rIG~$1'S*XΎxAIzgYO?lIo&5Yr;ͩv_]ej xs`r3TwB>/rSR3QV8؜:4SO0|d֝`ڰscesv-:D\ :$%ȴ@`֪M12o* (),PBÖ@Otiм X-3a{4Ef-Ю$ezu9T<4ubs R*)ZtV7y?ec^MҘm^QpǙ8#M%\ԗS>wzkÖ};qT"Uq*3uo}ąL-^hh,)9 0(6cY\ Y޾~c`Cq$4\#%dҼY xg[Ztoh%.pPt6SN+AfdGst~A Z?>tqOѠ=к!@D1 "pFWr[Qi&o֐Ȩ^_H`Zdcjx o0AF*4 my7?y\sk>^. (о ؾ({/TjuQvH "}xDwq~Z`xwX0U:-$Huenu:X@3.xV)+ pW c9% s9 }[vi#V`Xķ5+܌ǓQ1{${OŅeF,Cٮ{Фt52&)K| 3O›o`R7 !# lLD*BLOYPlsI)Q^X1ԖGT264޺Ł7aReL F1I\5j-0يlaʕ -: \a{4Iv47V z oU8LfdgDr".18r9{ _fL7'Q_fAJޭ9wۍH_Ȼ3R Q~,x?eCh*@jrW2:+&x 0ٹ b/LuI{rԽVoμXML?>Q̒ovQ]ã%&/!Zu㮳ky!˼:XVoJ:.VR c}BKW_(Y:..UcSc B*zjaBv EKdN.lBkCl^&rxf$a (o:ZOQHBX#Lo߶ƹBt?}A#= |3λѬw~%Y;procHwT?,Y}E=r`;O&r i[nʏŐkЃ&M+d縸i8?f_bbzbFjCӨ&$7Jzy)$3+#hȿ@q`đYS?a{M ^͢s1  O0ze"ɓ&5? LsK7!ɯbq ,X"k\HUwә?y*~K8po$cJX֏[tIL%.*3w`X]A} 1 dswLVASGsyZQFzAFhMOP Ƣݭm3C?,j'0w?!,G4Bəs666r3Rb$%3Oڹrh(N0 _0,Ňm  VԥFNLd:D!. Bq|tw԰~='-BeRWi<$|%/jK$E"-Q%j'Ch\{ܓ۵ʂ.zN`)j"(qs #̦{/NDiJ'ߕ8 H Ǿt8k~#nI%~* GCx_/3SBp H8Bv N | ü!4N.qWߴMbL3駤p`X"#L0|\no[|R Sv(eUZGL]&ĘФn&nTЈ7>#(`Q`#CR-θj ܕ>$)'H3Od 3ʮpu U5T+)י9H\3EA% ޟ?uö%Yݩ{cGh[Cm[e=y`ۗnӪ -G}QM; ™˥1m,[{( J'v:y܌s-V4T\!2Y1snet48r['68tŊ. W ` ly1}LAP \U#F?eSI:h3SHY;pC+4ov{ӬF-)?%Sj֍5c=$Et;+bhGQIU$WLz|Z2] ^Rm^3dPlhHLMitG7Oױ/Q9֑F,40`6wRKtOlK0~F)Z}ϒs/2,$Aܔ֕)4JHcLE/&[ޮdb5Ywr YYjj""BBv ]DBۼtk֐;]q}L?W~K{Gj^ _}D}nfyקrQuZw)0Տ+JXbbj3w,~{p}g_KU<&p]?u .pŞfh"Q`E4."k,_Jbx=NPZ5U;U/)ԚL+4>ýMW S'7\GSW;YShb%{b(+s7; RyF:&UJ2I]Od^FbI@TB>C,g,{,_bs'wDž 1d OƩBRt#c;,:X{4&gۦknz4?>K7+h ŕ),M쓫uCzDPj:)\Ay.h;unhBxHOAF̵,Q=p;p=T3H@w# . ߕpxɘ~o0թ_/`G װQOlu_>:tPoӅH05oWߣ]L19wiaq:ic~XEh'ݵq(: `tGPR9f]س:Ig=7f,wu=lV w]GPue6](oVY)"vfV&4筐DǤ/ 坦ܯެj0kzG[ůϘEw:Vt!m2_|]&)ur h'\2=CҷNoHMqo55tuhOBo(F,69 4"^xe=GqFċ<@z=@]x酽 HUs_?=P&/)H҂a&y R:> WBu& rROO xYzksE- r,apg ;5yfoOTvMkKNcN'pK@8K}wt0?Pu盧䥲t쫈!8,uܖwUja9A7UlD[fTО!{'+8^OxדͶI%l iZzNdw wNqPM]ZrūI"kr-H]TLwdtdD=M(RDI%J#rƠ>cكi!HwCmNXˑ]*haZ%Z&I_(2"; $遅H*9%RPB ?F禦ݤ#p7nLXMEH >NaRr\#23 4M `t|8`ͨ IRSٿ2yG#(Dr@yݮ֢2lrksQ?\l*Jzo"ނ OG 6Su"_G^M83 ̞2cM(f FZQa(], L's2|ZJa% FmGd5M݆x%*b1D6a7g dՒ/ q&<$&|kN2ܸ߶ٙty,>gjT\3 Iӵ5Hn5c+#42`IcA{}z!BܩA5NBtͼq(w)7+zbIJ io_*Q?q5+\9)  pS +gQ%ͤ;(kX%gcm"Qt]P'r|/DbT"Y7``FEżu m r2S=_bb;mJ/Co0cqJU+ V;]$@UMc+IC6t쮟H+D\?Z:'a'|᮴pJxGfQ7!ZwߧN'0@`.Z'$48[M:;ؿX~ȋzoPE#^M;+.\Lk+bqxlnB]hWo;z7Q.:jɒc)O+#W^ _ʛS^y`-[WB~/@:sI\P?!|&OE_ع8S>-P*/)H 9()%Ӷ,lśA >ҷ_ mmKhz:q~揸CWzY-/~ GQ2M7gEXȧ4d._; jSl=Z6vY.*[Fq[Dή"sXCf|A* <YcE^/^茱 |Ӯ'}:)*%l \vٲ7 |S у$w[`f^)-Dz#f;WZQ[ޱL_[qȳ~4nʓTdUFb'*r:7Lz͌b j&j {M>{nqSg4^W)vx湭N9joE11uW/M}=yeiagj9oCJ4}<:aUd[yGpb z1SҒ= eRGcX%r2]I,ՖּkkHn2Mʆeҧt܂, G|ˊ 2'4(i>+<}X+-cUf,PpN7h~-d=A(Wi܍GsaF|QhAi9![P/\Pk}b^goqFL,ԇ6ʩ*Xk 4t~q76m۰| ,ݐ 'MTA?xmcј@Oߜ9e~;*Q}'6\]^CaMZe/MXsj gCmE܍V ۱ 2kYa0Q>x#!sl)*FRn{|?8-(J! O'Q} ޕ@ĵk:>#(.YQr3`yô@X p N2_55d FZ E2Sh0_D\f3$-B\^q3# kBD^ex@q7d,Ul-2o}4a#㏠mY' ;6v K_ ad* ^(4*ʾX csp"^~ܢb\w:竩J-d):ޏ٢Ni 9}Z0k}CqBPHvR|߹CW?b4ZtlŃ+w_.,\`*f0r敽1pɿy]K`SJ܂t5үmUܔbW߽1kyricǠ_ ֶZ&J}J 5MQ QqeK^S]*@(PH{bh#F"b?+6鶧)h_quxTQMwD,SO $mTa)*'^5o4QNM _qfH2B1b~){W'm1nu!jٴUÂeϚJ<0tKC$goe an?7n~U  +Q& +ꋥjQ#1O%*$|_.RxPc݆Po,}j/?#-uL;~[xW{jS|;œ2~#-͙uP8 Yk̶nqB3:ONmHL%x._>?] Zg~kYU^/6ZHꊰԧEbm,q~$k'[)TK2 йnACz% :Z3?4WhXt=Mr%o_OD{<@4N8VP1KbK`ר3 ;-N{:9WYyXEZ*r+Uc8JPeǽBgDvldl>á[~&gMغ@_]~ꭀ^k:G r3T8OxG\⓺C0<5KnEKzI1o{UISɳ:ոɠ ۼ5NNauYIFjM}|s(0fPP-۵NLo^2LcwJs<շ-t zuJ鿛(9By !|,ż9iC:Pߝc?㔅`al+Phm.xo߾fjR>xmeVVeN./N[ՁAJȜ!w"VGҞCӏԦ披Tܑ~M~nA_yV@DWՍ|(}9~"ǎUϗ< ~ss=nLLwrʁӴ 8MSm0I_I:FδHZ%^切<ŏu ϣ,&X6*9F ٧VV1b;<g ܧH!ti`8gο63:tj+d&'yK>޷z8-JQu%Mm9]n̠azie". 9E.,|} a J/})lv4NvRSIe⨔Ed. QYvUl cRgy$L{+'/S!76* ~<"飂pT[⾧ w{[".1c3Ѵ.ċk?Yy As0s8sO6-{'=\q+-AkՉevf-M/?NGڎf/u[Ԑf-L[A*PFL!8{$? ?bYܪY$V7RP;H=^  AO*"JrqeǴ-O􀻘 ! |^Oz0 Vm$5ԍ,!Qي73_MCupfcFMxX%B[/ K@ٖ4)?6äL1Idhe6!pIr\*aW}Ցkk9l>~d QLdKTfYtNgz8p= sep-Gw4h5t,Kp"Yp J}1T?9KyeOgnbv@tܫB#$54B3<1G=#k[-[vի ,7Ym.xDg4hڗzm<HHoe_44hq梯A7/>極&ifZ_ɩu95yV)5Jd7GC$wL+;x󀪻з!,51e.uUyr0wFwqۥEkyk?z 4 ~3FTM^"|8 (wFqzE3 m&(Q7zV0{J -]y`RQvDtTՠzYc}* Ъ!O$ QV5  齣{ #[+?a||{$+-b;G UmdzȄM=\qOIXS^ b)d f njqq쬳ű4B}B(JW9]̚)DD |lRUK""<[]2 ex "Hmx4"҄cYQ,љs{1tmqGHC*Ad8>Ǖ[8Gy3k_&xgiWY}؇~E'yL݃ _v╏D$T-/8F Q!;3XI\!*uB8tGUͻk`>Z-K3@ Dչbdge`dkH, sX}*F>A㣎X:W ]}`QT-%[1fJ4xgq@3f.V'Sl0d4h %YXY웃s,ٌ8+G`_fs6'K\iŇƒĨˈ!KSf KWԋBȫ?`c"L_T^d 7Hd7RE(!Dϟ1%BA;lioMhA< jԙ\#ּ FO$#Y[(;ʓQ(m:gKvM85VOUZ,eZ]%#H*QmS ׊5mlSAnf{5WX4͘uCClƮߛ-}| 4)]{n$b jmMn0 :\ޅ>hK!AU ?I@:"Yk-G)*&ۇSG*P}iT[]OJT]mꡓpjVg+Mv _ʴy[i0J]͆CI}3 wկ3FAf7{1}t@WlsK7^E\>83ɇκږf"Э=Vva?P('xZvu( FzQv +; KzwǢQ=E;Y#:"_W^d-)[==^rDē/Q}*L=eHCo+J: R-E&83Qo5u'k;YTGp%Gd+A:"33r@c`yeCZb}6$_$J˜^EHg#\Mn puf:eG\ԁtERx+r"eb k}9O{5bjfN =w:O-g]VaZU$F$fHJWx[=2Xc9b_)F[N.2<兣SSC^F -׶Ȑh>ӻ#^rKD*UU#W\ck:b2$}~dJTnQ`T' % >vیHO&$-!alh忛 Dʵ頒1l $U˪6EiIs,t&(}zk:U@%V'3:83.Bbh^*d!=KKQ:qyؓ %۽:cfXa{,4r!<2G1/wJA'{ЯD% d^ fGhºq +.<袭m0l D8E<#%6e|iLšJx.\qlۏAg)}< Mχ4κrCGKo:<|ئ5xac@P&lZfDM3itN.8y7u2~mo`/hL3LK^*b4rq-,TlйD1Ywx[TLU4AjC׷h8/{egoQ1];IYsw>Haw@MZ{@SwJH&K)VWkQkMS RpJs#R{NvL> veMf5{]3PF|ӵ#RFr"gIl1v\WT%Gb{;bk ЉfI< &N(Y B5';QJ;-Ǣز[IvpP#2So5?VL0ieyxR]IJzjnWTkp|`qC%,qqhOBN$Rn4KXFv:^ -.CtR=̇0 'x8ǔZ!Gg/AYlߜ8c~kC8Y:0{מD \)l!g:sOn"PG-ucOz?&պݝ_\e(nvj#xmy yaeP}U2R'ӪjIS)$p cv$i|hFC2@1ңD=FEk0&q[@ UPZcoI;a ]M`mLgQAK˪}uV'[ 9{5`4A>{]>\x*ک R̰'eA8ou]O2kL7(ǾF;F=s]=k79`UG di^Io9/{^a; |ޒzA#hOX7| ǩd{b&p"Jfb ͉$h3!sA26Б&[BaRZC5@5P$ \ #odd Xg|'C੔39@r#4[]8KLP+ x'u[ 8q!~ f~ \Jj* ldvQRrΜCvDoBpBo]*h鯒ۓAKwQD䑄DxҊ<4o$Vu]u$u(u!,@SgBN=RdZZQ.GzA)dq';lhUqUE1'-6Auϋ H,:^})}js g}"j/3eXǜ<K M@h5&5Fy+ ?sC[rQ3Ŧ_g1`2%<7G^h)#8ȶQ8{&1dϑe`փў^]3OAPfNGBw]lKݎI9=c1I?7C~1qYZ$Tu}PXyzp}aY-+dͷ]_N1G߽'Y25SZE97*O F?_@E  ֌{ʧe#}$dJRPJ~JQLjk~"$&讉3Es*Rl.ԴrYb/ۄ odxkx؅&-c)wVKְD 7/&FfAXyQ?G%Zdt~g9jr'kNO%jG@;( FB֏4's7*g+i1G5o0^S+vYG{ak 7\FބV9aEl`wꃵHx0"ڵU}; M @ 02+EZGFx #p}/g+KB3d]/m"wfvUFqQ-1[1s5zwQh\ ۆR2?Cp&?WCJγ?B&|P_ B1ú0ʙ*P a ˾UbjH.{ Äc{3;c0Y%^ncAFjJ)7x%>xvh W 9_:텬eLo.WfVzG0ҽmHgjv΅: }BKe/+$xw`:E(̸Ŧsxb=9<M-hʼɎ0C7:XIBvKjaMK"cRL*(xZ~ o9C1[=%̃s^Or5]6od#x @(լxUY92 UyڊqѸP$ %VI;4ANI^O5ݼ0!y޵anZԜ;}j{N/ĘޘCԳ蘴!dZk Kfݞu^)2Lk+Z{&n<1P7*"N0ZeE@o=TXv~*g/s&CXJg6ŶC%K5T`…}vE1XjixdEUDޘ5bp(>ɳ털᡼ 6EN̬J8%:r"~{rr8.S8\Qg|nƢ;Y.}]9$ĩb%)uKuH*Wxyפg>+> IH$ ͏!yN˱O]Ĵ7v:$vrޤ& OP*J:>Q+:`~7zqQu\GECx hwtd4S'iJ!12)M; 3d|TW2'CJa'R?zd]%Saۓ;7`54u_3 װ≲xLk+DWVl芫^Yq[uCR>=vP^Y#q `gAh+vB7R)H mε&$QJ@^IMz1-. cWvu8╜㟠\7ZhKQzy08)~dЙeOW_C;U0&ob ߋN>,j*W+=S$Wf{o29Eat䥭ݗS-艔Ġ xVtq֡+̝DNMIq}zc@#voq8ictalPեC>V/Ƹ߳DaƬT< Z7q(P|=v;LpE z'1\ą[< ꢛ`rfxy{yy_8u1' /zڨCh*phzC ێFb@ş/j0(ğ"Uz ZsbX^@`Qd1\_?pxR+@ֵW]hvV ]wvȔ%r`pxᩫv16ׁ<țzBt6]tu9Hq0z3KEHn46'zf7)7iq4fnQ׵RP>9Q}pQ?Nf; ([YP hyRxju(^c{_Gb7IlJ۝TB} ʷۑt1JE)nR#: A8?C0G$0T|Nh[gݐk8:mm嗄3ӸH *D֚^>:="z癐 "K-k'hf>!nHR1}hlwRY?`і*pH٘WsY8x.oi>|9!3%:El};?n|if+.T%A- [ȋ oxF3ݹ˹LV+C T?JɎ߷9+P˕z`RR  ֋notu/%!8fH[@j丒@?ܗfԛ2#ƫm#&4?t}I2U]9#pbF!j'KJM IX뺈ͳM4Lݽ|_ii!%zVm؝m#MxZ34 K1&m^P:Ts(5,xk h[>: _T%8հSPJzpW n 6b3-}'tQn:B61|rz ?%፨5 V25 )-#%ed_n!Ql; sEKq?laV>gW85w]0R5d\%KiAŖ hcTU귷GUQ.+h8,O`NظRa{&-є06Uj> wW TLI)!*8Œz6˲+vbh#sj}XnV?фL7(t{/™VW<#MIaFeOD^N&E_Fނ8@X|.F7fjYsvg}e3p ڙ}I~</l+Zݎ#㍴%i9R!g&`"EdqU06&1N9sV7T;N*l~s.rv-. $Y{7٠4{H`?8vQ|#@[\#Yx"VO#l6i219 FY6aot{N!lōz~XH]N5qiZ{||]Sط`&M9|̒rZslp¹I,{&R4-u 9ZV5hꇫ h|5PHJ*+a(֪ I s(lM3xo0B:B50}P"]nC/Pct'E/(%k=;2{`}Ͻ?oV9{aMuX/6!n@H°9>@Bx&ouׂe}28S3n)0X৹CkQv`Q3; ||権?Q6Iv}+an`O={C#"JXh m/bF{lp>'^Bn-[ th| (^%`>ZQYp7m#\`)M{~. ]Ҿ4MDPt{{p2Fˌ]^*Z{Qh(X-T-q.uvQ9NhT6') h㔛z:ԉs7!!X9l@nBC2w :72AMJ='yN]zS![|XR|~-P#_2eCc +*x˓7-HSv/?[̽QU~\X,B 4|o ߑC6 ]$PǑL*e hvF}Iʯ rƞ|d;4ed7ta0/&p[C(kVQ{yʥm/cD$XoתT=TKr,{ zs|O#!(p"v$ǯfw^ "djH<L)כy]冪 -,j|p5fluwj旲*~V VxQ84eį t7]T,r)F;Y&ZS;2BD`7YxK js qbp@JC5P;H(ZV2m?Oz+qvʐ})92Fj캨tv(Αګ` OjV["z 5?.}a_:_9U'N*XUi#9R鈙BIUpfHޙsqX.KuR*ZN<9.NJE#!ǶaMYx%vbbR>f%یg0_qGnν@ReXEI<" CMV2uc;^d.pZ" t t f;h q`I|ű~g}: ;v&h_na ܈ [ԷNqnC.{OJ8HtfmbjXwW:^rϷiSn,=37:ژb iD`;]Be߯2C5<&䘠|IͰ4U)lfm؎Gݰ$a#L;afG-!UO{ gx%<~L*e K𶃑Si 8RJ$S6z7`.}%)}/L0L#6kyNnrY'BlG}K80SY ByCTNkֿ4ONKq$u`p;5dE2syW؊3Hy9L}SdOQeZ"|;\(t[39ؖ}SGM*ZssUj? {)6ܣC'򄲁lNwԎXc% AV~SaG9 Ae5k٧u*sCcAs ֠NPVz^k8KW#Yu QHTL"2Ek\ G^Xi_c*@-#^E E@KWzhN&Z~)>[_4dr.Vioq(*4{2_ W|r%D&H4­|R $W9H"K >R;Zwfn K{!מ>Kb^dS?ˈtW,B50yd..}D{gI@J,䟨]+ 1B WB' 4V;Y;'6Np2q&TKH='Lyl,mJyN+1tB+d2u5\"=xNA"&?ysv'.q5LnLb>;Y;5΃>-d SW+kI$Bu !cG|]ƭܵP`ލ;=N>6@">3` c/Om ȉ;*[RqX\- EE%Epy V93 kC1'2J+$QG3Y6_{$d{-8K{qބPZAZ|4@^iǁ<ĭ*_lAmɅ~6\leR`N46b3ka̗AZR<YAI7u]aS&ĢΞf )u00va@">B9O:nR7)@LJ(5e!v=ا J$^y~ Lev˖F2xꮶ4HQMޭ64^=}PHxeƏT8O/gA3_+1όGdDKBfKMZ%.n9 ,侅>t h REoD"̊K-^E%_nW{s24gP.ס%-$+IID6k.`ơZ|k|o ԹKX?^Mk?x; M"-m 4/Z4)rJ_揞O"X(ҰI*n/ Vfl(q!0o[iŨ/sPÉ9[kU{!F=?:[[Pޗ=݀O^:Ԝ I482 E;p\pg to!m(׹j\4]C(ьD 暋z-9hYPX@D7k? `;af 46A'ٿ1oNf`^I^`p;BB>~cE o!kе `.}@D02L s,ٶ£ YFoutKi Ȫ=O-pX*G2i}xɀ~̋hQ:\EJh씐+p2y\؍NW yudDad{dAX;48+V$^6 / T+ j"V0@p"rqN(Cf#9a -H2CbolyèػEf.K).E ,Juh ab .zܹnvHJ3O%Hl8[Z;t _ hV0jT9٪N1ɰmG7ɇͨ{6U،)݂K[R 0Do> A$} Q':feКҽ+< M. jkjjZAAF*(_YL+Cb7+EG''$w9b3yKS>:q+&^!Ĺw s] D*Sr#;6Gwo, NJQ^*0(,YE&'1;`r_u1SPFD +%~$OpN>KfJ<-|KԖM*xggPjItɕ^,/Q-iϙ@,!]\MfPgSjS)G :X<v]p[zquMn%CWu 7aߞcy#9HwN]\!Ck05C`;$H, <_F̅j?!g0zwV X64)zZEfA[T&WVCF@5G .bX.JLx|DL@@{sS{0Nie|XkL;wg]xn1#> 6R- )1In*ZzlMqpQU0tooD>0b!p<J|_@h}gcrknj9g-̫Q\mן2h:0> XK9$qq;s ,'66 ren>d h:B}5lxA7do\ vc&'PQ^[!yoI5̪7 L$3zK\QE.ltM 0D7dFIyLYz^dρ4FNQk5eg JixHߋeWdYQ-ebx gJ\F y} ʾflWigx:gƉ` Y{V7W*E^ioqB.DZS5Hg^wLWh-ץbֵ׷K 5YTryYhZ#1Q+*"ws9 t1zBD7|W}e3(ӼP:#-JA,`(NHr/ sY8/~@SD-" >,J=I&v ζ2L t`ƨB%1?`NH7Јz栠̶Z/SsfP2[T+} }K=I/&K KyQ[;GBn>oꛡ૙]i١mѺK7^Fcos:->x?YW43n&fX#&-l8>@ApU"EDP ײ &Ft [Ђ=mErEq3^?l08S ?'B슰AZf$ S~ n,7i96j̇!l#cfe<.]onFL|.x}*E ZPwVAPk[P&e |R賸bON 6‘J:'@HS"Ax%a@b$g[>j3a^L{-/]SJ QsE~lD OmkFpTzXJyJ:F~7ay4H5}!X<80 D+s+z.z+hҍ8m&‘Zs;1=u4- Kyym+־GB+8 .Z;=PYH8nD<;R,䒴Jb;൚~{lpdx=CqKCf$7ջ6vnz IwLHDYmeYh~I)yC*Ii ߳b 5y'23&} VCf@J~n,&]Xے BQ.FnA#(oJ,yA ɪj*EZ5 (5^Ӊ\1=UOAy # b0r}ULv`6nz*XO%)0(96J`<*5E$43¼ȖɺjˮtY9˿9nU"M@oI2Ī +uFK#u՘ؚD|xؓ_sK[Sx EC%#'J0Sܰ sN;DҦZ3)L;jq] 51[iCW#.Ei\{mB1&iC' ˨˽x6 x(>@#$,Ru|'k'zLROVًKMQ9ˋ?=;AڠAie#μEXXlDIv,c5+G>1y0*4e0:VS#MVs,wF !}Wh}s- \n=5``ѫKM{=m޼> %~ڧЛލ~!{VY[/V '+fl P^peiߩMEʦS2߇xzדfh)[Pk4[CȠ(wzNEyqprY1LG 2֘qCKwp7\՞&QHC`BČW˻eGeV7+R@ux;)Nv]׏T jwqBB0(} 1׎n$[!ixymf:8;ydub٨Ĕ`|t&PZB/bw.|5)~R|/9'Qߓ5oxr2V6SO϶}] eZىWT- 8k.N|A̕NA d{C>7;Q?鳑k?hE㯱r`׀O;,wSA/$5I)= 6wSBɒQIbpxzHNd޿Y8nRrCK5*ˁf#vlŷ4*eQnO/TdK|I]k5$y9u6`^HfKyK%?epq0o>3!0tzEX. s,ف:.`I\C2h<Zt=WqE\(1K_G:\na#0?|ݖܧYS3<ʳ` ;^B(Tl(1fsu'Jg o汄(댔Rd%ݪ6զ$xwR3dGu=}/dߒ溟iX6Me5o0Qzl47lhEÁc ރUKSqK_ѻ8z.=3[E9O'*7*o@op4JS,5M%<.1U\De_<$4CszV[6VHBY! g),mALM'cn|LR` ic HS&y`SupVg=2[>I gf湡1n5JpIDx"5nH P$&8Qix-g<ŞiJ.URlps~~ât+eXQDShݰ!v/VlX3 O^Q|^X]RL9I՛Cɒ4CJ=__RVR{*oh>t!sKΩ OwW@/+KlgbvP(=e6 0ޤml бB2W=7Q9 Wzۤo#@kK]9(~db` "NzeDn}n ԦRXd7-ISe#ܮY]HyxA[圮މ6 zuU!=J{\ANvdxYB鮄EWx7NeW^ +,;Gc5:/hrMZ_PT21hmgq^ |Jk[_ EdɩO빀1C37IJ5L;..9oȎۖT_+ń) [JLP!jnB ;L:[S69Fdq.jC{xS+w4;4-W:j y,7,RSi3d1b4ue ),{kAxOvҲO{r1HB=.(D) OzL&Z4S<_nW߼@8ὲԘྙŐɃOK80ea 'nO0I/Od:3͕X]T7PzJ"]1+> C$ 8 :rjɟK5>( {T-&0ַp8E.9}^t.Jk2l2Ox(g2p,gi9QT4ὴrsR4NY8;ͥG3ok-h0F2lH~AON$Y `:;NhKVEc39aK#3kD% ň|{kuao7j:R 0ncMeHkh˯ JU%.sqY JM$ݢWcUy~BS l_^ڞ|_esRZЗ)?UF5ѱQ }Fp^']=׻Jfw#s+~y D36g$C* }xyz!"]7ˎ$ٖA"[v!r*^Puuqj9K&o9ߙx5.5آNVsM7sjfP^P$`25 (;#֛0ړrgyaOP,id;hI(ڳ*Uas#Qc ˗gZ*JAtfu( =5\K? 0JS(r8+įX^jGLNcOz`&ɒk2~p^n=[ ɡ7 WQ2dzNc WpI~V AĸCE8[}>Y;h7_j?bøG6/ u8!huĝ¹fAN+yE̤֐PfCk.9Y P{+Ill )PֻP4Mgt4#]}v*ݍv S*(WԾPܐQUWa$ PhwPuu`;lVbnVo<7 8*mDQKV=}l"wțamHt?8)IX_gsD~?0AB۰gM>z Dk,k2B 2%CS} *BUZoˑjȂ{EĠԁxKHn b)bH#C{__]S(f~B[:b1:3Up*%sz/(gRH@C, 䄥vo~Li1?[SS: ' 9VI,{uțO&CA,  _׻b,䏑bLTCDמq={\-YwGL;O`2›",(~rBsRw;eVb8l ;Ϸ60})6gvAGJ1Q# ̏|^Cl$hFǰjMߥ HKvr^"Z<waC^Sq3Eoli4F ,f4ݓQKsDIgx3) u0^/&RbEߙx 5㽓–Ef9y~IOblQ^F'$eɰÝhkw' aԲJIN~),SB Ӡ 5vU2lEWW*Ĕ˩1_R[A6+ḷR?!B;&MZLB2r p+# ~}l((2Ķ߀Rf0\'r0MFѻl,~͈VC{f"̣$ѝWf7``8HDZ)4,cֵACj eԜ 2xw_EBSnpb/XQvE~c]}#f /aٞ4W^?) WLk1BB2S={3Vyfs r3(5)`La4|FS nFd{/jkApDK`!8qY|J]c(<4|a~҈ʥxߟ4%HVō_ A 1BDٳJ-R0J@w0E FXw %IC +=8E0 0bnZs4FFf&U?݆XeضTk-}.5$h.xp5Vi1 _c!t]:pIV te&|$41rN S6J5u&J4+kl1HbƧ%Nۋ=9on9{[}qvmTn[kCp3 D Qڸ4eAC}Z#. 5?͚>8Hfo7:м.af۴@jRfK>wkfSĪR9}OQM{]~lO0)ǡLinP20kNE^)]<5#?, W]0fx:cXhm0fB-'*K NX#TӠn_  /yQ?8bՈpN X; JrKfS&$+W C2!Dl sX1N} L^nr &`!U 's~T[\&bw3 ͅXTȥ~+?oT[|˓|~[PƤV3濷]^ᛶ\mwm2+[qzx9/:+Y.d rBb`G, ußՃ#{Τg59@F2}!9ϑ`5K9D1 j+T\7Z.65 !Hö~͉?ٓ[_:RWh?dڟr(gC@fLؗ]b[Ofq.vI,~vk=;(oHXrRruWD +El%Iac-Da(J "aI"#_>ie#9f&ޥ6X(52<7Pr^`6R1 [`x|7 JNrc9`j{cncN"PчK*v؜n;&4v(Ie]HaDZ7I4q)Ǯ,D4n7 u梳PYD4 ( ^WƍkV>_xRS2mlwӗab灪Di3o_7[t̃KeV\D7?EVoZ}V|)4mI绤,`a!;1P:g)Dn\>/58!dЎ!-s![#J*"h6ֲ40 ALgi+ڸp|Tv>dSv$S>Oq1¦ꥰH/ӥvZ?%3S5AM#vfxwz1w|ZCr>h}wD+'۠a4R%3 3EGjS8 /w(N-'4uXU+= !dEn-`K$ըLG >Ty96lT,MU]ZL% ٤}7}jٚGC-z[v,@S`SLPUP)knq|Zŵb dS`!J~\"jO^ϵU<e0g bYS@pzިE'('C| DLp1)j*H@# 5Z6O`ńGoa偒ʥJ+L;8*?-pgѱtCVM 3n͖b`Щ;3 ,&?X=iiiz$̳Gnnq[~=%QνABuJ"69AKt ?`OJ}p4\*$$#~W9 *j֩ n<]1螉)s6 /6;FnEmv=yn3 3g3 Cʒ"=.3Dzn7,"t?(Yq?p{k90o!I?x?BӫikWGBނc9^{{C"R:Jk>UNZ60v;U0|* luO*pX ȟߒd|̓xs+@_mS s[@w?(&q%a1fUX95wҫg;CXןbrq-Zah;8/G,$k`[ @Vs`bD<)?qҏlх9I3X NuX<׌Qrfذ~;1%۬RkUt^7Hsǯ][&؁Rv֔`aQ7I!zuI6_Rzh{Ә{ (D 1ND-igBA^INyaү0Lg$ ᙡEc-7@Wd4񝳆 rMH5-~:xGD1uWF_}n J5LqE~yjū?Ӓ̨ӹ.˶3}ݟsb&m,ܕ\">kVۣzAd2< 1ZnƮ1 h: q!6jqXP YAi_'snxx`PA%Jd L=;L'1BڮY+ΑX '78/0!I|ziFiHWuq8H7oU!k|s|"Nٰr}ڳ PiB ;(w֭`F^]@Oۼ W+ȓ^m/ڏ 5:kj͝DDIP"$C0( s&KU<(Z9cHSuc"99KZ~- $<¬ٸNB6W/!ʍܠ:}68o>a8)\nTr͋$ :TaQ5.n7l6{JMn4<8dhm!'L"ӦT l樘lɗ{\E d&b  CzO!UwJ+fH\Nޟed]R) "9g:Yk?%)űS.|Y?1a*ݮcQs&_!d^7k)/(n]1vXwMeQ-`-H*D)\nA~0;Q؈z7zT .t,4_~u_$D X|,pc D`3X_$AJuEa<"m!y5"j.P,ͧa9 ݔQQbK/E# ̖"h O V5}E-Cr3D=DݏM X:vQHXc6T5"#-·/<Gl%IqeM>d7"ą^c}/ZML\N_THv^~}vNֳ>kc=Hb7e 2z*qd⧯e&_{fb3X렿o%n#@I 0WJE .n1Þ=t+rc9K?/-aa;5!ePĘ־/QP,5x,ހ:l]\Vlףo'&hAY*)Pαa$ feeR9uyĜOMM֊ yW@spcrS|>IOӀDt̃&J(C yjseד_rʹwߧ-E馓z"y-B32ŌlX0M +6gt0z90|?,.W8OxxtJ@\97̞KmgBx@ݷ+$<ɵ0GxXƒF(ɢ5: i`` qUKvIaJU׏~ GCco` I1dI$0>'ӡQPBn@*R hsԫ!`>WZ]CJ0RbS'Sdگڈ]#V7V[6*]ѿ4i$zػ4$b!wy+TA-v?`5Kg8a$tǙ- G2€dY+K75HHjA3%ZBȇriut'o 0jBQ"?xz&s.`W"\\ǃ5l|{,=3F\{ZB=/hW#3a'cg?rAO Bp3lWn>='j؂O}uƕ-]u;n~# B C\o4e({'ou/qHL6-&n˪[#~'&]j2q$}c6%FK`GƧ@5[e넽.<}=Li"_F J{(aPRIPt-1L:W0a d[Y8>Xl2 ܟhݶ3rn( nǴdq.*穾XeNeś%NM{qԛ ƾ9X:u+e:7 ?޹dQhD(xAE2wyPd![XF_CpT|E$xOS;!',&S|W6F i(7"7b{O!ss%YB=nKS@7W'y%%^f!R]:T,?q)I A Uf>$=~GM(Cq !Se!>$}|_)D_F< #=>ٗ3T<~n]8w9OBW損!%:A.7gS,$$D9tԃiqTn+bq=3`p4r?:]c GaAW[|2?u!;IOǟ7 FHnFaɴƕYslw|ɤ=#P8j'&ZqRrw-=Yl{O}{</56kJ]cZ"i0XMSu;>I ۶Gd;qJ&j1Ͼz};2IhafV#Feuō*]]EGeDo[Lϥ=1r}갦d3x.k] ?=v6י22q 'yN9cֵvQ2A"d?ڗOqmkS oF Zv?%@@ѯq:g'-)ޅXS5VS_4|wBh")dS;;_Ҏuj>Qf>(euA컾0Ko'HM6wpO55->Nq}Hm@tJ{%!s7|c(9 1(DC@7cqʹ>oN&^JVjJ&]ʣHl62l#( %Ȉ5Mbk]*AݸG)fvѣfZ׆3*²XoY2T%|K`gP^e&०t7MzTIg@3݌M;+[.:Q?c>' ӧ 4ӧ0{$j+6?|7m $^SCo`q1+>_%Gf7 4%ʏX:9B pf =BC 4k/_A6$"'јk0Ru|[:ӡ a{OѣsSLuNoh;Jy^ǰm>S( ]݂|gA '&k<τ`(?~{o99])듿rl?\ihȻ;c796׷ie-6XD5JM FCVq^fm}_XmZdt97$RqH"!}^h7 !hCVbM*ַ6='Τp>@>*nLidk1,dD=25U(6w%ផU.]^re(J[*~dk05C5YuEK%*\a5s طcսK) )F694H;RKPUwjPlțGԣ['tVkK8h4 a ĥh"AkRB<1 LZd3^Hrz;XJS5q8(>A$1|Wtٷ?././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/Mantis_logo.ico0000644000175000017500000063520114332463747022000 0ustar00wattswatts& F l  E  (Y2PNG  IHDR\rf IDATxy]]g>wU%TP%ٲ8$۱X6y g HH膷 ЯXxZ"h @[n㌊-yd$TRMpsϩsXV;sǏ`s|OG!B 61lb ((PP)`S@A&ML!B 61lb ((PP)`S@A&ML!B 61lb ((PP)`S@A&ML!B 61lb ((PP)`S@A&ML!B 61lb ((PP)`S@A&ML!B 61lb ((PP)`S@A&ML!B 61lb ((PPGS{~ߋo|k>.Pk4{o!1i"^>G'~ul{0 ^?(^m|#&A-#XyPi]zhiz{s={Y.D{1Jo$c2S+0 8pۗ f%\FnC\;f\sUwG+|#2\eYf=z~iE}MHKz.݌N{y`u#eƛMWAAw;ygN}E]ƽ,l,S7;o]?3| 28Bmy#y_x}0c%;6h//К?? oe_D,(Fk2l/0,C?z=ˤFd0(%F8{ʹP;Vq2((c` ,6 },NgwBv5:,oE. ƬgPP^n(feRxAe0+H`Qeʹfpu/,UFOVPP5Мg0 A}sUG}X/l{L3yiU;ٺu9v~ι%:[^-PKqxu[P, f 4OGF>BWTn_uJ#tf Ga|1}"^ucd'n'&]]X xl¹{KDsO_{Ї?*?}Cbk_/bQPMBUzt 9#;[NP b8%DC)5/-X|o+7'*f#{ 3y݇䂂c4=Ax_L]@4DF N DK3Ľe܉!; @s1n#Hߥ: oNrg=j GAB+7wBs'sh1G ;K7=GOHM{I"YA)5ers'K ;K->rD:<}[ؕ٨_ $ܹ 柧NBM!{]n1JP442 o6a"1)|I+9T\*##jc#;*[6V"Wag2\ ȓӡE[SVd !%}Fрؕ$b8c7ZydP(f~hZR]|e\$~nw6TWG;4`ω76, R!A ~zEb.1xRb+UkdLE̗hzk Z\xDe%3}3MdmOM;)o4xtLl VF=īTc\z躸C<ZE}v+{,-a:-LnWT}!Xq]%Bp:.2.qPu!V=GYD1K_Mڼ]., ;y!f\*TCB8OK\P Ԛ#^|ۗKMtM{Y! Am"C]YL0a ("8S\$^ `v:*2eϧP5*eEe1N#tK/wfC5GԾL8tZhWATΉC^v}߯#G)V()=۸#\>?34ܥ ЍX@EAjJiߡUrpOP-AY3nDL(^sOͭ ^K`|VIAA ilC?BCh9?8-$|kik_aǘ|3|?CoNjpFױgߵO} #6}iaaT]џtyD17PToE:Bי2 u|,i ū16bx~ci&)ٽ5(B %8k:a>`JL E3)J6ۯ8i;ʟ8*#}7܅|Y`~JGegTI"T]G5-vNMeәo%L ! 7q7s!qMagI]ӲJLX' >zXoofBβ =48':t/=x!_0&MȁIrа8黼?r{~s_[2^8gNv!QSݣٮH)i4bNXGmM#Jª:hTRB/mL%G l~UcJ4r``I AjA~y^l2jP$>zeB)fcy /ݧݠ.*gI(jgS $ NzLƫ#T9BC(TiPk |Xv ' WV8 aĹ.A@#nܯ*̷ƞ9Qt= y 'Hii$:Y㫚vXm`mݏ>usUx~b8,@|AKqJqɻu?"c*w9ˤod95ŨraeLfCKa Ж]0`ye)naN'G![D3_+B_7y00 {f4`4X.UKa|~>0hq6ILk^_1́:ڽ<{\[1v&x3@Hf[MFr˓8U39j'1ˊnc${o8ȶ'CǹԚ!?/G!+Rr9fۡh2j,z+kuкg0ހ ]BiWcNu~ o,_9A~,/^ѯ:Gp!*Y~Uoܑv|.ϵ~~N{`-6)52UBnɶr:q[1t͝8s ݪW lf39`ZF5I$n**Zj<45Д$/r˾ñK*,h}nt,U8ϰ攼t,$BGg^C\ʸՓ+o 皽D}E V 4tr#y9k` ӾŢ2t2+u=udƶ}(S7`i6/ن i4= Nzz?XB68'*l62㮛H%~FCf O&-Qoڰ"03.SF~yfiKcqRqۛ~ g :x,dBpD1۬lp U*L4)&]CT6/ۚ; у J6)M?S(i.ϓSÇ:KYynn{ӝ}T(ȅOa{~m5|o}2ىV.EQb:pU$8oyHf~|"eQMn=f9vp0ʦEn!."_t&#T5v[r4{1 Ɯр OKL0c[ @KTvOI(ȫ|4V2&ZEǬU*n4WMf.oKt#vi9!C6T }P:#4 OdŻ B)MCvq]Jmܳ5.cɜ_UVu+*ᢦguA(ؒ5?HH˞ʦETk5) IDATC2 [WiX6j087{ Rgfid>]&ACF MaL'ƖO,'o_O+ *δQwc1@kT-6[^z*iݍλF?7-{vfiww|C+H\+e:]8="hSa*,,l4׋ڶ |%0ݷV=;H#G5{ nAaSNn,!'.{3*`x s*;ge@xn<+jg}H/:(\{AjʀIz){x]z\" X\Zk_a𧾁<?81d6^e퇿Gi:Op*8b9[7T@a25KLʘ\;tV=NɈϸcJ(؊xU\fhIyl-_WJeJQ!PxIF\ %7e3 ^8Y7|/Z7(FUE%d/I>&s*0a[( }^tޥZf?Lp nV LjfQi]ymk*fÊF)k;Kh:&+g&i(Zb~Ze)v?^vf{* jEg[vX+xWYwbMDav 1j lmd32ڴu-%pisN\ }BE .1 Cg¤ Cvrv&@K1C,,E ,Dfh*0]O3 oP ^{]Z|q({NڗFR7VoP۵gyK+J\`@uY-Bi w5ەjI#T!r=;`T%:SB嬌x@ݤiǝ6Ẃ2.]IHϥ.Ͳɴ`Dk>{|3weB&> Eaqiqmرc{ǎ@ѡ軘 >HG߂~aJ\ : !ZLzg 0;Y_W-nLˆH8yM9~(Fĭw|o@unyQ]MQOt3Tڅ47'T8I4#2H˭ٓ40]?meu r'sSQ85mWv>v}4;{:Y_#wB]@ u^о !t}VWBrD1E20NJڽ8ݻ*cRԓ?s&F:!o2k}wCJE`}5^Vd)(b&I^NR?AKf<Ӿ3q;}brC9Gi 6s]!x18+yjZIv?p]>.Rxnx%>C ً-CV2PCY2wi"8j׳HPNQ2 zV"ϥ뢨*aH6`L'|ҧG_Js@n6ܵ;y Źlۣk˧t?m/?>K/+|)t~I9RfM:AMr:&M?S$j~i1uMeƧv@%@›vѱzZ`+c[d~?`}D uy5ӈ6AfT%ʂiڋ*kBa鿖Y  H"/7KWbBu-RvhOe]wm<=M|E֧pR 8^&u}U,K.-P!XyrBZt6 nH`:w6Z]~sHU %$zIhTcs\R+rm?T݋Bp2c[;y[‘=F肌RT+Z?@m!{=uq;7d̟8+l*4FT5&"186#[c$++Kܠh- xйHG4+J2)ϵZ`,Т-6Tx $NQBu08lЯAK&)( UQXgOv8I~\8bУACϗd|f`%HTL*vWhf|bI 5B(><10I\E |D<*/\;zo#{Ѱ ɠJmeb'u #0X2/U0V 5`r%,bbw0j:gc)M^UR.baE$.SQ JeB1j CҰKK'C!ॡ;E$}%WjwGrmXrZ Y1'NG98uܐgT侈7i\UTӰqeBiP&B*E0 (*VtgS~h=o%~w"ѯA&{?y=z|P^nUpMks]0rNޫ#s3 #_h#|AxnsXXriaBōc ׋Az; &dZWh>uMaXȧVU[QWdrR8}1λW18ԯǨH0t*({tW=,.ܸ Z^w@ﲼ0cTS7G1D^gV^FP&X/1nE-`EQbvd+PLK<3#|B֡Ǩ*LDk5L?g/k5^Vvk"|JϹ=>u.T&cVW40_LV֚lRe۲ E48sWty\F<;L)יelE^/fqMǭ:9+Tk2rZk20)*3y}=L{8<|ɩ~yntM}WZTREE|^uuitp1YBBEh\$x:EbIjr_(ñRPfXTMgu,O=Wg>gYZh1}aR٢QmG+H \jc:]A ٺe k"fރK6e'9a%)[6q!4 O C!Wр`p+dX 2?y[Q}e9c~/ܪru,+<{ַ -GvO )E^ɤU`W~=a/:?vYb@u0n^(G Rf66`wgg:لo/7z@sI8xE`j[ڏ9uoފ,#*E1Sfc3`Úk1;o._#|#u w6%bAU9)a# iyZ07 ہCy<ۻCR ?t!H?0%ZEQ&MNT] |E+C JRrٖt.ȎeXELDITSl] IS̒V RSJHc^ PS@|=i0 RU LۣEꯩgZ>o40^GzqˡXZ^gCֳW)?f'д(EuUǾFgjmJK yPƶpr I pcN!␳2zܡYinSq~WNEQW18hA0q^*U7PܤBT!B,HBMUV%˖D^B\ŬjBIdU22HE~߂uMUT6 FEeeFSLU8%bϡ%NH94$ =@( FzVhTbgia%$o[@l'5R^^0ڟۚJf˴̊VTɃNkhs A󩾚q#<]|ɪN$[~n8 v;V_%Ѱ%f%iW$`JSrIbi:J2=çE%\Zi[L-|1te-Bg.,323ypj#\ klmOGY%$KkLŨ)l6s=^RXCBvgLǜoTإ4S=\ge |I ~EŬ:!|補~ Ų_xn#U$$-;9#? [z%B kZI+TDycԔ/zL 1Oy\<颯h U?f_Z9~7~7;QT5xk HdҪWyOdѤ@X$^VQ1Y~_U^ք<<03{Ԇd,Ҡ?9'4Z+UM >u\m~ɪfy+ t4G)ʆhy?_v32>ey=Y ԧvZeXw. R]/jL\|SQ&u&!8lٕ۱)I( orm9^CqsZBTP3#H<7~ܳ꽱u. A\DR #ZFCilɚKp= DŽsmDjF$5JRAS !DnntUwm{YM;H:U| Nӌhel6G3#r'ϒO м=G~_эxd-ZYQ`Dʩ1lMM:|P[*':wI|.*8S>`ߨfEnACygpQ13WlZӔ ڼq2zt9o K=݇%$odžPjܱPu#@a15:wr{M@Puck( kURr>p)Kf*!`bt2!}=`hZB߸Γϟ_T IDAT%&Tn{$`u9SuQ4f ӑ+DYJ,|AE>x)q}pQj*GqlrrsW!? S+X)9b|^ n켢A:t ,f%RoRipơ2>t 3탍^ҨUG灶ljl0S ˉxYdL 鄀G1v,j|=%ǎـ}\CG"GMEUL[L"PV/u)Fjgz-*s`!c%]ˆY!k&#Ӧbkb^b^Ө.]'[[0G`O2i,WQW1*dYt5O` HNF%]U$Tm#9"ΰ4ة&>DqU⯿vJ/KI `Q=I0WWF;*݀XC:8VEP Ǘ[mYYDjd2Fhk[oȅphB60!đ1BL#,ښ'Ao !XvlWo[%xJ?2mˈrg/@9(dzVc,bJJV1)t FsA'S6++-?'=Uyd<{eDc83s4"^GCpjǂ:LhA%(,3)AEW|? G'H"y"xD}4"lTp2iWՉݕ[ 'fOқmC8 gQOZ/Q1>Kt;]{^w} y1;@E}Hq\4_ ? g1C' :\D|@x@y.t o%rE^N`w_;!M p:#yࡀ$NGGZ9m=ֹ~qٚrrƕkl<'9q+`X69=U9%~*r!Z8h魴 T QW{.? L`-Z*l ;Wy-7;֖x< G}kDosU~g}o'A@J+&= CS 7Yϰaɇvu9 !4Oh>!6>_񯮼J}Ju ['Aa$0lrQ1C~tjn5ub4!)^C݇ 9Q:\ЧsfpcA}*"ح{dP 4KVs1Il=bK1k3 bml6 G PXKOD_ў$NSyϔ?ߴO>{,[)y:ȿӍ#6^\⭯6 S^sҫJ kĮ3sOsf!! v- n P㍴_c[Fme3:}+ 0j3G72&;'g|f:o4Bݸn鰬]xlv6;r23ۂfl0n)?*")v;?FW3%MSnM qC~Ǔ';¯ʯ__sJZ@z!7aan4MZ^_'~u֟=iΝ&;eyL," ȔQgcĝ`d}w~?CY#>t&p [C~27iɿ犿K7U32|O\9d4rgS:KGTGk4 RSD1_\JRUAcC-E4\kbX#DoK6fl@+! :iB(c$aQ 荫=kRS?)֪5O*mS#&@#k ^}q ¶ 1غqň ?^;>Hd#N:O12ͣ~WX5qwpR^+ryU)q om^7K'B*M|{}o̘}>7^n!ŀկlM0Puqi[`N?BqmjpKeaSA `W`1cE$_kco)h`RvDdFldRDHWbR.W2^rňkX~g\M(^/cK~~Aگ+Vh-46_dxZV Fu[GIjKC^yU~c;9׬; Ƌ嗜RL:ِm1lNgK>WX:2+W U@6n d(_U%aM#M>рSN `歏>;d5 A'4НكxcbDvl\=|w6𼀋/}pX+p( W[i±wBq]BhnwŐZIu[Lʩ,^/1vpA _jz}ܑ)%a*@zT/[kD%ل`rC_u {`7? y jfnp*k̤~\ۗ=@@z v[@cd%\6-M*ocy. {'jʅ8J KưW8m+`HNIf9?Gl]A<d.A iKwnԹxN2+ǝމlO \?@uȬ:EA]=Gۈt@6YvNq\:'lԣ&g豳מ\kp0{O=˧59RVYnx"6O2s'!FO ی.x$i_S8^^&Mp[}X?U ;H8yw?ȑG9~n,MFiח(JZ>GL7#@:z3db,ѸCbU"B+w&po^徣-\=.Ip;JIԮ`\>ox?Eh:?ۥ lGVWYLkND⨩CjnH;0W[@=Uё+LK(h Uk eT'jr{|flµC*"fH [yNhgޕᐎ"4amY.d ]!9@Npz<))ŰGx6f}N*Wg]fBo:9V7Vx"l{m8[n`7Ԗ}=|O~(F@Q7fBBhj.CjhEZ!Ĺ>.7iazt.(tCm s+w~$Bx;>vs`uSX'mN s(͸$dvQn!ZHtuu%byЎߊ{y{f-v۝Gd'5>1=b7ubWTqcf1r!?ﬓq )kg9P 1}sIf˜n8(~~6_ټV'[ۤ\T+Տ 0b9~xp2|ȧ`غ>sg!<3x(G_zK%P}92^CRrImF*U#riq84rM,}nF<1wgqJחŝ,oݞ j#μ?O?`{~o%i=c'uJBGB_O/ O𺐀B+"VӘRy&>ҽN &NdN`RP*_l 9bc~CFǿI}IǫZpb )Dkj숦|_n ڇG'#`Js)h=?Obke=)ؑi[iDuQ8D^*H})XŰ:斑<6F8,$^ӽ ?p[\{ϥ/Ĝ"JJW(e3J(QQ؁ VJmp| |p[f -KkH6gt_2HHSF)FCFI^*1"뿺7'm" R8ⶖ#d-ǂ)Rѽt~}_}5XIFӟfG7w E ;SDG&&/B(|F ڮˢci2dLvtX0N _?er*9ƤKS̛/"#IxI#(w`;w*Kx2՜ڥvu.RJܪz뎼'_<0~e*"" -e*MFbHe :;\Ë䥠fälՍd,7ĩZ%7^|_rwCLVN7MyU8 [|4jըv+z h@1uͅS6!:Hcu#%nt˚;2a8I1Y_3"xsy)@-P=/|9x;r!DZ[RIb ̺0CtQG)a pCބMJ(dJ]2s򨅶D6w#lF;"dę4 rBt{2ݩDONt?N#f)_=?آ?anƱ<}O:BoЏ 嗗1hjk6 w,٥>ѿlqrdېQy;tlKHLD:b=@Ѧ%ٶSQ6n,oca|fo ]*bJF"M Knax~Dƭ@MO0|꣼~oTY՚X9/Kְ5vpAοo Y[@P\V0|1{+cQ;7; e])ʿ7F]qߴ]W~̑*hGǚe-O~G_Jr@ㄚAӉt\n z@^mT_ W3&_п0"lcDV`l8γE;Q6ȡüqr*Д8s9G]ŝ q33}2'.;+'A f 8$|vRþ&LDk6_]^IXٺ4w!E`͹~~w~d|}ߑ@:JQoV Z4)#ӛ+c%,2Wi/`_ ZDǙ170$IUT3d)(C.@PԞ̘C5q6LAWX;&A̲1-osɐx7[N\>s'回 C'ޞZ"+S@0i3CUч+uغҹBjQ(c8?b׷Q"ue8>}ў{A m:aG+=q/:ۆ%vXXm!}*^AM/L˟!+Y?# 5W =$` A180b,>i-tጦ+aFR#iE/>s;6poIiRa@WakvE2ĚԟeЃRkʽaNKIE,%w[HFOx VsTr;X/&C,4$(K95Ď (?[]:?[ɦ7uE f tp}=h=8ZwmTCcJZձG5JFEw$02)zƖKX\2'-o66OuV[piTWmtKG 'v`5VĹ%CA3yAZt.%D^ Ōs UMY^/5V2)%}W=TtEa}wu[fPartqgC*BUK)d ,do-n3N ޘY Sts`^sغ8b{j?g׿cʅS`@[kDX s .~:*h!s zO}xrXF致Nk7rT?r{{RA#BV=laSN%#FYJˈb{pj8V$tDczܩG;T;m;_? \^zz~[}rtݑl&{D86pM2b202xmA8'?ؾ R>3)6KRaZw8^uTUdY>,jK sYr;FŠ烤Ae搇kwrѵk+  |A&SLs/NJG( WR&ЉūI >3/O)p@hP.dH'_$p_p|zQ1|/ʕk3lNctN#IS/qDEk &SAwmᔥpN[ͩdD\l6|/e9Q=M:e{KKH h qiPk*,p~=6Y$g~m'oh=$Zf 6f3}–H:=]W#%UsQH}ծ""' 3^M>3wGPN8R_p` +eÜ1eZvo挅ê(w1[Opgsp>H!ˣE9b~AܤdېG jKj˲aQ1hV*Y^M"k1sM>o{?ԛ|ҩ(:%==6f?yF_=M*MJ'^)i--e}P6Q%`2Q(k|nKk;At5@W:\2><^=.WM UG{|f @^cexd?i#ɭ@b2 $ 9ct0D8]K\i~$Dx?zyz+Z{,43B7"\ {~4uap=vs޷|?I6)rУyTq.Raf Mkzd=b4לJPoqR,uA8OVD6Lv.d2a$I\i9prq_5^=ˬHLEVWؔ[o(/8]8FzȜsY?]cۈ̧ٗ?9ғ; ^U[/dR/5kNil&h7f8t6\-]^7yǨ۠BgfSXQ l&Swғ TMhx l:cF$ÛU 4N&c2OjV.ī2(.R6ϥ*!|kmooIkD`fr2@ _#p~sٿq| kd~lT*3o 3v~ȥ\!q./3(! (ku#8(z!-d)[V ("Ng1u&\;sܨ4yTlD1$p3Ƹ:MӿYPHV)~Ey ,v IY{)fZF?|G 68S|А ܪƦ1[x+ů_ӧ= 2x~QֳnCHzlw+AJf-H Lfo{K~}֥-ξ>Ij炒⦉"5$|A#˖!iESm_Z elHv<>^k23bvS∣(bk*i_ wxcrIMQ"P$L(nkpD6L wZ0A[ȲeS\N8>ҫL󉣚 p)Id.il%$$̨l)|E;/V7u~ $S4E})A@)aaSo:Sc[L!?]_e;eV ]anQI~ mQ+^(VɈ`hbH hpyu Id$Rmv8bR+w湽oIs.$CDX#/ 0ɈW?U|h_Z)ˇTq>Uk=v2({R'N%1Hl6IR p'Gmr~ N@ oYh5温ae GE.vs _ I|a^ܒl w2'S ")-@+NQ@!2PxJl4Lq:\K“(h,9\{vS;x_ظ(I85})D)McaYaaA|ED9BJg5>g 3w3 TIow4ڢbR9VzLӯ3,۲dMCr1h5 IƱzL0J!/7=޼ fc1GY(e6 `tqeMtc6>KϢ7/JyP&MIquwv\S`PȷmD9㖗vkN>0+Lm>8$6MnXEG$%õ AWb[Ik&odQЄY>xU_y#ljNa6ͫ2d-=l+q-?CL/}Qg͉&&~sY Ov8 ֲ\9i?d+8fڟv.97e}%oyomTMb1^}{^;ٺlԣt~??y-D.! / YKFy}^d[3lr96Wj c E Vvrd\ӍwK] Y78l 8m5 qtK\gdFիޔsvV^3y7PtM5&TWY·fʳ;_ 4m ɽĬl=@Q-vZeID1rZp14Jt)eܚaia?ߠsɥ& ׻dCT-nѽ"u@#bng߈M=_U^r,mj\{uDoohfuX|V(orCg_/a~tFeI&kx.` D$g;<\_±tsٻ~ABV; Ii,>&b>?~ ˿l7X\Xdzś׹G)!\jٕ'^ \:JI{?P d~P4[>NI\jng$VeJB?"I(FL$bؔ-wgr^3lø)ݷ]#T@q2Ƴ`N]߆DsOFbiNyu`]P8ǁL(C^JEο|kԧiMF}'#Y4f3VWyϝ>זtww>G]!Dt9奿A5~x{d+%ڜ jh?eY=0D1+`j9fb4%qmð%IE\V0ycvL .(=T#A{m_(h*ڬ8-I6+~GNHd#w7xO%;s~$N>EZ pۇPYgQGyQ/ÿ6pϞ%-U@7^1Т Hz$IzEdJAvo^QZg1ى^JZQA+D4rI ԃǷchjF1<#]Ph3l iDmQ$.&'j0K0`]n4Y$Ry 2ʊ2h L=!OgIq᷇PH"u.-^ڛIؒH⇊Zap;4g}w NIF 5õzTc JxeIow8#;.©l^XX^ju/Qa˭xi6oz 7u 7[[q(W#9l(~+s٦XD#ݐaàyP%#g ,?&M -ܸW*X79*$QA1ۉ"wը!@㬬]'WˬRkwH)4Q451E'/#V!P3NduG+u$Sȡ~؞BiT"ə@<&+ 2/(fY IDATr.j="3@986 mxB[)oh-Pmx$NEoRc!+ 4:mcQ5 _-2dceKz`* 8 a:l#D_àI8$ʒpM};(^]ȏ̝W'kbya#8(#rXG>3'X밺1D(0-ƭwȺSZ3-iLjA:q|70jF,!X(Jbzoisg5=KYhHwnV_`)'˗M[ciʯP6 7gM8Lه C}:C9}Ԍ_qAP"b2MCL L14r\)h/)l~{I15梊o)>'yKԺK\[tc5C.qO fHѿFbg$ٓO]$ރ t /eݔqJ_3_RCEP\"^^^ˈ3gh̝O|֟LVGj?ߡ9Bzz1?Nyߦ?0I ~ޏz>Z; ʾ]N΋"7.7$Kƚ81 V8ssxiF޼7 ?,@/dzr'ra=Pf)(O1gU: ?/ܺq9a:.I]$^bIkS6h{)4 @5]nq:64EF}"Z-h-xo]nKNry<:f{ΛߦuGOHzujw캄EߏcFY y2k+wvL|U6?(`:R2)pY2r/9rJڼ!Hp;q:mh]! + $n֤ZSklib'%D%WRV`# ۟q#b t$&fsG~' ǮX^8p\=j,KVgF\]A_!QXXgucZ^oyg+YLb$ IUf#UulK p)n]~/l K;WؼNvt6+o|g> CU5x'gG` ~ڍ 6BYGS69ԣ!&JX2.v؟ '~GlXaz ɆC$<ǐ(+riiWhdHun)6`;^хV~yh' o6G?724Y;iv97r;x::Qe8՚s>`kjsbsg=y۠uND[|sn@Q5l^beIoh=높#K'J%;O%\꒥ 8K㠤9o~?h{\x~?0\34:p!Sbfuuq"՚^OZ#x/bA8i(f?ίT畔-̭n\w}Ws/-f EGU.@V k,"y8#]TnrUc@l͎1|&rN(~L*JņX6_1fBJKfQZd A^+ ފI-7c-^(HN; l,l`uF_a}}C._{-=diYn?:,&x.&AHUjsA&u1}ԧfqtF[2F8{B fL6 Fi"0G%3h)N#O;hwHV?wh9p6}fmejbdAql"' [XQiҐ}1ECF,6Ѭy$¯Wk6p:GnK^Ɨxů_AGIyUL- cR|@AW/\a!%NݠA<N  yNˀJ);],\@UN%s R頢[I19XEN 5L6-n^m M a̪M$6@`rt7gz/5,[R{es?ڬJug}h_qPv{'ku (J6R7ɈQOGoVT)ui]ĺ"U~ٷPN 3Jlh+S΃E$9E0rklo;AE 9r7|ֿ}ZG\3 ڂ$YӚY`pu*6p|P `)&{ƒD8( :QSs޽ɷ'8 زSa1emr o|!x s@2?;ILj7]}1|qp`A(P":Lg s2;|T8lY%3BS|PǭqtJu8*$C[ܿ4-`퍄:I^J0H{䧺QmPLgU-=p6҃ꬼӿ.:&9wBSg[6^1L1<Tr=zL- {@L뻁as"mItsbG[7cfjqcNqZW}ygڽk|?ɿ}A_ok>"ͻ(nLVb,( 8!̒_:rsK/3rM?(a@Q ? ^Ll&,W08G[8 ]C"F\~J5 ch4tR^VpLAִ,NejRZ˽W2mQJ;zܓ w@zݟ+Xt qo'Yn|1c<=o$S xX_{oFĎړ(C70]aCב|9sj>(=T*#q=,o$:oِVsjc8 @UU\ pj'2ჀA8 _Q;cːZ8.'39_o?mZ<ꂰYo.'ʚ^mc0`Tޏ?OITy@e;aAƗR12sgsţ{?? E3ڜb͌{MydY#?²0Y~q:OtQWW2n2FXCŌ.q?dUcv]qV,P8/!1Tj'Y($N2 gt煤w^f3uYyw$34bAP_f+If+YDl4T,I|@"h k? (׃X~?uӌsVU>_,?ytg]2?wck!Qƾs?wMD$B<27[%Jʝʨxg'M"Slqüɥ7_ 8 .|: ~.m,7=n*{5K  nV3f4Y" [kp??*ǞNz~xc[K8~)a2{S&A~-u\OMcOS;|w !ǿq^°a2A}V!25gʯˉr!ù2#{ q o1vr H}E\1ҼL /οm3.Ò1`RD.9>?My!3Atj^"FOXv?EHc Fvw,(?2b6ˢ1㉞9>|"+<Իi4+9tUl1\vN\hc@'<<<^SV0DE(@P0'>NM$0a`ۯ7;s~דl[g0 1WgĻro+fغ飜=ٰL 6IO6@tbzf#-\aqzww'{a *ڏL0M*=U{i9 XF?*!DE+6/cɈUU@Xwȁ@)EeUAkvNǒd) E(ⷲx,?rk, I` YXhS iw t IZ @H<년4Nr56(*Ք,B5+OհV̍W{y睐j"$8]l2u|iíMn&WW0&Jw=a(7kY"q$J.ٸU]}f/ٸ3`"w޿+y2?C~QE135C \nnZ܌(5 &;5'$4T0̸VO86 r @ 5Ae[A6BLWԿd[+1]D$(:O6%d 68O㛿/₼VŖ-#vܭZsSJy\C- Alݧ?@Hs^!joVeNѩ7 zEDs;V$} &SS, z!~1>- CvLc@uɁubk"6&3ƽ6>q#|>'U1H jMWQ5TN'~70?'[ː(2DӿcvT)̜ݍ fhw ms,CD]\ŷ_]o[x ޸MK&ߡa\b}dz'y{؊\Z~@Ә @uP((+EFZr9u#5ALˤ5bGa$4`P)t3#"wI[U(9 !9#9xٴ;AY{[ų|XfrIm / &f㝄l`<4ȲrŦ)o)$N œ~$,K IDATL\}ϓaVa:AjNv &w\`C4CØj0.Bzp5#~Is5^073 ;' PUHTeQ׻NzA9\fb_`\TTچޠݍҡaoGI:+oK/HC6h HF£<&A wz "(vv?a wa8(8.xR!7V#^]n,NJEkewР>6nXq]@=Fw.bȴDmĨ3&9f6JcVP|$E!Y/s ѵ ;ob\b.q}`_6u ɿ1v3.-yޤ\gt,7?rkt\?q.fŃ̜>%D1!$y %kyʟn c+Q[ _Gy+<A?10 >7oA FEUAkK5bQO !" rJ +q8K`K#Z̞ izanovk;jLWQwm 6~$(Q+Y'FV M/8TYq|+ܹ $Qw ˨5ٕ2c^j KIFT:gs'2Jq|l} /m]Xo~-ݜLImIWgɲnHϧ}>!=>Qk󚒬 ƒ'Rr 8 W(u 8Ele)])Xj\8%NRlf)trA8q?Aof /ڝx||RH'N\x1Cm=D߼Mja@~l*1$H9fmX͒*,(@nD.X= T\5y_)AC8݄W9h3[_sE@!k7\CQ\xt {rF\}L3}د&ypQn{+1SPOJ# Sq PN,񶆬[57"l#8(N5dwיXIŖ5t@?Lq7tͧ%Sz۱so+T^#ɻu J( "@L c7/R!KRi,ѩfK=>+[_:\opSzwnZ0ؘldD[gzpR؊d2l1"D1R)!5r L1<8f ̧7q8uG-D b~زpݔ4Hmob[ԢU2$ƫ Y?T J:fhb`7 p;E;$% n_V 7Ta! B]| 4c_oͧc ?(姘>t r_A2kn^d)~;ojN*6`m lL R[=B4qicl#EmWݚohΆxG&F{`q|18T#fV 3hz;&ZMȴ7Icٸ3;Vk:R\|eI;AñN %֜xs,Qw: +٭zBr#`,Klu@(xaWpj> ayQ E]^stn(n&t s惮؝#SCgFR8 $KBrӿ5֒<4sRHUb "`Rha;YJ"\IU)yVG, 8VO<86M.=zzP.`waϲZ!luҖY̬sRR睐=XOgmk' 2Lr2UPkPw vKnnާ$yrz' $ S^'Uy?8jÒü]|PY8{ipwE`Ul8Ryd wP"ޑHj-[+ K}vi,C $I6?Զ5A7F%d{ 8?jp8 v>/_#z Ϧ)zRf:TAO,m#XssgKּ߯6:3\]6Ӹ%r[1ZU+Ek𵩺 12`ӴO4S5) rJ8tE0&jr\KGva/P p|ftc,뵦  D{qlqQYDdqk˄.TtΧ}nd)B mw' p<]Rzݩ'c6Smq Y$*I1ml%p5X { W8yax DŽ[n}k3h?Kx<1,/ָ2@[jNw9{i!KlE; ] `1푩 #O>XNk %$RD*7}J5$ȵ fxp;1k 5(-!r5at"um_\g8~,v îޣ9VM<ϝanqv sgX/D4\B="ިG$yҍۈͫAɁ`<,(` A0aT_Y~w[[Fl^1X R rExpkWbd9#yF|vlxQ \_#75[7AJ|cIƳ bkX^@OufCP`wJwDRʿ;%Fj<ژx⯔8fx7DmnA}>O3Ǧ U?T :k,/f Yj1=m8~K7h+ Cb 4:3(KY`"?7&>`8Ɲ׊*@>FA[oxbO5i>yJ9wZsfCb?e &kz,+-Y'CL'= 7ɢގhNב^nwgay%PXimāGi:ɖ7ي\zwlޭv ʆ8ˍ~%bn(=Zr&4Î:p8Y|R1sȆ|XNj?EL=p8̈"9$IA[dAmْEC2)J6)2gHq,RK^KfUe-ۯxգ"32s{wηj"$NCa2;٧Zp1NJX, «[A, |\9OtҸ@i8&ѣ.`l 6Ν۔ҩG,-t90 c{Hp b{wRvXyfWDRBKnB%= +-xm:C;sV9{dTqRlRq| 4w֕V.'jCp]WqǣLDvvF(z;] ,n)AY'9H5cwܹMe$-=9tH)ANx T>܇Ge&c!0+ײ45MftT|[>e+BžFa6+|JqؓHWr8O8֡ŭol6f* ~Ȼzpҏ{OoO[ ]as˷QC DW13@Ř{UrbCDNɼd*i0Dԗ0U,dR(ThD%c, b$Z{#_+!6Xpۂ+)/a}Ty/Py1WC5@$Τ ?!3jVƥS]^&Gx K >`P0r^/M 0&zIQ$m\\0~5v`NIe{:-j|ͣBdb6onQsjI&$=!h4~bgFCI~ԯW,:iJm'繈Gٿ xtt/զF?Jl%^@ҶZA$1i ۣrڇ*̓ $;W A(c%AF7!m݆uOwѴc(ƪ!k*3y 닊fN@کD#$=VUpxEi l 8GWLJ FVj -N ؛Ny/޷W{z^c)x) rY5$kHuw!i܎(_vbYT飧rzUjh_~a֊ιl"cjJN0@14xٺ(en2U~';~bBvS%A8-¡SKAbJ(g w ;o$NIjK5ɝק5թǜA~ ;=֔k 5XUS.&˟:l(*U0HcU30ſ. ;f@YxC<ء;P"a#  VC|"ʹ/ۢV+v78=3._LCjX<߹ c̀-#/lh0@u̚iezrf9'!iODžsox=yi?^ k ɱ8 K0(_./Xn8xtM+ )^Qy@z+a)fEktp#օfO5(]!ˆO4xtt#n>$ktۧdqx7N;5ielC"0Aeò"R#R= w1]^=G%CNO`y.Ij!!;[|k 6{6j .MɶQdNlkRNB#u*HZy$x'{T3v C/-YEq{qFS"!2!(ȩc{d5uEcě=y{)w/hGI[D;H1haJ}J3( M qs7Ho|!n.*EGfI/G] fEiw+q{ujEG\ Zs$UFנNuщ8;`VAoYyʜ2cH,Ib~ӘU98|z8drG*z{X B 𳾂|N9ct [:B&q9HyRL'AAs0!>ݓ/7G+`Wcʲ#too 3j"uٞ`ʱEB w'E ,H&6CnMe;"fG0M*eg/[.t"UJDA7[`%~580u&Vhnnֲpkv79Vaw ~khx"oNO4C8-$ g-j Qsp;!bCbdFKV\xt>0卭'iQS6{z"쌭J@d}VmH)sXgqo}{xx+6W|q[h-^ut墕9?TR{o/ty4H>e8/wD6sSqg.gլkfo>_xE{4&TfA1@##&^ AR%^UW'~јXJBЗ]j| 5ȫΞhf&O:"M8x஄1Y>BhcO>FdKZH$x͢qԦsҡsHbaB"h,8a8@PXحpdJq3ь(Y9u  RJ }O@' w\%hFG?S_DKGN/T=4\fOd4s?g0]jAd<H)P q!".M-qwo=;on]'6?}TjxqN<+~?(m.ǰkMкmw}]Q'^iL} >RD9x32]˗I7o~gEP$w`Pn=) 7 GcEkv8uj7پ0q Ӕ{;̄M8v裱KDY$@S8*5D\矕yyN`E`V0kݷlxar!;쌶c^U`fiZ4WW}OkTqD ^ {4J"%40L4_1`рCWOl\>U)4&yT~{{0'fU,Q4AsI5bS0 Q̂5zgq q9|.T0}Sr؏XVk3^'~g?UIy[OvcTG5y߻K<nUqx#2Ϥ/>J&ww2l/e)ϹT (' @ƒY清/?.j=EICΌk* b6!m N_>SVY#I ?oGSKBxT,Gͷ"+y}r &K뽉NRLM8Ih4Ҁ@0I|]nl1E&ʥ`K ,8^_ֈ$Iu<`?8֖IJrraʷd3--DZhAbi/.Dp"3'Rr<3W@*>B!|j`}Q[o&G{"{,',ԛC̝]>CŲAI,~1qx~w=ht+f?<Ê u<{|%KF}IЫ_pL@m@)g =5!%R8.->/]>,%,n3=6McvE(npTxi2a)fS @Z[ 24OӨ^oG>Ec}Q )%E㍳4O`IAnRkE\ߺ]œn;!K:̝J)RFurܽs֚UH`țgѵM¶)eyǾP# d&Z:4CR$ҚBU- y Xs -m: lD  |j2Z7o~烿3T{}LXi4fqMVZq% =¨z %RB㹟A,w8xl1hP6¶'׸px?j!R?bF n aHCJb޿h&ۍN:58$R6^qQ!a'^̥./HO"xB킏ߍC)0٫?HlJ$^*g+^wY˔jNd ?EU~?P msv K kx4opoxj.tȝE&(\3\\aíC63bq$%3N!%|t'}$AQ8u[1Ho켑Rgg8ڝ+h~&*9׻1): q,c8ITo9I(= l-ROh^dm{#O3@ݼT>J3d+SIzUrSSYZZ`Ǟ`oo??aw$HFcxEd9:O=e6 to8r'#fYo}+s"RxI 0 œEHҢ`ŵg k)Ϻb7:msi7jͰV?kr@)nfJ³˖7{/oĦ±&(db0,ԍrG9z\1M81n=ĝ⻸m.W86g?ݠڱ^BzJo=2gD9o]|'b|?)= @[ @}q n"ΛQ_wawHP}~~q ϝwC$Xk$Ւ-6%{ipOA"bzf9O}ly֥kOP KK Ч>Csk{{\-/!ΓE;EWXFjhck؇pꖭjiq`P)XN?T AQf ۦz?AF?=p%q4&}EpO'!8MAkMQ[n%^^UT,lQ4`JHS+~\gv%}4smpS9#+E6hn_r[Â`!d4Ff] YpfT>P{S~dz_csۿK։S?V/IiW^cT @Y;~ K ǚd0H{X$ $ J ,N9zJ`W},OƉFPEƱ86NMw@I>j*LF_M[oz2gv#S[E#VFq߁#ˈAvNUߞf6AsTp(-ֳ*-򎸁V/U9d75zX⯴x"ZkMغ2Mj5VjNۜy m &ȕG\+MY9M)ZT'asS3IIFw6O>_6ͩ_'݆e\>7ƫ$="Ǭ(R%U~|{{HqDWa fۀ<9|\{qT8Hh.eRAbcׁSi_dX;W6T1W_c cՀ;!ݍxU=p2#F%_J aU_3wR@ \ +b'1Ir`ދF##"5ŽEa=ItH۝ ;a6Mdy)3yS Tz [_(m9V9)On x@ǖAyul+nXOc0$OJ5ץ_d<ۙӦo|+8Tm.|nGǴ/Fw*w\ȱ@`sy @L:' 8l1u)aq)JFæwmrm`V6Z>~[bcU&1Nw$ |D~̦hp&A$C Ni-᝔p?0s>TݤVfiA0+r˃CȂLW m ";h MTsJcKi *w]V3x 8F2*$2{-J^(0AVv=D;m oFhIAb7C=jNρûfk3|['QhbaUdiPmXG2GVmT89INC#CMOUh?|U owEB~`ԐQA9fAbU;Мѷߊ`Ũx2/)R*u>ȤDe)p EPi?n4Ww#{ ZHL:h R*O+[f4C7><>q, _aKMjșJH[_|e+;6r |^t-Sdce~,y:}=H˨U6mjy` =*Tuj[/G _^}#f]'t/, /T,7$SJǝJ?'|8zu .h̗ҐKmbb9Vww`q2cj$خdqt= ,GB"KWthJTX~l4KvڍCHS(EA:>nZVxy%-Zŋ4E}vCnܩL`B ytR@uN5=F[}}Mu"j{>x*`tJ a~$oހѝ T)DT)g#q1& r~U t-A@r"D~?ˆ$&3O|tV²k؍>O׼˧HAUksjM㏑xk-Q m`k4&`+tB BoMfV~ ..CdsAՁ2=v63 _T\=$Q\H3`qbnkHkO­X=.Y 7[T,jsB)D2i:53W; )/M6w8"IIZmG9e) ϲ8:ƱƩ`o[g=~sdc*!`Tb6*D/*vS1~J jJZḧ́O@4L3pO׹m 9_NMsO3H: Ct m!ӈԲMʁ Gy0HF>[&9<A uzePӸՆc~=FU\?Ӆ%icnpQma)Ns&:d1˞4tDXOi։^s_uZ5$HtR ۼK7cnQUJtwcAY^mM1if? aG*K0ϣKOaefA/[!u"ifN9Ky DsDX-\FYUג1[$&  IvEarv-"G0l$ohJ+NcliIeXv|\cA!T2mLuH YZlL7UiFpR=S[}GN%s.R$qe+$)Xہ/sgBA@$- kKA! $>ݸL it{tNpJ,y9~#'xg"DIv``hE\-EZ񙫕譞Jj we|a3/z<\k-݁7 YE22Qm?{`lgVE&kј{qLޫL]xWS?fh=b_QSȽ:w^jreOtRPQԈJiBlyrpM BN(=[e_r;ʦߦep`[м&V$IXü䧂`I@'*r]~0-^{IiȒCY秞_F;CNC;U|gS)U4KV'™43r'Ab^.r88"d~r0< 䉿r3asʬ ԛ'}\Cu6JvMXl. ь[-%NUQﮔԩx S-ա<@#3\tl|Jx?b0$yw^Fײ H*GŊG$I@g3ؼJʅ,pC: B3зln/RLd4v( 2D)8>!IJPT IDATyaO v2ap8'ƹO7Ypj7}^Y|؁=[WuVNͬIB ƱB(rar0h ,L0#Ib\53|ߠu8z҂'ھ^^h=sfk4}84,W޼VtR$80L,Gx2ndݿg,F=5oΛ;x^**B<`g2Δ{ۈbZM75(OәǞMҤe4IDcV1JlOU9,%<NtVIDIBuL,pI@pyh]9ƢK-Hd,Fu>oc]uӄkF*WcjKǙ R;&.Q,{Ҟwr@)TT5,eRfc@Tws.$T_Jٻ 3B亹2x{${7]T f]R90Ae\5(Wr@9ħx( mWs-6?lTzb:O[Ҭ3f mtJ mb뤬_Obm Nk#OF/ y) ꋪegu:#Gר1PLAªnjv,\WL\it \j;T;15Ek\[W¬X {)/CZbIMtĢZ3 ^tyܸd_K4ehϣh#JK{A*E~Hkg-m*+8<8w<^e8Ӽ׿6(ꋊ)Uq'uDy}Mıh bΨ;c@i΋b :6geK'Λā96pn]Ɓ׸+s&4cwoՂr UfEZ,VocUrrj~=RjdIjXkBuZPSI-KH^ A@Pṉyp9K~hiSP NxrݸjT I\L2PT}e?00\ʀ7TM͉ eҞ߀jǢf7_ Hoc_YL](C-.{b 2iDcS '8 t߸ޗ;0';qZ8YdnehmoյT*!fi܉3L (fA?WѽwQ@.O I-*UpN$Nϳucp8?N&]>\^*y߹bN2ڰ7G#@C&$kB>~R`WZN Ҙ @! hu!bN^6{g/&L5QlK 7tXI.J &?POXTO;ԏ}e#639 ۓŅSl] Yy!4]_T<\n$uM HҊd0WxZ n"H` ^ $qXЈKA (t]AB;k >خ6aֻp+avtW;KO 䝇'zDՋtĘ@bl_#F7^`u<85FM1rsj(_nmz8SA@`G8I:Uă'*ׁ%h%z8fPЊ#>Yk񔆯:c.{C^ߏ|`ۓbc\1]^D{Z~` P)ۊAV'U6_̢Zeԉ*(d=N52k 1Yq{c!x-IM̉f0 CIMe.1PQc6'SKj[8B  ^f"jclLT84`Ҽ5a2CZaƐqʦà#wn ygK&8Ɓ,e B<`ԇgW_,$|~I5yD`xX\DZLSh#gœCvБ6G D!`dť4¡_E:)Xq$521Nۜteyw'W%ثS8:4S$ըjT(H'ٖXZ!;& ҍwN2^2wx7&k1ڱ-[~Z0ι|ZьK !;s ;t)p3Ya#U?H q$[(;ypisZīW>/}3XqX\5&q_6o]%]jQ+'% v' &mpGл#^{S/*t!E\mWxɕCNKmxRNǀIՇ$$=+ qa3H" ?h,W5 G|U0EisAK:wpXH@ x^=6tƙ`H(fZ6έWMFjtQ^`2Ykl9U6O ]l0/tx&,(SyĬíN<;?̭ &ND401l3a>%Ie@E/xCoK|?QшsI?q T(w7LV~tW]dUv~`p0 c - OFϮp\wG;jE̜ǚs%0es`2ժ1 Xr_\Rt]}y'q}|BNϒeVnpevZ`l#t^܍#M} !&y fw^VҔ@'|-L9'F43Y1PXbq\R :ׇo=ZLVCԿJ NH'-/2(@$`F%(T[q!qPi1/M}?%j8ӈ-:7qqttD%A&F;`54%j*&iK1l{c("m]n.ARDQ*'*+ #T42F@À/!{X ]}ʲE}I?|>RYzPW`U&9 ڠ$"5A&03qG6ifzHS*9u5jk#8l;8暟"Ym^'e!c3*^G0k~F)}.nMXM2yic+S]YN( FoỨXOYYY5M1R0x,@|K@hEBj!Rl®Z}5dNIw/Wy[85s߻ _w)[1 J$8؞ Jb mY{\HixVNBM;l@(bEFױ+iHO納%8s ޢ"'tL?k|uN\F.VŜK[9T D-fU$v?LHǒ8FbjSG ׉~Ϩ8u[7Ng Y\יs[7)DR%Jc˃YcƃAA `0A01$[- L1,=LKDtdr<{oWuc?vuέ}[" OT9!%RpmxT'.8h9oQQ]@q gg_=(7FH@H"PPd^Z/hN.fn_qd+G ..TÒbКNsR!*uSu#j0Qr]2_)6]sƱyÖ(-nU0SʻW$g^H$fbfS·f- ku8jVZyHjT)^ GkN1xXI%-;?D0lIz[7DmI䘛O1 )}iT]/U+EYpQ &A}(lLʜF)ȒEϷe@D;gf .,s_xᗘOxL=XkPڒ0 HfAln8dF2H-{ls07z#9A(Qҧm J'=g"f'>Lw<)"|J\tFy>M ~ZGݓ`R"'%}ߎۯ09g?v:{]OQd'-"X9!83'F#dX)%!nN( z@/t{ pfJI`eRd&ZC*rFdgǎY;F 7FG}L!U@ȗ,m<ꏈf;IQl|M vS [ ?2YCt0}pII2DBL)(.I9 ҩUR l,A-*,v჋ؒg5+$zQĝⓂIIع^ !jK.LU8{`vf(tD ՒvGZnJ TeǒvUVû7p]"AdѺ0i6bFԗa;xﻘ:蒤3jp9xwP3XQĝ q{m>-OIPHH*b8da t|Ll^nYf{KŅ(|rSuQ.LAր.n6n)>Ý5ߗPs+hsސ_:pdJDhMץiS>9@p٨lzƁ T'me o vond%>xx^*^[Rѭ,s!9 ǧ/"XqYuwaHOO7AJ |8}@L PCUeRbmiTA>+cg =ǃ0Ha5͙$<0g)ӎO ?cO~)%s+JdF8M^QBQ9y2(ը|-Oot3PQ2ԃk9X~c.vr ܗ"ڧ[`>{1B"L>hLqTN TBp饐tQa2Kᆭًu {49&0\5Oix9HAѧ\?#{)ߟg!w޾/T&~(E$-LSF5 pR~SFl>$beջG&GU׻~\RbE>_$Vi^*7y":^,6u)*ߚwBZI lSWH4a(I/Y-y}B8*~];Ae>e脝k[+Y'CIR!yP3ؒ\k?Wbcᔿd,e^)xxݲDے0VMdf {}wyچlq]H2,nb|xFm|R ޲}#b8ڒ O,_ Yw~k,$&nJ 0w6btLb%Řg*Nx cpcP$Mzqs_'߸uILX8/~%8G|j2OEs핆-18ߜp&`\%hw BŋqfJn8Ae"`YrȪ,B]8A̿lH#Ӕ(pNAvY+uS)EVdc.V$_VI恵aG;{oXz6g%q("2wbd9w5E%Q"`,FwS!F[]ў$ 2$Ee L*V)}|Ô??Ju_&#f ` &箦,^@#v{d$z%ƒ -a,q#CAPS5/<+I0VƎq5ƛh]Dm1Es.;oa'錧kͳss?~^j)j3Ϋf.5.q2q`ڑx/JM:<&A<=#P@gq Mέbmm')@ ܌IeɌ1bB99ِٙ@ocӢe7=s#P!bHE4OWGԮgʛCӃxoJj{rOu֭ g^V̘ѐ@Yq AHbf2hz|߿s3aޝrsIرqf0^Y0 |-ŰkA$4,1Kfx:]KkcN^#w/~[&:j2L);ʭ(լg$-wҠ[_b=)zzl4UXÍRd1f+nC$)N.^hAȼCoS b9ziwRM]jfjKBjɛ)5q oq:]"K]oYM1GoGlxJ0#jf0$@ƒQ( }`}] _sR6F(/,:\'~v>R0U\?P׺֠Zm|pS^)XkQM.}d<%` _IDATb!{YrulJfڪ}Y;n;H#K#Jz?j4o5O v+|.x_7H3Ղ{o8trIZstNR":$ Qrػ_Ptɂu\2s2 zK̞󼅻7,n("-d98A9!VԡExq(Bt8|cdRr﯊)zL‰+[h1RFM]8^ \W,\A䘿=u[sn*h(x25_ rdfOJf6پ6\PCa"5Ŵ!rDY,ŸUj&MJЭOgI'p6WvXD H/~WkWS+X~jiۂ _V1u|g93c]@Gȸ/_. "`E#ā6L6NXtZ0dM/&a;cm9"Z4D)HX;F 5_3es𣾏O6<јފfqX-ȞIQDq^<)8"5r/ˁ[cf/ZV!Jq-Qe攢0ܴ i"}xQ$W~Q w@nz(Pjx2FO]kxzx_6UzC)( *XW#6'N:La]OlRE2`ʒ4ɹl xY\v-`x[NOUR!>l ᯎ6vV*8v9I>5h<3S2h29[F1l5/ V1ld'(X0Rx͜3 _׋t-wSg!;[=dm0C實(3O(R[`pŖCǚO~2D8(հUkV Zk,A АadA IG0 `vs{# %G &ӐTG=GV̰rę+`uĴv,=1޶thrm8wxsM%hd9Z/|{9yk%PdL9< l;}>ӿ ܗvǨXRBkO+),O`w :Ks㒬k{eD5{2X] ~c΢Gr 鮔9 ЧAƭԮR2gcp~ P$k{(`H=!i^ ,dO XКw{t]#޺eμ\g9D B|tm?jt2иٖ| .EI,]UQb@7z5|:P/)* RAԎyΐs̩*Ks(pՐ*#dGUyYjW+omd,?Y͌[TƠkŘaaSi}3Te\}eQ>Qsx,\xc1\Y}(:];'Qo4[Y*Ch/m_t;uD 8y1bWlC.&MW;ص_ۘ>Pb͘Ni̇OOgqQO0IENDB`( @   -'  &,4 9297% 4 #;7;++"9<34*2#' =# $76":E+ L1 Q3Y7W8_<@)M2U7`?I1!\< I'J* R+ \/V-^1X4c3 k6c5a4e9h;k= s;s<t>&`?GDUGjDwL}PuUp_ZO@"gJ%pH-jQ/uR#ei&C I J A @ KK S U Z \ W SV%J$W #B)H)_$X7T:(G+1!+"-5'C6@2N?VJWGgYoa*ڀSnjMڒkڥtyjNȖ qf99u_`.-c7}[edt^_s8^e{MmlMgsu`oleeNNS^oWkOoXllUUrl$giggm{l\A#MUbOi|\,B ")HB m\(H&!pZ+z*A~J???PNG  IHDR\rf IDATxYdysY{UWLwOO`0 dP\DRD[&e%Zp~K#ЃIPæL*M @lZ][s>?ܛUH!Ksk22|rY: XBb$N~---z8R?-L^ҬUu b)}oKK˿E\v⩿#ꞵj#&8!."* *f!hi^-_3Bޟ&OZˉX 5q*✈Doii7-|?WNf݌V,T/X^bq_H}.HWԉNw;wkd:{ Q_:n~%V,LK1|R< VZYDfD}.f=#  G9-dH/W[ʈՆ9K?t^dbU0[LZHwjiiW?k2zDcC9Ŭ[/{YwRz)Īd qKq \wgV4bKw1n\>~$NHRq*)#e[`GΜ;qEw]⢉|RQK) "[Ko)J1M1hi.]5)8;է/۟L0D"|10D5إ3$N:͊"-?Or*ORMzVM &u T!}k$$pqssl>ܣ*" U*1-2e@jW6N%+~De+GZ#V=R N+hi"?ߘ %ù7GLBQg`R,pM}GX[XG;K[4oZ5} | DH?]~UAP 5\or v5;X9&oۈ/D/rUZ‡wyh`G>hkF8'tVON~7+b9oADP5D?,~Ij: _o[ZrT~8ds/[|70PC0C;?@|u`0=D*ռ{S[œK(irdTcaR VVGhi/cߥ|6ί% "`F/3PUIv ;zO U $먍Uy[A\msO,N[\&ˣ"Z5ƪy`b0BAhiWGtLn~| R$4#oXrlߓ(XٯvDj`S,&_/v.>Igp盟 ^xܻ{/_fϺ*fG\+#UPU1hi1 lIFT+'qeu; @P'O=Kܹ_cf!cHOb/siBl ZnU^P=iFXm*0QMHFEYج3hivxz.OE#&qyKhg0tMF3Iܱ'Ȏ]?$@A*H<H7 R$ȓqF_t@~rqU?rs7w~,_0"q"rCDvD]eHhjc`XW2Cg2شw%NLo~X9X+ %.H|]]X,*'R :_a#RT0DV5GNE#ZO@6 <"%[C4`fs g~]8ŀ ;T_#<j>,pZ=W4ge4 &u<+s1r;V܈ w):V1(O;7VO8(V`lkw,/ vc,h0sg٬6Dh~£Co4ܥU-}Rx,/ ?"ٙA„/} 3D9/]1_WGs^11eZ™SǢs1r#ISʤ2unQ$Ce>޺ kDCK 3 ojp7^G]=T,} :O)T IS'wĊ QW-UOpeO&2ш[_n RB\y;Ϲyw(֙T#wN[%[Pg(ԃf"bYt)F'͟IїHUHTFZX4Jm:{Kê }'+|&1kؓGOAHxی6߄ExB=1US,/-!pxUVcBP aY3Xjg8%9%u逰w8h͚fEă +' __sxR٩,M#Ꜩj|!sYϿ`NFw/@K?Kd.PyWPR}4څ N _p6]sJGzTrIUh8$5G;kb+ˉ+Xѡ (ǀmR]NÇVm Ν4y;_)"шH3f37 ZWpk?-@uu`V<ɧ+go@ "24#c`-@#7P34%4F4$dJ5M GaDT#E舰 >鸮E<6!Z!y~V+@ݨYte+[:)^ --fS)P:i#DAn'k$T1¯2}fQ_>Aڿ7E4+pESk+'qy9&)($@舐Q_/kHYXP\%(BEgJL@`Os8+ϭiz&wZ%C0G@p>9M#Y-߸Nnsa>`5SOQ;_=T=qbl4Yt~DbʾX1Nyr&f2;7hʡ7zqpeh2 SL[HE|MrdQw~i~(ጙ:{i"6TA4Gn~@d`c)VN9:?Ag 8ĕ8+$!&D (ɗSl\@ T>Uے#K,%5CpxH%8Eu!ɈYjCr4"6 f5˧r7zuϋK)gR!f|.DQu:lyMfs9g(>4?wSsg牋g;=mA34­?`Z,Ralx=-1x x|?b84d0 Ez#x\taeh:L=# y͊qδOġYG 6zY8#>n1>ŪՉ :c;+#Oo\J]m)g~4ŹR,o R8 z#PUxtM7 7Ä,23<-\Κ2APO `6|68p:14fr,¸`y>Em\A: EV)3,A b$cܹ3-w?H?뮳G&cfelczµ'}T=1!)x@ػKC,3G4%6hBlzV#s~~ēqrB&1ƻx+V Sb%oƒ>kB!ªAYNW%"w!֏ZYf j3@B!%_S]ekg4+e .=)zVMzj]+1q5qJ3Q, a =B.&nE_l) N^ rDS0c* +f rQ\ `3WCP 1\rfL)P0T4xHse#!Emc D/9QYwK8yRLH3hN~2\:;Ǹ[Pd%)֞|?ar:a^Z j!-Xz6rqYFTRf \O=SlvSFPpLOrl~,Mm`CX'Hm^'/Ь71`ƹFɺd+g'\FYH)$P.u@XX;X̨|G; 8 iL-B #a" wmգJiK?)W~kF{]NEgUH=]g6lh)\ֽcuyKϾ'O2z8γ&@lNjhrmhuэ6 ~9ٛ*E|z~bx' 0D&#䠔8 ԳfYzW]G Y`h`"VMqC?$wp)ş|wByKdy۽Lq!=cg4S4G*l^2/=!҆-ͺvEQ ʊg2He2:pVSy] NwH];_7 ͲGwoY3QճT&T>Da_wb`3 >DJ9\+R{v.9zڠ<șu q/y{k㴿,u~Iw\MR~ϯ|"OTw{+unA  ̓_;O1VJR$Gț#! ~f X}{f)ScN}ǎ/C(qHEW~3MU/Xӟ[0K~YKY7:#ʲˊuu^*>Zt`bP"=!Noek; a8dM ɱz+jC0ryNߥ?`yNx8a +ǿgw~os]oyǴ<?~*5]K)ede|2=P'(53|ܭU訒 a8H~]j,S8]; GtnbG7)7ߩS"`?ۡF`RgdȤ3Ny7%Ɩ1J*ވ%ŒXxe|ܴ88LG `s~ w~7^vHvBsTcc:Bg3c'CjS#v[KۯP>x8xoF.¢* to8&³(xt@\N'~b۷H!p„thI`=!ʋ.i=.bWgG?YXBG1dC[߶~/"o 9J٘v|Y˿ܵ{)n ,i{mhD5:$`A+1QZ9eP"ssLv"x4*<$ f9~q)zF*XQYbd]Kl[d"K?r>r(0T=k%_=Cq^yzM>P@,86O嘫>g{}Ɖ,>׳|c0v,qcVD聞r8p&4ES'.ӓyT)=a"C̦v^-YV#hv_*U.c<2ձk}Kn@Amr~V? (SGdd&S̏ {_X&t-o}L.;XҸf,SXM;X5!#SzrY5ꦧ5|%Tz“8^0832CGf;( .82EV K+Lr=urVՃdž֡Wm򦩩Se"f2X`oom֠=_JKq|tEJ)jj܄ '0 #sp\b4`0ܥ2X\? IDATb!o!h^wٮPbՈ8xHM]b44G]^/*.›߫ H, \Qϧ}}S@sWV9y<'OPFEX̩2J%r1_tW+vc+mʘȉg"ETjCQeezy(,pJrt1M!qf;-F[Gr+,--SVu9ʺxȻ⛩~?K1508%ʅdd{]* ):L6n êq=?%Hb”4[LR7.]fb\Q̻,J=Xŷ&|6VTdze޳^|+x3/q Ν#J:EXP8QUr.{oV%cdCӓC~Itre`l2"Ռ167mhµGH ~Edo_|x1+[+d=7l썊zmJ~vC d]1 մ۹KؽM<"M4٧ڿGM$B} #7k W||XOc#K$ꅷ"cP_tXXg77ٻ{.NH= 2U0E<˸O(-ѡTi*G z3ϣz$hL /&f{C|"pnZ{%gDABQ/JT[?& I, > N*o)}om~:a+o@ouaީP%idO H)0/3Ug x%L)з5cBDϹ;yrf6y-P3RKyE$3*%ފ%$|^t1`RdU Գ_.<^s%ԌI2vSwb5 "YD&A'Ճ #w D̯r*sؽhzirvS=Xjc0):cS~{IqJsR3l96ԑ̘X-U%i:$MHgޫ9ӌ]1rgw83-q'^K%o ͊bBK@D?g >4%Fu ڙxeKwڙ WC e'5C' YF/ཫwih \= x,2*X^Џi G˧[S{L#8lW%ۡ"Xtpyb. *:4_1@%z,:g `bbuRF{|tWW>[Mp/Ɓ:] '^KS)[N1bklnJ܊%)7R]l7nB}OCQ=xj_~"\t].]},OuXGRFekgq?__+E"ӧQ_&G^4̭rg|*베NJ,P6;|ePU6LYb+Fމ)S  dpi?dE}OQ ?Gظtp*ΞSѰΩw{/-q>%~>(IբD]c P:l:ѷC[=9ϫrJxPN %.+@: >j] v6qULN3*K碙$9TU]qAk790ngs |Q݃fHBwEr - E;1vmG)سĽy+V|3N* g3bŎ7z̽5n}.W~v]ly`n3xn}:Ç(i:E,}/re\ЌdJM3^%vNhGyrpGvMϜ*紾jne9 E۩ag-8G,%; LYQ'!La7F`/Γϡ4_ن7)8jfߊ8{D-qMfM%}qf,OBɫ|/?P\nqoѽytTӺwR>9iK"@,K=iZuеFeq^3̌=$ވYYt<>nѯg>SiSNHyCތ%b]Kcݙκ44Y`NgP͖R|Na7X hk{ @wu|}>UŶPq;go:>[10l&L%>]Se0e;s @,Yu>b})pؐzAӔc<¸"Rd?%@ǚ>|& 1;pN ٩Ce͸%>0WCj0k jv K83♆V5ebK(x+즄k%ɚ4?!51aFqUSCk$v,-Fy+5|~XVgEV99bhDzb8E|Ӧ+\;$u-)_KuiP=QNd3ru:T=l >誐Lb 1*;v u}>4DgG'?r{%qJûjN#ʒss9g}FOS)[+o@&*8wP9UXSI8.u A_`ʍxHKU3#;>\f"W!sEνF9N R/ِIJL,1JF?%6SXr+WԱ(R1yD;wH#‮|N| 6`SZz.#ݠOwcx΋.93r;,cs[S娀8'%#GHb*Co4ESS|mM%>?@2Uı&2j`G/vE>9TX%akq_OQm\DX=aw~w.ώ%,&̐=T&&ƁSεCK R7ov\Gx[>X _ c.8N-5J|GkbY\<ĸim3ivɘ3c=ޢ6~p>0 7> 7\=h4f E")ɖCY#Y<pdڿIDZ,zX%Ӷ(Y-RD3hFO sv~8;Rd[^׫z={mm:c`*u2slk݂1#QA) x3TBF2HQBD%OT5w3m "ti8o wD6|]TQz=c׮XcQ_0v^ҷSƛE#yO|TED#H1Gw`;7oQ߾NFwfֈ=|*N8+N9}oNJ@Z1r3V *5'uD mdfB ;32#) $Q U\mQ boi-$Yx8$ˍpWoP:@ 啪o?Tl͘DID`wXä#zwjx+Wo~O/Ns>7׭A|3uXk;b%9-N:"Jt~V"H[{\'\K.K~.ʵ=di6gUm`aV׆פ񍅓1FySz#l`0p:5n uc$p9PfW ȴ>-7Rz99!P1WBNFFhd?*bjv=^Y cوs)8owXNgqj,_+aC~wq73Gv Oٗ_,/ػy 3BG,Xɀ>Ul!2f>>S !ww SM?2o/T#i1Ix?_Oպ#GƢbMbXS.cYcy+V|.1IL@iS [`'IJ&f0ˡ$/،!#GY>{[: ʻE^?2> oĒD|X3b4CBcD8mSl%k&򯍊GZu ܒ4UQ\8[~x>\'-HC07q5~/ )j`z1LYfrZܭyHWfH/VcSYYssx`d%>;!`2/Dn[` d TnQƿo9Mj}W2 IDATՈ"641= :\p92c.,3,Ounx2Nߋw O+.Fz7c2]ay^29[3GW)ZBͭz*A[Y&-\ 5  弱bKxOB#'t=mj1\c9',#_=ԑerLjK[隙.[wa 0nj"G˨h|,dBhSL-^yҤYH-+siyf|F$Մ p䛍ҏ!֓#d_^a'qާS60&nEz~{0J >_pxnĚ/ >V)7o)~p^zYV8:pg2Ny _CIԊ{y#[1pKkJnƚJs}6c[#'4'#.Y㨀Ɗ_,v|5TQ('ܻ[ 6wnsYcT+X#8|=(J&g_Rg1f(-2oMr3[a89fl+U?Tx.#DH9PP[04ٖ ~I@ rfyxX"!xޥhƱpdǰM1M]>'s1uk~p5bϽl@X|?JNqX*42&2i2ƪȦFhnŚqT.'CsX:ފT5s-+~3\@$niZ0|ٹ/͊b`| P1,nw=!&EA %,l05fp9Tl@B9uTVD 1f]3GQ_4"["2$AOܽ~?Z/K&XhM92Ф6f o7ETXr2cM_'\Qw>ux αx30Ø97A]?d(m6 Е$1ᨱK~;Lx[ku*y&Spkks]cXÀH4?& 9'vFP ]Y#g|.xf u^dfέ^w oi1&17*NĀ9 0 Np^/:_4EFU@Lkӆkoи&p)_d*j=IqE|(fS˚I1;.FN(z;SH>l@굛MnVkoA1lE徆i2+xBLʟ'|gN͑j׌ee,g9Yd2pM˳cb# Ǒn06&KD!j`l3_o\"i[ 7% (++eWbͨIF v Zn@k&d}#ճQCd7NazֽcA̦|,I3#]i_U#BYc# Tpz~(0Bg,<ّ#cLE77}ZpW5mƇ}0+V3kO)=gW7=cd?BXf)Z(ڜu9} &Xz2-dyA7Yu:Xy[շu8k3Ł Lr/b.`Ls$Ūc.vqLj ֱ!P1VMT j5s\5CÒx*&#4.D@E#6 ޥ-EvV@cS-5Y/23$DlMMby@8*R|<;G/x&$nOVc"ݸKA!CaK`F;%C~h}Ls.eWPߨ'|=ݸ+4%S/Lے3Ⱦr;ּ&knx'Cm8/kʢ jw8ks{yq8? a4jj:DXsӭQEYG"jd}f=q8mzyRMIUG;,O"Ÿ j(U֕ΑsSo&b!FVŹ#835Z(l8BZM+.a 4Kmn)КQg῱*_ǜ˛;10i4(+Y#S1t<$?8ȫUkx~sӯr "kVEp7+JJx|$j+߉[18j#b)kS9?$:_9(^qc=wedc'N%7XdI rg iW&Y!ʚ2(e [WK}&Cz7[kP#tQɰ xqu(F5YAzͽ=Qmضq渱/N4>T#"UdQblTа_Bs Q :ez)&3w`1V 4*MX?uX4~KQ:]lȚīWׯ.2o?i DX151l\5w/v4>ȍ1PQr>i3NBɃ83 p\,u8mc+u |.yC[Da 耽 u;믳wÍf8u7j/MBLF"-{MZy$EΏU_4W0Pa8C Иs}I@p99wcNwb͘n!_1ٌ_!/ڌ Z? 7"|4hU p.׈ R&;;ߧ<"1LIԝhF,5A ~dFM=Vt"ƟK-Yu #IG݃VqD縥5i,0SU S^&"{Vl?S4~@Jv!t31+Z5N>I2aI0Cb=TӚXI.~7|#[PӨ +}}O#vFGR󏐝84$Ft{曄7?p7 8x`ħ1i'o<Ȋ?rfm 4*p43|% |XdBN~1cp>nĺSx] 5*}1I6*F5urfhjErx&kkދl]G Xq6|;t&wPAb&TThvn|z3.2̶jv[|ˊd*xh]RDc f]R)7_40XiOTvB71_ %{\5k@eGl5!,YUi*˄ӥbl* oMqwb{x<ᗦOk'1`+6ckM־~|f Hم_ %*&sit,ci3k9;OxݥK<łX!PE⊂wBP]ZN7'3Ӧvxa<בWvBlVny ֥dqG~$%jTkYdĆM}Ab&bڛcͰq^IDƫNa3x:8Q. b&[aPє\"$mJS5[2n_Ǽ)$vɧy >؄ᄙD9}x6yI2}3ru}赙Փc!BʕP;Exx~c6<74j=b* N8֞YxRsOg,w8/bY?Y<]NP-g:"u8Fl?a&:o5UL65\ϑ`0F1`ؿ]{Fm̀, 5 y?A& QŤb3T 540HNQXq3TP3񺆃MXJ)D}O$#8kp`҉-".#֣$8AV $m$5ZRTe'^mjGGq2`K_1=*ir|C+ d2Fo'?~'|ZrEhGkP?e݌5cZt1+xx:"HY ~wpa/f|C_LF̉S [/QXҐ\ʢc͆֍*R Ib_wJk˦-$,6ގ?&%JH`cdZw/i=A;b$[U#z6zP?IcREP-c(-'".8ݧW?> PV %|d`'# \F}6::^AtZv,y3XXOeI* z9# GOrn`GOdd5q堨P r%$@դ36H#fx<& LjFufjW(ϔ#h]D暿 o9lVI0ʐT@u7i`O#ǧlG1xG#ok~ӡ8 }zΒI[CXO'u-)bGlRLJt]&7![)ֿELwC0yVoH6obqiqV(로F!-Z hws\skW{dd0GD&'_0M1tZ:-n rHL[}~,85썫a-Hٽ8/\А 4" *hlH5Q98Bh &{Plɞ0?:ӳ9#UnĊ]Q $m#Fj&mg5A`f{ZP3G}'{wpog{8IBF06].q2e l,njtN>TcZMG0]>.!(qG_;KwQȏjeF~1yCrȭXqsQ4XzĶQlWX98rs.dV-1x+}bwvq>R"M rKdZ=wvd8 8.gO %wNnW)[E.z@O 's*Z$_tgޛcbtEЀA2R#ޝW'ޮ 0Fۑb+$A~=g*8̟xGs, 8 z)WLyd%L=@~WäKh.;oT*&>?r;Xjaa:KiP'5@#?E oݿOn̬0:eO22f&0hހ>mDaC#>l+YSac^ '_s.' p '=~L7+vn$ e|$|08qDžb<,Ol;1Ð#,@ngQD||xOy 0'IMVFj/o@+rp +걲xڰv`C|z~_[":KX-K=I j\1ְjR G3uWX?P6@&sQ ǂ? O^v44nU*|2FZfm֗jĠ"wҒb۲2W~m/_d8O"j\RȺc`c  p'|]$t_ ('|XoÄUkb-8̜;u˘mZ*Ǽ&oLK ޺=_XaH`;Пhg>#gָ`B*㱝E32-n&="#LgG~LlxGr/v8m!%м7m)ޏ9u6`1ϝVa IDAT鴛Ixo1AYra~:5"ۥX KᙳPRX6|!t k{ nrj 'Sbr笠Cq`mJ(qIy7.a`3%z]J'r~O`m Hzs% bXr`[#es?(x'#daJ&T8-_DsNd /ddL gvpK7)Xd_9W3ފhBbSd]5ͣɌ֝,e|<:Md~ |\|{9ewY~BoɰTΉ:0ڊkL[5.7H&S-}$ )vV-sIf{X"lN'˹j^&(RVR;jRb0F':H PjO#nȮ*mQ1BwpV.z|Ao("!= 11xK12s>;Bs<<%K!LZp./ȋEN8ͣ!3P%~Ӟg+ɮNMC#PaE@r@uOW@fq?(B;O\*yƓsG^du*J:mը4̩޾3up[џ$ZO{/ ƫSƲ+MRjM>%ù魇DYUܚlV+;)!;1uaWCg1; ?ޣ4=gqb4y WAu5unjnυs3bS f辒 0/o@2nAU{đ7ؚ_uYW^MRH* (&k`HUɡK=:mhjUS?c6!"o|v՚ #?XpOt,d=O'+xgyy]=XZZdCD9f Co;n  F--ػS 87?j`/:.=SC{(wE%izK c9j~cp}d8g̼}ۯ`,œu A%` W9o 26u%މ5v Va+YS C\)0nd.oNMzBſF> U1j31ž@63d C70-t'R>h%\X89B'rfoVL|Lv20 pکDkd' nIY+uFUq#D kdO}|3|.9P>f=1r3m'^xǺQE+ݚjd/}`pijx,, ll{;{~"8f\yodft 8F"֒6~i°c۬+Xgq|BIesQs{k]asG!IM7X1h=S%w5fFIYE&F!r' İnPM^^0IDXPD DbH,ƒ,-"{V־vWuLirDRȁH`bHM=X7gC   (Hg%==,U]{fVy9~8'ެA"̺[qJʺ91mgX%ty;n*ĢFxxfo#G@BLV+p{[N $'[H asޤ UM/--]uI{rϒP25HL5ow 1Kũ=qc ySj MA;rU|jO(˵Oz]pr4 h4_/gt7nK>\kk?ӣ2~AYQڧ.g 2#YfeARt..BNkRJ$QW5$fQe:IOH>r_[Xt cKcXr)e2`X_He8uLcRL -yj|.&drUn%&4%bPx-8 R2g:wou"gaa!mpRa9yp Q]p]9x]Ev:C4"Kg;r#\h;Cxr3}!yI.Ʋ)K#;`[>9FFE p4oRUXï Cv{[FTc+iv`'+)~s&+%^Z}?blOpERŝ 釸 JV.dė5+NH-1u\mF]y.dː[+RVkRЂW6L$Iy z15Z*wlX?1T"i& @wt֮_r4o_UMm8 4\p ϛ[<3ax(# jtMȐ$FK(!EBX' $X+}n|V@߰!! V%D.%t,),WxR?'CB3&+2Ij.}ߞp'> kH_mDAjpY'?9.Os6V!wM[g!Lp7 F ͵׸s6+Kiʬ.y fi;L!IL7J$ 0x5I nZvAx i)i\2t64ݚj}0P8v" CwC! !v]O䅾:+8[]WP! R xwཤ\nQY|m8lB=6n"{klHzn{w;*ed 2 -$ln*qݟF~ r̢xR$9u3#|clmނ~-Px{ulkovH*kAjnb{-|&`)9rXm3vT-/< w:zhD]Rlrs ֌w`̆bUyZt}pazC@0=TמuND%g$N5ۚCdߖL<$p|TLrtX!7Vezt)d;\ [gef4L8gB0;(6^IYRMЁwqQN6z)Nݚ TVnj6_3T裺]8't)5ϼ7];_P$wP;L*%cx~o`>p:;AjAjw-U,V5K :IE90P^=\F3 N'81Y(5IZ Lkq ehɁ$HZ ԽYx't +|9z';6 3 cW~ _G^Fv9Xk3/HÍ$(I/M'$!P&ezRQyψxruZABԄB2yO? 3+"oay[E*zwjƘ0o/hZځWOg͠Рb;S_>Y&^b2f[lvk(#=(F|[FNhIV٥+s16xygUh :*ؤ^BQNҼlTIJ_=3j3w\xItI }Ёdɹז^&v56O{TA@ Bֶ\Ҩ4zc.=ԑInbkޖBwa[ϿWR/7 mx՚;gVX00`HBn3s>STJktЧs$2x ;Mr^k^2 [B{8q))0 t&x>Udy:.PgK+Uw)ēfהa>gXË\_'i,1;=vB Tm 7 Mh^,H)a_]w]qVniL_-If=crZ҄`EjbFMP^$oo PsO9(qfSasJCEC6 ڦ?Q_ Р-9䨌U- 'jf TJTB# M*2lpQ،NWF(@wf_Kl.8z3 ڏc0Mu*֋]fsJRpmW9ȖdgHT/LO{~\'x%%$Ag*HӔ6@ Sm6 kR;) P "X:u*'񡭹jJřh3Bg['Ǔ{oj!MEH~ޤ<[$?LЙDF$\^f"4 iS$ c^H2 pvg: bn~!B<; Bdo\$Jv]b˰4ڧovS,%i*rLxStW K =![ٖ#Kd"CQk a.T LrȒ[t'$!@Wiɑi$ i䰤#fg)o(#I* j?*!:txR%~Q=,8K@IwC "|ؙ?72 %/5q;=|Y@ЍGTe($UºҼa2+sYvq`Ex6\c8lu$5V<5ߪK~¤QH׌>!y.6$!B{])قs 0J` Hp̰hj_pX=Y1'!۰`'kg7ҼP eV"[?] IV#H}p͔436}%.1;<Fe+<|^N8_aI9H#Y--醠IL+5dxX{9NFD]ULn_2ٮPvCϓ/.˚tIbӠ$R8ZPסS|F#ۓ df IDATKp[' E(ϱpԄAZ4X{rV<58j؄ے؊_%06$5t8~N1b2I@1~%՚ҏ94^K ;7l h|36+p裂tW~:O'Lv{'_`7W.k4G /߸k/t>߇!]Dj4&:*$# *bR$ѳ@weIt,F$UA"5A1U(nJ~Jc!lIF*2$a ^~lOKO5 2eg5Ǐk\-5da@79G`BX;[ A37$լ,:$fyeqEUģ3IgMR9GU3XnO6^m k=Zː"  @j4U_rrPe)?<ݩ\,_dR ?Tm5P!%ƫ K_Ȼr2QqJ8TA !"\FLN"p!0(g{7_F&}6ߥ7=A85IZHLghkW'x}̳ BGІsJV9ݪ`9DޯP1XApDC0Dԕly!^S/Hg9؄s2%RLPZ`4*F7G/k/D4])"4+?] P垇_.8}bqv D>-' T-kCOҁ%U6;*Xm{?;r.H2Cyy;9 2N\' [8>t*Pڣ' ''vRҤӇ%ëՉmRFіVe_po=ux1w΄vҞĖ޺GJ%F X++5Y?`EGUN{?\9|9+XRqS'd 2CMAy8اLr" ik4`Ѐ@I 1%Px.9TG³d:'[2!ƼQ9b&x5Sg CiFe QqSYfpf}_S>‚ZXY송D8a.V DjTQ_yw[49= {[ަ!Kx'k8^\pM#|LMƫ=pBiI .)6>elЮe--s5mC]8B tԷ4'+LuhLRF*N>.\ 1 vup.R^:wMdW3;v䧎EU@%OrGx%%JG[rwoGo&7P%A؞\]f'ԇOt(|{Ė:GApoI͆AG.J)tq} ?@w{@98M;޷”hFJ^IR.iu7wmX"0BdY?! >;xz~Y$5IKrKVL,p<,DB!;9R @~P(3mB:w?PFr]l)Ǟ~`H?RLpugSqMwdFCE/[B x AkAKN%ݍЅ~_I0̶= +neZb*9h?U䣚bV3;dem*ҁB%*T =>٫(Fl~ʰtMk9ί4IڙGS["P3\םJj8fraO׫Uw;˪w:tXdPk.GHuA[ &<'XWTKLJSD#r)QUxfZM3Hd ^oYHH0 !ɷV9ߪK*[Jwmٍ^Bw(517xX>z׌2 GPhҏp$~V=ZByhMIln>v]C]xUcK&χdoΘ:k })`HUQs{3P2;q3czh9zVl]+әID/AMhҌdCM887cf&郋mR5aA\K2&ktFVFiPJJf9,=SNC%#'XŰrS1$9A o\Ǥ'p>ȉA3BTlw+_Bd*xP?%im)Rc XL\w n|H)K2q6jΰO?u+5KI%7o"t{1_Wq]qX[YhAdR !NG&QxCH,A|w4_iHDy }K0' F2GCє'*uM6\[ M 䧎ٱ=K+?vG;X~l G8O2SO&7̮Zrf%V6@Қה#'elܿQB>JR Jm&`>nO|<3>~B~\1AZWT^C(\q+ $ n$pu0\q{p{{P b pXPa,׼*%eBY\#p icc5v\p6ޞߛƠ^ݸ1v`]S ~ :cp6$\}G5QD!6&N5 \4N+Y0\b1ڮV~KJ?w4:/hs{xW~: -P֡c]SAig.GPJztdˋ~68s)k/k$]7 u"@hv}9c+׌ Fl(9W/k୥ǖDkvR]7[w(>!LǟZ?fv6rO*z'AN344% xMi>o:lD$dӬKۘGG{O৓Y "6Ј3Zv^A DH]-z8CF-ir$i Lh? Ĕ3yܲE{߆.wg?{hqF@lܥ_eEoKol+sLpf4N7˯* 5i&%aQXjTFgIF"NO,O*{6f͵ѣn 9IOBB'lE31Ok\tS՜>W\;Xϩ\nYtyG$[)~* T? kG_6tgEtBIR*d2AwЫ0C ]I!EU[rO}hȪ,QeBO,z@'T{#1u8j[<]MiɦA$5)ӧ=s_ 箜"vE#9  !|[~z6?)Wg+-8Gv"y;odt B:JV,i޾iYL;psyxM9 ɶDrxY|"P~. pȫ?Ғww~6NFb-(I>39}VSM-SKaV%l;P)A ¦$KЙrArM±nF4 'BڅF[֫ۗ5㽊ّi}'$[?nnD'f;*_bT6bgcRE =3#K>qbfΪ :V2> j-]QHjEG'P)zx)ɒg9N'%bʱe[3ګB@iPә`S /iL7g2= %W~/{Khaq _2J*KW/r||>G;*tT )Lm9P:Nk Ͽ-mԸ9;N# nVAC!.؝m|j,u~Tn/es-P=[Q;l&{YƖ_uUɄ`,]I ȨDtC -qiYhlB/|~?-M?Hlf~sB/{җ Tp:LdKr$tL]tfh&tV4k_N i,r՜|(a4noc?>9¿hgюmKa%K_YMQ\[8LOEIso\0И3|-eVR"Ȑ.[Na<8:ڼ\L,c;L2$q~Bw] ӒblPOpK~d)O-O+> @aٻEZRn\pxr|mv6?;2)ZӜÓ {#vN?# )!%RkV.BjN" ٫x2Bª)x1?÷)waQ} ;H?`9WcCA'~^ #g9nMȪ*A@?:mkg6Oߚu̽Œan;wmIL7#5$ަE4 s?'qigݰ~'e4S]P0~fN,×u63zd| 0ݨ-U@`[NwBo:so}6a"J}a/f]ɣnI_aU `uvżF-ƿ5΂g6;P,6`h'kRt2Q\oΙ*5=!yu!YJ&p]spdrHq_ @03g *~Iwq={l`=yͳJ~<ܝnO$nX6AR,SF/-8Fs#A{"&75JYF$bD 50 ֥Wk.wA 8tN[K*c︚)uTT~(DTܠ'?"x"CpxwvcJ{R,$!!/%BK&&::f?u|/S<]$BF ^-A?RO:jI7_ qZuܟ0ǡ 3܂@2;5 IDATPoM*7TJSO`GZkihLڷO:‘;qgO,v!TK5#Iz0v^(+r6{-c!1 Iy:U]Ĩ$Ig-hf}+-DJ]>S Mx >ـF=toU:tw3 ls,Uz{ Ԭ3 ``;1wMˡqxU|7Sxϐh!YzIc^x&5=?ϼu@>%4![iۮ$B$]~&btֆ1l+*e)"`q2x#+YV"41{d?pnn~$k*P4R4,wt)_n6)v^灷xǏ)>ͅ~-3Jc8ca.8?:/4n#[mkœ?ْOKzBCps=cg$>ϔFR)(ŚIR6቟4d5c `?t<0[.A$ f7tvjO1<{'qbN~Je2$Τ`+a恧SF9#{Dpm|sx~9(JEbPRO#?Jk͎,*NpaUy= \4j9}\ Y0= _|%!m9FTz]s2(#Aeܢ('9=&g볲La z'U\ /jp[ GSSxa20if/D$KĤkJЃun;V"Gz7~xy{2Huʢe!׮@2?>?k-J}DEoBhqAw+ pM6;u΁ +B`?* LV4TvIzmRfϖdyoOy;5ݚG 1h+,ɔtŃ Yԋ#/pA_lJld!h&n@nt7溷<)s~;[UͰN:=^{o}[B2L` sHfbAq޶ܫ! 9QԸqO>~|}[z1~ߎt 6+SJ99؍Wg,WLdJcc9rlW]J,v㍍up,D<৉K9Y[b{;ѽFѺ`b"8wb.ގ#x0U{jLTp ؏ݐ-( YW0UsS9YSQ/{Jq 6S:Gd !mOӢM%Η DsHq&zR dӊ鳚΢Ė#0V5JCaȼP85>+x!gjIޖN>?ϧ^N, 8"p.N1oq[!,)M.T#!&3g &2 TJҮVLٽ-w#H%Ad+x#v~Fg]{ˈ>[Pʸ'"yfJFh' & ^%kUs( ,\6 wYY30S˘Sss,B*ܨbo9Z8|\1P;$%D@4EUI2 bUT-mB5@B-H A QJC ՚huQ,V#X?s#ViYO)31Yoet-8apt<w'V1ĢQjߑk, \}7uW2 Jl6$2ߠHkqvwQΣՌ"ys)CFဈdB0/H` Dzq^@8k<'7ys/<F'gY9 \δm\oh!ǔaRU͊8^qxkxj'A$0MP,(:rw=5,^3\p*V?@fo%`Gΐ y폰=Bo1ة[JHa,8sT$-: iŬb悡 hA!!6HM@4P}Ih A@3B凇WC5u_{J)$%︊B.eG}dkj6RGx]o@!%m+culr<ڳ&ѱO'V{5WUj@G 9$S'0"pJ*~>BHZE7gfP7aGА EI `Qi ^XD*y YnW w=:MߎAzw6q{ t QuN'{I0{M֊&HXd{\1?b{Fᙀ뱟 vǹN^6(bсovt!y/He"y%x iԉ̿@ޚC8u$K +G Q"o: Pr0V:R%d6ޥJkP{C@jF^L7S?+D -`GȼIw^09Fh*+UBܗG7?:u!q;~F$&o{F'x>rrm[O y[$}3p+dZ|e_.h0 RS'9^A Cv<|N9D>6N<jI=(4iI$}em<mӹӟʸsY':ֈG))"azkdD֙ń!V7 {.pVVN1AE !M4jc/C֥ e(<\|݊3 #&v+PB Zp Dpr6:P#Ŋ5R!Q*M TkZOf֓8o!g1m.kW^vo-!YL &-)';~*!v̈́< ԩpl_Y^c3R䕄0S _s;b["JtUlN$=isJkOM3s:F(:ToFyR4D (,js:cYDpbq H) D,:{1"e<0|TAr^KB'X_3m:JE6Ȋ!J)  (B(SYǼ~؈bXJԝic-uxxN {bkqs ayKo8|z?G_@O/"BWs!@YU# N/taĺ;BiOa.b}BU ,=)D* 2y[&1 ڋ]X;t(i"`1vAȊ.GAѡ gF@7{n?QBoڝRB[V³(< y6u޲5‰ $gT$-8acն|%tQ_8:9~bX 2KjT Cjs5?蒘?t<Έ?c񛸽Uc:TeI@X#gPJ>."VHBx >y+BE(==D-_XagS# 7= z: O\ER`4LH8o|<; .u x5uJ3׳k*/|ŊP4fQst_1-~03:θ3nY)8RKHfEl]@zrY6 P8BU&cR3qъ=@wn=W?s%ZMe(RP4ݕ/23ALm'¥x Ss$dB0%@gIGŁ@l]&lE<JT,KX/ H~մι%,pC Hxq7`b:?׼ z5D/Jŏ-i`E(ìŌTB Ia S/c{⯯b`γpSRqV.HCKg q>-52Dy^lG _G/fnQ;PEhv/qěaOr3!>c1߿*pձΈI韊e.Z-΀ޖIcPxlJ P ]hGmʚb/BH'!dn <)Q:ayp 9}:ff}w'ljG| FMz KaQH]}E( L!t&;Qmv4d*#l58sYX̛vcJG!bf<Doqw_l&?@.$ۼh ީ^YN e!"n|׎*kWL+R%LrRƬf&'%!f%> h (xf< *#2{==3oXj^>%%7:f A,C PdIVᅪ{Հm}"fj H<=낗c%vjlSKr"NL9cCSUg&MFcIGus!U`v@6|k?(q%ȼKis aUU[1>Qr.v>ʋ襫ȼKP C@ElRb{;)k2^FW[~F}=7Kv>x9sJ!(JDC]բ"zFւS7S,W5G_:msC&#_/|ɆTԜFH m8ϰpx?@@.cǘP7QZ&V:`&e*2T><$T#ݻmBYl—{ƭ q 7>FH8+FF BT<$zC<95&#w G,*&_(B11S_gn>ɓybOf1;DCV;IVP57$+ipR;+d3ӿJeȲObSZ HSFG~x BHdFHM>{ӕ)%QF#AыWتh ȳw?wu."&ԟSw##ƞO2p"I\joXP,zf*~YgtVzvwe4L)M.V/Jkv"v}0BD^Bl%&Lֽc-j gy?Y߯cF},Y;n* 7EG$Rt#lg IDATNк|Y)'GÏ>¯>" \L㋾'ɵFR&J"5sBNAl?{wyt,4*QY; 7rb<'&~NH$$n34%I>uQr[Eh-Cz4S1ϜxĥT}%ǖv!prDN^uLP39 bE9^h/S{(БO&;۽ڏo4:@ Ō  #4,U$a|VAFtU)"(&%ou4Ui=<_ 9Iof TwK_d-t;,2t1YTљ>Ave%+ `+n#6c4pkJn͕h"NWE$_YD/_|7[^`%ب,bʱDX~rj*).ס-=!Z kc|Zj.ɸ8b]@ϗ|6d"Y]G[[Qeu^S(3+5tm ľ|>vөQmA ; !AGb@yݎ-9,Cbɳ'*iQ 2f"rld'O&CUVm*xrǯgai1iX&AJEb6c[!6>s.' _Kw)5HF._'s|r?v4O}h¯w1%j:1>U>%vMtsGe%k_VCiiwIsnpvrsZ:h,76nxqT5`IjnJS^n^yG妵r w숛Ñ^R_< 2_{(ffy!~}ּwD> %r,N~b͌xfrą_7#1䥠,?rP'17-i-).)'}S{ODӏ3^{ x4;67I_ ({ R0ZWɦ*7.8L0BB&H,:_Ew e?G=:](^3 A=?)/H7#BItVO"/aGy4SrZqnoTcnEDjE^RK sqDlxdvSN=wut.Eb c,@{Avt դ0#g#Xxbmºw]"j C,qVWMsBMWrA?0B&O q}UBǸ88 e |b8@O.68E˺ 1N-Ͽp}" W vccWwV$g,cg$N?^rdDxGFRbpp"4!#F[lXu%G-*d%UϱQhݱ̧ g^Dàꬁ$fׂ@?1X:L^kvO@l=ȳtΠ[tÑOħ&gB܄&3L~~-}?<—U.μ9y OEaVB,:8.JjOf4>|{Ũ_R]z5@8X~)c"޶޳ 4zE*NJjQw "'tR*J @h(F۸`֩+8W>f]Oǰ$5_,!֏Nbm$5Ye(2K*f44R5j7*O9e !Np"xq.N HNJ׊.+Y ]sɭA2e!,+BRkVE1!pFh3U0#'e6AòB-$;>ZW~Ÿu@OՔO|0x:i)9-$C< ӴpKtͨH\iiLWPiCцgÒo~eMޕ,DC<0K腋BMx)ܫ רE\E`HN!3xa~v7 $3{1 C"!K:gNjJ7w|W`9q^,& %ڻΨDHݩ8Y_Y\FH/*5,&@Cn K{pkm[̔ԛMG=1d퇑K_=7rdhLG2 jև%'nɜ3 ]!5`(#f0)>i] 6=ŬٺYR kRYPgг'So:hfy4@ S ƯQ|ku:Z> %)=)O4_eӘSi{_\{R<ʉRG%󼏩F;va``wWG[ͬ j}11b=TQ@N=w\UXHHh =x[_{ir>k3!*;zD7d⧊6SH~q;8Zc- BL+B7G"B($7WlR7ыv"vjpri?ç7|-d86 K,Qy?xX d̕+9<{o1k7q$H`xh>;^FwY !d<nC3dg_!bXǴ~0sN"l`͊w=&6hU}^E Ap#|oG2MwWZڭFqb}|GOg%j#P:yGX iAL sy> S)? tU6^Ĵ~UߵR(e0αlj\~B0xX/ts\'?ɷ{H-`נ zߺ![rvŧ.~wn;mQ*)fsr?Gk66zdAgQRhe%^9jN{QU7ԩB2Bz iW*RY } (&򍜑"g.BRngt pCAamhqdM R_@׋B7ϼ ~:gK0.6r>AvE 5?F!KS''_ox}e|V=|0:;K>CC9uv?ꃐdE b^~h4Ac7wn6#$3ɥ?)!ƗVBhYjȦLˮՐLA%?or.`#]Ɂ e>_imwciu#,pE ض#naq-r/pr$o>&{-d:a/|&05aṜ3pR$Ws`ꔤ(m{T=`ĩqH-*$z7ׁ鬣j;Zs*p㰣0`Tc>yitd̅Z332spd*Y L$9 4^ bb+\~GhM`DeQݟ'?7B_Z(Cՙu틘/"L((Woqow]غx/ t.Q\^#{qxRmo~ Q BgNВWzZc.p1}ܽG >Jxڻ M >^Lx|"0h]!xE|V< $5[LLX3v#)wWz|%3=Ƴ#HT }< {m0tǺ\QWx[J" 4̞Bar$YU]~b3t= hʥX\. yµw;aI!tlXU lC G?a!Hcߋ ˣ#Rpzt).{\}܈ ׆ŋ!Wbmxg|F\׆9 ǢrBHj nɐY'{ɻ4XDl: !dd 9)w]Rh89uk`+.f xH8QrJbFt՗6={w+,3?qox9#Nc5듋lϳq$Q_d@exp 9}: j5F16(KǷ{ k'111&ʧG#\"eR#JZU =K/!G1pq郐׾۔P}{Hӝ ugPJ"sg9 XӗJJjz=;DgN`wSpr}i+O_5W\!xr<|s!TX*K*{K~ǎ8ӗ4EUuxUp//hÔUlbt ޡ8iO^ur&q7}4"u5ّs#>0[e͞"E%NAɜ/[{DdE{VtL!i/_xoU)I6%:z[[%+ߍ4+:<+Ѓ`LQYT\!$+'cL9Lg ` ݯX-x6loާO 0 X$qMek2kol IDAT{,0 0X],!! E(/A~ hCFՊ`8zl/*58޼YCqu+=yc>p.%uͺ+-jDk΍oR3֍~[$W_'j8M۔n5Lr$^!v'Luxe^x*q{p̏-'ذ'q JU 4Ie_/wKEF$Ƥ~csYla*+!~g IUiJ]Y8)uLJB̧9ÿ)<dYpMsa)kGqX||9]!*TÜ]s,[ p&W,pM(fh?Ȝ>-5_; ֒Nܪ m/qlaq+"%$Jx8 rDiLg9b饜w[#KwD-ٝU-=' F2{9(_:I/mȏ{e JUO`&n .}/k17=06)8p;9kZ"T4b ˠOgP. ؚBD:Cr%[ I^ D8"wqeGޠ#OKיlu3K8;A;cQ3˴KHOx*ݤj "onINψS2^+]-k0oU_?M K1êBPkCpćE&⻪u>XDŽE0 !NMR~ )qI]g8N}9sS%*-"McZsRh[g0sGw(]Ѹ9Y"JbGJFΐO3W&sDS³F 'ۆ[Jh^LQSyrx?gNN>H[9|[" -Uc,c q#;ݣTs-j4 *5Iw:~QlJo}FKlEbDvPE=d:Eыq![}ÍMY{Ox*Xw#l WZKuTN!~Pfh~i}pc)AꄚpӪ3mL =}gV1&za<[KejKi2\d򍈹?[z %cK,$g5HhߐIhǣҰ=||$|G'\Th[!=\p U+,.ŧqL cr֕A4! ̜UtDqz+KBlgkG9u0FKڻ)vJ\ҋ9ٿ3:c0%J!$32 L-)Z#]v[w *BB2-k ;"A)%  RV0ӆ_,VDsHU|z\ªs(%> oZT? {k1 n #/rDO1ت^l([k_@&pHvu3BՑ0`k8-ta}RL+ݘ\ƈŹg$B8rv;5+ [C<2lfC.2)R)MIQAXƭlphLNP{OWGYmǪD3a&*d$ci*kLVʍK->CJւ57g'q/3ls!^Vo(k97;dEm !y,Ö֗ ڒO)JU\ ~GhэHaijtT>kCH8 րL+-f.DxoD/# )/RCk^Lq[0}^ٱvÉg.hg)YZIo#%bؔ8b}Ai6:GuƄן)D<.[Y 5g}Gr5+KD,@ O$|s2n.>tLN]-OEN33&e,?Wz3iFY fbXX#(J%>`fIvhi/(Zm;$v|0(xTe|S%"AT @c}&1%ix6 30F(,igIRMQXۣV Y&KSK*2sGw)THF<- B E2#0#h-H3r Ї[뿕xMSٞFL--`jWS&>#xVJ&'V#K{9kÏ~1:dg=w}ܠ`3OH|%k /p8kѮ?r~D̹})H=2?;6\]78jÜz;24ָb9t`N| Ό8Tٕ|@iťՋX=ozXQ4$Iwe_iވbjEQ% wrd%?r^UL@]OQ|u]ĸQ߰%+H>t%kAfՕiG2{It5*E*BR̥sIB8Jslvc,& 6^T(CZܷ u,H;_Op#gJKt*(GΊ&Q Y1:¡[K+RКD-5n('l2cܩŐm" rr> h^Qb =hQX𣿤lH:$_%^'`}s2( ablH):p/r| ђfLV|6+Wk j o6Mh{^SJJ^QO)\aS|o˿3w+g2$W i8 A܎Dވow bȎElǣyS"cfWJUq,[Ӥ:9kJF#J %p}UUHGL3H1fAsg7_8pjkQ.Q'Pi6gXaDBT5#C1w%f!2[%$]C =HR,RU͍K9I{=tZ<5PV&Kf}u/>^A,O55z3}%sS m<&>eΛe}`b : 3iruk1ۈZb͔ 1g۟d IGyL|~ ٱL{7ȹ]FːOCOoق]؁rB:i/+f>/Sqv4rrg/NIGٓ)wqEE(V?sßiWBm]gI#T-[t7~oE: ?gx̸*d935,H v̽0{%A%(,.Md*oBz/p*(t"nL0cE8YEuXާeΡ4`?9aoasdAbKB֬i[HPAMQ8?{"SpbvX}FNns߹&;_:OkXUһQ:{={Ŷp]Cߔ5íyc#d5;#.6=NىA7[W[$Gk=z=w=wٞJyW *^G^~|ǹY >"qhzL+@/>hd]n:sRGwQ@aZ>PKJcB/􁟕9OlWf^H_LE8!)ıfun׈t%5~An3rLs::L-j'Lh߼C!OfQ;(s=xLɝd:oV\K06pYx J4O,ȡ&\K/'DS*AiIiVWo-+ {s 7}?x1I1M柪 RKAA9tK:#;r1 ] lă)3GkV"Eh ֯RMp+>K?Y&Bnko^9>%d'dwhK%DڥXC-7CxTLg9ea㽌K-ʑғhf܋D3Qa ,P?]ylK-eIH3 ){Ȗ|j +#&q~&y?n]/.Yc9\!ؔɐB i5&jIKq~ZERDaZnbqK2YYRyz ZxV@ݍ %?}92|agRWpr8zdYb$2y(̵;8 }>k#lźVY=گo,]75Wy0޿˭A*sN@Aq$x;k;+ ]u\>BF1RԤnI4LI)?1 s; Qdأ Vo"sbQ?=E^Md:Ծ +ϯ2;El.a1\= odbrs(^;J{S Bȑΐ)ȀY^TWTD;HDZGTڧri.~,$v ̀2㌔\?-G8G"GX,}#azJ%*8p9ҡC$RrWs7NxQK]КA;$D[9"?w}k8r>3|drn'Auf7iʅ#MN:hFT"#O )"_ŧ"'v1zECBDŽ"8e TךzOR%v /OY9w 5/AxB [[2d#_0w9" |ѾN()v>"j!qS~gV/j}|WY:h i'FQYL8(w&Yzs{.jN7fxgҙYմg;,Hhi6 *yaq; f]r·OS>PM/J^MKorSh,8g3p'(VXQ>?HH &uw->RSAN]AKlĂ΢R_!,i{ptW5WǾx90Pl}J|K9HZOM Bs`>;u/pX YPP_Ź`X+2;8@uI/MКDY ZK3leݿR>p2BL^AD_8?ԩNȔd yyl XÊ, , ذ ,=krl;e7*a0B7 Q+A` a|ہkA~a nL h/ P8ҴN#'^Z%Cb {۰toqmyAgq[3`JՌ Gk\:ܢd`- ;+d^M'kƣr_p3?hO; (B1Wt㜟 >QpYMv[ݜ#E qٝ5[$B|[E\ U✧-NS5'WiẺ: OW9La!)dwWdmDvfRqRE; Jy$CtR &LB]sY]F)H~⎀DBrE'B1+$mb 6M|oOL#SKmC`+% ebl"[1Y,},[T%5sBw! ܉@5³bm?xLFׂoC{W<5w1-.M/{%%R):) Zg'ِۢXYNA \CiBm#,=]'}{)A:-Y}#G>q,hv%yњ w,hsl!"p?U?A& ZA3pSB GƤTJ>#F=.I+& L.0鳎 Gr6|Q(F:)9I{d1<|L=@} v:>*@@RV k,q={,"JOyIճ(ep:lM0KgaA;V]^ˡY҂?h')]oKnkҹK|!7EMًpMq@z9ADL*`<xw53cf/F ýnϏ+G[dw0GH!j^u~nƑ ?, 9Cά?`fj҅6f;DW|)n^x[n=Qsm'1u{.[1`{ǐP S#:{o 3@Մ0-/ u"b=So +Uu 8 B䉰8˺)س˜Wu:Ge?57\ނ8p^`I*9 ~DgsPlxx|^N oC%4ܿB, X>k*ې `1[#zR${-o&M9'%^tbpFUAb!wʢl\eLx|")X8;*QC'=ǝEG wA9_{Ayʢ VV$]I\Ko,'y槼H429پ>oV]Q2hƜ5R~^ `r\b "nacz[ ɔdi4RĞy@\0|1ֈ_BTfi8E8OdGB I"$SgHPZOO )}U@-!Y֚uʊe͕lZA y!FdcXÝHQwy Sc8qKR,'8Ǣ\5ՈٗR'FǵҔN2r'l`+sO&D>|{5r{Ɗyl]A%lX?0jԜKn>] =61~M]ٴq&h._xևxT'fLֽ9rf/)Z dN"cr3$CH-kb[5 UNYiAx ֜fzY5g"(]b=Gw0'ik5 4gֻ,pd!0O|nμ3{ $2~. )Iɭ!5#WbFMB=Dd3!$RJ[ϝCiқkȧ넯 q+DVXβJB9O ɜtα l=SpLs|ωsY˻&sSrVJ. Ş<%}h1NLk)E1Ai|l !Oo/nO*|cz"" 8x3p6g_}pJ:`f.G 5S!>!&4&Y}k?6\e iI(ٻ]rG#c3]\xjItzKϑ d --n,H%ĉ=YQCˑw ʡT:+Ig"0ϰdx`θ_^?7u闞~OH poDrS./A'f*Kݿ̸g#q]H%ّg9z\25sJ]E* ?$c9= _tx[+9ƧGK|Բ,i-JLr^bWx+,=uIĵի;aRD1;:r…hu#RIg+֜Ě0b苸Zs͙9r "Bh2W}/S}Csbńo!H=7!tD)${4}ӧUf9 Ei*|{8:7red`zGHwj*g_5b!XDBN>V9 ,}|`F#$gdIcwM#[2-|-| 0EZBèpp^:'-n\k|p5y=H _3.>y׫לsxSs[ 2}xւ >F}KsV{ht"pvn=),gK "YN}k-gAZ)lWZAIK„4럡(w/1:x'9K/CO.HBOA?/SNH;܍jsyw΍qCH|s)Gl;h:d'襳h-8{' 2tl= _#Tg \L%ntBu\T2jU K/D\!/ϑf'PዦzG9B[Ҟ"*X?paC7a:69 cM՞Kj.A,Iͯ^ o#+Fu\ibɮpܲ3t^MFxW%ɝcYq pi$^Sz% {s~Cg 䵧NjN;▙cjEs)y)C ]3e>&̡#̀O4/$߈g%:﶑/oփru5mǽ\' ƈϲ!O, TO T*`Ì2$Sz>;5n~avnb?*$6sW?wX ,P jpQu'\zhyd{'U-9Isg]ZDqK CfRGMé^mj{9}Z؃7FZ{SRTV;*aj+/GpDVI:V>cųRpTDJ1gȕ2s!WuSc0c'W\~UpP?-zs=e7֨j,d=ϊp#bꌢJ9a2GkoX W4H5̳ꨓIZ oݻ/'^Tⰱ1o;ECMj!H)'_ o ^|kQK0:"gtSrJDJrGsL8 '8t~Tm/AgIqKd#{#y&hɧ> _h% e#)T-V}=k[֧/\/PjOY5-Z3[~zٟ.ۭV>C -Pq#Ų|ȧ#W2#$CxbK:R8029 Vž8c Ϳ)KB>X ֓2Al (uqϓkZ/|ɷէ8's״޺z{iN`N0";$]Iw2B̬g:3 ɦ!LHc_v{Q=Y}%e&ARa܂ҍ y"38|ґ.Ԩ37 6R&W[G M-LJhӓ YtsjST7F  +UD5**͡peyx̸e zsԼ I8eSgvMN:% E@wC)8/6t-]G+_ @DVl<}jaxdJ Õp/26˘++ ]I`%I킩e?^lFyhlԖ';k "PUeԞ˓}R+ji9֠ vn{%ݳȲuNA˶,QYtu`Q6v>>|NbX[Qhkܥݻ#dǞۜ%v qgD~^P9z3_Qͩ᚛ !Z jJJ2c:H 0+ٵ^-N:zf+GBD x1ᤨ#a3]qʲjԑW];gnįEe pr(("'~a xw낲Xyҙ߈X~9bGK,sXrA,z2,\Yz9SyZ O?٧/(ϧt*`p[Z|Vj7blq@'쿞f[`t}&|ȧCWu u޲~XJ2g)5pɿ)F|J.:Yβ*:jsEptX|꯴pC@( w]$l(?c6k:q"Z;㰅Oȗ[SdF NQ O! yŅbG339<>"o$3bCݒ3[d2 *ߙ;wGYbLkѧEp x@1x Mxme dx?1OZ joD~9_ [nWEA@ǵ"H4zJ f}2rfk=C2 NXoS=/%<,oG b:K6ZvCJ~py+ة0yq9vgyж4 Cln݋XzŹVL_Z[;R~V P:C-#GdN8\LoV5<?3aⅣ[[Q 3b1w?&:7A :` ;r31H# ʷ#HG(PMBq Es‰ vJ |;O0k$4sq!GmAcQ 'C|a:h…N!%^)sk*> waCB ?SXWByԓ34q?L=9`7%ub`0 )+*~V[0}8(34w?ic),a-;lR^QG? ߊB!H|Dږb؇':l6;D=ErBϊfe"K(Kk< P4.T hފQPrLEpV(6Č"|Ao=s֍Qw>_d9U'y1B8 L΂ڜ. @jr#x!hT?v zIENDB`(         L83   2 Q0FJKLMNNONNOOONNMLJHBd6 K(AK8! H([O~C{BHVW n:D# K' ?!5 8 7W/CJLLLKHDe6.    N(QIEFHJKLLLJIHFC~B|B~BHSG@!> @! DXM^Ta[Z\_abcb`^ZRuJ  \ jM.Q   DHEOVZ\]]]\\[[[[[[ZYWTOE G&s;KB4PIsAr>En9 N) O) D#6 ; 9 P+~BMSWYZYXVRG B$0 "{@FKOSVWXXVUSPNKHDDw> S+,-48+ 6QCj[V_ekptuuusoiaQ  q:H g qD$ s vTLU]begffeddddcccba_[VL\1 a2IS f5 H&C|Bb3: J& C#5 ; 9 N*|ANV[_`bccb_[Qc5 L)$ W.DNUZ\^^]\YVTQLHt> J(10 6#(#  Y-DLLYckszǁȂǁztjX | S SC6IM=*%  v~TOX`fhijjjiiiiiihgfc_YPt> N)GVUA!37+ + ; ; I'w>NV\`cefghhgeaXF E% S+4CR.g*h"da_^\YWSOC P,0. 6/$'O8nHoII3   }?,V&]dmw˄·ω ψ̅ǁzo\);/( W U _ U>*59?3/ |gHOYafikmmmmmmmmlkigd`ZQB 7w;U\I'8#!# D$q;MU[`cfhiklllkhe^P M)b3 L'  u=!SaƕmҢ=za]\\ZUIY/ 3- 50"'R;YcfdW5#  5s8oTbozɂ χ Ҋ ӌ ӌ Љ˄}q^"WKuiH; [ Q _ WA0 D t  W1/ A GCLY#e*p&p"o o o o o oonmmligc_XP|A,4P]] g4$.3e6IRY_behjlmmnnnmkgbVi7 V-v;7 W."QayөF ]\\YMa3 6, 30 &M9[jqttqgd?V+LLboĆ,Қ;Ӓ-ԏ$Վ ԍ Ҋ̅}q^* qerf1& ^ P \ YD2 B { 2(#%+GW(iVԘRؗ1|$r p p p p o omljgc`[UN[0/# Q)Z^S< >!{@KT[`dgjlmnooo p onlid[G K(r:z=& =JFvU'`ZWNg6 8, 30&H6Xltz}~}yrZ2!  )E^lĊ3߼phٜ6֑$ԍ Ҋ̄|q^/%k`<1 b P \ [ H3 > w Z"+l:Q(ggؤi5Ղ$t r q p nljhfc`\WOc4 ; Q*1 u<\Yn9BJRZ`eilmnoo p p p p p omie]O N*n8H c1(EO SQKj8 ̄!zrg[/ B P9+ X O;8 c  d lE(p=TC|߷~:ф#n ifdb`\P_3 Q2   lDk!ʞWܶkɈ+{uj[lD_$ E.))6 [   b 8 0 M.M9qڳ`מ+s!i eb`]Rf7 ZTAQYepzɂЈԌ ֎ ؐ ّ ڒ ړ ۔ ۔!ە!ە!۔!ْ ׏ Ӌ̄zhT6mEYL/ lE_bbUH. <& Q3 ̅ukD$}  i LU:I)]$\XSq= >!33## E r I  [/P*fk֤v;ׇ"o l k j j!j!h f`WJ[0,#/<. *NN\[\hs}ͅӋ֎ ؐ ڒ ۓ ۔ ܕ!ܕ!ܖ!ܖ!ܖ!ە!ړ!ؑ ԋ̈́{lqH];Y\(!X8 c?J/ ! A) ^;b=Y:fnE+J  \  +:IHIs> ?"24'! @ NJ 4J&b\Ζ}<ӄ"l i i!j!j!g bYMc4 8-2&,+I%IFP_juǂ!Ћ$Տ#ؑ ڒ ۔!ܕ!ܕ!ܖ"ݗ"ݗ"ܗ"ܖ"ܖ"ڔ"ؒ!ӌ ̅|oUC+ Yic?     %@( ^;iAmChA];:%\4 [ ^  ~!& G(8")* :z W s; {@#]QƉlݨ,s i!i!j!h cZOi7 :-50( @#a4i7O*  m7'f/`hwΕ8ݭPݣ<ڗ(ە!ܕ!ܖ!ܗ"ݘ"ݙ#ݘ#ݘ#ܗ#ܗ$ږ$֑"Ҍ!̇!~ qW1 [9 jf) G, `?hFrLsKnFhA_<@(>'; &$  l `    Q& -  3q u g '[0 UHܴ޵Bʃ%j!g!g d[Qo; >!,22+ A#v>LOOF B# 'v9kVgv ӡJ}s?ܙ'ݗ"ݘ"ݙ#ݙ#ݙ$ݘ$ݙ%ܚ'ۚ'ؔ$Ԏ"Ќ"̉$"qsJYiiB  iBhFi E$-03, F44" $1"",  < ;.q|td9*"    R [R-K  ~       s t 3c<N V WPz@ H'/16. 8l9OW]bdffeb^RX. 9  kLj,߾uґԐv<ݛ(ܛ(ۛ(ڙ)ח(Ք'ҏ%͋$Ą"wWB+ .J/ [9\:J09% J/ +H@ % 4 P *#  /$zk~x]M,  <; ZH7# p           b &*q?Er= K(//60 6g6NW]adgijihfcZ}A D%0  B. c{'ڳiБӒ|ߨ<ڛ)٘'ז'Փ&ҏ%͊#Ņ"x\J0 1 \:tHzK{KwJoFc?A*  d2J [.%d>   a# j(pa}|o`0! SE8+:?G%#    !!!!!        V  W5)*1 3^2KV\adgikll l ljf_O D% X.Yu%Ҩ]͍Ўfؚ0Փ%Ԑ$ы"͈!ł xaQ5 ".[:zKQTUUTPrIZ9( +w<y=    D! C %eXzxbS( X E E4, C` ^<-5 !5'9*+$"!         V c +'!(V-HT[adfiklm n o o!o m jdU]1 X. Q) iCm"ʝTLJƁ՜<Ќ%χ ̅ ŀwbX8$*X7zLRWYZ\[XR^>&  *- ; ^1A"   ,( !\Pv~o*#L0 tIRW\_``_Z{OK8#,5V.n:t=v>u>o<f6[1 G&   ` XA6_S+! i D G;. A { m1p $_Rqc9*%!   !!!! iDF<V !r}KLU\bgjm n o p!q!q!r!s"t#v#u"s"p!leU\1c3C R) jEaed]f@, F- pGPV\_ba`[}OO8#)1R,s<{@CEGF{Bp=_4 9    U"&  q E F=0 ? u  H2@ !OA|zTF)"!!!!!!  E+7IB&  7gQRX_dil n p!q!q!r!s"t"u#v$w$w#t"q"n!i]z@ V-BH +!c?pG\:1>' hA}MTZ^ab`[PR8&'+J'p:}ADGIIIFu?Y1-  ,       v H H@1; o  P e* @3x}XJ)"!!!!  O;C+$3>.- (hRVZ!d#k#o!o p!q!r!s"t"t#v#w$x%x%w$u#s#q"lbL E$BO l6    9$ ];sHPVZ^_^ZPT9'%&D%k8{@EGIJII~Df6?"$),$:' Q5W8     s X Y<)A29 g    s f g! g5)st>1%"!!  S:BB -!& 8( 0'"I}mKW`.rLՐ@֊)x"s!r!s"t"u#v$x%y%z%y%w$u$r#p"h aN ; N'OQ2 9$]<jFzRZ#b'^ YXUOY;)%$  S  NJ.). `  !"! XT@-"zm{]O*!!  Z;@B 2& ,%`6EHD M,  mMFT!c;~xzIۓ(x"t"u#v#w$x%z%{%z%y$v$r#l"g!aY~D  |>P o8 kChEj<[]r9TPzK[;.&"7^2w>DJLLLKEm:F% *,'H2mExLzMvKkE\<  EA  B6 Q  !"#" U:) (nbob1$#  ^:AC4( *&a8LSUUPB"  $ +KP a8zv;ي&y$x$x%y%z%{%z%w$r$n#k"h!bZK <   5DD0 B:cKvW&{W%tLmEW8.% #4W/r<|AHLLLKDn:G&)+&H2nEOTT}PqHZ9& L.^   j g   !"#$#  n r 0r!QF5)# c;?D7, &'[6MV[^^\XN H'&vCZ/rwݰa2م&z%y%z%z%w%s$p$m#j"g bXC :     D$j7 S*  F"!Z+CDP<>* #  ; R.k;wBGJIIJDo:K')($C/lDOUYWSuK[9! L a vB%      !!"#$#! _ o Wg >=F:0 "(V5KV\acddb^Vo: 7  O-S+khؤGޗ(}%y%v%s$q$n#l"h d\KQ+" $ >"R,U.I( 9 D$$HC?  [/b7*xL0V9a0ZE}CyAn:M),$#?-gAPTXYXTuKX8 B;R ZE),    !!!!"##! X M u  i=<F<2 +O3KV]bdfh ihfc^L ?#; +L'dZЗFݖ(z%s$q$o#l"i!e^OY0% .R,j9p;r<q<i8_4 F&% \/L@  E#c8,xNJk?atAm:f5M),$$:+^=|NTWYXWSrHS5 9   , 2 L 6?     !!!!!!"#" d  0B3,+> 4 , I2IV]cehi k m!n m k hdWR- X.)  u>$\Oȋu3Ԃ%s$p#m"j!f_Rb4*,U-q;y@~CDD~Cw?k9V./ %x=B;  H'^5i=d8Y0B" ' 7*X:tJSWXYXSxLhBE,  18#V7[:A* ! ] - ;+.#!  !!!!!!!""!  j@  f03! " A0}EU\cg j k l n!p!q"r!q!n l h]t> N) \/  Q+SEߺJԐ)s#n"k!f`Tg7-*Q,q;|@EGHGE}As=W/ )  #. 9 [/ Z.;F a27 (U9lH~V![![ XVRxLlDM1% z' bTT~P>)  e  I:tdaQ3#$!!!!!!!!!"!!  Q, d  7, 8"s?QYb h l!n!o!p!p!r"s#u#v#u"r!o!lcO G&j6 I% -J8n{֮aԝ,q#i!faUl9 0&M(p:|@DHJIGE}Bk9I)4! % >"X/a4`4V/ A$ ;  ~?P ]0rGhEl;SO`)|OuIkES8/) 1f]^]jD' t!L=tvZJ-#"!!!!"!!!!  J8'    N.HS] f!m"q"r"r"s"t"t#u#v$x$x$w#u"r"o!hXT.c3{>(s=(\T0m$c^Uo; 4! G&k8z?DHJJHFCq;N,;$9#L*h8r=w?zAxBq>d6O*- C"IE  'T7iG}[,zT$oGf@R60'.T fadeY6$   >>1tqH9*""""""!!  s<JG$2  >@EQ]!i"p$u$w$x$y$y$x$x$y%y%z%{%z$w#u#q!l^s> W-}? j5 X.M V WOq< 7 ?#g7v>CGJJIGCt<O+6"5!J)m:z@DFFD|Bt=g7 H'   `1}? H% + P3W7D+ & !  @  hehleT6@)   1$}oj\3%#"""!!  U)9GI6RkPIV d"n$u%y%|%}%~%~%}%|%|%|&}&}%|%z$x#t"o dN K({?J?" `2r=c5 ;  8_3s<|AFIIIGDt=Q,5 2G'j8{ADHIGEBv?d6>$* :! @$ 5 B$ Y-   z  1 # iilpnwLQ37#  i*tepb1#""!!  ^=AB%1E: nONX b#o%w&{&|&~&&ہ&ۀ&~&~&~&ۀ&ۀ&~&}%z$w#q!hVO+r9KB    4T-i9u?DGHGFCt=Q+2 ,A%g7{@EIJHGD~Ao:L*8$9$ @$[1i8l;h9_4K)3 7U  kmpst`K0 kB+ >(j]}SE'!!  e =AH@?46;0$(OHR\#g2}5օ,~'{&|&܀&܂&܁&ۀ&&&܀&&~&}%|$w#o!fW`3 @ GRK%  3Y1b8pB%~M+U!MFD|Br=Q, . * :!`3x?DIJIHFCr<O,9#:%M,i9w@}DFDxAo=a5R-,  ,  losvwlY:iBqE $ %cVztf1""  k @?G@?A S   c!A /TR]*oZڠnKݖ.~&}&܀&܁&܁&܀&&&&~&~&}%z$u#n!dSN) d2ND  ]0c74~XV{^=e{Fv@m;P+ / & 3V/s=BHJIHFDu=T-9":%H)i8|BFIGD~Bv?l9X0 1  !" 'lruy{suK_;Q]:  n"WLzn@2%  n B?IA<? Q  5 BwGX+jg٣{B܏+'&ۀ&ۀ&&&~&~&}&|%{%x$t"maG! %}>EB! 4)V; jH!oFl@d8N* , % .K*k9y@FIIHFDv=V.9!8$G)h7|AGKIGEBy?l9O, . 3 T+ n7 I$ mtx{~x^Q5~N`=& H@5E9& t C=HE<= L  e$  U6R(c\ѕj6چ(~&}&~&~&~&}&}%|%z%y$v"qfO 7    >!c3 a1 , e /A F7 7$" 4I)b8r@}FHGFECu=X09!5!?%a4{AFIJHGECt<X0<$1 ;"N+T/[/4 `  nv z|ŀ | kU7yLa[$% x G<GD>= H | !! B 1L$]KɅR-؀&{&|&}&|&|%{%y%w$t!m`s? 3 2I'W.Z0O, =! @"(   L&`5$qG.S9a=f#M|CzAt=Y08!1 9#Z1w?EIIHHGEy?`3A&1;#U/_4X0 F& ;  nx#ƒ*ł$ƃ"~!onGnEapoFM   w J:GE<= F v  !!  L Y z@ X=wh1؃&|&|%|%z%y%x$u"ocF ?# =!a3t=w?x?t?m;d6N*1  EM/]3nC>cGm(NrAj9X/9 05!P,q<~CHIHHGDz?d5B&2;#U/f7d5W. 4  #  mz'˛H̔<Ƅ%"rSV6 attB) 27-"B=< C p   !""!  m eF  X-Q3lݳ],&{%z%y%x$u"p eJ F' ;!b4|@EIJIH|Co<Z12 {  :\2g<j=_5J) 0 -H(e7v@EGGGFDz?e5D&1:"T.g9i8d5M*! Pj{+ӮfӧXÅ)y nV0Pu{c 8#"( A i  !""##"! U |* 3J-dyت<Ո(z%x%w$u"p gNL* 8]1|AFLOPOLIwCV3 & ( :; ^ 7G p: F$! I)_7pC"}J"K JED~By?f6D&08"U.h9l:g7]1 9  0j{0ֵuΠT{%rhpG*2 k}|e@ (+ ]    !!"####"!  S  t{@*^nП߸Y֙+x$t$s"o hPP, 5X/{@FLPQQPNJh><34 ' A d t t n W @A  \/OI! W-e8/|SPtW{Fl!~Hv?q<b5D%17!P-h9n<k8b4O*# ` j9Эn2r!hZG. =' T6 x}o1 7   !!"####$$##"! l rKZ0$U\hО0t$n!k fSV0 4R,x?FLPQRQOLqBF5#9< Q w   | j J#CI Q(  C*_=&qN.|SoBg9Z0 ; /3 M+g7n;n:g6[0 :  3j?:l"a{O?) D+ A( *xOzr\:)  ""#####$$$$$#"! _' 8#K.a,e$d^R[2 2L)t>FMPQRQOMxEL7&9< Q z  d0   C"s;n8  & W=?R9E+/&D&`5j:m:j8`3L)# d pk)^UZ9,*6"D+ 7#&gk?&U!"######$$$$$$$#!  R km:GLIZ2 1 E%l:FLOQQQOMzFR8)8<L y S $ , C%L* B$ 9 M(5  c  ;[2i?!oBh9a4W. 7  2{_c?9$ 3!:% & 2 4!  F,]-a!"#####$$$$$$$$$#" Y}  28 8 / @$b6wBHKMNONM}HU8,6:K u d5 "# 8%Z1k9p<p<i9^4 C%,  E?  H%c7'nG!i@Z1G& Z TD+ 5"=' 8$ $ 5 D3  _=:%B '-($"##$$$$$$$$#"!  t= | 3Z2nB)O.V3^%SLKJ{GY9.7:I s k:"$;'a5v?~EGE|Cv@l:[2 @"  | |tJ# G'Z0 5  )6"B) <& * C }   = S3&"@1aRK:,###$$$$$$$$#"!  u `3 N(a5+uLPt`:dIzDr@X827=D n r@ &4%\3y@GKKJHEu?_5 4 .= n3 ! K  K C* / ? }  v  %o#L&N0[0^NIIzDc97+-< \   m G J  !!"""!! i3 v"XIurc1"$!!   Y }  `@ 0 7 _ v y t ` E D ) g"x?a86ZUzV}+UxEr?b8=- 3> Y  sQ%]  """"#""! [ " UF9rd>/%   Y v  iE< B k u W' b$ K6fBfBjAg<X2 :% "@T y x ]1r & %,%###"! \U6&.!$ V p  k B 7 > h z B2 nN  Y ^ ,9 =0( ( Qc { e : 4 !.B1-$##"!! y w 6 q  T c  m B 3 8 a u >% <Z e b M H \0  g ' }D=JB" { i C V #>-dRA0'#""!  f q#= J; M | m ? - 4 [  S 9 P t  | i H }, [ $,%z k J {'&VEo_M-""!! D 0- f{* 4*;0( m = ) . R  Y 8 N  v U 1 bnJ* U U pV&3,p_ztc4$"!!   (N rP v"yG=ZOA6  k = ' + G x _ : H x{ I/ < B g ~ A ; 7)r|p`0!"!   V  /'Oj"uxt a =! ) @ m  c : B r ^A? / N i r o e T F+ [     #N?xxN?(!   n9 o Y (D)%)FE +" I c~ e ; = j d BDF `~v h kF 'eVxi[-" Y/ F ] j e N E [OZsI  s p)4*A7>2! c : 7 _  h BCE b~ n ]=!+tfvh6(# q <Y   | m L& H- bd=& K m"y=4:0! { ` ; 3 U  k BAC \y b ; N 4'ob=1% z AX   z\$ nDVlB  7 fwy i N1 5 Qu n C=A V} f ? S 8,7*# ~ C R  } O  2 <' 7# Q3 nE*  e) R o'-$1(1' r D;@ T {} f ? V # y C K | d$50$,P4lDrHqHiCR5;' / ZP a r*!I?KA' o F<@ N v| d > S   u ? A q l#;5($8(W8yMSVTR~OsJ`?>( !   +T ptq ` @" 3 L lz b < R J: ` r"@3&$6'R4{NVXXXWTQqHE- * @ t9L0?''(; & Cfy w ^ 7 I  ] o  u!D3&'7'N3xLVYZZZXUO`=.  - U j n b L EL :$c`3  P }!y@75,{ q Y 2 =  z%+  yG3')6'K1vJVYZ[[ZWSkC8%  . Z   y e FPfsH  N lqpl T-~ 2  *^S0%xK2&*9*F.qHUXZ[[ZXTpF>( , V  g7  ;& W\. ';!C R+n '  0&)vmN4'/;,D/lESVYZZZXUtIC, ( Q } V $  3" D- H/ J0 gA\:  [ 0Z   o ] B 5'B.d@~OTWXXYWUxLI. ' L ~  c1 . G-_<d@c@_?c@b>7#    &X 8  C+`?vMVZZUUTSzMO3$' J z  j61#Q5kDoFoFkDb@[<O30| / gR4Y7 jCwR"l:|H|F_%P~NwLT5(& I x  m <-!L2kDsHsHoFfAZ:A+ # r (  e& J/ cAuP![+wNqGjCS5+)G r    p A, G/gBqGrHnFgBV78%  U   |uS6   R4 `=gBZ9?' !  Gk  s F#+B,a>nFpGlEd@M2.  @ qGXW C+b~OQ3   +mu+"9.>3,  vM!'- >*\;mEmEkD^<A* ! l . [vIP3 $Th_$ ) c l) z@52(}y q R#-3%?+Z;hBgBeAS54"  N WiBpF, A* Z[S3  * N f k ^ E%E.]=dAeB]<D- % p 2 Ub?4!&1 J/ lBrG #  0 sFrHnIX87#  Q  X95%(D, \:d?d?Y9F. G, -?  jB |MV6 .k&   F0O4b>lCsHtHpEjAb?Z:<% * {$,8&  lEiCpFuHuGtFoDh@_=M0    0     uLtNe4vKpDlAe>T68%  $% suNd5_1kCb=V68% 1#.# h +  StQ$b>T52 ,!  t 5  We@<'  p B N   N/ & p !  _ ??????????././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/Mantis_logo_about.png0000644000175000017500000036250414332463747023207 0ustar00wattswattsPNG  IHDR?7sRGB@} pHYs & &Q3tEXtSoftwareMicrosoft Office5qIDATx|UWTL[aZڡ$@B<ww=qwwwҖR(_9sΜ~>>z~k3w<#<{"_2XyCD9XG9Dؿ#r#};>sMŪǍSyD^QT~ __#"johQط~TۼTZEX#Q̟`^gz8ACmC"ii82$Yψrg':$ eq2{t'S`M!a:mzt¤I+e1Md@yc T+7TtEyC?BUV+0٠vNH&iXEt8G(85LD_v6VV# ~te1TQbi&* FF8iub +I__'= DyC?f!O߸V"jLdS17rvs4QxǗXғ-h])evziM9;H} 7l䩉U5̔Ad,?둙H1C_W-Gyte="Ǐo` qpE]h#Kr{R!a "8Y#g؆O¢H-a#ז)=+-Umlw=gm0ʂIw];KrK(m%*^ _%et &Q\ts͐߰_z7Q[PEyic:1LT^0@߆\w< NASDXE-=Ǣ"8%<ʣ]+""sQl$"z}LO,)cO=%x񵷾Q㉖M2jqvfPFშf sLJX;E"dc>{l]cp$ g("7G9_Pmu!]AOsdy^$G_u-Lc%<,H(79DӺ^C:-ΐݵHGGm$+z4|%`>>cKTm͝qL1(Mv0h],]ZgM;{ص7-LqIl$΃_?ə6^A[=e^b̎K5xd>f!ݵs'7۹&b'큐CDoa}(Κ$4M6J:V:C:V!_ȧ?ˬeY¼P b$0W3ezTs Cq&q!8s-4W32CgM1WSൔ %: ӕ@wn 4ZϗךҟIU;f,9{Fh#Ͽw"^9Dz霋2)b>/a"6f76c' ;װ; ߒc7ٱOP{è!92r[7sZ-Ϥ5݇]@ gV6R-jowN:š<,ٮW_yG; ?<謗eBޑAD2a|wر7=a8T\!:ugnS~,~ iqzho?ɉc8;LcR,%trh#^:ǚW$ 'yt;ө behT7͐2WN6pi JF%7&RdB㟖wu#ϘċLCD DXDץud][R4}=ύ?467";?}RF' &)8ۥULi5@ksi 7GLԁp7ecLoV(+4ĺc*$hm|t:7wGJFj2T#U UМz:.#7SFU> z"ZT&ӔH.F+Ոq_ᥧf"?k.LCDKqDvi(SH_0 #hYf^MGE tPk:a!yK[r/s5. WGŏ=N̕U0֥qjSg0\A)ګ8Cfob$N5R`D 6rq I4$PBYz C7SNQő[(W4bi8^ђd(sd^}eg:/5ݜ]TѠ$b+)Duf58[hش|/ѝz{Γ/{D׍kbζ&YV4S%Ѡ"z+ћ)_',e2 [ɹJ rd+Vn&CgXd"=6l'9፷/YyHk_ǟͩ31,`Otxo#}p#XS7Χ C!9yFibtԑfO|`IvHqD;8"95\Dž'o$֤%ۋÃci຀K0#& tx|@M Wpydc$s}0c БgOS^j4(T|ȍTHThLV-]읜.Ue0{>泙=78C\9D@䑍<,=1qJk%rA]ꡠig!fწw@GXw.LɬN4YPfޮΚ%+Y1](㦆B\-t`'Q5Q.^# m&3)G8n_5lZyo)˛JQ8-e?9"Jog{W`ٻܖ1Tέls!fҘfLM·I!3ؖb{9Ufʅrh4N<&otGYro{v{ κX^?}gBC=͏_̟}>oMb,<,7`g%9[ 59ʕ4v$0THw04gњ)RHC(g:$\NL[48_gǑ -f|<7^&Kqc;-Uq8"]r!#=S끗-!v~H%ؚؠƾ-Zdrg>W\Ssԇ9uT9hF_w&ۘo}pޫ6a|wZ6n_ClC؛Jz[_GSUMVjBr8Ֆpu6Ep7Ci\P&d@ ǝbn.yCc<qtQmct&)z"H$a+4YA}k;5-UPEYiM65JIb 9'^[쵭oo[\/'g{ܸ W/ߠCڮ\eAC̴ٳ;۲]JKQS5Vj&%*MFy+l]9 mbK50^ 4LDckkOVKj#9V`U=" Շr3KG҅ b_8ݙ…xn69rt= HxE?-,"rCv3a}oW&jE?9e5[2@zq55tpцcȍK:z!\woi**$Xœ8DA.tgP[MuH*':8]~=3|= /.*8hfvM5ޭ*F޺ JlSb%_6H?hۯl[(/~+ o: 0_"@2="1Qm@u#;Ӛ<B87!$K})nDcgLΊ\h V3W iY "O=)_-X9D11՘cQv*Lx'XNQd4>yBun! ٕtї}4}{s.Agt"%FICT'D&||#tL]T'n\8'n]<6ޅl\o+Zۄ9a}TC{f46d7ٲ24˓|, ~:n=K'a$6/C `bLb}1;4LOB7Ra`u=a4%ЖGwAzl(q`܇u\O<'%ZӞPF:O{eq9Y"'D cU "~=Jsu~c khˋ%ŒæۅVMhc"46/A},p=_~~с$,f›l^*VJ&e$w<u騯&fj맡6SZNS%at{ВeEs-ɚґBO6TqޏM ǽy֧7Cפ b#Y0k//"r_귳'dZT)"ij1衹,ӂĀ Ivه&J}z)0&k-fL|%SD58){&\bY'+k9Z@oU3=C xn"ܐ2Ξ{c8 }iO.??o'8YMNL٣ ] ܼ]*+ٺc${jkI%ŘhLA[e[~/4K 6.*mݻ+oU:JmT{<]E4gY5]6$K]@o]#G-3nj֌md*Q-+?a""rɃ* 0Vr`eZ_*sȌv$'6<:>=t;Reniku*?Rݭ6"X|'s⥭嬚9ES&ld<}< *fl{uЦ*?mClKUם&xNblwS٣8sCچ> f zDMerC\pO?yow|3CT&h~miL}{w awRB(O1'c+NfK95-P^ LdӪP~mSp1Ak)^f. 3(,9ǁ|k!G/'f qڅ!mpԟBWz,֊| jTW7_23}oLzYo®(]rv8Ɗx $DDsuHݣMIq&ba|kpw}UPLE+v6ڊꚥ(^Ćt%\BW Z!׈e25.ܕwBޭO>TH29~SGorLoNLP!P@ݍeMiz%ܺ~? wn_bR}l˱7'sT>W/Vq ?~{?jNuJL&a+{W`gV(4\g,_}˃χu3|,$hIGg=.>̐ƉXs;Ӕ̽tf1UˆG+1X F +k/?'_K9Dׅ%w?5 z.5g]tpq(Ҽj3h*Hh0,8u実yrS[gx=F{⩻۰ܱ;xXaab&[굗1_s]S?q}(jK4~YaW,l'`C= pR 3Q'bAz U&H_ /8jm q4dpq/>}Ľ/ϑÁmxl%'i4pF"}{rjWRq,gGh9AJ>+Q`zj#i>0HЙJ{r:60RHFJ<9Z`YVtЕ}TCOM ըSgҜdm<=#%"r=?ij'㜸bNĹ>: h*$ޖ+%Y~SL,=\uR`*h,0ӗ*MtFW})[i&[r@M{錗97p.oњ+t$|51aDg@qlzO7YSJt( MYE?FziTVLR/Ri jxj+᫳? _5 )/Ƴ[30K:*xpC~{ܽ$ă;`}%;6FstbjGMEkp'w^X W'wdTs4| o`m?-jsi)p)9Y0:t ӟeψǨxϴ|}О=T~pw68YߵҜ'+{CDJTXFf% 8֜6,g."==U Ku$g)ˈQ6p? f.sDm#6sH[#mJCy /rjt}Ik臢l;!Rd9R[ɡf>IC-γn44(`n`uaҳ(| Ey;, pپKQ_˖8m˦8+n,̴O`*a8Vعi1WOg(,٩6kMݛ<8I6lUoSq:kMTNBs۸[AY)nt3ǒ|KrОaNkAz29ȑTq\>}ͱ+Ӛ[:mhIKk i.aڅ,zc6P5o|CڨW@_c>Ntqxoq yNHաGZȉq%?Ώt.12d2kسnf!fN㐁_xX}9-ѥBR΃;pb+_PHGQw QvMI_{WCyk|pkC#9ZJw!{hI:bɰNm8RXeۖ`[S&n41XJCAIx[b.FMiMCa᛬^'V/|-k^#EKxO-v*MESy&*sT| SX3 U4fxc &vt]@O3E 2T@-.}9vї/`Q!B{5Y[I{9QII[滖[/CD @#W?sVcKN9]cM_WUe=BUŞDRKq/ТfSl&bm fWSa kgKn?,u~0\<-BW,Gsh &lF d7ƅ⣲(2פ?-X|Su.U/|nx{!NHmvJTaoz?SXKL;f8*H.ZTDq+g{,%%kUvn϶Dfu 6 8Zo/t" pا^eB\v) +YVF8AGM9V yW8F[аȝ ė|'Zr (YRjLM:Q;9Lun}دY&CD<4;Us~Q!#hϥLs;Cη’"=\OeAK^Pчg9ۓH}9! xCMW6,4:2X? w@s#m=v(\N.>#"‚1Z4LQ$Y/>?m|~t̵URJң:ڋ Ǻy mixb9̙6~ շ6{)+6|Q 76'1s fed>,_>7/ P#!vFk|T @~TxP@nif0h+DsK@M&G 'ZSӚ‘,N Us/MQkdW [̏khHCU1ouⷱ -K٭N35%aEPMKiRRQ6an9ғ˗0WENLJksgO?9Gwr0u-d'J: ;crkm^Zƥ(]%ˉZpU+f wSCv8%>H}h_(5CX$=҂,7J2)Lv +Ɯ]wlR #u9R`C4YƸ~O]2WM~Xa4{QlKU^zʽ9׉Ԑtwwdrth2qJ "/<- 4U6z߮D v%A*?{bn6jpخuWIGG29kBGs&ϵ?\IAFIQd$GA4W@mԹxS10DֿsR ru0 ym'㛳9,8}Ng:s+V- P5حZ*Cz0ϹyqRBWq&ۗWCv юҔÔ%[S|P؃=mtZLBV_C}صMXi*FkfJ]?}^Bbl:Jp1כ<hܨu6ۃa*U0@tr!4\s\ 1*!sI*]WDMHE.JH‘!s_4ȟD^y^gRd}hϧr>U6 (KB) q:]"i/>K &U%KmM:FVCZltJ 13$vbS?M V,vhfb Yy-`&[*\̷oFf-w!j?aVFD >Ɏتfo@d(㭓nʠZ DY8۠Df $ho!qa!*$mHo\̈́m[@.N5nw9{ K*a~!ʋq6DJ!e VԤ 8LsG -9Ji&߅xVd\ fF{{l]:LdYBf'N5R[Lyu!9 㱡,$Ϗ"wj+6']OQʓ,(#u( AI D۱xlR! ~l,=*8ĝc)_88~ɲ_dd7E&|C;`'S=)4&o<:ҭlw,O{lVqH"/YluԝMYV0*d$#XB\Pߢ”30RXE.H=@A3Œt w&)X`3B<ߍ!mb Ȍ&=0qޖD8 >BhgQ: !d-&q;ȥ@>5w||ȧz8OI3#iӌsP<dm'[w;;ص5ej\N%.+`6~u衽\`[SYJ6eqJA@KDDDʖ05ohɖ%$Du[R}2g>L,5 88QIxD+*3ܩ/@ ԝ+q'ՙS#EvIhI"'A2k?{Z`#?C~Kj'2Th©BW>J$geuE(kF&M79fߒn $0Z$-앿ݼW_ǭua쑁$Gԓi)WFokM"Lp>Ӗ˝9J|X .wےyMcR,TGpt+sbMpT\F^R>D $e"a# ED,%Rg!K^%q|e}u J9?d_l>B4 0JÜjT:QeG[t4'%RH~Rbwn?GHV]٧D7}Jc-($zyⵄIxi`8uKq[Mank96R~ݕ,{־7u^JIRSq4-eTRHq+uEz(K,ޜ# ȉ8?4eV6+2瞕E9D>Vq0U-Fr؃?s?4N5#! ":]pj{*1|r{{u%8f f2JMD\ W͢6ɉÄYiﴑOGx EɸhgeJ}L0UuWUN^s|5vKDxm="t s.+lSZIW2Wj]$vogdۅ Svb:K/yEޜ*3td"7!2?n$Ljo./yOsbn!8V[Pn65.D⭸]:ZRnGIIN;pQ'U]DP8*FjRHܕQ |R-j9NJPoBY;Ѿ (2֟ EL$AT xP-L&^었Q~T{R-Lmt(ZEO){wʀC\9.]SG ګή];OdPMsIپ ڽi6Vʷј?qV|XޟP7Me*Fk*g= Z#*H"HC.K T8RDn+Y ~R(~4yҐ&kA^!-GXS4jJ̜G^yYψCDM f%rt&Q{ [d>'SDCf Rx'}nޤ'f$جn:kIWvsl:F<%";e"nY7_C/=s}|&gKR5wostgtp|M&}p=8ycJ~j=]h׍])kiphJvݷDZNRCԉV&{+IA$n%Ok#qWyhevKt_`֔ x  pSHA7RbHN&9\܍GQKy751TKBR* %#D TxjC&-|r,t3}xMʞ 3jӘ#,"j 6(Ps|}ge;X3{ @ad/<0C-tWV"% (P_#@J}e*U40RNv"T8S.Q-L.۝4;*)4Ç<ɉNJs= /!".,- -fwVrC­ J-4> l@ yNmCd׷q`4q[e2.Ⱦ15iqǰU[W*|tmSDsr*uaDr{7s3|tebc8U˹b2C.RvuEۗo%NNV}IR%#Zxu".x-HHrzR|\C"G6rJ)c$EQ! !^䇈{SHeB0ebCy&7.Ҙ\  *@CC( <88biLޅ8]m8WǷ_׆I=l2{׽Kn!u!ܾ^ϗ>r 8cY|yp4gٻBA a$zsȊ8@gE8mE @%QNYv,- Td>":Ji͡ tJ3󢩕QaCu9>"}0Q9DL 7ᥜ|>7⫑n.>8Vcwm˺/3d,@&)O)⬉ 97HGm j7 #d]VL 1"z~7%<ߦ2$[CvA SN}Ty PO(( ܇_J$AT$Iľ0}Kb(EQ$&Jâ.*\a'r$^Z|r_q$Sm)6EqzLFs$K4f< 1!ܼ3`7ٻc)9i* N%/$ h+`hBLh(O48 #(7rq,tGa"'ZQCyQ45ٔmFA f]O C;QUZʟ^("rwwaiMBj^hs$ΐ3ȥtqS~51yGd L?mϝ %&TYқͧGBl2;1kD4Ƙº (.Gfś/p5}j 7*1A:_]VRgd :r*F9]WS=\&Rl8ea~MzaF5,#u5ѾI FN**d%(TER֐,M:@Ihi\xcB=<"uDPG~/a~{EatF(aA"Hnd#΋21YJaQ@ehcL O #ڂo?*dqt%mis`JvAWy5W-cl립Y$Q䁻.+$G <ؖk|x< c9% D "z21@V+I{qx{+3e\= g;WwWP㸇9]?'Yuգ\i%,KZFxyo Wh@,&{-Y[Q@N*2S ,#%e)IKIM_En%Eʔ(R r#{;gMic"Y +  "ɏ'7",@_B!#؛I$%"CA$S¹&pop{;kl_4]V^DIkhž;SD׉-;SIyB$Iє%FQBn2@2Cc)%;D)jΦ.·4 ]^:jSh* 0ܿ,RCCuM%$׌4jSlO* QfIlO 8DoŒH 5u/ɋ^!"JٴҪDJҌ^b[*6T⯺S!2Kd~?gs~jK996BvtiU2EdЕdfCSOm[@ _]AG1 Ð-^kItנb77Dy 9V_Ֆf_N"jqM\-ΊQW2-RX$, #vI(^KqZ%de-@YDNRsk)+(+VS;][%:x8 "Mܵ;hJ#(I'?&a bC &-8$?_Rɉ&OIr8&t=H"# L8]c(vO>7R m( z\p=FYySۄ%ٺjʋgt*$2؟,N{Y0鮴 d\. @Ku.5yR#"Uy*s#iNc.ƔPim(,jh,*70*2)8^3uSN*ނR~ B(f;^BI'{OUC O1Reܫ1o]7 ;?nҘȤ_Ч#nWU$NWERHI uG#Z/ȶ߰-Ȟ{d"/=z0^|IUЗ('0K\ y\3|>ţmXKۊhw MTgNG{QSs>)&^FY\Cs*f<ؙd$ͧ(o-$;{9s*\MV"ȬBV2EU[iDn+תq4h_ D$%<-J@"|I(YAO #%8(HfFg0%p$DA~o2L=1&$#t1\]adwR쏯vQR5Dy /trsb$# $$4zZnCCI5&KGdEQME^H4M'$~4*rnc+ަ4,,/ڊL""\\+ bCMI6%8 +0/4N}d`O^)dof㟖C? OfmR5T|LAo֤UfKcpȲʎ_{2xERL|b{N닿#?(nsW$;N DوHvʬDSf!T!#j9`G۸{i|m=W'. )@sL}4xyboAYkuHCZ5bӒa[]ݯN:"!\Nz)DL!Y2Ťg/'?_;hJ~G|L`jGI.r}Ү ޖ.g}y4 T~L,1Z?LW.b5#5q ֥0Qə\q7Ostڂd SPbuX/nӘ#ldB*8 #(ȉFi356ګ&)כ sH RɈ*?"JĊF4r :uZ [M {}RK+|r . yqQTXyp&NZJtWI@ޙ4fQjEwu}t18ʩ #tw4P&R-,!CGq"G2L .;a^Ԧ_LWm \ {+ˤERyt){ =ٴZBKA5ޔFV qEaar#, KY]`->JĘ8OevLW'7r!+:РJOT s:ׯ#Fa +W5w.o͠l\n`4q-A_OT\GTEҖ& "1i+P $}#jdNFhhpJ5]O[!*è f<ɴ&X@m^f6dဿfT%sR?_qL >w|I06+ᠶ Ox *J"4Gz줡<$D*2bh+OeDXGwf$-<&'@]NrchRKE Wט.DGE, y!g + #Y",-<;s2vSBNA&BDbKr5'lj-7*~!T]Ȍ䉧f"?1DƘI&iGj)X:*kʸ ܠyaUEo9yT@++/r`]}!?:pU:Ӹ?snI2(ld:kQDE6 XsAdsO%.>ŷ_?Fr[Wx#֛9g=䧒J at ٧GIYN2=aZFGw3Tꑻk \Z7k~8DU:$Kp,"㗴5$,#:n ы XwZ"'Q"8ZfsLMieAu.8Xxa"4 Q/*H6/%̸H2JF~V px;H#p7&Ei| |totוR#U|9wT%,`9^hL!Xg&-& /ϧ** R(Ɍ:7^A@D>etIZC:ʤ%L,A1J@$R<ͭpcчmn>1eѨ#9GKV_=__Oѵ.N0Ձ8唺[ PK"% JD_WQBGK= .9žN?M@&3B EߩEPmLa62fPc9쇳+cO0Gcv;ZQBĖ8+ќjOG3]Y.tfӜt꨽nJ~ yE^}Oɻ'_)?&ٺucQ K* Zw5omږ{vZ>y0_(5v!űxg12Y&{&k,imQK/y}2co@doDiLW&쓏{3>_F駻x$n]fZ3}*WY8{.E3RFMaΤ9ncS,Xz-^4;G:.{xF wl%k3f1kmKV0AAu\vUv"`qpa U;.r npDW`~El7FSV>Q~VԤ (hci-MIApa'ey(#a$ǟ`{g-i!rFwa&\zcܽ{l5I5D_5_?GA!qrjQLDyr0%Iaԕ) ^W&@'lO|6dQGw}HCA@T:8QQ:}⼦Pi=# $PL:TJSψ!'3T `+M4օ'%͑LL'ŶGlS,icbNb!2HM NL̟O?-"-=oiawn꼿 o(wk=P\+;R5 kT)Pf:.{+/Cv .m%S4d"Z+xl=^e=nY+_DÏxCSǧߢX4;0pQ@V.VbNdњEEf$1$x8eDW.xu E{~2=R{{Kgʀ*j[Ԑ/mrYoʫ8fۙIS"q% 0Xe8ج~Jw/n.x1sl5Vk0\ 5m#Z|Z/l Õ~]Y͢miD+J,:ѠVǕk%l"6rk|a~'ݸb2m9ѕ'@R|NTrvgӜ0':ќA 릓s`Gkn:'j2)O F. ֚bFqtlD (OEZ&P+L "#͹ 5g2$WISe%yd'G$0)b+ l)K }82d2,W26-l-Ŋr2­Iw$6ILihL%W]Lz9y.?Do"Y<-!f7IY1=vdDd12}l!}ʯ2OfeTf g(M܋傿nj7ѤC/Ɋ+d 1Yc!iLeAg:CUuaVy>RtTKLb'Qa~DFDx;m2$Rp-2k+֘3P8u6KΔtkJ ĝ`y)Z X<%tkfWeJlZjvX*llfeT;x<(q,yxP'T|ј FR5f{p2@ŘZ[۫hK*~>Oz#ŌkIS'8w3P[O5+hR#*$K+K+;ڊ"I\K}~"e" F+ʎfSgr|'h&R@Gh9Q$A(>L*Kw p=\Hvzb,qX9HUf"jOHm=dMyQT'{ҘC j]ȏu"1S?98cH;2lȊ>@I%5IPgۚ^=kҚoYeyu^\q䴱9gtrQ~kUY2bLo'.VeDq7=NP&FmIIu֕^5slN:tVRb>{>-\r1l\J㊹TΙNŌ>Ҵd}Vp6G etr4WG{,v.ΎE1Xuٵtط+RZ數^OwzWmFU1EyQOYRT*/X SȒ(FC"o`;vZ*P$ϒgk1;Ջ d`B0 auYޮοSGHvv%Hm=-aiΕ-]}8;i.X0a#VMqRx$v0PGGC5} i~ΟH/hua!Ɍvrxj3.M HPETZ|,J%XPzK0P-4N@UFI i$ $]D @)D"#ԉ$!3Ԅb0#*"Lͭb?..9DCo?W.賴 y<2gd[ڷȘ ώ+|%2)Tޝ Nkphhq+Z#:ƍhe^+e67 he2I[y'Q\=\٥/^n B%4;M}ܻ'Oqf3Ǚʜ5aP%\;8dƕ@GrϜ;Ѻl& * RU\lyx+b=RgX!,sRW.uޚKh5N]Pzq0Y]etAkb4.`9l;bhk⮳uQgeD9.q$ߞPW((%\͎"CH9;@Tq4&ݪgC|߫E6AO[<츗hDgoɪt3cB2+XsEĊH$ֲ\ $tgpBsi0Q_EGo{=t,J:2d9jK!?sU:KL~8<#$2ɖRO+&G SHC>N ΙfK=\ΥGnQe0n-ZE᧣͂ױ{fF»mFި @hxkUޥ) /fz;j9Q&Kٷv!j4]h.Z3j{,y{ ޜ̹8/bH# vPtd8aM#9a^FA!" 6݁&57Fc_ooN >"r"wbKxMIr0g |y)O&+\iVhs>2(KI&'xyt]2}4ס8Ȅ2?a'~ZXGyT^˰SFw6_L7 &ʎ\<+}n) ߲:5k:f$nG]Pc!p 8mILڲMXE ج~'BQWj,]W!"s"^,gh!.0wڳcs(?_H>X#ռ/@rU9V½<LHA[4g i #GXLⶼE?67ѷz}?{>}oϣwL'G7{M_݌K_Mr7.fYlx=ʌY|g*yWd: ^{9/" `7{ɲ%ZCFPqG^֤[oWQEb4O\Q7ȇך8ѕF%'јNQt/Z@DJA(*ӢpIgYefr# `#f $Ә+"+w*}VRCosU 4pa>a,5Q *M4ra#eVg'SDUZ2~x5 Kψ(Z/CmF(I 4d [! PvSHLD~Dz[ߩH@PbG@) `S)I&:+Da%Uoȸߞ<,e n5F5$ uޙ 2d_^h۽(|sC[F8%g(5X%]νkz Nǽ zf*7¥q9/re{|(]>n-+ ]ʮQ|=־^{xeoMgQekB-jE#Wf̃ڤ'RڈD"րh "ͷRuD yy~'GS %)8r%LJPNSV,];d2)i̧ dR͊6͏$w\d2Bwcqvsa 9ItV Q(KEΑ$XјAQjʼnj4h΍.# >-p_'L&ʢt"#ٓHCҝ)ʌ0ɇ[%TFSPq'11޻""HH]z{ォ5jLO&$2i$3;a&3yv>s\} }"Z#1/ǚ`cŧa= mKM+,꾚Ȋ|[-nlYlq۟ȃ,:-"<qd8[/*H i WmxƠʵK玑w@T=1Ym%!U6-]B4?_M~L^㽇hNe$ۗh*R3RJW%m.g8}S9Omk=cQu~(_~z&8z?Vog@ʼח@Ӗvv> tVv!> Q E9 l(Ƿ\M)VQfȜnEmuL8 f_dž7K ]ɴ%Q\w}:@>W !k@|_}rM+|a]kfTD(`km-AZ^ M&Q[@}^γ-B)/djA>Fza<\nn5TO>oi\f6&ګ]%̡[s#:JfgOHKj:t ;R.DSA""\m@*xׇkn.0tR 2$-bȗ\wf8Ӄ[љnMo#Y8k~0x s6\ompo!b͚}n23rkl) aH_ȃ[,E&rl_v,\y&kmCn?|h̴|Uzrtֹ' rfQ>g!4;>? ٲ{K])E1;q4ԥ7ƞ$/ڣ3 Zup)GF~z^u|J;5w/_wf~}_<~ۚ*",Ƈ/l-BI߮yIly<0y>8|1v(5IoGFCF@覭H={=oL>p0xF>-Zm(m\cϓ`Jw:0@~[ԫsQ/o\_~zcSh-A7?U칮іjMJS9=anzOez.)9Ԥ ȟ#><&nw 7r f7rv1kՅ dUSEoc ]te К@Ka 4筅) 'r5[åܝ亰b*sION%#:|O_"iȡ<8ˣA0POO ey1H+t:1P@e/g5O"x|k]UMԦ㤛W |@Q|6FM5EJcaw…XOB m8< `-lKrADfMDjT &mY(*[ٍ+Y('6Sٵ\>i96O[FΧ3 lU:~WkEF( ~q\;GۏNYXu̒]ٰ7y]lj=#X+p=I_@CjF4=DSz\55cwr 7V",fJYaZ).|_x|<ï?ptmytͶs!ÙktńkK^X8!dGQMvX*9aI&QAal" nVM J<ܸpsBD1! q++=yܛV>|uwpwEX3Rí9.[tMyx57 )C@ j#͈!wDR+Ib)]Hn35R[#L$xr<( `nOb2lRdT@e܏2J}Q+ PW+O!ȷ?+ͧ]nאرjٷs RS1hi ;$fDB7teY2{%l}~+IOͯk2es9O@D[ >QY25[ 2[bxtx? >?_"Oqk(ȏ\>;yԝū)2瞡4my5ٚ%̼L9ӱ&L0ST?y\N}wҸ,bK![ #"nW&q7ș⼚@xUV乷0wW}tǧdxM4$$p6rgX3W[VXoEEq7#`2'K,L hCaR_}% "W_u cn14RAEl i1sYE<9)u?N\ );WK_=(%/&8C';0T@.t ;|rq#ՊL߹<ٖε\]J/>›{{EȵQq n[)ev3=Hf';0X@[MtՖ!lMTSNH 5鳣v2Ztc*7{ry8)frnMuq69d%@v@$etW (vr7_/=4F9ҙ,,$`&*($}K!Ecq=KBϗhXN%ɭ^4yp4_Vh(i,I/9䦧`x44̶ Ë`c̛˾y ͭ S?yU&?AybŲXB>Du_R_}FeweI[Ӭ]Y(;W.O~;'_]{#7Mz>?L/&cC|hNTJ-ܷ<=^I5N93V2cHy:4Eѧ7"= $;nfӛEwAUHyT;UinV)n?ģ R ^Y}W%q_Rq̕*9Meⰰ&m;"3RC(KT2SfW ޹5e/}Ŀm}zhDI9[Hy! <*̸JE.qI{9a$QP6*ۍp(k)da9.[ђTk ڷGKx~/o?ǃݫ׆mdz6ߒL_n rgF&kӓr;Z讟͋YSBsQ9e%QDHyWӸޖ-a@ e9)I6Og%ٳZ[r?ϕ¹Q>td2Q?uDM8#GS_,RYHem섥YrA<"';,-P7q.Q3W]d&?@_>o s 4lCCoX|8CA,΃,6C7=evd}锇A 3.0<D꽾f 래qQ[.~hT/w!urd$=֎q>H]||`%!GG(X](<ǧd*-qRaT0S̘З``E֧=J=lMKVT: 3v9c `wwh=$G{paZ{+% wd{ .M"Ǚ<]M ЙsŖk31R n czsv$R%ű{QepyϏ?ƛj*)CE)+qndVďoM4zsr`KyIDzGM^d*Yd\.]3Ȅ1zx`pV MUyқ@!^oxA?>iMUL?o=Dõi/h{ mtͮb\Fsy>fQOEjDz+3ڒ%`"n pKhgE9d$%QNɮeW\sO@d>tCQn ps=thȠT$ s2Ci)pAٿi)DN}|.D^B)iݒ4dY[ dqd.bp"rĈv쐛O/B"I 94_"X `5^U`&Sz/ %A6W ذa < |o+D6"j-KQjRCw8ib$s>}k̏BIE.8-Z媶$-N0kyXjtqa~*iij!RY9:dkq2}1D-zoy(, ^+eɺR ~DUҭB ם5E&db]YK2?Dv;m[oȖ/u$n [3jstZSHkVy{3TzRmKEs4I~>rlT0mihіdw'\#BM~:P[.;Q)SUܛj{=,y^K_q*сt%[]@oEh'_XMS-ٌ1ͼ@?ZLYUpU4VZEgcMU%T ) 2)\fẀ LJ=V]B~j*ui4DĐKULW.N-ZyHGzgs3^DŽ8oo}TQ[UDY~;RbM]%4` kt 7 Ds1[%~=Yie˖mzb"B"}<@rk,R9Sw҇ ;$N {dqW'cǼ,;McJS#Œv~H=&5ڰařE&muo-$O,J/VoșAx^tpKVO-)4›O> y dٛPxم@HQ&iڥCC>52 c\?.~ {y4? T88F*s?Z1*[1 vBNN8,?P2|z˿:W Y ?۳n:O%1v2Nd]ZNQ">pbhr׮?Oc F "3DR%nsmyo ƍ!*p:ρxt+?ㇹYq1GT0ݸK{0qcAfnVCFQZLWͭ; GL5l}"]]h݁ύ4544 %@2W0,r()8"HQL[zc5,yS-2z2-DYsOZZ=-^TP8Yen}%Kt=ҍ]Yuwsy3vYˏ_gV~ฃC~;?Ym/.ϾuxpH+*{0Nw9яѝ$;ʹPR{d :jD H~{u9ޝjr~Z N1'IXjb$PNf`(v܈p37:6D_=CL 71SOUQ:TaL@ZW!W[ӘjH`-bZJhFSYGsv-))TeQ#Qpyֲf7ђ%Ld"\rs[5UX@]aVyAES_KSQM4 KmI=ъ8S]TP>/} Z4E'f-dk%/\:y2 ;v;,d1D,* ;dTi?ߕ:LwRrmˊO@dTg0'ߚSӒ/8-H9SeKmc|v3oO2~T8K&qZ|툳5-w$THVb; [6ӻ3*|(w_Pwr~~} v;o.^:~(ḅ1̿j:k*~2p/|q߇ D'#|k/}^.ΣtJzZckRrVO) Lw̺ۉa.V⹣qtqRJa2[1ف>ΊVُZ>z8{iη4̸[&A!lYGqJlhw$Ǘpz*%lՙyy4 櫃TdqYYj3s7t3Omn>Yd$S@A|,I1yi"*y΀h0=;jrhɧ,3$Z@zkd%+mL60ޔHS.g4;/$7dKKxl+ϥ,t+hf՘Ig^)poӍRP@{f|2i^˭ʋ1ߟw[K1ޞ3p5bO!"y̗Yp<;Hnш/?a!y^GZP[z&kȖ',IH.2Ex c6lCyAƿ5pS>m1M-ºYœ 7Dltbh2wن|w; f9aN7[vku8"dU~|Gj>\_n;2iT;%) TrZ M=lh@X܎ֵKmNn/{7%v`" f'c*Sd0={]G^>L2tnp^j|)(%EXIU\27BvTvoQ^TKs# &1 rnn}8Ȼ^Nrq;@-תj,}vdJȏ#rL]԰$BY~3}{ 2Yý+]LU2VJsI2UY4sh-Nx%"n Tp]kes7^MsYyd\UAsM%-gМHCfaDҔ@{^ e"s6L3UFMe6id'CN"uA4xіF[3YtySeK}1)&dCE~7+^z)DB?Yd.d&KMdl?3;xr: bI,z"O؇ĢBc>6_oBf͚%/J`搳 g(X]EXO$՟{[ ;kvu+IlAtD٢mH-Eڄζۻm ޱ\ی dXigFxq*ЋK9jp@Su~ d})>30 6я'$~ϗ-|` eJP-J Suz)O/COŅu&$w'c_896Q35>Q#v>DYU`^!<ˉܻ­f)%Mͩ K!7.xrH ՏK( N*|_mg<+Uܝdrv\ZJɄ\XW ׇ=RǽzQ;Mzf :i.`0tJg"YFQOyjWK%X"O!7A"/IYUT]r[뉮Y\-ٷd"X75_Ԑؘi24wkٲ- mF]~F1&ߵmN.<  D.][Q8J} ? q%flvaxdho% >s4E|~g|!G*_Z7 Or5_oKey/_}1 qÒ  ԶnNI'O޸oʕZ|]q=uwٝ8 ۰ۇ!dwa!@b ꞵh݁q*Oem?+xЛ<2q /Ay_E$vm쁕F:}\e$@&3|x{yy֮XǮ/ejvlB94F%KGjS?]tp_f&[h+F߂@ Uƒc)O!ћTՂLL1.dfAC2{#I{0\_rC+Hg>M DIa"a txӛ9:tiv<qާy T>ۖ7y22וe{PβXJvQ/Ȟo1绿 Wkd^jruZDh(Gߤ?vz܅OvX/2o''^ygyy Ξ:@2m>63ƭd^NqY4&,IGF/frW=]ʣx܍vRcW4øV0RI}t`8w(f1yx3=O4.3G1V:HI"s# Kj =НѴH~M>ux%HyYĊIN(#)M5)aT1ԣDc(PMns-?1"8{)McbK(K%#)Za#] L 4ģ+L7`N<6PePp\( )4TT 4PU(Afh Y&T%>3XxS2׺s3^ÕjvǏ$2Ag1ڔέndZhr5ʙT'swBO &+ ,譧;%՟Κ /kYsץ֬2w-ĞEֱg@mEY [Ip:__j(s?D[ȹE Y-̖{=;7pb&µϡ3YC3DiPf'^KVe!#)<~/y#ӑʃVb+Z@TwpzJlDcFLw>+[43:͟|σ_!_{>'Q eJ2+>5/կ^OzA_!zsJ=Zq?c~Pgw'q$m3MWG Sx)+cRKcXA6ű{V;&;9PAeOzcV?zzHcf 7ZFSg}Kltwwtq{@^& ria"9W5) EK~"'ˮZȁ@?kG+q1x=xKݙtΑ~DoFx"ֵ3b4IϢ2*hϙIS7r:Lɝ.> ~]nEJc"ޙ!\ Qτ2K b.bxJc` @|#[=ӛ_W-@_?zj`W0 8,YKD:B@%<_9T_Vϟ}*LX?JϠewao"HzOW8՗CYY}:|Bo2vGOn6TlJyTz hMxn _rG%9rTCw|0$6YrYIvuuxeG{9.s!Vꑮw8/'.8DZp!qygJ#yq7?b\ "OnF =nIl=gМFmt$ItǻF~YCL#-XWs6(wZ]rf(ܒ.ooF~ؓd+=Ńɐ,rV`)SRRH |- _+srRPJ[NOo 65z |DYT hڊΕlZP޴UK1>RMMHTSXz$ \ol_^XNP9"t KJ6$̔<'g*|H͙vj 9{;V,g5lܴ}w-`>oMw߿Ϯp%8]l~ɖ\S|-4!pS3Rϫ~RsJyه{H~BIIƒBJPz oe2%@̍zO 4ٹ#M ~3 ɭFql=7!ޛMƏ4PNUe#.(4 RH'ǎh_BD(Jf>kys 9ts9fPLe2Cbh(-d-C%,f.8: -Ke9ՌlL&VH#/ƈ˔&^BM~0K?iU{W/뙽"an}e@ 3ٹڅ",_s^y\1EU\̳eN:C,~n|/ Uh\:Eq 1јhM  U2,^7!YMU|y>~JknR<hcBBaHe\`#fެ UPM^ER)NDk;m.X <"Og62ܟ s[_s|b1H.*۞(OmX+Bf븼R=yF/Z{GvE_wU%ِJ}@4ui-@De"c׮^+5/=GJ`"ц(3Ig"U@-|Is) D(L]޼%]( 1 IuݕtofR)8ũr[~ ?v:y[9{mYJΞ\=CԶ ?:? #C$XELard#RU⃺h>,G|$`QUqՐ̇}Etio>?kL}ѢO;jkW.Bv#>Ժ⣮M)%^4F ;K JKenlx /.cͨL$op&3y0í ޞuɱ'2Fƣdxd6!spLO?~\6ў!ǒ@i^0Jz[/@ifd{p"w'2ڒ6nOvrM@dZ|zjfPަ :(H '.Nw ~=\b>DM 6p!҃BKWZ3e&lZJ3/HK$ǗL*Jf.,ztҔOG^U)7g1_ʝF&*i͡(o'ySIiqhL-6Xjeрz YWldNnd>xJ` 0-Oe(\:u6 dg팓Q$'AYQ] LL245ȵމΙ`[[fE!ܙhkm3XD{i6^_AcU)d'%Qh1>Z SȠNx{Wap`=6)3|\~ɶ"Zl(<<2( &/9rVHSzj45I4G[0#5ܽ`u9d槒O#!ΔR]Je;%V1^[5BD#u)k(TT>|_Hɂ},^ xȈf|z/wu%NOϜ)F7jM-fE^E9"ڽH$FdyOA\tv5rcb,ʂbH˛ gcG5NdXgoO=()&7Ryj̈́.H9wxU2M:}=/#VYNt ӻp?/Tvgv%߀ޱ]JmAr鋴a(^""<^tLgnJӄfq9yvx=]}9pp-v t  5h5֝|C 7POmXFƜNp=P_E:BɶJ}8+SUVEMDw1@&TNnE^f):+Ǹ a\,ЇD_.,/Z>#INɤ. 1 wjU3v|[v^TSw1[l٩%|fKŽ[ zN.oTҢɈ| J"HM:)`(NվG#y@M?TSUbHp2twÜJLq?J[ 4w%;E)T֖]s/&Xg$MaarN'{|KELHQ"EvR]/qbJn@fe6{K 5ȔPu},hMg,Æ{ V޼[e^UFN 0Ĉȕ z^m$c8{ Cz.; ?确/{ -9!ilnNFX06n\v%?TrhΘ@f< 2=˽k`_O+c<> {5yť@FCIyfRu1TTl2IXA+°jeQ+`PUAgc 8 W`[xy:IR, s(K]88b)65Hr}ZII'Ë(;r} !;,_S}E! h!76)DBoȏ<{KryW|TϵlD䢊k<ĵgle4l怦$}J6kuYrO[[VԚ%O>1w<aR< /<>,KMk_$Q"/d< VěhbmJ3ŗws#2)8pE֚iҚPCuf{R@FS\i &R@[E saߟ8੶Ϡcwr\C_-iT#`f$G z )6I;.v!S{ hKPvsfmDN5h܃1aB92`GyJmd0‚X3nX3AZ7L3Av¦THґ!Pηg1n6'bϝ@q$aGI L9U|ז|d^+_)ag/:[Wyg}p&/SH..$-# "񣣰&9ϝA~0oɵ6Z)N'-"4Jsf-D|:r9k*& ) hv3Jwv:+eS5:]7J:iȤ-+@5ԕP]^@if( Fƒ=J-Y11ҘtC5 E$eK^o45xKbi6Ň8o+7"l)2!֔la.F3S/H%4ehDji _XB,ſf!OB2[=i}4gBInWɵKoHh 5;%VTP. ]_Gww <ڌ1 /,!Ja?74R4cl@8ܜvr&ΑT+[/;3F@Y2-pH=)$^$#tG^-z4yXgMU&Sn@ j.}q=eYBF,L#DOCX@쪵9< em(M lA.C6Nn[.gǶsFj _{_6J%Ye/7l h`A #!L:2nxs$)@f| itٚS蟠,#Y\@qAnԋK8i)VfgiեJz3 j2nkE| ~ȭ1BOZe'vmW 'O0BK""9CNܾ2}<Ãk}<1X'M"F[$14זF@%}7nnNPk mTVWKvA(%[tE_g+cs+7^# R#QfmH5] 0њN}x EYTVRPF0ccOH!;"%h͍*lqBa@6Mhd1^I*'tFR!#(O|u*ʊsS(֒,}O!w3%?2{sA!F&ZR[ºR,d ] eDE&*'͚%DUVD::YƊeK c"ڒlJ5;)Qf7kz= 2;Evm^NAMTi&R H,tvAWa r/Ē#%C2lp!J|Z|֖,[k  $"g󬓖FĚ5?ȷB "K=anF]f8[_hK:oٲl.ו˟יu* +Z19 $Ab"sEV.؁Mܵ;֢pxȨ6 `IMa$;SlI#nn8pn3tTW QLF$tl;m@& tBKsDZygIe*gzo\<O-i. Toi6pTrJQ@fFN 8ھWm wE 8W؋nbOm yct`㗃1ϮD857rQfaQǎpt,S <.PfC'9{Y@gV"厐s+].O15NPɕJ/f=hfvS)se27/~#>G8c$˶FDE^4\Nog-S]ܟ>^íֹ-yӉ 3Og\/NsECm 61_rʄwrerRH ʼndRPEKk= - svO4-YrG;$Xj~d֞D>,;6Riڲ/νc)Zt ȴ'рJTRjcH3^n{gA6ZjhmK!v]'چ*O+D:m(vcnĘsQQ'eMԟʍOJ5sM=03xRusA0 6 Zzl]ɮ/r.4%"i9'6Def4KpFv;6Ь>qg$Y~`#u|KR,ΒUoE9E.وUdPAQ:t p>pi%jtG_o#gw.C~Ջ8E":ԙN2Z=&>u ErmvVrZy%JR3N$gd6oDvs S0Wφjϋԇ\Mg%#\&2ܿ;,%6> ˸P\h$UytUWĕZ6dНhOӓ t6TNiV͵y 53Yϵ:TpcJqn{c\h#]$GіXe!TQ^NJfDx8w9d*3s,`*,&[3&RACM1iˆb] &\; 7\̝BЖMyeZ) ȓR?:Zq< ӹȪ?$'~G~d$\=,.-d'OBd鷌3r+]@bǎs]\:R vzoԸ7I920?=/XO%{=!2lT=OK*?ϡظY lszuuiנSz{msÉ:' iJT9QlB{2mlȳ7#&ӓf}.7AYg5ݹ]k+(Mˠ@jo'-(PQ;8sU),s$dHl{Q޵$ ;V=G7q.v]rH󞍀Yu\%;dqrU;ڲ][Pܲ_iTMR5dJG{'yr"&mYʥxAVs4 h%B4j 9C.YƔq?)ܪFep#2H{a(Ʃ¢w&QxSy:+-<5x0σ\観Bҳ '']'>44/a9jS-R_G_k creyn+3U͡Zn 1;U#u%'ҔKCUTI 볒؜iaIFH'˓>9*빿ٟ=my?=gl fGVXy?'@rf@N=YHҿ7DfVl8e-TYCfM~t9TQaO V"Io#W0uir>O>厦(.PhNM2YGvR*u*-R-!ʚL_I JH[;N\vTa|;MՎ!-q`z$V#eNZM+9s=:RPܵ_޵(ٌF%0%kj*o=9gp/\,-lvtj܎'"d9BiIJuHǩ/%(oXG*4鑧LI >H)Q@mCyܩ/=!.ej,zͣ(H@N@P.tey0Z'Nav0yЕFA+!'dfw^oQtD$|@ܽqOo|} &.(xSȟ>ďڴF2D_"Tqcfrmnnx`c!ͥLt0Q3o쨭ҢJzS.P%.XkDEy\,I>~4]@Em\a=<*(M*#dwa"8fVo1Su ̍TC@d:hJ:d)KowU O!"ͫ6ZdѶ ,?s ?[V]kNju^oRt=+-5]85n-nӋ.te-CvDNXF]&6NΗ@ אgWO7qY*bBhQhQe%M`PU *hM%=*t61(Y5TH43%̌0Vƫ lr?9f)RIV8wgFVi=ܶ ϣ#vᔤ9sx+rGpH^UWR׋snۍa9\ln6|>$Vrnn,`n6oFudWڗٲb^\} 㘴ݍapar'>p`S{ivԆzБXašƺn+ZА߉4P;.qiTdqIJxxmq[&Q(^=o\21Ii0t"q_="/PѢ5P ‚ofw 41sXntQcI& 1}gWV T؞]FcTiaTO(maJ9.@y+B[eDO+:\8۱<ܹVV| ^졵8Jov,H^^,02G&OaUuܿ㧸 !ƍ߂& mde_u@$1w-9fg6hn#[6"Gɝ&ECHQC1ҌQaAG-C;3L]ȣzb7g\ ]jTS#5eLU"ɛx'-0q},z 1=/Z׵W3ٜ/hwoO0_O^A2~~!89p2n~'! $qtH07DeOinazr.L 403jVpsAj((}n,Zi.0-`z|-d-}* `0;΄ a2P) lΣ"Ϊskc*X#,N`>bUיK}m2$x TfQfCiNȲﰑ Y +?k}/LCj{`QD5~ ?wR O,Ee GeXԕm̖۟e4gNx]Ѫ޺d=SVWK6Cm:L2;CѢ_M~O1`z6S4XiPnz~G-F<tvg ('/G4 P]c? 7ÑJ`vz7zP Ȏͫٸf"Ewwt'6QtN[ϝŕK{5ʑw/V|.< T=υ0ټeJ 6nؚy$pzxoLvIqzVH-Jw$M$˜{a<|DDEϜ& ^zŘq')fThl3;0(f rAqTK&F\22\]tF 7p_G3(fnn©ZTrWor^ъƲxRq.A@ xO^!\E2}ܨM/kIzDZ2ͱ޹۷;DaƢ,j hev7RO?C$G>mX^<)Ir]u%L9śC\-[B{U6=5YId{B$ZK*ˠ,΢2"h.g>[*Ff䒜LTb"d'PfGq فt8ÛY5{??$m'A@Yo8m oK/IHN7S_lɺٺ&(49?̶+Yd89o$BB}+k|W: 2[DY~cÕsIrQf5 _dhYB2n?Ĕ #zL2NQLZXMѩ%K&tYjtS+M4'UU&XD.Az5%v4⮶MX(Lm?NIf֬^s-翇Z*f1I~sGٵe9R4Aw:7"\AjK\0\K(E. C}i%=Th瞻=go *YRv\цQ5pd? lMj`|^)UK[EVeٙԗ\Y @GEnơ@Bj暀Ge^~l$ 1(=EqcTWTg #me4U.L#5HaѐO]F,q1&S%`]@ǫ`*':3dCu!1"qVghL,g%+??F+6[kŷe`!OBdq&jW_۲a7yI.ѝpe滴@P_HO/tem$Df.W_+у?fz T{G>Z#J|o]E=jpSATjbXE{ *V_@o&{3+$Wn5%JUɶV$^09O1IVdYYQrŒ,Jsl(:tПAkf2] 73>\ǔfqlfp"dSWUI_SL0rn ss@@TW)E4i ##,rJΞ]+J)N01DF;5\%lĥ$B`QNdFXRiJaD"48)˖>|6$H#AkWՀ;6(ξ^lt7mb2y]E'g6/(G/[轠QPDzL~W:Flۧ1|HYHwH?~nhO k@U6#k%'o@NTm!iѢ՘\ޖJ{ g ų{?WZ< Mpfn ͂>|J0Fv"=rh-bL; iϤ+%Z&Fim.WTJj)jVzZm+Q VpG<^+glnkܶhɌLRsȏ 0.ع P͎&rFENI!*9 "BH!3ځhKL ']Hϒ&oO!?"ߕ`$Ix,_O0|[o/6W,TbH^Ͳ28>[d! vwem,[ *bH_|`{wD+y\8*ThV&m#9~nnԘzXIR2ݹy?GqUYMYڮ&]4"̀LSC*/En C6To2{D޸CW[:סE׈h,yh;KY3 59(ƖeXG ޛn+UG):I w OgEP+nߍG{p:(S[V }-[s`J%VyJ 3T@$5q`-Nxz9mM.❑f^m2n5o#d^`zGZ;vw/GsY¡m 4>)TE|LNgAAt 89`{1g`17q$'ZGq{;=ܝ"2@[3zFsaYAhז09ZL]"h_exh| % b*ΖfF_Z}5Rja1TӔJY?Y)Ŕ6PY[FYAyDMRxLJcDbHeTmRr3OM"2Ї`w҂(H,щh+,H3 Q%GsTcoyk=@|p翥kk˓-~@ln7==ZmlfRo& J/"]d!CS%ȭX^C/.c̎ϣe\wAz#6/;QpWgM+ Ք.:kR%@JіGP=]yl^2[TcyIk!:t0G>'^*Kˊ,%%w"/Esk*_ <2ѧGkw#q=0%ȹM8q# 7DyS;yوgО$"*'0T̄vvzconY-]S]*d1Y޹GKF>z$2645o1\l9k5K c8)BCd#+"1"GE[/\|0sV@2zZ=9ݩLs{jHic~a %Esg{/< !5)RsHcdwp:[)6KJ+T4&:NhSZ V&ИOid,URZCin9QAFLP1\LoMCs6SVKLtL(qEzSMMA/ERnOf ZP2~oTfR,wAU3X s6ntm v06c3=geL0 u鵨JoJMGvaJ|{,}{,Sa}!w5PA]MFtu8u}2UPA*je3Eu (>A9}:IV7n4$A$g+[ v9 D %>\䃧 1TUYTI +H|?anyL%̈q8OypmW;Wt/Ӄ"зQ_SNVz~:rGw<| )PMsu6E  p=4QEV7% 4tt"X&a>ܙlp5W)ҼH/$ԬT )L -ē@B $#*DJ΢.b2s>@$k鬌6݋PBȋ#'Ԍmh*$6Xhߑo+-_ fic؊ofϹ2P%g&˖- ''?jY-`c}rw,xW_7uOmZa G%6̳Kسe-vle߾ˈE,UvZ13LMVReZ͈=r+ՠҊLZSNY3,9*B} ;`O!|`'dd .9k##sx; ^uO `#v@fǘJSk£/ࢤBGu=m k)qx:Lp~n~x}IvvYao: vsnv<-iN+fowq<x oqj TC~^.xy~W'\\<&<(82ӨO)փ0[Zd|C"j ,C@f;St_r*7jc]4N{l 10Lv^=I`<:dP;K)(2~Z15y2"UVA#k%w TkjQȟ 2gO|Nvm'yf=ێ5ƺl]{BUmFiIn-r7p5a!*9>6&&8ZZleB.RnN69j}TC%6>q,^zh(pd& 4UqupՓˮ> &d9zbsg9{ q\AYg}yPKq{ww@@(uw{4dvdwvvv?ϓs|-rNM×|>+7Oܽ|+g9zr C>bI$6.lr HFr!eٙE2XR./gFgr3Ү̝©a.L3;d7WO rK3}wӘˈɓ~EG:(ђpk- "0MUxwgGod| b#ɏ @+Ʃfi.TЕMa^HkHt[9֓@/ͭafgx swQ (<˲'Dq9zmIj \-5v5x_X,Qn ﴅOCThTd՗NeXAFxZ`QA"kD@wpyg1uqs}}67^\;3o\xܻtL(1ΞfBBb HjlWR)d瑒KxpYa%_S\tKCoʠc/:TηOoazXZ+P@}t1ؔpQ:cݜ==Év+Uy; 2XYPH[i>Cu(df3ƌGj"k.ƙv^mΉU2)O}FEbnja/*hl$_˝:A{ ٞZOeO Unzf qM(t+,2 SZc)(IDAT !"iKI5Q&3F 85wiݒ CdO&Mʴ[`bN&m\LMɕXVNC=F=Qێ 2˩Хg܆۸i,6u`wmE!Κ{,TYK=[U*`g)fxXί2E|{=-ɱU7RSqM"mUE#A^$GTX8DD @Iv"^ܔq$YUWxM.;CkQ#>&siO^9^]7s(3¹O u{>D 0EKLl $'+'?xrBiLgs pi Eƥ\x-WsP]jHc v41:C⳵42щnNoH}F0 ¼ (K`=(bH1F* Sh.ڸ{C%kʠ2ѮbN1ٛ/XJ[/ "sV Y? DRA;^?@~Zz׭[.U5KVU8_qUGf”b(,@D1D~ͳq!=A{Ռ:e;+&ŒqRV)[d(BI]B:52Ѐtݲt1ʨ'}iU1&Gˌͫ7|5pSK %F ו[ gj#sTwrvm}[qhN, Б.*ˈ[Gli4服9D*Y3mGo -%S_HO} )F!±GEvdNjQ{5\-չA1ԉI.̝b{*"t4&2޽s/߼/IWH<ιnKD%Nh`ĈG #H\\q $ĥ#@# K{^.'v*/܉1 (]>/ >ĝV.Ou1=.FW 41"1>Ggk-]- eEqIR1:`{ ۟M{uqbPYsbjr:i-!;0 Mlpۧ(`Z(X˹xWsSGr91h2!WPRLXY8udwa2M[6cBD|cρߑ]m}3ORyِ&5QG%\^qpgEAu)Nzp& sS(+nyz=%6BAN3S*TܧȄ&j+˓&#G40oN5LQߤ> p%ZOj;k267矧A?H }x~JnkٰvϮ\L;Paq5xxrƔh}e}U6~& /Mu,qܽm?>;N_g ChP,a! DE. GBB|/p$`Q4wo^.5_y+/envڪj)j]@(/֭c WjOᩴpw hx:q^&q*J å6tW1S9v>Y/QtUg09Pd;pd-e RޒH}}EVp1N U;"k狹s}){cg==S{,."s Y  L%V.߳D0XYob6jZ"5]YX aϭXFv+kpTېkZ`c5IEuh^^ L+银oOCj4ab”]ZXQKn,Zdc#Ǎ h1͑T[ZmBXI:9Sl*2P^Mw |z 9E_w\VYS 0X襰)ҺBtí j1DY\j{ ~5Cfuu3"N]+\O"*dʩs;2j!޻6o7Rc8i.˪j [{ wqĀn ]Jy Jظ|)7-A &xj쮭޼-ϯfgpگ@=ݸcA-UZpn #Xs;mrv&IDwށJBJqiく3іF\Nw(-WMxF.o*F/^捗.ƾڋ:Tw!Rx N'>*V(xb3o1a "p|4mLIn^<ŝ+3ܽMtřqZ*Ȍ"94 r2 )FںJ8uE)LE&prd^L?'ϴUDQZ#w51yDZRDH rz#PQPAswm%SJEJe+M'ё"pD=]cwZ'oM[G<(k!$(.N 9<8¥vʜ"6z@=]aZ YE" yA)([Yl_iu aE}L׭l<>v?~rG%5fZ*bKyާBĮ$GM(UPV2)PhbɎSN4jhWC:[7*+6L L4Tڹ $Zr= wlXKElڴMWtvLŐ7 )@rXنb&b2;LNr .$8ٓFx(>< !:$``$ξyTQMwD-]<.ҕ=0FKy#-u}6I4dlnjoGa2䶐aFWN)d'E"D/UCqDK_( k2ÍDN |s859Dwk ň%ƤUHymm=4 E"]wޜK+Lo]}?Rũ&%TAJƚ,c-B {o<[$H?(L,/4\FF/H)mԦSՒDaM7S\ho!43wn8HV j{ײgwEY<ҿDy gyHRؑs(Dyƒ6PVo^nرL+WY9ٲ?6`m͖=Qz(qUEYu34枅!5~hhځ8S](U1 sbFR:$ͦ ;;pN8mȑ޵9#Ð8X^C=b̆ukЗֈevlX+&CU>UclHڧGVe8=M7>jy;Op8Iۦx-Hpф&Ɲ(^cb]c>[Q3dfrcܻvN@!Uؚٝ&%;O[g0QE}vtnEbEa3ia'w$>1dlHBLƻUn: SLQQZA|l2 eKBJET5S\AGoS/]|"C(kJaQ:G86d'q w-@f:KA:멤882 IqV5ƌVG / Zɬ"U@_\mpH 4'ջC} 'Zm*wbj",_X>B'N?0 lQ [7[ -vu&y$/|g̬L͔n-#䍹pЊU'S e(ai+ʴ)qR۔N6$U92dQiH Gl8`KA25T{~6,Gs*垁/pƎ{صl;6=Yb֬bu[˃S">UZ$Uo.Zbu*9BLN[Z`b|Zot+|cC"sڎTtvp^d9Q&FW|p^AyimHuצy=-rrʽk':ȑn2 G"/82H'2 /.Ӝ; Zh.w|! pfWNs07fq+'Z0l` *ɮhI!E@;3ȇpDH5 Y*l#2LTS4бV Lw<ܚ&PLU7cHP`i7ύփs-͓ܸxK&9ubVbR2'#$,SSI!=|Jbh#i.\:ǹ3L HkG(aƏqlbӓZ$zN40Wh{6=UddUVJP]ծ:*㫭@1a~&SMP" I7^GP= ttEZ_e9y]LLXSB'(:=C5\N^=W>D~[.f9'f-~."Gka }k0}ݺ%kbs\j(5\ {C}֐g# (ޫe4i N yn}$O &9ays{^vbNӔd 0DlGcwX^6m,8{ݛH=xB]̫Ge Kx)caL y>,+ukY,nK*/{ c=jSWF=Rk^/\}Rl-1k]} "2(Pԓ?ށ$ ʥO]0~؊ ywo=H ڊ^L*B`$!8菷/~$jnڲ+|9>2J|1 e!+bizQNP"Ws(]m IXTфDBRzd eVIkE=Jӣ-$头PqRBD7szpb۩&:mM0[VO/Ί{2P''؃pB.){S7PMV]EQ,4Nj[9 !\C's&oZOܬ7 @Z4 ۷Vwca ~fY>We}b/1wP&:n05 U13FɏTMmw"Q"=X* J#lʚ(h")hZ8F8eUQ֚ș1ܽetM7yNNsZ%g9>mJOQ.IR!߆T{ٻkӣ' U|QΚ(r_0bxF.Li^x _,6+NuPěM^S᤟SJCvXG"-8l&V}QH.eN2)%G!usXs?~rPMjl\+T\Qou^ LQcj: ZOb&)Efz<D" ܲe(N בi"I${jn.<';@@CJՏQ3+mi fї[߾oBmˑI;&Ƹ98 5HDHa¡ EGP"b$Q'&ɓX7޺/9n^+ 6PUH$O$>{7yY^=AZ8o$>!F&#H1% $'Eef!cvӜ95ƹK^nmgnJF+iof`#=t5THe 2<əT#T] xh$-ĕk, &: p/j"E\ ipΛW_槿Cd[:kE;>HB K 0",xt/_9DwᏋI) ɦ8(4` LHtwb/߸޹;gx1O\S@ldAgdE$Mxl<ɩD&+H81x5yE]՛\z3'9s|3=\{s\>°EmuETוQK@tfp62zrd'hLTViLlY qn"5ޒ/Q@H[=ڕDQm)9z2greƧ3Ǒz\&;ZHx9/hmG$)xU^Dfkc\Tk/ޝj^;t=^{9(ܗK>c.P~X|ײGR?x_DPXP$k$X,V!!y7-lA>t℩{q=>iA7i68Ę ʚd%_;I1D]XicĔ Whm*%Gb+%wʊv^?j|V|p >Z\ro'KRZxH$ :߶v;*[gB29Ñ< 0#DPVrvgK= !6<?:é6pcL݄i|q?Cu#~|&i8! хp0$|. # BI}LyUe6swQK )<og8ܹ4p-EF$ W@EB"c Ng%Dxd|b$Ms%^ss7ϝ!±Fꨢ+Ko|{s.b'r|z`{n]iP#qՑ'!ą8Zsȣ_ rGk?RBc}Yw712}3g̩scC#] I/15sѩRfg+gxĚ#"*ʤnܞK3)^e7^*y_zKU)s! ~a,co6|2YCO 'CX4(H,Eضhtq;!`O/}Uس>C<8iqMoŭ,y?5 qG;ЮBvⴝ@9ru(tӢJWPקKSvUu*5eHEY (શ'=Eud=A:|(8Ў HEf6|}{)pf4u4غdnN8d+}6gskͬvp%_l@!#.q֚e:õu.o)ĸ{*& #7@4It`4J=!+{c"գ03 R_CWTeγw?x2n&L^1H#&$4~BCc0D@$</P|h.,fy=/_ލ9q< sL 5#r*Zn+uQ &0S|CpNWLuy^TDܔʠxP. =`cpe{:6c?q1img`=Ut5! Ep{.'gxws{ew8} V'qǧ /( f2(/o ?J@ Adyn$k.7.<C۲(^[ W].ȚOOeJTxFל4S=bq{=0Dґ^H!e)=H&5ʴYରw<,]}k 6څ$Iw\u֪U .mGChىKKM(t (7q@'Oji4涮V=+4ŧosS\v$+ %?H("" tNמ0kn7re@mh/-`uj]R H;ܩАۍRl5]E B)Pfa1 " &IH,1JĕL 7/pNN2Mqr)$Ǔ" S@jy-%ͭ73\4NsY{+s]8 L)uŮ[ PPiCcm4C5  U0@ƫ8aWGxZ%_筋e\n`d>Zim-c;LQk:5]Y 'rmڏ3n̞H\ vsgx֓ÔTHo6ۙ䛥$6,Dn!s#S|NJci!mU q@0ټ(b"~Q'l5pX,iV'ҹʲѡOOL* I'Jz-m"|Nh7Т\ 4hէF8lw<ׅuO{*voN{FRpc&϶+x%n Aۈr[[rlmuÞrw2Mݶe˞̐poO6[\9F)aN7v԰Vӂ&}NIx vGOD9;F8N8H$T(yg%IP*SGx0Vwg~ztS-ͥ>dlR DK28csho&͜`sȉ *O$pi˔(… !@,00WRB=+S\ D_>-~>)vA[I&?h'Zj:RH;~gOqWnU34XHZFW3:d>M;0 }L@H! %qq/µ  jN%/;̤gnm$.tO  QWJsgaL7Uqzvs<ϴ֮>+p\*"gItfuv-M #񒍿%^m[ kȣLm_L }¾Cټ2# F#f-;M 6CZarsHSJL(*m@fk,9,,ք¼w2^]}P#a^⪣@PLVdh:3b%-;M(2Z=bCpRP7$xx %⍟;n>7s %/\(iC%8LL}">/~rm\jg 4 'iHu z2ºguwcj=MM:J!e;jCݩa ~nRKaJtX2fI5Sױ^́4U]憫q}?>,dknL~ xxCJ$X2*H|pta4;j\ X'WN73NKn6u̶03L^lj0r ?_\-m7RS5E,Uѕ'Y'b j$ I8ByS=@cPwoO=:Jau#~A8svC@?.J+.*xO:R?s^.":)o iqZ*=ꝩjUɁ\.څQz8u+gnu*5t\α1G-xz3m;c5}7nrqB1 @]7qO~jI'b>V/5<Ն6mZ"*0ŵxy#)O/S_3D-Sa|d1HV>2mfpC[LMv=Gt1"g.iha> X7~gicHG1u5PADD{8J2]hXJ:#LYƴ%ho$l_3KSO'XT$O=)>p{V˓)73OhV,{xgs  v۞2[\Ɓ͏=2$Ʌ:Zg @`?w$!9ge>,#R dSJA!Bኛ>E@d;Wgy%^wWg86s! qsw?Bdd[LLnٹS]IciFg1sik7}gs;_lɤ:[i?c*Y7YA<QQJ1Eql; 4SdD#j QA~*59VlOAYf8j y_yu80B`(ȭe>3Kyeߚݥ#8_}(ۚXq]{6}[,rԘhD}l<`y9s5'|59nzd늵9^z7M J$\jTKw8$Nsve3GGostuq2PT{#M.q_==XXѾĸx`s8Í8=Noaea (FPA)%\:/qnܿ;i&82o< #<+8B3H%+<:'.q=ٹn͕rLN04Furp%<2<)ͷ`ɍvf~$/s*t)+pĜ|C(qdu:8ZN%j$e[֚ԑV$TN FyZφaX3IZ?%kFE4م^4O'a)Lmaa] G{$!Ģx 1e)" hx9&UMh1mƄ6|Zu=Ϊ4((ӫȘ:&i/˔)L}x-Y-#6Q,O2YJLs!y5],-L.PvldI܃Z~f|=V<=x⡫;kVgO&ENteS7cGqBQ㯬D3%;yhf7_)  MP"]cB@MjͶ^ӏ_f8LcW%[LT`}?q 7/K]QBfh" )Xgʃu-n޸ɃpwL0MI1!ѐ0.fW[ܿ~[Op . PLPX"Ax$&) . bI r3~S09by|ܚC\FR&p9 qz4Ƙ=:`}4qguؚRkjwe8Ӂ(Cxl"~N{p ,ZNKןᒸ/ 4p 3uʳCSmږh9sDM7h"*"xR|noXjA.DyQDf!VeQ8ɣCaZQU(LJ~- o7S\2v=*(Sh ]*j(&6c!=H>5չiTr̂JT2Z,3Bpfrtrʜ@Uj4hutg^O?Ĥ eOz,_L䰵  ݡLS {)-aRXMX ' ApWoʄB9٠hd{pm|7xUr^dݫ^BeةVM+ $D ȅ!I %>rZZ(&}vҥf=Q]MFJv;@O * Y=(V&͖-c,]K})Ǭ>ٲ>ό-'^5?HAYRA.p$ɔ,td:9u>D8@v&6oZŞ]k5:HSWU%K5fzgլZw: SپH3B]/@${5۩@a~ /|:"(|sk\}rO8@|Cj BDs=QcZ, i 3d[ Dic" {J?@Vo(~QYVLaIϫZ}/U387])s;$![=ldRέ3c|]|;˝k(@|¢*$ .4BK&H((? ȯ9.^krE@eb *)H!=@M+,273)I] ̍K=LUM;u TtP^Bu] ]եg-2x׍ G|m%H yTDn"0И4j ̣,A8D~w#%ig¢uܺixO|ӼQgazB6ףA"?c`%)b,{'Xy %xI|7j-W)9N1/Ca^d,K3YJPyHi3mcHG-v{=8M7UQUݎ֕۹;EBiW~{BI R*Q7(ϲ?}ؿq%}Bԭ5}A) ^JuP ‚T/1u EP(r<6a+_ 46А)_SC]|, q$Gg~@BBJ%o Rlw/E>;[R ڋ%*xb|M0p4޺;]qfgiH"oȘy2,S#6̇Bj[;vr,׮e$M]bdݰ36X'}x:kKdE:#޼Ǭ^bW=M6 N80NqZG?EUWiY➸丗,O\ 'ߘ@]#[rr'jwLrQi%eDQiM8f&ajTWNX3Lܰ'ʑ>:2xv~Nv3&UxO 3Y͚$G[:?u$O="~Ğy2O/1DW.Fm#rV`c;7~Y!=/;=E]trޜ#< ua+P5;pTX1@C`gU|mr”y}ԝT-6e/FF8gerZ/'] Cnl\ 'e] {PM DbnP yAIJP3఩&N*LuqFHNxHo~BHB싣&:hJ͉Z^ˏ^MV 5pu%i&K{n%nF+nXe3)Ȟc% 䌿->I$<ϰ* r|)ȵDWKx4~q>y!Ks95 qx"S5T"{1$$'t"ܚKz=LsYnhiY`aBX鮔dZQx؎f aWf'(HHMĻ%,PXĂ,/MaBȪGRdZ`ۀ"i=]i蠴~6㶇7*5NjUw1nϤ*7 gZI6%O uU(yMVfT +ͬ~|!ߺi1߅m^5KY|"v*af.Fn4w4 ̎$+rlh !o3S|G(,gw*%yQ`ƴoV@I3I|71N^EmD0_G7< o2@3R<ܘ䥺$N0]K|ٱ D 9R\EC|1yմ'c{̓8[` N+I}^%ATAHyx/WBĞp +̅Y^/]aN໏MZۇkD.>`kf,gPL;^y>;#|r//r%볹.h}i뺛-ȓbFD?eWUrd¢cMN13S|xՍ.{1lC!uBrǃ =f\'c'y"3gN`il|GRIHG@P?oQַ?Y1Dp|TmDbC7{hE8_Ai<}A;Y1r˝.F*aY*NoƬݼ](hlBN;2R`RMջHvsFo]'P<kX?=x';D->;U߯OZ%N~[6FYihD-u!ΕP/"DyRkM u 8LjC(ˇާ }DPI4n댻0w}MBNė`G:⣹T ;]rƒӍH7$l*1L& HYڹ\U_'(uЗہm$ڨPhAf!B 9톟+6Gyq-]v3uoq%<ˑcdV 8&gf>hz\}'_ϵ̝*;-|z/ɍ!{6Q*|{;҉h)$&b.-!82RiLY?>fťx(~^dQ7G!FnpM1}U8*nd.X"/Sb!kKk7rK~SCaEjv1<0O1DI^,O,}>EV͞fgճĹ(e"縐D"tQ]~ebPnj~SLk!CT dn0j\{v鿷1K#OϚʤd0"Zh4p氽 [qێ"1*q& '7 DϐZc/Ru})gQcڐ(kNOS샟6x.G8>NkK&՗hG'cK爍=Nnl_MtPcMk y6bا/^{Eℛߊ5nπcݍ@-Ր( ׇwsp).^=Ź% 85)]w2V8ƌ@^jgB`%y F«k**#0bJJHNÝZteۑh"Ց; jr.=+u-Hڂ-0wa̗|SG0%{ѴX><6i-뗳~ozfIXm[ ^8/'\)RӤHI=zkˈVeԀw^Z5h1T֕n'O-e ft3A*1rOu$ܘ\OkJ](8`jj 3dÆuoA;IZ[qv%ep9 !GQ7b\<  uyH&i.\]MbF~#%z=d#|o?ݗ_ళ/IF0P܅:nZC}~}zXc&{rSc(ɎX+l5#]ˈScx%~w_o҃+&'8Ck 9u xGcfoP"8&,;˒Ӎy;=ǽx|m>~~7@znM$y iMA, 1')h#Ia>y9de◕Of> %EaDRdJW=/ `!%%CD &X88de/|O"]} ru/cBi::z?n'}n>KBY8i+`-.fB.@(q X*P(`HgcKb\ 0[\]u%ՔПG%$z@$gOΟ:ʏ!?z"g&1+gqvX{:KLIVg9sI^^_}|?0yrՓܘP' 7Gq߁rsa m]64. ڧh8On8gHOK-9L#w%_#ULuIu*p[Mn…,G.2Mml8 䇿RT"qނ ?),xQ|D"h]P#[ ]c1$Ȁy ?B]H~cRe 3hQ2㾉9c ݜ  ۱ hiÁMOn=~ g[jhDvPDSqޔyd5oۏP_EbjOGk"/6QxGn$ gF/^A~ ?F r*RG6{UzH3E54UTkc|w$݋iP9AanG3U|tr# .#"ZcS8ꋏ[⦩KwuPxtg:?,%ffػ8a'&)Ǝh5dsvj^'rw_)_~_|p޼* ->}7_֥iƚ=h2cv25t2sg,9y9c48Ah?nɸY%'b{VNi9K.YbD3.dRkH=v'ҟ`l=5HᆴRFɦuȴ~D o ?e{ $A?o6^R%Y4Ęl@χG!~|/ѐCEr5L9AEtE +3emF&[.kV`L9f[`NI=MbTCCaf#>o7m~+B<q `.bW}^pZݔNTu9UVsI-4#`A&DzYxc낕'VfYD7GыwۮƟ4ԔٿBV´ˢl-%4k|Xo!>v!gW밝lX{nTZ)_(x|zsE PNpX:dk˲c?{Q9@=@6…Tj"=q1d߮6XA>zM pڸzN]}| I#ښxGWNົp iDQd!PmaHrWm৮XL<}3w=|'>Ae"'s`Wx ^6׎LM} +}|v-^ tf3IaPPFhQgX^0}U 1wbgyU>y}~~/_֋׹q<_ SѼ({rZӓ6Ş#\wbm$gdkQnA}[ -8`PSRaa1$P`gNc3a쇥e8hi6AdS/>_nTHӀ|SK"r&m(FM}1DC/<(_]߬1b9og~Ei3缇&[(6Yz4^c3b̸lg@tNfܟ5*x" ZmځXZb<) pRC16 *ii ;5pu٪@AοG h0މ4{,b}4<8ϫ`%,ݫK]aZλ,"nښᮯ>fH줊i?./_$ %`ݦX^LL?|\p3Ǜiȥ16Dٹro3O-E[I0's=)O6!W" L8D]=v磷/7ї+>͛rY._W/ mr>̧1J[ 5-NۑL]#>h'u }*a?RpK#LڟD;2m)8Q&@LSfV(gr@' YM$)cآa \(8g4țFO. W0#YM&wpm/̸hST_$I(s#p Q+ Z|5Tq 4R#QyG~>,tq7dۆ z[6IMp(Wϟđ v4zKWOo~ o?{_ݻ//G3W^g7|4- 9o{ReB*)T&2to8-U1DGHps'%x kR-MI"͉"bKE$9D's8=(bm(G[%K!Y1kGJ:(.mиvQw@?ȟ_bEjq&+Q jEj Rm58b՜i~|/#Ooa(~ (H_ȓҵHV,gڕܴ}[gݪ?,-yŲg;D ;԰٤0}x;qmf}Ho,VԸQI x FpٻSm"!}eܮCZ%j]xw#Ctt6' _SCd#F 0 %$I/~AKػgeKWt9t O7__+ͻ}ɶ0۶6}^i±Q.Lν| ~۟?-~7ooOx_@ ~% |Q(^TFYQɃ8~~1O`NjT+Rh)2ЄUbM`.G^5nyQFI7^h{WnMwmށ}=m a8DiDe >t䨣֭K]HՈےoOݻ(ȾX,zc<_9J1.z[ JuDpso8!0:4NƄY\RznHqLmz}FZ)w&L oy#?.5#3=mS"THo?e)(73s\svԄZ72u<ۥVMXQL CEyticJqYa"zxZ(MtEyE) -, mA~vcB96\>^˟Oޥ->D3 Lђs>{ߋW3Gxso凟^x]=;D?x|U~P(>v/>x|:ロlIuUF|v3_( DmȍP4Z*/hV'YP;]<-a0ÕD_ZhrŚQ7 J1r0Z=M=Ah^xjŊi Av^S~]߹.c<_l]V=('iwҚ`ϝĹc m?2l ]p8YCLn㕼W q#5$)I:oWlNW%~dN 殤 Q*Pl-`gwlr>$x/ksR3A :)2#ʐG[-ЖىXiafB3R%.]n׋8FIubC´%*LF3ݷ.S)pVXc|ge|o? *OOzmx6E~WOJ/wWY"]$j6̻Ф1e!*acLf Ԛ..FD8QE{->4ڛ1HSU6!TنRnK9FNeu͗%Nٿgؿr|q AJS~<"!a*G6垥=yPHKA 1rXkoX7+5HA-4z=Ove\4?;wiL&-Y)}8E6VB 9h7ӆ3h"`#!9P$k6=윫(+oQlhOy: w-8%y* MX?QTJ5t_Ef U4rAylDHr6DOa<ňz|) 0/_ |<'_IAr?WO~P$߾+|}>./?@_E!~zn5JW IW=,^(?L ~W n { YϭV/a f V۳Ām%pPlm#d)OF*fwg?ڸPmBTtqQ"H_Bc4Zhw6 P|<<-h8ȉP{c,tظ~gߚTzR`bQ=HI PR&@P LT)'C, tt"Փp`g{v0N(PEs$47ࢢ<>&JDYkϫ.sK"Hq;/￾ͯ"o !7$x2}}|}`$?7J_;yYr ,Nrf^ꤹ7dŘO\ %>n沟<LR¹BW)rSOM<#iM'}kP"⺚ VF6hJr]MʯɒoGc<_=SlX۪O"w`)+[`w7ߋv;,{׾n.&6RQpxRA'^N AU+rlX ʺITt<+wcN VJ׃qk:IjӋL/W,ĶDNtY3ޮH92WD# \-2߻ hhB2w|=gu~8ɫ^_DW/WK|9>}k}t~!<*>?U'Gw[|U&+2̙*x~x_\#4iI!P(sވ5/Gf9dP*& YI!k_Ľ?fQDFr Nn\ R5RF-S|S?JWAT\}C1D۷Rx Vxb~cͭg~ u7sO:%J Rcˠ#{PA-5an4ƺB{?gӊW'Q(we+duۥɚ]h<}%+o̵Ȝ%b*:>6f"xXmcL;^.ۻQfFUx}E.Ns?!1* mPE4poD: ;p]4E/տz |"_wE@"_|x>GR%o~޼>Rllr2cv{/|,;ͻᕗ~4S<1#DŽTj 8B-(ط& =1rAϙuGƴ|DZBJ V.D v$aoŻ}[ٴ(6[b#O>c<> 1]Ssצ/Ҁ-.F#T9БP, vo=^s"-,+dz4=fnr&@ʂ*:ܨpD([7`$9A$z@a&oSe.5;t)QVTw /:ajlTX{R, cuwh+{"=7f|U>_ O^:.r4rwǩM ܌Ӄ5q뗚x>y4s=Ƈ>idY8Z !UNFΊL%;Jt]3ʝ|KGT#c#v(!"ٶ]A }h͊ _j#\iIpc8ˏxwtN+/^V0u{\eܢ:[G4/|Tb.9:RlGwREi5ϡ!S  p&ZF@Dd]k*gI> %L;uyIt6>-}|_?w/~|~ w.ۗ+| .?//~(W. q1[K V5.0kڹpٙn_n|(/ʝ^.^mxqyp@͉:Ϟ_`_)QAd'wYJdjm1P3M˚"KwJ d*+ߢM^_fqmeHƢv(6gm /Oi=v!ۓO.!FI~PgO}izR`ߺKW ȅU+l)cn?Z˷qpPEt|I{2ͺ:9PjC aZ|ZjmڀҞTPK^eMܩOW9uh"zdW>߾7&;~~/?y|y> {2{3i,:orF9y36nDn F33;Ƭ*+-p[pS4僬1{b9[4SvŻUbH!:QbOj? 0𯁖|rtĒoQ#Lu#[/Q*;ylLa7C1. 'Iw/hq`&z襝M3t$\]-ێ֓Q*wz6{o ȳ0 HH 5y?I͔f@dާZ94c( )1L kRr|L{_ 5[ï/x~~y|߾ "{WX@Onŧwۼ9}n1zƎ.*2 7G;m}̜ef'`(ۅ!}&DCR\_>%40JHLԉP3!S;.]z}:WO"lĚ5=0eXC$YN$C{+`gmC _IDqؑM+!{Ģ"#SR#0++&jL/lڗ4@th[CSĨ %b@`[wLڂ_COFyf$ NF |^ȷ?K#k޻⵷ oLF;xѯ ^L17Ԃz0*1ח$&+UJ И01AS+{!)O@dP2LS @^~D$yPtya~A^}XkӒPk@Mӝ:^*Ռ0cO`O \f}k.k(E!<"X? 2^Q'qۦf$F튦M'Yl`pA {tX3(14.[ߒDwhy%??BD">~Y8>uڼb2ӬV @Je BctJL3*( 0-X\dM|(dPDIE2zPt ndAzzRJTbmR6Įф <5% {g^ el*{;H.5r"$"BC#`|Hގ}RXal CwE Wꄬ@+ząq{-w"iIX; Fc>(n+.F ?</o^;>y _}ۋgaHg+)o*&/°S1 <cE]0W?*M!l(z?urF ֧}!JB΃F3Kd8iQ=gp$+Ay8 g- V+5#~pi]q*DD!^hrcPRFgDbF~<`h465KgGcpXFʱ;=krce~QivϭcK[ t2% ^wON㻯GGa)޽/vS gbֵ(Y}?#;2;wE'ʺh0͊Ů應$ fHg 1| 'œ5*i*T>P!·gHs7Xx+GB8bȺc{"Q80>e}lٌb데ƌ Ke5#C%׌01^Һ4\$DD14cV@ Ar]ѡuB`J6 ^ci0 mZ88<7?~~=:I)|)|)yAo#NߋWދ֢bk dwa}0s;P ֎8PxĭqwH.>}r%; EH,ͩV!ݥ!/\Zk"H""D6m۰7o-oN_JxԦ`WN*6 鍍#p<ۓkۜ;® __~=~~%u)a<0k6lô1l#HG#+ ڻaLGw4GfVb4c-8W-Fp-"6 /K; P*C f0ơĊJn 1o$6c?:=eond@$̭ńOݚK2]Z˸qDIDPKGJžX;$kbӈt<'==maM]Qa0#EvDI ڙ1;0e]}10[lAT.M>2E<ȵ!G9 u-C5ePǥŚ26(,K+[֥qDD!⟇~a*)I񽱣G Mߙxw]A$WGٓ8Q<ܳrA,هj1O) IH+@2~GHgHOڊrGMk*0 ](Dy&1F iU|Cp6OtܼrU O H BJLAUO?c<4Ƙ0\ewi]e/($:>q'±G 4b*5_OaHr,E@ID$"a.-&# a{ITÏϿ~?x<{0v=<}ӫBe1j?"=fC B~`󻪱TUXLu*,c"S=~ )oB_|'v#/4 3Q5XWW4wX&bŇҹ^ZkNrqHhB_$"n+Fa4(]p{ fWtGr5G\ |(-VFc'Ky&,ѓ%le(0^OWE3 C?l2z)_Cc0{y=ij,LcU /м DTpi͐\I.^7dt"$"B?c;G4m77ESk…/a(Vt\X*14d ?kdnZ%B \S"f1YbPS$`ǣ_tW`@X1&`zXޘ z&7 $R# kJWDD!B"WzᑐLjE*Ģш >0㦢W`wǨ>#1<%XYE 0k0 Cfh0URU&QjH(U2Fv9i~LP`V'uH4>֤d3͙?Y+F!Ʌ5F$we}.WD.G$IDu@/dRj $`;xFaeQtC6k?y=cxQȉ "mCҬ3"  yH1I2U<ԱdRc/3}PjNk FaIYRTF{bMʒqqjR{6 n-au˂c%%Xѡb}^.ͷ^E/".Af1)̀'Ӻc"$}b-c!/zg7?6Ea@A4dCf@$@fBtZv?2Y*1DƓ24(!"F(QfK(fZnؘ94kU Ҳb$V(bFn; r:nB‚pFÑd!uTkNTXPCP9cY(f`B@OLL4S"Kݰc`7't~}F{hkN"K@" [*`\\+"H""DUpoɾx*3Ӎ{0SQD(!z$G\'H 뉞 i=x@1ő1lҭ_}cpJ aTXJaRHLtzas'fp[Ǧܶv DHdu=IZ"$"B iVXG3"1ӨGk|1r#};:CdFZ$Htw EDX0,mE'802? >/ЄtL ô -D`sI-`k=HnպUtg$"B VeTb#4FV0Eٲp oFNk2;*E*$i>!`> 8h5JEʗ 蘩azƪl_͞,:$NRȥ-EID Z9`GE(0Ui`j0]Hk@b9zxh&7!€PH ~0Ak cxBҴȘr bT\&Ey+~U:س궄IDLIDwm!]炑^cT Fѡ\e8={Z1͂l=(T(XcO$b͂ޘ 4 ̹@ć:yF%cƼ56Wlt:޸d@ LH.ԉI^'box)0Kn'_P$"D.4j,A6͑(s86ĪQbTP%x ULWÂ1VHQP 3a% $(t01%Wi(Gi?E&S_uj{Y@}@"k%W/6LK_Z.=ID- A 뻫.^|<1`E!cf#0MI>pzdR5R'AOև\rmYPf:y+5}0!" S#t> nCc{f۞%b$^bkhDDIDԵw"R '")6YQjĠ0Y L BRxAT2x|TiPzAiʂʜ5 gbcBx&P}FU6M, ̪C"la*{8Du$""4:r@OUԦQ'Fo˜Fc\Tƒ0q3Q Ԇth}Be " cHDQ( -Vikjfw#wB߉.%r%*um'ef]q=+;Q$"B&Mvh~03CZ b`?B0fj@;<@YEH_BcIҔ ZfYFm= Rb["'2RrauHĶ$xIlR]Dĕ E!BmR6s~?6iPfR(#"10HA~ DVBmGɽǐ`?BF xZ$N݋ FV *@m8\6/Y&-J5] " B MxJ.l(.+"!0Bٹ`Z㏛,(7bI F&cdr D!+/FOX+bgзW=w`lR!b{iºt9H7=nV_JKF/򸑄Ʌq-O: $R$DDqV-ݱeӂFцW7vcu efðHEŠ5 08u[AP}4JC'Œ3xj~49x^#rju7+YroVj}Z#"H""D=Xb[ߜYzms۲<ז)kzoaEV%җ,lsGcr4o,EcmpEbwt$XgࡱUn- n,\z/VmybUZ^"$"BG",+*^p'4[*sm]eq޾\kbɁ `8)5يtm n,5[P^VhȖe5"Zݾ4nBú$B$DDq` HXpA.l7gB᝹1Ԥ'cRL,Ţy#cW}4Oř4ʼn(YY̕erBĖȫ! _Zh(H""DQH""DĖ Ip?Ԏ,{Y y`$2^ fQ P  :2MB8C/qe{fZ]J$DDq֣T oVH"i֥\8t M0Gw%:EdCH$MCAs3Eå{!0p"J^z3ˇ[qh ʼnFKo=,bz<K)X֟Ʒ` 릷a|;Ðsr)H2r,O*a^b t\bc%Y \TC&P,_> Գ 0\bt\>ޫ$?F"*hfݏ{?um8 ', o!hp.8Ь]'xUmúuTuEeoz!yh]VR´E;?{=ӽ}ڰC~?پ{1,~ t1)dԝ fwݽe*H@&+G4` e 2X@ aAYFL8˹DH+uk"Ѓ w =`53°0N)BcKxdOs Sm"Mr[}(}zt L]&m:­W1)/"o=YkNqÈvBB~=Awb%K=Aw6 [:$J\I{KVǽU*$QLz $[ >ta!g"$Ҥe[t 3I{gZ#[}ü(i/g2J1¡o,6ID9+^y\WiKkƥA:l)喣SDv](ȪsgDJ G7~^rNt*EFNYp3)kc4dn&sv,!GAo'*qc(aϬ+ C&9$ D] {y_`ױ@qgV7rݮ,Db",dtd¿I6BYsϡ<Ξw\84m;qVs5zFxAF.m;Ju+DUQj}C.Ѩy˿[-м;)+qPL̏cFB> %jik$L[ Dt{DWLRJ@Q{;y QK^@t{qW$/jBUbeEpoܵ;. :>Gf0h־(LR4w|ȷ<`n^2G/8'$4iݾ9tpY[B G;sB>$܎:sOՑu%Kc%K\PTVfS Y@: l/خi$r6%++濝Dlq2?~4 n\3 ~K܏.EM]P`-^ڊF1V4ղIaqHVm'&\ܭ."+C&:bC*:`ġ\Wj}%HMpa-v[\"ȿNCH/\dfoc ԩV7P$GB"sI2avEzGd|C!N"4_670[&M!+ZdbY:v}Ԣs] +%!~H%DR*t\-q /Tb%W:q_\hmR [!" HĦͽ$@V NF:yݕe6Ҽv}~EzwMCuV_;<޺r'X־!pC !KK]\u3q,QEW;|1룭Bº4c!"[HDHȣhs.PMMV9nE$-&;Qw++XoD|sdmt!T +?kX4nN]87C$v2i^U2i+AY .uۃ$>Zq_5 C$D!e<+Nte.$ Gi,KՄ˓hܭ~,=Ns_wXuЇ~s`43H]A2VI]2[&uTgtg.cy\jv5ID$!CFj?.=nx,K%̂ߺWy{Fu["oRܥwa"\Ք]DN\L.k Z Q$bwoɤe,։T.uqˑM%DE$bw uZRxW2+]ݼG|ЪIݜGx݉H"m#=t?L[]+^ͅB[p+aE$R2/k ;!Kp镈fH" oK6@ ޠBkxF6џͧSDZx`ZOdI[N_n9Q"m[Gj˺Sq3h}*'\Pzw($B2s QY[ 5|מP7B^h%YD>.fB *g4F&*a1]$rYS&&m;C^!~|.l_ۡ|FV>ouܯ-tDY,5}4_2\'\nD6U sl[ sHdQx>_fq_6/FF8 -=*oDZ2|7uYllJ4mۡnіD$,c-Fr<F8[h.-DD9|KIa^`]Z"~FR>EU<3`|Nab$EWgk_oxgkyd$re$nΟ@=e K﷐~B_ pϗ}U<ėwu+B憵`jsV,ID$+$ai3-F͑isY:؝i:6pf4L%D`>f\?I#S{᜘qK ێ`*han&ajG.gb+b{YX O[{ݤu;DDII˒C+і25B"=Ld}tNy#/k$rиu{'lK[nj]̉f:,Oj@^@$~oc>p*ID$DL"B\ºH$eF7c|d{ǠT;֣I7k˂_3F&̥VzdVZ!  [m;¥X:2/)"*%kg~^㖖n~$"H" #D"&ɹ~-0,x5|,26 jITnnzǠWsyg12?{-%MIKn]Єh"x![cH=g~s2z=\ gFf`J)(VR٬8',it'I"%"mg&Dskv(V2H[X̸ͮ^^hF-[|j^y|Yy mMD6_ 7:4Cz=s<$+@Y{ )ZF,E{k?3=q3^qt ɯ3K XA(|+;9*8tp),\,nbJ `#NbkH""nDX#¯DҢ$%:1w-=I vrS.Wrtz=>a%ǴQFLZG=,s:hMCޒchڪZUӚyЂu yHVA\ʚ]?vz.]F̗&>eaݒ=V2MFzoODZJrj~oo@hRXY]Xn[̝Na+nC]Ga96(m>= >nE1Hf>.1f4i \`/,8+fY{A1KY˜YW%mߟϨUh-%rVᬵkP20 ܒ:]*uF .)`\jxkDHZ Vb.'[Ox"1fame6~sㆥgJ̽eт,X#nGc'LY1aM`ł]]AtgfF& dZZ "Rvͽ w-%?z I$˞uݫ<6rK|o]b"Y$&z~$q [֜iAVI}Fnq: '=N: !㛅F,ЮZz5|]oĚ[$tZfn#6{2 RU]/rt[l$º3{4KgAffسOKeK/ _CW"=Cn|9 ߯"i~Rٲֵqt-o7XvH""n%+c _: / \X>ӠIJ\[37-x fF8x,.} ~5i81 oճD*>\A @4 ! ^yn/fdß/rkF&dE; ٰhֶ>)"%DğU94< 4\C8q #9AXIuHʺܒ xHHDF$Ro$-'m`mx6NtDt=)uovSUCRd?S$1{I3ޢyF:"rV!jTxңi =|qBK,jPXJ.oO? ֶ5Y=K² M!Al4V9l}w YRV,6!< /];BBӻRB7លN"zH+"SHZwtt#W# muՋPnBs\jcB?d|Y{9U=a>u.*&${76tdk!"鳬/OD07UXnZf0+yIdI>2{ %L%˶ے&ffpIeB-yd鰊Yfq3G i×Jtvfn,Vc^52Hf+]ORMAZ[U>^ iYg.ެHXzw~]Hm5z717_8Yx,%I಑ 'NH@Xؒկ#,V@*[SwrYi,SVI3a!kt_޳wCV ){WbkotH"wH:7ћB" g=E,0Nߊ0ͼ]eDko l#һ~kk C*gf{/:oy}'׫߮%U/$ e>6(,*mM-husF+o}=M9`&Ϡ:ah2N2 "_sJ1{'59mh~ 71!9,h]y^l21઱C@oЖnB W魱F5w|MɉB MȬzu\vIl}\DY&?%sJR[^:QY3N˜G'kǂsx׫!#k8q3 } mdžw~6R㻺uY*t# e`&kď=)KJӎno!m]yG;}w;{b5`>\NL+qM,(Lڰ/-6`L6+t= \e>K+;()isu+Zrϙ}WfB1V<ējLK o n.1>]|Of19f|y cѱ+a)){lo;#c9+Uw9_Md 'na˼Y*D2ǮrMw$|7f`$A(MHF>n!i4yeϩgMvKEgI[[?-q㝟WVICƳ&"%uLLw$x\r,;榫8eYVcb>ߩ-jEOW gN\nI -kAafݰTW8=minNO[ߙ{[Z. +n+ E`-i,qƮ^X); ^17Ƭ672qk?4fP =m6Ͽڱa?]eB$/k]_w wNA[M5/#[Wؾ+?6tgI[ȰL%Xɳ~WgBߚY7e&cYUU6BVi^biyA)۾4M d` vn3Fi'&oDV05L3jq2VvcV ԏ\-ZdM6FW%y>2#ؕЏZzRSRH""؃22S7l> EVy#Y +[j*hP7<¹/vh%nh + \( +*zz-:?xΰ띓`_clc] iLvE&o0eg) w|`bcNE%4X6&l4i9,N0~ãkv.j'ldFb4q3 7.4h<]^!s?_Ҽ9*`E֖%b6!KaTLY_HDlyWUP uj))RVHs4N$ }NJ[:u_l㪺zO2(;$B$ۊ",<%HE¡g%jBulg &a]0ڼt3ighr˴jҲ\53dr_|ߴgJ7z! Ώ`AkGkL\{>GcO[2]@[1?':Kw~9ʴ?-ȵS22% W2\&>: ]Pfs0MRLpH؟\*k)y . 5Lie|Yz=T>,xNM`O؟tOwĜ?lLu<(,gnUc+ ?2kԟ2k>_|J}=}?/9#'RW$A%|X8Dc[~CciyKlk_2ZC˃ӹ/B0!( NGb`LA/),|LL?d߱}f7 KdTx]-IHRXE|"D) # $!ʆ:>F12ȿd~K!H""?)#Tȕ }Ox{+dE2&O1i4/TNxH5CЌQW{(+j ]ӭ{HHt96dty2j,1[0TR<5^AOJ}ճ z:~%ۓ@˻2aI=_$$aCBزd ?CD eA{!5) "2OOv<8q,4y1}fE2t*sa1 29#SU~&]0I{J*[1 k8P 1Z/XguLL)NԿ^%ŢjGZF &GZ#Snk*TRIcӫTHWҌ*4>Jlz=OkK[6z2yLku#eDJ ZC5??hMg2C8)݋E!iNN-]]];NNFطJIHܖІU4y3w-R4:ҕYV$r\Yֽ$S&RKl9(Mj@9Xb3ᕘxJ6MGeD J׌] w5殎Ӛw;W}oCB=G=[&o ^EP(:͵^wy1 Ky( 4R*(g>>>;RzTD*SMTh?ӉID!C!WxxH]\]IgBLF]+QKZ',(y J{%%DD!BH""D!B$"D!"D!"]B hLh·IcUP8TI{H$MMٖeD!B]EU5PqYʴ-*0ǡI)-,mq:W x(@{w~mI[+[g"BnKa;I5ID&Êhj<\c-RzHTWejf8zf!dsNbJh>Ǧy]n|G'iW^),kNB5a4w ֽb66n{h{Olwh睼pM3-5jd(8ا-CEM;R ޓC1e/-;Y6CS~dNu4$"B?$@ƘOM59>^ObBHYVUk9\}'J\QC=R}T%$:.I'Eyj2՜<:NY̫/9EۗэPsHkE2W|*i|rxPmf>,{BW"E ߵDmWu3㏶F CY ~żPr\>ai7] })7m +}>c7"`ǰlzґJ7LH""DvjNikNl!;Ruڅk. :\̈́1#K͋&S͉%GjI%BHwdYh|sV mH[}"Q~GLbDAt8þ59\;h}[.'>[m^s% ~p:?"ܹ= +t1-PO'`\+BF<+&<~X™NRX0FOi1E6fbn nƬ#P ;[y՟P%}U'Vl(ID4VXV;>p=/Ɋ0לL8_sTNڸ${9QK}gА1et܄3kƲd%RuT^N%yܷ)"D^F"E"в(ړqR[u4̗\}BGh&[̸K~^~# ռZxˬ=CQg:*Iج~g)+PQQ/jP E۽1p}0A;d~ -QDDG#qGqLs9^IBuRH2iG;1Yha`hF]18d l4)$zQzqesYm3:GQOŁl$vb(fHi\o7cq :ycmJ$r_HiOd-,39߲h4 )5'5=[Fd7:"5'vy6 Tv:O~UD~5پt[}l9ԪO)m%"IcȬ^1K@^5-: i^Wex݈Cĉ. qQh %^IJ*̸WjLج{(yXRB#w]B#|\T́v²s̯t${"Y ,kW֜OG$ nY "{rz?L_I)6]D!C"5ǻW$cr5'֜\IȊ6>^M hNkNGϥMNCcn9c/ۮ;>ƯD9*:\M\ekNl!bXB+><]UD4O3Rbn5d4S͉DabD89՜<ʂ~5'*,N Viu[m xM>xP;+A;ji ;NVFT@DNQy6a\[]a͔Zxω$"BĿ ]XFXjNy35jAZjϵ0:bէHgSS-^ˡˋx?Ε@h_,fiòᴜ>j^w*[T/úS ; ԳZ"t6(oa}ӕNwd"h GhFX֝֯(FW5WpU̒Sv5 @>~MmRv9%1+/T2qu cFFF˵ZÌ1G=8l9Xɖ#QبHGD4ID*B'v~v9*]Ku Bw~w./퐀$ij>;rHrfss>.{\.79n,ѡ݆̹H!`ul \߻> 4d4D 6D;_`cc\q Xc! :a mox~_ aLj5AʚG^gkU^߯L>?rҥR,wT=쳣 z9ݡ~cBFطB8s\ qG[!yquӠQj4Qn:lODD=h4esnnA~/:Ly!rΥիA=5~;9$4Z aL'טUX&)#ZէV.H@ =:j^ Tea~牲z=Xq4c{< 0%! M^OpA w6襸Tom888Fט`.h=>>kxj@I͸+bCSavyt S5aW9VZ2BAzr!D+Iw׵!%9izMz8; $+RBytA/Gru}lYL0$$IHدK/vH@ \0, ͰĝzmmH Җ``cÖׅyLے$oddw=Jj#<) b9l;mNa 7^?퐀@]z.INa1Ҙ`bKmO.lH91m/ 0,JRi^XQ!IHW-EH@ /DVtAw/-=0u cKEcٞ4 Q}2CI9$ Bo]Ȗ6^ljGu$fh䛄$du!0 H qwL!79 Dͦ&Fކ"yޏH~q% d/s=lJ0L"Eۢ)ڨ^/[sл Ӹ+ #^ބaKr`S/H_cXQEuqS466c1dƤApqo䤩d|w̏oMD8Nv7qu݁u|k\AK/Κ82]bI@~q X,m؋Z!~`Fu1t4g>~Eyn"{ײ6GQ5~e]t)HCY~!!WbTuNzG@yezC|I~ׄ_'BI [:Yo!~[Hf!g0z255[B?$"'sdQ:G?@s@T7"$"L@!djjSD0 #l}:G?dW{'6BK@.:>@ZeA?;! BlT LMM%D4TbASؾS(OgnҶ~`JNǡFdwrq78]וzmƟL]Ê+h]UUWUOTM\/zYVsWt2\tɹv:ǘ;9cnV:}4>|7+$O2:+`y}i[ +a!Hooؖ]힏B@<>|DJ墘2dðgԠ= ν{]S;HI@nְqrOЃ>H2Q-Tv5%s:XGH4^Tt7">z-._yG~;rGQ*@DL|{@4~ՇI茱jFAv:$0I%g÷`!a,BH9T(LUȰ `s lD!X}Q.?66< yJ \L@q}7A C0hכV}Sj5cǂdS]ʛec 9~]?G6R s2"؁7FZ5:l%C`Fy1)q$sj422b>~P!z= Hnʲ~M?vVe ؜:M`nωj=j2|ЁvW*T!D=px(e;0d"~og }= 6uݞ ^cAr !WNR*aM9_Sď/J?,BԱ `FÇ/۸SP8:LaNR:oW*;֛rTC֯)GQHKA7`pF˗/(кPP7LfH@H/;ʵZ}7ܔU!~ğYG!t둈``lFx\.Gy9GƩ& 'NQ= i)B? _Sďjmw$""Dzyqhtt|ߧZB1r]ȱ T"BPBQ@v/ۍ֖=@ a<B,\%!8qnccc}H6o2T*2眮]t t :t}!粞@+٥w;Lʒ`)+P(hMgXߝwީ|^S= aސ}ߧ9|Iɓ'鮻R.~2آNJblXV 校777{L""iNid]yyǡ\.x|rYծa﷤ޔ>Ycc#]\.whR(9 ˺oJBFS(dذI?G[nElT*gڰr F1>ik< YIt$"  >utP܌P(]emmM=ܣ|fʍ:2엕Fy⾦íVޏ' sexN@ = gΜQ>oX= oHi6}mPD HKMuַIDZ]O+D*Jtv[{ʏŠ7)B7M)˜N3j5uɤ:H=^u hdSob:`J[BQ-/U!X٥ k2SB } t/3_̮" s:N`Vv.{]w)oUq]V1+;TB@gϞU*۞%o#ď`kaqߝt%5 XrBksΗz8T׍ bQ.;R*1ٳgkz=U;@"BGX~]K@:_3$رc7,BK@ Z䤑 IU:ïjMr?GQ5|]9&v%42%R F7ϣRaN3V)C7; GAَ?,*ummMj߹>OOqaʗe^8΁7}?c= B}= m) G#~e=aTzeCh ȋ/:55Q.;0~{{X}omllH}oW=<7zď2_YYAAsH {\2";H@ TU:z7#( ~o#o6t BgF+a' E L\QT*tko"V.:#)CQ@F?wzCJ_fV*BM@ 2DR 9eccHzU*=eUW "r#( ~o8V= +Q NBZ^o z@kyYm箻cǎ)m4C) YG#~艀_)5 $ˑnVr]W97oj줺{^jU}iMV)o֨ˆ&4g]6_(h|||߿%) :|{H"=kQ|摑#i@zx7pay岲?l)/--i͗h[3Uy'A`u+kU| H,..*/衇R>w\+`e IDAT Wm9qErj?6Y5a)j3IEY3;;\4::ThPT> (FA[[[{ /M/+@!-.әQ.Gw1ySF4bh.--o$DE& BaAw«ɽ@oPP.3D o0W*2E@DDNrJ6uW.;99IgϞU*9( T&8ƫj>rUr8u?!j5ݼq岦{?l)$Ug‚DD+Hz@i78GFF'Ӿ~% ۉ[C a>_xvnq~`bE2*tBZ $ }sI@衇Rǰh3ummSHD]|>z'^0$Yo>Z;.\@|^lZfM=!e&MDlRzZhg؋)H@_CYw{LlT  HD VD俛Q6w}׮]k霫{7tι7ѮU ݼySŋwMBdQCeVꗈ!0]S ȥK*V=ujh4J%9nBFCkirrRl^Oܡ[nFGG#9W{umY|v{Q#M.{؈hg7tDA󴾾n"!\AYz'P.n^&&&hbb"jeuGYY[GW^cBhE(BP^uL't_zCN_Wďd㟛Zzw_Ik\u7Fe9'ubj?'s%`/#yEVVD4FDuurrV{LyƆXfaDS e"D6#^ԍJBZYjmmo̶|B\7(UT*C = _6Wr"Z9CׯkwddDyUeM Րs/Y{3eDM& 5X\ 7y]xƔʮ[PH'!D"{4du=wkc}N@ִ'Kxgˮju$ s4l6iqqQ|87\.\ծ7ﺽtz")q%:jtWtElFHDTV}# H>'Xn[}I[zeeEܲ=&ZZwL4U>5}DދB@Fȱ̆͛7w~ul!f-!ݼy)ïBi9_mU*:| # ~u zʖJ%7d+[+U*0fL@Q ˺;7{S<[MZ[[:O?|]^^y-l)7tHhA630._ъ>& fsAwɉYM@lk^~]ï8}ݔ3{ }%9z?Bn2h8YY\׶s.u;{W]M L=slR(/*+8_NDDW^-֊&{@ yxg.--Y+l}ffF9,r: *XD;{}]MD*[ƺïcO+< n5m}+I>. =CsRޏ?G6B鈿W +*CE|rkjjjN-AT.ȇ+C|;\5JGZn?G6B/8C"Wj JbwTҩT*{C'5Ibl6o׏ ښ ,mG6Bًaa!ВC%"3s bI@cE"z(sܾ&`%-Ib6??`AY>(>|XbϦrز?ⷑM_~]=zEJ'} oVZ%99]$MBOrAdfFAĦrZd"~ďm4,~σ1tP\ 8BzѠVe$yp9Җm||>99I/^T*[,I)bka5E? 433CFCBoH+-. 1ZXE>Knl䃈hnnNW-[\\.ԛ)Y? YGg#+ͥw7'&&^2W+*9 *o\.GyTȑ#Fcsj)%~Q:UܔAv_Sď㋿l̾SLDW%K8411AiFGG6ICΜ9C{Rم)GY)Gx]? 4ZID~@E\ ȂZh6ʫ!u+5m*!W T տ*a66H Ǒ#GV(Y*һŭ׬(+YM@nFmK% "ZYY-cr9G>TvaaA%rZfɹn}DhT^"B?_VիW׉Hk榡صo"7|SO<MLLHBHߤ'4?Wtbqׅ1W1k,""ݥZf$ 6,T4T|cS*wn$GC݄7lll CҖ|[ßN8A~HMҥYe3%gjZ%ݥwIM řHޭT*f74&Df_=sJ7FAוֿ-D/_ݸqc5HD0*f% {j5}ȇ$c,bZ ;3~闔ͅ7eSlMt LD~vʕI-BusΩ^ӡC'&DD׮]>C=DǏ.'ô4&'L|xb~1I!4q& Jb,) & inV*# ?Tnee%ƒޔLGCzEcbR,9 ˺)ʉiN>v&811A/^T*+an6W]_xjy\6Q".I{97Dއ>!ߞZmhmwd`j4Zؽ~ebr7(VB#tnn.A>+ ey4 yܣoXz򻖈-(r-Z܌PvH"3K9srs#;3+Hyc쯯\7_~#uclmm[%"4,K---iSRbJfahoQn׮]YG!jh~'!X@2Vz@l`TUP|Jegff^o ,y[o5]_>9&D@DUonnK H>3knM9kWZ@Ow"8WU ; jQRfid CFwov:stfI^kMYwQؘ%]֯)vY8Oa h4hzzZ / !.Dk"hnn@M$ YK>L ?TnnnXh͉Nd=0d"~o,>Snܸlשc ;Qi6jZѣGT&S^:F֒7tr9z' !aMSJ~M?k? *?,wl6ZD,&Vkܪ~i.j=ЏM78 l_SďzPHD_z2Ea; 2r# &?N>yҵOMď"#(һ9+KŚxWu|￷G5jAdfӧ{.WheeEI)l? YyݸqC=ϾXXWNMMdљ|è r򱹹i$?tSy! 7A?QC+O7n7%z?,@T*lhgq]SO=%]s% ď?,Y?&حl6W3Sj\ H֓hiiI8O=MLLH[XX0,<?ϒ_W&k./v 4$ u!2q΍|zAD4??xSB7 GXз?7xcknʕHؐH8ξn4j%8΁a^|\Oc}.y|Mnʲ?G6za?tNzEʌ%D!5hP(% ZX}A^|>99)]nvvC? (vM{4D1+WB{9/v(4MT*k(7r=T& !a>:ď+yٚAXbO@Tz@r|ujiק~&_;vΝ;']nyyh-+7~?G6BſJkkk=_/կ~UAbO@8 *gQբ[o|UB!]H>[XXE3́̂yMBX3$d=_7/u.;ܐ0cLijJbQbj`q$5tÀo44==hU"{HnF5J|=*]$$įkהFHDW^yE>DƖv'$!hP@#ׯ.'ws[o`鋟s>tavH 2V$ ·ZxUu1$\j}~n:tf6W|g=}(^؟^rEyA# |A9ZFuaQ.f}w㙛3ʧ>)$] U5Y) !W_=oDһdEB w3DFg駟.AF2- [YnXJ&"?׾'kX0ƴLDԉ4! IDATk3Bys)˴}'x;&].S = ;llF _|A !>`+"^^\.+ؔ5KCؙo?YM#=y:uJ_Ati"m6T3ƾ` +z@vai[[[>6MB˰ku5#tJB ̱8%-7n@f]!woY!bcMB Ȱ7olݛF_LLL?,]Z1$ `A,m񺚈snl]ҮΦDk/VEH,꺮 LQF8;;K?OReOg6ސH$ GŽ~mƃ)`SHVBT?̎y60mi^vq}Y2VJ ?Q^W!Dޏ^z$"kf7%T*:tq@$a봹iCjKr16io]\ϻc}dp'疭$:BXCZR"u+qf F_|C#GHT*F% ,;fj* q} g!j5:t萑 =IE&_9C˵~:? x7n-sL_?QXϪD!z7rZ5$8{,s=Re|ߧ~[mQ=,^f{C'lG!Mj5z 1#ݬJ@d{@r5}rL'O4R[&}L|}LzGٽ=\YYGQ ;7x#А^|ƪ J$Z5OMR z3ҒTSOVKD<|:ݸqCLDsޏ*qGz|>o?0ɛ򲑝Ӊ'lllښAEQ)^s?JD~o ldU9!"FASR1:} q#t7n$bG0'%WYlvBvBh/`UR,o6NDYZ-ڵ`h( j}3gн+Uj wWfЍ 믿7Eg]ص| !iJ@^y*)"ry\111Accc$0OFG?Q*3==MVK2JW ,Y&/<ޜ^>W9y뛦\~_.9dYBD$(2Ɣ*J38"s8FV .HbIO9]_]@#{~HLxؿG$1Mү);.aImkkK{ b첲IQ,ViGSNIY__Msjwӿđw R b &>7 / g,{0jsnlo X*1瞓NlXz"[i$ rc?qXu B|ߖ筝^qN:EΝ*l6innc~ K֢mduGo>en܌0= yTiA\֏0>A^l]"ތ[R1Q"o7AEgPٛ7ojסP(>r6L>tz8.|O`]BD H\PbNrmookܹst]wIY]]5K-NzvQm]8alF+"ȹLL>'"z礯 z29R_ܠXr9Iaޏ ŰsodǏC=$U<>6?>{N.r&uarO$t[ l裏ɓrDNOOr-N 2K~m]Kk֖z[+^^fff.=3ҍ{IVV;K@Ri;\a*---i̙3tC6gmm-DD~r !DQg)lR>n3qǞH2ADOJV, ~1oV30ض, ]pAL22݄e|~2Bh^LTv( Ɔ!Pϟ;SDg;o?a86_??++!+ ejGS؉ 5M'"Z.' A'O1Pmni-qF}2}8zSi}}}B,zIE.=S$˄(\&F\5OlQY3 "lM@u%#N#hgX z # hmmMXwus=Re]{5a46>B'#g]+Q'ݖ ?U^VE6 wte#u}GRev)s,!bD4^@SɩRoQi[!'FAd2)mZFwndr}QxTrPVieeE|a$!uAM_3Yz| rJe,>VR9'9UUG\N[=&T*# }GwuT{:Uc= N;K= <& 1ڍ_!\훀D\a'Ç~eMڨ_!XRe&yMU_oDԊ0z{H0s# -B=T@j` 9Q$ B&rEm#m 5ds2R"fB6s;*c[ё!%d( 20 Ad$tij5c=y jǩ:8|iZjVjG9 cc#D  v'o+B Adq8΂Jq:*fsHp0ïn6ϓG%9Q ַ=97!V*XDD|<3:a$0Iگ4sn*c2Hg|t'FV{GUDD_ VKm~.#\1M31<&_r9z'(.SVnں%xwW,H&& ï,}m/ 'c ]$y;9UytwKl2RhobuvS՞2>>n$m" R1tӧmu# ;+aa ^Hm #= Zz@lZwI4>>NZͦr#u]r]rut'9&_ zu7ε2 W&.r.\rϧjQzj.&$V lO@vCok6l6)ӡC+nlSݍVedwMwyT[_Jvt`@@&$ Gn6r, Ady"XVIMqtX,Ix:uTO Z9bh-AUGN@wt$KA R0]&D#d=%=}mV\PwQD' 1mVÇJ@洏;66F}{_-,,wЉvY/{hsv1L\\h$2*DM{V2 '= D{ٽ@M ;6y=3K~TYe Xc{{L^/QIAD]U6iFj!aDzMݞlTk#Mz} cTR1*^ uW~aaȤK jZzU`B+ P# pԴU^hO@ïJ+W]mԭN@Hc޶JĤz@^vw絏?99I?09NU7ij`h#\ 0$IԥMPel%ʛjwz@𫲉'Y8Sԝ\m6?O@磗FadAW&0鮀U(uɑ(U>#Kn˚ ')HNM{HPîv6x^ `"t"url6zCT ^ejxN<r;,FwV<31Zo"BD]U6IQ.A |U- ĈiOOz8T)xR= V/{ʕ:mT*N"3LiaQ,jPGһ.W6k{N@uIi԰d^%lAU>Ϊ !h;ïʕq?ҿjZkI@Z,Q0fid;NۤʘX7L'OM5 d$&'ݒU. 5U;øﭷ*n-v_H( S-\Vsnd³8n$s@X,Y[o5777Z5^+۵ocp$&IJ8STyZ~>u{oǶ?$%#sNzƌT= D㿟 E-RĉtY+_$85MՏwv*EId#< l D]ڃTTkrjۚ*czq4* L@w!b]j5#KDDn|̙3tBX?h&ʹFzJEs7‘a;TY݈H*حdT3;G^{#?-d}|7A\׍u7t"%#=0gϞZjmmyæ z#q_otOntu'H8Ib' N6p$J*q!PwY ?^5W_gf~$B#ï=JΝX"ޤ#9X p$&¶'STUuG "z'\FG(?>/8$$H9%sSX 4z>QWѽ z;Ѿu`2uiT$%D% rek^kG'L>?$t޻7MjZC<ϣFAVkUjѽ+5jyyׄ2V۠kn͎d޻>VUWlP̭nt0 _o5-#1S3F3-Yph"9 R8FEJջw/+7 /=[Uȅ b}PH@xo<)+l1nZ d8(]#fX)t@ݏ$kYX{yzeh4t:E1 t:c@or0C/"V_Ε vy@,k/IBE٨VSH,'Eg$Q% n#='??[$H?CDQ FxՌر|[cpyyIwo$ .-Tv3cmӺWB"(:XΉV;#fT,6fpmQ8yгh?|쁵q`vnnnp}}M29y[lb`wyg[UNGwɽ;&4 #:XM-#u7R;#n]6(YeOZP)$8֑6({,rb>-˗/ɯv˿zkcֶ|f _D(a]pTie$t 3CUF0}6{D)uV6)MSa8ݤtMlw}Om]i_;K#tlTe$XM"3Z;(]Yjjt>H-g/, 1t/^sUqcvb) xm2ӵeJ7/| _xZ6ӵv;m8J|&a҄ f:XM-#jja$TVbWI`ގA!V7^`:DqtzSQȦ}WWW$nksvvVIFU 6Bz]?bsJ & M+# K`$ Fוjuח IDATs괼6g_$%&0 JVq)FRyVsl|ӧO֭,{IjpրP XQXNf`50H*+LMGcl]2n) `b@z}ij%)HYb@ykk?JmBvfZ Xb@j`7s2|.xFR3 %ur-m%5:({oqb<^1k?d8Z162 _@L"E7ձ/`f^l% i]#2ު)6کq')&g?vZAX8 elr\ݨ=?\]]:cvkRv"lՒC`Tie$K`$ F!5JҍΓx X`B_Q-[-u{~5wJ'N44obFu02կ5g$}qZ?hk0No?ncΩ v^t0mA 9aZ޵iDzݏmeHu{|y90qE F5VFn0QFI`$xRXFA{G?FD)E^ ?F݆1i^ChzѻϷ|t[otm *kt^{((t;m.y)AFuZFb9 .H<#M*2NZ¯5F〬)IAnE&mM¨tV&7y%y~Oo~ k!m*`=Pl}b62lvFIe%T&+kfnP~ae`d@U"aӀ'ݏCB)u.U5idĴ9t vBEpTie$t 3H0#(5&RwQ~zV~U";#b9,K Z;O>œ'Ozxv4Mq}}M~M 30]bHjhcLBQX1FR3 f$Yu/F)-u;W7l '. }3U3 N̎R J)c0倸.MwcoqɷU!F n= ; aҼz}=?#b632H*-RW1Oߥï6`K\964?fYlzkiZ/48 gǀ0aUZX黱5gu J*-bƶF4P)3y`2 M<q# ChA%|g 6ovoxA [PPYaT*[~_+`$K`&\FbI7E04w>SI(kpDO3RuRhۈij'lF)Uz$crE.믯m@j.9QZkLVbewck0f%TpS[}|qEcP)Gfs!.|GE+KnmMoaIF u (Gg%XFRMmc<庐1וPBs; Jsnn[So&<[aE ܸP ,mWXij:Ye%i* U[X @W01|L#"[Z# ;!Ysa!oҿߖ U!Ő^rɋ(HJ`$K`&\FbI7El0VaL\v u)UhL^dN,?䌺hLqt7ToR oUVZ[FnJu`%V `$Tfb奶Ⱦ"d;dYQMHmKj/2ITHt:?vm@lA<-v@@ RcDJH-L<+2 nj0Y_om H1Rqm.`ՀNBϹ/))rss1H$V s| -߁Cܑ ⴔfbpl FIe&\^j0ZSTqj|AEL BQj+S*UȲxu 6 믿NV2n[R^%FGFE?׭j%g%XFRMmܦ G~:``ÀhOdMXV7q `cV> g>{}}]xݰ޶}1֒UVb2`$)J,+শȾTT?dwïƣFo}@d]Xxes@E6ew󼍺c0K%?}muN}:'N5[#u7E(]32H*-q\W SgQ'ʃɫdO gr ib^u]8zqq=ɝ؟>} qpxxs4%mٹЯ !ys%x7ٷ-H<#rKm1vM7;?&v?$l 194ME\~(8dű_2 =K)^GN>^1Vre$i-6NVoUa'a8}1d06 SՓn8WJmddl|3A]uR~Ή; ݎNۃ"]F'cK0H*3R[D G50HRxՀpj-4-(g Le8E[^ntb632H*-"f:֚ZI$ma%N];[xr7tf|VǏe@%o3bo@10~ñ%g$Xnry-"JMznJwhhq0`l@RuaHUlJ1+`tFߩb632H*-"f:SkAk膘$|elp6y ak@ dBrS%hxXRxa-R*ޒw-H<#rKm1`{Z6c ݰbs[ZMOdS֭:u6w@SZ;7+M{mt$N A-L<+2 nj4lZ+T wï] ȋ/NOOIcQ4"lkV11; Vp>'ʍ)R ǖ`$Tfb奶j&{|9umQPl@H7 ƘDLo~3pIe}xw߫8^{ ~N&i ԝG9mc+Z_K`&TVby1f!aWgD4|Q8H(9lw@@)unyTt4M8Z#ca4M74n&}\]ˀܷ۲q[\OR|^^ Qg1K`$Tfb奶j˴qsއaC?/=>"$IVthTzѬzQ7Ny1WvZVzD5FƖ`&\FbI7ElTGaevQ <|1A`:" CqnwR{-_{59.Jz!}ϙvbñ%g$Xnry-">Mt3`?n,{n@ŋkw_pg St:niV5/nhSϥ=RƖ`&\FbI7E`38Mm 8ݛ!$/ 4Mq}}mŀ(V Y:Z'T'48 ]t rb8#2M./EemMkAp5X{N_~L&?DZ] `=5.*a3q&+`u X/xb632H*-RG#`5+FF0]Bt+WPU5 ^'j@?~ z>lG+d ک%cK0H*3R[D G55dMO6_ݏ8՘z>HyXuVUZEsQ SB+>O]Dk13L,7W{`7 ӞYuFGwo4ÀɫVA$IV^l2} (N^$J vz`wa&\FbI7ElTݒXm d?͗QbZկ k qMHS H)7:wl32H*-"f:XMmjc~ IDAT&ziyz8j ͐ E RtVt:EGAT%VRN_!w@g #2M./EpTie"8Q,!W(B`8a?X1<.}]ijq/51IR '''kkmU:#t-+5 2f&\FbI7ElTe$#^J`( |ٳ/co@R1l5sgt:%{pp4 IX 7ӈZu@'~ч0H*3R[D G5VFE )&qɝ R`@8~NᱮAx((/;5 4 NNSR m4eL8+2:b@FZFb Q). ?JP JpH1;"yh<{,01`yJ5yWb]mAq ٽ#LRN7Pcwma%T}v`wF0k}H,#3)lT e$֖٘'J rU ; 8I1Z?ra@lN8:*uKc 1|_ZG:p3IĴ/~n]-`1Xnry-"M+#U N˅*gu͘+|K# RTfRߩ!XJ)n|^?qz܃@Ƈȶ+2fJ.#Q%ت<6X6?XفbTv?^ BsՀ}K-ΈO[Tfb奶j`7opܾ&)ɪ1wX0V@C R9$teaDv@)ԉq0|fJd_֤5fY'ZH,#`x&"]ūH]wIQ*"_Rb@կjedbeUJY1 `5-=IMwG;= ̛ 1`uF0F G1Ye(*WU:f˾ udɃT@#Bg!wzx!p]׊Y aJ~|IOztb! cv7 FI7E`Tie$XF~ Q9A\_Wȕb1RI Ix7 aZ1^o<$mWȲK#a#Q%X-J4R U_] e bzӹd4M8j  qxxW>mU2%mqEB f$Q 즕`fxz]'fL-?u `0 O9q%x]E]i@1^#u"NCR=:h{PԘXe$u?ŀ*a55\o@,[r3 $DAt'ߦR1 >0q}}M6IBDP?}23 nj4VVF &Dh.kUAc nU(e·%xWp.6_;= Q6՗?#Q%XWlҺFQpwx}m%@ ֚Ԍ``EKB5 NJdU:ǝxt%ߎ5?.#" "XnZ j6[$zQ\g;._ch 2 8%sl+yܦȊ铎Wt=g$b6ԲpTy3z۫{6 'H}L;A*1uJ*Vv?Vzhd2:|rE3 6@`tb$IuZFb `{[<AM_FOΙo'J}~] Olc| u<$on&值&ڷk7]k|f$u/9U:XìĂeWq`7כ6AU?%R1/=8"irX邞7GI\NPk+`I*-":XM-# <ޤn}iy|ױ$)#+-nc*i9CIb9 &~v*`=>aH ~1UjjYk8tIy~N㛛koQ*QSVn8~C vPJ4 eo+d=#btDK+~ý0 njl XfAZj^ѳ>^T~_Y("2 㜕]X{81Nos/⺮n{muK*Ic(Ua #3)i Xכ[AN8k@-2 ZO.~6šR.n5̀lu?Iq5ٓlFFRMm/8jjj6^[nņN˅RULƘ\XȃlF:de}7 txVaY5 Hۮ -QH X|X=SP`55pI?fѳcw·k |/vmcm@C~2FV7exY5!C)>A->z0sz3sY|v`uF0k>]k64K2ߵ @FV¯Lipیi0D.ׅH?>&''}.;9SEϮ`57$tZ.%Rl@`8!AHv@@)EJDJu$7 MB' Jw`c+dwCX3XKRq|'Wc8 Gƨ:d)a|VJn4mL`../~~EV1M4  -maP-/It7N_}+I;Dk)cwo$Q^5 H=6;BӓDJ{#P5VVbp<8&k2J~PvŊlk3Y1$i&yHK@rA_EM3#1 ^[耾+ Z{Knjj!,cݩì4NB\9UU )}-!+;\l:b0چsU\ӓ.(>%FuD琲82+p9![5l| lG;*ѕCҵ1_꥕xl +89g,)gRiy8uHc |oSVSH,e\oQvjeC Qhsӗ8ɦsW?g*|iqc-6M1 ?,;>'VnPUVVbpPTCr;J]ɚ0~e@1=hRB7cA`={,$I`j<ิ7 \ ㏸ZFbl˰2& mkїA4_~w^)U3 9 Ϟ=\=>MSDQ51?$I{1Gz,>YX12va8v:dC%~ue9~7Hk:X$spB)uoIm݀$G/iJvEZx%S@jZY!,i-UI. -z+ ǴA(Q*nSAѣGFYRB|2 WzrrEqQ aiVSH a4!Mc!#;Hp#./GJkM!MOjr.9-u;rղX1";!U! YB~u=>f] udϕ=8h8RhZk}dVFbzkH%>y#X#BYVw7 `;RSu?R~W}~wb@VIA#ZB++W/~'%qaBkMsIz=b<슫'x9,N9 q5v8F9y65ŪHz!XA]h(++lc9_.{pn@6VVn@Pw?G؀w@gd${jK3yA66?ïA$$kȃԘ9 1anRB̀䥶l IDATx?/G%'k@&-Rm7`eXc!ݪA:peեݏ0J 1J7Íe@dL:p@V EFb9/)AziӠ1统[0 Md ǛVg:nn#k<i4 IjK6dSz1( ݚ`CdAKwAqzgTk~xɃԜH #2 cq$I5'B; C~TV)LqVGFbhFs}7 2-MsYTdFo&;tn: B%t,4~n`peA`@Ωtzj3x m3BN:ҋE atb8* /agAQ]|/;MVk  jJX Cq./qyJ6mH9(֐1к$5ci;N%Ij|  = կ~g?1PiȌso=8شz-w^;cnʶ 1\vIuKa(RH AAD/j@PD b@`@mMIlA$<^}N3 ݖ~ft kF`f(ldըl 6P hYo>1Fczs`/?=1 nƘ2d0 c+}E6G]?Th-pfA nڏ޷1€P%MpȮ/#LBZAN|ؙ !0e{! ¶ΕۿY{a@`"!7:V7g[J0Z s1y8Un:o @! ކϵָQ~WM'D)I܀DVVt|>h!lb8AzPSK^lT]Աc:'€h('y2vK̀Ciy @:efk8$CA)m%jPRwrnc .1Ɛ H.V7XgW m"|Z# C|zc8>znŬqhXnKq|M pߗ~-Q<p[Ib8AÂ92LFV~ pb/ H~@mZB}ooI1u$jN:> +Eq߫^ O~YqB?!8ADٳ, ~%1z=21Nkm> ivAsz̎#3?\0aŻ WB wcȌ~yRӹ ÜVqxxX%ّ0!y\ݶ~|ؚ(DQ gNc;aURJAh08~4W~7&]_ AYNHY+l_Dϐ=Rp7cm\A|Fw귿E!d@H͌1˲"\Қ7_q79yT. lR~\ a/mt?Ƿb}c+d Hn9anl(|GdBQwri{8$@b8X% ԋ]FR χ_%t̿“1 n9eo@cUɏR@W*;B,_vi*U >wSQ X4 WWC+Ax<SƀP(P~)N}]trdn4H\AȂ,t?p?~i|KQJ?dH TV8 ;yy' pC.qJ\^YWжRrttDzݺcD8:(߀PaqάsaY zd?h׋1@fTӫm:PH1{c@d:,$Ru|t{"BCl^5)׽A,>uZ?t1?77$I 501fN@w@bjUkA(UgtMC`uU>4g>F]Ga xVJznB׻H[4\?*=yc1 J1l!Òj{yX @'da1 @va~#\\\>NBӦ%++@Y[INGŲ E]%կ./Q?xgc QJQQ!ceݚj@Ni?\v _z QcԘ,9>Ջ_dMD`|[uկR]\ _j=V^(no,1$7ZJ)8snw5ھ#b1`RY }j!֊|%o/~u#'! &(P%΀PAPaJ~cy?q7ofc ~~[8:葾tkx -ɅD dcsl_%V¯FםG߲1wʀ(>Qo__^|yk!]tĔ2Ԭa{uBW5yNG]*p BΪ dmX J#^~'?_52 v jɓ'Zc4jHcc˘\t{mWdVӨAYu5 k|B]hy5AFR-|d:|B>A؜wT|6z 252 i`U$fZkL&FN9(';"iab1Z#ZG?l'oBV!v)}MUp1+){R~[$42 ~y^ Qa8} ]"?^Q=˛G-Ӥ` yG>  اNzNXcE0hL[;fgi { L(c; @>0Vh: )yF5p֙ is\]</_%QlcW; 3aVib0ܛqBv@rH 1C :} G>AKkJ.FERWVHw8>o{n:̜ GPߵ|; Qc8.~؎_ͳ =MSaXR !IPw\#ׁﺥo4ML"zAK/ U~,'E֎y d:yHms]Ǥ[CZ]mrh:*C*t5ɥ(KُKw:ym1l(? Dр|J`<+.U\= ~0bH4 x! ]؏w-Y H*8ph!ߒi h@; a -N{CcpE rSz W?t!zA~\.KQéQ d'7xJssqt|@Lʢ^~RTL+ ԙ˗v¯!yw|4vAY:fVgկ\Gia)25֑)/{zlJ.FT\~̏>|)}fbc{w!eI\ׅ1_'1|1-*Ḟp/Cexjʲij f?fRZcTM,TWA8y8>>p8m,\#B>i¯4ƛIXGTh8Ju^I^S˒/^Zi>'Q>JsWr+{pZq8cA0 ïl E֘.)60#Wt~1H)`wC c.yY]ʻL*i4Xy925֑)X/XYQF9'$Ҁ(>'|}8Sȶ9ONIǻBFV~W78cuFUb%VgG+i>0{i@`ab@o] Ȫݗ8LIZ9 _ͧdZ.)Vb}Bp盒j%tb2~.{i@1dB:Q 7X]S J^ /oW7? G{i@֤F@ !V- Z6 }ϡĸJHcG'0l&i P=[Vb}END)q@%'F 6 a5ٻNo|Z걊UZCxG1R(Hols>eק{ȆG 1p@^8A@ }sCl}Hm|LG)t:-6v`3l Vi :j!q|S ;g ^^ iP>x5 JscOI 7u]M¦fbScF¯<ìlVY9U'8s?܌${w#yw_U=sw)r)EIQ$ȋЛq!/~ A%eJ0GGJAYtDDJ.\.zb5=Uu>CDd}s'+.%3WR3 az> &v h4R:c_F1nx}1U5)dY)4->~KGEuRʼH 25i܆R_epyc'Dw6SiBKu ׊jS2ʻab;a1{ݯo:lt2O>OLmHA ~>aL0"gJR_*V+bKU)'3!7]:?!3W#eRwcO2 -]_mky>i2U e:N:%-˒pxu=n:豂4,CWlA5x5;Yx'j$1doz4kf2g?B#몙^w׿` b۶ ^O}j;s1q+ӐK 2j?lG_M] ^<Ś,Zx4Svt=Kۈ˙;'xG>."}O%6ZV&oY֡b=&v JsC4`WaW b .I-ލZaͣBBPfҷ ˔S3h0c^;Q'CZ@R*7Ś"㴾{YFXzbG3=h Ȥ,J'(Òl0D[b~'LjnI,2^o ݰ]Olof[_}3WTcu /H{SRZx( >n|XGiz{A+ok5TSku jL<ߧgA!'WIcΚsZ >'2ʨЇBZ9%51I}% W7J8. mjXEܖqD);U: Q\Ապ:qILӐ3L=">n }X+tȉa!yb}^KE57H|1*m;0`iI_$l mFR"rdڸaĶmiZbvQݰac$-S%Lb2lcHD1gЇB TBSaWe1{Y Rj0C$@` FCǞwt4H;#Vnx5 SaY}ȇ" T ߫^TCyƒv}u>=/Z ,CDD)uw*8Z ^/qzTl/JƘ!gήdP9MR^堂/9~kG- 4+$U0<IDAT񴯄,`;q_Y1-˒cǎ2 nPY5?͎eXG1MCΞ=,+OMNqB Tߩ/, MCTp5sEzEɠ$q8νf)"aijճ(;;;ZK&u s1jɉc8T%VW9EAu^a揰v{3{"+_H0AUH1~qvٌ4F>Ҭ2>d$[Vq!ksoOxQaȹ'0|t?@= /lVLIӢBtze|yS_z !$*$!#N $kw #-qK:4.m,&:>er/|_JtBm /lVL; HJ`qCΞ>R5yQsez=b$+d߰*M- qV@tCe7nܰ?lȉQa!"2 BGuTwq1e}Y9yBe_s4^TEΙS뢾" \O^yJ4 >_{_'hT[_ HjaM*k4;@~dkmZf2Ŕ O:v|HU8K2yQsoڑ\z{]d 1U!>DTJ$Suo* [ i?Z?ig !1e CPYznQJ)m["[[Aa ?df`I!.Mw0}rP:g LJheG)=ϞٿKAHܠyު0x!7dԝ_g|q(`ĝWD[@5[GIce,, E=P |J7T qcU_qS2)Ti ,ژdj>y͝ێ!".P\wdxC*Fǻ`%s> EDNY ~pA߫͘ 6u5~7D8eRxjzyaaJ$~,۶e8y]R >~''yq).I%߫͘ SJtM}3źZ:*ye C~YÉRjUD΍wZB LL1m8CDdtU)O܅Sb5,~1\ oLmĴmopLq7O7FȈD)i5p(A"nd08!+#੥lɹ3ՇzdxC*kZ;kz:Se (Pt0{XuaA n+aD4 V(ÒݳG9wty1L=H  %›1xٰ)=^筿ǿ*z3.$Htea y2l/dt?m !8s+q9~|I[}eHfFy1n|vcyj>x@~PmۇVC4BdoQ+R1bY8~{wb/^ <4,Ӕ82 f".>J7d&6@v{CǝDj_ׂA QixY<opkyݿFe;#/ J'/J@'u-u/m\f{8j>=v;{aerʅZ5R-(gh[QZbZ5nQӤ#$Pp}vA~J)O8NhI3bu?|>Pu9i6͢UFBkǾFY} 켯_gV- ypk}}/k/"8+Θ.XOtpW y7b#'U}ruEj~o-Gz' Ǐ/ɥKW;n'z+vx ٜG+"^07 qݎ^c0b۶8sPx0h40 -G+-%l5.@+O)u뮶o?X%X?b(:"%ڶ=*Z#΃UJ‚ا/G5=r[&=y8y_ ` SoH'F$kI9+;Kg>|FÒ^fY&@^/{kۼ߷+  .EsxQ&>+n# cݳrO,KzϷr@O)]mk)w+W"v)pͼsκR*0i]0h(/뻧eYL *ޝ&>nw6hE`  Im{JIGIb\>Ly]i7gw7|\fZ> TP7@)9}QuDRy5aÏ<( P~`(޸m+7ʿT_)_/b.4j58SyiSh4,YX`(?ul0WK}|nZƕt1 a@yq9!5<'| A`+PkoIYpؙkgEԹy-~pWJ[gN^!Wy*rMv{Zo}eB~/k[[[bQ"HQ-wss;wˢZ=9sq*KΉz/@Hӗ8/^6 iCJ,3`gG}gЇ>eYWt 3T>2iMsz>^Ӊ0mFٲll_|wV;/cX(9 `Ms=y^ۿab툇@vgɟ>ߗSlcfFH>ڝN "#LH| OG?.2{cLjR7;M|X$tCzoePcsu㻟 *̤"H Z5bls]W޽˗/4soϊ =I8>ծC"RT "7Mk602&(x {q/G0ϱo؝3w^}9)oW=ԺC\.VY9}Ϝ9sssg-:ah%aSjnyQY0/wskxlyd t<%o{Ʀ+uT RDTFd!kyc;_t[?wm7p$ٷqg^H4n'UUd?OD 82ouaE,a#FvEp0@(-vOMDmyI!FYHyCƴBHO[$ ~QyiuJrc @Dd{^lKZUd ~qGܲ2t.sH5olaCwIO>ƀs#tL_nYu@q+Lxe@DJ~qg-- @xWxi2wBnw KG/iVnYQ"RV:_EOږu_I+Er^ӓSnYP"Ruz :Hջe!Y " Ut`vKg2 LOsLP,4+dJݭ*@(VbYWLݲ[@VU>i[}Ǐ#P,- yƠE$4DDo(Cw$dҶ$AX:gS,]¬v#xLk܇O!$PG&Axii4Ddw4!P- I5$M/˻5d6dP2RVE`0+nX!'u "w⸏:9 DjzUPyݲ3[=G7@q\ =U " $\=4}d  Vu)X2H"y@+s^YBA U=x aL  :/uw +VA 9Σw#B E" *K FI Ns܇bk*'P.XE5B2?@q΂* bq} {$ž)y~:8@fٖ"mK?FT1u ŚV Y@[J!@~ g ?ݜ# VIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/help-browser.png0000644000175000017500000035442714332463747022160 0ustar00wattswattsPNG  IHDR  ph IDATxy|>ݲ@ @6 (n"o_۾WۺTm]ԮֶZHAWd=@BC3?’;3wa99ϝ """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""AG$DD&ϩvNsNßwͦv.xR Q(SasڋvQEG@ˇi~G& EC(oғEڃz>(U1QJ)],_M]pi0 hk48W"J% D*=4bM 5Yp&hK*r "P@(7s,q&p0.MJ+B@{@@64.hkN(VTY~}? ""Œۛ7_'M@(B@ӡWvv,'v2vdd_(pjO tPU =^*z<>x|Ttv{]NMSO AS5hUӠi*T T^`f&\N{窽""%L 휫#X ?mJg"EMBQ p9lDQA6 r3P0$ r!'˅,ߝv!'Ӆ 2]DE@LJnttyAG^txAsGZ;zރnt &T4UUUдs2Vy.w[DDՌ[@N۔B&7\(!lC!Y^( /$  # ͝8҅͝8ԉN44wI Jjo@Q#-T7bSsV_u'Q0QhXl۠(LtEQzCPؔ#aat>aT)ay^ݖ4?Nkm8І#'pdj=jhԀ:֬ yO]@2~8Um8DJB6 6E@)!QUQ(+)!^}p7 pdꎷf:N74 @@C @ H54ԭ|`kCD=I5 i0%"M(PӦ ƍ E;f(Grt!s2dkYOރZPw4 4탪#~ @ י{56$1D>Ҭ oJYyBwX -R"`W#6BĤa_VeE_Z1C4٭ېEU5ihCM}jĞCPS߈h{fז,Xyy[_" "Zw$JǮ`XA6&WcBYʇa|P /HkTKjCW߈ݵ 8҅FS$4?kDKK$1DQD&Zhڗ\ʦl6ma#@UpL,ƔqŘ28u ܉NbmSp~?Ht3c&M_:wE"L) Hlөm qa(\0q$qcv |{9܄Ob[qls Mm=>Jt iԀ'5_ׯXr0э!nUW+: (Rld| a0(ӫFªLX)Cơض8>s [N}*|~? M[ .e<є|Dd=vgD|D'BGa^&.<O'FIQ^h΃цvl}:wEKG|>*i~~w䕻"J.4.3BK'MAVKbјP6,LͣRU 5ݝGpI=^|Ij]? Oiu":Ѡr2Y q*v%pŴ2\1ӫJt$qKp{ز6næm8 S薿@r?CQ6v2R̙Q+ 7.=+IK{6m; [Ύ &5'i?"vDع}_WQQ7tdpeJ\qA2ܓRm~s-^/>|ޤ#+hoHtcz6\٢+۽gd%=zPp(pvaⱸRd8n>=)=~a=o>7B{>^?Èd dCDaJްl\4HQnnCfM-ü ʓ3tpoI)-u;·s:$5#x# g4E>oK^ؽyMP;V6MAYI!]YWLİD7?iihĚkr^:/?3i߯_tWCDcwK=X#Gmsmp8mU:nDDر$Vn܋G{^jr߫~Ʈ(EJElGCQ;v;.< ubZyr\TM TUƭuۿw=gyDQl⾃/_ v҉hLM.$VMnGfs/OΟqc՜"JY5Mm{1CD ®(!4/Xqh J MaGnvns>1=QZ9؁g^َoA{/|>vo%(YżNՄh"lp:([?~*$"#֎nu6lD7aGDT>'8ϥ͉j`njJ;@!$ ]Ĉ\~t4<8D4}=׏_ߍWoÉ~a"h@S7[TW'Bt!*,B{剨nSt:1jxnq:_5 "J2^/^':KԣD/ԭ|eYC" N4qfCˎCg`*"Jz^_/o؃?܂N5"W vwj-H2 $+|5ǻnMi\ܱx Dr^xm'4u/!׈ M|v/'rtš$e7pGxmSzG~^>`>>wjZν Ũ3kFw~ >%#/X+'JC"LCeׄ0dz3O.םorBDu-kՃ/?аn}ᎎ(6܂bP>oY \zN;2],]/~d&ij z""TCs'~}B|կXr0N4- '8Edg:+p9~7j$z}7~҅7^↦}.g܏GC"8 2bJ5؞Bz8vde:3{g+-R?Í֎4ӍN7:ۃ/z ۃ@@C'-D^dr\p9t";ˉL'rt"7ˉ f /y-RBWf}tvyC64ۧ ! C 0, GūN݆ O+ݟ\?%F{Z܉.j©.Or"?hv 7(*ưl + ;R;ւG&y={&Щw׮X۸UJ8 20nE~' ps\ƌ7oWī`;ĝ߯DSjǑ8v;p -]xCqЈoiǰldXJ԰\ GQ7g7vtwX zG4ĭR}QU_~& r8g"#Qs`.ꎷDjV;Վ.SFLd9!b) E!((/)w ׏?܊_نtw{v^ h/zy q(pAGɢ,G>:v\Nf]X{>}%F ۙ^qo ?X j7a'r1&d+DyI>Frt&Eو|xI8Ўy#Qn<\'=_۹3%EbPq򩚪=8=CwY>5^<.^@ MP j7X |Q܍'lpVv# 0Up^0.΃H5ؼ<׷pq;-k*K>WDɌ{R"hiȰ:8vdS$qw jmn;]`7ܭj8"SF9KVU0|P SMW}=Ntv{ F%BhխX&Р6 ǣ>ӎc9TKLpˏXSk7֜5'o`A!3lD@iE_Q>JGc8|1!rFu kDgw|zJݾTH|R b+Uhhp:/祸0Anj–DZm lGDw a*uW2jxq(ώO(*-v!'TU~ƣ2d.jRM{,OvӁ/ĽŅVW[zX=n;ͻGQkKD'D$8g@#`F(L4SǏ@{$<ظnxZUxP 4ؤvoA ~\ `u  n"ivמǰy1<Ѐzn -t!p?%Jk5q:?ڠ)T>'EFa|P^Ԟd־sMl@gWvMd`= B}@uOZwp璘Đm­;Hۇv[;Fk둌= YaC Kz19L\2e4fNJxt$I7a;BX튥QQ[A|w',}ߙ#pWVVgЈW̎[c{͉J a#aUﯣ((86LWKK916bZ䩍8؁h&- ޺.YSZ7:/\Ve)p9p㕓p'g#/+ ;ւ [:<zBGÊbnF`2nt!fM+i3bH,-tty_7a}ӝ4`ۭ(1ҹAlCUBS`8v;[YQ#}aKR'm+CGG9b V kVDD %#`r̚V bq!pT=6dMX+"JDDXbMOȳEQ53ag ?'[#X\M0i#arH۪3XV-irE9jz9x,GG,RK{X}& hZyK+"J҃Р|CMx6v IDATipHX80oĺwx b ^R%p *Wd[WyY3cEIWϨB^3/+6ţlBsK=k.y^J-EmꜮLSz"t)c#,=ȢӀV Z;zxu$z~|##l{#e-c_Ԝ:3 L,ƜsQ9rq sT~A `y6xYׅPZ7,be= |壗M{-PΎX8 ԡa~UHJ:-i(qml̽dfL*_!5Wo_x-=xVWOʢ""qD)b;!Zd8=?\L*nYUǷD:ЎoŪMq9$tDs#>òsbY,m qzCpJ,bFF :mF/l4C_me%DV ˾?>CQ2\N8 }*de8J J7`8G<:=K͕to F1\1 .Epɔ<*b/~ y-=%Kpw%XY tߔ΃?оae5 yY3sVVll]XqVl܋֮>X{nqBu#Y01+dZ-n*s33zhH\1/!Yf[E&ܸ< -q{7ݞ__G48n&.~(W0Ϫ:\.;??\.|yxu'Rȃa*n#^#a*)D-5GZW'-WYfSNax}<7a)Y{mU:he%D2PQ.P `Uuvfbg`P7VUɐ޽8  PRjffcs/}imw}e;yXGY%M0p^XߤdX%qH19{$KD_9y >zL3̳kmx;%Kvw'FGk&%377cDQ.s>.XbU5l{waMT1 fLqÏUn+ ٮ * `G(I~ǿL|MAV w~r|rVT/Mɗ`ߡ3YIlXtذ>XZ|q)Fl$@m*O-`imxw֭s@i5lk:* H=Xx@;6;)T/ 5' c1x$0t<Ӣ[5`ItaR2BI4DJxbܱh:yè~|-;c%'ԀwYUY7$PE@Yt?FT* })u?܄߽轫U"Ӭ5tX8xnRts Bd$a)cp QQg*Gqcбftt!mY(/]˯Ԡ OvB.'.*X¼,UTjrxqxh!OJa>aH ^+i ٢ *Ī0930(7HamX5ؾ;{C&ЩԛW?‰LbB SU`{/6wbދ͝v*Ɠ/}6E@UcCz0s RDx#dY@ ?e$ҜI0aW0ivWL/eO5oՠ ՒӽдխZl_HbS|"+P,Rtkxx]xz6t>8ӭ7xxm6-d dݭM#g)1Bё(Hv͛WVnWB4 xq3|uXQ !n[q3VNJuE4_QM/~\݆ | `e/E"M^}8 @opjt+Ӭ$tDs#\a . =@c DJ aΌ˪M@K[K.NW&]u(7}?iH鰡`H6~9y?î s0(E8kry0…,VE$H F&?rʇHq}|N5uǒ'оph?XQ8,7 }!9|N# .„"Ň84uwXӭY RBG dXIBHADڋ7MGa^ACiF8v = V.5E^bׄxBv+ÉEx1Hʹ+N!~_Z=8n$#aXEd#G"FdD_"+.FSF\|r*l>DSU ]BծK&/IJge }Ehrfwyo.D^iTj}~8|4)UoYrCmb&=ù)o7Vob* Y'bDcqt? TlJ"GCEpl?&?ň?DV  އ;3BPtw_]PU^\ 9CQfM+-P%?P@ܐ?ږ֚uH- dwzfG*&l 3nY!UK[ hl7ɇ F3@)wp=hJe f a t8x eo>KWIzc #m.;TfRdy$~ ?~v[Bp!U0N 2lF"k7! 991UM'GHﰥ?{dO=?)iĘCp'8=|6ՏCcKOMWV-iYټ!Yan̐_zF4t07֞oȆ y{;V${ի*o-Hpt[͙?=+T#;=;j&-1xwO R8m5,nyg ,:6ߌa2֗[¡֣x GS2-b?ެhN j F.u l?#(VxyMȈGEЈ ES08OoAgO)*llGg[vnMC*`<ұ۠8é@鴣01&2|hu; ;wqa4G=L5?ŃG*G3"\@DojRA=AF"Nq9X4*+[wwl%N4KziV.}Wv48paٸ oziLj<0vPEK- ˟|[?;MQPP~aFo#G;ke]I,hz~F;*K^UnCH=0i]?]c ]|iWկZKvҽ hP pӎÆKoFEK5xOowvZzȼ<1#yCGTHd;7_S#.$V3GE ".qcBs08t =ъGB4+]/`J{I+P7"oz?\ӎՒP6bp׏_>6^G4G=>*#QG;yCf؈zsHPa(Ê, %F"?=+ iX|e>x}ϝ'@GB4UlR 6HzdQu3z L:v.zQRC LrէݎOYg3/۰XQ1tf@jbAȈ9XD2OߒB>GH" #ϑmI_"?T o8# 1iv>f`ش=^?|>Y﷏mY<=Rb.hWfCs%7cwJ^f&*|$44za!߹e"x@?`)A+"bbij÷&bdaR hve(HGE;WBðhȑ.0p:Mgcz(f_F _fr.־w sPQR6 daƤlV"f叻dK, Xb}fCaA6~qbWQ,X}Iڣx}= :x6נg3D ႇ (x' LF4LD!A;L&+?3doDonGS{WMQbYuRRQ~6M7P/d nmY741҅˦ MUͦ /7u#M,U$El÷[wwřFpwz:~gG=;zFr`4źA&;td#`2L!-KE32YOfD)8ҿ̑x9G"'iW9on?UB n֚uGeJhZH8C)tGweSd/I{wwƷ[M]gYvʕsLA1굃_ ୙jCN$G;ts!FFMj}=dHo1/\OB/gT_dA$^GE, "fmjk1nt!F͚Jldy^*f@2꿶x~@EYBe(YN|KK*V_GEx_JN@PBv33ܩ͒Yl$;{Y㍝gE}Ѡԧ6 *¸Yw*tIPRN6 bߘzPjm~|2lyTafH:]:=Kj n̶k1Ņ9 zez(ox|By B@Br9pqbs IDAT\+6>*z|wa{qq;m{έOC3+ի/hyNlDLvf8h 1i'5ƠALB Wu5oze?*"K3ò>E΃ 8t Lt"҆ݮiex{wI{ZzWż?v+J"4~LUݦxhw r3e,Z_q$O[0Tǩ?ݠ'tSQyq P Iw@bp3_̡#23GFg5;t((ёaDgpa! 7lz> zXf# !0%JUrxdX=KVy ???bS$`Iw_߄~G:mЙUjB!Lg.`5s3#ybf6x!SeRA& [)A%Pb/] mx' "}7jUAİz@s[6lÅG~іz[@hy.\0~^RLJO*ffYR`. OB~R, dJ" *hl}zl5ڻg3?$@7_mX^]mnX-Bf~&^6-\d@oκfmpOWlݦ[^{U0bh;dd(: G3B pm5kkdHd{òqrΑYN|㖫H)R늎ƚkO o'z\OSԟn@غէv?`0yܱR7u7d0|ʰWߢ8L" $&_ 1m+ % !}/bY _I={ nWSB _|Kͺ&)%RJ`DJUg)P&< 3Áf(Ҹ$mxM{\v >tnA:4p2]Q 41F>` If6 (Czd#eA@2 $ 7UDw>LrtTiY:ms'[p!DZܞwCu{^q>>x0 eM?Hy*ͦ`h~~y()ʓQd$K{~|u>Ή1| J u>A޿=*að> B ¬ RFYǏFc_QQ~‡obFoXy4keSKaSB:?d1mCNj@@ҝf19RbIs-σ]{'F@(YN7`z(ETbMvkޮ8ӰXGL |9a/_f:6vQu˙@ig,C4 sbHCǴXIpKHjs$fGenMՙrY4AƋ-,'qڰ {SiFA^& ;ȼ3M67rS^ >0iP# <\@ nW_4VFz$n/yd;3 FQp87-tft$"xװ+T}:w#!;0sYc@r5`N׵լ=,@J* ih꼀b[ "^t,$vட-g Az u@(6O;wzqP8 `ԡ? ~q A/  cߨ~~b{1"{{YӮd"F hHK>Y!Ù!IuH(]͟<{ֻ%4ICkHCQ23/_12/MmWXizQ@YI(ӯ~CY=`}UB,0jY1HȆFsG6Ǭgaj03{G']$0_i):mJdz_&薧?uZq/֠5Itd>}6aI^zBVaQj0x\{G ][h,2ymUG4dp$ȑ&Usc8ufY}'_ӹr%ƢE@־x.oE:,sgv::D^u`L>ӮiUE$~ ǣU 9N$CIIf#v%@5Kf@oW[_0k1 ˽Ɉ'E9Xb\þ|Yo8z(p ݱ5 {12Ck9>=[h(Zk@FGt2 d2}zvett#"h %Z`g rA\"`(PHF2Ps/'׮`FNwJ[,%#<%ڟ[J -#U1.$9| sB2]|AWg]9v*%]Zh,Z)%m#l|(B>>{a4XU_dލM&3 z Wez5 [Sq2y޺a[,B1纷_gBQkogp#=d_7 -gd__$ٗ #.K\0'0l@Y߽bgvoۣE` C49oWlQLuKZ6shC|ڠ ڈaA::Z]>x0r^3fm%"^~~ѓC܅vSF2aa}ϣTՅrN^<{sZhZId۾ @ZY؀MZnE~aAXuڨh|ȗѨk8<|3[-~mQvPIs 봲VdYHE-P#ߗ]OJO iaeɤpC ԫ8K?vY\v~!Y33M< tmk?үt l~hMjR(^1 zqnDww={6RB(-@2)ƀ4w`NqSJB<(ޛc'\qH1]IFX^: UC|-tv ψm<,n3|%ϟ @Do<\vRDvY%㻀J uG+҄Xw:d:O鄶eDG~c{#E+?|S!v$R#eT:Ŋ@ȡD?ۭzzb"&_AWHGu]{O"fL`jp;k!_o߃烐e]+>zf)Mi2, },B-XPK0!\5#Q-,$+C'g!M,)ȱIm?i6VhLP^O>N(ST܅x5碳#ֶRTB+DXg:dc&_5M0 <4{OϿ~ֆ#]c|P~ dX 64GFw[>b^'J_p $xPDDq 䰇ǐɓGѬB쒍G=k=`ݰn<<0G똊V<{wHM_U2?-0ݙuPD˅zO _cCJ><OMC>cL` +h{'k)Xu* 9pl ɔ嘮@8eW+%XM6dDTrjì!",a>ѢgV/@4e5ygB<+/Xt2FZ>_0 VŽhVּ7}m])XMBu萕LXӛ/ZfrUn`hwpkBϮ~cB>xgcA|{oW@kؾr$p%לP\7 /S$R?Z!dVRRCC@`8z$QkRӳxmb_ƳQB+šOd^_|^ LiA6+ް}][h" Mo}3[tȊ-\u2;`v^n >(ԗ|F/9َ/>V.!0=Xa*E=6ֈx;x skF8$e 4.B"W^^㤄x6#"<[ UBB gsI\}2F,V/SC :D\~}Vj8.dTeY3xI[Fwm܇w@ O>8%_c =U%JE=:)VFx42G9Qbd Xth?I#CS(TXBm㕣< ZƇ!" l@y] b}387 -2 !s=ɢX*yUk=½cLlFhM:r/,$1|=1S?ݎl yG|x7(#LC]2k  ^F4tU4˟"p Ub ?VP`VPCh!+\1v0$=4*B*myN^O+h0]4Y\h4z;i2b/:eЏޮ >|X!"<4%ZZco CCHpӫ 9lc/~\>#7f8d|PPZ]`"p׌/|C8hQeW2ʕ`f{l*2_)/hyaS-#Ћr,0^"i ˆ IDATHLXDD* 7axQP,P(P(Uv1 =kZ$*آBl~-ze% k1g;ch3邥HDkmdKuo>t=qZVQ;? ~ !n,W~ɗukAjR 0 mGyn&Ot$A 9Y>$AI e lȎYRq$cH'cH'bȤbȤid m8 Tlre#k&-B<پԺ^ZJM//ϱ˫Գz*Ʈ&x*moKC7MC;YM6E;6V*mw ~Ju="2<d9dsApZX׃VC |[e,sn0= N>|xIB$8ZA.P_!6+A8L@w{]ItW:*ShKk۶12C8 иqE5FHGG9:"uLh?RNڵ%r{؆S[[4!z4ƒ$N>KTe^_ Dnyx~|G9˒Z㯏|Dcʕ^K귦Nă]Dd8~ ё fugӑBwgmIgP2&%ũ13b)%;mIJ?EDӲOB ngϣW(潸18e 1-ZPGk VN|\0 Y=]"8zQE|KxlWn,P'!jKFOfp2U<5iӆ=mXtdm,#p0{}yEZ)W3e: 1}G`?6|ȼ>_{$+ApQZFDYDyJ;Jw #^$|V[r*xxG##vU[5ւ"Z#Stml_!#f,TP=bT_>wcx0Q$Lxl)ˑ<CP*>ANs%3"@>#H;!WHo㉇i̟فxN'D2a1jETBm0MA`)I)hW<q|¶ݎS{U>J6JP #Ytž#gP,%>AHdHH""0k"_'ߴMɪ 1M*}'n3:Ľlٙ/a-Ck V7@>,Y g_NU@>WpG=D=<9*r}a);)I>$~EQ,#W&]T⹝e1 fvH8`T U&1ӄeLS A7PQ(/P(INv='_:θu$3uI,iY #!<;l{ۨm8o4 پ0>wwdlheg|קN鰭hE@~Z|,p)C>GzF|_.MшzxVG5!fX2 +`ټn̙)ˣK0[&1cQEs,2-$zB BB || W+h Ot ;^:3 N&EFN&*⾺DDXy^۪Yj\Nڲ/WYⲽx|\s2184*#o'Lk!"jNO,e&)>2< 3{_ⅽ'/B)ԢsMs%GUFmȇMCBO_g V,E=X: ixp =L-qp`ԓxrbrɶO (knٶo;Spޯr!H!XRHe:=4'/<֏BAl(>w>&ZVAHK"D")C>Fo|3ɇWT" bQ/A'm50_X2H1>Ӧ ģv BJXH%bPx\㹂( !+`:3|v=ѯt/_Ƿ'ZS{wI$,c:\w)#ىpT*5uA |v9U'g!Yxݕp%K' ouJW*bTm ,OJ'cpCUTJɿbbR4$2E>* ȟ,rKjM Б#B1#A`V֯ȩ!ЇqeЕǎ Z#$N@Ea?]hK)o;3[/4ТD!ڢ| s <v+X6 +f بC:iqۨ%2"Mmy-T#Nc)FyƲayq}xHƨ@k-e5&RRvl$zfP8k&,>dsa˙ٳQG+R'tAU9T|\jQ &b _k+=kǫJ>\o+y" !iW7l\93BlCVGyc[NMo{:љB&C"f8պng4P(LF4okaJZ,W獘ʪ[&CLI-+! jxyEV&/""P&&.Ywx\9P,a~\,a?p)q 1Sat,*ʹcgvo{Hm-" uͷtcUXz^FrI>EB#LZȇh2D>(*׀C>j:0Smg)WNG33^5/;ZQjO`FG :+yAvgr|>?I6t'hjm+hA^gfLUyFD*!X8-1w q(#CDyl"Rh!^;l]=ˑ]{Obù ٖj"4y(Hƙޫöhc[{K1?g[5$`Q,ɯ߃SgF&RBG&֛S?l1{y=N;?]`i(lT#a -(]32l۞jTX Yя0CV$~BM@>_BiH2nЖcl[rDEh UQme4d#C!n:hcT}_q^,~\sIOZ< OtkJÂ>oy#-E+ 6 sz 1%Lgq^vНp9L4E ^&p u Th%lv\~Bl8wAln.IRqVq綯mOJtCbqяz8D]?>%7ё—OFlǚ8tbC9(#Q@3JD<:xYz!4z}n.;U7:3L2P@{R&!~Ysxb(ęޭɺ(hbM719D \|Q _}7m$ZD|| :PrJ׬A:Ih2 bёČ*ĝөNUn  ̰k*t%Fr1[9=GNsd%"pa>B-#5bfJ_[?MnUSk \qbtd!QafwǡCfw:M|qېZ#ZdM5O@qA $1/UBd }Ebl'|"*|KHGV'!ß]NŽ&,~=PVS4@BnO`~ojnjqK|ATBP@6䴪(L6: 4z;S? ɸ,%Apy A2!0W688ՇP̾i_pX#R}Gdz?iI0%|:ljVF8kӗfҿB5pת__8M gw#|(FSdɇw P!!cK_AO/%b&x)2$כAw{D]dQy]p8p! RF\Q'^Ԫ];E+LT4 L"Qti]ڋ}G029ɦeꌆRP ߧ$RL9;R8xb `\pBBy(HЊ1F? T{zMV5'Gw ={n\Lx4 O谩?ZX^_UN"aWj"3|퇿L3L8lO{EB> e̕O4,r=ds}^:gFGGfO'jH7+ *פZX"b3R՝!k[6䶨Q$QtuؤpIv: ;]r"0> o5ֲPk&# lK0'C® 1$Ӳ/-QB)EEփOw~Ʋ6Gzot2 =#{XBhƍX t9Pص~?c9^0;O^a$NZظ?pj 5 :ߙI|8Go"rZT#1UDemHT+0gFy!tWD*>S?HXG!Q 78 Q!! ?=GQ~zزa%ےloE$h ؗI Tdt*wD?"0N;rzh>7@'a&#Nq$|;:Y#>]:Q'3He˚򴦨8Q]ʙ5RI|20GrΚk.^"*f]Y!Bɠ(K8z}:%dH\X4'7 vyȤ:Y42;ZD6`ʇxZԲguR ,Xj Gp:2d-=HL]i2vVy#,W^vzYE/=Y1X;l PDQsnEˢm!-Pw"fݓ"!W=/!咞I 5}ØHB"}Ǔt5O 6oX6 ;b&@/o3 X1(Ț%[p3ZDGUEn|jvetX%s:^qz!_7#b2#3כ|Y;Ms 狴2MQEAuDݞmPAu3C "ZB𦍫ٖ𤋉Z5. +k-9ue v%!ECd l r$+CK 8x||e]w.2LTDq۔fLwa(3xĵ:LjG;w@H>8`NG~-L|E%.&C> x,A|Tzgh6jя jhmlVjqz$򗗡_E=tх(^2mKNX~X_Z!t,G?IQ#! go֨ot. E_9:lhD1+ArLrTvב!,5Q+eGЁo]{6"Cwc}rԳ"glF>"!82)l7^ŏ\~'ִ,hpLɂ[/'* V;c8qzcIYVC[Z9 'c6MGHlxe*'~BycD}{x:bNC$6pr8U| Ѭ3i8Qt-ިDBdքvqlݰb(vZpW;&]О$D,H򇭃 áE@B`:FTdb&]>V' cO' zPS$e8ak}D$3F|x&]D LSءˆf@eM3S(kŽp*ӲߤdrJ 5{xŐ;xq=c^9|Zl"j]t<\܋zdpu0aHذ?*#fYx ͽ·~Mw\2Jƹgɢ wP-A¹R3@-~e.XvBLma!½%KBbbGK!fQae)GC\dCQ+W: [?`5lՏZ=IoziXIkK0hXzAEi?W_]ƏkxţxbP0,!0uui}zn1d7lb9 2GELSWR\.',IƐI8;xIV""o2k qC2I~I:?l0v=ɮqe=x,rz6M'H@؆(MRWo6𭟰E?O,Z`⪣J>d:uj˭P^11cQ[T-dGȢX,Po:# Ӭ86^v|L9RDJ@}L,B$M \;z2ҿQ'β ZZу9=a[lw A{&_&z}x~)Oiդ '!uۋ?b|0[u \A8.##ٜаYQQSfg, Xke/e^KW4DzJ5~7mՈ蛝2]iTc(v=!jZ<7^i$E[oatAH*NbMEF"nV--݇odN8˴Y(SI>i2ΩkX=ɇ7fҟ$&OB pp)FվFպrNX$-G˝\؃3˹T"FT=!~Gj S++#A|]O$pM t?Qm \~ׂ$ `ҴABl@,o8_AǶ^gpsAMݹs}aB/pʠhT@<{hG'hS}~ jaTT# Xn"uݹZ8dHr>$f E2/(9OxkI%@&cNLl E@$x78OEexչt^O#|%+x;oVȟg;S'}j+Wu?jk1 MM CYS""it|.,=A="z홫Z=Hed?yi vs 13ō7j6M,lbr9:i~}VI2b"|rd 1V[ir\9]ɇ?;؀/=LǹVя "(i.iG>Rؑ#fdlp1=JDy:5gFB}`$ħ8M/ yxjd4lKZ%" X.YEi޽%+G`m߿3`CD>$ -+5RZ|HM S"DYa,pβ#Sm4OGbQKض|>-C#xN'e qˢ;KHDl)^^k/~~s`ެc07-|k:ZDF 5M5k`4Sq޷,\pD߹WDI-bδ07L2STՋ|s]ڵS.#T4A1QӲTeT TL2x& W .茆EQ I>.)^2;cq6GÔ'4 lٰRG$^ DiRuqf݈]?%SՅ,!0S|2<J~-l'_o &l)A+QmJC"uNQ%( ]\8crG,q|e4$hVv$VMBX}/ eqKlF|7_mm)/|`ٖϟb1ƋaFgZUS/ŋO)O D#];й& hpA5ӣݧ2SI 1k2a= YkhCs;cyH<vi{pBգ ,z:6Me6U<">я~ +InM 8@xA.kqvX@p9 静Zg/E{:Όrx4fJU/孉TFW!!^euwƚa>B; 7^ '̙ࢋiٰ򙦁 {y̪+^>؇+G?#1J9,RΩ^ɏ8k:vSwu?3Q$`:H0p< e4Ifteu$!>YR>ZA`;fFVŢ9HcJrllELa}Oo0KEF,f⦫澱""ֿ߭'!D hD_ͶUeM76w}1Ul t Kw:s1?!BAiO)ug%M#mPSIY$߫,R i}ע3u-!7\ ]$_æBq96_JAux}~Hʑq| E>YOd^b2F=W=QcܟBߞ1bBtKvRߘYРShdIcn HxIO3!'qӥg!N?ۥ{X45" ˽0-_=Gd<S%*:Ji]N`mdB,Ns2 v*g4Mן7Bp_ai)E0Ph_[FIG* !qp-1+ABJ?ؠ;Kh8kl=VM=BN,5YT_q#u! !p}uvq!Q%aHG0j:I.*d²,X"Np֧Flۂm IH[?/~YfiR2eOɪ NwU"!%JXH#9FL^XO醮`ӫ똆eg*E@ * `fw_bDһ9zM W] @xi@C?I| 鶷LyT'bE=^ROzCD@qӵ݋DĊ@ 3% AB8^X·MW(Y5HTgd,{]x7]X(KNeK"b 1W2۶]dN%2^UxuXt-$;oG#͞J̼X,6hZ;+zP*P*e2iXbAg޽;d]6BY"cO51Iq?%D15!RȊ?ۖk1j:ueWV)"#đF-<D MP&iW"UO@!"Z4g+/H|bL/phvT7Ҍ(L?3ynJTrYSIL]F48 ?6qMGO j:z/[L:,her*E@=ʦi`8k ]ysr%߀rN|PD I>ʀɕ#$!6fZiA~@z Øt!"QkhVJ g E 3o"ߦ@u8W&ܢsv U_G Z)D0 Xx<>~#v6Y"kLm[IW,Doe ž$Dfv=I)Hs,x㥣8x=#\~)+mmsZD pݫVh/7|^qTɷ= F>Y6h A"_:T@%aYV(n'ܶbFXBPBn//N~.bIx TvъBf$Ӱ~n./$$t_N!t<[SǶQ]5edeȤ8vES L.\={!h&"2vq^in0֩WnU!I]Ty~+/`hn/M#ntyh4S*ĢJ lJK%ŒP4&֨1@2L,CyS}SK3!" x9 hC2/&_oo;kU{T|wzUjYA}ذŪir@cM?KM_[:k=cHƛێ5fc\h9x3鳬\wBl)VڭYش9{cֻ ~H0njpvIMbY]i~!$Qzx]OQ*4KP,(dD2n" $#s*0mRyB3L`v8>sϳE2e[,.dYB\ AB|xCoA2n25DӹexbA%`ɲ-_xϝخ"d*5ˁ취'H%撳TRѨwx_5 F?TBLr%aׁ|oSɇxL­O@qm|C9ÑS8xbG?8b0A(!Q*0ͣhGʶ c<zn1< $q쎉O(t-teGNβZzF,NFK ,Z*]_6f*ZtR)&M&M oTl&.;:2F;_9='7K#́^*#PWHJuAvX)xĞpOBbhGdcx,qz8l)C 3Y:91PY hDD&b(dE1wydZ~䙭e*^EP' IDAT gN%J0)!@&,eR04O:A}SuD61H лyt ב|h/?Z0hms"NgqoNopF8WmCy>5Cmj"ܐz! cd SEp@#l$$Ȉ$ϒœA0:gU,6t4,S -2[qneȆ"4F+ 3 Jb$@acđg=nY42K>_H " BTzò,$ qmю\8trNbp$\V: &a`HOc$2#!0 baJO~?bf]HH73@=MCd|OɑB]B؞- [Ӱ& n[bebEAs0K@BJ^ހ#pcGQYT31+bdzY֕{Ts-}:Bd"W["Y!*s/ uu/<+~UyB,gϩWg)WG6_x#yܘl70q g18h9o$T%&z;Rۆ31f!#/`fgZj^0t |ǭrTm`9tpևf"KBRyCdO$@ּߗ-\}8tb fuͦ!LA1/6)*-ĺr+炰0S GhUіw9܁N"dPXoz0B ґ/di-\GpFpce豼=|qo_<Bfv|~V-AOGJV" _qfvq-@bpDpeߗ"nb,+($M8($D%."u"$N۷=zCO4՞yg FGBnDڍUdX1SyB |IgG$!9xcW Wiɇ&֥h0 õUXaXsE #<>'p`CLtm8yz 'OEog g/JlG13xWk:iw,M%zk@z nH#OB6CExH SL$+IB&>>k0%T.ƣW" }Ţu'(izL{2_ @ise᪋j~x.e^Ԛ|Q&YAȇvz|{C>K<¢P,ah,1+ ӟ/+j1R.Kewj` >}Os峤lLFOXβa -!f9HG[L@\vBIB\ GaKBdMGfWaJ[75jftra/Oa;U-2q-y 7Y&.?.qluaU&gS@v1sYiG7ȓN>*4 Ym# k;*%'NŃx`?ҩp(d\~^9r7_6At6pȸaiS u ó$poėt]HTI&I Aׁgc =Eb,Vvxs^C!!Y/V" n[:mMs@<,e6=zҰ*qh&D<%lYlJ f9HDtG!xd24(VN>5q! <18vz83;Σ.AH/PSE) ?6r'K6N e3;UɉjDd\׉{-=_XՏC|~7tzL:h,9CSHVB}核`p9 Nz ٠Š&Ŵ؄Hf00ˋD?fDdۨU (,~<=S|B`YA۶'m,Kad 5XԌص/C8Z0p#u*v/10xAj5a4[ k)LUZQI"*SJ{xKV"qwippn/,.F#GG.rN#!26ʐ$SB{oHBpڅwOd O겫0m`-o{Sa?"z؎7Oj1HIG?  lY<ݫSGL ò,#_,8bD|iD%{ _7~}bză M$1TpɡaCeloYuESTk_dz3ft})S>͞`e=m._OزcPٱ؆w`ImgH%Taἥ7}Ni@3cF@b6HhFA:g7rp/>4| $przi(+PZ6CRx% 02.fX9g8ˢߞ%6mcvw=_hL jN<R&7ɸNuo'W;¶ZW.Ҡ(vv {WFe*Ob=c?^Uj WE:i(Rʢ74 kF@lT[KΞB=|{;3A4LV Y'P߀VNhiz"D (KsDV7O>oaF2#aV,-!$4:='6:1KD_0~l dCReu~a_[l%/ju8y(V!ͤ9n;9L;6'8xW|EEk/*}#3yeDV:|g?|o%NcE4}l-IНsѫ@ "_ƣ!x >x瓇H<d%s@;鐜"K£HN)sBzUaqbfqHo;I/k8\w=(Ww  ⁦0\Ĭ|Ւ|0MXbsÏ߽_{/$A0+.М˒ u]T/h7}la}([DęqX٥iCyp"cꕗ B:n"=xKߔzw^y [hal1̗f8xF.F>9J?!ӆuFyҔ8BZ=Tj17G>>~u_=P5 R0+YCB{FiEV{^7lj H'ם#S IQl&|Wo ;댶u(VYè~Y)<{4sHCش8p$"h4vwVZV9[|;D=8U̓*}_B}խq,-wϯrHxfuntȏ*9dE&y' }`K񖇽Iw$Į]68CIi?6T̶xWw12_~,aűpMG@8V3^.䁹 1f6=h(*lʝ#itnF1u`u5c$-;'zyY˙C_ƞt䕙 I *R#,;p؝,LBvn?Tm'C" "/>#*Mac8z_@]SᏞpV> sxFEE>$~mDQ\=``ҡi& cD}Gi]Qoʽf>0w,~e'x8W-g_6RAKCq*{$yʣgD&TbDGaEI'e]Ռѓ$ESeކb, G WR('1?;fbj/($ѷ]F/=^moE>|dY٧td4H>{;QFC~0½'haΣMÛ7{h#sN~xOtH*hԟ.B71u8T}HY0]aawhg~RvHONM-*>6*gCwSoC to='|{ /gIO ՝"F69Vq+vf3?˝R0#{'{utT2 F"C lu6ivڰ0|֧0.%[;=|_ȇ@yw)vH]nd}D^]O8Bt0WqbA.6﫤$ ۲z0tJwΦ^ne[j7k};t PA/<{ SSn}p(oH7x@}ݏ`kMdQdCCo0d +j:?O_>$/[&P;ヘ?{"] 3O;- sYG>ȟE8d(i1y*B:)Ģ 1rA H7Y Q}jytz[UGmj>DtǿɝZKpV^)1^%)lQ=rv rc.?}^!KYG@1\쬇w!P` _i)7Pur/ܱo)H*ٵw fRdFxce%(ad"%ky!0;Ƌ?( *=J]ٶ$ TW6 ѷ}e3>ujɂ&#Nׯ}kVpqz7q ]şCltA3^BHʅMPRPkelTِU0.Gj=b?z?z_|YL+h!o3pZhЗ'H \<-cB09vwﹱ/%ca=}Bvm++'/c+C?wާ=" 2߮<@tv+ttKk13 l"GpJi5/?mXxu95gJ^1`3*!cC>z$lG+&ق4\2ڹ?$FДQ'.H9# 䐳 2z+23!T,sEN탱 @mO muoutDq"zaX@_wo6x .C.uiӓS,ҾU~T ,nC>mC1'򑎹UC\CB C)d L8{\n3 )an! ՍNpEH9) IDATKci^SQUL rc@w{JX8eF#KOM)%>rZѫbg?L \tԍ>cЀ̱ɇTSʸJ UxȎPITX8thhYO4+:X:}K}")?!ё&-m-3O5݄:sTO@v'l+Ǘpꨤoo}0(Z}3IgȇTBkX/GcGOBzjDWԞ]r*Tp#m}p_'-"} 瀔JB UMO&Rd\5@k߯`D'pdaM!;Y_>UO@6;0پ3Wf7J+cr5SrfAQ-a$j{dz]T}gY׮)WHo{dKeؤ!SCzpߞs+r!X.1YZEq6t$F6~+ԱBIH 2$CZ#MmMT,kL,dP.K'\a\O@ TV5~{U.lzS(RCʙ_V? 4\tQȇLTG"8wrӶ4emHh? Xٰ$Z2DNXa$DVW+ԦN3ʎ.fmd~GB|>q+ rݐ0 O@_qo<Y(+I.]aV"(6UjZYI0$b me<:lhlz|7P82*]k#-,)'! %L6C1Q[oW3AbWA83|:ol$D+'nUՇzrAjN3Du x .TO@bg@FsOQxӇ"WOP"葺^Se ޏ9t{lH1j@ {?ȇLVKM"_lϞ*Ӓ #D-鐜3&jg:qUWm+W$}4 Xx=%pBVj-h뜬Ԟg5"2U|Nyxˆߟn'M@^}5j"p"f<:U,zJVJŭr`*U2U-5ZVa_hT4JEE|͝'i[*&Nv p :=6g-kOv@a^_G@O~FBT߬B :PHet`DxN[fA>"]E۰1wDo>^IG+On=`+Es\.sz,?2gz~Gs'={s:B:Ԅh!|᜶L f~ >H臑cs;y8Ɉjߓ6ճi{c8ƺ޶-1u#5ԶGRU' eðڟO˟*b pp~FQF$7Z9(eb66j6r67!!{CP" bjL:( ['qbi&GgZ4{jbWu11oe{!!RP;Axt52Pw{- ݨ0+珢8$ chpbAE@V nEъX]UkT[s4:A>IЭr%Lp+幬_P .f @`an*lU%H|Wr"!^*}H{) GNڋ캟9y"c "4<ԉA/p|@Yk+v%k_#MP:!H%|dHA^7! *ZN<2G ! ڗΣ7Usm@=&)I&W=ߵo=ùN2#i]E/Pwoj 3 Sm)CAcK8qdΧKbm{6'w")ڮ Tnr~X'-oc+S!(eCGfC ՆtA6d ?t 07m~Ȃq6(c +`EQn=Q" ɨ1ɖMI5eYJ޽'Yh/>}l sS;:;y)mWRn5ȅd S#@P1rY;-,l 8Ś d#iKlɅx$=Еs'Ib7©# J# 7](s*/W7n D2o%/YVb*Ӌ΂x@~hg$kxp<\:su֕Kyuڗ?UOf`@ʹ̆WbBpUVvٓ9`/8:|645ag F>$z .>o&vHn㷾z äM}3EQ.M>D0|7;Iܸ?W˅gTO Ј;pjքT"  :J@1?9_T K@GNFr2 'z8+v! QW\aY|@ukCƻT M+\ɔI~|90tЈ W/bnZp7Fl~BV2 N+H2$uq(~0?ugA>SxWs2-!X"LJXK@Ag Vn>&/[笞lM6V231Na4M΢ga#!#ezd {OL t \PJ p~&!.N}.cUġ#yfSNVw8hHHF 323C^fMm#ن^ǧwk9V-TeL,Vʀ`k{%Сˌ~+_a* o2e?lȊof?|,6,Q Rȇ~0# Ry=*]Ȏx'f!+*ҥxᢹga<*Ja\\пhϏn eŚgT2}m+cޣ-*,hbJU8<488]=F0*0GE"6r.)կD2Mn! X<Jg.J&Av33js'ϟ+Ijbq?Pu~h4F?ŮޒR9 tK`Y?s໮/㋛38+#z룋3BiVlR1Ww+`kW6]TPUg|3QΌ^d?cUnlZQ0堜:2SIF# SN< Lُ^#6;}y%$d!bOiaR;HS[E}ٲi&_:H@WTI@.<:}g@e|u\Q4ԊýͨfEe?z ™2"HJH$mGVoN&ʙ"f=T(xȳCC2wlqKh 88mp0;J:[sʞ/$$~'Q:*dMun ]ƄbfA\7VtyT}":N-Hs%7$ QxE>ΝZN)MsNYVY We`6jj$$Yݹ e?#qhYÖt(O(ȆPo6~/aڰcpta MÎG>`n~H>#)d1~4QQ7` *z|y7836=fSmD i6\<˝1n[W N(yfnMi?k^â1]T^3itYY(ёCI<Ѭ4ґ;&2s3-7.cְ90tn")@zUW*B 4m_ْ'*1RVWooNgcKO u3LɝJa" H]Ed@>Dh(= RbSv$¨|`4D~E>HP3+K*#Q|)ޜnG]LK78 $D}5m]'-\Dx9*6P:n8S*F=acObg=& UB 7nԹ bT83Ā ߲!מ4Nƌ4C>d~$r"*`6aO*FTI@UA9ICB,. TFé*Dud2D *TYXUɡW)I.J |pVJ2baz"R%!#'wysaWS.Lޟ``=V;k|p wBBd߇  䋹ΪP,nn>jҬӎ!V ΞXK,I@(0!0dMTlB8"BXvJMd2gp^7AMQZ۞ɇLo^ʸ ᐑ,ΝXeL q)w1mLVa)и `gP9uˤkdթmzu〢~^E2ĉeG9[%N\>qtw,Y[]RE¾/ex1QTH{(}#fKcsVTéW0׶R!;I#A"2 y6&`$ qG#v]>ShVj5["}a* L0g?n<y[C5߱^CґYqZפTe6~QHͭe@y4 K\оE_TG@ZiF&#,KE 9=^*ɦTT>J.353܅|n0>gn֣:^%9D!It(~+ ){}C휂q bSWFøvw05O?ɧݻcl!^nڭjZ" .#L"-ܬ8s|iS # QwBy_Gktw P\^+}!zY T=3NOG{@IL/|d%sb (l^b)PxPJҡ!#מ?Yh5XF@ * 諘p֯,v$d>­+OK$! ۱r6bzXP֪m tX#shZ=">̀TNp@&e߼EN)a4P!E,;B6y;}DWyK(]7[뿼lEB\T"jf }Tx`8<:!:bNY;HF-EԲkY#\zh}B#1A <2{_4HS;,!$8Q bv˸rtS, ~q01aHem[;NSԁE6ȶMooG_#rȬs$cض8"b hNQ.PYX:p٤UXUf?T]m>}wS r*|[M%j>H848<#,f8@pnK>8.zLC`mmz9/ Ⱦi3 !$iZ˺ L>j$fuʀ<2!a/pV 9 @ LL}IxcQNF!UoR!T֍SEl/kAa+p?:!%c˧߼y07rɇWm۠nn?No^K{:AB:t@r6QBd*iS0QG|v@%+`ѺE~p_m2zןe[UEnZ|ȠI}yHZ_5hҐ2? 3$D tl W.6ۤ"cpr#h7^`!qzN|VPb?!/Y:^|HjD@7WE@.}im@9;7h4)uk,-#\\bTE&RFK>5£2!uUFzɇ7ȯCzNr] vDngf~)ҷ&),Ҳ$Q7AhC#TTDVn2G8Ԑf=tíLC1$$6v=Q=[r}8A>~ʉG^FN C#d=J@|QOVI#wN\J:r7ŧ/&m-,P!(*5QM ԔN7Ėa(K޼ Bq96*KcxpW\}RrUB]FL6ם-O^Í^CUz"J?h>k1>^18EGzs1Q$B|%f8R!XW_)$GY>`]һ%AGi , >^D njeG:ZiD>Dc\{!HcX% ;w6&d)\Z<7!V0qN{`"TvPEr IDATzt"W!XGgpd Asܩ& SzJA`=M?R=U~U}(SL+*T=nCN>`-P\Q._Z>v~Kp$m@]B`02uӯC6vFV6-zĭxcI r"|2bS~г R0Kzv> tՇ,ϻ#U^" 85´_ CG 93)!e r[7ݐ0ÇMnS&EfB>~g(Eƕh6I:?׌bC9Bͭl^(:50ZB=WչAEkin"9 F@7!xE)4E*:`TU6/D{ca@4"|L mnuOckNl'lȇJOFqL˳o^m&-Nm`zVWh6 "n퓊q;:\t(7͎Ppdq ";٫ # :=%j5cG?0O6FKït zee 71nӜEi`H$)Rd#EDxo]#q \! p<~Oam m,͙tf1|D>(ƓtKMy>,rMn'IaID&heP҉NV_;@_+*p"^WDX^Ov9Mn.b:9,Ap3qE !$TF BQ|H}7dU$hA>$ӾTH Ϝ]o~ 80iT²W3ĖcWq :} nt~exsz93e? 2A>((G29/,WzJAӘ&l 'M>,烬Pnv?yC`_@KA# ULj7՟v43 Uamk.T,; :}XJ7"7;`R_~bT(>N#kP>euf*EUVuFsE Afn_aH| ҳ'Ok>}%͆EJWeO6w)}KfaeD>(v;㓻k[$Fg;F=>˲+n'p;]&} gX$Xr^7 QOC@ gǍG' *&TkPʤ (|lLR}̡2}d=rdG_{~s'in88qsǥM6w!#d]|lu>~pOf/ɱ@?᠑0s~ ɢ:/'–)gpc[%L A9W$)-'p_bï -`/K6cij F!'U!= ?-'"[^TȉOOgw7O5nBV׀~\F'|t!ַwɇ͛f"?ƚN:Y4,٥# U3m!Xnn09D!XfC@X?#jRPG)S!(dt&9VȜEX۶Uz ZnJ>r+]!S;"L@GPDYtR8TGx0m0Xlz&.]΁r+W^vA~~eOAAYC\` ܋`X< RL7J*зݩOdCR9yl}`mhRꔀز>՝s= Җ`\IbjTVُº'2C%# '1Ll?&2*[ !k_<]XNibnM6O*`C(22#A `a' 0_:ֶ6vʤrzME9Y{2wjz}W2`~% HGn$ ɀhzsI#H[90t~Ee9>d xI;=^ZXTfUVB>H% KI>LW_9+gsY-,6#l"EOd$tcjjD>a'=WM 1yl'ΗDB?!JCŪF2u^SDg\6"źUqQV$T0?a}R= I '8F׫l`zřF1oűUwL9޻O/lu]C[ټm,UD -k4 }ncD@,yrg !0|4 }k4cEkI CwԖd`H+f=ryX2g{7#c$oK棊|(||]ʝay)yqWf̡WlMm4zC{⛡y߄M"n#Vnx+.U0,㊠L!"Q}63`SB \*|}l2m2VrEU&s!QFW{yY_޿+NBtunq %!~H9 K~FYu{Vw+-Iq,wBEvWw%s(pM(\C-'7"v< BDÆQȇ߼!}!o>Ϟ[˫64aG֋˖"f|u 1x o|scẑQ+k"XZsaXUʢp?Ƌ! !jݪ`ѾDŽB/!5]{hڬt=uRQMco2lI 9@4BMvaـD%K9/ ox;&9]TV̮+Z(l 0==Mv@C>Fx a$ H' yNB$!bP/48ٓkseI= @_>ʘn7@qA01NHl6`')fZ-ʬ`="S y𲞻,5ڥҘ)0 syU$H_~^|1vnҚ~0L?U "EP$"LM"rm|LG>cC,Mi?k!I+#@Vy:Ɵ~2V$t 0 + HC5ˀ`hKP*f׺O/Gֵ>^8U~ȇwOR;2dFdôҕqÄ.W=A&̇l$ZF,{e.h4xc]?jO>C"[&T@~*aݳj:Hm!paQ U]&j6`pV;0 ғ` E!SE}7&f}!V$ >`KblD7g.[/>2Ove"YG] j6hiM(lnvKxw ̂5D)J-Lo@7 p8 zi)!Pql݂k9Sr>h#C]WzzNC}e?v3(*-J8L>v\ϚN8jч]qȇ M@;Z-Zncڕ1mݟ\G7U*NM'ԙ+oXoDʪ"ZY]!J_OaX[U1n: @ywP ʱn EO8l1ehҾ ~ 望8JvdP)'g_>F`&aW^ϙ|$*?*nlV`쇑 1#?" :O6{a!."A>{}KtPPIf#.`dc=Rai ͩn >\{oe; W`5*!No]CN  ;<9Лx@׿|Gͫ'417m&)q(Q!yX웨zOMc: Ba(O |v(d){/5.@}&,pH@0jr޵[bK83zlz۬}-9:)R+EOݬ2^j#SE>/csZ`vD8&Wao8:8{|fg-jW;O>CJQ(eOlOQZ MV֪՝Ԏr^qAzi2`8WeT*%9^N/Lr^|o,#AEd?\QJ{9| /\<"s?vKsN$$\FA^fֶd?N/~o% ;a5 SWKgGۆbW]`0T` `BnuP\ ZφE<@1ÎKzP4dȇ9kϝy`sE$dC|GBg8l/4"No<zι$dV:fA:,EAFR;Ү5~gD5Z\ONqE}uoj`_kϲB,<H}>&+K~bAiǧ L dwe( LTy6K֑nWm0X-6\}~"t""elضs6I: :B ;>QP0,J'VFn$CTd (ߞ3M9A"SYze9v2T: jC$Cx~@׾teÅAYmf:"|t5㣇Ah/wU/V$j>rO[4CQ*CM{@8bbۢL㐀L]k[R4ZUY5wRV9܀e?LC'_{$N1t풏aO~0ToljBٻP?\A}׻O6za$1bC{Õ$$JBw, o,[[@!J'`Hʚ}ѥ;UD㐀L2ڵ*WE $޻ b5}6\̇2aKsF?gZ]b\d Tܕ!Vz]CJ }s/?&B$WסuP!s[;/{␀f"|[vUYdɁ>:.f HT2ZCOrVYc;nAqw%IAà$;Tܕ6vX郲Go۫hCMB8>kb_,If3eBv/bղr֊rj+x11c`V-UjeY_5=igNe/ vm<<5>=}zJTs?z)ops %ŠWQy.Q!]4MZ-rխ>: Go޸.pGt_~fLnH\VDP\۾ ''Bs߿;:e:ZGoUU-C7a˜ +c`7:3fqeaYb7sQJe$CO/♳Kzx|p?1 ]7}A;Yn2kcS<ԉvSzMgm_~D@bu&,u,@fdKjuk1 "6UQfçnc+ ciQE} ^h*X#9-ZA޺w?{kkj¾ιLiMf8kL gA(2hI {Aj< y B^ e~}p1ADX(1gaVS*0]!`d/mWy`ld|~(}K hE )a3U)3@2F6 YS&n[L'D Hܩ3Mb8{GSH6/w+1d = 9jߨAW =U`aƼnSbHpv[}V}׶zO>ãpIH b HS$+0Rð8Y|@xl4ev<3VLqz(}ٲvOsйX@Ha/gsɩﴖ 묃ۓ,)ʐ NύX˸11Dmޠ^eX' V`]+oWyJ՛+j_SC`') /^9jsS?cNDć#alg3Nrُ?d/&3Qu Wuϱ윆U IDATZKD{]ЍAMB`U8vi@~Z׋X-\P |ڍmCzo0n2R}j#'= Bm8~qa,({mG{s94AuJb֣=KƬ;g^!܌[&@tTV-I$ kDTtz.m >lP:9|mmsc]r7d(BRфs(m(7W_i> ɖA|BAߨ5\?*t!l F$Yn9}a!vX7p &D$vVšjZ|ds%͠Osn7ͱ 칵}/=^xD4g?Ǣ3Y@%rZ-|^A~HST8[FatFNz(-]oðMO0d)plw{.m!arA6..ַ)S̏kϽϊ"آ72UOQ % 6s~ZЪ&S1S :_& sj;i. ᠑,(HX 91\!@[Dd,;v)j",)ڊyZ8ƺU8N`CQ+zY{o!yBK\zO!咏 .ZiWe,[x'ٱ<d >͒oW"\϶v̉C CfyH2 󲏮W.϶2[`ֲ%lNV#gV`A~pqbie$3z!nM>/$TP| ͦy ϝ^~cGo޼N?o;{&Etz}ò8['m׆?Dbw K2Hc!K]Ec8˭ 6߹J!XuZwqN o>re|۠kO+ɲu>f/ga<4n@(vhb]iHJa@D!FKe:4$$$СX60aV |+ԩ*]evU7jX"t$ "ja1IW볼|y-Ltؓ rY3ˮ aU㲉AvBoZƀ>bnî;&…Tlw5 +$emv^чXTVAnv`M7A,`CdXtLTY䙺=FO+dN_iLrՃVP!f#GU>2*= ÷aJB'er:;.KID*)o(e\svO+~:FڀtglձA VzzEqV0n6_NEWE0}(lHrDA(lpbiV3>(xmfAb]CcGfWQH=r":pxɎu=Gڪ:]jDې%vkDn`=K0QDD'#(ȳ ~7˃[^۶~ w_8s/t܃FgO(#2d?0&A`\7Ӌ2% n=/A$!ٓ6 ˂0`ӽq_ ˵,nD]0E6pd"&)2dRU#ĒW^Wbc1/` { ĺ=84a~5j\HA#"NbX@&ɽ wc%'K!!2^S 1i$C_8)SH!6"E%tY'9螭7ݡ%B <1igs|Y?UWsI 1d(J ʿLQytۇ# DtǗ@Ə+T⡋|GCZXg@|T'^=k9lN(8^ ɾ wz|Z͆ޠj2D2cakGRHscu" +`zq"( qze@Εkgeގk "{{K[ * FDLُ0 iywW0W1)=+SddK.@-Qv"GdJR#JXs@@N'c R >z mòN{]2O,@hf,'C7-8bb;~l#{(H|b`ȂEIdm8s\<ODC^] /[Ch*Q1cN}QD=;3Dogè9DzBnmͽïD.ܿ,!IJDL#btyB'\]f'yO$, [4QBI%,;jAK欤N-XSdmbpH@)Lrl VKsSI-> !AΥmBY%WSY+mɖ-Ye@vB8%+2D@v$#02@ڏAmWolk\\uאv}JX3TSu>y-j.)x>h- $&*L aEq'k; ̱O6*d0-%F,N\#S-L#I+% >Q`~37n$$S2,O1Y|:D>/)#^ξS5" p")U8d@gș >V`³b{J :r9m BsL*L<ʷZBy| "jo 5~:: EzA1cاx(f@*t+> +'Jǒ-8;'K^ f0nݘ;iHhCT:'` ;#]ѯb%;fۀ8D!Tae"cYxgFM$}+`=ZF8FVG@bG?BŘgF37zM+bX)?REAT˔1$!z#vPUgD[R,HeI*uZ8E.+n<<\`0p0Ruāp'F՟YLc@(TuȔQpc#"= 5>,',A^52+L$SФ~P72/M!c 0,\>Җ=w.m-gT KK£-n ,UG@D|"}L@z|s V= JQ;S5 BE&Rp/b4BOǙ)o Zj'Һuf@??g Sw̟5q/~.k"H|3 =9 $ 5+O/+Ό {D*'$޴{]ɫ`{%2AwB7W+1Fs!', rqR&Ўl{; )|nqw)׶w7 P"aV@.0`aPEx̔hϹ'~Aq jG "sdtsBN2DY+ 2:V o" l `ߩ^eL&"FnWp<}t޸g~ؽYcm*Xr= mv6?Z@K8t8šnkON6Yp&SٖGYxǓL:xې~ a ­ˀp̑~1r-P1Es8@9 :X bвn"4 XQ7frA>PvNï n0n8uWMsϷ.0k#?B\٫bqHCUzbڃT~,Cq̹xh4v6N=h4Rr BI.'H=ق:*R/bB$jlH9ۡ>}Y)ܭxNT/.Q-SMܼJ@zOl߸ˡ a" UÏb#!]Rxѱح.2%WઐCN@Ow]&MOgjcLX$whe [a3:~&L~(w8ܬ|{VCq =%xP*5Ddx͎O ŕsG̅2r'U2is,~!;1vT[;Ϝ0 13]ZN딬ȇm@dz{]Q F[=}k"]&q|8qN}aϴqEFcY} U@"asgc&zH>uϩ󘞪,ugz1D rzhAOk6 s كq!<=8'?oYٖJ"RŲYՁZ.~Un_C!©3ʘX80ٝz0*TyW>m A۸I@A }Q4ғ7&Bc@硆ȼ-[*8ާ ~U=:@ܩ&|25=[ ħu# K^:P6~LԱ{;=a1C} -˦_^1\ɏTaX`3 10L~pAOo׋|x?\1e]y)JB$M*R3cհlyB1+6Cα y0PjW>P$Õ" [u" jݑ^ug6 e(dmVq|j;qdaMl+*Vj(d^۟>/?RYfcgA{0LRACNst)7lfl0 @$6=,kS~ϊ >69-ovwA;p" uWo<|*_:nE@$LlXz$N&͎ N Dq%nv_wnOXG B\أGGlV6*xWѬ7WsC(o}E|TFRp31Ct4diiGʟ~:ҙ3VEV 'uʀtn,^]Ё ' xl0kܮ"VW{ KV".+܂5/ c XOV@Yؗ' ?z6w'nFU{l{zD"_|f F}ՍlwAFj_P]@Ty[ŸP,x#^;ذ͌Y IDATħOqYwП /q[KhlOK a[E}Vx <ӅLVr&`"q /X)kXqb G:4FdV͏*S>RkWX 1L<+*U,Xg)*Dr&Mb&,LA&WP~sٯ\Nȫ>810޳ 'coº}ɲ(N4wj qdv۬] d ",2s$,?g\@M F))} %tfxT{xk\Pyy !Aਾ#֓N!|lb-ےQa"έV]vtՂ7=)XJ}ָ*]Lb_(q9^@ΘEs#w$0dQx0Hkێ7/ňÞJ%qC# !-;L51ݶl>k\PqbǛ:Q(*, ;АtnQNB`\R,!BVf#ڽd)WkGXs=*}P ߈mJp9વIV*- ~_{<8`0^Oq[L.39J/0߿w.W!@X)\ '\vQM`.zs_Q0{WTT= 9^Ԥ,l# I`UEPED"9/2}JV/v cUYUXHWZ;UxΫQ%V-ɓ&nGZv*&U$@jPV~< oUxH P(Rf9Aj¦**8q 錚AMos_'`7ڗ68uKgXƱeCH 8ۖo7G U~YJ@K{Z4iTuP;t._>SbLQ=d!:A\L :ī|HC2xES8@U *B&֤,Hnal:A۶",a"f~ƾ*< JūH ?DT-B'b7dImEQ98d=doA&k5wXqi HY7Ԇ@8V+,H2z8؁m"savu.#&Ԥf ̘2LG]m `Ye&73V~o&Jɻ6id`c%FLc([,:ˆγo3?ʃGOw$ז͝XtN3}c:(K[-yNǐ/U*zUU5<|L`nR5S#.Cj岇"۴|6>0ybw~ ܹRi^jR:eݢC;#BliQuD._(hC;2ق+B㨾fدctŊٸf~kS)\(B=s>}ї ϋt:[e nq[9bz"Ny➣6)ko6>x͙KfF`,8|!̞>[. Q yˡlۨqsͪ9F׮շ[¬x5.(Ji+œS 94ڑNP.Uh:+FYB7]1ҰC0*|(:~*٫֤pQ> 饎Ʌ31Tr|4,7ghC(|t# mߊjW^ 6⁻LQlukBʂQ̽B's8NNf.`Ubuj']|>\lԻLLoEfNITED X p430԰¹ 89 kP~ZSHBU ~fU` _(⍣-㽗 0s2Jb>s qPK7/_P9h |r] 'W9062%j?xd28@qhdܢ))zưcJT3 ;)X*KT xY]B׍=WiH#]TFOèU0˒9I'Tm90Ԥ,!afCؖb~Z#Fi]aI!mT/RUVs6E]!fE3#+YXu!zB M:$0 +ɋFc'rKG!SqƶbL,+3*"P6O؄.~V<nEQ[h5h7Eb2tQ)GPNcP P̑Ś*mc(+R`XdV,5*7h!3VmS!0ѱ*I쇛nB^vI XxybQ-\< KM; s\dezT`  ZVxM#-IJ vl[%m,a_q:GO-=L;%b$*]!>쇿W+0_*(_%8SԇV)A6/S^=t 8x@>W(`sd B ۲<x%NQ['?42].T Sm ʓ ʯD[y.X@R0E݄lߴT?` f 8xiؖZ_s*fDHRdDBFFD JݹӘka;W?XLg4F&H7_\;g?V9}u3 )*\:LVڰb.]8#8Q3PF% ә dKbn1p >!JِXC|Lf?Ȧʯ"5C`k?~oitXd7Ám~n?'DT UJ)]mc`07iou m_8>#aiةixL<(kӉՉD5]z%aڊU, 9Tbwq!WEoڹjRJ @pT#}5E,nf&Oun &e k\GD9s?QptZ+d?ܡpeFqē a33pݚ5F$a%Zl"~mW{PG<qò~b^6t*_cZxr%20=gzΉNK8V@cHx9Oj3{Lhv?_+4=nDŏ֒ш @ |esl%nF7xB@ %k@s[GoXxY$7rzv]'I3E(a1}'B NS4ps/8~M 幣l }]+!5+RSW$rmO(m&WNj(XlݰfNnMVqh> O{eXA $+† R~=_)WIZ39EHL=̞.%y}}CWRixE"@8=p exjiߚRۯ_K Ù ãAt9)j+uh@^p$6>J 56;F\N[3AdqX<֭+`YVѨX*ㅷ7c[xqָB"@(-2rHŭ׭¤5bFo3 G4R,61]yUˇ)F+>w3ȼ -ؗf`qiLHY O47n\0h+w P,*XDP`M+әĴ k e+n3Ć91MۧS \Au '[:Nd22ڧ1J^u{ɥwܛTⰁb-)HM-al.WE1=S/ 0#VF%ql!hb5*y jQhd\"O 9LڣV[ 4ŽYA ڼT­A(?a`Pyש#D*ƶ e NzspG%B)rlN!Pe(T˰<|m5kA!M$qa+EE,k+3{;mcΌɊ߄!ˊE F0|@ G&eXpMD.oDIy}jĕ I4%O(AVۑ: _ H\aCsгbӿZk)N`R0!*e~1웍U|sn ![$*T("v ƕE߸묩%g9? a$kL'M )$BD/€`85Fm*3LZWml8Q01q\N$Q)S R$BD34SJxi qx-6Fc]FW-x 8^ZxV|)X!qD{NxE#ㅷNF,%6.>%;y v2,/hF4csH8l C|e2O MT O+ ?ddozW_t]6WgT3k΢/cv{XSXeJYlw07 2:~t(u&V4B>,^rAV|v^~ƶX3̢𘿯ZɗFsKq~]qFbbݰAs}v6G8E3M4]w|K9_}M$ GKG?hE߀ڻ?lpDREH,(*[Cx#ӥWnaP}bfYi/RyC9,[p #/DOϑd?DRV P&_=L55 &#y8*0:\{Q1O˹=0I>øp_Frf?($IZT82ꥳyM$$De*P{Zapp$i.}#\'v\b;NO['|GLM )>XEsˊ_o@F‘᳗'y! MT U~][VV"3{; {U8 "Z͚ WY+V :訵I(8FȌQ8"2" 7Y(!DO-[XCD+phǥx?i%Yu! φ+VǺ(^;҂sHsT͏~1U"?C#QVW)N4rI2Z72(rp R)߉E!ג\J"FT6S|C1#,7 Cգ!I@Ӷ죀դ‘qOUXl6۸ԱMg]P@#M&n<h\ȼf;fF9s,D%#jC"@dkWٲaCYC P[Ct A2?R%NU˰dںu_]эŕnH>?⃴<*􊛇RYwFmʯWIaP'o܏1j1t *+ ?y`W<5 =!fex4VX4gn{m|MDt:Z$?LM^/=/wdS(`v&l[hS|"4Vz% ߃=C-rS9R(;ܙSFZ{qd˯jj#jD"@$Ю^pe9xgAeN#ccn&m ?bB?GwFT;^~#8t!"dqxGX VG1QǼ-o[+٣TOR\o1xxcDmY?9ȟvj Ԗt3xFw+%SBŴu떍z;7|@J^蟮r #AG|> e?D:ZHm%˫A#O($g7BCm+*xrYyAjF"@pU(l3+bE;w9u L!H-U˰|P)?))&tcq hm׾VyHF&=Hϕ͑_8{nf/ 'ݧAgwTOTՉD"%y)gtD jkRmQ,SO7}Շl:q5kn$"6}Y`| d=H9}-6#UH< 1H#,` x0LJqItFtuixay*$ՍD(ѝ{S(Jϟ>'}RlRx9ᘙt]v,/ TVbD¯9 (gHu-(>aSE|:߄I T W!wEåW:n|Na6u+!jjR7I x\Бo$H4_{qB4~ѣKmi+d0 |}ԐC>w<D141r_`'J6 [1p>Ss^a/!TCe &?@1վXkW%lOo+;Cɋu  :kWٶW4}is&LJ^Wk,?5rcxp{tDς(xEHL8$Rc)uCB+1`Wlhp\'Jc]G|C@~)/kgewݲj}G~8z0ɩRu@CH m8mCyU4yڕX{ >XÞ(́񗌍cq~&i(?5SI$B @dhgݴ K xk&u\~JqH݃8=1KgKg KԜށ ׀^e.۶偯ƻs!$D#er @*{/H/ݱet>MU-nXq5h$]P}rȕ?to)٢IVvſM S|xZ!D~P 0ʯtkR>,$';tZ91hM # qdAX{A? mZ*w+ Qqz+)?d8F):3 \Ƒ DfNxη6AF]N;ξCpC~[69'>3~t @"ށ |4:4,MRO$E"@tcB?D+0ɞijƧ4W'@OS: %OwW'8$b!1wCR/*>Lf?\~QbGC9S1;"*̘ŷ!Qש'p" 1 |Y(w&O?rm\A}eXc2z \1xIBD(/x)ruqC|xu4eb{B9lGJy6N<띠8qB6n D *Jo)O<)}R\~OZx曹 uʰm> ^u>TG D_<,R"$(qPDUxJ'[|H>$M܂~Ÿ%x ӄ>?lmXD,k-{B- &f!1v4dA qgHGXKwl#K)v IũlaԞFh4_e2:C;!.N1WEHТ-H,L% L:w4bMDxҽLM  )u!tuP(* g?HAn/eOo,Zz4DϽ 4xޙ(Zh>pJEh'D{G?J*U4)~F"@ c ַUyynVMYOmCm gbH9\?W n_Ò< ZgB$24 \dvbu!/H+[ȋ[F< 'D:>H;>C'E-֤7ҫ6t(Uò副&+_F"@@&]HloЛ(qXh&>&paGaIwMղ$KSG Q׉d=F*/ `sMh-ߒp _1IܻT˯yӹ}FR!oq/XcȮ4l#Ώ#1s=}b"9$b842ю(H 2?v׼>e?'cJ3s#-zjEhQ}i l_xA D |0cֿv*OO{A2YښaBM N3Do#m ˗HÎb ( ,r'ȥoÃ]Ϸ-CxWي8L{LRХW~A-ɲeʷ|W6.P&_<{ {tН|OQ zE .hs_9ZW ($v*lk*MTBG7xGX~+C%sɔI_y5>Z{_:m}]D QvՆ=>Cz*<Ci߈ۏÖ5E͜X7irmm&x| 7ފ>B0c`I „?]>6QÆ~crŸvܱYP.j|mFZEpx@B7TJ̨a%2r؄Y'qy:okBewmָg}\ d@B@W)zzOҝHƫ=W.oȼ!ߴE&fG|m]FR| !ZE`+S}*eY??]+r&Ks?k?C_Yv~} 'r>~=q*ֻ6.u؃ѾP,ᇻ|]c..$$yl~_'DS~1aEb|6eoQ"GS>!q`h ՘@Qpr#BJLxzkǧj܈C\|wM^)g?4@ĽIM?k*>b/qc/‰ y(j<={L4wtA? EފjX2muNF˂q8PS]DS'µb |1>~Yf0wQ/ s۶&/q5&Ս ~QAk J DOyeCmR ;?_GXK;6EnJ4B#z "זhP> #6hc#c+:*}.a>-;ƽ)>mem~Y˨#ǝ]fq8ڍ|^} |7G@ uRСן㯜>vzlZ5߷)y!vF>HDh}Srl` $j^':>65\9ڈ@%*O M|ZFf?Fs,]6V[)NQ>ݎgh@GgΚt%C"@Bĉ,|]Ƕmtvo5qsZ) m1m >:eX#>ۊ'h͂M6rw#BRlFڅ)iǎ->ha._~|xw@dIfeHGGi[%w ޅrYcFjXB+,j$0_~{fJâR \ӦLĦ qM'`ՒYxh|p,ʧq?ZNJ,.1x4_2{nYBj珛7nql(>qQF`޸.PQPm?2rv'N>Oӻ$/o ϻ>!sf~zւG>0Ơ~.bQ5) ɭX>cw ׻+Ml/ 3x܄|>/ՖOhb\RޱU}RqGXHv:p#ܞJeCh],Ƨ ~{>1sIxx#&>ć߼6!FV|F&ln-G;߷k,8я_=wLs*a`E`+/[.هy\W#Px:lYaDT[\TJVI>}}#3f!99GI6yrQ*76)7,XazX9:YsGAz&6$hнhG6fWW:%0-زv!޿Gm_ޏ.%6?g{u%PC"@"Rm\88so6j*$eچsU4[<$y6^~<~}tuKj JDDgն \=/S&nL$Lf=D*zIU|XK ?ga!/<-[:^w6;)jLz*C_-`k܊gd?ŏs]Cjx3Z(# ܎p\'_( > Q mfقOlX,eՖWCCW IDAT!Xg@PbuMYR)Sv)K_g_I9x{ݔ~xaY+4G~&KOE>7^ > /.nm+.P#oL'9-w%Їd;NU":zX|οDGx2, l8tbհ٫,Z K%wQvU\Y8ȯ%\džǹM,F|cXd&jR)X)•#eY8ԍ9;hUmiZvuBB #7+>LO;Bㆵq2n8`ߑs8rA mYo53`*W>_@c[?>}5&髦 ذb[{9@h ϤNsʽ+#ArHKDҷ~ʴIen„̚=Nyh#_bw(nA}.s\FGVOd!'f,`:[-޿Q`:oe?BAbU6n޽3y`0~J?)@)u҉y-|5E>,k%}]$-ij4Hc,\񹗿9~K:Gxg%"}7RKbxWpcOiYTWݣ!R-e:$.ZKf}3CLFYW<q&'>'hA+7hُ%2ҷr8|LN˶0X[,hxn ._Ty,’E? ׬_#΂P~-};b;tbƄƛdA}xH ˹M,N_oo$Bh e'&w Yw0Kf=<\ǘyfXyK1DOӧ޻ߍ@r,<]'[$+Mw~GQH&GSW~g۶ Ln|wggޅښ˘&B \&σIε1/QGjGώs$Xxb\=<uurydRkb6O'p{e >v'5;f]|x<}'! ҽ&ĵN|CoS84uwu%Џ8M .}7mX_bTF&WD:W+4Q6?k*Θ7rЋM0}\fA;=qP9M;9#<F{ 7ˡcatj" :B G'>H$'4ʑf!7,_*\r^z ]ʯDr 5D0zO={~3(O hšKbق R7/B].Bt+>5^Fp9I"vq[p"˅I1±`(PGMyE/ŎC^`5chz"ّqWcǵ9--c. mCӞΔcPP~qmw~*z">M9."#`*GVyʍIsQ'֧@c ;zt w9^q:NbgQhM\?CbBiWGl+Xn+8۽SMt^ixGųmbqZ F |ʴ!:?A Hqio~[[V*kV/X\èept3,p`O,dt͂8eBT $t>Iq0yceړ7c'X}D[ά^2!j&e*ʳ<ƒ#<5G@E̸d-XV,ǜGaNöm|g̓#j)˲nZC"@bgٱfU\Ιfg27jkRxX+3yGTi%*O(c1qw ܪAXB |M)ZG[uT!1$DdD=/ \np*ʵ!r wnc@JTgM>sL@mW<|(ΜCh?m}7%0+&([ <6 ]}/@[ц(ᒩ7%S'^"06b~ںDF}>T9;b%|> g$㓷v6{h赱=trgU'1HGўsq\+"=>$~*>k1}JrT9?~-ں?”8 HLwu;N[6>U.8>V! z8RЈ)uذ|^>x |8fAaDdBL+s-aYT"3 eutgfϘe f#-oGI[jK!!@|Ʃ鐟`#Bh@eu, H!y}w3y&'LMv7D $陫wOrٶ\-رm%&ij jDI/a●‡X=~_L=R3P7R|+`4@BImo@|P:.otA/]z x>޵u ޢށ,ql^i}zD QX,!/']n +a0G}K03ojI^G{xQE+iBxW"K!nd8dVv, '$2 J5e?Hu |L>ms,:t>ܴ+(&.lo7WAgjW.I=4)Gh1Q@4tg:f~t~-IϢ/X}+Z8m_v%j"j I\CCY>Ӊ#5r2V*uj𧿱}tr:ыKAS+b۔/&v\#Ď&cu };ˌjʲ̞]ITRnDc55ocfm.sC|0sq:"s>qr^i35zc㔅mvj{qôt+(s'ɽY޿϶/Xe&G`&OĆtjYP[6.fA&% Bjo*r&5x=ѷӒUy,'-+CapwȘ6+ yПpgZ).";4_G%HH㲅3𕻶E|J5< < 4ȁrٶ?RO K2{s^|l m}j"̟=UfX= 5n8Վ0+s,"@EHHEcQB꣆!BsL*>O yE'.!2I! K.O7BkCA@_CR|Tb꧷aʤ "*ُ# @}SŒ.o7kio^U,p3^S;4aO,7ߝΑ3?n25)e2$b&.NV'PZQ񽪔p SRB:^s3hqG S%-~GGwfW%ۻ7<+/7\b$s+ކGTӟlv +7}u%Id57=`*]XDPBKnv֕9AĺZl۰Oa O Qq"~M|qc#d|24?7ͅ|=1Q (5HZt}ړeN' zIB߸pp/Mb;`Tg)}}@\G\_U8ނ ]}h-~[apqSX{) ֧uey R)l^PF!&b6 e#5RsA$;""D)X\D[Tnrr.86y7I2Tl֣8&B2VBItxNn⃢k^xl]9Kx1|aT1H'qC'i~ϴTy$2wc3XZ|lg/ `esd%:(5Aɓ&`<2y n~SDK6 qcui/BCbpPpbX66Dy_x >=4bD|TrXKKOcgGo=m7G};BY  S\ j?@y+۶ql72L76s3e\a1l@zL$YWFXEaAgѷvp N.-j+`qK\yڛml^VV_mP3LFKu,sO?{fMߙ|/X}+gw ~JD@"@!N-Y{ 6/P^߯X*#+\/nv%&FezS'N KpQ$sER,cq l~! w+kGe*C\%4Ma۶񝟽W6GeYҴ{D =[3XmWdЗĺZ\jJ 0#B&]&YJ&ccڰ??$_s~Qa5!D<ޚH ZL2t.TFjͲO^k%nxh+7q I8>OkH zH8ɽoy_(ضL.]xUk927-Eck/:zu5 ؜6?uZ ,_TMN~v2!KODEq noYSm˖[ zH|Ǽ srS 3WLS~yt?z'O"զ咵at&&R=L;N§tJedrh]¾ I]]pzV!>1# m(6~"(B|=۩TAc5"bW(k6Sp%H.fփ 3.]Cf]w "W._j 31072oA_I,{}Fa"c[̑kw̵m:"3y{,GM*M"$uf;UA0L'л;>+KHlb A|xIYZovXBD nEC|P'GY|˨x#^]})>MH,4bo;Ԍ^nܴ{75&0REXg X/ͣg LڴT `se"Ί*Btb9T"uwU s~%\> O`:F} XFl uYB 4ڠ:|xKf9O|m{*5uJQm/޷Nlm֞=%m "DT.[-nSupf29u a:l\9_@X793&S N.h)"ėóH#rR۰s`m*"˥":Efjx9!ܮ/p^|LMo~ ܼ2}Ogoӏ}[a# Ug5294cst~+czf|LX<o8byb/6?$B(>8x a!J G{ÄL'KjBCN]|P ӯA|]->mhuo 'xP 7q D >w& R5v@K mP&#غq f_2Y$ɂ9pŪxy 1vtM mzDR)X1:ڄ(BHqxyc!^F6hGcQ]]َa+vVnu^n\B刅UgS'OڊKfՍ-_Vrm4=gRzko]j[tJe3yjW,)a-\f]2ڴ.(!jY8Æ%&&}bt/B}2|6dC8=!F+Jtz[pz opay8λ_6,985K8|C}6~ԴgN} DT1nBtEyoƍ[zL민M=!c] h]X2\D"`6ijIEhi>aZhQG#m#PQ߇d–tNjtO\Syyُ/ᝣg;Mdrw6-7M0*ƅ{K3|` 3eF.W`z-+P[zN/BjkSؾi)2"[G+0`qRǰq"c:0,D8lB PgV,A2!>duɘt7֭+67q? IDAT|Wp3;t.Z;usL:b${Ykny>`L& .C*J^zfLCER,)!1c7,B<>eC+#_MmHFl?߇jsȗm{vI ~HHz;MNp/LvDXxV/'ϣP, cKd6,11D/&B߁0G EqܢE|Ҽ o-"DMS'>~ ] 4@`+'x]+m7oVD"@շ, Lsha#$< fOŶ KpCг <6̌ ?yh!c|b 8O<(mHtwM6z;z㇙MAʿ==A!>̛?k+.]ሏ_sm(F{v~G'a" FWԋ־:lCNŜSfWL\ZuBb 6̅)r#BD)O_"KeC'DCT |*Y!#-:Nk_pӭowͺ_KB[1<?. HHI]{s6hAJe p WYA)"͝k/_cM]OPT8lMG7.6"AP{}yI Sa1Bo5ރ "Ys͟\ɗkGͽ>͟?k+V,Ath_ʼnS _GkI`|  9lnj;Y> MITP:C ز~1f_2Y-#BO7/CWog;̂5-"٨dU1:aΆ0|6"Զ;$p 7':UYv 7.q5. $Otɫ8z:rmiڳu gg`Y.Bt:qŘ9}.j#@&k/_Y'p*#"D^4Bc|m E+O"%Qw+mbA|n$hJx 3`mg)>Hai݄|qW&k5H|8r}CZmټgZI+$$}{Gqw<{r`f  $D[amɒ^=[OKg{}{l렕hy%Y(`@DL==9g n ~N߽UiOfnW;KB`Kgpvx?ROiB!xƕR1*KȚ Uyl JDJ[ 7(J0vDttuaA>]}mmp?>~{4 Χ[?&.O 2=9“8(Qe r,wnf3"̕yӅzOmJH1 te4يUOٌCkSF?1PGGe JDrC&eI YOF zq{ƴF>ho>~MmVُ`p 9R>^~+KȆW!&ImOReDxRn-WHVCH0*^|ػEx5bc8wy3s{r7<邹P_}Obzvusp?00 QŐXJ8q0zP!RĒYY>SƖՂC;;Ź+ b]k1NBQq/P@$}dʉjHx(Vɕdž: YRqԒ: }hq mZA O)L̈́9 "_g2 LT=$ dBG} kLeOfpz`7V`dzܲ #,DϮVrJ/ޞX T:ِR)=~LdLM*PA +(WU2!Xx(n%0r9yXB|n8I-cj>>u 1546L'_c H@Y_YjsOpLeHeqz`ۤ-z!!nw Ê3Cn*~U/J-z?R.N,"qAKF) FyIZUL,@eXW_J<6C^Tl6ݹwN$kc80YY9+/>ym`V !d|e~}G9µL@*+4M8!,L%D B1KUUBb%!ymYUzˑbiiaY̢| DbXc]XⱶD-Wf$Zm'ُ0? Sj8O~S _`g7"L<ɯ=#!:qׁ N^T=΃YFRr|ѷe 27Ṕ^6 !Ou$[u9W//))5DCBBx| "|x K%8C4hp6 N/}$!clhR6D8BTj^uQ&V8}Eֿ4r/7nUH>{8V?“B` !#0A\PpR,h 熂8wNBYɋ{nT,@xP5JrqfCUHDJ-mJDJXlNvuɇa÷nǧ?Wg>iIz'/L`hxB ?AD CBhBd ;tNSS8?;F> X8n-{09Z'WBޞWl\iW-5v>,JG~_RQ~JfTWXW U>==ͦH, $dP$dKu!bۈb1;Ո -"(]XU֣Ï<ۭVPc~rvt՗a)(;oKW+0L=6_[UVJ*I!]&sA2*to|8O iǗ?uI][ֳ_ᩰ%R|r,^UTDBU\E٨[t dGnÎ͍'ZsCs;C FF^ϏŃÏ̟+%bexk3 K,XXd؉6gý8Pt:Rg󸰣Ib̂pZ<@7Z} Md6SQB/ClƗeىԶk6XoQQlZ](QGYdr87?uV,`g]ᥜlȺz"Rl#7+"<Oү'Vr`/vGemEػv`'H/ ENbPȾ4_KNH.^߬ke$"hh !R[ʵ= ,d>}z 0ũyiBs-gmfA_2M^u=~̅_}: )Fʹr̆'rC +)K'Xw# Y):gG11B!c_B.4Rr]Af R3b$%Hves >a#A }%dRِu!}Ulb $[k,G>_9ug7,XJH\b+!gPfI 7HltD6*`'a)SPN<,;p,߯T&o<>^~{Sds %4ڟ~w]D$!lH}-Smlt]}38~uvUBB#/LJqKHFS;d?4b{qGv+͂vwýh;Z|L9/N`tr^X*<.MԱz}Y9F*C>G~}iw8hÄzzZŏ݄[\5I"4^o7([Ȇ Rn[ֆ@[\LC B90ʵ+"ξģ6]թp6,4g'šI,,FD 32?_jvDT@V^7}u`.1b6w6<ۄ$D,|+'FWSY )7l`_""\訋?<ؒ%Gqw5cU}W ;aJ5yT>r9<{2Jo|~3 s>եB-E% !P& -2EZ_q6c1qhok۟m -6e_]wqz`o"_BgC6m IDAT4\k\m-k Q9c,'$a߶}ۻDϫP* 1N1L̡Wo/μ+r][Ҷ,)fR1v+ =Zzv?U۳AmYc),JF䎷q!oIbj-{77IxRGq|Ӹ<4H ȧ3~ + !%r ݶ[yחwx*! (nل߶ Cp9EK x 85*K| ؇q6D ""0( `9eK@9j6 mBɯA*Sϟ bhxɔ[x2'~OCAj*d 1&z|Zb8C[Kvtw>y+z;BSҧ1qjoB<-5H6RC줾kYfWc_7oB{cmL>;/O`|r^BfO|bIq^*."%)#$Z@p](V+-:سomF[6%r~fgOdsȫDe2MZoa"& Q tY |bU-?˽Ưqk@[x=.t7⁛rлm= T<_}j䊈!k ,PRel0QղZT?{wk3i۩}3jOfፓExi.;_ bCRTH5ˇx>oOW!~LX8ܸ{1·Xő ..`t:+{>ZDJg*#eJj¼'d 7q^l847o߉_o\+aa0<0]B:("Y (kԴ+CzpڭVv?t#z8 q39<%&[{{4Iy\T g,HBAv'6ncoXa=w-0sv:A޽Ia Y?(L12LsՃLED8\mW Dt,u[f ǡvUlҡq"VN^?12>tzԀg?Ⱥ*=%$b$57Zʇxvzs_?UbLֈ[wDsŰZ>2<&QE1>&1$R5Įi=h]MRx\vt4yAW]->t6{Y΋P1?`8~,޹09k\|3ǿ\r1\TMzIOA J\k nh6o_,Ncphm`sGg7}l5x qLc^a&bXJ{D~-7=hу&/m2)b#w16qn e^rր|8C I}@ery/&0N`!\8" ciJ,TyHb3lV ^'uh p9PրM:hTbT y cxldZyO/4YjaLg{/34{Av}[>%bG~uM,ݲރn׃O?>6$AU\pEi,.%Mc1Dh)h xK4dK=N>d`sڭ{hCDυ 'c6?x'/Natr =痒s6{WArwzq8lhkDog#>~^}bX_ d9<:(%TϾ5߽&&ds%Z47>N]: JF-vcmaQom/܍.F5;=G܅G 5>¼G&'๷1>ԂX!Xxq6^QAk=( b2zm@Ymw-VVvRr-F<ݸP/VM]HwC ̅x~j TcSϭLϡ67U, k98Vsl^[M g}gq]ho `[W >z.ܾD ñ^><3gDS,i{gL&2O}'/jJi)KFM0 5)Jv|KV@?y]hkmMMxp#c"$dzo bxrA,̭^/c'%SsaO۰j>Z_I="uOnզ;|:eS{<D mo bxjS GtB6stuP2/|lJbXQy]-ݯ8}c[x=uho wsc> { V<Џ12TP7y>N>|$Rֱ``]"`ز|q_o ^OZрn7mU=ЖOfrbӋY@dI|zş|-:ԪbZk7KV`]UMeǏx:but S\NcSنoݎ:)GZ<x0^{oS^@<]V(3SS<+E7 !=-+U2T`]%AO1@\,`szl@/sW` Mp:lhnŏ÷@gOh }{Vcj>[g'05R~*O}ttDhaoyDEk|( 9WalUY״i#z[> 6M~6No.0fX,4=hiǶf>+!.b~!|A5\>+?1(Z\Kfl1ۨ {̳bvWS|:4}{ZB!@(ѓ#x(fØ[D8|¹+SO?J˼&!!CYC[=P:ʱm6K뾏[ײS.FS\N:qV۳+#exgQ|SH&zO |>L]9|b(oڂ, qaôغtZ}agӖs[@[MZ-h CwG=wEשT>( H$OOSc cv>0r9i|! =FX|,TZ<&W˾'sYz\hnы#pߍ[j"UDy <#AN\A0L0RBx>,pIV+ua$(Mnl񡅽v)Evlۼ97=cYCsS=6puj)BD-Iؙq;3H1 ="|!L>gÓKj+8|-RJ< Fu^J8v]@u}m}gSq:46`7f{z̡P 850c("8T\t/kj~0m3Jbi[jm ^AJ$>f}("6@QDښ|mfy[:*Ї)afF8vf??7م(CCX\66j'\UUۚV:%Uk91cKflxZk/:ؚ@sc=^l܄]y:}f!Os8vf ÓEᘝ#a;Y(ӯ/Dp },$J碵(k90VRc)خ͍;_ۇ&"|4h x{n۷wu4L Jd:.O8{eP%#1Q6Y-Lt|?_xVj{jeXV_kQ"BHsr2fl%U͂~]~m8D45ܶo3VEECЛl3Cx$ b6|h pYEx%|,g4w¨İ\r_G[9pB qyw󠧣w6n҇3B&GXggf YJU@!Nnig* j3sFAI5窅(*@8aԿ&&E\-} YXI+EinbWo3ڂڰ^)>k &p~xc`l#q,c{NG9B>L<|ܺj5^k傞u<ΨBEf>ޤ^#4G懶73U!!V߽ӱ^f*8ι,$.x.v4@w=m~tףt%Ai̇`lv K cq)T&X,X,h,X"e[㊅ llxbOC?䍘0JLFBCSw<SL|c+mw[%gjV+u\xvnݏM>tCjƨhr:GQהZHz+'>, q}z=ڛKĬ:l6 E!q9t9PtF,RzaċI#Ma.\(ps@2C:E*A*A"F"A2FJBrP&:v|ENA XHШYӆC| 15%A :d'VqeZݱqvO͜fGˁtrr:ttpCn4ֻp#uuvnHE'#`)F8B$$)FdrHdHH3HH3Herǎ |!ͽ>Vxg|f j,a5ދxbjI *BR}Y70Wƕܱ[#Dqp8mp:lp:lp8mpۭ۬p{]8s;uSee{uʌD"E"E|//M#A8B2E6G&C6C&C&S_:)΢P!φw㳗Gzllr۰gv1Ȭ(G ARY(!FCjEal+[nQױ&f>PkV vl6+6v+lV+6K_6V[ܶ\<.;6 \6 6++l \N,:}CY  H39dsdsy$9 <,6JZռ-PȍC>pHj2dB&<~:6~dra(\T:OM*YGo!  HcLE5IZqzX% 1~]Z5ZD-Rȧ™Tptd|*Y*׍afzH}-gJ@R}Ab1o}XvBI|Ycx7u{9.1A'td\b3f Gr""-KJ<RqYfg2`chS˸b}JD|%1זY}u-;ڼ-{aHxPHǮdcgGȗk JBl[Y1}̮h׌Y  HuL[-KIOC8ʬ禝vOn sLJ66!8;q9 O8J.SeCL-+zZq&$&61bި8FmfPnux\[ؼͻlN.p:Nh_l|R*8t!:yr(DT*RM:c)^z 6 bca0B^ŸEDn)c1˭Vuոm۶Yue@(R3Dd0L$N"Mr1QIl[VmUK+Rslb&S3t284L>!v#1m)qHضjѳ+hģ !!ʡ_.Ul)^Y)YZH*Q"4%}-nwNVWٽV{]3gs4ZFjK,K|.*R|6,db\r)&B3|a%"jeC*QTR _L;5fV@ABBۥa5XF>trq}c!'mi56rw XlzQYXV_Z\]B>Tg…\fK/DzH(O- xFdT)#dCĴc`uz Ka)V$.R0[n)c1$BN?YIiǪMvOk|^8mm9̓qVg,6q guq6_!x ||!U3|!|69>IlO%TI x"FsɥD.6eԾZeCĴJ6S+ˡ4E1}I<BBȁDDZl񥌩b&$rcdHiFL}akxԭMJDXfCĶJDSCDgJLljbU !ۚ "r(kS,ƒZ$a`U_3b4҇h\8Zdev(evXDCB(^,Y lm:>jm4롆[k F,#Ruj뱌aaKo!jf%㱎z dx,Q[6RiZzҹܶJEu\dXe)$H@)",۰,rJuRڱz$z.Hzlv,eC>jnR[DzѲ+D CBYKԊVvE[XKzmzjȒXȉu6Dl[DD<|Ɩ/V"$TԈPVaYjde}ʈH]`! BTHiZ4ԕW+!5Zcm\fEW!!BRհ}E Vb)%\f%xc*~pH"bm\FE !@Zl[FXG-SJk)Jz7+JAZgEK-FrZquCi| 'ZokIBº6ZɇY5֋"#zfCĴa35Cj[)ZvΦDYH@#,%eF>%B"ZR"^Y@Oiv6Dl;eU9 PuLRhha=eDjVBGFz!f5?fVc5bxjoKvB2$ QB C-a5TWLfzM:J+}XfCgPkL=CJ|m2c,H@3Y͸uJN5DlI<ԅBH)}ԬW=LrjdX\YA(0F:zz >RhR"XB5Kz,QK4%,JttABj:j1ՙ !@,$l;#"'VDXR6)U/)?m"tIF́U|BGԶB[FkfXd>!-6bY6XhIH:]!! PsrʕU #Ʌ:uJN{Ĵe(W=r< Ah Q)#,b-$,ŶCL+飕|)fRZZmŴW^KِڇevEs&J# L Q+}nCB"fJ96J-aF(W'gg QҟU~jɉZI:CCB"zˈ8Z ΃E YFEZd=XHXDj!"f>f_I6BcH@˜F|,=3f9;6-5K]A A6FF}ՈCEg|W6DJ{#V'`!Y#lYmj>Z$ެw2A !!B pW+s [D%~zߚ * hV&zˆXXJjyD j Fg;XǡgHCŬ"$QC7zf=p]j(h5x! at00kt}&`?GDUGjDwLuUp_ZO@"gJ%pH-jQ/uR#ei&C I J AK S U Z \ W SV%J$W #B)H)_$X7T:(G+1!+"-5'C6@2N?VJWGgYoa*ڀSnjMڒkڥtycJ9o3.00H|ֽOde9.0n9B#bOcI읒3W99@{zEսm5Wӹ ꞟ 8y>$$ ܸN,3 A =$ԽJNԼ[,09?$?! ڿOO^I,?!yy<79 JܿI_1ꥧ0 Gy<44<)PѼgep3./ / r4͹Jf5,Yr:174-}?&^c/롏9lLe/1r4/;y jܽ`66nYZ-,]4vUȾ_^mXYl5X_tI̾feIalnZhܽeþƾ__JJMXhQdKhReeOOk̹e#abaafteV>y"IO\KbuV+z?!x(D?fV'D% iT*s)>wF???././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/media-floppy.png0000644000175000017500000010174514332463747022126 0ustar00wattswattsPNG  IHDR  ph IDATx[e}j^lM7(i$G f`Ay H0o oA & OLddA+iY$[6%QE*RfٗpT:/Z"^ZN7@~׏޴sSFv>!3y\2;eDNFP#2y#rAD^|Cuӥ?~/]^i ~m&EfCD ]1[yBJ|y~"DlUȣf}lyٙl ܽ)0܍䰙{~])" l~dC=R-X~7?1;~Jrd6{B̌r[u=W/\y?Dl%*1g"&w[DnO[n8ziyֳ?fV73_?gߎ9l6;n?%}r'Ƒ1e,kҟF%;; TQع#"bFiQK{~E\ܱ/˿jWbH4K䢈y5yW_fc|_fT|?yw߻7~ʻul:d5ƹz7TuMs/- пp*.t1cҾҌ>s__/jCk\O <,";՗_GG?qERȯƻ?:}D66"5#94P؋W.0_q|ȴw߹ȯsil)juVv>s.j;0Ow+k;;GN WȻrc!|f?}=%t (y..)jQbt/0}4w.n$:>; xGȇ?A;Kaơk("U%o5e1?? fkx%ε >8TFv5̤51{84ΓϿ("Nx}݋G5zs;~c@NyS\\԰vVG_Q0OGن7t{O>?9q{(~^zbz*q^@5| ;u=uv}|CׅvE鎣|}r{a"LRj \KEtY:|ñd;~ۇu+PBڿ^iQ߹ůscׅn*tLy]X>υ%PO >zTCH@8iNӤ %}׬į{C _¾#\dzůsϷFvCG1NyG߻ڻ_Wv/+φq{+[[~!9tNwB/2vLP@Ҿ*S)q!ZZ*u!N S0f3y9O?&tT.XϿ[v(ϝgN=0 :#/NSM8D}{'/8>{is_g>7"-~C)9u)qo*'E2"\9+Mqx'y)!x8_NS4]ыcLJx/V׹sq_g>w}[<Ê=Jyp~1ر7gUݫKİq7cqic1(~#5Ϳ(E}E~vY\=JyXpOY=;d]^혢߹3.KZFG$+9G/O LO?:&)b1}a5혢@1onGt}.{s1Y p+,t,Ju+5/FE\Lƍ ;un|v~߹nGψsБGq#EBJ.n%XM3ׅ;υw;'x"X=_φ민>I.)KPXqş ̈JpluyZ4u= 9 J<ܵ p=ԛr̶۱6iZ:[wL=M3&j.ߔW]y5XT-mnx}~Yg\VWD?ϻ] @{?GY '(tN1:<|%#^qBܿ7҄uBYD[rU?nI'p6+_RnGN?^$A@:@r. ,@<ہ1+ @sr5XDC^3hTn&C9 xs/E#[UOێ~Ƃi.x>0-} rͅF:z@\<( v$EZ( GbPX0xd`DY:F7 ĉ@{ "kotnG0ȪMW3ph N<Vg;CDkQy+y;t~{ecc3x;ZN}?Bg #[~#"dX5%xDC5=x- :,^nk 떃e[c`@zL{TQ!m "I1H!tdW&@v-d #E hyBpGMeQ:W`Dm4ݎ8 Ko3E'0CeQ:^mG<ofƚS 8!tb +^YnZEEŚe@ ?2(ܚ]NMb$zͭY?b CAI TjnvX5Ƃ1ⵊ*O1P'S] :BGr@#x-v`$B'!{@";zvTFO@FBG)kCq{(XX@~`\ CcY'Nxŋى'ӹXh,t,j.l51Gx]X;rvD1| q vV[EB.cz4uRHBG@)BsriȒ#o %xtρY7 \(E؝a@֌.+x^5 ȊCF_a] - @\@:'t,".Vsxpz&xD t\TG r.4h9t8F 4ܵN92~KGiU:XC\\V JW׭$!v;&~ЁaHy#|8'"ͩ9xYD 0tk S9Sݺ@< vCG2N8bi]c9M⦪d4 (qLb<:@c&  N<#v;&) ^`D4y th #}!:$(PVC@:&) u1xf4b*Wn{iL. Sk,t iq2A,/`&tCHZwh/x1h  x t4:k`Bv42Б.h9$`@c a rK] Ko^8PHRi$ B= HvpB@<:  Gu!8G@FCWu~H'~xPN. ~Gȷ~4@B`B-v;i9x:@v3Qhl::) apОkHLAon(_cJnGؙÉ@{ DƂ'i=t`a x$ "t#I]T&a?5 ʐ:CǢ4S!"x<i@\:z:DXD3rM5(T9x >HAu#tW4Um@Sɺid<\|Q*Hn!xNW;O Z@V۱>8h9t<,@F aN<r`ҋݎuRCF',t,IY7txrmPDzc TPCǁ %t#x: āA:A\Mj.!:&́ghB\0* cr1ά:_МXq"{GPt-d1v:rmэ B:!X``cBu! tx9BǢLuA91sۑn +H^oEkvó6˥n=ЀݎDuAI<kB k't!x x&xj "X ~1mNM|/8x>b"`ah6v`::ԾhZAY~Lz'I Rxu+!Ah ҹ+>@ _qn& l1v;`2!pDZ  ~H9  ւDzq@b:@*Eqۨ@mZGnG@,,w S:Dt@ւGJ0;(9xh<VmYb   a 41A>,vE8<ͻ<~ Q<AkGHTF|'b8' ѸnɶRH !'C'0Ї5Sa "ti}MG .BoW"x5fabݎ$uV uYG }pd]@0&$x(<9hPier *#@+k /sU !<J+:@<*ռQ̼!jEqfNZ{ 낃FY !t T!sFɀ4P<($훪:@j(jOy|j(l5 4:K^Z"p&9A4<wDcdyjX-MFYۑ$X-KCeu:z:`2&11xp Оւ# C#th-t&<{ioAp+Qzݰ}} ζ N(n C ] v;ioA/˱QzP} ָ#t_7ش8k ޢVae[nka}?htDvX\,JsaIAe 8xSx=Fg:-y  L:6{dXݰ\XR4Guʶ3Xn1C_|e SmdblƳ:hpcLJcsqAu nnBy!PG.Kv ’ʞ\V?`Bkq}?ht&2X=<./:\XRQssR}uʶx/P4:xsx ^0H.,)Eae[; &tw&r1\dǰ8To~;zR#lkBG a=eO)bZ$6Ήb߲ C "C6r7Yf%c?ܗ6TMZ7Dxs~1~qT`}If/_BWv!l<ł@ƌãHFnx=eWvx:j̎l埤)%txL44`=v!<Ųcuam ]lv;& vL/v/ж%2sxOE:A<X=A@Va?T%sf拝kM+v;Z=ٰf)BӛlDݎtcFOׁŘqN=ّ˗/ܹsr… Ff?lKb8g\ C oλ2@rn=~y:f3;gFP8],Цcʹy:vmqАsŋ3O?-oV,'hN9wǔs\irz1 y{+><}gf[6.=+{`@v$n%BG:=91}ԩS~VnjZ]uʹK~y>~۬J!ܔO}Sriyd>N}"6:Dv;@FFmy>?s'n|P;&>lo>N;WevGbN|X>Sf-=׫o{8s&7tޟ5-Ra)kzx衇^^z|,: N`YvR$(+ {^Ÿ_|Ad~=x3gȽ+"b#>~>|syd3ۋ!l&܊55yU`ݐa'{xl6gjinG٠w;>~g<#$-=׫o"zᦛnzHZ_SEb#dnL=[ԝ_~%/'Oh6oibڞ5-cAnЇ>$O=\q5:=j0 v;< .7t0s\p{wo)s6yAB.r%cz8t?g9BG9CGme"D`\ ҂5<o6z1GM=u] g?OH5tں0g ڠ^<w|KsW_˗/˥Kիq~EOS}9~?~\N:%s;v,h^M`fEcO766o>sEff[ljtu @ XclPYB}uv@VH+͞/s='?\tif39sr}ɭ޺q.wyc۬j1(;#,~A𭄎dC=ϭ# :DNe'{=lἹ)'N}\-͞3ȟ˗Ƿ#/կ~Ung?+wyg5s=vowe-~ C Z@:a?б*b+m}?mV(ah1a5t8͝$x86l6\hm,|pc{L1#t>͊j<5#E]G:%#ǝAp*L-6Wľկʉ'J>o*aczC4*Ä둄oCñp2%2q7?gG}TnV9scS[{A_F9<0,#`G huÈ1YL> 6Y^~3yH >_~|h:wg}Vx#mη5:|>o;nhl׎Gw>\kkL\榇rzH xI X= uC<@1(\pL'Ccfm׿>8wE׾&E۬\:bQSQ9nHϡa·J|`BP)k'xB~~wy[ro*ysЃ !-<@\:K 1Cׯ_w}d4[/L>BbAsS:< e 64аO?-W\'妛n} Y_C5^Q<! C c4fô駟?9n=ƹϭ1t,<҅PaCmV}t钼룽`z>:~E"=C9>㺦1x:Uy' r n,t,؆#vR^|_ٖȬw7FDvD<ڔ/cdcƸv }*=C ͅڹǍW#dBp*?4S.no9:mr;_C43۲qE9t23׃o:YCi=Ęc5dXe6ocȡKOs"n*ez(i:!LDbxQV_\zՐK.=lyj! GAUMHfwdEa:XDu S ?n=Ęc&GHR7T@zYֺ=%fEc6@zI< Jdx<׃Rh-D(IU .;7-kDk/ uǔ[c̍\]aH[kh]GAUȂ2Ս۬HzHу7#^(BG9Be?:v;܆6V7~ :b)aR1=LFx+x%!,eaJ(n,o"6h%cziyfW:ЉAб(?1fcXGyJXi!FS  n;x"x!$nGuVmV]JX4=ĘOqf# Bۡ4fE ѡEbbAC6~Tˎ@&nGuS"W˹WHBG˦ww:"j!S…j da5]urn%,Rc V[HO>w; Y: V6mݮ<!հHza̅b(!?.Xh$tt)6+~/^-.Cǘ2Ǵ zCkE#p1t,JxC@{V tŸ/v; b0h %%fE&$ԼH,sC=QBzJw;ױ( $͑p1t,Jxx\%,-TK]HOݎ)nXYg۬*aVC1标{7G{Б񭎊Ce:,3[ccQZI<ͼ~1`?Vh0m1@PRۄ 7bq~S Cl\w-Y!T݊PN1(TGuc2IbG7Hݎi7&t$,u:{A"޵ qU"xtNPWmV@zz1G-=F(.#- v;icHɷY:zDzhs=.ֵQ~_!v$QnYH"!%{JG9B1uBY;f1Бp򒹂ǔ;)ȯ=Ѓ|#E#Fnc5 @Vv;Rr Q 4zzHnaw$t!rP#t:XmV2徵Ebc:BG9~Y A1EHUh6V S,!G.e |E?r@sC]=AHQ7n#qz< ؆ nB(c[skĖz1==.,e(#7Țy&: ؆+YL"q9!t >_S">D"XB Wǔ;)(G 4zpczHC.C%x #؊nSaAuC㷠#hPO1樥:\:g@Ɣۑ%xġ6+IJXCbAK #] 0]3J5:'h3xļ͊QhPn1hj< `c|hP#!ϵ+T!HРy.6+E}-Dz(sC 4ßgpq DYET7jG۬J N 4z(s' v < FmVlv/類b)_ i:GIБfp<)ƻ`0A9Z\$=LC,4S_=G\*@&FH3ݎ!1nZp%%czC9b< fC'H?#` C$]rQ~JX=h?g^S:Gkv\OWe wHAu vEl6;>~wcU}A]%rz.'!BHT7՗۬\{@9Z\$C9o|mOCeݎ45x|%,!Pn::!:Dz|A@CJXC=Ę,IH5?\6@dY g8J^\_ 4z=Ęcm?0Kc0Hjv$~uc}/aQ@ѡ=LC9m|T%~G1 ^38\KSȧE=Ęar\ .cq\B@,y7ݎBԲ@C9JXCbA~Мqo#8"t,QY~JX=<8\G`^ $`/<5S_(aFz{1G=Da(^xq 8x.6JX,!I-Dzs҃#DjW+aFA3-VCGakú@8Bw;"K=E!DhP1ô^[ \>G@v x:ם:g ogm?~E"=C9έp k7KCb8#Iݡy-BEQmsa^ ~ #4BG #MxP~1(+F0k>ì?  *]$"կIDz){H*a5x{8} +}v t炌J8J!@u't]C֎zTCçm ]+a*S"QS1标{Ne@sz=%,!N1标x=FHW<7K9#x}ݎJOuֱ8BYVV.!m1CAGQ>ߤ[9/H=J׵HD9Z_$=}|ʿG tU~'pA cE}LCbZ<.n8AJ^r@zC9h+tn9Q ~hf3U0H*aN]./ưb"a&$t-n@<4@cl DEbI=Ę!~hcu[DD(nZ 4Z=Ę!~hk2Q~]MeK^r@8=ĘHEQs],@::ʯl-#=!0<^zda i$n(x,_<5.Pcz(<ճd W6n%,br@}=ĘE. +v2cD̶"`lȾZvxd9衍STY O˜5 ; 2nǐC6{N_8/׮;=~2^⟊u0Nl1s-n/rz(*ñ,+?.<"9mHrVd۱nᇷ6G/,Zz^tJ\pQ9uꔼƭZ"]ze]s7x~&Brۭ&= W\zC|jd衇6ox^\LUEٳg mll߾gDM=ĘA+BC٬`=1QnH귣 ?W~zN~[T#v=!=%G=caemƱ#|}/Ba?w]]pa9cs=rၿ)vwVcuBc\ =Ԫ.A\uC6|gkN:j$Vs#O=c#S911KՎ i뻙ȓO>;/3>qdcbAUetAe^!9"wCR!d#rM,B>/O>皃k!}JI¾a𽒠4xWý+Gb*w@ IDATuׁr<|{\=,?Wr.sڃVa%x!]Ia>߉jƞǍ>߾ "B]2fBo罃GE96QYn '(7L:Dt,\zܔzh]Çٳ"R"z;KV4,_.Aop<sU;aS٧[gg~1Ck.67frݧ乗ޔW)٢љ;eqys}3#mݶ{={3#Y"7  H %BB ~P9}$QBfv{}^ZrNխg4{9׵;ѝ;w|ы/uGd.1b%尘AR&y{z 2—dI[a$D:۵Emo}DD(BO} ~谕 ^|EjZ4>>;y wޮvDĀLjDƖgy q]~Xr:k{wS^~=Zܼ'DTR D͸l ik; ,:K}Iۺ1+42^v<=>;b@ 1@|Erh<S}%YKֹ57[XXXk7G#fen~={raڳ;]``2-Nst賟lW x<#us1ob+E owwo,Mm\IL3 |>OV[Ib^oPꉝ0xQ O[ܢ#=!.B,lU߀8>on[{տc"ckrk{nj6n:u>|Bg}bQ:-..?H}Jc?>_4D{{WlNx5g̃ {v{24yM1} ~~_j 1V{|_|Doу?2D6i~a%bbzt8Cv1:z(8MNNRNxGCCC422Bq2&>Cbj]!:->[Etp:Ҟde: %>ąD"/ }}/}EB<|lo\O{޷O/>GcW~'$oODZߩ{OkIos<ɂ /MEL\z1PWy. !Gp4E<6cT!>¯مRkJD<~G?h#!٧x;,dAVʖ$">P<.Vtohj 6yrZά3J-D[yjf?3kyD$bGyoĀtb]Ag.~Ըۙ*Ûut-"צU;mTpyo-?#!cO'靫7DnyDGѡaC#P$1T'*_T1#r%GDTLQ ˷2Y!+Juc25Co_(B^:8o;2xNx|M9p91A oP\ z qt%SrUDjÇy[gͧ""drzn\%BOK8΃Ρ{8lF؟tN<|DP}.Ơ3WbXtl/lcU,2̫Cޏq?߳ '*!B J˱Hs{=jr>N:65>w)I3b^ &>g^@H+/l^DD䵼J'DJ,D¯{>:sfaNFLѵLyDoqrynt!a7ovXU#AC 5 qnё1Jè"҈k30c"c?=svan%Dl_+BRDΉ 9vXVrI"b@ 1d.Ġ3W z\CƸ nExHƒm)VJ_sWU(c?٣?E3|3_l]:O}iϾў~D':06{wѽJcӮ@O>޸N`Lvz%pSڟ8뵎_Hcٳ Rb9\E/VsAXI086k%,^W ]#Gcؾcyb@ YAHZbtm뺜]t/mGr ک~:?[KmBǯ/2gϷfv<EHX0wN<3X1'@sI6o#b\z1ծ#:z >Q'x8EG05n 2gH yN^eZOn!y#:3I'b@P;v-Ey1PWy1}&1j':kCf XOcfIC!!!gѼׅ*v$ V!󭙇-!B[ t¹n9DP=g8Hty|%G>I+߈1jjW\d/RQa{&_L3/^DjC Vɭb"Цt÷%b@T59P1CtB (]bE>w3\t?iocƈuʑvBu$X_~u1rNڡ<~ޒ'G'eA3G n(]ۺK05|~:~lg7K\5"Ց/! m)B1:xO>1; (krxc\A7b0k[u)/ѡvODIqf@EGXx?0%%N*cY.O!V%w;l~W8yDyGbrrCtr)(rIHMjQ/g;D9ʪk+ǡts'SB˴޾xy~߈ 93EB'3XS蠐08:I"bp=*ץ%:Ԯ)Htęjb.:ƒPJsG ;>-/cY\k4[l 2q_/Oߓ!tMA:XL IX+l9'GI\MCbtR Q落΄Eٮibll= --iZͧ7ÿPDgWU,Ǻti~Jť%zMO*Ǻ%)isݲ:$K]&T#$1d.Ġ3Ȋ"7(++P 1tˊ#:;>NMRgn Dޠvtt ߜziXY \Ir']mw\!CO YYY˟~UBxDƦη|rTujlơH&h:1Q1'J 099?K aVWaye[KZ6| 6#:}:KԦ4[SD>s֖ZiͬCU <"rJתmy9qO+C_`!@S۴KԆ.ߙ=EoĽlnv"&iZգՓqw^iNGY_a̿U}=tY&%}.e?6ks!@޺PgFIyWO2m'^6?B:~#Tb{2yIcDHdeq_'߸)8Eav}n߳Q"Y_yG8έH; r BQv{敯 g~T`!B~gb_^l>j$D2.L B3O> NEPQ:)&>~hO!~1>t:kg+O+:ōN+;c/f7ÇND@Y]iG~uq]@.T@o?&]nlTB B*﯏-L=;T~h0LDIU")fn%1PUե?y%&NVq"ABl- o著~ki ;OWOOL &Ca# rCĆˏ6Y~29rWwȝS۴0.ߙ=TNg}Ź'{DN;^K'K_s ;8'?cFop,a47|P"Uw =gHܷ㡫z}:iz2Һߚl!rW$"FlӗGxIǪ,Jm^s%I@$6{Al!v-h \(!ږ~Lm}f')+?Ɏc:m\v'T%-οT$ |0˿j+6~ U *G/RU1BIc* mna+4rz]%$7):Kxd1cp;$1S%NKM;Us?i,FLm$Bqi6FRHT?8 p:_X$ YI|ebd)<¯G8l1S5Y.QQD=`MwMӀ@Q$.:yOvV_MkB!i+V>k[Pi߅*Gk"%Y*Im>JFVޒՖ*ҚsjX>c N&%&>SᑴJGpind%BDGX_TW+!Frq hM$=ڟ* D%ϰc*<"WGuGj8amDMz-o:5W_vRJ_vYPr8X wuDFs4~]H !wdHӥX:#͖)J"Y"_7!%NL* ,msI_-DtT{i$5^_ܶ꘴v4;HaSJbUDj[]?ӱxE n&9ƙl봩IV'ਊ@+DR"LuMǙPes 5cR6]*IDuF@V@La%HD  Dիr q֘ z jҫ* Tp.*=!icR4klQI5C򞏼+!@ HK\'Ĵ= DbaVdUi!&tt)KD]""pz {>X~B@~DeKbCu  RKtlڌIjKk3 $)w@VE"DD D6cl @ I|M+i}.T@y q$Qi#W ; *cxOHt[Z䂔!r_xp >1&@@!w)V]HnH *  !*<K[ˬ|IDAT 1Mx"V\{H!@ {BTƘ G@Q$tUO v~yh#C8XImNP e#ϥW@ Hb)r8\ D[萪R@@ɛUD6i!,. =y y R܄GStOHZJR5(ҫ,$$i.ܠ^i*@¸]S!P$\bqT?tւ:0bĦOv;}HW@*cn$r U?@ gb.D>E*@G9+" &t_< O  k2uH:eYzeHbZu)12ѣ'_IT*P$R"bAp0b瓯x $k iΓ  y,+XIloB\O?>]IENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/mirror.png0000644000175000017500000000053214332463747021042 0ustar00wattswattsPNG  IHDR szz!IDATX͒0 EvAHIK'tDJd#$YQ,e9 vR'/{dR5ߺRB.5 !@OH ӐrC6 {5 uH'Rjw2ܐI cwFգH p1%$1D@P(FQ Q_V_ IxS4D7 y ^ 2f[@犘R/HR{w(Po*rVc`VD\LIENDB`././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/images/open-sl.png0000644000175000017500000000031714332463747021106 0ustar00wattswattsPNG  IHDRh6tRNSn pHYs  oIDATxcdp\3˃LHDsi ,D&I,h.Y,D% ܻz4 pmÃ{DiX?6\[$%. from wx.lib.embeddedimage import PyEmbeddedImage logo_2l_32 = PyEmbeddedImage( "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") getlogo_2l_32Data = logo_2l_32.GetData getlogo_2l_32Image = logo_2l_32.GetImage getlogo_2l_32Bitmap = logo_2l_32.GetBitmap getlogo_2l_32Icon = logo_2l_32.GetIcon Mantis_logo_about = PyEmbeddedImage( "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") getMantis_logo_aboutData = Mantis_logo_about.GetData getMantis_logo_aboutImage = Mantis_logo_about.GetImage getMantis_logo_aboutBitmap = Mantis_logo_about.GetBitmap #----------------------------------------------------------------------- Mantis_logo_splash = PyEmbeddedImage( "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" "0N3Li7pibT3Pq//MA/f+7X2emDn7wjnMcMjYuCWsaRwbYPOEZO0iUWsHatcx0iK3v/irv/W7" "zz998akr5EqxPfvwj4YI07yg4kv2HLqeREQIN2DL91ldWaVWDUn6W9RFwkRFMDk9ztTBgzz6" "pa/Q3jHLwdvvYGWQcvrcGVbW11DWkKYZLT9gutW+LAzgeh6O06Kz1GPtv30OfyqgcDTJsI+1" "kiyPSZOEjdUVgmd8ZnbvpTE+gQW8MEBYh95aH5NpkiJFeBWsE9DvXKI5NY1tRGT9Hmeee5zG" "zA5U0uO9d97EmdNLbMUVjG3iux616hY3H5nlhvvvwlabrD38Vc6FHZ53uyzIFOUEKD+g0lAk" "gyFZlmCNxGYFlUqAH3nbJQ6sgMBBKolSAisFbqhI+n2S4QjH96iPtxmvtxCuw3gyZOGhh/h3" "jz3K7W+/j9ve+R6mds9z3R11JnfMcfr4CV586inWVlbY7PXJdU53a8igO6RS8QkCj1ojJIhc" "lPIQl0VLWI0S7vbJ1AarCxASKSR5kWCQeE6NqYk9WCtYCddZX+6QDCXScTFGY6XCOh4Chcly" "rNGYQlMUBSBJkozAB9dRIF3cICKyFo3DyI3J05Q8HZJlQ5AWL6ziV6pI5eEoF2tglHQosgQh" "fbASayArUpQKyI0muyxceT6kyHKMGKKAY/tDjuy9ha30fp59aZXPP/QkC0sr5bdQSUnJm45S" "sN4guXrn4Yk7/taPHvsX1Zq8Lc63Z5xhLX5UZ2xyD2bcdv7B73zkn/z6P/rEvyDTFogu/7x8" "BcnKdzbqh99zdPytGSlnVxOyrSHUawgc7CgHKTDdnHSwgDe9ixeeef7sZz/9+CeviIi9HA37" "uiFCa0FIxdzBA8RGMRrETIym6OeGzZV18kZOxbMc2LXdnDlttLj5fd9DNLHz/8/ef4dZmp/l" "uej9S19YuWJXd1fnOEETJM1olEaJjEnGIEccsLf3Bp/tg48jGGMbYwwXNviyDdh7E2xMDiaD" "kECgMNJocu6ezrGqK65a6Uu/cP74aqRWq2c0+Hj7YKj3uuqqqtVdq1b4Vn3Pet/nvR+KKOb3" "P/ZhVleW8TZgg2cyHrJ/b5dEKqSoxZUQCuEhSlLy0YitCzewaUZzbpYQPAFNUZU0WlMUlePy" "uSvMT3KanRQhIpLmFN35DvnIIjNDMZiAVujOLLnPqLQn7UaMx6usP3Wd3YeOMtNqcGQ2onKa" "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" "49TvAfFtBJR4HYLrdgLrZmF169efVYOiQLVbmLYhNKdxpsnM3B7y0rG2fINGq0Wz0yRtpaxc" "Kbj6wsvMzHaY/uqvJ56aI9vq0792hXIwoLl4gCqv8GWOFHXTrIaoJqgopSxtvTFHYNMHqkEf" "zp9n/uhh3vC293D2Ex+i01kh6fZozk9RjB1FuQWpQE81iaOAlIqxEuRZiSwdZpt+rnzABA9V" "oJUYtGhx+dkxe4Nh/s5Z0vEWG95CHBNGLYpiiNUVeEmVl5jEUKFJfB3jU5WePHMoFbYN6ZYg" "a+wCOXV3rxIUeaAcwFAGsq4g7wqKKMZdWGNxf4fj92tOfeolxuJO5Mw8UikCAu99LaBchZB1" "zLIUghBEfUBpVSfx2UCkFaV1CCmwou5qSKVACIQEJSWogBTgggchCEEidU2NL8qCUtQwzMoV" "ZKWltLByfZlmu4lSis50mzhNkEIiQsDoQJlZTr79YbLhJr//0z/OytWL3PPwu2lPzyKlx1Y5" "zluKPKe0Bf1yxMrFi6iVFZI4QU53ULoWksHVeYx1ZyngQ0UxGtSdISCKDUmcbicGhFrsOE+U" "pBSbI/ojR3OcExmD24a5xmmr9ltVdRg1290mb+utSiVFDS91tqbNa0lWlgiT4J0lTiXSVwTn" "arYWdWdRKoVQpvZhKYcWAi/Yvg6NtRUqjRBe1nBTUe8y1npZoFX9XCql6xDyUI886ymjhmDx" "StWj4iBJhQJr8bJDqzKkSjP/5jfTiab4Lz/5ywxH2c5Zaqd2akdg/U8XVgBBScnX33f/+//8" "PUe+q0Vx5KlR/1d/+JGnvuXl62vnt4VLkKD+/J0nvn1xvnGPibX7tUuXvv+3z1z6CK/NulK8" "PpO74HN9YXJXZPaOTk8I10q690yhd02RD66jfInqdRHG25/6zU/9si/DgM8Gmt7OwC54dZP7" "7QSWv0lUOV7Fg7U6yahm38HM4QOIzizp7mPIKKbbbbK1foPrZ19kZs9+TCMlaUYsXykIN9Zo" "zC4ilUaaCDcZsfnSs4xPPY/ad5jIj0jEiFyNiSIIRHgkXgrKssIHsM5TJDHLowHy3GV2HzqE" "fcPbWT3zCfadTEi6Aheg7OeYbgcZazqNWUKnQ2umx+DGDaqBhbRJMcrIRlsYHRFJQUtANzaY" "xizDcyP8ZIXZu9poUxD5AtGUlHlENiyY+BEySbBWkqQxHsgqi3aKSKUUWUGRFUgRYeII5yx2" "ZDGxoXIVwkVY53HSc2Xcp6wSsjYUUUR1ccDho/u44y2Sl568xHhDErrTCBMhtMLZ6tP8JkId" "DYOojewEjxIgdD0ijLbz8ISUSKEoCxB4kjQiWE8pAt7VBPOysoBASUll6y5MCB4pQs1z8hZr" "LVsDx+q1ZXqzLRpNjYlqsVqbswNRpLHWcu/7vozR5iqnP/ZRlk+d5uS7v4Cj991Dp9fDlTl5" "VrC1scnS1UuMn36W1khQGk20bVgXuh4Xv3I7ELXAFM4SR1HNutoWICZKwJVI6t/f7nboX7lU" "vwnQmqQRY/N8W0jWotFLiTYGqRSVtVTUlxtdk123J911+PZogkyb9ePsPSIEhADh69vlnCUI" "hdQaaTwyCGwVEEriw7bXyzqiJKnzJ51EohBCoFSC9CVaa8AhcGhlEMGByxGyDrIONqCFJCiN" "8DVaIoQ6yxInkEKD1Bw7cZS//FUPs76xyrmlTZ5+8SpluYN22Kmd2hFY/5PEVTNtNL/xjXf/" "vS89vP/vK1z64ZWtH/qPjz79j2+s9ze2O1cCKL5sz56//gWHFr7BR5rTZfFr//GxZ3/kFlHz" "eoOaX0tUiZvEjzzSi3rzu6fZ8hErz19Hnl0iXmghZkr8uMHla/3Tn3j0/BO3PAf/vcaLVxNX" "r2pwB+hnFdc2S+6+691YqWoMgK8J243OFC9//LfoX7vIgfvfzvzePUyGE17+xEe4duYU97z9" "XbTmFpHeI8uc6vQT2AvPE+05Qif20LQoEzMuFVUlQWkq66kKj7O+9jbFDVYGA8yVJebn91Jc" "OchodZnGngZR24CZxglFe7pHZ34e5UB5QX/9BsX568ihY7yyxhgPMqCtZ3eaMtNIEK2YiYsY" "r4249ugqe94yw1wrxft19FSL1rDL5a2MrcEY4zVlkRO0wEiFKxxGx4hS4IsSYRRVVuBtIARF" "XjkQEilhXOZ1p8LBjSAZDWO2UoObU/izVzl67AAnH/CcfuY6o6HEpU1EnKBMjBAC62o0gceh" "tMIHUYdAh3rM5YNAvRKcHOrDz3uPiGKEqCirkljFeFfhMofWuhZvKLTRdfdoe6SmtcRWDmsL" "nLAMt9aYDOdwZQdXGqp8QgigoxQhA87WWIIHv+z9PPKLP8Nw9SqP/NIvcurJp3jje97Jwv5F" "pDTorCK9eJGZoWd50Kd5eD/zh0+iWi0m4xHB112/sL3157d9dXiHrSqkiGoSunNoKQkOkjSh" "0enQNzG7FvdgjcAYhStl7VXbfs4FocYrSOq8BiGQUm53jUSdTyglWgnKjS3U7t1EcVKDRrfH" "sz54BBIpNZUHKRU+lDjnKcuKsqhFa6w0tr+FikMtClWMLUdoLT99exACrWIk9ThXoAmu/PSf" "DalqYnyQEm+rWvxtd7NCkNgKhJHotMn+XQvcf2iGr5xu8cLFq/zy7zzJiy/f2EE77NRO7Qis" "/2fF1eLs7J7/7c33fs9DC9N/0eM2/tvZK9/6o4898x+Koqy2O1cCyI90evf9+TtP/NPgvcmS" "6MIPfeKJ7xoXVcZnw0RvBYTeDhwqXuWyz7ltGszBmajbmI2R7TnyFy6TXVzHnr3BuFMwXDhj" "f+569usrN8Yb1JuLN4sk+Tofg5s7XK82FvSvNSJ0wK/9/mO8+2vfTzQ1T/Aeay02n9DozuCz" "Mf2l03T3HqU1PUPamaKkyZOPfIwXH3+MN977IIdme4jhGsFWMCnw41Ok83PopkInJSpKsXSx" "1tJstxgNCrLRmLIo0EpRNmM2sgGdrKSRzJAtX6C1t4VpxMg4RqU99txxmN6evRgnkAQWG/ew" "+fTLDJ4/Sy+SyPkejDaRJmIqbtJqpQTvGOVjoipl+dKEM7+9xP637mPXnmnkaIBoCnypCDZm" "MhFY7wla4aXEl4KtYoSWBluAH+X0ejFlPkbImCA1ztp6RGcM1kOV12HGMmhs4biCqg+xMxc4" "eniWY3fFnHr6Ipk/gBeSqiprIbOdneeDA1cLgvoZrTP5BAGp6jGi/0yDFGs9JlK0Ow0mo4x8" "DMYoytJitKKsKmqrd0lRFDXtXUviKELgibRAaEU2yXDOUkwy8vEYFwQmLpBKU1kNPiEyCXc9" "9G6e/f3fhnzMytlzfPDiRXbtWaQ7M0XIJ/iNnLW1DXKbc/TACXqzCwzzbNusXXfmlKqFiIg0" "pS2psi3i9jQhhO1wZleT2oG0mRJKx8ZwiGgMkFriqwKlZI2vsBXOFXjnkMrU2YyI7W5gLbJe" "eaySJEGGQD6ckOzTBOcwsapFkQAtNc4HnAs4L8iKgjJ32DLUUUFGYG3d7VJpgsw+szAsZVSj" "HQRIEeoulzS1KX67eVzfLl+PgFWEwCJ8iZSGgEMKicTghAGvKEuHEkDUY2twndZCm7tOHGX3" "QoNnX1rml3/rGW6s7fizdmqndgTW/2BhJYTk4QP73vTn3vyG7z/abb3zejZ6/KeefuEf/fZL" "Fz5EQN0krmxbq+433H3sOzvteG+IVP6Tz5/63meuLD23PRr87yW1y9sIrJu/DkYI3ek1OvXA" "wtGZbWOynLgxz2CyFX7t1NVf+pnHrvzGq4ipm0XT7eCir/Z/Pa+9RXjbt72//shzfO1v/gLv" "/vq/hi1rPMHYB+Jmj25nF9nZx1l67pPklcARURQVSkZcWupz7eoHOdwWvGnfbuYaKShNqBz2" "ckY81WO63aTZNoySEptoxFSPalfEYHNANhzXJ348LokpXUB5i5jMUm2WqLkGIpmmu/cAM/v3" "0Zmdg9IjRED2WkQmptNs4NdXkVJjl25g4pio3ajp2it92ktLyGHGoNFjdSnnzAeX2PdQm10n" "Z5FiA1NatI+5Xhg2qoKiysiqnFS3UKTY0uKdZjLO8FVFYhLyMkOqgJYJToKt6uw9BbgKBoMM" "oSTDice5GCmaiIsjDu2THLsLzjx3hVwcILQ6NUjgFayAF1hX48qUNnVQsqtqtpOvY2rwUFmL" "0RopoawsUVDYWBNFispZnPJ4F5BUNTsreJC1uNJSUVU5jcTQTFN6UzO0e22ct/jMMRmNGW4O" "sJVFqQhkCylbJFGDmYV59hy9gwsvPU2atiiKguVLV7l87jJZljE/1SWK2yweOMbMiXspg2c8" "mRB8LRChFjQS6g6TFPXozJcEryjyvMZwBNDCkzYStpavMxj0ac/vJYoTvHPYIkNpWR/wIXw6" "XNtWDufq+x5UfagLUW83xpFGWI8TYNIG3jmq0mIiVaMdfM0Zc85RVTWx3VV150+E2ssWvEe4" "gE4SdGuKsqpfXq3WLEJLqkkfqSxKVDjvCcLXXTnv6xgp2N6spc4uBHyVIaWuu45O4LxAypjg" "K1xRknTnGK+eY9DfJOn0aKRzvO2+NotzCb/9By/xzKmVHSL8Tu3UjsD6HyOuEqP5uuNH3//V" "dx/67mYrPfT01ug//egnn/hnL15dus5nGFavROSIv3D8+D+5b8/cn3KR4sVi8ou/8tzpn71p" "NChfZ9dKfB5B9Tm0daNQzbZKaLcQaRPVHNM4vIvOyZP81Ac+8Ss//OiFf+NccNuP/62ohVcT" "V6+3k+VfpYN12xqWju//Tz/NXccOMH/Pu7CjmjElMPQ6e+lXiuLqaZYyR2naRDZjfnY3wksm" "W5s8e+MK1zdy3jTT5c6Di6hYYIsCoQ0mc7TWh6RzQ6qFvVTGU2loNnoEMc14XDLenKCsgyhG" "SYW+ep3i7Dppy+CikuoVzpGSiFTX5mMZEffamMN7Yf88fjwhLM5vwy4leE8VJ4jxmHJ1E+kC" "aaNFVma8/AdrzK9O2P9gj+5CIFaCZFJvmt0YC4rKY72vyeSFpSqgyALF2DMWGZEWxM2IELZz" "BeOE4AVSGKqyQuAQLlAUgfOXcrLMUS04ysGA43ft4ehdhlOPvYzzR6FjQAmENigh6+vcXs/X" "OsKHgA8O5yzY+kRdm9nLOiZGgAv1Zp42BpsXGGXIijHeutqrBCiptrP7ar1togadXpc9B+eZ" "W5xFakU+nlBMSgbLS0y21iEE4uYu4tZuXGMa4QX777qPYX+LwfoycZExHmcYo1BRghaBuV6P" "xTc9RLywm431NWxZEKoc8UonTtReMCEEUmucg2oyIMicMrd1B9U5ummgkUSsL/eROqXZ6xK3" "EpytyEej7fsVEMLUyIrgt23m9Rahicw22b1CG43Qmmx9RNTrooyhzGpAaNjeVkQInN2mtTsH" "26JXiHr8WFlbB0Rve9zi2XlElGwzyyRSKkycYrRHiQp8nUnoX5n6B8enrfDbMFOhY6TzCF+b" "PZWU5Bacq8eVLngiHSGDIB8MMe02QmiKqmJhfpY//SV38OZ7dvPkCxt84smLWLuDddipndoR" "WP+d4mpPr9f+C/fd941fsDj7z0tli9+6eOWbf/SRp/7TMCssn4mXeUUglQ8t7vur7zxy5JvH" "rsKG8NiPPvLEv6gqV/Hq3qvX8mLdTli9mtDxqVZxFMmEbTYPymO6KaEV8/il5SedC0Pq7cWb" "x3u3irVXe2zC5xkT8hqjwtvWx89t8Us//it887fMkxc5qJQiy0mEZj6ZZ2ArtvpXkSJBVJpm" "IyHas8hIR8Q2J59M+Pj162zmOQ8ePUjaSLBFjsszIh0T1pcJN66R3HEvjd48Pk5xWtJqNyln" "25Q5hMrDlENODjP+6BLVjRz2efLxhGF/SGd6CpOmBKmpgUQK2YohJMjIELK8djR7EM4TWg0y" "IRiXJWVV4qsATiFDyvUnBgyWtzjw9gPsmYdkMCEVFW2VsCFhMIEyKESo+VHBBaqspBKCYBJs" "UaFjWY+ZfKCwWT0+iqLaNC0UVVkxHGTY0qBJsbMNxMub3HXPAkfugtNPvIi3J6kaLZTzCG0Q" "Stdm7LJAmwgdGYKruyjOVQhZZ/lZV9ZByaJGgQVvEcEhhadyBdZVlNWEyCRoHeN8hVaGPBvW" "hndjmF+c4uDJPZhGg6osCMGy5Sz5YMxkOEBrse1LisDXQM/WTJtD997P6U89RpB9wrjAOc+4" "P2SqG7Fw8A5auxcYDrbY2tgg2JIQHBIwKmBknbcYXI3HUMpQVZbga9REmRfo4InbCXY0ZLS6" "SXf/EbrTXbwIZNYidYQUgrLIkLo27nvv0FLhXYWudC0mxfY7EymJ0yaDzWvItAGCbXCrqPlh" "wW1vXta4Bwjg6xghXwmcD1hbt8Y1gszlOFK0BxNrlJZERiFMgpIWoxKQUJUWW1X4ShFcUS8z" "uAqkRMmkbizLCCEdwXtqkLuqlwGQ2NLjjUPo7T9XSoOt0EmH4C3NFhzZ12Rxdo4Dcwkf/ORF" "bqxPds5oO7VTOwLr9QsrgDcvzB/7xgfe+j137Fr4mjU3Ofuzzz7xLT//xIu/Tv3mL7qlw1Ts" "StLDX3dk37cqX8Yibfd/8oXn/s2Zpf55oMPnxy7c6re69fOtt/1zeFzSJCYvY1OOJwRbG2Z1" "1KQRWabakd2+3bduBb4eRMOrPUa3wzN8Xg/WzfXzH7/AFx/4EIuLDcLcvnrctbTG+GLJzGJC" "s5HSt/U4wnqLkZJer8XR9DCD/hrnVm9wfXOD65cTDu7bA6pARIoSj4xSRC4YvfQ0qjdFd98R" "ooW9VFriYoVvp1RO4kpH1TiBG25QnT+N6W5SqpiN5QbNqS4zaYw0ihAEqAgaKThX3zEfwHlC" "sJRZwXBlg80bNxhNMrJRxmiU44XEeYf3hsGVwJM/e4W9d6cs3tehsyeh1y+4HEX0E8XmRLJe" "OozUxHFM4Q3ZeETpK7TUFEWBNoI4CpgkhSCwk0BVVPhgCUpgjCbPHWcv55RVgq3AcImTd80w" "XJpw7eoVooUjWBRBWYhihDYQapGFqDsfdvvEDA4RJMErBAIfbP3MOosI9fjU2Qp8QElFUU6w" "tiJJYqAi2AkSjxEVs7s6tHsdhNFoI0B4hNE4qSiLDEFC8A5XjWrvkG6wtTFgfu8sM4eO0r+x" "zGCQsXH1DDJU7Ln7Dlrzu8msZ3VzhSrPMFKAD3gCyID3NZwzyLBNmBfbkFKP2t6ykx7aU23K" "wZhsa8Tiuw6i44hsMkZLQaPXBikZjRTjrS2qsiTSCu89trJorVG6fnkpJVGq3tQcra8g0inK" "UVYzqbQEqZDb9HvnHLby+FBnEtZROYIg6rBnZS3CeiZbE/TUDHhbG9JD7WVLGgqBQQpPAIwx" "CGmohKHIPKGqkAS0SkDGhFDVAtFXNb/Ma4TU4LbzFT1MJjmyOY+zW3Vsk3AQBFJIGo0pYtMm" "jTLe/WCHE/vn+eCjL/PIs8s73ayd2qk/4QLrdSIYFF96aN87vuG+O//DrqnuPac31z7ww08+" "+feevHDhuZtGgjd3lrxUKvpzd5787n1T7aMqboQP31j+4d947uXf2O4YvVpu4O3YV7zGePA1" "q/LBF5UUxTiDskDEGpVqUlky21Pqv6Nr9XrFVXgNwfWa9dJWnw8+cYavGO1FXXLIgwlVVZD7" "hNVPbDBzl2b3oRamKIkkKFHRbQkcnu78LNNJm63ry+jCM5yUmF6bSAZCVeEoMZ0pUh0zWr3B" "0soSjd0LTJ28h/bCfrxRlB6s0VRxB/3et7PVX0NeXcIpGF4KXFUSoSS9+Vl05AFJSJvgPUIV" "hNLi84xyUrJ1bY3N515gfO0yZVZhfYGIPG5SAAqFhAqC9ax8qmRycY09b0vYd6RLkJ6ICu01" "ZR7wSUKZFXUws0nQUuIrC8owHk3wad1dCE7gvKKoKoRSxK0YHUlKVzEZFdxYAeUNsjSkco0D" "b95Nf2XIYGUVOTeNjeN6RFU52I7XyV2OiRIECrZDhYOtPVm+soRQB0GLACJYjNJopQhkdU6f" "r6hsUZuvbUFwFSaJmep2aHe7KGVAKKJYYl0g7bYwnS7xqEdwdV5jVWVIr1C2psZXpWNh/x7y" "cY4xEZ12g24a2L1vgaG1DK9dx8l6VCul2B6NOSpbIbTa3q6rsQTWVghhULoOsRZCIiNFe26a" "8bVlogP7QUKRTRBC0O51aoyFEEynCUmaMu5vUuUTbJEhCCgp6+uRtbhK05jgArYKRDNtvK+N" "9IJtPpestwdDAKkErrBY5/DBQBB4Z8EHIilReMbrQ9oHwraJvTbV+yqHStUdVFU/T9ZaQBOE" "whHjnKXR7GHiGG001ua4ENAGNDWyAV8LagJoKSnKktQ0COMcm4PUHqkUZTlBK01sDIkxuLLg" "0P6Ur2s3Obz3Cr//1FUuXt3YObvt1E79CRRYr0tc7Wt30r/64EPf9MBC71tDQ8X/7cqlf/pz" "n3rm313f+DSCgduIn+J9h4996xt37/v6SniWQvU7P/WpJ//DtrdW8fpAop9vS/A1u22A7UV6" "l7a2EyoDocL7gtBoQDVhYUpF3D7a5taumPhD/M7X29V61SpswU+dvciDD9zLVHeaGx95lON3" "v4HZ976d0RNPETaWcckms/PztBuGYAWpajAQEey7gynTYG7QR1Ggds0jlIJRH1YuE1av4bIx" "Pt+i2ehQdrqsrt1g6cO/xeziAgv3vZvG/D6srEOKQyum99Cb4WO/w2hpleHlVTY3+7giY/bQ" "QXpz0zTaLUzaqE9qxuCUoahGjDcGDC9fpsoGOFcg40A6bUisomiIuptQSoJ1NEyCzUvKJcfV" "D2yy637LoYf3MJVusbI2omOaXN0IbMiEASVFXhJs3W0Z50W92RcixiOL1gbv6lgVnKdcHxG3" "YqSCsnQMqEiVRpYBmZd0eussvvkgz310ghindUckL3BAEALiFKEktnDoOCYIV28aBgNInHV4" "VwssvKtHYs5CCDibU1UFwVvk/5e9/462PU3v+sDPm35hxxPvOefmUDm0qoNa3S2JbklGwUIM" "yGPASNhgDIwDa2Yt7DHMGMaRscce8AADGBkE0oDFAgSSQKBWaqnV6lZ3V1dVVw731q2bzr0n" "n51+4U3zx7ur1CrdVK1ugjlPrbvq3hP22ens97uf5/t8vlLR1IdE16KFoN8dcOaBEwyXhmhj" "EJnGB4fxmsVjQ1bPnMLWjtBOEkhTKnxoqKc7mLLLdDZgYbGH1gZCw6DX4fixHp31U1zfnWLx" "mKyYc8MCWs1hqEElH50gYQaEAgFF2SGimY4qqklNZ3VAVnbYakEsLDEbT9LmoRJILWidJy8K" "ekWBXhigpGA2NlRvCx2Rnv7eeYxR5HmBbRxOdSi7Jb6apc5ZAGRChnjvCTHifEiPoRcp5xKB" "jOCcS/R97/EipRx6n5KoA4EoLbZJfi8psznXS9K2gRgUJu+mpVCVg9RIpcmzAVFKXDVNCQKZ" "xwuB9YEQ1HzJwYN3uFlNlncIguTxE4a2mSKcxmiTjP5KsrS0wUeezDh/fJHPvHCTX3r6EnV9" "ZII/qqP610Fg3bdg+OiJ4xf+2Ac//F89ePbUD1yd7D33Q5//0v/9F16++E9i23IHcSWAan2w" "+PHvOXX+T2opqUpz8a9/7gv/9fa42ifF0Ii7dLDux+B+N2Hzlf8OHemM8o0OrYLQEnxAuJbo" "agqKY18heN4d0vzVdK/g7hyseD8dLIDnt3b487/0Of6LP/5HcXtjxOsvs1C8j+KRM9jOKWoV" "mV67jKprsqJLPXNMhqfJTjxAubLMyolVuv0CnMeOD7GHu9jxBezLX8ZffIVgZwRn6ZYL6FOP" "M55uM9q8xvj6j7Jw4UEWzzyBzpcpmogpDXL9FNnuDerru9RXdtm/sYs9d5nRmRMMzp5kcOIU" "Za+LkppWGxqtaOsKFRvKY4vEMKM5OAQhCTainYVMIjJJqDyhjXO6uiTWiu3Pj4FtTnzbEsN+" "zuBWzUIhuL4Pt3KDoUszrogYfCaYNjWzWUWeF8S50dq2beIZucCsskitUJmmmrb4VhKXDcqV" "vPyc58nfJlk5Drubhyhb4IsOvm1wUSBLD0oRJfjGgM7wCHSWaOMxgG0t0cU560lhgyP4mkwp" "vIvvZDUqEVHGUOY5x9eXOHlunbxXooxCqORFMkZTlBnLa0OmB6uMtgTCWZAgYsDZMeO9G+i8" "YHF5QN5NHc6NtWVOP3weOn3s9hRhUmM5uIBSpOd+DGht5iyvRLF3rUeZAqUynIuEkERPf1Di" "vWDqTfLceYttpjR1zXSUE6LAFCV1p0Pwkcxoik7aDGzGfh6qncZ7eu6BrPcnIBWBiNQiCSHi" "HECafFxSaXzTIAUolTxZMcS5dyqgtCRULabbTaLTWSIeM/dzxRhRJvm/vHMQJUJltE3zDlfO" "1hVivghh8iLR6KPA+hlGS1ofkSLiIuAjmkiW51il8I1FFskoH+fbkyFKgouI2KCEQkhFtzdk" "I0q+6/0l59cX+NzLN3n2lWspheCojuqo/ncpsO5LXA3y0nz/w498/+9+4tE/0+kWj/z0pYs/" "/Peeeea/fu365uX5db3T6M4rrVd+4P0f+B9PrPT6ul9Mf/zFL/+3z1y68gy/kdbOXbpX3GUU" "eLvu0p1EViiVFDIihDRE4fCzhlDXxHZCLrIBdw5tFl/FfXu7sSDcno11z/rHz77I5T/75zi3" "doLf3zq+C4Fe6+KOl5QPPIRYWGF05Qp7ownjosNM54hrm2S9LmWnYDAcpADdYY96oc90f5fQ" "VoTRTdT1A6KFdust9GSH9ZNr6Cc+yGQ8Yjq6yeELn8GYHoocNa5AteTHBpz0joM3a+rnLuFv" "XmX6sqY9fZzmsccoj58k6y3hRcZs74DJ5mWmbzyLn43JYkZoKtqZxdeeoj8kK8BVU9Albhbw" "VSB6R6hAlR12fnWP5uY+p77nAqdPdRlOWnrllOVDy61Cs7VTsjv2OAS5z6lsAlL64OaHMbhg" "8Qis9QTnkS7gWktjHa0r2R8rtDUsr9xi/dwxDi7tYEdTWEh0d9e0xNkUXRYEo3Azicy7CJPT" "TGtkUYKWMO9YaS3TFp5vwdeE0CLxCKkSFwowUjLs5pw8e4Lh6jLSmDSKDOlpKKOCIOh1C46d" "WME2nvpwD6lUwgnoDNuMOdzdYnS4gpSevYN9zp8/zuq5s4xmFYQ4J7O/3alK5K7g0/aiIsUB" "gSREgQgC2zraxlNXLcNBxsrqkNmkYTZrMFESXUuwU2RscaNxgn9KzUwZIpq802OwOCQvClyt" "UjdPpTGbIBnc925dARJrSwqJ1proLc7WKQInMyQ1mMaXGoUSEufAiyQ+8zzDTSp0p5u4W4p0" "W1UgxIiUKftQakMU4KuGIBJOom1ayn4f27a8vdAbgkcqTZZlNDZtfArpQabAdBECWJfEXN0Q" "rEeVaYu06CzjmxGhnWB9ixEJVCuERMRAlhf0nODsoufkJ/o88dAq/+RTL7N3MD068Y7qqP53" "JrDuS1w9sr5x/N9/4hv/9PvXV//woag2/+IXPv/HfvL5l34YH/xXdK3gN+f/CaD51gcv/MdP" "bSx+YyYFT9+48Tf+8Zde/Ef8xhDne3Wq7uS9utdo7jcJmyI3QkglghcpN627hG0j9cEhvm3l" "b6Frdbfu1Z3E1n2Lq7frhYtv8cLFt3ilP+SErHlKncEdf4Dq4AC1MGTw1AcRGISVuBs7VKMR" "B5ubTI4v0+nmqDInKzOUXsCHwCRaBBPUYk6YWkI1JjQ1oplRdkqOPfAArXsAax0IgZQKKSVa" "K0I1Jd/ZpX9uk3prh7qe4dsR7aVbXHzt17juNIeUVGiubdc0+wfE8Qhjegy05GRHsFF0WReS" "wdIxYmfI5OJLKGkI2tLGGidMyhqsLbnpU110vPGXL7PxsQHL33mC8iHJsc1t1nqR14Esz7l1" "0GBkYihNp+ngyosOZdYhtDW1a9Ph2lp8lcY0o4Mp7cwy62b4maZDyye+o6B3vMfW6w1i6jGZ" "oPWe1rs0LrSKKCS+tUiTEUXKwBNaE0NCG3jn8XWFa6fIGMiVRIqMGCIhBjp5yUJZcObsBqcf" "PU/e7aKyHB+T8TwRzwNCgLNNiuoxKTw7Bocnkmc5EYutJuxs3mL9xBJlr8ekCcg8J44mSXgI" "RYgeEdJ9I2VAz43kETlnYqVcP6kU1kaqmaOeNJheRlbk7G4dzn8xIm01w9tJ2mqMCfXgXE30" "lihyqnFESEm3V2AyjW8TS0wqSZZnGJ0I9eXK0rzLmEZvYp4vSIwEl7LQtZYIL/FAkzJ3EnNL" "S4oiZ/bWdfTCEJ3nBNcmQS0iQikCghCYYzYk0mjaaU0M883AGEGk+xsPUad8RiJkWU7rA1K1" "CUKqJd46TFagAoimnfPESkRsUcpgpKQJjlBPCXisrchMjpKavOiQSUOmMyp3wMceXuHs6of4" "1Jeu8avPXJ6/pB7VUR3Vv8oC675Hgp84ee5b//CHP/ZnT68sf/PnD27+1I9+9lf/9JevXvvy" "XPDcyW/1tlCqzq4f+9j3PfLIH/chcj20P/fXP/f5/3edSIDqLgLqdmKL2wiru6ES3i1eAhB6" "RR61UiKESKgtmckJrWW6PSX3Vt5BFL1XevvdhF7gqzC6v7teHx/yZ668yV85X7C2+gH86kkq" "AjWRfNBFyxLnAtpI6vGIqy+/TF5qzIk1Cp2jsoze0iL+4fdxsL9JePOLGA3+sMHbgGvh4JkX" "GYYG/dBTiFKCNklgaYPp9NKY63RDrMbkdsKgHkPrwHsWD0cc277G1rWrbF2eIELFrqjRA8Ns" "3DCbTnltT/NSu823bPTJ+zfoFEPqtoZ2RpGVSDxaZgQRCM4ls54RtCPF5o/vMXpmm8XfvsLS" "N56gv9RS5lus3PR0ybmx3yIpIUSqmUV4aKoEzAzWgzKIqFEiYIzBNpb93QnK99hqHC9NHWcX" "dhleeBgh9xF1SAJJShpvsTEiQ5ZGhcEDEaEEblIjTQGA9z5tULoGERu0nnutncORlvUW+x0e" "euQCDz71AIvrq5gyQyqJ8hGb9AheigQ2DR7b1DTTfbwLhBDQpofKOqhc4ZpAPR6hsw16/QXq" "aUVoW8z8Wd3WFqkFAYsIEWPmv3JRErwghgRm1VKlAGrn8S4JxW5XIYhU0xolNcG7xOVUCcbp" "2llKDAiBiCXr9THlEKkVWglQGk9igiklMUbTtg2VSx651NBLQFetBcqYlJMYAviAlipFFHmB" "VwLvkmkeISmMYXc0gZNrRBHw0dO6iJKCmEmCEHgfibVNYlJmeN/gQ0CpnHo6IbQzjFGE4MCB" "kCkTUcnkoRLaIKRL6AipkMZgvEc3aRSsTQEOgqsRMST/VdlLHUYhEGklk2DrlN24MMBUgVk9" "Ym2x5Ae++3GeeuQ4f++Tz3Fze3R0+h3VUf0rKrDuS1wtZKX4wQuP/tHvPf/Qf2UF8Uee+/x/" "8neee+6Hq7qquP2W4LvFVTBG93/f44/9Vye1XpppvfWjX/z8/3Nzf7RN2hq8WxTOvQCi9zMO" "vK0HyhS50UoRoia0CjupULmgAWx7z7DmuwkrcR9dK+7x8fdUn7o55n96+XX+m0cXKU9skD/w" "BD4vcDZgraduKmw1RooOwTZsXb5ECIGV42sUZYFSkmJ5jc6T38y0bXFXn0MMc8Q0EmYBtObw" "+Yt0Jo78fU8hTEaMgegaXDXPcxMa2ekh1QKmKFBZBlKwqBXnRIBgoakJo23q8T5u6yrNjddx" "owP2b07ZuXHIcHJIb7DP7NbrLMZEBRcTT6wVSmvAIpROhue5UZpS469LJj+8h/hczeB71zh9" "9iTLC3sM8pbFTHDlIBJDwSGG2axCKUOIiRs1q2Yg08ErUCwMF9jf2qOuLDoYqtZy5dWWR84I" "dHCEUY1Y6GJySY6iJYJvQWR453Btgy5yiIp6eoDMUxfIW4ez6dDNtMJIRW+wiMkzyk6PYxur" "nHjgLKtnTiF1Dm9buAXIRARIJnnnCM6nDTghE0BUaqRMaU4mK8kKTXSepm44+fCjTG9codrd" "TdE0Aeq6JivzxJFygeAtmLQVF0kQzdxolJIEH/C2xc4qpIj0+gXWOtrGkuU5zrVIlSWulJ2m" "rhERESx4S2xnyP4KSku8j3NSeuoSZVqhTY5rHLPZiO5wEV10gCSkUz5jAspKnQKjXZvo9yGk" "dEghBDJ6pAZioCVQ9jopz0qkLckQBbadbyYGQXTp8bauxvuArSui8QgvUwahJxn2pUCQIn18" "FOTGYENAa49zE9rW4YWm9B4/tdhZS3AeJXSKFgoeM8+gNMYQVcqdlCRmWvKupbinTJdE10CI" "PH62x8Yf+FZ+8hdf5XPPXuTXs5mO6qiO6l8FgXV/I8HlteN/8KHH/+SHl1b+w8u0X/grX/rS" "f/aFNy9/Zv79XzkSlHcQWgKYfdPp0//eo4PBv+Gkjr/81pW/9Guvvflr/OacwduR29W7ru+9" "AKJ36hj9pr9vrAzORyWR0mCdIESLnPONgovxNuLtq90avBOW4Sv//JZF1t96/oCzi1/mDw8X" "yU+doFh9kLZtkbOGhZUFgmupDg6I9ZjJ9i3s5JDp4VkGx9ZRRuGaGisy/JkPMHUZzeUvpA5C" "LsnrGu8k8Y2ruFlL75u+CbWwCM4RvU8bZ9IRhcDPwZXm7RGZl4mdpA2iVyJ6fTquhuMn4aFH" "YHbIRjuDtoGti8SDHfwbE9jUUBXE0Zj2zQa/26Ayhfee2aHDupCib4gEKUDlNM9YDl54E/1k" "l/73bnDhXI/F4YiFaxNyWXJFaba9xbr4jgm5bSdEkcbETdMQ2hZFRFqLFhEvI3VlsTOPaCJh" "WiOLjKzoU9YtQoLLFS54YmCODwARJU1bQR3RRZ46IoR5Z0OwcvIEZx4+z/L6Kp1Bj7xbUPYG" "mLxIaAcfid7hQ/q7axva2WTOGjfkZY+i76mnM7xPeZIiJOFicokyhsneiKXlVcZXLmKrGonH" "RI23Dq8NRAVR4UOgCY6oUyxNlhl0yo0hzkOcgnMIbynKnHo8xY1qZKlpbPIkmUzgrUekG49S" "mhAcvm2TwV9pvG+oq1kCmuo5fFUJDrc2CdJTDBZSZyiC0moOZ00jQKkVQqmEQXA+EdpjQjnI" "SArdrmuk1uiiTJ6rXCFiijJK1PiIE2/nLkqc88lvJlQa1VqH1JEoNHKOsEjLCgHpBZnR0LQJ" "RBolWmmquiXXGrW8jPIh3acSpCoQIY0oTdZB+AohI8wBqzoWCOdB5QRX0S1KMidx0dEGONbP" "+cHv/SCPXdjgJ37+WXb3j3INj+qo/lUQWPcV6fJNayff90ef/OBf3OiVH/vFG2/9jb/28vN/" "5tZ4cos7G9lvJ66alX7/sf/DQ4//CSNyLk0mP/d3v/ilv07KIlTcGSYqbzMCvJu5/XbiJNxG" "4KSPC6HOrg2OC23ASHx0iCgRGCKOKMW7Rc/9UuLv1em6l/j6qsuFyJ/99BZt/Hn+UOFZ+Pb/" "gEYNcM6ysNJnsNilnlWMt24w2blFc7jNrS9vsjtYRxXdxHCK4G0LvTNUSw3V5qsgI0Oh6DUu" "ReBs7hB+6VcYfOwjmI0NovcIoZJHCBBC4uoKbxtUniN1hpAKkxm0zkB3iWii8CADyU+dIwpB" "XBsQO2NEdwZL27DXIqd9yoElTA1xe0SoG+JsF209KlPEOe3bVR6ZKUTIqL9Ys3vxIvmHeqx8" "8zGGjw9Z7GzTeb0i1pq9mWcyrZE6p9CGxqVMO0mcR+lEpPSI4JDKkJuc2Zv7+JEjtgFRe+K4" "RrcRNamxKwaZF0gBkoBvWwSKaC3ONbi2JisyIh6tFMdPn+LC+x5h7dQG/eUlTJ4j3j7M468/" "TZxPiAJrbRKESqNMRhCOGDVSZRgdaLwluJq8HBKchxgwZU6Mnn5/QK/bRZR9qv1dxKzGVUlQ" "irJMj5uHPM/IMo0U6WleVZam9QghsE3CT5gM8o7G7U1QHrav7lEOSmIm8MGjhSHEBrzFaIXJ" "Cmz02Nkeg+EJtCpwjUbEhhgsWadDlhts3TLYOE/W62HrGtfWpGmiBEKKGnIOGdP1UVol1pZP" "SwIuBvI8J97cJV8YJJJ89MTowMW0GRkiXggESVz76HE+zPMSExcLJchiIsdHoUFIhBJoZYgi" "xTN5Z1FCUJQFYVolgCqKot8jhCTmQhAIkeG8RxKQEpQpEN6lcGnv0wudlJi8h2sFPjSpA6ky" "cpFTO7DR8eHHT3Fi7Rg/9YvP8uxLF49Ow6M6qn+JBdY9xVWuNd/70JPf93sfeuzPCRXzv/36" "S3/8x1547od8iP4eI8F3i6sohNDf/8STf3ojNxd88Ft//5ln/vtZ04xJW4PcRrS8+zIjvzkK" "B27PpIK7m8rfFl1BSEGhwoL1jqxXoDo92sN9XJkhQ8CF8NUIntt1uO4GFf0tmdxvV5X3/He/" "tM21yT/j/xIL4gOfQC2s0+t3EJmm6JZ0h10mK0vsXHqdg9deoDm8hXMegkTkfaLKAIlXijhY" "oR0dcN3XZK1lA8GCyGl2D9n52V+g/4FvwJw5hTY5OssRMhG5o1QJ/ti0eBcTFkBniaJNRpQK" "J1qCmxGcJlYR4ecjGmsQ2RCxqpBuH1HkiNCAglgFxLBETytie4jo5SAkTGYIK9ARcimIRlON" "ItNPVcyef5PuJ1Y4++SApWyHY5nl2esQrOKgqoihTRt1SiOlQhmBczVunjeonEILQ3Ozxu2O" "CLMamhbV6yAKQzaZITuLtKXCtvV8NBQRIqB0hvUt3llM1AgRWVjsc+Hxhzj54AU6gz46z+ai" "hhRYTIqFSQHJghBc6njoDNd6Guupq5q6aRLUNIKSBQqNdy1SaRAB7x1Fp0+n3+Hk2bNMD8fM" "Dg4ZTTJaL5HG402CYuLB+WQAj0S8D8yqgEeSZ0lkxAidzGEUhKpBWU8WNKurawQt2D7YpLUN" "MnrUnDGFEmgBodnDVh0sghhadJ4hCBRFQZYX+GgohwNs02J9QJkM3zYJSqolhCS0QohIJVL/" "SQiUFuDBB0uRdWljwPR7SCHSSBWBjxERwIbUrRIy4n1MG6VKY9sW7zxK6SQ2lYAYUibk21E+" "IiZK/zwHOgaPzhSmSYZ9IxVSZzTe0rYeYwTBVekNBRapJEImE70UKTXCt5YoJKEao5RC6SKF" "ayuBF5FMOpTp0XrFmeML/MHv/3b+8VKfn/uVZ49OxKM6qn8JBdY9xVWv6OR/5AMf/Y++5+yD" "//nre1vP/dDzn/9Tz16/9iV+o5EdfuOWINzeiN58+wMP/NGPra7+nia4+HPXrv7lZ2/c+CJp" "axDuHOZ8L77V3UTV7UTWu8VWkEKgZVs6odPrpoy44HHBI20iV9+hE3UvEXSnMeXtRN97yiK8" "3wrADz895rmbf5fv/57L/MAf/iOouIK1jiAEwaejx5iMoihxzhJ9jZ2NoN5DdpawDpxrEEKg" "Ox08gb3xDWZ7I87ZHoNuH5zDf+5pslubmLMX0P0+Os+TH0tKiB6hHIgWaZMuFyZH54kR5ILA" "B0GUhqBKhKuJAULIEM4h8wFsFMhZDUyQ9QGq3yLrmrBkcDsC6QJy0EuHn6vmgdMapSI6kro5" "+5r9n9hCfiFn8NE+H3iyYFAecvKG4Y1R4I1Rxn6VwKSQUA5SMTcka3SR0z2xTrgxotreTyHF" "TUuUCaqZd0sYTRErJT5T6CA4PJwkj1HZwWQFVT2mriq6vS55p0vZ71J2O5giT09vwTuxNJAi" "dqIIBJ98SCCpZjV109I0ltHhmMO9GoVJhm9tkt9IKUymE1orJqGisgQ9PbzyFqYQ7M0EtVAo" "G5E2pI6cF0QX8fMFAoQgCElbN0gKpJYE7yk7JXEuMI1ULC70CC5igShMoqYjCH4uZlSG1Bl4" "y/RwC2UKOmWONsl7pU2GrZPAzReHVNOKIssoy4LQttSTfYTw6MykEWtMIzYpJXMGKS5GlFZo" "raiCxXQLrHdoMyfVp/kmAWjamhCTYV5pTQg+wUgFCKkTtsJbYlSEGFECYky3RcqM4Nz8xdjh" "nKBxLTb6xCmLkSCSV9BkBhcteZ4jhUf4CqJPG5wx5UdGl8aUWaeDb2aICKbopsUDIqgcDygy" "nMsJouR3fsc3I0Xkk59+7uhUPKqj+pdIYN1TXD2+tv7AD3zDh/4f71s+9p2ffOvVH/rfnn/m" "f7g+Ohi/ayQId/dbvQMUPb927IM/8Pgj/6VuG3nFh1/4yRdf+uG5SLudkHr39iDcH6n9fnxX" "7x4VhkzLfkZcwEisneJdizAZqihRmcHo5m7Q0vcigt6TL+xr+aT50vWWV37k02yNa/6T//AP" "s3b8BOPaY12k2rwK196gcIc4LEoJdN7Bo4mAynNsaHFuzgEa9Cn0CUY3N7lhJ8igKWMOjSC8" "eZ12dw95/kFCkUZdyAhCpy3DPMNkimw6pqlrdF6kTayYwJXoHrLXQeQNeI8sW2JtCa0DJEFK" "OBGI51vY20bfvEqoJXb8AowqVEcjBgsEnRGsx+uQ4KQxIgX4GMHnVK8GJm8eUD6VcfIbBqw9" "qjm9NeL0ruPibuT6GMauIZKYW0ZItJIMloYUJzYImxMyUxBmNarI0M5i8AhjCM0MM7H4YcG0" "rRPawDmmkxFSS7xtCS7SKolvHG3dzj1giUX1zhPhbevfV/wGCKFw3lE3LdPpjOlkxnRmqcZT" "lNQoownvoA3Au7RRl3c6qDzHFCXXL92g3d3m7Psewh1YprUjswLloKkajMyoa4s/tHR7BVol" "ZpNzaTQZgiN6S3/1NO3uFjQBSQ+ZaRrv2BtXbO9t0c1bukUg1wmtEGNAa4k2Hax3GC3IC5M6" "fBG6vQGz0TRtIWpDpy8pyhxlNMSIyiTNdIRzddpaFcn0rrRC+IS3EAK00SilqacOvdrHeo8Q" "yQOWRoDhHR9WDALbWrybjxe9TX4xZ4lGpZxDnwRqCsFWCClorYWYqPdvg06zzNBUDcSEoQgk" "MeZc6l5KHFqmLEQpDEJk6ed4QZSB0DY0YYTUOdrkSBPmkUsOIxLzCyRRCnQQ5CHjd3/7h9hY" "Kvjsly/z2pu3jk7Hozqqf4lGhLcVAN908sw3/6ff/K1/UQaK//nXfuUP/tybr//TuRnkXluC" "txNZPtdq8APvf9+fUpHlqiz3fvyLT//P06bZn48G75QzeL+i6k5i5F6sqbc/7oe9oiy06fso" "MEWBmE4QEoSSaBWx7UzcQ/C8Vx9WvM8/X9OatfAX/u4X+JnPvs4f+jef5PseOcbyYAiVw9Qt" "btaAa/G+SQeRzrAipHFcv0vjHTZoYnTIwSLD4QLtbA+vFaJYIWzvEadTQjNDzSrE8VXC6irO" "1diqSaMupRFIhM6QRQfTHZB1+3NDeAoWzrTC6NRBSoeKStfBgwigMk3oQ+wcRx5fhxBxgwHi" "y68iJg5lcuxAYw8b4qxG9lWiwBOxtSUogelJFJHZF1vqi7uUZxc4cX6RY2ciTy60vL4Vubg/" "5sq4YaY8mbMMQ85iZ4CyERUlloCLkdh6TB7Jg0PnkrJcwO77RKbXU7y12KamtTUiKLIsx7UV" "bjaj2d9lsn2Lpj5L0UuhyDH4dzbmpEgdvkhEG4OP4JsG51raumZ82GAbSQySanKALgq00sii" "gwgpu08WGUJIOv0ObdNy6eKbnF5MSI28U+HGUw6nDa3zKSdPRJwLBBcZj6ZkecIzKC3xzmFd" "Rb/M6C30mN56kyJ0qMYzsuOLbE0qru3eoJ5s0+ZQKI3MisSnMgaI6EyRq4yi00EpDW8LMAnt" "rCbrDdBagdRY53A+kJl5N6jo4FuBxONdnUKWCci3MxMDSG2w0xlNaMj7fdR4jFISQbo8hCLO" "oa5J1Cq8tckDGCPSZCilkl9KpA4ZpBDstyN1CA5iIMSQsBAxohDImLI+hfeEkFG3ggxPN9Nk" "GjJjUZgUEi0zmrrBuYooHAFFDAIfHAo176JFYkwRQEqmx1lLSVYYQgxI0ef9D2zw1KOn+dIr" "l/nZz7zIze3Z0Sl5VEf1L0hg3XXE9m8+8Mj3/OHHP/T/3auaK3/16c/9gaevX32R32hAh7sH" "Kn+lYIqA+30f/uB/+mh/8ftm9ZRfu3Hzbzx75eqv8Ju3Bu8EEr1X/M39eK/u1sXynTxTOSqX" "XlEuLqFzw/i1NxJZWkqy0hRfcXve0/15G4F1P7DR90Ryf6/16pUD/uRf/TR/e7XgP/jQGh8/" "ren3euTeI7MUYUKEmBegWqKbIrs96Bi8dDhpcLqDX+kTOw+B6eCmNawdx1+5SLiySWwmqFmN" "nFWYE8tE7bGzCSFIfHz7oG5AF+QLJ/BR0DZVMtmpnLzsoIse3lmEEBiTp+gUH9FSEEKgrZu0" "Ni8EYb2PKB7AbM6Q24eETsCGGp1pMgRoSVAgC0WctQg8yhiE8YQW6udH+Iv75Mf7LG6UfHi9" "z+PDDq/dHHF1b0JoLaXocer0WbI24iYTjAGZKWTdIDsdMqNZeOw8whjaWyPqyiPX19EqEnyN" "Dy2t9cgsQytD8BZbNexeusat46co+gvJLB3S00pEQUqFFog4B2RKkXIPQ6StPbNxg2ssAtB5" "F982oEPKziu7SJPjg0RJTbfX5eKr1xlPpvTPbiC1odezcDPSNC0heDKpyYc9ZNVgRYVzFd4p" "ogjYtiZToETLybMnqPe28NOK2O1zOKmJOxMu7e5wc/cShbTIQY4U3TRa1gqlRPJvBYcpC4pO" "by4ePEYbpNS4tsKrPpPxGO8cPkRETPE6giS0TNkhRot2ktl0nMCrSiGlIkaHHY8R+2MyEd8Z" "Z77zsilSKLfUGS5aondInRFFIuTL+XMsSlLHSQe0MQTv8I1PkT0qRymFkLyz5ZjegIr58kbq" "cnmvcC757yg0QoKWaRwvhSIICbGDbSDS4u0MlEYrg5AKqQzJ6JWYb8KF5Fd0IGSGziVeKky5" "QjPd4mNPPcjZkwM+9YXX+cwXr85p/Ed1VEf1z0tg3VEMaKX4Pz781B/7vQ889n97frT30//L" "c5/7M9f3drfnP+9uXaV3C6AAuLkgaX/Xhz/wx37Xk0/+nw9v7nLd+l/98Rde/qvEqPmNQNE7" "iap3X+d7AUTvJWZuJ7hCJ5OyU3SyQuS8sHV4va3G9kGjzkbbILqaXq83IG06xrvcr3cjvb8X" "MfV1GRHerp7frvnPPnmNf/8jD3JS7/NAMeXMSoelMqNX9MiaHSQWoTOi7RF7PWLRhaxL6C/i" "eivE4SJquE7beA5vXCXqACajfeMSYdYiLm+iqhHmxDECAWdnKKmQpksUER8d9d7l9K5f5VhX" "07aWynTR5QKQ1vNlNkCYToouEWkLK605ps3DaFtUd0hccUiRoUQP29aYWSQPCkGLBlSRJ5yE" "9RACxkiEhpClcZq7MqW+OiEW+5jVHk8UkgcXOtTBoswS6sYe7Quv02zvQaYxRmCyDKxDebBX" "dykunKD34GlKo8mjZfHBD9K/scHmG6+xv30DoySIjCgU7nBEYzbZv7rJ8PgJhssK5xxN0yKl" "RGmBNnIe65IM5yEkE3rbelxrEb5Fa4UjpsUBpdB5B6XTsoEpMqRJpu03vvQicrzPmQsfpnIt" "S4OCPMtoQgQPTWsRhyOkiGS5IXpLcAFpFDJEbD1jMMxYXV/h6Z//JOcXerioOagbtq9cZac+" "YHywxwxLv1zGuzyZw5UCKREybf0V3T7S5Gn5wSeBJZB4pdE6p5pVTPd2yDodbD1l1tZ0hkvI" "fnce0KyRmUTWM0IUOO+Zr6Bid3YRV3eRCz200fOxYMTH9FxJyIgAQuCdJ/qAEDqNspMBCwnk" "mSHLPErG+dKDmOdSB4zRGA9KBfw8u9R7j1GKMlPU3s0vJ4nj6EPaRowgpUYbTfARGz3KlERR" "M60cWga6eU5UqVsppEzeMSGRKoCXeDQBTSAg8x6doWRysIl2sLa8zL/9XQMef+A0//SXXuat" "a7tHJ+ZRHdW/wBFh7ORF59999IN/4rvPPPDv/bO3Xv3LP/rKc39+WlftHcTV3czscdgfLHz4" "gx95/y//2q888/FHH/yB3/vUB//0bFIXIS/HP/X8l//c3niy867R4LvZV/c7GryTAAn3OYZ7" "p4O1UOYbMsRe1jG8cW3rYjPbjk+sLZwV0aOkQgev7iGu7jVC5C7drHt1tL6+TyYtOXSGg7Hk" "mamn81bFQ0uWnhqx0jGs5IF+NmbY2WKpr8kLic4zOHyNbONBrHyEaCTdhQ2yBx/iINfMQktW" "Ruzla7i9Cf6GQ40tcqVEdxUyHyZ/l84QtsX5hhBl6i6EiJKKGD2uHSN1QXQ1bXuI0mWCRNoa" "bTrochmBBmkISiO1xmeCtgtGS/xqib82xntQKiJ1g24ExUCjrQbnEs28dgghkm8malwA5yPu" "1pTWBsg1uYem2cbFHZxOW2eyl0OZEZWiWOshM0Vza5dARPRLYqfADgoWH7vA4rnTrJxYZ/fN" "i4x39qj29ojNDEXEFApXV2y9tcl0amnrhr0bN2imU7JOn+GxZZbWFun0OoQYU9AyycTO256o" "KBM+QAqUzpFSpXy/+QLDYGHI6HDGlU/9PN/9rQ8wPah44Y1LPPDxj3Lq7BrXrx1A9HjvmExG" "CdNgFJnJ8LaZC6FIiLB26gR1NWXv0iu871s+ysy17DS7XDkcUzdTFI7G1gRXo7NlRGYQSqGz" "AiEiZX9AUfZAqHfyD5WUOB8hGxKjwtuWGFrqwykS8HbG7NAR/BJFr0NeJDCq1hnOtwgB1nrs" "rEKNK9xhhXnkocS28qkLGON8vAeE6PERolAEkez4aVnYI5TBExHKpO6hlCit3sFmeBeJYp4j" "KOR8UWT+zkoKlBAE67He4V1Ea4EUqTtmMok2STSlVmVECEHeXcAtRby16E6XfNBBioi1UwQ2" "bZFKn/AxShORabwqMiCQZSVVPaPoZCil+cCjJzm93uWnfuEFPvvM9aNT86iO6usssG7b/RmU" "Zf8HH/ngn/uWYye+60deevpP/sTFl/6OTzOKO3Wu7iSuANqPPfTw9/6ffu/v/R9/2yPnDjb8" "7FQzrclc5JX9vR97+s03f5G0NXi38Z+8zXX9ShFzty5WuM2/3/33d3eI/IOrxQnp6kLqHuN6" "8lwpw0a0FpF18CGSFZ0ShEnzgN9Uivv3iN1JOH1dNgjvVY+dWOX9p47Riw2y3+fAVUxn++xy" "knF3wJdHUyZ7kXbWoN0+K2XN8aVFjPR0xVs8unyDM+deon/2JHH1AeTaQyxsrGOomcQKkZ3G" "XtnE3prg9iri/ohso486tUzMDNI5RDAonejW1jYopVFZanBKneF9i9QK30yxzSFKFogYsNUY" "okOpIgFNgyc2aVsx6EhjR5jlHOoOyoLdnSAjRBvJtcS3LSZP8Mh8WKCnFhoPSuIsOCKNgzZI" "go0EHxFB0PiAlhJpsmS4jyKR5F3Atw5VaJqtW9D2CVuedrHDqNSsPPowxx86x5nHHmS8s8Vo" "c5PpzRuI2CI7XVR/gWY6ZbQ3Yra7w96Vixzs7yB1weLGOVbOn2b9/EnKfh8fI6511DNLiAIl" "FCE6IgGpM0yek5c5WVkSQiTLc/rDIV/4qZ/l/acHTA7G/OTPf5LdvREzJ/jA7/wupNFsXt3C" "Oo/WEiUjzjUoqRFKJfK+CAyXBpy4sMGv/sxP01eaouiwu9tSOUtrK5pqRpEbBp0Oq0t9OoMF" "TF6S5SaZ1YnkRaKyhODTQgkek2c0NtA0AecqmskhSiqCbwnBIvFgZ0TfQ6k+WZ4Rgsf6gHtH" "4ETam5vo69v42ZTu6ioxJJ9UCAEhwbuAm0f9uLbF24DRKeLH2RptciKRQMC6FqMkQqUNRKFU" "sgSGiHXJM6W1SvE+1iJkTCgJKZBG4UODDx4x/0+p5GNTOiO+83HwziYhlnfISkk56FJ0c7Iy" "p62nVONtQghEYZBKIFHIoEFpgpAEp5G6wFf7xG4fZMDHwOriEr/nu97H+nKPn/mVS8xqe3R6" "HtVR/XPqYEWjtPx3n/jwn//Q8Njv+JGXvvQD/+zq6z8/VzNZvD1QU9xBXL39x7xw9fIzX/zF" "n3pxrZN/ZOfmLgPTo9bm+o8/88W/EWMU7xIkdzOzf7UROOEuozhuJ3C6me4jQBQyzuCFrvWL" "0QuiFwSnQGpxhybVV3rN3qu4upex/esqsHJj+L7Hz7GkWpzXWF1ifJ/zFxboHD9JHJzmpO/y" "1rVbVG3Fwe5Nbhzc4ta2Zta2RCv53BXFxtMV33j8BqfPvkJvLaN37lHU2gXKXk7lZujjywgR" "aa6PwAfqSztwGMkeOYvqZIjoaNsZ0reo6HG2QeoFhM4AgdQaQiAr+jhriERyXSQyt4oIPyaG" "BolEioSEyLsKFSWZc6hyGTGaEnue9pZDChATh8kNSs1jVVpP3jGI0hBrj9ESN23IMkUjA22d" "0ANCeERH0wiIbz/qOqJKOQdiWnTPosoMt3OIDwEnPVvPfontm5s0H3qKBz/wBCceeZC1C6ep" "x9MUZiwVbdtysLXH+PXLHF55k8PtLcbVCC89bTulnu0zGU1YOXOa7qCLDxHn5h2qokzvFFzq" "dCgtMXmG0ooyL1haXmBna5dLv/Z5eqMxL3/+NRpVkGU5Lz/9OrKzwFPf8RFOHh9w+fJNptMW" "KQRaSYJtkVomI7+Gx546y/VXXuatpz/Lt3zLR7EiZ1y1TJoGrSRlp4fAcfLEKqvr65iyh8lz" "lEndJiHl3GenIXhcDNi6YTyeUlUw2j/EKAHe07YVSiYYqJDpDg/tDG97RDqEELDWJbQCEtsG" "JjuH9Ef7qGjIuh3atk4+NikTSX4+GlRa41ubcglJCI/gA0FFtDQYDSYTSJ02YEOMaecieKJI" "AN0YQJAI7koGYmhx1qF7PWRm0EEh54HSCDl/AZoz1rTCuyZtigYHIaRtRK3wbU3beJSGrCwQ" "ao3pZASuQUVBprK02BATfFWWXbLuEnublykXlpDSIKUjxEC3O+S7P/ogZ9aW+bFPPntkgD+q" "o/o6CKzf5AmSQvJvP/LUn9rIut/2F57/7Pc/s3X9V584efIjp06e+t1fev31H7q1u3ORX98a" "vB9xJYH86tb2K/+fn/rpH3xkdfW/+K61k39wba3Hr+7e+rE3dnZfA8p7dKzul4x+O2EVee/b" "eRHw/Uz3sk5BLZi+dGXn2necymSIHuFTgKu1Ls7X2e50PcJ7HGfeT/jz121E2CsLvvORczy6" "voKMMw4mDd7CU48cpzhecn0qyYseKnY5uZ5x+tGzdBY7vPmF59jbvIYnEkVgPDtk+9oBv3J9" "zCf0Iie3c6pnXkQtv4jZWIdhD2La2JK5JlQNYGhuTYjNZYaPnWbx1DJucovpuGGCZaIjTVtD" "U0OwSKnQRS+Nawjz4GFJJgUhzFBSomTA4DCxQZucLO8SnUVajzQSXfaIHUsocjiUqD2BnDgk" "aRtQHjTIskQZQ1SCGICYQdNgYpyveAgiKkXPSEHrPa51SOmxSuBCQJgAk4C0aVPRt45QaOJQ" "sv3qMxgtGKyucKZzhizvzLfEBFIqrLN4JNn1m1hbU9uKpq2JBA7tNkoptEk8tsOiTIc+Kr1b" "McngnRUlQkSyoosyBpPnLC0tEZ3nH/zNv0p77Sq5z3n01GluRc/BtMLkHS5/+RJbFy/zDd/2" "Tbz/w0/QBsFkNOFge5/xzgjvIc9LHnzoGH424aVf/hU2Fvqcf+QC1YGlmjXzDTtJt5MxXOiw" "cXKVcrhIVnRAkJhceZ46QfHtflPEOs/hYYWeRtrDCfXM4zNF8BbvI3iHwBGjTYw6V2ObCZOD" "gHUB58I8DDoy2a+oRpZh2UH1e8hM4iYpSDSpm4jSBu8DSgm8C/PH2xFihfcRg0CImHpLwhBI" "7LEw52PFKHAhptxDZRAClEyeLRHBSE1HGcZaYqIi05KiTKT5dBkJFqtNDiolAxBtum5K4l2L" "lZLMK9q6xRBRJicru9hpSHmcgJSRIATWhcS0EYo4cQTiHD/hUkSVkAiT8+EnL7DYy/ipX3mR" "5984oGmOullHdVRfC4H1mw5+Ywy/7YEHv/1D50//Xz9z6Y1f8rl48Ae/+Vv+c50X5sUbN/7X" "vdHh1XeNCO8lrr7yZ+Sjqr2SK33j3MoilYqXfuLLz/1v88tTX3E53GUEKXhvhnbu0Km624jw" "nfun0GIR72gFe5u745uD9z2YGaPeOVhFtHBns72/2wiW+wefxjt12L6W1VGa/+ZjH2Rp/RiT" "acVjTz5F/cIXOK4a1jaO8fKkQmYDpOnSHtYsbwxZe/g4w401nPUo6ZDBMasnFN2c3sIGhzd3" "+OLhLQ6F5Lw/wfpogJi2NGqfKFva2FJNDgnBk5shKvZxBy37X3yNpb1VFi70GQ4zqjYyaz21" "9bi2wYWA9YGmOkCYHpLkzRI+IHWaNKtQUyrIRUQLUBKk80iRIYRH5QYpFIKIzkooDsHsIoqA" "mGl0VuIPWqTNkJ08dTGmFtkvkK6lyA3aekTrkVqhRCA6P29bRpy1iDYdYsHa1OWwLV5FYpGz" "e/MtdFzCtTWHm5fZfPV1hquLLB5beQdmiZRIZej0hwzWNsiHy8jdmyilUuajbWmqEbY6xE0P" "iN4lD07Wh8C8G6dASDqdks5wkSwvWVhc5PLFy/zkj/1NLr3yLN87OMeS7mGXlxmajKs3byFN" "TuM9eze2+YX/3z/h1Asv8L5PfIQTDz3I2sYyh7d28Y2l3yuYbl7hCz/zT6kOD/jm3/YBeoNV" "Dq+/RbAek+UUUTIYZGxsLDJcWsYUnRTKrBRFmVGWXZASa1tcsMlkHmH32h5uf0wmHL6/SAh5" "6pohqdsKiUXKiCaiiNSTMfW0wkdFFJqIwLpIvXeIti0FAvnko+jMJHyCVggFColSid8aEvIe" "Zy2+rQg+QVWFlCgJWaYJwRJi+lql0ijQ+ZCYbDGS6A0SY9LWYJYbpAcRAsKD6WYpb1ApEBYI" "84Dr1AmTOsN0cjKn8OMJRbdHNR0TbI13BpNlBOeRMmBMjjYubT4GECoQfMBaUCISVY7RHYzp" "IlTAuwrNPBRdCOpqyoPHl/lD3/UNfOHcVX76szfY2j3KMzyqo/pajwjj8eVF88T5M5/YdlVc" "P7byfcfXjz11aW/nr33+hS//xe2Dw0N+I++KOwgg7iC0fKb1ye84f+H7u4MhP/3yyz+6dXB4" "iV+Pw3kvgupe47I7bgZyb3M7QNBKZqXJjuVFwavb1Vu39mf7Cx2zLIUBKfDRoYxXQisR3e0s" "WAki/VV03eJ76Gx9zerCsM/3P/E4v7A3YdbOyPM+x7tDVjoVV/emzKKh2+3hhcRGR4iW7qBP" "f3mFU+9/gun+HgdvvYFUOdJKukbTO3UGfeY8l29c4sU3X+XxconHhsc4s3yOKDWTdkqlNtk7" "vMGsPSArHDKUmLLD7kvbzK5vc+yhknIBsuhBtnjjcUIRYkYTa+oQaUKClTrA+kQLl7El9w7l" "LbgWZDZf/ddpzGizlCHXzhB1hVYOMVSoDsjKI9yUeE4T9wNSpWDi4D3+xi4i10jtyUsNo5Bi" "WCJJNISIRuBkJFiLFiXONhA8UUM0Gi8iwUhu3niT7tIKrnVM9rfZ351RDDwmS7sTIsw9XtIw" "PH6CtUeexNYzOvv7xOCx7RSVG1RoEO0hquwhdI42mrLU73SGut0ux9bXQGXs7BzwyX/6k/zS" "J/8xh7ubaCE5vV5y4dQ5nn9jj2ZvyrnBMnQGXN/f4UBKgrXcePEt3vrS8ywuLXHhmx7igfc9" "yWC4wlvPPs3FX/sM3jYMipyTD7wfHw2CiG0dUiqW+l2ObXRZObZElhfJc6Q0RZnT7XVBCLQx" "SC1BgPeB6aji1htXYfMmy0tdxIbByzx1mYwGkeNsROmIkh7X1gTriLLARUGICqTBVS3VrRus" "9DLi7oR8dRGjBME7TJEhtEi8WyneEUpvb+iN6zHRtYBAaJU4Yoo5kiESQ0iZzUqjZcDMA7d9" "SNwsMX/T6lwghOQlzHp9ysVhwi2IlCsplMD7BB4VItK0ntmsRZmCYjBMW4dSE6Ocd9U8Wur5" "WDNitKF1Hus8PghClOlzEXQxwJRLmGyANi2qyMC2uHofQ0SbjFk9plsUfOjcOsvdBX7yc2/y" "+ltbRyfqUR3V11BgsXs48Zf2J7/U7ZmDy5s3xrd29j/z5s6tl+YiJ7uNqPrKDT95F3Elgfqh" "9Y2Pn1taf2TX+4u/eOnS3yMhDu7W+boTloF7CCu4A539DkLr3cIlLHbz/qCTr4Nkd9peams7" "U8KUrqqR3Q4xBEyupJBSRPxXLWrvIZy+bgT3d1dhDIO1DU52DnHRsvnS65T9RW4WfTb39tHd" "LgKNa6a01QF7N/bYvXiGhePHKYZdusOCnaYGaeiWBTFIhMioxmM2VpfJFt/HSy98mX/2/At8" "48pJPn7ucc6Viyx0ztGNK+we3mRsR9QHhzgaivUFphPH9a2a3kKk7M1QpkXlJSYviUXaiupE" "h5PgbY2PluBapMpSxyoGfND4ukXEGSIGRAQlTcrBszOEc+At0YMSGpl3UcIiokQOC0KnIdoG" "KR1KN+AFrp0DMH1AlwbVQKjmMMoIRgh0kdHEtL0mc411HpVrXAw08y5NtxxgR1PGIjJc32X3" "rasMl5dYOX4s4QNaRxSeiKdT9jj+8OMU3SG7F1/BTfaJriJGT6dXUvSXyPsDRN6hu7CYuhwC" "hCpwLnLp8iVeevllnnv2Od66/GW095juMr1pxcaTD7H+nd9NeXWfanvCZJSz+fo+F9+8TtQS" "bz2domDt2DmoxqwudFg9fR5jcm5cukTVNpRGc2L9BPXUIruKaHJGviGTGQvDPoOlIXnRoSgK" "lE6ATGOyOaMqEOceqBgidWPZ2p1x6fJN1qb7rA0WKLsb6N6AJtRUzSSJDZElwWHTr3bAE0SD" "9SBURgTsYY2wgU6RIYYDTKcD0YGzdBYWCCLgajsfySYUQ9kp8T4yOdxDdkqCdRA8UqZ8QwRJ" "EBLwrqae2ATKlQahJMElp6r3LvnERPIMmiyR57U2iLcFuw84F/EqYtuAbQOt9YwPp0jVAhJv" "a2T0ZFk+b7OFd0aThDTe1AZsqBEOnANn032qg0BnOVLmyUgvLAgNriE0Y0S09Afr+LamJzQn" "2z1+3ycu8EvPd/nlL715dKoe1VF9FQLrdmJFTKpZ/IVnvvTzpjA/f7B/8JWXK+6jYwV33gL0" "CNH/jnPn/mCvKPjkG6/81O5kcpNf3xz8agXVncaD4R5dq3APAeOODburvazcCMqwH8ZvEqKV" "+aBRLuAIGCnx0cgQbtvRu5/u2706WHfKI/z6QEZ39/j0C2+w9vCDHD9dcPVLz3Pl6Wuc/MgD" "6GKAjwrXOoRx9MsOuAmjqxc5vH4GJyUyBjq9IrF8VM5sMqVkwsayYfOtbUw348zZB7h2OOVn" "D3b43HO/wLcOT/HE8hke6Pc4vvYQo/E+h26L8a092msHlAs5XmtGsz62GNLLPcJOIe4iyl1U" "L0IRkcqD9HjlQAa0MEiTIVROdCSwYwxJNEWQvkZGhxIB5vBUbTKUEuCnCDEf5xjQJzVxViU+" "VR0R6wX2msc5iattOtyDIlRz+KVIm2Jt41IXS0ikVOTDHvQN2WIXP52g7QxjOkQpaJuaW69c" "4uqrV3j6U59m5dwFekuL9AZdhksDsk4XqRI3SndKls+exY+7uGpCDJ46KMZOcu3iZaqmQeZd" "ZnVN1dQcjEbc3N1ie+cWUqfQYpOVhHrKoJ3yu86fZeHxb2Dc6VA+uYKqDQcvTBHVLuuHu/hr" "z2B7gmo6QpQlJy48wIUPfjNr585yeDDm0uYOmQvo2NBfXmE8nnLtrbfIuiW192gp6fa7dMrO" "O2MppQxFoVFK4axDmYRlECTPlG0d01nDaOpY9ZE48whnyIsuzoObTglBJA5XiosnitTRcdFh" "vUAbaKYzms2bLAlJxzW4tSWyTo6rKvJMMVjoUdc1lauTSR1JlmX46Ll5YxMhBVLlyeenFIiA" "Ujpt+kWPUECwtG2L0DlSx3kHNU/OzBgRIkXmSGMSZV460KljFkkICOsCSkaSIo5JoCGYTacp" "k1B4tIgEJSCalGdIinkScr6FGDLwHhHC21H1hBBwIX1t8MmJ4X2LDI6s6CC0IrQ17TzjUOqM" "xZVjyNGY3/4NgvVFw88+fYX9g/rodD2qI4H1NbgMMa2mUN2Txn4vofVuMdY+vH7stz+5uvat" "N6bjW5969ZW/z2/GGIg7dMhuJ0YEd/db3enPnTpX7xZZ7tRi9/hCro9JE+Klm1tXQGRtUxdB" "GRARFz3KT8gzaNwdb/t7FYZ36l7d7/d81TVyjr/86U/xP5w6xplTG2w/26eX9ZhujSjX+8i8" "wNqAlgFFRCmDHU+4/tzzeBERoWXj3AlUaMloiWPH3q0Dnnv6TWZ7NcvHhhy/cJ4PfuAbeWvn" "kK3tmzynJC9tv466ZXlo4SRPyYz+4irDostsa4tqr6bb7yOylloYRCspyx5FUMSRI94aIxY9" "sSMJWU7sKgiHUE9QuZ6v0GegixQYHUDMw4sliQSOSnNFWVhUNyKMSt23zEBsEUrAQBKFIHQL" "5KrBlBbGOWHT4eqA8AqtFDp6hJIIqbAEMBAygSxy+udO0cSWup4kDpUV2KpGGEk1m3AwHrNZ" "Vew88yVM3qMzWEELx+rqgKq1TOsWpEJKyPOcphnTKXsIBOPRLnle0LYV1gda+/YBmuMkNKFF" "ZyXdTp/ZZB9nLRt5h99z7jhrG2tMM01We5xraWaC/YllFD3lxhlOLw6J9R71wRXqg31C5Riu" "rdHv9/jkJ5/myq0Zpj3kwrEBk9py8+VXEAYW85MEZ1FlB20y8qJLlmUY5el2MvI8e2ccqEih" "ySnXL9LaQD1xqCDIMoksMmSW2FNN43l7Ii9URl1NUVqkTpAPTKswf6zHuMkMDvdRPY2uD+le" "+AbKsuBgdIBta6rJDJRKsToxhWZLVTI6mBKdQ4q0yZfCmCEKP3//GHDOIVxAkJAcwTkilhgU" "UTl845AIpIDcpK1NMZmk26ENMnikBCEEIQhmM4dVidMWI/NIpDg3yUeCTAHswYe0bSkF0mi0" "UQkLEdIo2cca68G3AdfWKB/xNjHSpI1zmK3ChzRmlEoTnQWR2GkxeAaDBbTJ+FAeOb1W8stf" "3uLpl2/NtzKP6qiOBNZvpeR9iqZ3f17eZdQnnzh+/Pf3yp78pVdf/Ynre3sv85uhovfjtxL3" "EB/3ElTv/jh3uJxwetg5nzcu0ybbu3Rt/zWZ605sQ99Hj5B6/mLXyBj83TqE4j3c7/Fr/HXv" "uT5//TpjNeVM1/DoQ2do6oqJaPFBQZCIvMA5TzubIUVEyBn26psMhwWPfOOTrJ0/zfWXX+Hl" "z36OFz7/LFffusn+3oT13hpB5kxnFadPnWKweIyr/QUOd3dRaoFWRX7l1g2ebisyVXC80Bxb" "jtQHFZ3plEU/oC8GDBtwvmRJ91iIgazXI3aA3CMHBrE2gGFOlBVRQrj1FmK6j59NMDKC6UCj" "Er9q2iDLIm34NQHGFaKWqOUBlAaynPm+PdEYYtMQhYIsQ54doLcOEF7S7rc0VqIywzBXuCBw" "KlIu5Ox2NCOluNXuM9tRqE7J5GCXvFPiZ9M05rGR3qCP9xbZzRFEJk26j4P0NNPkJRrtbIMq" "CHh0nqPzgtZVdMo+URSMp1OUUqispMwlIUZMnhFci0AQvKeejclFII+W7z69wrkLxzhYPkYx" "7DE+HGO0o6kjk1nKNESB6S8QlGBJCvJT52njFDtreOX1m/zY3/pRYqiQoqAWfT7/3BusLA45" "//jDzIJi3HoGvbRI4G1EF5Ky0GilUFoREGgZkEIlZpgPTGY11bTBTSe4piIqi+l3KAY9vIs0" "tcNai1QgiSmX0AucbbEOplOLbSKumcH4gKyaUegOLsLS+hplp+QgWGbjAyh6dIfdJHSUAKlx" "bUs9GaFFwEWPVBKtBb1eDxFagqtRWs5fjQQyK0GZlCRAotIHa+ch1nNR5AK6zBGuJdMKUebY" "ukojxxjwMWUV1nWD14YYYTZrkyhSKRsTkURkax06RJDJtyZkTKk8EWKIWGuxLtK2llBbEJG2" "rhOXLUSMIC1RxDa9kAiFNBopNFFHtIa6HtMpOxAXOJPn/IHvWuTJh9f4Bz/7Kgej6uikPaoj" "gfUex4P3+33ifseNX/FxuzRceP8Ty8e/vXG2eeHWjX/Cr28Nvte6m0/pdhDRewmt24m1CEJt" "dDsPSmk4cMPL1/fcjcKoTM6isqJFK1J3InYlaAnt7cSn/ApR+F5GhfEut/PrWsvDnOMffz96" "J9ItNCfPX2BvsktjItO2was8BT4Hl+CK1tJTkbMby0wPD/iZH/lVvvyZ53nt1cusL3S5sHKS" "FkdoBLnu0fjAbDwhz3OWFobpXX8MDDNDLhV1XTOa7PJyO+J1DK6IODkGcchJ3+FhOeTcbJFp" "0adSipM+0umW+OWcoCrAJ/r2cAnZH6JPnEe0+4h6BFmO8I546TX8eEQMfWJlYTwXUQGYOVAj" "hCvATCHPiXmGVDkxK4jTEbEFBgOyjQ1k0cLeLu3lPfLMUAogk8TFnKaXczPz2Exihgts7m/S" "jiO1b1kSQ5ZPrFEMF7C+RQrHmtI4bxl0ery5uc/IKabTXQ4PAwvrq/SXFmnqJo2y4gwJZKaD" "1oJQdHAiEoXi8HCXbm+ZEF0KGVaG6FusawhB0/oZZ7MBJ5dyZisF8sQZ9nZ2sa1CxAzfLtEe" "zAhNRbQV+UKH8c6I+mCHBx5+mG055E//l/8TY9uyc/MSisjxxTXaYMiEoru0guoOublbYYUh" "qLSB550nBp+ifQQpPibP8TbQtDXWJgK6847gPU09ZVSPEAsd2mioW0cQ0FqHEIq2bRAxibKm" "sdStJQTJ3v6YalRhgsc0UxZKxdC2yLPruExRT8YE5xAhkhnJ0vKQgx1LVactvCg8IlpisEhS" "ooEgpE3AqHGVQxtNVmTM1wXRRYkuI0LIJGxCEjYBEKIFIrkSBF8jiyKlEmiDUuBtMq3LGNO4" "sJomIdV6QkwpPboskEqky7UBQngHlCoQyWzvHL5uED6FPxsDPji0FGS6gODQOkfIguBnqbum" "s5RJWU+JpJgpaTIQiro6IMs6QI71Mz7y8AarS31+6pde48XXbh2dtkd1JLC+CgHFXUaCdxNR" "dzOo26dW1n7PhU6v+9rW9qdfvHX9WX7dMH+/gu1+Bcl76WLdScz4xX7RWV/sXPBC8NmXrz67" "uX2wv7iycFwVWsY2NeuCBG29NBLR/ubrb24jir4aYfuVfrGvu8h65MQyx8+ssXPtNXpaUHYK" "NvI15DDyyq2r1ON9TJGjBEhfMdCGU0sDXnn6dT73xc+ztXUTk3dZ6A+xs5r3baxx7MQZnnnj" "TaZIsrzLrVv7dBaGBO/J8xzrLEXZYbCwgMo0UikODzbZ3b7JaFwhe1CNd5h5y8VOy1vNLT44" "bVHdZWJUHL85pb9aEFeWCDnETkEwGnxAKYUUXVSvi8gLRLOPOrWClsegMyCObxL392GwkICP" "b+4RvYD9Q+K0RcQJYlgSBwGRdZFKpiic2QhVLiO7Gb0hlCcMuhboEwWxL2mdIhjFrdkeVxrP" "6aVVllbXkLlGGU13aZn1xx6nOLbBbFYxunGDdrRDvXuLoCQ2QggN3jc4p/CNxSgNGZhiSF1N" "CUR8gKZtUsizSAdwVhS07QSdZUyrMUIrjClwdkaMgloNGcpZ6mr1hrimZjKDYCXRKeqRxc48" "9fgQgacdBYqFLv2zD/Lapdf51Guv8MWXn+PYwpDF4QLXr11mWPawWYdyYYETDz9A0euyf/kQ" "N79eAoF3aY7u/DwYWQqcdekFwnka6+YCxVE3DTvjCiszUIaqmjG6uonvZzRuCgTaeQfIe0/b" "OibThrZxzA4mjPZHmBhZ7Cq6wRM0dB57CKMUkgAxIGJEK6hnFd4lFEPTVng0SkgaXIqfiSKl" "CEiNUpI8X0IKjzEJMipF6hyh0uaeUhKTa5yLWO8wRmGbGiU8MUowGb51SJUScZRUSSw5j/eC" "4BW2rtB5H+/TZmDberIyZTJKo4gkgRVDeGdJIITEwRJCEH0kyvhO0LU0ObaZkgeFCx6jkt9Q" "yogWEmFiItY3NVIFhJIYUxCpkQJ0VuBRnF7T/KHf9X4+9/wtPvVrr7OzNzk6dY/qSGB9jeq9" "GNDf/nzQSg0eXl17P8rw2c2rf79t7eQrxoO3Ex/vHgXe7ufdThyF+xBX99oeBPAnlnpL3Uyc" "E97x5YvXPh9CsGVWyKLX0UxmiCzDC0dWFFLLO/rUwtfwfv+6YBp6vR5GpRFOnmnOrA+Y7Vyh" "GRh6WaDfhf2x5VhvyMPZcV65+Ca+bSk0LOSSjYUBz/7iF3n6hZeJCx1C1kGpHt1el8OL2yxm" "JcMs5/ixs1yuN/GNQ5cl08kUnWUUZU4hSySSTqdD1smJoaVnFrmw3qO2Da+/ucmBhq3tWwjb" "kvV6fGrvCo/ujfnwwimIJcdvTOgeK/Arx4gLy4jFJSDg924QZ1vgLfJwhLR7xLZJ96hzEC1i" "YwN664BHbZyDcp2wuUm8cgMuvgUHM4hTZF9iIigEuAacTduF79tA/85vR9w4JGYQqgZ1fYd8" "d8yyNfzMjVuMm4bTx05SKugqKIyg6A9Zf/gxnFRsr93i5kvPUgRLMR0jCNhqQr/MWOx3iVWN" "1DlF0cX5NkFV52M/kc9p8Y1nMj6g7PUwucbkJW0zIzpP2enTzQoODnZw+/s89g1L+OUBZB1i" "aNFKE2XJbFzTHFpCKyG2KGD9ZJ9HP/4RDqeRv/4n/i6Xtq+T54ZpM2EgOnSGi9jo2Z3cQsiG" "sw+eYPegoo4RFyONdzStpfA51nqKqN+JlnG2IYZI09pEoG8t1nucd1S1RUgwCpCRw70b2DaZ" "x7UR84cw4H2gaSPWekZ7e9SjGZlrWcol5zLDehgzeOxhyo11lCQR4EOCdLq2pZpMEFIRQxIZ" "Pvi0KRgDQigigrLTwxjzTpizQCKlRMxN5kIzRzEEkClT0AWftlpDJPiWaGtMlqG0xkZP8KSN" "QiNxAQIijeu0Jso2pQ8YQ5jzrZwNGKWSDyoEfNugjEGKdF/EZNzCO5+u0zuYY4GSOglBn9hZ" "uclQQqFletkUAlxTE4IkzEVb2emjW02MNQKFCwEZBXmZ8du/6WEePLfOP/nFF/nyy1eOTt6j" "OhJYX2Ohdb9dJ7vR6z15puw9dn08vvKFK5c/xW/EPdxOVN1NaMH9gTnvJqzuGI0zL7fU7x0v" "TGejiXL/5rh5CaAQYbGws25AEAAtM1xQsg3ydkIx3EUc3kk4/XMv7z1llvPhJx7izMaA1WzK" "pdcvo6TicPsicvgAXlTs7QXWTy5gzh7nYDyjV4BsZ7z0y8/x/GuvcsOOGHbPIU2Pg70D8qxD" "HSW7u9ucWT5OL884v3Ceq802QQfqpqGdTukNBxiTMatnzMYNme4xLCO9Ycbrb17hC69cobKR" "Xtkh5B322oYVXWCW1vji3h7U+3zEO4wusVem9J9cRZ86T8xKpNI4neOrFvY2CbM9aBpkK9G+" "QugpQs1gGFK+jQ7EYh0hcuSJc3DqUeLSlwlf+Byi9YiqIZoseYGlJOqMuLJMHK4Qz5zHr06Q" "bUBUkVjcRHZusBIj/b0RVw4O2POKTuZZLjOGB3tcunyFB27e4PHv/T7OPPkIveUldi6fo7N+" "mf6Jm7z24otMdjfp5wU2wn5dpWy66FPeoXPUkwNCTIuQs9mUqm4IQrLaP57SenSJw6dYGGWY" "HOzxnRuStSVBJXNUO4NiQFZkNHVAFxKpK4R1dIqc1ZMLPPrxB9jfm/Dn/9L/wvXxNUQuqWct" "zjZ0JxX9/iLN5ABvch48t04WW+x0D5FBM205nAqqumVtzaRuiJIoJYkhpEBkfDJx+7Q4EqKn" "aRxt3aAzyFTaksMFfN3igKaNGJMM/zFEXNUyvrVN4RoeXO+x2hkwcHv0C+hfeJLOo4+jjcFo" "nX4ZXTvP/IsE26JMlmb6QmHygtF4F6M7ICVKGYRQhBhSADgJUPr2bQgxEeONVkgpcd7j2oAn" "3VYXHHgHVUWoHCHrACBzQ0gqizjPg3DOJ3SDi3idfGFZVhK8TbNCJRCEJAZtQ7CGoBQg38k1" "lFEiokLEFPejQ8SJCNJg8g6FzihzRXANUpFEm1PJPO8cQkSk1ITgMFn2TqcshJpMKRAJzHrh" "+HH+nd+5wrGVZ/mFz7wwD8Q+qqM6ElhfS5El7tKJAnAPLB776JLMB7+8de3HRrPZNaB7n6Ox" "OwmQe0XI3GuLkLt0sAQQjy0MzuVZUV7bmX7hy9e3XwcU0Q9lZTvBuzSSCYE8G2ilMgX1nUaX" "X43Z/Z/PAygEQqSNtJcvXWM8KXn8Wx+iOrxJVuT4gYG6wkdLIyRt1uHYIw+jnv48enefmgln" "Hl1g4eyDPP/GFpf3KqrpBKE1Ks+R/ZzNw11kmLBw7BidokS3ipfefAOdScaTKb1ehygjWliG" "ZWSp9GyNZ3z2xetcv3WDEAWm6DJ1DougLBc5cJbVbo+F1YwvbV+DesB3jVaYvBWJn36R4b91" "EjVcAutQWQdRLkNWwfQQ6hlxXOPrSeo+5CViIhD7txBdoBzD4TYUq6C7iF5EPXqBsHtAaJPH" "KfoA9ZQgKnxvBbW8AVrBcIDKOjBt8SrH5SV951m5NaJX5mlTrJ1wbXbIwcQi/BbXbm5z6enP" "86Hf9Xs4/v4P0f/AB1h96DFWrt9kVMOzV65jaBGFwTYNjoas7M1J8S3eO2xTIWRJVU2IUqG0" "wbqW7YMdgijQmWE02kIoxUdWcr7tieOMkKhQ4g8qymULyhLaiKsqlLIMljOWjw848egJ2vEu" "f/uH/gFffv0Fyu4qKqQtttHIMaktw66iHh9wavEcv/O3fyf7o4Z6VtMblGwfjJlWDdPZNJn4" "VZm2/VpLmHdxnE9ogxAFwQvaNnJzc8T4YEyuDUV/SHd1A701IsaA8w4hJU30BGfJlWGQKdbW" "+mz0VxmWklBt0ykXWHj0faiVE6hcoTOD1gpiwNsWKSLetQka7CdpzCYUVeOoW09mNBJFnpVI" "nRGiQGqRNgvffhcVw3ws9/bvU5ib22NCJDiPrafJ4Fc1YCXeC1ApQ1FLSYiOEKCqWrx1eJvi" "cbAOE3RCNGgF0SaUCB5bz9D97lygBUJUCKFSjiMeQsQ7l8SjNGkUqTJClMm0r5iLSp9eFGMy" "7QfvUxpBnEesElBaQTQgU/dUapOwGAGWBkO+99s+yMpiwT/4p1/CWn90Ch/Vv9YCS7xHwcRv" "QSBEo0T5ocVjH5VR8PzNG798B6Ek7tDFutdbotsJprt1rwL3Fz0Tjy0UGzE4qhh2a+ctgMly" "bbJSC+sRmcZHy0DWvaWeGUzGHNxBIIp/WZ8sUkmUVhyORwgJaysZ3lpmhzO63SErH/smppdm" "TDY38bGiIZDpgsGT74PxTVb6EZVBtC0f/AjUleP5F67xS194iXa6x/F+n2KQsx093lbsT/dY" "f/A4sMHVgwOoGsL+FsPVRTrLA64ebPGpX/xVLAqlNFnRJ+sMmU6nSKmow4ToWrwL+L0tji0M" "KTodXrUzzttbvH8WaJ47ZBQ8w3/rdyAXurC3g6xrRNQIvQxGE00J3oAdE2tPbCJit0YUCtEX" "0NuHbgt5DroDq6cQS2fAprzDQAYyh5WT6LMPo9ZPpsM1OKRtCTs7iElNKEvKY2sMiqsYH/EI" "FvIFQrCouZem1+1R7+zyub/y33PmY5/g2EMPM6lqnntukxe/9ALBSjqDZVJms2JSN5i8w3R2" "gBLQK/o01tGGKYPFVay39LsDKmsZ1x5jAlJlTCpY6US+/aHj0FmirgXSdWkPJTbMAE9oUmZe" "f6HL4sYKaw+eZri6wo/+8D/i73/h00ybKctxkaXFJcbjPZQ0VHXN/njMUrnCx88+wOVnL/HG" "9gFh2OX0qWMc7mbsjx1GaWIMafuORDtPEy1BiCCkwllL3Tja2nPr2phm6nigG1k6NqT/8IOY" "yUvEakSZGQgeUxScPH2BsjAICdpbMiXRUmDqHbIipzhxbt4h8iipUFLQzlrqqUN+BeQ0hEDV" "WLwqGU0OaHwaExaFoeh2ksggpvGc9Mm/JtJojUga00lQyhBDRODm3iyBaxu8a9FG0yIJ1qOL" "t5lgEe9BZDlRVLg5BsF7j/Rz2rxWKJUiTUWEKCJCRaQ2ydtmLTLPMSYmeGkI+BDT7XIObxQ2" "RHSUKc4npkzFLBNIND6mgOpGZYTQEkNIPx+fKPVZRvAerQXKaIKQhAgqKDKRIbNVPvGxDzOe" "ef7Jz33p6BQ+qqMO1m9xHHi/ZY/1hw+tdDsffbOdvPja3vavzceDdxsHxvv4uXcytt/Ng/Vu" "cRXuIOKC0tqsdoqzOsKNw/E155wFoiTIIJxURU426CfDd55lZZmbu3TZ7iZY/8WOB51nPJow" "GPT50FPnWOtFDmYtVWWYbe2y8dQqcrnLzc80+NEe07rCbV2j0CBUj3ZSEfyYdjKhv3qC/e0R" "8eqMJwfHeKs+5JFTxzi+0kc98gCTN7awbcv23hZrD51i8bDk/LLEjvbI4wQrBXvBcbxw7DdT" "pm5AMVgCpWi9I0boLa4xm44JUbI7PSTTgpViyMge8PNtw3G5x9lyCf/aRcY/+uP0v+ujSBxi" "5xaiaqGNiNglakMsNJgSbA3ezmdsDtqWWElEG2DBQKcDuk/slYilDcRwBdFZQS1uIHt9pDHJ" "qRw90bWEw73UVYieKGHWelYHBcGU7I8PcbFCFwVF3mH52DFWT56lHAyoNl9HSYXb20fhObXc" "pT5xkrcujSnLDucvnKFqGq5c32TSWKTUNM0UFwTTZkxjp/S7JWW3jw2R7cMJZWcRKSORBhsi" "nzhRsLGyyJVKMfU91KyDag22SVux3ns6wwWWzp1i9dwGy8eP83d+/Bf4ob//D5nVu2SmIMsE" "Plo6ZRfXWjIpacdTnlw9y+GVMZ99+pO0ZYeJMHzLdz/F42cXeOP6lEEvT2O+6FBqHvGCSGDQ" "EGhdpKodMQpubR6wf20HEyTHS8Hq2TMwXGbl5Cn85nUELYvLHUx3wHhU89bFGzTVDB2TwRyj" "OLnS58KFJexkSrE4SLgDBEZJptMRMUSKThcpk+coBEETkhCp6hbrPFoYsryLlArBfGFCQZ5J" "jJZ469ILSpyby+cvv957vE0E9ndenIRAqgznKkKdoxYNbWNRWqZuVwRtCoJtaWYV3nmcdIQQ" "cdYTg0B1BCFopARlNDGm3EkfEoRUaYHWMoU7h5RoIGIKgZ41LR2ZRphKCWC+ISkEgkiUAq1z" "YhyjVY50LmUiFpLgG+Q8zzISEVIlYn3UxKiRMUPqgu/45g8yGh/w2acv49wRL+uojgTW16ri" "HbpQ9pGFlY8MB8OlL1y99CNV224B/dsIqtsZ2m/X3brT/+/Hc3WvzcF3dMfKcn94bFg+lkXF" "ofPXAQsYpVLwLoDpdGmqMdII1e0W+g6Xdy9hJe5jxPp1r9FozBefeYOHTi1xbnWNv/WLr/L9" "j61w+sM7DN//CIPRjFsvXmTqG5q6YhoT4wc7Q9iGwWCFyy/v8cWf/CJKtKwcX+bkwpDz73+c" "+uY1Fr/xCXxvk8mbV2lEw9jkFCcfJAstQrV4qyi7GR89ts4nPnSOkC3wEz/3ZV7f3UZqR6/o" "sD2b0bYOoiTvFBwc7LE/GdNXyfT8+qzh79+o+Y/6kX5vFXf5LWZ/b0TvfRsICeKwJdbp3TlR" "ImSZ4kJkCdKC7kOo5yQ3D3WEvVHaKJSSIFuiG0L00B4Sx4IYKmLZQZg8dUnahugt0Tt81RKa" "hmevvcXVg5ucWF7h5HIfLwJB5wgkiz1YGsLi+iLi3LejBn3KhSHWegZv7bJ/s2HzSodgI2fP" "P4ANgTffvML+wTZF3qFbDHBRUllLE2vyIseYjM1b17FBYOZgSoHjo6eX+ZYnznNjGpmFDq3L" "iVZgMlBKEQWoLKc8tkJ3fZnhyhp/76d/jb/wl/8mtp5xfP0kdV0jlMa2DYsLK9hqjGqmXCiW" "eF93wCt7e0ykIVMZgzLnzVeu8eHvOc2HT5ymmk0S5mAuaIRUc6K7ovVJRIQQ2Nud8vKL1zkY" "7bFoHKfObNA7+yiVECytH6NTCmKYcH1zjzeefZHptMFkml6vpBEB4QWxaXhx/4Dt7UM+8NQF" "jmUGXeZoBTJGppMWkZVIlTqlhEjb1IBkPBrjXfJUCWXQJsM7jxICIiiVQrhjTHyqMM8tDCE5" "M32YA0ilRITk1Wx9JFMFOi+YzbaInQWIYn6ZKXbHOYv3FutaQnQg0hKAdemlSxYGJZLnS6lk" "ood5MLRSOOewPgk1mHdag0e6FiEMLoASiiwr0GkxM3mtlIAQcSGQ5R10Z0hzeEDWtgijCQiU" "VEitkvGfQJQKKXK0zIlRI8hpG0eRlXzftz3BBx4/zU/93AtcurJzdCIf1ZHAeg/i6U4dJ3Gb" "rwtSivzB4eBbYvDxpa2bn+HXAabvFk/hK4RG5N7mdrg3muF+EA3vvkwBxH6nyBe7nQXjDW0U" "zTvXJQYhCCI4TxM9GIn0je7mIr+LQPpqNi//uddoPGNrr+CTX3yNL754hY8e72EPdghIiuVF" "bN4hzAIqRKJ1+OoALUD7iIlDrj7zBt/6xOM8+Nsfp3z0GK6eUhO5xQyVw+Cpx9gcjYmTAyaj" "A9qmIZo+YijJtMA3Y1qZI3rn+fRPf4arL15llFlmqqE/XEaSYInVpOL4So6zDq8Vh5N9yrxP" "YMRzVeAfbk75/Y/0MKsLtG9tMtvZo3t2HSEBF8AH5jYZhJZEDSiNMAVR5KACwkhQgqgjqIzo" "HVEqQrQIn8jr+AnRCgINNCk6JdQV4WCHsLWN3N3n2tYBn3njIvv1Prsxstwt6XcyNhZy1hZ6" "LC6XrA40i0MFnYymLOitr7C/PWL78nX8/i7ry0s8dPY0Rmp+9XOf4+LVN3Ex0OsskOkMEwQL" "vVXKXp8sV9za32Z3dMBwcQNw2OBYV/C9DxzH65LtA0fUHWLMQCpiNCkYO88YnFhl6cw63YUB" "P/e5l/mLf/mHaWYHOOco6VJ0usmjQ0QQWFs7xfTGa3x8achTD25QTJZ5/uYOB7MGGTXTA8dn" "fvpTfNcP/i4WVs9RTUYgFCbPsdYmtIEQhADWtlSTltdevMne5i3KLHB2STIY9skXFjm8OSW0" "h7hmyiuvXufG1gEza+n2Czp5nlIFfItzLvGngmd3NOXVS9cZLmnyHIzU2MkBB1v7yKzA5BlS" "qRQ5ozKc84wPR0glyLMcYwxGp+gaKcAYhXceTyQKjxRq/rnUhULEuVk/AAofI6311K1FK0H0" "FtdaVKdP9IEo0sucCw1NNcbOpgRbpa3DQMrNFBJlNFmmUDKgdAJjKZW2B0HifSDaJnm7YkSp" "NAL0RExWIEIyxyeoaHoJTjwsh1CJNC99i5SSsrtIWzVMp/t0ekuUCysoZQFHDKkjJqQgqpzg" "E7BUSo3GpNu0rzi7tsB//APfws9/9jX+2S+/kjYrj+qojgTWV9WlutPXuWFRHj/eXfzGQ9e8" "cX16+GUSGyreQUhxF2F1pxEh3B+a4X7YV7zzvUF0tTRdXRRYweydT4qI1BHhPH/pJ3/lJ37H" "+45//MKp4VBKjn2FSLxb5+pu48E7ZT3e6774mtWVm3tcubnHBx45wbS/yK88+yofPvsmO9dG" "jHd28c6Rq4iONkWDANnKKZ774mXq/RnxfQ+zvzTgcGxRUqOlZe39H6SZjMhPrNM7e5qtF2do" "o5lOxvjpISo0tAJyY6B7jBc//QLurS0eO3WOU/mUa+NrHMRANR1RmpLKeRaN4cQjDxOqmoOd" "LYoMjg9WOJgd8jO3pgi9z7/z5ABzfpX6uVuws03vVA9ZQLrqKZMwShAqEDNJNBZyCSoShUDE" "APk8fy4vISsReYEscihy0Dq9m2/rNKppZ4TJiHBzh3B5j7Bd8csvX2fPgskWqUTGpf0JxTTj" "5uGMpVs7dC8b8uw1lhdWOffoo5z/6EdRytBfWebhf+NjrD/4AJefeYmXnvksz195k839HTqD" "VarDCQfbI04+9ggnzn4DURdsb77FG5svsjsak2UFtplh8gJVVXzbI2t0ul02d2vGrkdoLGWe" "Y5RBZh1aB0Ya8l5Jb9jhtbe2+H/9ub/C/q2rBG+REmbVjOFwkIRDsNTjPT6ytMRjj30j37g+" "QK0vcoqM0O/x5uYOUxvYO9hltDXlF37kH/I9f+QHOHbyBNFZskzhQ8ria61lNqtpa8+Vi1vs" "3NiCZsKTJwrWuhmXLm+yMd6nNJqd7Ru88NoNLt/cwWQlg+UlOp2Mbq4JLtCVJVUzo21aHDlB" "C6a2YTTaZ3nBIFvBbHuP+vCAbG0dpVOHxoeAzLsc3Nqkmk2QKtIp1yg7BUoJtJIJZxBDesx9" "ROVJkL3duVIydbhCTLBREEiZxmpECT5A2yJNger1aW0FShCCS4iN6Zh6NkroB5EhVMJGCCnQ" "RmCMTJZAk7pu2uSAS+HYgG0akAapNEor8szQZhlGSlQbUvC5zogxJn5MCHOeVkxCL5I2CFF0" "+kOMUmS9LqbskHcygm+op3tpHCpk+lmkiJ/UJVUU3UVMvsK0usXiQpff/Z3fQL/f4R998jma" "xh6dzkf1r7XAul8BdT+XEQF7pj984lgxPPOrN6/8tcPJ9BZQ3sdI8E7dnLvlDt5uVBju8LXx" "Dpf169wuKXNdhyIqwrSx229fP6GUQGghpeO169u/tnVu+cJDSi3IGMvb3Ja7iaz3Ipj+uRrl" "y8Lw2AMb/K//7Iss55r/buMBWrWMDxHbBmQOipg2lgzMJlPefP4lFqRmS0QOL26hcgkhorNI" "r6vJ9Q6dW7soUVJN9qgnkU5/AEpRW4ttK8zSKrtXD/B7I7754x9j/QOnMJ2WerLL65v7/MLT" "L/H61V0W1yMaS98M6CBYWTlGcIA0NE2DzwWf3BzR+Mv84PtO03uoz+y5Me6KZnCih1aOOGvT" "eDACmUJoAUU6ONE23duFQShN7HSI/QEMFpDDFUSnlzo/wYFNRuJYTwgHtwibW3A9oG7V/OrF" "q/z8q8+gygKp05ipCZHpaMKuEDw/O2TQW6JtKqrpM/R+7tM89LOf4tw3vI+N1TOEoP7/7P13" "rKX5fd4Jfn7pjSfdXLmqU3ViaIoUJZESKVoSJSvtWLBm7YG9Gq9nYIyxYbzwBmAXXhg7C8wf" "xsA7M5jxencCMLbhtTSSbFmSRUmkKFIixdhk5+4KXbluvie96Zf2j/eSblHd1dWyEu36/dPh" "HNw699S97/uc5/t8Pw+zG7d48dkXee3WayRFwnA0ZmBKJmXJZGWF7/zBH+LUE0+zd+0usVry" "7NUaLyJaG8aDIdWy4YOnN7hw+gTbDSx8Ruci4HEmkCUpEUE0EjUwyEwzX1b8j//DP2L/1jW8" "d9R1S54ajAlEaxFK4duWn9xI+OsffRedLii/8zsIq2dofvs5VpaBZtVxY++IVKeoXLF/p+L/" "91/81/zo//o/5JH3PUPXVCRZjneeEC3WOnbv7HPn6uuE+oD3nhmymTiuXrtFmhu+/i//OZuP" "XmT3YMHt/UN0lpMPhpSDHKMFOjNEPNooRJeTNApnDSF0mBRUKhFEZLVkenOPtoXMZAid4Dp/" "3G9o8V1DmaV0tjvuezQoqdDy+Fc6+D44riU+RHwQEBUh+B44GiPWRaRSSNH3E8bYZ5wSJKGu" "CEIhNbRNfexAOZxd0nZznO/wNoJwSJmjpO3xEKFHRAgp+v5BrTBGE1E475BItFJ9+Nz2DCxj" "NMYY0kRDc0SqBeG4DFsge4dMaJQ+Fkgy9GBb278f6XB8nB3rtyPL4RiVJdSLGd47IooYjz+M" "oHqhRSTNh1TLO/0HEwk//JF3cXJjlZ/9lS9w6+7Rgzv0g/NvvcB6O/H0du7UWwmiP+DOXNw4" "8a6sLMTX9rc/S59jyt/gOsk3iB35Jl/3Xm7W/SAYuI/R4JuJulhITNZ1yRzfXr6ze/iN11ck" "STQyi1o5NtfH7dF8eUW49l2JkuotxoFvF3C/X+H0JyayPvTMYxzMOm7tzLgrBa89/3Ueeewp" "RmVJIyBRERMTvF1gdM5rX32ebu8mo2e+m9p62mnFaG2VECP1omM5W5LnoPbvMig0g9xx98Zu" "7w4Q8LYGKWk6y/NffoUyFhzkGa5TaDLy7BSPPXGKxx6/wO9+8cv8wq9+mt1FhV82PFaMGGQF" "oguYLEeUIy7fuUsVpnz67oL96hJ/6V1neej8gMVrCzoH66sKNcgAiXARGgcyEDuPaCMUgkhL" "1P0NKGaGOFlFTraI2oCzEOo+bxU8sW2Ie3fh7g7ijkUcab5694j/4dWXOBKeUgh8V/fQSQzW" "RzwdEomKnkQpTDYiRMeLr77I819/nqEYMBCS9TwjTxOe3lihMRnIjNwZtBOsD8asnTxD7GpE" "M2N/echRu8RkJbPZnOiPeGKc8d2PbbDXBo5ahZMGLwVG9iyjKCJIR7k6wQwLZJLwq7/2Ozz/" "9ZcYDxIgY9sHlnVNohVd25Iq+OER/PQHznI00CxjRK2sMzi1ycnvfS/5S1skr93i9RufwUlQ" "OiHTArsI/O4/+RfM7t7ise/6TorVDdq6Brqe4LRcsq4qLp4fItua5158hZjkTMoV7uzMGJ+a" "sr17SOcFw+GAvMgR0iOV6HEHRuCjQ2hPWkiyaBAoJqsleZkjupY4rVi8fAk2zpIUA5wHFwVo" "hatqlIiURUEWEvQxwJPgUYnpOwMFBOfpgofYi6zgA1JKYoDgXS9YgyAgCSHSNC0BjQoO31WM" "BkOs6n+VXdcRRYN1c3xoaW2NVimHhwckyZCiWCFJZN81ePzr3y8K+F7c0HcTIkQPJw19r2F0" "x5R3PEqaY1aYpGs6kiIjxnCchYsgNCH64x5DQQh92fYxtaH/nrzGB0+a530TQF3hbYuSGhdF" "T6c/RlMYmeGto7E1qRkgZcL7n36YU6s5//RXvsxXXrz54C794DwYEb5DUfYHxI/RKjm5On5m" "2iym+211+Q3jwW8IhvAGYfVW47X4FgLvzTJU9xoZ3qtO5w+cum1F7awqSt16Ib7hbQsfnA0y" "eKmVKVNZBx+uh2BJjdL3GA2+lSP3RpH1ZwLlsLYy5PzpDX7+E18EwIfIjZee5d0nhqj8JGWZ" "IENPRJRR4zrL3Reuk5uC0cYGUipmezNsFyiPmVBN43BBYzTEYBmMxmyccExvvkaqetCjKSbc" "ulVz7eVbXHz8ceZeUu1VKKPx9S5GVEwmJR945hnWJyv8o1/4BOGwxumErXyVE8WIWWLwWtHp" "kitXX6Lxjt89qLny+Sv8wNqIj6wOGSwbdncM60ahU0FMNQLRB9e9h1bSg5kiaAdBElNDNAm4" "lmgbAhGqA2JdEVtPPFzAfgW7jnrP8fntHX72+mUOOovPU7roSSL4GIjekghJmg6ZrIxYG03I" "8jESg3MtPnREH0mV5HRp2FpZYTweoLWGNKP1sHvzgL2dObev3eDX/8F/x3d8/4eZnDrDZ179" "Ast2SaISFlXLVi75occepREZ+zVUTtG4FqM0w7UJ5WjYC5NCEbWgWBlx884O//znfwHnApPx" "hLZtGQ1yDp1jOpuzmQR++ok1vvfsmFnaoddXSdfPokarGJMwOn2aal+gry0oizE3bu+TJpqN" "k5uMTp4AYYnNlNnOTepFS9DQdh251qRJZFgqvvrclzncP+B9TzzD2Yvvo+kM0U6pSdhfWqSU" "FHlOFK4PoScaJH2mDygHBVpGpIhoIVjdmqCjRuxNidv7VHtTwvo5nO8rhroQkWnCsqpQKmKU" "ROiCvCwpiwJCD+tUWhJ9DxXtXSJJjAprPSaRICSdB+/d8QhNEGOkaTucDSRlCvUCNSx7h0wI" "TFbQWUvwNYvlEft3d6kWDV0LSVqyugkbm5tkaXrsXvW8Lec6miqi0rzHRcSId4FwDBsFECFi" "G0vUitg5MDlR99U+9jjE730kBo1UBhc6nO/ZWWlZYrv2WIgVhOMtR+EjaZrhfCQgcCEQj/ll" "ne0IdGiTI2xPsW+Dx9ORmSEb6yv89b/wPaxNnuPXf/flB3fqB+eBwLqHkyTexs0KRsq1rUH2" "rllbv7a7mF+lL3eOb+Z08QczWW/noN1PRc69xFW499fuOd0hhr2m6Y6OhaBcLJpdG+OyTMls" "DAqlFkoZMi3MfQgs7sP9eydjwz/y89RjJ3nu0nWO5n3s7MRqyWQ8ZHGwR3ZqgtJJv/21bBkX" "CfNZAzZw4pGLhDRnOWvpLJjO0zUCozOikDRVTcwM+AYRLJOtLaiXVPt3GW6eICQbfO23PkGZ" "pAwffojZ0pMaS7GSYuWAatGwaGpm04bNtZP85R//IT7zrz6HaAPT/SMefvoMywBjIZGbOY27" "yJXXXyX6A+4Gz8/d2ONrq0u+f2WNp2YrxMPI+mqDShXR9yMOsoIgBCKPvdnqBcEMCEHgFzVx" "2cFyjlgsEPMZomoJCwVLSzedc3Xm+fU7B/ze9DbKRB7b2KQYlqSuo1lUpMmA0eppJoN1inIF" "OViljQofc7q6IcZ+i4xQk6mGzeGA0XhIMcpRIhLqGeVgzInHH+XaC3c5+vXPsLx1g71Lr/Cp" "F7/K5btXSbRknAlOlQl//onzmHLCnblk0XpshERnFFlOViSYss/qyESQlimRwC//wq8z2z9E" "FjkxSqRUZLJltVScThP+2vu3eOxEyVJ45Mom+YmzdGTMZ3NW1teplp6dO5HdeUYbMk6fOM/0" "cJe27jCThMHKmGd+4KOMT5zEO80//kf/lE9/+l8wLNeojw6wi4qq7jg7TPnwT/4om09+F8tp" "y/LOZXZuXyO6nlReVUvyIgEdEepYdShNkmh87Cjzos+LRciGJbQ1qnLEuqUOKbPaoaZLVKZB" "y14UBYsSoJVAGkmZp8diq6etC9/noaQ2xABd64jHnxHDcbBcHtfY9KZSwFqP6zq8cxRJSrAV" "XVrQNDXloGC0MmJ3Z8ncB2YHR8wPpsyOpoDEJUuij6yMx33tjQSpBEorvBN0zRIdI957kApl" "CqLs5ZXRAicEeZIigsDVNWIwBCWwrkdi+AA+KrrWkWQZMinIRiWm6JDG4KcO1/XQU5kn37yC" "RgE6Sb65LYmCYEM/EgwenWbk6YgQLUGCC56mq1FRkqYpf+HPvZ8iyfil3/76g/D7g/PvnMB6" "p0H2yJtvE7qtyXD9zNZ484tXb36t7bqKNy8/fjPREHlrThZvIrDuhWMIb/Hce7pv6yvjjVzL" "pPV+1rTfFFhCStUIIdooFLsHtbwixW1FYJgna8fvvXiTkec7QTD8qVHfT26NsN7xxa9d7UcR" "UnL6xBqfvbzDu05OGBhH1RxgvSVFkiPJBwmPP/UQeuM0yxixViKjochGKJHQ7O3RNA1RCKIX" "qNIwXyyJYcF4Yw0TWoaDAXdv75FVjrUnHqXYOk3dRqb7h7Rtx8YjD1MtJbPpXk//7nYYZCk/" "/PGP8PlPfpHnr76K0wXnn3k3sq5ICIwGAyYrJ2l3K4osReQd133DP5vv8b5uycf8Q7S5YZwu" "GKb9KDJaD0rjG3p3YikI+xByS5jPCJ1HzOaI/QqxrJBNw3zRcKWa87XFnMtdyq7tmU4i9N1y" "Z3LFB9/9XlbWN5A6p4uGqlFUbaBtG2xX07SHuMJDDCRZhjIZZTkmL0cUkwlJZghNS7sfGa6u" "UC8b5pde5eKTFzCbE37py5/l67deI08TnLO0Dfz5J85x7tQJ7s4jRwuHE/2PZpoYRqOCtDAI" "LQnCkeiElbUBn/70b/HcV7+EMYYkL5nNDijLAUbAE6Xlr3zgBFsbE3YWB2Rbm6Qnz1J3DSIZ" "MD2YMluZ4kLBwmlcNqJY3WJoAqnRJDrQzuecefIEG+fPkRcjfu8rl/jt3/udnq1mdyiTAXVc" "sLGxxdmJ4aEnH0JtjGibfbSOhCCQkb7w2vVFzYkSBNv21UESnHAMByVCSKrlknI4QihD187A" "B5wN1F3AWkHnIokQpEmGdy1FprAyReJJ8oQ8NQjh+tFgX6iD69zxtqDEWdu7mT1FCnfcKxhC" "T6Z3TcdsusB1LQpLFloOj2Z0J1rKwRrjrQn1coEKHuEt0/0Dpge7RNeDYV1XMd+/w2J6En9y" "BW360Z739rjgOmLrngmHznC+JkqD9xIC/ftjJKnRVJ3DlCkiip4ILzxdF/qAvYS28/ioCF6C" "1LRVS9dZtOp/bmzX1/SEKJHaoHVfQSW8wMcOLySOPo9GklFunKAJRwih8cECPceLoFDC8yMf" "ehxEyz//1EsP7tgPzr+TDtZbEdXvxad6o8jyZ9dW1jIhB9cPDl6l75Ex/P7s1Vs5Om8XBL8X" "nuFe47+3Crf/gbGkEnEcfaQV2rX+XxdsBR+ss976mNI1UV/eXbzquxhXB9lJfv+G5P2OB/9s" "/NBoxdrqgC997fXf9wq/8vJNTg8SThuPPLhOl4zxvg8DT2cpgzLjzMUJ21PLYDCgqZYsFkvU" "7pxsJBiuTsAsqOaH1AuHRGOUYOZq1NAwXltnI4Odw33WtlY5+eSj7O7uUbUWaaA5ciyeew3n" "HJ11hCCINuLqKaurYy48fopk3jLbXrJ3bRs9KQhKoGJkfTxiVD7F7HCXo8UBHUBl+e3YcNA6" "Pj5/gqZK2W5ucWHT8NBYMqEkoyBFIH1E3G2Q7ZKYGkRT08091bxiSsvzbeAr+0tuzvZpnGc8" "WqMohgQKWlvRVEd4vcKekJDkjEYr0HWUmWUly4mkxABC9SW6Pjp0MQQhCc4TbUC4OWbWkimN" "WR2iigwpPBc+9n6ubt/lV776Fb5+50WcMiQEtGt5ZmvMux85y2zpqKzERkFEYoxhPBmTjxNU" "At7WiCRhsjnh1q0bfPZTn8NLxWLZMMw8UkXqxSEf3Mj50Yc3WN9c4c5ylzpoTFqghhvUbYsI" "DYHA3rXbdHZM3bTMDrYxqSAfjKmn2zTdlE459MoQG1MuvXKT//Ef/n3C4jaT4RaL2QFeC4JJ" "Wc4OOP/QRXS1gMPrDIyDYdr3D4YIum8gsJ2nbS2ZNKg0JylSsjJhbXVMkmhs2yG1oq4sorI9" "JypqagGdtTgXSaVBG010DUYJVCJRUmFSTZod565iv2XnfF9nE7yna1pCiAhlCBEiSb9JGuM3" "w+/zytF1gRgDo0SgupbZvGF1kLF5epPFckE7n5GpgLZTRNOgg6BtG3Ru+mJCPM3iiLatiCEl" "Rk/0IJVBCIGzLSL0vYRIhXf0Rdmu32aUQpCZlKm3BN/1Y84g+ueJ/lIcpcCGnr/lWtdPyF2H" "IhIIRBTeO3TU/aVbKnSaY4Shcp4oJFEIXBR0weNmM9qFRQ0zdJLhRY10guAaJBHvLQLBx77j" "MTrb8Ruffx3rHtTrPDj/dgms+81U3cvRumdXoJZSqyRl1jZ33mQ898avKXnzcPu3vr7wJq/t" "W2tywlsIr7cSV2/K8PLW2+AD0sVwTNfrnyxEEMG7xEfObIz1s6/fPGxauRwOJ4M3CMc/cbzC" "v+lRSnLt5uHvoy97HxBCMPeR//Tnn+U//b5HeejpHN8uoK7J8zEbW+d57qUXqLxn5WSCmc4Q" "VWBRLXAx4qOj3BizMjlNdbhHtdynzDUyldRdYGWUEOoZG6sl44+c5MAo6tvbOGkIHeSDMU1T" "07Z9b53REpzC6gStWzZPrvLouzPm48Dlw0O6IuNwUeGFpEgTEqMo87OUswl3dl/H+hkSyStJ" "jamu88HJUxyGJZ+9+TLDZcaGHrEuMkz0FLkjEQG9U+KE5u58waxNaFTkyHYcxkglFFZkCNGh" "rSWKmuFwwiTmnNva4OTpk5gkwQnD4XRGWO4zmqzgW9uPl4RCOI00CaoYINKc2FmUnaJsTRpa" "Qtdya3fGV1+7zZWjBXuNZb9quLN/l7Qcsbp2gsODPer5jMeyjB9/z0WapKCa10xbiw2SLDWs" "ro4ohwlJnhKiRUpPMZD4uORXf/YTdIcVwTmUEAgjyAR86HTJhy4MSMY5+1KziBGVJZBkIHPS" "4YAocrASe1SDF2TBYRdHGK1ZWR3iq012divWHjnNlTvbfPL/8fe4vX2D6cHr+KBwQZCkJa2z" "JDrhJ97zKD/ykz/NnVuHDIIkpiPq1mMSBVoSBXjRu4QmyVFZQlAQlMKYBGUkUQl0mZNlGdjA" "cncPpz3TaDgkI0HiI0ipe2CoFGhjcPR1OllRkGYp3jliCL1wcA4feixD8IGu7YjRIpMUqfqR" "W93YPqt0zMWK3mGUYmUyRMz20LlgcmIDXItdHJEnCosi14aRdoQkxbcdsbOMV9ZZtgu8sxB7" "t0kpQzEokYkhBkG1mOGcR6oEj0R0HiUjQkucc5jgyWQkKE2SZmitegyWEMevEaxzBBfwLtI1" "HYFIdI4gQAjVZ6xkwFnbh/110l+0pejrjZB0MdA6h20soe7o9qck2YCIQUmJyRVeSGJTgZB4" "F8iyIR953wVWRiWf+NxV9g7nD+7eD86/Ew7WW4FD30pUvWmWatE0vqoXoMQbx3Nv5l69ETIq" "3sJtejMn6s1GhG/nat2PkxWDjwtxvALdl6n2jy19CJ0xTiM4sbWxevjc5aNZ1+1NTCgQov+4" "+/u/1h8Xof2PVLi1rX1TTo1Skumy5QuN43bjeGKxT4YEpRgNC4zwPHxhjcu3Bb5ZUow1TRuP" "y2wty+URrV8y2lgjGUwIXYX1HSr2a+eVc4Chjh03b75OdjLFVkeILMckGW01xQnNsusDzGmS" "o4Wi9hnTRYWeQNgqWGs9lQsc2shh8D0sNAgy06+ur6+vopVnvriLb2t8iLwSjzDVFZ6YnKXF" "czPus+MD+8wZrG1wOF8wO1qynO2jTIYqhizaPWhblDGkgyHOW1zoq0lCUDjXE7QjAikcRnbE" "4JB+idIG8pwgNCG0CKURwZJmKUoETCpwdkp9tEtd18ymO1y6cptnL93l8rTlcHGIo3dEglRI" "M0ADXipUkpBXC37s6ceYrJ3j1nTJvG37omytmYyHjCYZ5UjjXIsQHpNJBiPN53/z8xzcOaDI" "Sg6O7iAknNANP/LIGo+dGtBqSa01OIcebWDyAooR1kuyMuf4/o+vLMEuUHXNifWMmKWsnV3h" "se98hLqr+dXf+CUuf+UVZtURRTlmMNzk4OiQppoxzHKm0z1+6ul382M/+JNcXyh27h4i7lak" "K5vMt2+RZJ4yT9k+aMiyHJMlNLYjWokSGmsDrfXYzlPmGcro46C5ppm1tHlkRo5Lc0TT4RzI" "LEWI0PcICokxOXmeYLKErq2J3iKVRuBQIvaB9HDcm+ginW1JhUJngrbr8FEghMA6TwyBtmvQ" "ONbHKXHfkhQJtq3w0ZKoiDL9WC5JDJsrA/xyxpKA855qfkBaDjBJgk4KdFKQFBkq1f2IzgeK" "0ZjOBoKPKCTQIn0fYpfRI7RCWI+OEVFkSCGQeOTxSDP4QHAdwTqQmig1wTpC7IvspYxY2/Wl" "0EoRhUQohUAQXY97EFJibYfvAq71RBtpZjPkJEVspgRXEbzvs5tKoZXBOUsMgmG6wrvOw7hI" "+Vefu8LVW/sP7uAPzr8TI8K3Gxe+lcj65nMPZrNZV7fBqOQCPaKB+xwRvpUYuV+xFO9jpHiv" "EaGwwS+iUrFrGhnfEMQ8mFbuaO7aC5OCMs9PdVHYhdK3t0ozThKTdG1n6cP8f6YE1B/2OOeR" "QvAXP3yRmdJcunyThy6coQP29hcc7LzK5uOPcW13j/3b15mcOsXmuQ3Ovv9JuqhYHC3Yv3KX" "djEnH4xIR+vUh7dJgsJay7zzyDwjO3UGt3uVIk8YDXMO647UaIzWaCVoOmi9p2478rRASFi0" "gXQZycYJYsMzJqMMgaKMXJku2K0WOJ+gUsN6nrF5eouXr1fs1QuQAglclrskTnN6dJbgNPvt" "IcPNDaKGjfEWW+dPM93bZf9uRYiQiIS9psb4gBlpitTQ1C3L5YJOKlaGQ9JUMSoz8vGEOiYs" "dw/YvrNLlhlULijKCXmm0EkGEQ4P9lhULYdHB0xnC+4eLrh2d4oMDYfTikUItDoBkyCjQ5sE" "FyKdC1TLBV3b4jrLTzz5br7zIz/Gfg0yqciyfQYsyZKEyTgjP+6U83ZJOswYrZVsX77MK195" "BR8anJRoFfiuEyN+9OI6a+sbLGKLDQJsJCkzdFagRyPIVnFW0C1aEArbeFznqG5VCCdZ28rY" "eOI0w/Ob3N7f5hd/+ed49bXnKYYFWWpYLvZJTIqRnrapEAQ+OFjhe8oz/OJ//8ssxyPG5x6i" "Wnh2dl/EpIEPfPACw2HG7f0ltbUkWYJ3kapuSRBIIQm1Q2UWXXpkCBijESQ0bYMdJLTOY7Qm" "SQtUkh0H1tt+Gidkv6WnFUoqvLN9r58SKKGwAmKesZhVPTuLvnOwbVs8EusDOkkIISIl+Ogg" "eCaTkskoYeFaTFHg2hpJIMkSTKZQcsjdpqaeObSTlErRCEXwnrwcMFpdJ0n7CiSh1PH2Yvxm" "zY0KvVCSSpJmKU1jCccsK6MkorMk5YhGSIQAoyVCBToH0XlEhLZqkFqjTN6PBENfhRUiOG9B" "9qXYSikIfdDd+YDtPF1nsSHQ+kDbOITtx4Yi+J70Lkzv9AFSK4RUBB/wXc+bK7MhFzYFf+nj" "7+K3nr3JF5+71ofnH5wH59tcYN1viP1edTh/oB7n+L/V/qK+ftC0V8+UwyeA9A3i6l4iK37L" "Y4K3r8eBe2ew7gvN8MbvJUIbtXSj0URnaSpYNBEQIRBofevrmtA2mWtt9cqt5dWnTww+JIXQ" "0Ed93sH7Hf8sCqs3no+97zyPnl3l1599nV/Zm/J/NxllOeT29ZraCda6G6isQMdIFC1bTz3G" "+Q+8m3y8wmJZ89Jnv8z2119DKE+SjajnCU1TQwp+EbH1jK1BwrlzQ7rmgEfOrXPrsMJoRTFM" "yVcGHM5Lbu9URB/xziMQSJFROce8DahcMdu5y+xozqlHVzl7cZ2rNxu2G8tklLE3O+TLV15l" "v3MYo9E60AVJFJLn7R1co9jIt/AqUtkWbTLKYkAUjvzsWdbXaurlgoP9GVHUzCqHdw6jNdZb" "WtdhrMS7ikEyxBAxMmdl/TSqHBOFYrnYR6pAJyLTWcP23jWcD+Acd7b3uLt7QF03uCBpnSPT" "AicTIoEkz5FElFY9m0tA5xyh6ZjPj7g4WuHJUw/xxeduY6NCJAmdF5SpYjjUZIlFyQ5CwOiO" "0XhEe3jA1z/1VTJvWfiWDMfHHz/Ju89vUkfHQdvidSQqA0IiSND5GF1sYjuPSQO+dQRnaReW" "GCR6YliZDMlGOa/ceonf/Pl/wJWrLyPx5PkIYkATkF1FvZxTCsdQadZdx3/88R/hK5+/ye7e" "lP07+zz/wivIPEUnhs7WbG5GTp05z9U7Auta6lpSjsb4EGjqGiP7G3jVeZadZZAlPU7At7T1" "kqBSvLdEmSEGE3SWHJPXBShzvEEYSfMUkyQoLTFKfJPKnqFYLhua2uO6BucCbReh6zAepO5z" "U1oprO0gOPIiZWVjRJKnuGhhMjruP1R9p6BMSAZjTp5/hu2XbtLMKowQqLyArKQYD5msn8Qk" "KShJDD1rysdwLLIkUkSUPt6kFMcLGoCSgkRrVNMQYs/mkhqUkUiZ0LbLntwuBC5KNAYshBCx" "1qOVAgQxgLOBzlqES4iqH6OCQAiFQmC0wmeCaFOc7IjDCSFIohNokaCIxGhRMkFqgRSKZrHA" "dR0IT16MQS358Q8/xlMPn+AXf/Pr7B89GBk+OP9uOFhv5a68WY/gN7fo5tbtXj6a/fozJ878" "9Nro1Yf3Z9Mr9KBRcQ+R9cavB2/fQ/h2oNFwD9fqLQVli7KdTqySKjdpkgEVIFzX1V1d7cdJ" "ydpkPYPobxzaa09vFR/TUul3IJDuB9j6xucG/hTE13sunqUoEq4dWJ6/fsh0XvFSFzjrluzW" "NWeKk9y5vkt57gQ6NTTNgv3tbZLXbpGNaxaLJa7rMLnE1w221iRpSTVbgjDkSZ/N8GiqyvL6" "c8+x9ujDHHWS8XiF849dJBmPWS+HrO7PuPzsFeq9KT6AyhJaG2iqBrkxoXz8FH5nijm7CZMR" "63SMqgOiDqyu5Zw99Tg7deTWzh43Di2uWRKCZNpWXBZ30SplY3SSbb9DNDn7e9tk5QDnFigi" "5eoILSWntta5cusW3oM6XofvlEJG6KoFZmXM2a1z5GlGNZtB3eBCQCaKLmgODnaZLzuWVcud" "7ZvM55bDgz2SJCHJEoTQdJ1jWXmE6KtcMpEQY4cPEdfVdMGRpwOSrGA1Rn7s/KPM547ddpuo" "FNpI8mHG6kpKnkuE7FBG4V1FMZBov+ATv/BZDvZmRByPjAd85NERK+MBcxdYCk0dBKEDhSdJ" "DSIfEJMC5z3apCRG9yHwriXNFcMTW2TlkBuvXuVn/+ef52uXv4aLDdIoEm0wWlEt54yk4vFi" "xAfXx5w/eYquU2jnmWyMGW4coKYJwjsSlWJbSzYYEkPDpVde46OPnmF9dcD+rKG1DbLRJHnW" "B7cjRCVZth2mUmRGkUiJ8wGlU6JJccLi04SYF6is36qTSveoh5BgdEQnPQE9ePqNyqTf3gsO" "YlS0jaeqGpCGGCHEgHQBZSIxWHy0iBgwWmFUymRlQJJqrI3ocoL4hhOlBFL1hPnh1hke+8gP" "oL/yLHKnRimDWSkpT59hvHkCmRiEkkh5jBsVfc6rB5wGpAiE6AkRfPDEQN8vKKCbVwRA5QmB" "gKPfxpRK0XWA90T6PFbnLSCQJuszZNETQsQ5j+4sPnHopG8vQPbjQSklEc83AvNCCqQyVFVF" "2XVIGemvjD313dsGrQvSYoy3R/Q6LqClJtiOx89m/Ec/+T5+9jdf4sqt3Qd39Afn31qBdS/3" "Kr7JaO33OTTOBfc7r137n77z+x7+iz/w6CP/wc9+9St/N8ZvCitxj1Hj2zk593KouIeYul+B" "IprGzq2XSxPN6mpZbN6AXSCxXVe/sL1z492bG5wq8i0Qpgmqy9PCDDMjF9Vbjia5z5HnW76m" "P8kxopSSsyfXQAh++Xcv8cwT56iallGhWdkcEUVkZhdMm5r1OOKwqrAxELOM7uZ1ZgdLgjQE" "58i0QsRIV1WI4DBpRiClbSxGQYfjcOFJVjdYfyTQRU+RGUYnN9h6z/sYn9pCZznrsyW3bhwy" "3T6g0Jro/fHFGWrbkp4bkyeSRb1AyIhNB/2bJKDrlhAV3/H4wwzzDZ679llIFK2r+k/8IXDg" "50x3F5w6dY557Jg3HYtmHxECRZEj84ymswwzzcm1IUpnCKWZDDRHU9+vxUfBvJpx684VTJlz" "VFcs5nMef/pdOCH57S99kRA6huWENBmze7AkekiyguAdyWBAB3RHC+qq6TlVUtLNF1jlMWmK" "JuJi5Gh6gPGWH3vocSbDMZemFhEThJAUpWZYtJQmkBiFMR6PIwrLeFDw8m99jaNbt1iGyNZk" "le974ixrqwXz+ZSFDxxFQXc8LhskKdl4hXQ4Pv5BtKRpAXGJd0uylQmjzRMs5oHf+Llf4hOf" "+Bfs+yXl6gRFSpkPiLbi6GCXlZDyE488yfvPb5FnJYuYkErNxne/D7O5xkN1yiu3j7i9WBKE" "h9g7llpn7G0fcOO113ji/BO8cC3gHDRti5QStEGFngkVnaKpHJVpsHnA7x3h5ks6s4UXHWlR" "kK+MMYlCa9F3Uh5T0YWUfc7PaKTIULLfxnM20rUW7/p8nTIprbNY+w02VtKjuFS/ZSiEQMhI" "ojRlmdPNZrRCkE3WiQic9aRJTt0GfHC0XYcbrjJ66v2sPbOFkpK2WZCUOWluSDRoGREKIgEp" "JEpphOgJ7VIqOttvMUol8MERuhqRKJrlks5G8jyF1BN8oLUWnMcj8Z0l+F5Y+s72XYNKImMf" "cidGfBfw2uG7Dp+0YFKE6LlfCIkQktY6XNuhuo5gG6xr8dYSjSS4SKIzhOjLq72z2M6iTIaz" "nhg9RieAJ0bB2ZMF/9FPfZBf/PTLfOHrlx/c1R+cb1uBdb+1OW8nsr41yA6QvLi9/YXPXHnt" "7//4xUf/xnPbd37tpVt3PgcM38bFebvXc6/xH9xfVuutvncBRClIfddqXabJynCQvOG57nbV" "bHsp2TTp+eGo3JrPZl2SyrLMkjGw9xbC6K02Lt/OoXs7cfbHJLAEs2XDteNPkK21RCRn13KW" "bUvIc+7EhE9evsZ/8uhFwpFDTcbMF0tc3WGqBqFzouhvRsb0XXz4JWlqiFGwrCryJCMqQ+M8" "6cAwOrHKwc4hw8EIu5yz/fILRKEIWnO0mKPxpBqMEshjwH4XFMuqI8kH3LEN81u7bKwXKHXc" "tZYNUMkKRTFgbx54+SsvsTYYsTPd7zM5EprgaXJH1U0JtzwrJ86QJyu0vgItqKslR/MDVscb" "dErihSIGj1SSKDtW14bICJsnVxBSc+PSVXKzwcI1BC24dOcO17d3mLeOk2urbG2epm0tZTWk" "qhxpLljWc47mczpiv3lo+947AgRvKYdDrHcoIUmkolGeDw9WeWJtleuLAFEyKDQ6EQzKQJFG" "VOiQQSEREFqKgWb3pcscvrbDUGcUieGZhx9mtLlK1cyYdZJ9D0sH6MgglwwmI5KiQKf9aEuZ" "lLY9omv2SExBuTLm2ssv8ol/8ileu/QioZCsrGzgsTgfmDZLttKCJ0eGH7/4bs6cvcgrL71M" "suaQZYrJByQDw3h1jezio3xw6km//ho3dw/Zaw5ZLI8IwiOxXH7pEh976DwnJjl39huMUn3x" "sjkenTlPFIKqajnwlnJlQH17h6Kt6JSmk4pyfcJwlKKFIDhPmki07ouV80T3v6Teo9IEJQWu" "7airhvm8oa4cs2lL3XmaRuJR5FkCojeYtRYokyCloGtbyiJhMBywf/V1wngNNVzDBodQisYJ" "ggz46Km7hrYLhKQkWxsglcHYEiUCSvpeYKl+Q1Ap1eevZH8JkEoRPMTYoySE6DNObb1A5yUL" "1xLTFSIKoXpBKI4FpUYSncS5BV3rkBjqrkIoyBLZX8xjX83jrcd3Alv3qIYQFRGPEPTulYe2" "bRmahKgStIooafoibG0wqYLgCLFFSI1U/WvWosS7CqEiWtMLvBjZnBT8zE98gHOn1viVT3+d" "qm4e3N0fnG9LB+udIhvu5WS9URgIH4L4+Rdf+K9Ojcr3/Mz7n/m7f2+x/Jm96WzneFQo72NU" "Jt7BmPDtBNdbuUd/AKugpVC4FtrKjBJp3qg9WqUPrZYYv1x7bGvw6P50r0m1Kh46sf6eS7d3" "X30LQfhW79vbuVn3Qk38sR3nPIdvyEBUTY33njMbY+4eVSwOPK/dmPPK4QGXpkseHwzYLfsC" "5a5uCDaiMkeSDelCi0Dhu47gLIsQkFLjvaOznhA85aAAnSLEnNeuXENku4w3NjiYfZnh5W10" "UdJ1Lcu9A2LwVLVnVKQ43xKiRItA11U03nO7joxVznh1lWYxp1rWiNgi9Jj9Z2/zQ+97Lyvv" "PcHO4XV+6wu/x+efv8ZRUzMoBxTJhFoG4uEeRTkiyVao2ynCZLS+QyrD0XRKovpxS13PKMsh" "mq4PDIeE6axFD7aY1i0KRWIUy3rJvF6QJQWpGXLt9atkacr27iG2C6ytrCGlYtFUdA4GxZg0" "9AKgWtSY1JAlObLt0MGSGMkZnfDDF05hg0RKQ6EleRIpRpIskygNJlPgO1ztSbPAwe0Dnv3M" "ZWyl6ZzjXc98J6OtE0yrQ7omsG+hlRoXWsrCMFpNyIearFAUwxytDMvFAa5r+4ofY/i93/wM" "n//kl9nd3wZd0DpPKSQDU7BYdlzICz587nFOaMPBomb7pRfoYs3qcJXJhZOUJ89hVjdwSEbv" "exKRr9P4EfK5F5jtNXSLGU1zxKA0NFXNjddf5eIjjzGdOQ7biIgCI/vkgW0thEDnRM+LSg0H" "u1NGk4xFAJtlDCZDRqMMqUUvkgmkWvfbkDH2vX5K472naz1tYzk6qtnbWXK0V7NYVnTeAwEb" "eyipUSBUX4ocg0cIQ5F/YzyYMd89Qgw3ESalqwLGpCzrhio0KA3OBboYUVmGEIpEKVzsxVQi" "FUY5tPQg4jeiVgghCSH0G4Hx+OIiFcEJAoEQHTJ4Wh+Q5RAXHfIYNBqjBykJLmB9h6cHuHpP" "vwEbJdB/fYxBqIjJFDpNe3HmeyELxw6g94Qo8Gi0NjgkSid9rVGZkhoF0SGl6TEQdEgL7vja" "YMyAGBqC6xAETGKQWpFE+Ph3X+Ts1io/+2tf5ObdB1uGD86f7pF/DF/zrfhR3EMUfFPwzaqq" "+v988cv/B+85+lsf+fDf21wZbhxnmuLbiKZ7/dnxPoTT/by+N1epWnVJktZKaJMq+cYORXVh" "azVKIUiLTL/3ydPvubZzeKl2rf2+J059FCEM7wwx8XbP/RN3r97s3LxzRJEatk5M2G4DX742" "5dbBgt255XcPbpClArk/JY+Qa4NWkmgtoatp6yXL2QGSgFKKtq2JoUPJhLqFpgvYrqNtakwq" "2Tp1omf9qAQfIoe3b7F/5SpHN26xnM7obId1HVXdYq3Cu3401LSOrfUhW+MUW1uQKQGDd6DS" "kttXtplPZ2SnJkSdcOrCef7yT/0gf+0nP8hKbtg5PKRqG0QQTGc7VLMjaD0ShUCS6QIlNcVg" "hE4ShDZ0MdDEwKztUJmmjYqb0xl36o6b05pOCIara+hiArKgJWNvtqQcrhGjoWl8D1lUAlDk" "OmU8HOGjR6hek2spiJ1kuT9FIchEoGw7fmTzJIPJCRahIDOK8UAzGhbkRpEYg5ICvMe1ltBa" "TDR86XOvc/Wo4c5yzmi0QloMcUJSOclRE2kdRB9YWx+ydapkdXNEXkqSrF+vT7KScjBhOJrg" "3JwvfOJL/N4vfw5bR3zUKCHBWexsySPZhH//yQ/xg+efQLQtV2dTbi4Omcua/GyJ3ixJTp5A" "jtfonKdeVqg0pctGJGeeJozOMHeBLF9hZbDB1vpJHj67ybnzp1jdWOF9D48Z5QYXQ49TiYEo" "It47iD1eoN47IrSW5KEnOBI5VmtMKklzTVlqhkOD0YLgbU9Jdw7XdTRVzXJeMZ8tmc9rFnPL" "9q05u7tTDo4WzJcNTe3wLiCUIC0yijLHmL5+yPmOJDFkWUZXVSwqjx6MCRECisYHGuep6orD" "oz2m832Wdk6QghjpsRGyL1BWMiCPr+pSyONfftFnr45zUEoJjOnHhvEbYksZgvNgMkSmiQS8" "C3jv+vJnAk3b0LUN3oOPEu8jAkMUiijEsTsVv8nNUjohBE9wFoiI2Dc+aCVR4ht/vkBBn22T" "BmMS8jyhyPp/GqPRst+2FMKgdIoUPepESoNO8j5fpvqFDilS3v3oQ/zNv/wTPPPUow/u8A/O" "t6XAup+gdbxPEfOtIsLszud3/9vf/fz/vnX24G/98Ef/4bsfPvtRYAr4N4wW4z1cm7ejs7+d" "KOM+3Kxv6qudutvbU9wUAmGyYoN/HZ6Xn37h6rMzZw8SBA+d2jx/96B+5aWd7UtPP5J/38nV" "4gl+/ybh22XF7ie4D28f0v9jPdYFHr2wyt3pgjqm7BwuefXmERul4b1PDKkyy+lYklQNRgii" "67DOUi3nJIkmBEdqJKlRJEqQakmep8csHknbWeqqQpuc82cmDMsMowRaJ32uJgQIgeh938km" "BE3XMp/XtI2naXqBlhhIQ8At5xxce43l4S6uW3Kws88Ln/8ClQncOFpy5+6ca7ctNw8KLjz2" "nfzMT/8Ej5zaYFHPcLZiNBjRsuTgaAdJgkIwGpQoo5FS0XYNVb3EB8/17VvsTI/QxjBrF9w6" "PGR/2TCtasoyIx+v8vXLV1nUDuvg6GhOVXcsmx5YGQlMF0ckecLKygZKJzTtkighykBUER89" "oa1I6oosSN6Xa57YOsF0qUjSIePJCsNBgSKgtUYGiC5i64ZoLUWe8YUvXeLOXs3SBXSRM15Z" "42hvh/2bN7BB0jpB1zmKFNbGMColiQkY7ZFhznJ+nenhFXys2N27zb/6p7/NC5+/jCJj0dZE" "kREEbAzHfM+Jszy1cYLQNdzY3uHWwRGH1RKfpaiJIj8xwKxsYtEs646DwyVt52hbT6tTjlRB" "snmeIh0ivGdQlozzgseeuMgT3/VR1p/4AA9dfIT3nR8wyhKc7fBN11fpSIF0HcZFdi5fYpgL" "ZD6gUxrKkiQzaA2JEkgCxiiS1JCkGqUF3lvapmY5X3B0MGM+r5gta2b1kqPlHBsDAQhCUAwH" "DFcKBkXKZDRgNByQ5Rnm+OcEJVnsHtDMG2Q+IESFSgyd83Rdg7NLmuYIZ2sEgSxNIUbazqJk" "INGgJMjoMbqHoSqlUcYQQuwD6N73I0Mh+63IEHFt1RdKoyApiFqijEDEQHCOGD2+rbHVDOdb" "pDb4AJ11eMTxyE+gdf+eSClwXYerG5RO+tC/90gZECIifETHnjzvgyVGS1SGGHs0RKqgTFWf" "rRwPKQcFUmrapgfpiuCPBXyOMgkqLRBSkmUZJjFEFCc2t/gb/8uf4L1PPPzgLv/gfFs6WPEP" "8dz7FVnpzcPD7f/qM7/zf7uyu3/jr//g9/zCT33off9ZlpjsDSKL+xBP9+tIvZPHv/X/yTt3" "9/ZfubP3nAwtW4l+/A2jPX1z9/Ba27ZXNYJEqML70PwXP//5v/P6tHrpL/65p/86vx858Xbv" "F/cQjtynMPsTOZ2zvHTlgNY6bm4f4Hzgw0+f4sbMcyPMsRNPsnCUbUCHiCRibcviaB9na7pu" "joodqY5o6TAqkKey32AKYDuHbTpAcOv6VaYH2yxm+8gI3nXE4DFSIunFlgiSfqdKE6LCWUd0" "gcXskOgiqcopTcbKcIPrz97oN7qeeJy7rebuQnJwJLl1N3B7V5IOTvCR7/4OLp47zWgwIDWG" "IsnwwjKf7yHRJOmQtm5pm5rBaIWsKCnKCTGCNgnCFLxwfRcvElbXNpBK0UbD5597BSlSfGep" "plNcZ9ndnTKdVkjdOwVE0WfNgLqtULqf9I8mK6yvrbAySJnkCQMB70sNP3D2DB0KnRaUZYkW" "ikQn5Dpjtt9w4/Id2pkDLxkWBV/80mt87otXWExrEmGYFBsoUyCipDraZ7F3B+Ua1lcSTp1K" "GOQdiahJZIORILxDuDmu2+erX3qOX/pHX+bOTYtKCkIQHC2PyDJ478kT/LmL72alGPD69g53" "5nOc1L0zIwWmkIxPJphhiY9gI2Ay2rZlujdn99ac/WnN0ljquOTs+cc4uXmClUHBxtqEpz76" "ETYefoTNM2fZ7TRff+UV3v/wkHMrCdJ1iK5DdC3KeVRtSRcHbJ5Yw+EQicIMUkRCz6kK/pgN" "HFBSHm/diWNquse5gHWew/mcaTWlpcFGhwsW6x2LpsKFjq5tiMHjnadqWpx1aKWREpLEUO0c" "kQwmJKMRKEWWFcdEeEv0C/A10TsEgtQkCDw+OhSeTEeMjKQGsizBJOnx6M0g9TdEVj/SD/Qp" "++B7/pTSGcE50jwjGxQgIsE1iGix9Yz26ADfLHFdi3ce53tAsI99obZJZI9UUBKlNSZNiQKU" "1j0099gqCz4SXCA6S/QBHYEYMEWBkgatDEb3m5lpkpIlfcXWYDBERIlrGmzTfPPvQyl1/Ocl" "yESiMolMQGoYlRk/8bHv7HlcD86D86dw/ijLnv8wj7/VcyNgDhf18p99/qv/l49V1UtPPXz+" "bz53a/vLr127/S+BkjffTHw74fROhMibPf6mvYExeHdre/pKOLHJqbXiMallElwIgK7qtr5b" "t9cvOPH+2LQjIL15d/rVv/vffe5/e/7U5GkguY/37H6cuD81x+rNzkuv7bE6KfB46saSJZo8" "T/mXn7/MhY8/zv/75ef4oWKDh9IzWOFxhUAYQwgOfESGhGArgg+YbExqDEpYZF4SuykiQr2s" "KIuEIpUcHE5Jq5pysEKaFn1hdIAQJSFKUl0QfIc4hud7B0oITp+akMmSPEmZjEqqeWCsJMl7" "voPdmacOLaMmUGQJnXNUy4gdBkS6wUOPCHZuXaeqGpSQrKVDuuixvmM6nyEldM4T24bp4pD1" "9VM8duFhordc3j7g8q1t1lYn3L57iyTJuLm/4OadPVbHEyoXyHRPABdYnO1X4kXsxzydd7TO" "0dqGiMAog/UNqdQMhyWZgMeE5/u3VhHFhIUskNkAoTLwnunhgtcv3eDGnds4Kj783rNsPnme" "rzz7Ip/94iWUNAzKnLViSCYymmVFmidoH8ncgnIsGawY8kyilCNJzHF3XUfoGg6nlt/5yk1e" "eekA2UnKJCG4BuUlH37oIufObGJ0wu5sduzQWERwiOhRRpMVOckgJRutoop1RLaKKcb9GMpC" "O6+oWo/tFGrREn3NysYGMR4wzjNOXTzDySefQqcpLhj++S98kq984Uvs3L3Dd3/Hu1g5u8n1" "fU9jHWma0+zcYW24CsoQhCAqgck0LljariM1EqP7DUJnLYIAAkLweB+p2o5Z3TCrFtRNR5Q9" "b6oPkkca2zCvIuOZJBEZkrLfaDz+bVdKIPDM96eUZy+gsxThHNb38F6tJJlMiELTiYQ8Xe1z" "UMEjpSA4hzCqh58Se9EmelI8RGKU/QafBGc9QuvesRI9eFZJEHjWTm4xPnGaq1deQciO6AO+" "WdI1M5z3RKf7bb9o+v7CKBAalFEoFZHSo3T/74h+4UKJBCP7QvMQ+1EhMSBFJEsMVqVIeUzT" "RxNiRGndl2b7fmsxz1OKsuDw9j7GSGSSEHVAa4U2GrRESE0IgFH9+FclXHzsCf6DH/8Y/+xf" "/TZt2z244z8431YC635F1h8QJd/y2FttGurpsml/+Ysv/Lefeenqz8+WVQtk3BvZcL8u0P2O" "Ou/ne41L111FCs5tbT68tjJe2d09nAHCe99+7vKt33r/2tpfGDhdCoGMkXS2rA+fe63+DH2A" "X/EHA+5vJajCfYwH7zUG/RM7WaK4vb3kPRcKVoc5v/R715FIPnt1zmdf3OF/8ZMPobuW1aNI" "7TrU6oCgE1zbEVyHyVKC7Winu6ikQKcF0bcQIjFqfAh4a3nkzDmu3q1RRhN8AGeJviNNUwIK" "5yNFNqBqZX/xlhro+9Imw4J2dwbOEaQnNQmnn7nIvszZvnuA845FlTAqS5RQtKnEeclwkJMP" "T7JxUnC4e5vWtmQ65+Z0j2tHuzx/c8r3P/0ww2HO4XRKiILWOvI856Bu2T6akuUp3gekUvio" "ubM9paodtttnazAhdB21q3Ft29ePxIgxhiwfEoLHekekh1AGwHYdkzJnYDQPS8NPTDT5+ha3" "xYguZEiRYVu4dW2b116+zO7BDERLYjrWg2DvxozPPX8bHwWjfMAkG7CmB2ilUS6QtB2TiWE4" "MaisweiaRATSIicfT/Cx5fDoJtduNfzWlw54+eouKhpWigxFyumVVc6trlKWA2a249ZsjpN9" "gXXUBk9E6QSRGsxgiEhHNM0IIyek6SreSkIXCYuI3W8Rvu8jzJoluXLEaDl56gxnnlhj5eGT" "dE4zWzb83D/7JZ7/0pfJVeDGjW12b9/hXe9+gnOPPIUNBiHBCktaDnAuYAhkuSFNU7yTOO8p" "sp7jRRC443GbINK2gdmyY1q1zOuao3nLwXRO1/h+XNV5UJE0N0gEwUe8i8fOVf9rL1XfmtVV" "Sw4Pdlk79zQhRKIU+M6RKIEX0IWIUSlpMmB1soKWAhkFSggUokczELE20HUdWhtMYug6Swx9" "VU0InhjEcYUP2BBoOs94kPW9hKlif2cHLUMvmKPDOosPnrqtUTLDtTN0ACUSpOrL340xSGkh" "WgS9W9yPChsCoh9ho3oWGAIle3K8kmBdxKAJoR8RCqmJx8ys4PtMmZSBbFhiiiFS0W+Tpg6h" "OoQSCKkRUqOQeKEJUYICnZR86JmHGBeOX/yt57h550Hw/cH59hJY79SpejO3Cv5gR+E3HlPW" "Obl/NNs7FiLyLb7W24XY4zsYCb7To2rDbZtQrQZ56vT66mB39/DoG4997dL1LzfvemR+alic" "mgyytcN5c3j83qt3MI58pxmsP9FNwjc7y9ry0tU93ntG8h9+TPO/+YcVi0bwya9c43seX2M/" "nfMbL17ir6w/zYW24M60I4wMZpBCbBA+khlJ2zXQBFSS9AwhleK6hs4HkujITcLAz1AaYprh" "YiDLU4rBgLZuEZnBB4uRkqYLKGkxGqzr6JY1d69eZTJaZeWJpwhSYnKBCY4QG0KMzBc1dTun" "yAoyl7BsNY0NjPJIPlhlTQoOd+8wSAoezc9ileDk1haTMsWkBTcO5rTWkxYNtw+OsNEjZcqF" "02vcvHsLWy8RseVotqTr+k64STGkYomzFof/RnYY7x1t1xJ1xHoHwSGUZrZcon3DREYG5PzE" "xTNcOHuCl+cZuweaWV1z5ZVbHN7dx+iEOlh0KrC+p3k3XvA7L7+GFZ6sKCjThHPlOhOONwxl" "IMkt6SAgk5asDKSFJksNQlqq/etc25vzqa8dsHsTxuWI9bykVAOeXF/j0a01dDKgrSvuHs2o" "jaH1AR9F70Akoh+7GcFwbUI6GKDTCXWtUAceu1iSZEVf7TAPxEWLWy5QxYCNjQGnzzyBjRa5" "MebTn/xtXvq1T2EyQ10vuHNrDyWWBO/wtkGmYy69dpWDG6/y6LkLbJx6jEYuMWlBMJLESBIJ" "WZoiy4zoQ+9YcXzTj4G2DRADi8bTusC8qVm0LY212M7TtC1Bh549FSFRilxnEBXWBZwNOB8h" "BsrCoJVitn3AstMMW0voOnSiAfFNgeV1hnUCozPypBcrKoIUnkQJhOi7LYPzLGYLkixFt6of" "B2iN876Hz4ZIcBFkoHWeNkq0FOA6qnlFGyPaSGKwCOGJdFTLJfWiQhlLDDVJCkYN0Ep9kxjd" "l0MnmMSQJJIYA1H0hdshiuOfYfHNz4lZppGhz0RmQuJiwLr+0i+ERClFUL7vf5QKk2iGmxsk" "iaIYD9BaYps9gm8Q2iCE6sPyThDRdMGBtjgkj55b4W/+1Y/w6c9f4pOfewlrH9TrPDjfPgLr" "nYwL71UO/VYCTHyLuHozISbuQ6jA/bGk3qkoUdd3D3eOqubuk1urpzZG5RZwjW9Q6ju76Kw/" "HI9H6+uT8fhw3uzfQ0CJdzgWfCdC7E/0TOc9i+Zzr8JPfZfiP/tLQ/7rf1Vz6a4nyIRPvbTN" "b7yyx0hc4m997AeIr2wztQuGD6/TRUtwFik0Qom+kNZb8sGQrl4Su54HVfiO2kei9Wjh+k++" "aUKRp+A7pIjH+ZYEHWC+bKiWHVoGjDRo+rBu1waqg0OS4Yh23uGMocgE89ojhSIvck6cXuHk" "I+epgqTem9EdHSAQKFOyuXWC2c4uZZazNii5sVgw3a8Y5EvKQc4gJiRJxt7NPVYnQ67cOqAs" "LKNBzu3tA/CK1GikDpTGMJ9NKcohIQZms8N+3AJEFamrJemwoGsbkjQlzUqaxTZRGyKa788K" "RrLgt15d8tLOlJt7c9q2ZlEvyfUAmWbHrpGgsY5SD/j8nQNeXd4lSMlIr3C6WOXM+ATRtxha" "iiJiMofMLelYko1yTGIIPnLl9W1+59Ihn7lcIbxgXSiUtHzPiXOcW1lBJ5rOR+bLJSFKlt7R" "xohKM6xtcRFMlpEVKSaVFKMBWTECfez61C1Cl7ijilAtCXVEdZZJkYCvuLu9z/b0iGt37/L8" "pec4mO4xGK/jQ6SrZxTlENKC1GQ0y1skUnBiPGaSdAha9u6+RlXXTLLA2pktZNLbzFpIxsMM" "QsBahz+m9gUfcT4yW9TMqprOdjS2Y7asWS5bYgREX+icJQkEjzR9L2DbOoigpEUpibMdMnhG" "BmZ3ZpBv4VxAhQAioo0gRIkxmuAS1HGuKUkUEYnFIwJ9z6BSuM6hTc+Oa+sOryRpnmGtI4RA" "iGC97DEJXaCuG3z05MpQz2r2bQ1lzmi0irUVy/ltfJyxmO1wsLOH0SU+aIypWN84T1nmx66T" "ANlX7kglMWkCMWCdQ0hQIhJ8f/nWRqAV5FKhK0/bdH2jgDruHozf4HT1vLaePh9JtCQvUowx" "KKHIypI0T1ku9giu7gGyGIyE6CXOejrbogdjjmZ3WNtY5Sc/9jin1wv++adeZv9w+kABPDjf" "NgLrDyOyeAsX681Exh+W5n6vjcI/qiNv7c72duv68pOhebhM9Qr/Oowv96t6free3n1vuXXu" "fU8+9vBrN7Zf4/66D8XbOFNvHBsG/pQ4WG93fID/5tc6/v7PDPn//g3FbzwX+LWv7HBtX7Je" "apb6kF25z4qUiMOaeOMO49MrLG1NoBcewgdEtDSzPZx3uK6FrC9AHqWaZnGHw7pmbes0Zb6J" "AkJwaBnpbNUXNusEIy1SabRWBNsX9G5snuXGzgE3PvN5nnjkvSzSgq4UJIlGOkcqDWfOrvOB" "H/4g597/XhbRcPvKNpd/43PUd7cxOLLBCmunUmbb2+zMZnzhteucWBkQpWFtmJMnBbuHFY+c" "WCUtRuzNA7vTOXUNWZJTNx2JVhADuVZU1RwXLGVRUhQ5rfN0tuuLhaOnqZe44FkfrnB0dEBR" "5JRJwsezlO9NNnjhUscvvnKNKoUsL8AHNk6eJVjJ7GCHSMAJgQU6I3l+b8qtpqMoMkZpRtAD" "jqxFdJ6Ujqgtw0wgg8Q3MO0ih/U+r27P+fTlipd2F2Sp5GSR8vR4gwuTTQaDknlTEV2gE4JF" "5ZCJorYObzShblBakhUpaVGgjEIbhTymdBsjGa+cICtKgve4qkLOPVjLtD7iuat73Lz2Kjdc" "xVHdII1hOd9HpxqjFTpAbTts1zGabJCmOUZYUhU5tZ6zdeICUSY0Hcza61y5cQ21OuHs5jO9" "I6MERilMkdLWLVXV0thAawPLtuOorqjahrpuqJtAVQe6rgfaKtnXyUQR0Ymi8x1VUyPI8b4X" "9YOhwRjDctmwjA0Ht3dg9BAuCJIY+1yWhxg8Wkmc0hBjT44noLRGhuOeQi1A9PU30QecD8TQ" "s66si8QAMUp8jPgg8DFiraOtG4rMUKSGaXWENRPyLGU8mjCbdnRJpKuXTPd32L27jTEFaT5G" "CEuSFqyvT1DHIFNJJFpLNIIYFVobrO3wLpAUGb6xKBFRAoSI5GlKu3+AOIbhhtgLNedFv0EJ" "hBC+uRksRURL8LZGG4XrOtI8Ix+uUy32QSpikCB6+LHSKUiDyRUmK6lajxaGZ55Y5+Ta+/iF" "T73Gi5duPVABD863jcB6p+PCt8w0fYt4+jd5LffjYN3v5uBbCqx6US9vHMxe6k6t/FCh4sk3" "PtZZt3zlcPHc+7z/4MlBco7f34F4P84V9xBQ4W2+lz91oSWl4D2Pr/Kzv9fwt/+85K99r+Iv" "v/8Cd7fh6pVd0sFJdusZB7MrPJKcgblCHTQkpaKOEWdbhJB0iykegclKvPcsZxUu0WxkgadP" "neDVyzdgPseJiNnYIC8zoregJE19RFaMKTOBC74P0puIlILdox3aruX8iXM0DQTl+3yId2R5" "QmcD45PrnH7PU0zWT7CuNEoPee2LV6iv7SASw+G0ZmutJCtznorrPLQ5xAvB9qJjZ96wPasY" "pIal1Fy7vY33gbZpCMGwNioQRDKt0UJQyoTGeY72Z32v4bBENA0CC0r0oMjQr9pXbcPu4RGT" "RPPvr63yF9Y3+Zc3Kr68d0SbKXSSEoUhKfqi4NneLjbYvi9Oa8os40494/JiRuUCY285kXe8" "uHeNl0MgVRm2maMzg1QR21WcWJ0wyODZwymXp3NiLBhoeO9owIdPXeTchUdZ1DOW1uJcSlvX" "VNZTdQEjM6wPqEyipCQvEyarY6RJewdECYzRpFlCkim8b3FVQESYHm1z+dZNrh0cUTVLDqoK" "31YInaKyHIlEqZQsGWBdh9EJq2sne1HhOlCCTGd8x2MT3vN9H+fQFjjnyQLoySniq8/y/Be+" "SDYY8sh7vruHclqLOV7QcD6wOFrQ2kjd9Uwy7z2zhWN2WPV/nz70HYAxgozICME7pOizXNZZ" "YqTHhbSWoswodaQLLfPaUm4VmEFKkhqkAE/P2wJQSiJFQp7naCmRoUMqj1CglMLaSIgCay3W" "ekIIqCixvh/iRXp3KQLWehaLBkIkSxTCt5TDFSYXzhISSfAdwVqkiMwOD5gfLQhBMpsdUfiA" "kHPMUU7bnGSyNoDgCc6htcY5R1fXxLRfBPDOMz+aUzuBtxEVQStFlmXY0DEcj/v3JzhckHin" "6VxkmOW41vW2YfR847sgROIxhsW7467LYoy1tu9YJBClRAA+9I5hqjU2CoRJUbJkZdLyl//8" "k/zCJzVfeeHaAyXw4HzbCKy3E1n3GhHea9QnuDfJ/V4h9z/usLcA/Pa8fqltHE+eWH0GeCNw" "NFzamb1qXaCw9uG3eF1v5V7dy7mKbyPC/kycECL/3vsss6j4L3/V87d/ZESZDXho0zGOJbdn" "jtevXWJvOaNbtrz7/GOUMefozjajrTVq19B4S/QCkwzw1pIck7hlhMY7TkwGmI0z+BgRg4Ta" "VZguILMcZCAiUXg8Edc1/UgqMyz293n9YJfvfehJVtMJr28vsLWHUFLkJd5atPHs7x1x7dId" "8tE6w/GYW9sV05s72LahyHIIDdViwcbWBk215O58zryrUXnJ2mjAIGupfEZlI+PSUOZg0pS9" "ozlVE8mNpHMW6T2Z7At+Y6Jp24pikLEyyHCpZFa7XihkA2KwLJdzFI4fTsf8lVNn+Zc3pnx6" "1uDVceUNkRAckYTt29dpj3lcMUaMUlid8Nr+Dp30jIuEc5N1tExAS2xbY3HU3pKQ07We+bxh" "N8ypY+DKdIpF8J6J4QdPPcxDoxWKjdNYbVCDEbJpCV1g0R7RuUiI0LQNxbBkMMooxgOQCqkS" "TJL0QsEI8sKQFzlaRdrugBs7hzx36TW2D3ZZ1pYKxSgfkxU5lQx464lB4WzLeLRKWa4wmx6Q" "JDmJNMyWM+rFnMR5PvxowZ/70R9lnp8hX9i+AzMCpuCMSMlvrfHSl75MORzy8Lu/C+c8bd2S" "JOY4lC44mM6pug5rPcuFZ/vWDN82dN6ilWA0KKh8hTqGbwopGeYltuvwPkBwLJsOtwgsFg0b" "oxw3Dth0HZmX6CQhCIG3juAdQklE7GGiymiSNCH4vhhZivDNgmXXBYLz38w5WRt6saH7AJ91" "HcpohFB0ncPZlugbhnmKqFuEzDG6F4Jts2SQZsiQo6JHyAQpLInJkCLBmISuPqJuDunaIVpn" "RB1QxqC1IPqarnEokyGkwnUBvMQIgQ8dhRQMEsOh60gGA4IPBO+IQRPpafXWR5A9jNSHgA/H" "5dSiL5COsb/MSSJZNiDEJZ6G6GQfqCfSWEfbKXwcQpyjhESblLQc0dgjfuR7zjApUz75hUtv" "+Kz64Dw4f7YF1v06WW83GhRvI8zeaf/e20E7/7DfYwTU3eBeqQPNo2vD922ujyY7e7MlfXZM" "TKv6lm9sPDUaXtSJGrm+il7ex0jvXmPB+wm4/5kQW//TJ474xD94jP/rfIe/83ML/k8ftayb" "ilyXrGWeeWmxRcpsFnj51jVOxwpVBVJ/SD7O8HhE6Ct0oklQIeCCJ3QgE0FlK4JZMhmfZXm0" "w8rJddrQ4CuLFJJEJISuRUqNjopQ14jCQ2hxomXW7jJQBmRNs/A02xG9MiFJEpCw2N3lxd/+" "PDo1MNjkxvNX0csD5s2CGYFRoamrijhWDIdDbl2+xBeu3caLlJVS8u4Lm7x+5xCL4txmzldf" "uc2i9mSZYXVlwLDMqY5mrOU5JaARyCKhti0yOs6cfoh6saC+e5tZ1xHrJWuTVZbLA75PJ/zM" "hYv81q0Zv3rrgOq4zkWkBm9dn2ELjqZZ0B1TtbvOkhrDleYQrxVrpmQ9zUlCxDlHmuR9QDl6" "0nRA284Isu+n80nEdx0nBznftXKSZ9bWGWkBWcmNvSOW2zusbK1BUiB1ho8JvmuRBoYrJeO1" "CUkqkalEG9X3zomAThWDQUKaJ9Su5vVbU16+eoXXtw+xviVP0l4Ye48yCi8EjW2JAcbDDepq" "QV4OKLKSteEqxigODvcpuppnzg149PwqT3/HM9jJOVwbScoMqTOW8wXWNpRrE8rBkzyiHmax" "t8PdSy9z4pEncLYPh/sY+txUaNg7PKSuPV0TkV7io0RGSZ5mZElKJBKjI3oIBJx15GmB7Ryt" "91S2wzlYTOesJBGGKaFcJyQ5USqUgrZuwAekTlCyZ1tJrXoBdUxYl0KitcZ2FkmE2G8otq3F" "+YgNHbKzKGNQSiGFIhwn713bEF3HIF+lO7jFggRZL4+zVAGlBFliGBUppZTU3iORFDqjsy1B" "aWrb9jBfJUmLgmKQoLXEW90XWZsU6QJKeWSIaCHQIpKkmlwKfO0wwwwtFakRSHoxZdvAIiz7" "JYG+oRAfw7GcigQEvq/eRISIMpIkzbDRgbdY52lD+k1e17yWIDxF3l+w0zRjNBgR7BHf/Z6T" "pKnk137nFUKID1TBg/NtIbD+TR2ubxUw8Oah9vg27lZ8m9Fb/CN67eqFq3evTN919vrGcHJq" "MsjXdvZms2889vLuwaXderF/YWv17LmN8eqVWwe3v8Xl+lZMw9uF3N9MZIU/a+PBb5xPvWj5" "Jz93k//nX53wn//jjr/1P9/h46dSPv5IYHW8Qj0cMz2c0ZSBJhFc7/ZRouZReZaB3mR5cB2T" "FhAD3jqStACVHleeaGQ54Oq1GzxWrKGjxN7eJju9Rd3Me9Co8OSDCf64aNY2c0QwjIshFx8+" "SxUFjXKMi4QZlrqu8FohRkPk8bhm98p1PvtPf4XOS2zTEqZzYvQs24YYNQNjqFrIBzkfeeQs" "F0+MsYXmsK5onWJl4Hj2yg6NLbl4YZPdwznPX9plZ3/G+RMrPLySM9SSkkiCJiUSfUeBYJgW" "jFTJjb19gg1Mq4qmmvOuRPJ3Pvh9fPHqXf7xnSOMVoTYMfeOYbZKWYyZz49o2xnEfiTaOUem" "EzoR2V8cMVoZg3NEHxESfOextAhpaJcNja0JwjLIC9LRmE0jOJumrKRrTAZjuhiYOsF8umR/" "URGkYN7BeMWSZj0vSWeK4cRQjhLyXGJS0CZiTEuSWYRKyAuNj5bnX73GlTuHzJcdSTYg0SlK" "mW+OuFKt+3xNEAxHGySmoDpakGUD1jc2Obu6Tq4yjg4OOHXW8P6PPMV4JWe63KctztIuWmob" "KVdWgH4cNhylKOEQZcHW6Qvk4+/h6osvc/fqVdbOnKNZWpZ1Q+c60iSSqsjtO1OCSAkRpDKk" "SX85ba1DmaQPWmMRQO07RNCkSd+/iY/4tuuLouOcukvQ6RCTGoSQaCmxQoOO/XaljBjTb1z6" "rkbrPvQthAARUEZjRb+dGELvXrW2z4NlxaD/MBIFqECIfdl1Zy1GRUZpZGe5AxsPgTiu2lE9" "Q8s7ixaeUQJdknGwOMKZJVpmSKlI0xSTJuSDEpNplJFoLVG6PA6qexSCJFVEHE3nSZKkd/eC" "B+dRUqDSPuQeQ89E81IcbzEKfNMSQsC6SGv7bJpGEGLE2Q6lUgKxr6TyKTFIfNNiu5Zp1TPA" "uhCJrqW1EmUMRvf5t0Ge0rUN73p4iHcP8ckvXsM5/0AZPDjfFgLrj6MY+q0EF/chTu5HdPxh" "RYncPVwcXt+trpxcW/tzhdb5GwSPunO4uHNl7+iF71pd+8jJYXH+CgfXv2W0J98gksSbfN9v" "Jqy4T2frT11oOeBv/fdzzqwV/J9/6gIfPL/Kf/mzr/Grn6g5s2rZyBJy5zFSoAvJXvT83p0F" "36ev8+FMIKoK6SWYBIIg4kBqbHB085YoE3aN4tbuVc54xeP5GLU/xaS6B6d1NUFqMBm+rpEx" "El0gS/u+uSvTjmk746ms5LQuibOKg2qBmQxx9IF52znq+ZKIomsbtJBkxRCtNT4G2s7RtB2D" "RGEyw83tBXd3HKc2R9w6bGhcx0MnB1zbXnJ7Z8bDpzd59Mwal27u8/qdQ1RIOf/QJoMuYquO" "VZNRjMd4BRudp9g8yWdfg9gtcdazIhT/q4sPsTvd47d29/BBYILAeo+IjlObm5w59whf/crv" "sjO7g0mGuAAuerIsY79boBPNMNGs5DmJ1/jo6JolNiSk+QBhMrp2Rq4Vpe94z2DIe8oVZk2g" "0RmN9Sw7x8JbDqoF0mSYYsBisYRuQZobEukYTDLK1RSdgVYLyjwnywRaKUxqmHc1L165zWvb" "LfszT5qvggogNUUxYrGcok1OMd7E+74jr17OGA836aztOx+DQjjL+uoE0XrObuU8cvFxKgR3" "plPQJ2nmnjpOiUYRREQiELGjGBrSJKetlnRdh7GWEw8/ys6Nm+xcu0o6GnJ4MKdaHOCIJD4j" "N5K9gylRGrIsQUiB55hyjsNICdIgoqdMcuquw8renZGx/9XtgiMVkaqNmKEmSySJ6sfqSWJo" "6t4hUlqD7IWRkpAlvSMlpEBKhdcRu6wJMRwLNIXToS94lhKpVe+wEntQbdMQQ2A0GKKjo6sX" "rExGmFT3dTtGkyYJts6op03PlZOWSVYwXx4ymJxhUBYMhwU6SVBGIYTCuYBWffZKHFPeYxTH" "W4YSQSAER2ISogvELEMQiT5iQ8T6Pk8mYqSparRJ8D7gupbOgQv0Dp/v821CSnwIyBAxqSLx" "CfNO4IzAth3BO7oYyIym8hJrCppgcW1LagwmzSjyHpz6wadOMcoyPvHF15ktqgfq4MH5tnWw" "/jDiC+7NzOLfQET8UbhY0nZuuVP7V41KfmR9MjoLt77+hue2085d0sF+9OTq5CG4+ak3CCL1" "JkLzzURieBsx9WZu15+Zs7OI/Cf/zZR/8rc933vxEd7/vzvN55+9yW9+5Ra//Ooht2YgBZwe" "B7aXLbvLjivzwOkPvM6qySibBNFJVJ6AViQmpXWCuDjENhaawJXqiN1gmO4teXpzg3KckZqk" "z9LMF5BEpOvwQmCXDUsbuDHtuD1d8rmjQ9QjWzwzHHKmNtSHhyx2FGZzve90iwGp++CsyhOa" "usV1LRKJbxt0qmlaRxJrbt69xrXDBdf2GuZ14IlzI1Ynqyy95tTJmt/9+ut87bXbnNmacHpr" "hVvbh9w+chwRGUfJE+Um9cGUE06wuT4CKZgNE4zpf0CSxPCBZELRlPz89VvcPKxIywE+Ri6c" "fowk1aQ6ZefuHZSQnFg/h3f9Ntaybdiez7CJYG04IPeSd62dY7XIcG7OlSuvkyQjbjQVSkWG" "xYh14fjOUcqpNGHeeY68JCCwbaByktb29P1UaKTt0FqwNsowRhJMSjYUpCnkJeS5IcnAyIp5" "0/HCHceVnSX7iw60IhpNEB6BQUiFSRQZkXLUV/ZIpZgdHbC+fppUlSwXh6RpRlMtaesG4yyP" "n99gtTjBztxx0Hk6MewrXvZniNSgBkXfsxc8JpFkedIHwGtJvazorENIzdqJTa5depVf/4V/" "wce+7yPMlwfc3K/oREGWD1jbGLJcWqSg34ITEqUNwfWui5ICh2TZtBhtiD4eb8b1QkKnGakx" "RJEwWUkZFRIjAjEIJKCkguhJ05zoW2IIaGkQUiKU7rlPQPQW1/V/H85HXACBASxNU5NkOYJ+" "YURpRfAtSkZWVldoZtsEM8DkJcFHpJRkeUpeZAh5hnHxELtuj0RYnBIkaUleFmxsbpFkJVEq" "ED0sNcaA9+KY2eaJURBC/3lRiN5dtbajTDVu0UGWILTqRXMQON/XYEUchIj3Fh8CQmYEWkIE" "by1Ijc560dbX8vRjz9RkSBlpQ6QWgoBlaAxeGmpyFiFDWIdUGu88QmnSNMNZx6JqObVp+PgH" "T/DJL93hYFY/UAgPzp95gfVO63Qi9+ZbvZUwutc24tt1IP6RnZnkVbzlPec2L/zGl176fSO7" "2/uzOxJ4/NyZJ/jd5823OFdvdLPeysHibRyq++li/FM9L27X/OW/9xL/4D8u+J5HTvHRp1f4" "wXe/i//jwS4vX77Lr7x4h09fW3IkHeOkL46+PA+QLJj4TUQoCJ3GRUua5AzTEdW8obOHvHdt" "HWTkK3f3UDbB3NnjA3qdmPafcImuv3lpResDOQlfvbvP/+uzN/n446t874U1xsOcT9y5zJNq" "k8eH69wxcLSYEodDFP2ITShJ5zwNER8ctlmQGIMLgbrpWBtlXDj/EPnqHkELZo1jbjs++8IN" "dmeBR86u8qFnzvHq9RkvX75BYhRCQN16fv1rd/neEyt8x8oq57Y2qA6WvLSzz1def4Enwvsx" "iaaWhqK1vGdlws1l4HYtyJIBKinwMZKhOXNik5PG8OJLz/HTTz/Cjf2Ol3e2qduWynZM25aD" "ZcvWeESRgqunPPbISZ48eZLmiXN88qVbXLu2g84SsuD5/q2znJmU3N1fUomCoKG2lsWi7nsd" "laJMM4apIJEWXWYUg4QoOkwpKAaKvBDkhUAnituHR/zezSNuT5csrMVkI1JdIIzCWk/bWUbl" "BGs7hDKMVk6gTYoxOZ11JGmfVLO2o8hyXNvStQ2PbY14eJKwcWLCS8/f5NlXLrN2+izj1TGd" "h7ZtkT5QGINPO7JUk+UGqQRdU1EMS0QMdE0DWLSCtZMn+MKzz7F7cMgPfOi7WSkzLu3XuE6j" "VEI5KrF1C7F3V9q2wwdPlqYIIQm2I0RQBsQx0i/GgHMOpRSpyUiGJStrBamRfWGyAqJAqxRB" "JE0SQKEkaNEXJQt6p8t2nq7xNJ3AR82yaZkvA94FdCJIM/DekmQ53lm8tQgpGA4HTFZGHNy5" "QjJew4u+cDkG6FxEun4D9+x7P4hbel5/9TJt27KaFww3txideoRktN4LPSGQMqKVQIhI8AGB" "IIQAQtF1noigcx7nLVpCVy+IaERZQpoQQgdS0LaW1jVIqYjB9b2DMvYU/PiNjcmOtjMYq9BJ" "wKRJH/bvHMH3uAxrHQiBC9D4iO0shFHf7uA8MVqE77dpeySHxGvDyfWcv/qjj/Pi63M+9aVr" "OPcASvrgfPs5WG/nVN3LtXqz8SBv8rx3yr76Nwm7R0Bc3ZvedC6GsRQPfetzrh3OX+46x9lx" "+lBqZN7a4N4gqN6uwPlbkQz3s1n4ZzKx+dL2gn/v7/02//lPPMXPfM+7sGpISsYHz6/zvQ9f" "ZOEFSzenbZdMZzO62mPECuJyiqgEAkMbJMIqqumSohxSPrJF2x6QZ5an1lbZ2Z5RHFWMVc4i" "GoRS4Do0fdGxjDV22XD7YM6ytdw6qvjQIyd59vaUZ2/PWb+YMBFrbNmcxXQfpwV5PsQGh1SS" "XAl0mbOsW8qyIE8SrA00TUvbSrJsyNXrN/nSlW2aDuatx/qedv3Zr16lqi1pmpBnCYvlv/6k" "vKgdX7h7xEeTgtMbG2w9ucru3g3Y8Vy/eonxZB0TNB9YPcGJE2d5/sZd2tYyGK4gjaGp5hTe" "k3Yt3/v9F/jIB0fYpeX2p15G4JhZR4snL1LkrEXawGSYkGvHfLrP1o/9AEwPObr2EqwpCjvl" "L524wEq5yjxP8YOM2El0ECSuJQ0thRKUKzkyUyTKI0KLyT1JEZEailFKXmisr3j99g6vHmie" "u33I63NLnmrWJyW2tSSpQcsErzokisZZBIKNtdPk+YAkMcwXNW3TYFRGW1f4pmZlNKYLDe+7" "sM6Hn7jAxe/9DpoGtu88xxe/+jWGr1/mA9/1IcYb6/io+n690JcPSyUIAqyz/ZaekqR5idAJ" "88Mp63nB9RdeJZJyZ6/l53/9M3z4XY/znSfPcHvacVh5iAptDAJBVVXYEJHK0LmIEgGhE5QP" "2NoTXMC1AYlEBsmoEEitSIdl30SA70GaRqKVIcZ+3Ga0QsQePhpDL56c89jWM5tVLCtL1QaW" "iwbnI23Xu0la6OMOwp6TJWRECUm0kUGZkGiYH1Wka+cJyL6j0Ghqa+mWDSLy/2fvv4MtS7Pr" "Tuz3ueOue96kryxvuquK1Q5Aow08SAwEDEHBcUgphhQlDaWhRiZCMzETCmnIGFHBCFITMwQZ" "5EAEDUByAJAwBLqBRqNttS1vsqoyK33my2evP+Zz+uO8LlQXymR1N4AuIHdExnv5rjvn3HPP" "t+7aa6+FSxS9O+/hxOK9HBVdqtk2ZIru0ip5NyNNOJxojEipECIcBpC3onsfJG0KO3gfCDGg" "g2c4PMCLRdK8g04kRrZayXgobC/nJSE6lM7BByIRH2Obvh3bGB3XBEIWDsFXa5sho8fISCYF" "HoEngE4xJIQgaHxESQlRoUTrr5UXHayLSKUwiYFo+a4HV9lYLfjY586zvT+9hRZu1bctwHoz" "v6u3smyIbwKoxFuwVbxF2yx+C/br1duuXrq2+9KkZtfEsPaa11NntvcvTepmfyPTJ1b7xcKV" "venW4XvwVmAovEmb8K0mCb8twdZBFfkb//ZZPv7EDf6bj9zPvcdPYhkwrCDKnIySTlayYjyi" "MyAMjjNaGFJ+5mk2NntEDZcuj7n4wgXylRViSOlswvGlnMXeAuqek7gqwW1NETtjbCLRIsVW" "gSAjaTpgOt/ldK/ioWMdmhB47sY+QRY8tNlh5ARnkobJ9pB78wHjeYMVM/I8JWiPMobGe1Jp" "WgaKgFCCGCRlWXFjdJ7bVwuOLB1ha675/Scu0wTFkbVFHn/hCiFEZvPqdY/Nfmn51HTCXYWh" "yTQPfcddLNoP8Onf+yQLInKSwA/cfj/7znEQIsJ7NnoDqhiJYUohHBdfOMNLt/d478PHePQr" "X+DywYT9aszufA4ROolhNUtZMBl+bhn0FL6SfP7XHqXXSdiez+iJmv/zvffBrMO/euxlskVD" "0u0ioyZTKdpKchKWFwq6A0UUHpMrdN5F55GooXKeyXjKMxeGfHVryIvbNU5ajm1usFQk+Fi3" "7bM0x1lPt9NDKEtTV9RNTa+/jJAakyQgJVK3uq26rHFNq7EJTcl9myl/7oE7GWxu0qCZliUn" "7rqNzZfOsrV3nRdeeJb3rn6IvNPF+pq0W7Tu6ofsYd7p4awlOE9TVqQmJd/Y5MLLF/m1X/1N" "jCnIk5R6NuXjn/k8953a4LsefpC1wQov7zucB4FqQ4qdJxCJXlDbBplotFIEF/FBgZJ42xAj" "rGcVxvRReQE4gpfYOhCDIqo2bkYq0CIh0loThBAIDqyTzCrLzm7N9taEqrR470FEko4myTRC" "tuoDkwi0FhAkZVWiZaTXSwm2pqpyss4qNoh2+ME7ZtUcZEArQZACO+ijVEqi+hi3glGBPAt0" "CkEiHEb6P3B0Fxxqr1o9WGyhUZsXeBgdFKOnHJfQWyEKUCloIQk2tBe50BoUR9rJQneY2RlC" "xNmAMBz6fUXqWoLUSNUS/1JKlFREEfACXAzIGDFCYaRGxAwfamSUSKFRUuF8IM0zRN3ghQcU" "88Zx+4k+f/3oA3zuset85iuXWmB+q27VO5DBeitAFm8CZL0VEPpmgp9vptS1/cn1vcafPb6+" "dlpqtRDcK3YM8qCsd/dc/eLp/uL77zqyft+VvemV17BT8ib34WY8sf5Eg55vtn75pV0+ef7T" "/PTd6/zUQ7dz/5FjJJnGqYSGnNB4sBIxGxNtwGrF+GDC4K4BS8kyx4VidzLj+ktnOeJ66NU+" "tRYsrB2lWF9hXl1hZT+gewm1bZhHyUzlHJQzpnab21YT/utjJ/j4C0N+7Zkt3n1yg6v7B7zn" "zmW+sj3jE8+c4b9+3/2826wzHO7TT5cQaUYtI3Or2atqGl9ijMFojcolqRHoJGHmK1KjuH3Z" "cNv338m1keXpyyWP3H8bX3zqZcqqfsPj8rmtA3727iWaUCKqOevLC6z3Ory0tcc9S8ukuWG0" "M8EQ2yiaekbdONx8zkqeU9sen/jEc7itIc9erBnWnpmrqe2URKX0dc5SbxlvPcPRjPxYwtJC" "n6eefJnl48sc2MhPHDnK6U7Bf/fEFc7Hmmp/RGZrVIx00w6JE+RSsmcN3XlGL5WsyEjhHcPd" "KZfrhpdGQya14erMcWM8wqQJSkrmlaU3WMbjKJsJwUfKqiK3DVIo0iTHhZQYJdZaIpHZbNK2" "A7NAaDyzuiRRggeO9bn71AYhaVuMo70DpsOKpaNLrCwucWP/Ovt7B+xtXWf92Am8NuisZYmE" "aiffnLUopRksLnP92ja/8a//FULAi2dfYjarWF5ex1tHXhjKCC9eGrFz8FU++L6HeOjoCpd3" "GrbGEi1ztFLYQ92VDxHjAsHG1rLBO6xr8LahMJIsS5FJhklToI11altShzosBc62xqVRtMyV" "UC0QqerIrA5MS8dkMmdWWVpSXNIVCV2tED6iEO3knZZUZYMUgk4/p79QcPDSeUxWkC8tEWVE" "m5TpfMSscoDDh4rgPZWTpN0eoomoGNGJoNNL0cIhY+skbxKFUG2m0yuhzbHddyE0AfA+orRB" "RKjqOdlKhjCqZRIBnSY0dYWtW6uFJM2IMqGuSlRUaKkBR4wSZyPeBZraohKHUQlSapTwSCIS" "SQwRH1omMQZHqgxJ0Qc3R/oZItIyb7Ti/ija3E8pFWmiMCrF6Irv++4Njh/r8u8+do7JrLqF" "HG7VOwZgfbOtwdeCsdeL1LlZEfib/f+tTFEjIKuqmm7Nx0+99/Sxv7g06PZ390bbh8dZTefl" "/mfPXvn9+x4ZfOCBoyv3/d7T5z7G609Hvt4xulnt1Ru1Er9ta99F/odnt/ifnt/iwycW+V+e" "3uA9iwPWF5YpZI4yXbyfw7gh9gzDrX1uNEOGqwP28j0O9m5gZGQ6aciUYz4bIWaX6PQrmJVI" "J1k8uoYSNfuX52xdG3K+W/Mr58csXnP89Q+s859/x1H+F6cX+JfPXuflec3V/SmfeOocp1e6" "XFNTfu3xS/zFlUWO93Jkrpllhto35CoQdYIIHhVafYzWCuumXBnu84ULJfMyorWkU6QsDjr0" "ig7ry10uXH1jgOWIiJWMo73jXLh+kUXpeOSho6yfH7M+OMH+8ACTKJYHC2ztj7m4d53KNWx0" "Mm4/ssJEJkybmp2J5vpswshNsaFEKkc/63AsXcRYOFNugQITEqYHE5ZXFonK8b4E3re+ysfO" "7PHi5ADd6dFfOIKtK7wLHNRz0IYQPG5k8Xvu0I0+sjHosTs94PJsTLG4gqZtbfWKAmE0jW+Y" "1XNS1yPLO2htqOo5WidMxiPyoodSmqLTR6lW6H4wGiGjYNKUbWzNbIQSgmP9nLvvvY+oFUY4" "0jxh++JVKLqIrEd30KcwGc45Lr10ieWNoySLHaRWqOSQ4ZGGSGRldY3Ll6/zj/9/v8DLLz5L" "agyLSxskuaYsKzp5lxgixHYbo8r4/S8+y6mNHg/ffycLRYeX92v252CSglS3bWMtBEq2JqvO" "tkxPt8hIRU2WZaisi07bkGkpAaFQWiJlJHiPUgLnLc46GuvawGYL1gt2d2cM96fU1hG1QERD" "3VjKJpA5KLSm15NkhSD41qjTO0eSFBitmV7ZYXHjNHm/g2ssLgSm85q6nCOwuDCnrEuizEnS" "NWQiSFNFIj1aCowEFQ9B09cmB6V65ZLZ5hK27bxgHa5p/btiXVEHjTA9Eq0PEZnH+RppDCJJ" "CNa2ALtx+KCwlSNNZOs8T0uFCSTRBXzjUTIShSDStiKlUgQCmVGI2oP3EBukgRAkie5iQk10" "JUoogpR0OylKJpTViCzP2kgiJfFCcMftHX7iR+/i1z52nr39yS30cKveUQzWW4VAvxHIeiMP" "rPAGj43f5Pa90e+vbPOl0j3/0aTTu/Po5p27e6NrQPq1+371ws4z1f2euxYWbtdKaOfj1zRY" "b8Rg3Sxj9a3azz+xKgP89oUDfvvCARu55u5+zsPdgrvSnELnKJ0xrksenw156sqcbW/5nuMF" "93a7FNNAbR1VOaLjO5QHF7BqCxU7lPWYg0u7zHsDvnp5yC+8cJbnqxnTphWvLuQd/m/ftcwj" "awUfeeBO9g9u8FtP7/K+7iYn1gc8dnmPoZ1zQSg+IE/A3px8vUMqNA2tMaeQgqauWpuDTLO0" "vEzs5Dy7u8+wLnnpyi4xjIEdup2Eh+45QZ4nvHBuixD/8Fu1WCjK5gCnlyg21iidQPnI7Xcc" "ZbTlCTZgdEouFUtZwe5siBORo/0lvBO4ynJqYYAVglEMNGFGx6QMFrqsJQusih6F8uj+CjPv" "iDZw/splHrj/Qc4dnOUH3/UQL764zS+f28H1FhFGo0IkUQk21NRNTfQOby1KK1AC6x1BSZ7Z" "32c6n5IXBhUCdT0nCslSd4m5r5g3FcE5JuWE2jXkeUGeLYBUzOczkqRDojXVrCLJ2tw+JRVF" "3qGuaur5DBEc2lvuPno3ghSHZ2VlieGVK0wPrmGSU6SDnHs/8C6m4xnnXnqWg51tXKwoOst0" "OgUueKwLdPsFmxtrPP6VJ/m5f/iP2B9eY2lxiRBACsPi4ipNU9MG+kG3v0Izn9CIFlS+fH3M" "td2nufv2Ne45eZKdZoGtvZqZt4TYcikiCpIkRUvHeDpBeMFSr6ZIMpLuAKMlaaLxh4BKyraV" "FiKH03QO5zzeQ1VbGhcYjmr296YcHIyZN5YoYgtuRCREjxceHwNNaHMR52VJ4zyZMa1h6nyO" "m1tWTpwkitYpfj6f41wNoSaEBu8qQvQkaacFu0S0kXRSQ5YINKClpugU7UXXN61dQ9McMlYO" "7yUChfceT0D5SBM9emkD38kI8bAFiEfT5g9+7bJaNxbrW90UUiAipEK15qYyIqQgSbLW8sF5" "MIYYI0IImqYNjE9UIISaREkUEqMMMteoYFEhHOq8BCFafACjU0TWhRiQRpEVKUJC7SpOHRf8" "8A8c59d/6zyj0a0pw1v17QOwbtZc9PVAlXiTv71dN/c3Y7T4FgAU8cVnX378P7nvhH1go3/f" "o0/xiVc9v9wdTi6N53b/eK+4f6lbLG+PZvuvAlbxTdi7t9sifEfXVunYKid86sabf1P8n8YV" "S8WU23sFa/uB5VywkpcoYKus8EnGTjPl8vmG6yPLqPrDE0H/5MuXKavA3/m+e8kOajq24Gfv" "PYWUitFsxg8dXeZ/fc8xJsMpWdMgVcF4NCLvLTGrZqgAFAnCO4SOfObFs+hccnqjy0fuWeFT" "Z3bY3ddM5w0A01nD5756lnfdfYwj6wtc2Tr4wx9Mpdnaazh5e8LKyu1svXiRNCmY79d4K1qj" "Ru/JjeD0yjJVOWYhy7hj9QhPPX8GneX0uyf5/IVnaKxjvbfGQlGwOFjBSEXSOHIE2XXNhd1t" "zk/G3HZkjbENxJln0wrO7M/J8xzTX2C4ewPfVCRpjpKaIutQB4fzlsqVBCFIiw779ZjRZE63" "SCi6fZpyilRtzIxSGi1S8qJLZUusdxRFj7ppQCbE2iJV28rNsg5IQdNUZFmHIiuQKBKjaPyM" "4CKnF1foWhjvbLN+5xHcwS672+epoqWIgbTIue87H+KFx8/in6vxzjLauc7g+DEmoxn9pQGR" "wPLyIo8++hX+9b/814QYSJKMJFvAmA5KCIpikV5P09RlGzMzH3NQVZR1e3zSJOPgYMinHn2K" "s2cv8D0f/C7WTmyyNZxzdtcympSkIpAbjQgBEzzTuiFf1STdPlm3h5YQYutZhaB1LQ+toNtH" "jw+BEDnMMwxU1lO5hlE1ZmqnzKoG7xydTo5SmrJpkJOI1BpjQAlJUweiC6BaI9Pp1oh09Rjp" "0gplOSPIiGuFT4jo8a7ENzWonCRbQRuDUW1YM7Ftz2ktMFmO1AkxekJoQVCIrXdcjALvPUFC" "IGJ9Q08rnLXIbh8r20ED21RoWbXAr57j7ZyqqjBJl8a3MTmtZUVr++CiI+0krSVGDChlcLZV" "YwQvKCvHfG6xs4oUTXQNiRH084I0VUg80YOKbSJADHOEjRiTIGWCkEAMCNUOPxRFQUIG9Yzb" "TkT+wg+e5Lc+fpGD4S2QdaveGQzWm7FYb9ZCuxkfrPgtAldvBgS/dpt+9sLVl3ZHB1eXpDhO" "67H5tfvo7Xl57Zotz5xeXnz/xqB7cns0u0Hr6P5qFku8zrZ9I7qrd0SL8Js7YyL7s9Zy4A9q" "+Laf5l8+fZUzO2P+i/ee4qOLHYTKcNGBSugv9tm47yTOK4aXJ4z3KprZiHp+QF6VBKWIqSLP" "c3aaXfbKKY8+uc33PnI7Wgru2lzGR8WXnruEPXSJjsBTL1xBqdeX3eV5SmfQZzqdMbnxMhee" "fpbjR+9E0iFUFVmSIZqaPNX0dcKyOM5kbrlw+TIyFWyuH+faeI/re1fp5Bl3n76f2NQgBYmE" "bqcgSwzjyQzluyz0Bvisw9Pjc9zpBGef3OGZvRk7bojZb1joFERgOt2nyLtkJsVFS5ImiCBw" "wlOFkrKuyfOcxLTTbp3+AjHCwXhCJebknQ65VtgY2jiXPKAPW3XRR4xJMUmXurFoY8jS1nqi" "qht6RQZ4OllCxxlOd/vE0KBFRdi+xt61FxkyI6QGWY6oq4rB+jprxzZZ7Ay4vL3NaDwlyxLG" "kzmFC9x510ke+8rT/MP/8X8kTTPyosvi8hFUUiCAbq+PryNSCPK0Bx5kEsi6y8hmjogBqdJW" "WB0V12+MeOzJc9xzx4x+lvPI8SX2xjW7w5LSKqTSdAvBsqhZzBpMdwVjFNpIjG5ZpBAj3rda" "KynBNmBty/LUNlK6wKyyzKsKWzf46PA0RBmZ1zOyrE08cI1iPp6gZU7yNeAgIFpHbBr2zm2R" "Hb0LZMSF0E4dSuhmKXMv8dYhCZgkxZiWoZISUtUCthj9oZt6oBENQggEqrVUCIIYJd619wkR" "fAi4UCF1QZh41gZ9djNDWU0hTJGmxnmPa6ZtUkJsgab3h1aBQRB9xAdIUoXWGoEihthqraIl" "hHYidDI7YO9giJ9NSNQq2gd6RUqmA93UHLrUp23wdqgRjQIc3jukNiS61wZYhykQiALStEAZ" "QxSO0ycFf/77T/Prv3WW8bR+J1wpBbfqTz3Autl8QvEWj4k3eRK9VWROfBus181ur6x9GJ3f" "3nviXUeWHsqLYrmcz93hfeSsqiefuXT183etL33ne04de/CpSzcefVVLU/KHJxPfbBrwrQDY" "rXob9dWtCT/760/z0SM9furYEb732AqbvQ5+e85s5PEBpFckGo51upzsptTOcDBPueY8l+2Q" "qWm49+QKB5Xihasjru1OcT7yrruO854HTvPy5evc2PuDke928fjDtT+dc6OqUds7bBab3HHb" "vVSVZDKc0DUGHWsSrUmVREgIRlGLmmJpQH95ieFkxBU/RSeBfteQdgvmBzXSO1KlSDOFUpKp" "a1DdPu96z/v4wouf4+zsJT7YeTfXxop9oQg2AHPuPnkPhTLcmGzjXWCiBPPKkeiI0BnDgytt" "SDQBISydzoDoPEIopJSEqDg4GLGZp3hvMTqlbGZMyynLxVo7LWcSpNJIAY23FJ0uiUlpmhof" "Ld41mBDJnOOOwQq5Mmg9o/Cw98TjjOsx806KWuohx1eZDXep1ldZWFrg3ff8Oaz4Kje2t9Fa" "0xv0WF9f4YufeYxf+IWfx9YTBoNFis4KWdHDeUuiMzJT4EXAHU79VVWJkoa8s0KaNTg3J0ZI" "kgItJM18ypWrV9lYX2Yl6VDt75Ibzem1DIlD4IjBkWcdssExFtY2SVNBkkiMlq0NQQSBxAYP" "ogUStbCEGJlWDdN5yWRWcjCaYm0AJeh2clzjcd6DbNtcMjEgBPNpg5SeTp4ifANxztxPmYwm" "hGOK/f0hrVeoIE8NMgqCTgk6be0kkpRuZiBGdGjTFkRs7RacjSjR2k8gIsa056MQ6tB4NBJ9" "ICAOGTSL1imUNav3bDCzmuGVbTLjMMoTmykh1tjG430kVBUxtudQJKCkQUuFxLbnffTtxKKI" "h68pcHWgno2ZH+zQ2IpcJnSjYLFbkOlApiFJUkLUBB+Q9AgWqmnDfDZGpxlSCYQCY1KUCu0E" "K5JEFyz2l/HscNtp+JEfuoP/+dfO0DTvmHgdcWtt+LPJYL2V4Shv0RoUNwGY3i6gulnd19ex" "WNY69+Wt3c/+9e9Y+69Org1OnrkwP8Mf2DHwxOXtz5Tvtv+Hh08fe0Q9+kTirW/ngv/wtgre" "2iyVW4DqW1ufvDbhk9de4KGXLvPdKws8OOjxwMoyawISJKGx1BG6/XVct+AzV3e5WO8yzWBY" "jjm6WPD+B27jVz77PDcO2hbCZ594mV4nY6GXoeQcH8KbboNEcFB5ikGBHA5ZztYoxzOk82gE" "WigSIwlNCT4yto7d+Yz1Y8c4escdPP3VLzKZ7VCWE/TmEULVIAgYBSZLCMEyqxtuTOZ0eglu" "OOTATugmml6vzzUb4SBQxATpJbOdERtHj/D9D93PpKz4Jy89zgEzQpTY2uKER+LodBIkkiwp" "8MZjmzlZ0ccYTTWuqOsS6y06yVnoLzCvpjRNxaC3QtPUSC1aL6qki6SNfJFpTt1UGDx5iCwp" "zXKvy9aNHU7nGezdYP9glzERmQgSVxF9pK4mSOnYvOcoVx57kc6gz87+Ddx4l6XjJ/k3v/Tv" "+fQnP0akotNdZHH5OEhF4xo6aZcYW8ZES01QrVRSqobgPRJBkJq8s0A9K+n01hjvXybvDCjn" "M65cucbmkTU6S0doQkJVV0TlWBikrGyss7RxtGVdXE2aJRitESJiy/oVgbaUrVFnjG1G5Nc+" "5LYJ2CZQ1Q3WWrRQRCJpavBViZQSoVvvqxhBBEE59YS6Js3a41sdjPA6wUdBXVmywiBERMoW" "MCUKnNZoJdGJpGvcoQ5dEGybVyhim5PYelA59KEje3Qte0mIEAU+KqxrM0QFEq0kLtQcufM0" "iYXpwWUEJXmiqFyDtTPKsmE+DwjhUCYlySRFkiIIbeuONvVB4pFSE6JvI4GERKmE3HTJ9IjU" "RjpBokREJzk6zUkSTZ4ZgpcE5yBPsXXTTgc1jiAcZJpEpWgDQlZIBEolSGkIOLJuRhM9735o" "geH0FL/9sfOEGG6BrFv1jmoRvlGo8xv5X90s03UzRqTxbbBvr71NPXpp73P/2z/fqU8sdm8/" "c4GngOzwNvPy9v6Znbp57PRi8dDRxd6JS9vDi3y9wF28Savv7eYq3qpvsJ44mPPEQZtJtpRo" "7ujlLGiN94GZd4THn2MkJSMRWB4kbCz2eeHiPlsrDcc3Uwbd9JXncs5zMJpxMJrd1Gur1nWS" "a9evsrhymqqWzCc1K72cNIJykVBWBNfggRJNqTXTsmJy9RoDkxBcSSdNOdpbZXj9OlmmkYkk" "uHbiq6os+74hE5FqOmJSlZzOl5mnhoMwRwlNR+XU3uFmE4rZiDvvvJunz13gC9ev4GXDwmCJ" "VCpSkwPQTTvEEJlMDkhMa8kwmx1gm5pEeoiexcFSG+YrJVIIbF1CL9DrDXCubY91uwWEyLyc" "IqXCoLDzihWhWc07TKoaWY4YCM20DkylYlTWpE1NSkqaKUwuCSLSXepTuZLBQh/RUfzWL/8q" "N5rAla2rpEaS5Ut0Bhs0ztMpUjp5FyU11lvKYMmVREvDdD4FAXmeUTWw2OkznU2ZNPbww5bg" "bEnPTPnIRz/K4vF3s7+7SzkasXr8FEmnQ7dXsLTQo9/VGNVmWtZlCbSxMsYYlAoIKQh1g/OB" "pvaUtaVsGqqyYTKr2NsfM53OiC4iVBsCbV0bNRQIGK3QCGxsI2dEA3VlMQ2o3FFP5zQs0hVt" "YLRSEmI7iaeFwIpIolt39DzrkpvQgqUQIWlblxKI1raB66F1Sg/h0AsMsM4TUa32qvE4Z9GJ" "QUnBsLFcuXrA3Y88wMH2CS5eeJKIx9Uj5sMho50Zs4nHJIvotItaSaET0Lpt5cXoITh0lqKN" "BKHx0SNkIMk1C/0BvrFUkxm9rMN8NiQYg9Sm3QYlUEJiAyRKURQd0qzHOAyxLpIJjVCRKBqU" "kkipQajW/EG1NiJJYrGu4YMfPM7lSxOefm77nXR5E7fWiD+dAOtmo3PeLAD6ZlqCbwU63g64" "erv7pi9cH51//uLOSw8d6b7344/z7191uxxNytGvP33m3/+fvvvhv/0dtx2//9L28ByQ8MYT" "kX/qJgbfabXfOL60N3kDMCTZ2p2ws1RSN47Hz+7xwuUh95xaZaGXMZy8fd+cvZll2jhWRUOh" "C6YjjzSSTGhU8Ni6RmhIlzIaAuMbM6bWs6Khv7LA9d3rjKYzbr/9Trq9RWbjKSI0aFm00S0B" "7HSMCg22rLDTMbY64Ojm/UyzhFA4ipU1kh3HvBqRJYqFQcHvffJpfu3GNV7YmrHcN+i0Iet0" "kCJSNw0utuaQVTMnSTQmzcAJslywmaVIaRAB0iShCZbEGIRIiMG1AvAkQUTBdDzEmBQRPP28" "j29qMqHpA+N6yvXRnHuXM0Q5Y1TNmUdJFTwySJSUpEVKp9/BJBo7g6ap2JsNcToQhcHVI5YX" "BsQYyToLFHmXNM/aTL0Q8Hgyk+FDxFtPc2hsKmPE5Dn9YpFON2M6HiNCm99ndEYet/nxH38f" "D3//jzKpE/pHj/Hys2e49tILnLjrDsgkTaWwpkAXkoXFHnWhmY6nNI1DqgQEOO+w1jGvGkKU" "1I2lblqgtb8/Zrg/pGlq8iQliEjTOHSiSdUhER6B2LbVauvaP7nAfOzoLTqci8RsGalTUApl" "JLiArW1rOSIEiUoQWpLqFF83aG3aIGgRiIeZgK5u4ND8VElFoH1N69uWehRtrJS1nqouMVqD" "DzRCcfHcJSZ1BdHRyXrgtwlVxXh7m+moZjbxJGlEijl5niEGWRs0JASJlhAcMUiESEGCChIU" "CAJFJ8HZAqUFuepwMNonCokQEmSrhQOHQKCkRmtPt9tjnHWxvo1nMonG6K+54AsEGmUKgggI" "alSS4aLEKMuPfO8xzp07YF7bd9ql7Rab9WeIwXorsMVNAq1vpk0Yb2J73uo2UZX1/Pxu+ZWH" "Tm98KEmS5aZpylexVOLJqzuPzp0d37O58BDwa/xhP6z4DQDDW2DrT6C+pqPa3p+/4lQ9sp7H" "zmyh1DemLa2t4+nrFT9z7xGquaByss3iy3KoSlxZsbC5gNxM6N95kvLffB7vK2wzYD6aMy8n" "LCUpC2bA9t4QHwLeeoQQSKmoa8vWfk1XKYyvUdqxJgRFp4dFYjJJt0mIwePcjPWF44ymU64c" "TPnYjUsAHEwdC92axkh6OgEHWoJHkiSGVAeK6JgiSLIc5QNCtnE+yjU4AbWbkeUd5iFgVEJV" "TVjortI0FkIkNQkHwz16acrxJKGH4Mz1feb7W6ysbnJlt+TKfMRcBbKiS248RRpIsgydJUit" "mE3n1E3LsEShaAQkaQauQcqU6CPLi0ukecF4NsF5R2YMrrFMJnO0Vri6QStJkiYoEVhbXmA2" "LaGastzLaCrYXE/46Ie/j9sf/giVz5HSI6VgsHmcvatTzj72LCfvPkFx9+1MpzOIrYYtTRLM" "Yo+6tozHc6xz+ABV0wKhxjWMyzk3tqfs3hgzGk8Jro3BSYzBxUDtLInWSCMJvs37a6dmWqam" "bjyu8vhyhuiDUznedHBSk0qFsxEZ/SuXD6MMMVhMmiGDRdQWQUIIkijbVmBTNa2/VYg47wl4" "lI9o1fpRgTjUSQmc8wTn6PVy7GRK2u2ydmKVJBc0tUNKixSacjhiuDvEWoV3DdO6QqddvFvA" "24KgcmSSoHWCUhBd02ZholsFvkoPQVMkSRR5zJBNgCjR2uAPxffx0PFdKEkMDiUUWaroLy0y" "a+aYPEMlqg0XxyGEbZ8ff2iFkRG0a8O1XWD96IC//LN3cP7lCdevTnn50oR5fUuXdQtg/ckC" "p5u5/a3idN7Oa36jjJa4SZbt6wTqnz1z/rH3nX7kLyx38+71/Wb+qtv1ld3x9SsHo2fvPbL8" "vkGvszSazCaH74fkzScV4ZaQ/dv3G8Gr3hHrPPabyIktVEI33WQ2lTTTksIoQt1QH0xJtSA7" "tsLe+Ar2+g2EUOSJxjeWvYM9tJbccfQk3cVFxuMJeIdKJfVkiux2aaqag3FNJy3QSlPWntvy" "DZRs20Bpbug3no5QTHSgEIrt/QnZkQ2mV54D2qDh0dQxSBtMkpBpTd3MWV9YJk8HDDJDt7fE" "45fPY5s5S+kawVp8iMybiqgldV1S1XMWVzYwSlPXcybzIalJcLYi14Yiz9kwGceTDlvDIdP5" "nIdPDphbx25ZMXKOqNtF16iaoleQLi6RDgaYToftq9fYm+4znw0ZLBwlTXL8dESmu1gXMGmB" "MIZ5U+OcRwtJVVYIoRBaUDX1YSSPQ2mJ0QmdPGH3ymViM6OTrnJ80/PRP/8hVO8oE9dBVzUh" "tqado90RUqUQezz/1Rdx1nP3g3fTVCVz5dFakaaGIk+QAvaGMybDKVXdzsYEH7ixPeXcS9co" "yzlay9amQ0qa0IIiJSVeRELTkCYJIkSijyRSU7tAbS3ONsjoSA00oYtIM1SWkucpCotvAgSI" "QiF1Qp5kFN0u1WxEdDXSt47sxNarSgLWOmbzCqkMgYCvHTpRmCRFGYWI4GPrFeecpd8ZML54" "gZoFxqN9VCWxbo8s1cQmEu2UajZjUlqstQgkuYCyGqLlGkmqW3CsBMhW0N/mNXqCjwRbIdAY" "JVESslQQS49Xmqh16zknIMSIonVsJ3qCs0QBWWHQxRLZQo80SQgh4JoxQjQIKdr9V4EYNKiE" "JtYoBFZCfvwUD97d50Ohotmfcv78Fb7w5S3OXhx9O7UEb7UM/4wwWN8oyPpGW4PfKLh6o215" "q/0wl/ZnTyqMP7aQ3XZ9f3SN1o4hAnI8q8aPbe1+5iffe8///t0nV0595pnZY7xKCM/N2U/c" "qj+l1ckT/uLDd9M1fUJZo/yMvs6Q3iNFZP32I2xNdrl6sMex0CPMBU3T4JOSspqxOxkyWFrB" "NjWEgIgBVzWkRpEqwaRySBFofNsu3Nkf0x30aCwEV6GEocgNdx8/wcJByhxBd7HPOM3Yn89f" "2c5pFdBpyorp0IkS01tndXWJhX5BJ9eUXrH/wosErSmMYi0ZUBN5fHaVWV1CDOSdLjf2Dtjd" "q7n7jhPM6zmpkczn+2Atm/mAO1WPPC24sPcSi6JiOVvk0mifHRGpTYdgG2gqkmWD7nTIFlfI" "+0vYJuGFZ59lGm4gUsl4PKS7bOj3Frmxs8Xq5jGy3iJ1XVE3TZsZGAJGmUMBucQDjW3o9joE" "75Ayxc5mnFpfoMsx0o7nwz/8HTRmwHgWEK4iCRKpNVXlaKYNuIhKUtK4wHNfOUM9G/OuR+5D" "kaKMwVtHYkAYQ5olZHnKcDplNi2xjWS4O6eczzBtngtJ0kbrhNgySEmW4GkNNp1t/da0aI1A" "nQvEIAk+IvAoqbCqQKcpaaopMo21AR9VCzyUQQhFkmpi9C3trhVKRoSwSAXBt5dj79soGutq" "EIKk0yXGSNNYtGgvYdY6yrqEGEnwTPZHFCeOoWWDbRogkCYGgWFQZOSZYW84IhDBS2Lh8d4i" "pCDLc5IsxRiBMb5lsfBEIoR2qjBCOyChBVoZZqOI6S8z9QmLPmCdI9WG0Ob5tMfQO7wticGi" "dRsInxddhJA0TYJthkQsSIWQEhENIUpQ7U+p4aDOOCiPYFRkuS+458N389EfmHL2mRt84rNn" "efniNrt7f+IO8G/FVt1is/4Mtghf7/ebQeXfSMvw7XpgvRHTJK/sjHeuDg9uvPeejfd8+eUb" "n37ttj+5Nf7cT4vwN+5e7tzzGfgyNydYv3Xy/ymvfifn//lXPsJDg1UuXqnopAHZ6ZEIRTk8" "YHGhT1xb5Myly/SXFxhZiyIwnLZi6X7RY+5KuiEw3J/iq4pMOEgkPjT42QSlFdY3hNrRyQSm" "EBS9BQICoscoUImkv7HE9Z2LnN/d5pH7vpPfvXLpD12Ou1mPfton922mW1EUJElKkSacv7rD" "Vy7sIlXgnncv00lTqnKMUaA9NNFw5uI+d55Y56kXLjKaTrj/zk1ubE+hqamN4y6rWDRrXN3e" "Z3dvj59a28A2rUfSrKqwOkEJR973FIMuydLtFGtHyQYrPPHJ8zz7xc9QzsaEQ7dvleakRY9u" "b4DzkbquqesGpTQxglKGsqrJkgQC1HVEiXaiUCrFieUlTp04wf7OLkePr7J+uksVc3a251hh" "UN6SxhopHcPtCfWkbk0wo0CKhKxY5IUXzrM3vMQHvvsjrKyuUJUldS0RyuI8GK3p9jOGByMu" "Xx4zHpVoKRCIw5ZcK2QXUWBM24IzqcZ6h9IKHzx1aLDBEYJGSQlJQj0ZEmKG0glKSdQh7Roj" "SG2I5ARbYRREAt42aCORKKRSrckoEaUUUkl0KjAuMinbyVglBMZovHOEw2Bk25R4N2d5oYPc" "n6CqQHexizESGSQhKGSEKAxJmtHLFIVWTK1Fa0NTzgl4pDGoJEOYFKkjJkkwiSJGj7U1SI8Q" "ur2/azMROyZl3FTING1tJaKmaQS19EQlCaE9niF4QvB4Z1HS4KoGnzpMmpLnPaKoCTaCEa1l" "Q1BoXSB1xNs5UkqWiwihtfGYB8GZgz6T/iLv+s6TfPd3v5+9nX2eO3Oej3/qSR576grxT+5K" "fgtk/RkBWDfriwVvbI1wMxTozdgzfLP+V69tE8qyasZfvXDjhfvvOnaPVM/q4N2rva70l164" "dPbl7ZMvvv/UkQ/9wqMv/dumsYeOejetv/pmaeFb9W1YP/qh+/nB997Nuaem+FmFPxiRJxmE" "dix+cbPHtIDnIhxxNQ/0Ir3NHou1IFWRyjWcPHoHSifs7Q6RPpAkBuMERZ6wX9ZcPBjTLQZs" "726xvliQZwm+aYhOtNl5iSFNDNW8JOl0OH3sPr4yusGvv/TE122rlKDKkpV0hcp7Ao5Et/YK" "ohHc2DnAeU8nMUBgPh2zstShu7tDrQUXtiuG44rZvOHhu2/j4rUddg9mvPDyFY6tLRGqEXc9" "cDsxTdja2qc3rVg7mnM9NyhRoaKk9paik7C4ntA/dprO8bvoLB9lbzfw1Ke/inEJIhQEOUOa" "Nvg4AP2FVeaNwzaONM1w1pGZFOstTdMgAigU0QXSNEMCoaootEYYzeJCysbJHqWX7I0t8woa" "a9FOYhuoy4b5sMLVrchcGdB5QscUrCQ9om547LEnueP0CU7fdSfOQzmeE5HMaktpG7TJCHLM" "tBwjoiRKDn2mIEogSoKA0MadQvTtDfFrjE4bFC1lbH2qlER4BzrQyXQrSQ/x0HZCYtICFyPB" "V6gIQhqU1IeZfIoQbJsN6FoPrBAivpWe44JnPp/R7/cQQhJF61HlfY3GspBnlFeuEVODzDKU" "FIfTee25UU5vgErIk5RuoqmsI9c50hiyvIMXEnQL7KQMyFf0ja09g1QaF1qQL0IgEZJcHTrw" "9zqE0GrVaisxUqJyhfMR5wLWtoBQqjbS52t/00lKjII0HVCJBqFE+/xKgVQkCqxzxKhZzMZk" "nfMsdiSDRIFdYm/s2BodR/TXWT6yyA+eOs6HP/xunn7yHL/ym1/l8acvUjXuj+OyIt5m9+dW" "y/BPCYP1dpiht+OL9c2ArW9FWxMgjm2YqMTchYivNhEFkJNZtf/oxZ1P/OR77vxP71gfnHju" "8u7L/IEG660E9W+kwboFqt7h9aknzvOT732QcuTZOX+FpUQi6xrX1KycSOifGvCFC+eZzGtG" "KuErB5bNKKntGKMKnIssFD1MkTMbzhDRkgZF2jh6nYTnpgdcKsfc1unSHSzio0MkPYIQ+LrB" "FAnRN6A00+kU21g2Nlb4B2ceozxsP32tFIKjJudoVnAxVug0ITUGhSLxkum4bSfOSsvl4YxH" "7rqLQMPt/U1Mc4Bf15zcWCYCqTGUteXJ5y/S62acvbLDw4vL3LmwwYVpw9X5mONpQtEpuD45" "gH4X5QIm0aRdTbrQozhyks76SbRZ5rHf+wIXnv8iaa9gpX8be5Or2NCgZMrCYI06QhQOicA7" "j6BtFxEgMymzyYzUJGRpRnAW39Q05ZTzVy9w28ll1o4PuLw1YRwVdZTMK0+Mmhgt9dxRTWpc" "ZfHeEaKnWDSk3QSlNSrp0VvqIWxk++p54nMvsHHbKfIi58bemElZM5nMKecVKEdvIaOZtUai" "3gWMBu8ijXdkWYKSLbgDaJwlMQlCCZwLBOuBQGMb0qKLpgEj6PYMeabQQiATRe3aS0va7RJ8" "ghIREQNCBJSISCmoyhnWCayDsnFUlWXeeGofWoZJKpxr9Uqubr3XYohkaY8iCvYubWOO39Ey" "UVIhhCTtpPjoGN+omB+MiV4gQ0KuImmWkXQ75N0BSZEBDqU0SaI47EASgkdI2dooBHtobFsj" "aV3kK52i0g5COCIR6wIlog2WjqE1aLUebx1KpdSNRViLcwHvPfEwzDpNl6nDtE14kAVSKrQW" "eFshpUYdtmNVNHQYs9obcqyoGM+3sPNVpuI+6tBFi0UefOgB7jp9nGceP8fHP/kVnj6/w/7k" "j8UR/o20w7fYrD/FLcK3A7K+VQg7fou2941YtAhEkxk5ryY2tKNm4jX3kb/3/LXP/PR77/lr" "H73rxEeeu7z7Em9uS/GNsle3QNc7qD54xzH00PPCs8/TMYrctMJiXzcMNjd4brbPp2/sM5k2" "PHN+nzRN+aHFZcZ1QyfrkiYZ0mh0NOgAJkgMEe0DIQim1RglwLqIixHrYzthpSLIdiIy4PHB" "c+AjLgqq8Yj4GnDVAqzIPWmffpahyhkmUegkOdzeiJGBk8s5Umk2FhZ4eXcbO5+ytLTEmmpI" "Vgyff+kyF7fHKCm58/gaZy5uUdaONM947/oGve4qs9FFZrXl6G13UBYJ03FgMnOIoJDCtxN+" "i6skq0fpLh/lypnrXP3sZ1nudqkijOZzusVSGwtTQzUvcVKwPFhhf3RAcIEsNe0HJUQSbZj4" "iJceDxilGQ/3UNHjXM3aZkESJReePMuLW9e5633vRRWDV2wqfBMJNuBs60Nl0gSVKdJ+6xe2" "vDZACkXaT1nf2OQ3/s0vcunf/QY//hd/lPWjJxmOZ4xHI+Z1zXxeEr0nioDRBiOBQOv7pGVr" "TCoEPoZ2kk+rNp8vyjaIO3p84/ChneIMoWqtFWJARI9S5jCuRxG8RysQmEMhe8uAVVVJXTZU" "paCsJJXzTGeO6bz1YQtKtwJSEYjRoaRGodr9R1L0c5StiFOF6fRAanwUJIlBZ5pEGXoLp7ky" "PE81v44+tCNRJmVl8yjLGyukuUGqQOuaIIgEhJCtbu5rwnshkMIgYoVSGleV2LqmAxAkMbYa" "LLynntcYPMK3LeJpaZFaHFpL1BhvMc5ijEEiyPKCqrI4B1EKoopI5doYIRlRUaKjQkuDlAPK" "2GCkop/PqMttRL0H4SQlC0wdYC2nbz/Cz8iHePmZF3n8yoTPXdxnNv8jAVpvtR7Em3jsLaD1" "DgVYbwdkvV7r8I8LWL3Vdr4WHOllXa8Nd4dXidRA/ppvDea5y3vPP319/0s/fN+xH/+lL7/w" "7/bGsyF/kE34eqzV2/1Q3QJX76A6stzjR+65m+eeOoeUkS6a1CRUswnLRxe52jh+4SvPIIpI" "rye4r99nVklcEJi0g9SG7kKXIBTb17bRUpGEiEFR5IZr05Ib85K1fJVpPWNajZFpl4VmAZRE" "ulbELYzB2opZNWXqHdNZ4PWMqjtasG40TRWggXgYGSKlxKQJS70uq3XKiZVFslwxnE04tb5B" "N0tRViL8nNs3N1jsL1JWc46srhCF5GA0pJ8q6uixIjKrahZry4k84+reGNUIymZGzEGl0On1" "MINVemsnmU8jT/7uF1GzMcePHuXlrWuMx/vkRZf+4jLD4YTh3h69lRVm0wny0FvD1Y4gBLZx" "2LpBS0X0jigiTXRIERH1jO+69z6O3X0nw1HD7fdNePHaNZ7+0he5973vY2nzGOPxHO9bUBOt" "JUqJSAxZP8cUKWmRoBNDN+2y0O/xO7/9Ozz+zBkEgX/2C7/E/ffezXve8wgrnYyzB1Pc3CJ9" "hBgIwmHSrO0EWgeh9aQyRd6GSJdzfAzY4BBaE1zEN637v5MRKTwqyUArfHRUZQXCk+cJSaLR" "RiBEa8IuYsuAEQMhtCHl+7sl+wcl0XnKqmHe1IhUkfWK1oQzRIyIreg8BnyoUUbR72jilYa5" "C3R7i62dggK0buOBQiRb7bN+73sYDR2V3SZRY5L+Et3NNRbXV0hMgkk0UoL3Fn04Keh9OLRH" "acOxlWljexDggmpTD4TCxdCC3+Dx1iJ8TQiOYKf4YKgbcGWDTCRFkhFlxEuHiLSvg0KrhGA9" "LgiCt1TWH4rcExKTI8OcGBJcTJg1EbzDkLT5hioh0VOKOKTxmpkVjKYG21Ssesn3FopHHjnN" "7+2M+NKL19uw7299vTaO7a0skG6xWd/kwf52qvgNPuaNMvj+pPL5IhDzRBV3Li5snrs6u8TX" "hz6/coI76+tffer8rx7ZGBz/Cw/f9f2AfYttvZl9uAWq3qH1V7/vYVSpmY5qBjKlpyShqWjq" "MZt3bzKNlsXlhCJPePrskN/81EU+/ZXLNDKQ54pUF4wmJRcu32BruEdEkicpnTzB5Rnno2ev" "rBB5hk4Mg+4iayvr2OCZDMdEb5Ht8oi1DeWsbEfhhaJdEV/17Uwr7ljr0U8U6yoiPMggKFaW" "6ayt0+sWaJPzlfNDLk8s5w62mIaatcECdai54SvmMuH4+iYLvQ6L/R574zGd3LDcK0BIthrH" "ld0dhpMDTnW69IVk3lTMyxJRR3rdFKEjwqR0l44xWD7O4589w1Nf+RLTGHjh5Ze4ev0Ki/1F" "tElpbCBJM3SWUzWWeMjQENrcvOB923qKrdmncw7vHJkEPxty75rgjruOMjoYs7+7TX+9y+ba" "EUZ7Y55+9FEmu9cZLHTw1hKcI3rwoSbpabJ+h87yIgvrK/Q6PXqdgk/+zu/zq7/8q8xmc6JQ" "jMZTfvf3P8PP//zPc+2lF7lrMeOuBUNPOoo8ecWCIQRHmqTkJiE6S1OWKFptkYwR31jquiHa" "dvsb37YPtZQEmZD2u0gliByGMFtHUzc0jaVu7KFI3RNioK4ds8ozmXv290uG+1Nu7Bwwms1p" "nKepaspZ1dpBBIEXiijF4WxfxOiGhSIj2SmJukB3Fom0E3izecNs5mkCuFiTbqRs3vcBjt/z" "A2zc+UEWTt1LZ+0oeVGQJQmplEgZEQSkiK8AqRgjCNGm80jACGRqmMwdqreKSFKETAlCEwBn" "HeVsxsF4ynDqmFcBj2iF8C7SeE/jG6xvaHxF4yxV0xBCbI1qg6DxjtrBrAEvEtKkiwjtZKV1" "irnLuDYecGbnOJcnp5n4BZTskCcnGXQKNpYlR1YtLuwzHu8TTSCqmg+96wg/8yMPsLJU/FEy" "WOI1v4ubXDtufWl/hzJY3yiT9UfBUr1dBu31DELjal+nq0tF9vynz519A9AHkPz+k5c+deaj" "D3/6r37vA3/5985c+MSVGwf7r/PevF1z1Vv1DqvvfuAoH1g9wstP3aBjMrpeUCSa0WTIwnKX" "J8e7/N1PfJmLexNqG8hTzZH1AXfdfoy17iIHN3bRg4LL+9eYu5pMSEajho7pkHRXuTbe5fnR" "RZJUMpuOGZg+QhmETPCuRmVJKyAWAV/NcWXdgowYsaEVSL+6UgODxJAHyWrqWTQJsZuw+aEP" "EhqN+vyXGNYWBGRZzuefeYn333uaF+pdLu/uQNHh/I0tLl6+TpomJEZT2wbnHEoJqqbhiJpz" "ZbhHVVU8kkmwlpDm5B1DUI6sk7bWA4lmsL7BtWsTPv+xTzCdTTg6OM7IVrgQyPMcB8zKGXlv" "AS8EpWuYTaZILdupOFSrDQ8RTxtmHK2jmymkq9koFN/9HQ8R04LpwS62saSLfTaOL/PUiwrb" "RL7y+5/m/d/zIZZWVtiazQlJoLvQp7fabWkQINea9aUFfuWXfpnf/dgnKfI+IdpWuyRaQLy7" "P+HXP/Epbj+6xHfefyd/7vgGl6aRraljHixaClyo0Sojy3LKek6NhNAK4IUQh/HxAqkkja2I" "ESwGqSVCSqQUbaixECBasbbwESEFWgnqusHZiLOB8bxkNGk4GM2ZVnNc9ETfphgYneARGKlQ" "UhJQ2BDxvg37HhQFqbVcPXuJ/NgJdL9PVAIXA6OqJgmCynmM9ngjiT2N1kdZVscJ2qESQYgO" "RI0koKVA0hrmxgBKamJoWsZHyFbEKiRpmhCafaxcJEhDJxVI1eY4hsbh69bl3rqItVOUlocO" "7xrrPYFIZSsSrRFOoohtmzUKvPcEBFIZhA80LpAgsXXENpIZDShFYwXX9xXXRcF0MeKWNRuD" "nCJZJDEVa50pvQ8LthY8//bTO/zypy8AF3nXHcusLHXY3Z9/qwGWfM3aFV6nDXgzTNWttuE7" "FGB9M+2/b6cSOxM/2xH+kknCG+UlRkDO5vX8537rK//wH/zV7/zHP/mR+/+jv/evP/tP+ANP" "rNc72W+mX34LhL2DaqGT8p+9/y78zgzTVOQolrtdwmyOn03p3rnMKJ/yXe8/woPziixJUSrl" "yu4em8uGotGMUHjviAqMTBikHYrSo4SmbgJ6dYOVZptBZ5GOLvDeMy+nlLWiu7CASg2JsyRZ" "jq0qat/6ZEWTUrmaRHz96ZQlKRvLyzitiVEha4dINGZhBTv2NDVcGY45vr7A9d198qzD5d0x" "wlzHKMf1y3so2eHE8aM89cLZrxtbX14ocAG8KDnwFV7B0mJOqCPjpubGZA5G0RULLPQ1/SNH" "6Q6W+dh/+Cw3zj1DbgQ3Di7jgU7RwUaPSnJ81TCeT/ARTJoilWwDkYlEEdv2UfSE4Ei0QEqD" "jLBQwCMPPkhn9VhrsLq/TUwLsm7B0QeO8/BkyNWrI15+6QU+8Wu/wX/0sz9Jf6nDcDIj6WZg" "WkF3RymWOz0+9puf4Lf+w++SpilKSprG4Z3H6AwbPJlJ8cHywsUbXL62xyP33c+dt22yup5z" "dSjYHjZMXbvgZyqllxaMq5IYITemDROPEect2mhSnTCrq9aaA41OE4xpg5eFaMXZWgukbNul" "TWXxPlBVDbOq4WBUMTyomJYlNrg2UkhEfAzoREOaIBNFkqcEPLP5HI0j1YpEGeqrNxiNJqx8" "6HZ0p0NZVTTe44NjXpbMy5JIQxAJsZ9DkqNQGCnQ0oOYI6kwWpNnKUIqYnBEH+AwTzHGABKC" "O3SyjynOtfmDPoKSgSRN8HSh9tg4bi0vnG0Dq4PApIJAgDpQlTUK3R5LqVune6mwPlJbh3cC" "YssCEj0Bycyl5E4ifYMWJZn1xCYycRBFh5jkYBJWc00/X8AkyxTpGqc/dJyfPn6G3vLjfPLL" "Bzz94rc81/C1ACt8bf15Tcvw7ZIVt9qG76AW4Z8EE/Wt2q7XWimIsnblF569/syDp5dOvUnL" "MgLJZ5+6+MSvfO65f/HD9x758WMbK0cOW4XwjdlJ3AJZ77D62e84zW1mgStXhgjrWIkRU9b4" "+ZwuGtnp8okzO7x4YcKzF0o+9cQev/XZc3zusRvMK8n4YIepHVE3JT56EilY1SlHFpfI0w6j" "8ZgvX3qakZswbabYYJmXE/Ikw6gEYRRaRoRop6lSbahC6zWVykhZDgn+68fJR9OacRRcTxxz" "BwOZErZvsPXVrzJ6/gyT6YQmzujlhvPXDzi+ucDlrV28SOh0uhgNz58/x7ys+Y6H30WnyF95" "buci/U7BXlVTxinezXmprChthdYSnWcMxzMuvbyDVF0W1o5x4dlrPPvJj7PUSci0ofEVS8vL" "dDo5Pjrm5QxHZDybEIiH032BGDxRRIyW6EQRaXVNWZLS7fUIIfDQXcc4cvw4TdDYcspksk0T" "HDLPOP3gg3zXD3wfOYZ6Omfnyg2e+NyjdAddlJJ4aynHNQpYW1zkia88y7/9pX+DUgalUwar" "m2yeuofFlVMsrd5G0V0FKYlRonVC7SRfevYFfvP3v8i5C9c5vtDh3ScXWOm1WY3O1ghac01C" "YF5WCAQiRqy1OGvRStBJJYuZR0uBSRVFLklTSWoERoMUEX3IXn3NAiHEyGxeM52UTOcT6lgz" "rmbMmpKyqfEyUtkaW1fUtaOqHNZ6nPPUzkNQKC/Ze+wFxCBncPftrcALgfUBa0t8fYCd7zAc" "3qCsK0SRkRWCLBVoFSgKSJSgSFO0MYfThwKpFElqXiFhWtG7ojkcMCBGyqZlsxAtqxYjCKXR" "JiEKiLQ2DcG104TWBaRUBB+oKveKkarzrmVXvW0XzQgxCqxvkw+kgMZH6pjggsY5yd6lbcrd" "hqYssc5Ru8Cs0ezPFDtzxUGlmDiDFX2CPMKJ09/B/+Znvo//1195N3/9I/fz504d+aMCWOLw" "p/wWtAxvtQ3fgQzWGwEK8cf0Ojf7em+G+gUgr+5Nz/3Yd932Pfnvvdwvy8byh0OcIyCD9+of" "/c6Zf/nwbRsP/eRH7/mxv/eLn/2Hh+/PGwkQX4/dei3lG299AL7969hylx87dQdnzs6YTGcs" "o+mahLRR+BL6R/vsZ4LNzT66iKyuH8GYFNcE7r3jCMp59s5MGW3VGK0RpUTEQLQ10iQIA+nq" "KsMLLxClpA4ldejgZGubFBpLuTdEdguKXoEvS4oiJ090GwwNZEpg/de3K4pC46i5qA3cKDla" "rLDcXSU88TTZxjFMp8ePrKzx3z71BKvLA4wQxAjj6ZzFNCfVgvvvOMLTL1xmNJ1w121HOHfx" "GuNpyWhaMuh36OWCkgYXA1/c2+OexQEvzy2zAEF4rl/bZXCsQ/fGHqEQ9NKcvRvX6HW7ZDrF" "Va3BZ1U7ggjMqhJrPbKqSfoZjbXtN5w0IcRIDAEtJMIY6qbCecdyalhZ2GRuJf1exnT3JRq7" "B/kC1WzOwuoqiyeOsry4xvpgjSvbF7h+8QIf+AGDMZroHZ2ky6n1NZ5/9iz//J/9C7IiIwRB" "1lvEywQZBSbvMp/NSIplvA3EMCYKgVQR25QIOeDLT1/gzNnr3HfbKvecOsZsqcfL14YMp2Ma" "JFq0OZDOtWHR1jZYW1NrkDKgeyk6S8hSjVYghSPPEqwPrVhccmhzACEGfIzYxtHYmsqWlPUc" "FxxlU7b7ZgPaW5x3SKWZREeeSRIliFEhvKQ+mLG/NyN713uIWuNmFUIKlIikWrQgKzZEqXFO" "k2aGqAECnVzQSSN9YzBStC1tJVEioHWCd81hW/fw4uc8wUZiE9t2slGtWVsEJRUxzgnNnGY2" "xNYl1byhKQPBeaQ2WB8QAaSAaANYh0wynAOpFV4oWnOSSO09dQjEEEgk1BEa0aEiRSU5Zdpw" "8cWrXG4MvRWPKRIqpyFomkYyFJJxHSmMoKskiezh9T1snkz40ekVvm/lOI/ddxe/8tQZXrqy" "9a0CWOI1LJZ6nTUjvmrNuNmcXriVPvKOBFh/1K3DbzTc+fWAzmt/qpevj8/UvvmbmRYbJZw/" "PKFf+9wRUNvD+e4//9zZf/XT3/vwXzt5bP3jF6/ceBlI3mR74y3K9p1fP/Xe+5juwJVr+0gE" "y2mXjuxwff86cX7A8eVj/MpBxc/95vOU0xlwDojccXKFfrfLJ75wlh+97Q56eYGrSzZ6y7hq" "hhKCIAVKSi6Pr9MrepjUYGSG1gXlfAeVFCRFThActscsRrSO4VvzfXorC5SVI8lTio6BnT/Y" "7tnc8uLVXV68dJV3F30+mlnW1AYnKBgs9Hnq7DPcaQV/5dQJvmBLnr7Utj32d7ZZy4+wP6m5" "6+QqewcLXNsecjAac2xzhRNH13nh3GWu7ww5eXoDZEJIAqWHidHIAobXtlBFQZSWUM8YnDrO" "yp2PcO25HZrhAUVi0M6jQ6C2nqquybsdvLNt/hyBSKTT7dA09hWrAyFo22Yx4uuAL0s2145S" "1RqdKdx4n4Odc/hOgrcls/GIsizJ+j3ufP+DxNLS2Ior165z/fJFjh89QeNqThxb5Uuf/yz/" "/jd+G9s09PoDoklBSlzT4CIkOqXIuq0oPevSy/skJiXUU0YHN9qJuNBQTRqefGbKS+cu8sCd" "d/Dg0SW2y4SL+5baCWIILRjRrZFnCIFyPkJosLUFVjBpgjEKpQJSgjjUU8VDV3dnPdZ6msZR" "OcfefM54PsUFh1Tt/Y3RSKWQSYLUEusbcCmqCmBau4emrpntj5iHDqq/zv7+BKQgEkkVOCnx" "QmGkwYiUNF8kTzMqZ4kithovBVJpIgKtDUZLom+wTTsFGGMkENptly3rRvCttcWhJ5b3ER9n" "iDgmlHuU4yGzSUXTgCsDUikCsc0y8xZlEgQGWzqkqUkzgwseI1IiCc5bnLNEbyF6cimpoqZk" "gbGTaKMwGynJ3pDRY9ep6RAzSHPNJAejBLgEmkBdwURFXCxJ64pkCH5nhhpP+cET67zv3d/J" "rz57hV/6ra/i3DccIC1fB1zJV/382t8Er6/N+ka6KH/m1yH9Dt3ub5bVersh0eIbBF/i0o35" "/rWRP5nn6ebBpH7pEDC90XRj8utffPkz991+/MM/8dEHf+Lv/fOP/79f9QG4GXB1q95hdc+p" "ZR45usLZ5/bxUbKhctZFhwtbWzzPPqeXI1f9jOMDwf/nf/cwz20FPvWFl5jNLX/zp9/HP/hX" "X+buzQHVzjYOh05yBjgCMEgS0kQxqedcb7Y5qMdIG9hYWSY0sl1sE4MSEqna0GBvLd57DiaC" "C6Mh/WyBQGAc9ugXX3+6ex+4tFsxnFacMTXmhOR7S8947zzplVWyzdMMZzN+bPkoi5N9vljd" "AGAyr5iWc65vD1nsZqws9jgYl5RVzfnL26yvDLj/jiOcvbRNbTUuSirRZuXtzi1ZJuimCi8E" "TkHSy3DdLjHr0C9gfTAgSNDK0LgGo1VrAtk0FFlOZT0yCgSRLE3RSjOZTBG0x6LVSguMloRm" "ynoBw72rrMQ+oyuPU3Id3TmJDw5blUwORnQGXdZOr1Beu42rWxe5PLnGVz/zeX7iZ4+xtrzE" "r/zb/5kvPvZltE7I8wKBQpoUHyIiCqSUJGlKkAGCQJuE8WSENikmLZBKUU+HVLM5Ek9/sMxo" "WvHpLzzOg3cPuOfeu1k6sUjpNNd3R4xnFUZCIw2zCrqpItMjNvKEoshJDBgdUEoiaPVX1gVc" "gOgdjY801jOvGybO0YhIWVVoAS74V4T0WitMohBK45wjRoFwEj+3EGqMCVSTfcSRIwRh2onM" "Xk6MDtcEjIhYEhCODEWRGlIj0BkI2olICYQo2uMTAlVlCb5BCo9oFe34ADaA8xZrG7SB4BxS" "KKSocM2c4EpUGFE1BzTliIO9Ib4xKJWidUqSmEOQHTCJQulIkmbEEHCuRmhNjG1UTpCCgCNG" "0CLSEbAbNEp1cNFQNTUbnZLNh/osZlOevFLincV6x7iWaClZSxXaGKraMm08la0xowPSrUvs" "T67z7EHDpQuXyXspOs/QWn4zAEscfrmX/GE5S3wVCHulq8Lr2zncrAgebgnh37EA6xsFS38c" "2/Lqk1A2jT343S9e+MXGuuo1FOxr/wlAOOeaf/IbX/z5/+tPffd/8+H3PfA9n/rSM78NdN8E" "XN0MbXurPfjtWAL+wv0n2bs8p6wDHVNwsrvKlckBnx5epuhlrK6t8LHhiL//715kqZ9x/72n" "+LHvfTffff8iL7y8jw4NH7xtkdWqhzgwnCw6HFtb4Nr2HrgGYz3BSIRKGSQDMh04fWKTyY1A" "tV8jRYvfBREpArGqUEZybrzNjXLCQrFK2ewzrwPp65xFw2kFQGMDv7Z1g488fIz7l4/z8t5F" "VOrYczV2b5cfXFtH3y/5+2dfZLdqEID3cP3GAbMGjm+s8NLFa8QYubE7YjiacnxtCRcbxm5O" "01is9YwjKJmSFzlVgDwpCKZA5T2qcYOaVhgfqa0nZIrJbEqlNVK3GpwkybChIklzpDY0zhFc" "K2yfl21UTqrbdqHzDacyzVLeodQBsb/N+NI57JEEGQNIhfMO29StCWiSMNrZoRE177/vQZzQ" "/Pw//0WCCmxtb7GwuIbWCWnWQ0hFWnSw3uJ9RGmFUALhJfNZjZaaXBc0tqEMDbnpIbJIM6vo" "FP3D9lnOsdty3v+930mQy1w8e5nRjZdY7BYcO7aKjZZh6ZjOI4NU0U9TlABTLJKlCq3EYeZO" "JAaHtZHK+ja82TZUrmbuHLO5JTStI7sPrTZaSkFUrbFoOZ8hVYLRKYFAHQLeOaKzeFdTB0+p" "++SHGT8BkDJilMAqRZIk6BgRzqNiTSq6ICIy+taxPbbsVFlalJRE73HWkucaJTTO19SHKWMh" "eoIryU3EO0ntA1F5pAmkWUAZj6RmOhkzPphh5468N6DTkehEY7TC+sPsSBnQqo0dauoSrbo0" "PrQaMg/y0NqDw1igJihyk6FF+xwiRlYXBhx/cMSdyxe4SE5V3E6e9HAhMLeCfpGgEoOdTKlK" "j61r0qLmt6/t8Nkn97+VVxt5CLDUq9ab8Ko1JL6GxYpvg5m6NXH4pxhg/XECp2/k9vTxMzt/" "55C5eqN236tZLHNjf3rulz/13C/+xA9/6KcuXD+4evHy1eeB9CZP6lvM1juk7jw64DRrHFyd" "oZOUzc4StUn52NazuLShLzQug0duO8rf2lzln/2Hp/jMF88Q3Izd4Sa+hP/7jz3Mss/ZvTDj" "wZU+m4Nl9sdDZLAoYZBBM40jggwURUZiHFPXEEVC8DVCBGzZ4GJFp5MgceT9AdNqxA41p1xN" "p5uTqJzUvLnD9KiOPJntc/fR2zk6uJfr18/RPd5lujXn/M42P7S0yoMPPcjfOXcBYRtsjCwu" "LLJ94RrzMuPISp+rOyMAaus5e3WHB06uoVJNmgrKsmRCjShFG8USPE4qyAeYzirljRl2f4zw" "gXI2w0hHt9dnMh0xrEqUMmRak6U5QmmEFPjaHoqlE+qyQkpBCJZoLUdVziP9PtVwzGCjR7i2" "RZ1onNBkWiFMgslTRCoJwbftvvmQO9Y3MXmfTz37JbZn+5SuJE0yXGMpsh5SKqROaOqKKEBJ" "QwzQlJa6bgGMi44kzxFWUUhYGiwwH+fYqgEc1pbc/65N/uO/8pP0Tz7IaFLTPbbLVz/2u7x8" "9gW6e3v0epJOT7O0oCg6CyTFGqu33U/WXTr0+0oQUhACaK1R3hMbTwjgA0zmDQcHJbP9OeV4" "gkLRuNbgUxuNTgyNbQCBixVKG1SEGAK1tWAdloAOAjHoQbcgqnaIABGJ0aGVJFUF1gPasZBp" "QqzREjSSzGjquqaxbRiz0bLNyjwUuzsvmVUOH0BpgbcB6xr6mWA8rvFlSpompN2IUmOCnWGr" "EaP9GdWshCCo5lOUkuRNj7xTEKKkns+IZOhKoDKF9ZbgarRLEDpFKkGiItFZMilopMAS6JsE" "nSbUzrNnJWbacCIbcOftOXeIXS7VjzP0HyH4hChag2Ct26zF6Epiv8PC6m181wf2ePb8nOnc" "E0J7vIyW+CCxr5Oo8DYYrFdrrngdgHUza4t4g7Xv1sThLYD1Rwq+XuuHJV51nMXrnMyvPuG+" "1goUQPLosxc/fvzoi/f+Zz/xwZ/7//7ix//zK1sHT7wKoL3RByFyq/f9jqkHb1uilxkOckER" "NMe7izx/4VmMikxczdGFBc47y3/1Tz7LT37fu/jv/8sf58tPvci0DvyjX/oSRaH44b/xIygL" "12dznLMkacqkqYhlRdFfYVpatgrYHo7pZHDs9rs5d2POYnDMmil2GDjWXaNIFEaACIGmdLw4" "3EOGQGqSVjjcePI8fdP9cS5SKcX5aovy7BadpZR7/9P/FeWFGWf+xb/hie3L3LF8hL99x8M8" "bqeosWcYIkFJrtzY59jaIsfWlriy3X5zP7mxTLeTsz+ZstHbpBo2zEsL0aJpfZaSxS7d4ydQ" "3VV2fufziFmF8BGvYDqb4MZDsv4iMXpiNNR1jU4lJkqC55CNAR0UWaeLdw1ITaoNd670cEFQ" "T0Z0pWc4r6mNRsoEqRVJnqHTBCHl4T9BNdpH5B2+9NKzlM6SpimNaIX0QiW4KEhMgnUeJSQ6" "MQjZRiFFJ9DaY0NDmqU0VUOeZ6yvLLLQ7fP0lasYmTAvpzzynlP8xb/yExSrp6gbj1Ka3sqA" "zTsfoZwpfLkPGDqdPsXSAosnTrF5bJPllT5aRObTMdV81uYFakXVBKyLhNCacM7mNde351y/" "tk85nVLO52RZSprkWFejpME2tmWyQkSqiLMVwcXWLsHVbXZjIklVl1gs4PMcmRiIjrqq2oui" "EKRZhnGHoElEop9T6KKd3rMOLRVl4zDGEPBU3pMaQ3MY9eQQKKVbM9DKEb0gkRpflQzSRZbX" "VzE9S9MMCfOG8cEB1bQmEJFCE/3XrsgWFywqaeNutFGgFUInGKOpmpraB3QWIQoSrcF6VnII" "vkdFQrfooU1K7nMad8CoWeHl2nGyO2VlocftfcXW5FmuzR44DIxuzVGdiwiZMVg9waC3wfct" "djh5W2AytzSlZ3owZbgv8HKVpH+Kre0ZTz13lpcvXvlD071vwmDpw3+er58WDHy9LuuNJCyv" "t7aJN1jPbrFZtwDWHwmz9Xq96tfrZ8fXnNSvPVHlr/zeV//p5a1t6X3sv+pDEN+CAbtV75Ba" "zwsKKbltfYNl0UMcjFjPEvozSWeQc+quRbbWcsynA7/wG49xYXvG3/0vvof/8u//DgDf9647" "WNUFn770MrshMG1GuHHDar5Ib2MTW1uuMma/GnHb5nESFSlUwfvvWKHaLmlurEPj6OaGTlEg" "bE2hM6qmIfGOu5ZXibHmYLLPyvICG4V5y30adBSP7V1HVmO+a/kUp959D/MlOPsbH8OnOU/e" "uMxyssGp3jJ/6+6HuCgsX8r6PHrlClf2RuR5ytpiwWqviwuOr750me88fZyu6KJyTdVMEVGS" "CYnIEkKqWLj3TlTlKS9cYzifMRMWGx0YwfLaOrvjOUmSUdUN2qQICUoFlFDUriI2EUnES0lm" "MlSE9USy0Cs42B3BZEaMnrmzNAKKJCHJC6RRSA3aaKRUTA4qdvenXC8vsHrqFGnd4cruNppI" "qlOk1q0nU4AszUEJhFQUSYb3Hit86ygvFU1lKbIMSSRPMsrRBFyFlIEH7h3wk//Jj+PNApPx" "HJUqhDJtVuB8htYJTVQcDBum1VXevb7JYNBvzUnriiRPGAwGpGnCbDJlOp9hHdRVoKk91jpm" "04ZyXFFNp1TlnEAkhAjCIaRsL1pK4oMnChBR4pxrWRbnDyOG2ilErwdYk2M6BcqACB5BREQw" "mUEEh85ykjRhPi/bCUFpaaLGhTbE2juPP3RX9zG21g1S43zAS4UUrdFrOd+DaIlBkBcFg81N" "0l6HYbmHVLLVmtXtJdc6i2/mdLsrVHWFCxa0IOv22kxHo0iTogXPutVeVc6zP90nT3ogBNrA" "QjchTEHQJVEFmTZEnWCVBF8wrro8f7DLHdqwtthlY6EBzjF0d7Yh0UKglCUaj1GSvJszcwF5" "fInjWRfpS6Y7u5z56j4vvniG27IpP/kDj/A3f/bH2D4QPPrYeX7/0ed4+szLbwa21OF6r14j" "HRGvWa8CbyxjeT2gBW8eHfdnms26BbD+eABYfAOk/2qA9upvDwDKOVc++tT5v3vYHkxvElzd" "AlrvkOoYw115isg3mZczds+dYyFNeHilw9F7+vy77SFfurTP/+Wv/Xn+9s/9Fmna4b/9p1+m" "0+3R6STctdDhYHvE3ihQVjVNbSl7A3Yne6j+CvNYU4kaISI7+zusLvaZTUcEXzMdefK8Rz+R" "MJ8znoxZ6C+SpK1/1Eq3x7gZ44RhMOiS5D2Csa2vkw9vuE/XxpbzWyMe7HW4dH2L+n/471nu" "b3D7f/wd6Pvfz4Vf+hUufOnz6HLEkSPHWc96fGRd89BgmauTKfuu4rY7BjyxO+Y3vnSmZcaC" "QEjIl5awzZyqqdoFOTN0bttg4did7Hz6eeqDGxwwYXt2wKyZcerEbfggcUJRu9ZZWwgNoXU8" "b5oGpMA2ltwkGGQLDKYNG2ur1JVjb3vIJpaDmWPsa1zHkPZ7iMQQgkdnKZ1BD53lvPzkGSbN" "nCo03Ni9zurx4xTlFBdFu2CrBOcjGsiKAutbc9F5VR1aDrTRMkpqvKvx3rK6uMhyf4Hr++dY" "HHRITcNf+pk/T5ALDHenyKJLEiocjoOdEZPdEVIYsnyAZc6sqvjiZx5lOpvyvu9+H+WkwVVz" "iiLHpAmdfg+hBG40I0aHJBKCJwaHEBEhwCiFQoGKpEmGP8w/dDaglCIKSNIMosS70Jp3Nm2g" "MoUkCkPeKyhSTaICUUiCUK3OSoDSCikcTVlhREQZgRQOJQXTyhNDy/BY52i8J+BpvGeh16Fy" "DpTEhUBzKHDvpQKsYxYkUkdmwx2inNHPJaGxNPOS6azC+rat23iLlgkqKZAmQeoEEQMh+Nb8" "1Sg8AaMLvPAIt4/zzaFXmiSXOVfmkSIvSE2CURoBFDohNB06YkDdLHJjWKK0ZHXBs7E4R02v" "sFceIaoErR2JDmRZwCjFrm24Fu+klh20rGiWj9C9d5f18nmuXb3Kp7+4xwMPr3H7sTv46b9w" "jL/0w3fw3NldfuPjX+azXz3H/rB6IwZLHa45r8di8TogK7wOKHozRuvtdFP+1LNZtwDWN8dW" "vVWbULxOOy+8BlS9+gPwapHh11it4nXuC2/eN7/FaH2b17GNjD/3QGC8d5X5pcvYkUd158RY" "cez2k6j7Cp558QpfeOIGH3jX7fw//o8/zie/fJ7f+J2vHD5+gZUjS5zZmXMw3KFuSiSKrUnO" "/UdO8OUXnmR9Yx21UpBMpxS6oNcvoNNhtw7I0ZzZzghjCnItqOuAr0tkP+fsfI9xNSRYS9Ix" "jGdjRvuS5bsX2uy3N6lf+fwFvI+E4w09MSC5fAbXvcKRlfdz2wO3sfiXforJtRtMqxFb8zG5" "a5kj1wSOph1+9PidZEd7bLvn+YkPPdwCrJlDKEUQDpkaQjWnbHKcEbzn+38Ivw3bn/8Co/I6" "opOiQ4orJ4zHQzqLKwyyFBscMy9QyqClwceAbyzdTofGVQQfCdLjrOdUnqKbwHhWcXDlMrdt" "dpg4x4QabwNNXREaizQK1ckoFjoMt2t2nj9HTATTsqancqIIFHmXee1RRmOSgjTPiVG0TE2i" "CDFS1g3mcHow+EAiJUWRQ2PJpELhsLM9jvYF7//wB0kGp9nZmVDbSCI9TaiZTmfsXxvjaos9" "1N8Zk5DJPpP5Pk9+6QnGB9t88Hs/THfQZTwaoczh5V9KukVG3TjmszHj0YThsKSsSkL0aCnx" "xFZgLsH5SCYTvGoBVpYmWCLBeqSQeCHxrrVKkEKh8pw0y9BSoBSEqDBpgcLhm4ra1ojoSGVE" "69boVAmBD45U69ZKwQXKusFHj8NDFPhJxCQJxNZeo3Y1XpT00xRXVeT5AoJ2AjDNFVmSo4pF" "uomG2DCbjSnyPi62cVBBCtAKrTMQsdWVKYFQERkFXgSijAzyHlVw1CGSZyml9YxqzcrGMmma" "YZRGcujyLjwy8bhE0VSayXRKmgkWeoss92aUdothtYASFanx9PMMJTzPXK25XBmOrKQURqGC" "wecJiw8kDO4cstCzFJsrVGaJ4BJiPeT0ZsPf+svH+JkfXuJTTzp++9MXOX/x2msBlnkVuApv" "ALReDbJeb6252TXy7bQN/1SuV7cA1h8NAHu9pPLXY6vE67QKeR0g9kav93og6xtxfr9Vf4wl" "peDBu/v85jMT/tp7jlGdP09eFKS6QAfL5y9f59la8f3feYJ8sMwXn3yZk0fXePr5c688x/6o" "ZDSfsRQzunkPJSOj4YThaIf5sWMw6HJtvIVROesba9jGEmVCnM8ZJIv0lgsml2Z0teLU+jp7" "0xG9NMFHTzcvuGupQ2VrcIqe6CJEzbMXbrQL55vUZN62KD57dkQhA4v9Dp94bo+fTRK28n9N" "E5dZuvskt33gfVz97Fdpzl9EpwlUJXecOIqWHf7xp77AfldxaXjA/mRO6gPfdfpegpdIIYlS" "gUpZftdDbL77Awx/7UuU0212yj3IJFmqWejkTOsJie8RkfSKDtW4hAhNXZEmKT64FkAkmkxL" "jIR63rDcKRAhsnVjh6O2JHUpu5RMRUOnn0GMCK1RRUHaKVAq59yjTxHHQ/JBl4FYYDyc0O3O" "8dbS7XZprENKiW0sIUaMNoTaI6UmNymNdUQfEVGipaFIFcTAaqFJXcMdJ27nxBHD6qn72N2u" "GR2UNAhMLFHaMxtbqqnF1f6V4OrGNtho8d6RGMHBqORzv/853v+d72V1c42mnrcXDCnwzreC" "c6MRNOxsbzMeNphX2D95yOa0BrEBj/OhvWh5h/OtJs6oNuFLKoV3Hts4Yu6IviG6Nqg6MZIQ" "E6q5JQaFUhm+8fjYoA/9xX1sW4KCiAuROlhK37TsGQEtEmobCDiUDhADja1I04xOnrG7M0Kv" "dwFPlhqQDucduECqoEgExhi0SVpTUQG1dwTvEMKDEmhkO90pJFpplIAQLCYf4Osxtq7pJSnj" "WclMd1iVgk5myEyCd62xafCR4C1K2ha4Bcd8XqO0YLEzYKU/Y9bsEr1ECY1Wgmm1RzN/jk5c" "ZOe6wgtDrkGphE5xknTxNrIOmKxDViyS5RnCB2LlsOVVVpMr/NQxz0fe2+fjn+ryu1/Y5uLV" "YQDcqxisV4Mpf7j2fO1neJ22Ia/TPuQ1LNebMVo30zL8U7de3QJYf/TMVnwdlC5ep80Hbyxa" "F2/x3NzE89yqb5O662TOdGJ576bmd568yLtjTa/XQwSFC5G7tOaffvlF3v9X38PJYwlbz2zx" "S7/+6CuPv/PkCj/0gXt4z9E1dl+cUsSMTEYsc7rKcOXSGRaXFzh/4xpFA3IqUdrQ7/XQTU2m" "G0KRc2K5y30Ly/TWF3GX5kRb0+iEg2pIsZBzLQj6CpKyotNbQNVvL3j2Rt1gTZ/Ht0oWnrvB" "R+a/i1lax6yssHDsGAsfXmJn43HMkTX0tQOW64jOJNdf+Ay/+9jzrzyPkZKyqTiyMKBYyTmw" "kagid3/fh2ie26I5d4ZJHBKEx9VNu1AnGqkMw/EB3cE60/kcoTTz+Yg0L/D+cJgqBvIsQ0ZH" "U9V0ZpGekjTScn20x3tXjmMZMvWBWkT6iUFrjUwykoUB/aVVRntT9s6cRRaCWAnWlo+QdRqG" "+yPS/gCEIk172BiomopEJ23mHwItFUqYVkvkfWuaGQIxSLqZYSlPWdhYRczHrB9ZZLhbMaoi" "09JjfSSNNdIEmrrNDxRSoaLENVWr7zIdiqKApGbt1BHW1zc5e+ZFpuM9Tt99D5PZhIikbhzD" "0YiyLkFE/v/s/XewZGl63on9PnNs2murbtmurq72brzFeDgSbkGB1C64QaOllkGJoihS3GCs" "giR2JW2IS3IpEaIDRQPCDECA8DMABhjve6a97/L2+pvu2M/pj6zB1NRUdfdgB0T3sN6IjMyb" "N/05eb4nn/d5n6fTjZnMWtqmBRVQUiC1QsyDG/HM3dPdVc2PFArhBa1tcdajhLwqSgehFJFv" "kU2FSBVJEuOswyqNiiJiETAKQjt3jzetnWvSnKU0NSZorLeUsxrjPCpWxCIQyYAWzFuarsXh" "WMg7gKGoWnppl1hJoijDuZpy1BDGY0RwCFcjpUQGEEpcdcxXBN9izYxId5AaVKQIQs7bmUqT" "K0lQGkIgkZBJhY9Slnsd8AYpAkI6Yq3m7vFaIlVEExKatkXKDoSEybTBe0MSJyx05l5yUnYQ" "EiajdY4tCNZWEkoz5vnNmJOTZVyTsKJjEpFhQwcfDVFpgo41WgZUD7xboakPUpaX6XGWH/rQ" "Mm++t8sXnip+4BNf3njpwuXRJ6+CrOg69srfpFUIN04fuXb9uVGe4S026xbA+raDqZsBqRu1" "8K5lrK531eVldtabtQJfTpx4q14zJSibwP2HOvz2Y1f4ysmGf/veE9i6meekRbA8iAhB8T/+" "+2e4dGHjmx7h8nZBMIa+90QIyDI66Qp7UcxeKPGJYVZt013q0hlAPnCIuEtNg8y76NZx5fxF" "9nW6yH6Pp8+cJdYRXgSm9YSZHdGPE/Z1BqQ24FpPOas5eiBBKYFzr26XOr/nOV843nPfKp95" "YZdDSxl7eyV3mV2mv/LT3H/fB3nxxRc4qAx3H7mDn/nwLzNu9vg//8DbePjEAf7ZRz5H0TiM" "95wf7bDaHeCtp5Nqjrzjg6x0j2I+9RmM3cT4et4mqkrAk3ZTfAiUtWVSlSgdIazAO4tE4GxD" "HMdY00AazTPpTMNQSlILz29cZHl3ykra42Ik5mHMBHQkyPodVGeBuLdEFHV48Utf5ezzz7FZ" "F+wUM+6+525mG+vzCUOlSdMOZVPjvSeJY7Sc+zdJKQkEWtvOtVlSzTPtTAPOMOx2SLMucapY" "GQ7YOnOFR589TXbwIMu33Y6MNcEHbG0IljkDJhSmsXPrhSghThWqHzE8ssTyviVuP3qYX//F" "R/mZD/80f+bP/Je8+/3vp6xr2rYk+EDdlEymY6ypiRIBQVIWNR5HIlIa28wn66QkEgrjWmQA" "JeeHp0gonFKYtsX6MAdeUhMFh/QGHRSJFoRIk+UdJIHgA00l5rE23tLWNY2BsvFMG0dtPHUF" "voXWuvmRTvt5fqbzeAJ4SyYNC2lMdbmgmmiW0xgpIYo1UiVMij0unrvM9kaLdwm9OKa1BUpL" "siTQ64K3M5xTRCLBmJo4iUjjHC88PgSEnAMsSUAryLSmFJp+P2fYT1joeESoKaqA1gOiq+yg" "kgEtJcYKRFC0raRsLMMFRZrlxPUYHxzSeyaFo/X7kCyynFk+eHvNfZMtHr3coRUdYlIi0QU6" "tMTEQc6DBVVA6wQnNVJ3EPEQHw/p+ZO8KzT3HVs5+FOfe7LzS194evtfFEVzlrmuV1zDXl0P" "uL62JvnrWK3wKtabV4rdebkf/t8xIEveWvD+k4Cym7XybgSQPN9oAOevO+cVWoO3ANVrfHc4" "tC/nuY2aL74wwzrDOSPwVtLUc38b6Q258DcEVwDWOl44v8HzF87h2hGH05x9Wc5K3qOdzdCd" "lJBK8k7C2Aue222JB33KxLHbzBhtVFSlY8s5Xtjd5sJ4lx07pUlizhZbLAz6LK2sUrSGyhhM" "2zAqavb1Mg7u773qd7o3MfziV3Yoopg9G/j4uct85swlaqH55Fe+xL/+hX/GxG7zkY9+mM9+" "5jd448OHeezKJf6HX/o49x5Z4L//09/F4ZUBAJcmezTNDNU0rAwXOHHvB2g//hma0RlG9R5K" "aYSem1JqJZHeEUUapWF7tE5japw3pHk2b5d6j5IC5wzFdEzdVPiq5NjikG1KLk63ua3fI0k1" "RkToOEcKiRbz6TLVGZJ1l5lsVZz9/BNMjaEMmnFZcubsSSLm8S74gLMO7wJaR0ipsMHjcPNF" "MUikBx3CfJoMgTOW4AN75YyzG1vs2z/kwH3HWT52gDSTfPYTv8sLX/0MSezpDbsEAk3d0hqD" "MZbWzIXkSEm2f8Dy3Yfp71vkwMF97Gzu8NSTLyGTRX71Nz/KL/z8z9FUNZ28C85jypa2aphN" "JlTFmDgJLCz10EpSFQ11WeMbS3AeIQNa6zngvhoM7kLAuoB1nuA8SgSUdsQJKB1wwVFVNc5a" "tIQ40kgpcAFc0BgnqVvBzlbD+rmSnUuG3SsleztT6naeYSiEpDYC4yTeC1rvscEQuZYklEzP" "bdIbrIHQGOOJ85SVAwsMV5cZrwc2zkwRLsLbCnAsrOQcORqzuhhIdINpx0jRYJqCqhyD8Cip" "5xE/wc9/6UpBR2oir9BCstDr0c8F3ahkKdpgf3SK3L6A9DOUcDjTEPzcD2Ku/48wbUpdQVvP" "mU2pBMZb9hqJZR+jKqOxKcF3ONYT/ODtV3jD0mUSJcmilEhrPAqLxnhJ00JjBSYkeDpUfoGZ" "PoQbHMDrxTDIE/HOo+mP/fCbV//p4X35+/h6FuHXtFkRX7dy0Hzj5OHXTvJVnrhB90Xw6o2v" "vyMMsm8BrG8fiOJbaPuFG4Cn6//nbgCgPN9s13Cj+98CXa/VL5wUnLnc8PEvbf/BdRMklojK" "xLigiNHc3r85kGmals1xw/I9x3GrPdarEc+dfBIhK2TwVJMJe9M9Ig1HD67QyxWf+OpJ/uFP" "P0kzFUw2KkbTKVYqzo92UVmCkBqdxsTdBLfQ5dee2uTLZ/Zw0hPFCZ3uImYqsO23FtWxOzH8" "/MfPcmW35uLYsdyJubCxzZFDS/zeM6eIY7hSC756+jRnLj7Pf/M9D3JqY4f/67/6CONiwv/u" "TzzI0iBnXBtia1gJioUaqk99DHPlSbbKTVrtaF1D2zbEkabf65AmCWkUg4Sqqdjc3aZuSkKw" "tO0MJeexKliHmU6ZjPaQRU3HBsZxhDA1R3ONiSQbTYSMF0h1QrhqwqmyLlGccfbRlzh7+UWM" "aKjrguDngdJNMBjTYNsG01wN4RYCISVaqqvfyHlnRQpJEkUoKeb5es5S1xWVtWzvbiB9zWR7" "F3LF/e94I/lggccfe5RP/9avUI636Q06KCnw3tNYO59aFIJsqU+82kP3cpaWFlBB8Gu/8qtU" "pgbh8TLi81/6Cv/vf/T3eebxx+h1MjKlUdYivQNvmU7HWNsyWOizMOyQRnPQKBHzgGQkwoGp" "Da7xWOsx1mKvtjvxzRxcxXNw4tx8/zGtpaxaiqqmtQ7jPI3xzErLpGjZ2ZmxtTlhe2PCeK+g" "GJc0lcEDVsdYGeOFwou5xaAUc7PYqGzZPbXNyvG76HWGxGkfnWaoSLG4tsoDb38vBw7cDdZB" "cHT7yywdXGPlUI9ONqOb1zgzptq7QCxA6giJQElxVYc1N6LFG2IJeEeuIxbymFyDsDWRq1mI" "W/ZFV1j2T5Obcwhn8d7hnce5gFCaQEQx01zcqJjV81aiaRumFQjVobUZRTtg1iSURiJVj7v6" "M+4bXCHRFiEiBALjAiYoKqfZqyTTWlO2OdOmx7RZZLs5wMj30f2hS5Z65ZHlaPUH33bgr99/" "fPF7+Uavxhud1HWXbwS2xA1O8gaXue78Oz5l5FaL8NvfJvxW+s7XCwmv30H9DXbMlwNy14oR" "b4Gs12B5H7iyMfuG68ooxcYZxagirz0OwZGgXvZxYh0RyorRrOT5C+c4FKekSY8DeZeXmm1c" "Am1oOHRkhU+8sEmaBN710BH2t4orsxapciazGU0xZXl1kSzKKZqSpJPTW1zmoNnk/sMdum2L" "jAc01jMdW7T4wx8PT263XBhZNtrAn3rvKnk/4+Joyv7lJS7tjNk5fZl77Qr/1Yce4l/8xqP8" "g195lPtvW0RKQW0MXTQLzhHKguryUzS9mKm2yDgjzWKapqI37ONcA0HQ2JZEay5t7jGptrn3" "zhPkaUaWp1jbIAlzPybbEEZ7vGHlOAZHMZ2xL+6xEKdsj0sKZyHzLC3lCOkIIiUbrFLttDz2" "mUeYmcByd4lLG6fJkhgdxQg1N6K0MHfLt4Yojgl+bsPQtA0aNdcBKYEUEmtbkkzhbcA0LbUt" "ue+u2+ikks2NK/g4YvHYYd7yzjfy+COPoRE8+unf5d43vZNub4kygUoJahQOgb8KEBIds9Tt" "87Hf+CiPP/FVlpbXEALaeoZzLRevjPjZn/9ZHrj3ft758L0czBOmsaJoLcoGfFvRuEAnzUni" "PrOipmlaoiSZG6S6wJzE8nM2TipQmrqaEjzoSCK1QmqJEPNDkg0eTMDXc02X9Z66aalax7T1" "VNZTe4fBgZkf/YILWDc/NFrfguxgvMBaN9dDdXvUF9bxjcILwbQoUVlMWdXEcYmkZfGeHod2" "7kdsvZmRcSjtUIsan26iox2GKuBtTds2KKXQoovzBukkSswZOGcaMAVxNgARkeULZGk0t7Zw" "LVI6dJSio5zU1OR2izjscmWWUYYOBIG34B0UjWHalKRxQHmLbTyoPiLOMb5hUlma4GkjTy+R" "5FHGUjIj8WeorcK0A6wUCDmPL3JOUBlL2TqmpWTSJIyaZUR+mLa9HNIFTT/qV8mkbt77QPK+" "fifJnzi5/fmiNJtX8cDX2oVf+zF/rQD++nXp+ml5z41jdzx/eG3W61r8fgtg/dECr+t3nptd" "vtHEoOfVhWuGG4Cqm7Fqt+o1WIWTtF5ivMe2AackD/WGaHEeG2686RY7ATHdYHp+gwNZxkq3" "z3jrEn2VIFpBd7XL4m0LXNjZ4wfevsxXXigZBMm+umWv06WezuaeQLYhOAfKM20rxs2YaDfm" "vuUuJIIkyqicZ7pboxPNsBtzfvMP+QUJ86y7R0/tcW7nMZzzeHuGD73pPs5ujVnfqdmr13nb" "Xft5x737+dSTl3jizNzVXSqJIZB0EqpI00SSWjRIpbGmpTEV3V5G3kmY7jVYb3FasjstuLQ9" "oWoM+0a7LC0M6HUHVNMxzpaI4FH1lL5tWdIddmclu+Mx70oXSa2iDi1aeExVE++PkGlGeugE" "neX9PPPpF3np6Rc4tG+VhX2HyNZ3kIkDFQjWEqmYqq5prSXvDL8+QhwCWimst2gipAg471BK" "oIRGdGIKV9ONam4/nDGbjimrKSpeotvt8L4f/F6WFocsrSzw6Bc/yyO/80u89QN/gqw7RGxB" "pDU6mpuAKq9Y7fU58/xJfu9jHyOJM4L3xHE+n7xrBdK2xEnO48+f5uL5i3zgoXt4cHWJLJmy" "OSpoXMB6Q1kVJHGHLIvnmjVrEEgiFSNwtC7gBXhvwVkIHh8kSs8jea7mJOODxwePkhKlJXXT" "Yo1lVtXszlrq2lK00BgIYj6dqFINCrwICA95klz1m3K4MDeNTRDsXtokXV4k7XeZMPcZc23F" "ZFIgfUsQls4dkjw7Rl1HRMIQkpbSJyymklgVDEPLZGyw7QZmYsmyLl7PMw8JHmsqYmr6qgfE" "5P0FOlmO8x4zHjOrHT4RJIlCxz3iJLCSN/TSDS7tRlxpVmhEDjZQmwIvDFKCsBWYlGE2pBES" "EebO+o7+fDrXeUQiEDKin2gyNiiMQCSDuaN+gBDA+UDVOsaVY3Pq2BvX+EajXZdVEcKwk4pE" "C7cQz9T3rOz7sw+f2P2hTz524aefPb3zWb6eWfg1kHUtuPI3IAKuJwhu1GGRvHrvLF6BjLgF" "sG4BrW9C3jczGX01gE3w6icVb6bzulWv4Uo0tEVNYRxjK+g6z12LKQ8v9fnK9viG90nTmE9e" "KnjjsM9CnlNOC5xv6Oc9DqRD6ts0lyaOpp3gLLzhrlVW1sFfAOErev0+wTRUpcS1DUL2qG3F" "4vIyS4s5aRfiPKEsoaDBFA0+C7zhUMyTp//Xv+edUTEHl1XNG+4eY62l18149swm46LggdsP" "sNib2zQAeOd5yYy54+gibekQwuGVQUQa21RU1ZR9ywdJ4ogy0kwqi7WBjb05uAI4f2Wd2w4d" "wNsGJTxJIjFFhTEl+6MMIRS7jWF9d0p/uEjROibC0nqFL3pIoRkcGtK//W6EGPLMk0/QujF7" "RUy8F3Hk4Cobo3X6w0XG0+ncsFJahPc4bwnWI5AE5Yl0hL0alxOY63pkgFkxIY01cZzyhuND" "usNlZrub1OWYuNsBqVg5tJ+H3vduZPAcvesIT3/xi+yOZkTpCnE0b2kZa8iSjLXFHnY25df+" "468SpylRns17nFKR5AuAwrQ1e6NdOp1FRoXjVz/1Wd5w933ceewoh47GvLA+Y1K2tN7hXITz" "giRWSBewrUd4j5ZzxswHgRABFwzCOxAarrJ0WguiWM2BFXNvrdZYLIKiDVRIGu/ZG5eMyoo6" "tDhncMERhUBwEBlNFFs8Me6qI3xES1cEoloy23Uk976NmUxwDrSASEmctTTtHjAmHwzJyh56" "BEJEaB0xs8tsm4jV+DJppwbvKKoK006wZgyRxpoCIcC0E3AOYRRWSEQQhOBII4kREUWb0BhH" "aiSdxM8zPmVE2nEc0bsoeY6Lu6uUfpG6dhgaoq4GDKnNWY4XWG/HQEBc1bU5I6h9F+0TIhWT" "JgmdSCHMHrXVoHp/wJC3BsraMyk921sl09EuZT0ijqSo00TOUqdWUxlFvpsPs2z/4mLv3v3L" "vTu/8OzWz3zyK6d/rqragq9PGcrrwNX1zNb1U4fX3lbyzWalN7Mzet2CqJeVhNxa3v5IwBWv" "AHquB0DXa6yud9INN9jBb3a7Gwnib9Vr9QuoJccGFkRLGzyjVjC1LXEa+PG796GUuuFvuY99" "9SL/8ndeYBYEdaipcai0g9KavNunbAKjWc5jLzi++sKEj3xxxAG5wmgaGBfjq60pRSfNqKuG" "1jbICLpLXb50foNGw241B2Ol3cMwppg06Dr6tr5/YwPr04bjR/azMy6xzvDsmT0+/fgZHrrr" "IJ386/mHW9MpPvK4KGBiT+UMs3KEjD1LB5fn4cGRRkiY1SXndjd56uyFP7j/eFzw7Eunadpi" "biBpW/pJCrblzrxH7DUiREjvCMExVZ6d2QTbVjTWcPK5HdL9R+gvLlJPLC+9eJHSlFxav8AL" "z38FTYtGMJ7McChq16IiRdrJiKK57kpIiRASKefmmHMfp/ma1DQlkZLkUcSRfsQdRw5iLJjp" "LqaZYJ1hNp1QVA2DlSWeffwc65emPPy+9/GWD72XxeUOC0sZWSrIs4iDBxbQouUXf+EX2R3v" "EKcx3jmSrIuSCUqmpOmAOFkkjQfEUYrEkmRDnjq/wYd/95OcfvEc96ymPHAgJ9eWupwQQsDY" "BpwliSMQHussrWtpTElVT2hsQxxFKDxCQBwrokgi5fyQJKQgIKgd1CYwM46iMVRVy2g0Ylrt" "0bia2jYUbc2srmiu+m45b2hsTW1q9opdfDtD+prRuUs4kTO84whNEHgkUgvSWJBFmhhD5A25" "FnR6Db1uwqATXwUmEeNqmc3JCsatkCRd8lShRU1bnqeYnqZxJUFAsBWdKJlbVqCwTXuVZZzg" "hEGoiLpV7E08V7ZqdsfVPJYHhaPDoKc53L3AWnGGxVlB3NakkScxAvXCRbrbBaKd0VQT6rYi" "BI8MGmMVdSspG0VdW6RQLPQSenJCqHeoipK69RS1Y2/q2NypGW/vMd7dxtataBorpk2kNss0" "2vRxbvOsZ7JEyzxnMOwtvv/hlf/Dn3r/Xf/d4jA/dBVAfU30fr0A/nrx+7WX5TUs2I3E79dr" "sODGGq6XaxfeYrD+M2wHvpr/h1e4j7zJbV6NPcSNWKtbIOs1XnGsGSxJ8tIixppprdhTkkHl" "eedyzl955yr/9HPrOB++YSuXVQMCLk9LFvIeVfDQGkIU6CWSydYu997W4cS+Lv2sz73xYTgb" "c6l2GATWGhKpSfuLLNWCcjwmW1WoUNDrpEwLz9rAc2ZXkh7q0x17xhcrNqtv/zHuy09f5n/z" "oVWWBxm7oxGNkVzZKel2djl2cMjTL82nKZ/b3GXDtBw8uMZ4PJ2LrKWiuzBERRHltGC0s0Vd" "FtTO8uUXzzIp2294rtPnL9HNI+44uEzkGnrxAB0CvXzA/sUVnrjwPMKUxFWXEElWuosMFhbY" "KAquNDOefuxF7nvPn+CZ06cYb22Rq4hOHFNXE6rpmMXeAqe3t8h6PYIJyDhCRgkBiLXCekcI" "HusCUTQPTFZSoGSECzXCO5YTwYOHDhBnA+rRmOnGJk1XItsZ3rZMR1M6BxOO3387v/dzH+N3" "f3Wbg7etsDBc4MjhIVtRIM4SskTyy7/0y7x0+imGiwdIs8FceC41Oo5QMkImPawNuCgh0opZ" "VRCSmEhFhLblC8+e4bGXTvPw3Sd46OAiG23E5bFjXBuCAKXmHlVSShKhaJwhBEccxyTSobVE" "qxjJ1elOIXA+ELzHOLAeKuNorcO2lr3tKXujMU1VorTEC0jSGK8ESZqhsw6IuaB/VlRIYQkC" "LJ69K1fwi7dTBElTVGQdhUocSrVEAmTawduAkIpO4qnSgBIwT9uJIDjWJwsU0jKUe8QiQsYS" "ZwratoS4xegxzhT0OwsEb9B5B60loW0wfs5ECiHwHrxzNDYwuzyj13NEyuLNFKUUZjYj2dnm" "QBiQSsmCsITdhvbZHfSywB/xFKohxAofx3QCqOBxQTOdWZSMiRKBTgS9XBPVNVttyaTtUtQR" "VWWppjXjvU3qsiLJ5vFEQkTCWxmVcae7q50P0oglIqJ8iDaNeuj24X/R7d538Pe/dPYnXzq3" "9dQ1QOl6K6HrdVrXA6QbSVauD5PmJkwW3ymtwlsA648WfL0aDVa4joa9kRhQvIrnDK/AnN2q" "12DVleHM2HF8MYOLFms800IwDYY0xPxvH7yNPeH5mU9v3HCrN8FjvUNHGcJbagz7lwe8dd8d" "PLnZcO/xA6xvVXjveXF7RBE8UdIDL0njhDWdkcWK3d0r5Cs9yk7MbZHl+Iri8l5JrGN0qJF+" "Pu12Z1dzdDHn3G75bfsMJtOSJ06t896H72JrVDEutgB46fwOWn2dZJ82jk+d2uAv3f0AutOj" "O1hgNJ0xLQo6WYYzhroqsCGwNS3YmdTf/JEFePrF8wjf8uCRZRIp6GUZ21sjMrHLqCxYVSnD" "WlH0Mi5t7SBEBCri4B334zCcf/okOy9eoBfGLAyXyaRmqgKXdre4a7BAN8sxNhAQlGWDpSHv" "dEhjhXWOREc4a6+KnT1eeYI3c8PMqmDf4QG9zgIizhmdeYLdvStEyQrWFNSzESrNcGGB5UND" "TtxzJ499cpvf/Plf5djRw7zng+/h7vuP0TSG3/yd3+TylYskSU5AkqZdnA/UVY0QYm5VgaDb" "HVJXmuANg4V9yOBp65bgA9617JQNn/7SV0nefgeHDh5mca3Hzgw2didUKIKw1HULCIQXpDpC" "EohlmF9WMVpLIq2IE4Vzcx1a7R3OB1wINK1nPGuYTEYIaxBAY+aGsUEqkiS5anQKUgiCC3Pb" "gxCoHNSRoPI50WCNceOJU0maaZTyWDe35IjSAcEoXFvQiyumSUZrAkmsaI2nNY6qdGwWgUQO" "55OBqSFWNcY0+GYHISVpnJFJjfU5WdxFx/PXhauIhMUHg3EBby2mraiahtG0wIYxUKFU4PRz" "Z5kVCUmvYrXTZdUNqXf2COMKU19ivaxoVlNUV9GmGSFW9FWXRGi0ipjNQEYGpSKSTKAjxWrH" "odxltk3CZJxRT6e09QhjSkAR+UzghAyJ0mUbOjKKSdGyih2uBZH1iOWU29f8W1e+7/7/8eNf" "ufJPv/T4S5+08/DRwDe6vV8Lsm6UbXg9AXCjluHLeT1+R7QLbwGs/3Qs1vXAyvPNLrriZR7b" "vwwrJrh5XI6/BbJeu7V/pc+b12LETCFUwDuolWC7hb4R7G44unnCj3/oCD/3+xfmDMQ11UhJ" "EALrHalOsMHjywadDPnNzz7FtI546VJFvuqIvSZWChkEwhqUceQqYupGLN9ziPhoxpKaULYD" "Rm2CDoGlBEZ7NSqCfCBZEDE/dt8i/+Az1bd1lzp7cYvZrGZ7VHyDkam9Llj6qTOXeP7MGQ7t" "20dIIuq9eXxK3RiqpqExLU0IrI9GN315znmePrnBYp6wutYlAorQcmp3nb3JjENpTBwlXN5t" "aXXKXlFg0g7Hjp7grT/+fZQXXyTsTrj/tuPsTCcEazi4b43nz56mah0oxe54TBDghSDJOigh" "cW4OOLTWWMLcBV06vIO2qUhjTRRiOt1FWiewoxm7V85TiwYl5+SBtw1CeqqmpNcZ4pqW2FZ8" "10MPIaXmF375Vzh+1x2IOOWl86dxrkFHHZK4jzXzMOkkzkmiFOfdVTN7T5IE0lQTCQiuZf3s" "KUJbYa2gE0f8yI/+Cfp5h9HWJm07IQuWu9a6NDJht2jZGlsmRUXrHcZ6gg1kvZg46hKcQ+PI" "sxSpJFYEgghEzhLpQKJASs+srDBtjalrvPBEUqGlIlICpSVCaXzwyCDACHxwBGvwAhrrsck+" "SIcoqVFJjJYKZwJWStJUo4THWgumpCst+zoJ69MEH2mMcwTv8cbiGsnY99gtFJ2sYV8/AhKM" "M2QysJKAdND6lDgIjAkkqQYBiYJEWUxTsVeF+eCKc1gnKY2lcWNkpHCDhI3WUZcTsI471CJV" "VVD5htGs4Eq1hWIJXWviboeQJ2S9GOccOuuTZRFKGmZFTV15kixByMAg8Ty8eoV6Jjl3boKz" "JXiHtYGgaoKIpZCJdk4kppXBp7FucJQ0xEoSK40WEQMRjv7Jdz/wt/q9/trvf+6x/9gaU18H" "rG60dl3PZjm+WaN1LROmuLk+6+W6Pq8b8HULYP3RgzB45bHUm/19s+Dob5XBulWvxS+flvy5" "797HvsUEkoxhZ0RReFqp2QoRSSm5zUtmm4EXvUcrgbkuC7AwFhMLqsaSJ9k8uLaBbi555wMD" "fubXn2Aw7HG8kLx1sA+BJBeKrJMQtwZpDK2cUCU5HS35yGNjLo0cf/F7bqNqY57bVmjWSLqb" "DOuWJQLvu22ZgycE63VMTc72zgRjLPsXO2R5BBpGs4oz6xV745Zzl2s2t4qX/Sx2dqfs7E5f" "me0qK85ubOOcoZN1MT5QFhUmcSCgDYGTOzuc3hy/7ONY5/jic5fo65TDSYrrJbRtjfOencLy" "QlxzUcJIBs6O9zBNg3vhJMNze6wdOErbuUA1OT3P8AsBZ2FpsEzj5sL11nicCvT7fZIkxRqL" "wRDHMd43SClI4wRv7VxjIyS+cQTv8JVh6nfIvcUVLV57vGmQKkJEGiKP9w6pJPXumHZrwl33" "3MPnTn2Jqdvj+TPPkeQ5WsdkapFub5mApqkrIh3jpcA5j7OWJMnAeqJEkWUR+xaXmOzsUOYb" "EGucDfzIn/4u3vTBH+LMhT0Gd3hs23D59FlOP/dl8IGlpSH3r3WwZEyKilFpmExronrG4vA4" "t999CFuXVGVJnKYgJCFAoiVZHHDW472lLhqC9XQ7OZVp8cEihYCrAdKRVCgh5gdC7wneI4Mn" "BEPtAqVPyEVKJ4rpZAlKeRoTEE5ilMOaEcrOiJTEu5qeWqdOV9n1nTkTbB2IuWWGJ+C9YlYn" "IGPiyJKqluBr+rLEFCNk/0GQksZ4nBd0shRoiCPJ2rKnqVs29uYAy1iPtQEvNMJrkm7CotAU" "hSKPFaIpqbemTILlufIKO3abnrKkbohvDd7mdNMeWRJobaCrFYNhl6os2d3eQOwqshzSbk2W" "trzvnpbYX+S3PjVjc2KJsoRQIYINAaQoq0ZrIdJJpZSVgiSktFqAl/SsoochDn7pHfcf/kvO" "e/3xLzzxH6wxhm/WVF0PqOCb9VXc4Ee/vAlp8GqF768LkHVL5P7tAVCvdH14mfMbObTfKIbA" "c/N4ghuNxt6aInyNVxQrnr9QYKKMuilZWHR0c4mIYlrn2agDm9uKH1k6zMVTOxj7jWxOJ9O8" "98FVQojxTmPNvO003iv4ud95ie9522H+xn/9DhCCtdWMpapltfDckXd5++FDvPP4nUybHU6a" "y2TxhAuXJ/zUR08io5hK9giiRrHJkxcm5KngLXffwf13LrFvMeZd999O7eCFFy+zuV2y0Ms4" "tj/mrScS3vbAgIcfyBgMNDsTmM7ab+sX7oX1Lc6PdtktZ+g4oTUVrTU0PnB+VPCVU+tUjX3F" "xyrrlt996hQnJyU+T6mkJ4ojRs7xtK7Z0zUtBuMtbT1hevEclx99Ap8tMLznfUyLmKo1zMoZ" "xrQM8z6BgHEe09aEAGmSE4JHaUmsI6w1NE1LXc3bTq1taZsaKTzWWw4vLhErCVmKH+9QVw0i" "y1FZjFAKnUYILRFqzog105L77rqLlzYu00QJS8uHaZyhMRUBSRx3kSpCSoGONHnWRQeFbc3c" "v8o0QCCNUpIgWe31CU1Lng5QIvD+d9/GW977PjZ3K6yHtNtluLbGkfvfxP7D76Iqezz9+Hk+" "+4knOfPseZrdgjVlefv+lNsjSzcK7F9bYv/aPuJEU5YFRVFebZF6FB5cTVMbTF3hrcE7D9ai" "pcK7eeahUgrv3Xw1DgFkmJtshkDT1gQcTTKkEAlCx6RphFKKRGcQYsqZpW0kPmgEEi0SIrFH" "rk8S+Q1CPaKpGqqioCn2cNMKiSZKOniZ4n1CJFL2J4JusOztSoLuoaOIOImQWuCcpSgbiukU" "fGBlUKBFgXGCurU0daCsYdJAkBlCBpJMMsgUyjomjeeSrBj5FpWmWCVpm5p2UtFOSkrrKLyh" "9JZxaZmVhjTPGSwu4bxla32H9Svb7MxGVH7M2+6x/PC7S4ZZg6tbgnOYygpbGdHWrWwaG82K" "WhZTz95MMm0SZnQp4yG2s4aPE7pJ1Hn7Pcf/6zfcc+wDQoqvCdpjvi58v979/Wt/Xyt0v5Hg" "/XozUl7hMjcBWbcYrP/MW4gvx0KJ69p/4mXu/2qB3o0EhLfqNVhVaXjp7Bg7vQJZj0NvvJfR" "+BSX9hxCBApruVLnrJLwtx+6m//bV55iUn/dSf2hIynHuoZnGkfrLa0VeKWQjeAdty+SBc2d" "3ZYffcs+Ql2xnMUcOXSIPE2ReKpyhCpHmE6FUXBw35C/9Wcf4p7DmmdfOsW//91LfO+7DnPf" "iRXWOhYp4JLP+e2XTvNAtMyZ6YBt3+Erjz4FX936g9c17GtWliKsU2zsWKrKfFs/t8dPXuDS" "7g7LvQ6LnS7j2ZQ0y2lt4Nmzl2jNq3ebL+uWZ9Z3uH/tMF5dDfeVgR3b0oljTPB0OjnSOnw9" "Y3L2NLtFS295mayzn7raYVKXyMmIpaV96DhmmGjy0Q4izSDMI3K0UnjnaGuLd5Y4SmnaBukF" "Wkc4U7OYpxwWGbiAKi3NqEDEPZwSEEfoOEEn8wlEHcdUs4btS5s8tX2RLWlpgiFKc0SIKIqa" "5bU1krhDXdcorUnieYtOxxrv57YH09mMPMtIo5hekmOLgnq0Q7WzyR23D3jnd7+XvUIwnpQ4" "zxzYWU9bGoKN6HX2EeuU0d4Gm5sFZ89vEOweH3zvw9zzvvfQ2bdGMDVJEjMQXZSUzIqKuilx" "gDEWZy3WGJT0aC0w1mG8RRgLV/Vc3jq8Aq0UKigCHsTccaKua5o4RnQWSfKMJI2QQhIphRYx" "hoy6LXE2IYgGlRQo2aCjhpxtcjbYKvdhGk0xnVLsbNM6yOIlIpWgtELj6MYta31Q6xOmOxrW" "xnjt0SoQR55xU1JPKxJqElEgaHGlozVdAhHGQ+kD3tRkQmGqhjhx9GKIRWBkHDsULCwvkw0q" "dMcybhompcVNHOPtXWJ6RIMOIHHO0G0jut2U1UP72N3wXDi7zYWNmsERx6DnOHxU8gPvGfH7" "Xyy4NFnAB42xVsQqFjLIkMg8zExDaw15rsg7iibpM4lj0syRNdDz9eA9D973l4qiGT176txX" "rgKmmzm3+xvIWtxN1rBrdVlwcw3y63YNuwWw/nhAFq/QErw+HPPV7mzhFYDXrXqN1VvvGgKa" "Tq/LxsTQGgje4n0gSWLq4Dg/sxxOl/g7b7iPv/PY05S1R0jBd79jgM0tWTewPRUUrWegNUEk" "HNq2xDs1RzoJm/2cvVhTZSlyUvLchfNsbF3i/v3LHBl2uLhwGx99eoO/fGzA2x86zk/9xrP0" "ogl/9vtXObcj+eQnXuTI9x8hlVOOHjvCzz+Tc6Rs+OBhzced4q4ffSu//fuPsTOeM1WjiWU0" "sX9kn5n3gY3tgo3tAtj8X/14o6JgazpmWS/T6yW0ZcG4bmmBcNVXSiuJtQaaKUVVkekhPXLK" "0oKHWTlDRCkqmuu3jq0d5eLeNiE4tEoJzPPm0iRGiJTx3ohYR3Q6PVzT4KxlFc1at8vm5h4i" "0TgHxrQolc+n/rREKEBI4jRj/fkrjHbPs95MGNmGheVFtFJzIOUitIoJUhDFMULM2SAnA0pF" "KBEIMsx9qrzANA2D/Yto5xlkHVaPrPH+D70Rp1eYTRvqokUoRXCepjHMJhVNG/BeErxCiw79" "fpcoHTMZtzzy1AWW7nkDtx2/E4HEX9U45WmCUprJbMZsNqZpPW3bIvFIqQlSkMQRbWvmYDdS" "8+st8xxD35LFCeEqmyXD3OsqVhEu7ZMkCUoIIi2IlKRqDbGOkfmQpghMpxXFzJFEe0TJaJ6n" "GHYR1YjyckRdaryHKNLzVqaKEAoiDd2kIREVZr0m296hXL+AcYcQAnSs8BaMyfClxzbz4G2h" "MogFwc7QOkFZNWcXZxI3qVhZjemrFOUthVM4ndAfwKGDlv0rFiktJ694XtxW7FzZoR41uKOB" "1eUjNAbGdcWgzVnoQraQMawXOHXyMtuPWYYHDIOFmv2r8KE3FHz6acvJrQWMi7C2oZkJUUea" "LI/wxlHVjrzXRUQZLupi44w2nZDKDgfJD3z/O/O/MqvMPzx/+fKzV5kqCdjrWCl3TXfs1RwE" "rtVoyRuALF5h7XtNtwpvAaxvH4j6VsEWN2Gzrgdaf5id6EbxObfqNVi/+Jl1BvmAP/fBjL/6" "bz/Hjy2scmSQcXHsMLXBIzHGcX4mODFY5O88cC//j6dfZFq1/IfPjVl+75CFXorarBmVNbmO" "iNNAUlgm2y2fnM343GNn2S4q1NFDrFp46eIGvSjjyqxkeZByVy/i+IPH+bUvb3N5tI0XmmfW" "LSLrIoOlno7Yt3gn1Wib1k5594Or/KNffZzf/Z/eRefRs3zs8W2+512388Wntzhzfud1tw3q" "1jCqGtLYzkGI0FjXUhQVQdWoJCONU6BFekswFhepuUVEUMxEdNWNvEFJxdZsC5k0xDqiKWuU" "immamrqtiKM+s70xWZoilaYqCrp5TioDtw9WaCy0lzaI+jETO6VWMQu9AUoY5mYEgbw/QKDZ" "fuY0Hk8rHc60jHa2WVxaQccxve4CxgcynaAEGNvOp+8IKC2Rbg4Yu3kCV8OZ42BYHSzRvfsu" "VhY8i2uHGE0DjRVoGWGtp9irqBtHXVkQCusCHkmQiiAkWidEUcp03PDbv/Q7EBRvfu87sMbi" "/fxQJMQcRHWyhHK6ST2pEI1F+QZT1sg0Js4SRKxxzDVYQghUEMRaYZyds1qmxZiGoZb4EJhN" "piT9Ab412NoSpJiHTctAlCb4tkMh+hSTlqYw6EjQ1NvYqaHaq5G1w0xgXAn6C/uJ9Py5kZBK" "wYLO6ewWbF8qyELM7ukz6JChewNC5RBCoWSCTGMQOUgICpAWKVLCpEGZEqqSqqzQTcWq7rCi" "+zAumVUSlXQJSU3lIjye21YDJ5ZbHthuePxcy8l1y7nwIlGek+oOSmhmzZjdPVjoeFSSsTrM" "WT+/y/pjhp0FzfK+lG4n5v0PFSyf3eGx84vsTWr6HRi4RCQyIWiNdRYvFUl3Ea27BObMqdSG" "TAw4Hmcnfug93/VX/+MnPvmPL29snL4GWFm+eYLwWwkt9TdY627U9XndTRbeAlh/9CArvMyO" "w6sEWjcSy7/aduGteg1XWVvuWHZ86ulzPPrSJm++V/Hdd95OZRo2Zy1SZkRa4WSgFhE/2LmN" "4w/G/KWnn+HeJc19C56Xzu/i0YRIsdvUxNOSJJuz+IdWNP/VO9doCsPo9DatXWFfZ5FuNqSV" "llndsk9m/MqpKf/2Iy99w2t75oVt7jmxn7/0Qw/QkDPzHda3t3nb3Xfx77Mef/cXLvLX//z3" "8Pz5n+OljTErSx3WVrt85YlLtMa+braBsZbdoqAbGpqqIY4TlEuQ1lK1NV5KsiTBEmialoDE" "1S2pFBTeM1Q5G1VNOZ2wsLBESGIqK7EEgheUs4K8k5KkCZO9bbK8i45SJqM9et0uBskBoRks" "LnLx5BWErcEHGjy1Tigqy6IQBBlQnZzucIHp5oTJ+XVqQCiNjiK6vT7GGLKsB0ITxzHG1Eih" "EWLe2tNazO0hgscaSyQ13ltiKVhIc7L+kHKz4cDRVc49fZJTV8as3HEfiwcPoqwHG2gnlumo" "IviAEBHWBJRUhBAIQSKilEQndBcOsHtlykd++Td4z4feR5xGtI2hNQ4bPFmWsbA4oJrNqMfb" "1KMSWkcVKnSekcQJbQgEIa6as4I1c5Amr4Iu4TxKyXlIegyJNAgnkWG+/8cxCBHmIctBYEkw" "DCjrkp3zFcWsh21bbF1hnCWLDKa1BLOJFItE8RCpArEIDGnRl1rSKoKOYPPZp0lrT3bsXnSv" "S5znOOZRNiJNAYulnWcwxik6NIh6imsC7bQhciVdYRggeOScpQ6CpJvihWdn0hLrnH7qWBsE" "7lxrOLpU8PwVz1cuCDZ2NljorxCrmCjK2C4CVzZKUl2RVNBNNNrUbG0G1usuC6uOuGN56HjJ" "vqUpT52XzKqUqm7Yw4GSxL0+eXeVNBuihAY8WZyivUN7g3aeB07ceZ+I0v/9L3zkt/4/mzvb" "69w41Flwcw2VeAWg9TUW65UIidcFi6X+3t/7ey97g5/4iZ+4tQq+uhJ/yNt8KwK+V7MThZvs" "uP8F8NCtzfTaqvsOxHzfAxl/9xdPM60cb7xrwPd94GGK9SmF8USdjEG3x2qS8K53PMzBu45z" "Z1nxwR95I+9/Y4+xc4gERNUSE1hKNaurS+RpSr7WYRR7HrjnEHki2Gks/WmGdZa6NSSRJo41" "yjb090l+7Mffyv23r3BkX0K/l7Ezqkkjzfe9/Qh/8598kl63y1tO9OllEXcdjfkPnzrDydOn" "eeCuFYyVdGLLpW3H8WOHMKakKM3rBuEv9bosxQNEEESdHBXFNLMC5wxRlKDTBKkFw6NHWHrb" "2xF7juSpx+ilgZ3dLZIsxfm5vmphOMCLuaFn0ZTIGLq9IZO9HaI4QamIspjS6/bRIlBtr/PW" "fSvEcYf1i1fo+IooCnPDTO1J8kCvl5Cs7CM+cIz+4hobj7zIlWdO8czmZRrhiGINQhAlKVIn" "xN0+VVvhnCXSCVpHiKtrXggBhcCaluDn1gWRECxlMUsHlrnt3kMcOH4I4i6XL13hkx/7KFcu" "r5NpybDXwzpBU8/bhM46qtnk6tKq5yJ1D3G8wH0P3EenH/HhX/kwly5d5K477yHLc8qqpPUN" "jWtoW4OnwZqK3c0S4zxxlhKUIEhFHMcIKfF4BOrqUVGilEZKiTEFKQatEuLuKouLHfJUg5jr" "ouIYpBQoMZ+0rWtP2ViKcc3l9V0mpWFWQdUKKmNwPpBnmqwniYcDdJ6TCsNQlNwu9uDsLrHK" "MG3JC+fPU84mJN0u2cHD6P4QpEJlCVEnI8pTokxjfIWOFEIYnJtRVTXSS3xR4FrJ86dnPHVF" "oTqKJBcIpUAoyloyKVK0sCTKkHe67FsO7O9PmNoeY9cnhJYsG9B4xfbUsT1qmM0aZBDkiaDb" "EbQucHG7pagjel1YXtIcXlOkOmJnJGi8JB0OWF07RK/bI45jOmlCL0/J0oRYS0LrMIXFtoGl" "wfCgwU9eOH3qeb5ZqH6jNezl/hde5Rr6Snrk/ySC91fCS7cYrD/eVuHNzNTETdp7N9pBv9VW" "4S2z0ddw/di7jvDxU5bLuw3dGL7vzQc5LWM+tjPmPcur7E0rjg563OVzOlVJudTQ3plzNG05" "X2c0IXDbiiTaNWzUkm7aJV1aoGMln3/8PP/81HnuPtTnntv6HF3WVOslURoxHs9whaATJ4jW" "cyIM+Nknp3z2kZNYa7nrtlU+8M5l7jkoeer0JnuTio3tXRZWjjBuE+48fpDvetOMSAc+/Pvr" "3H/33exf3WLWVHz28UvcdyzBGsvmbvP6YLGcJ1IJIlK0TiCtIEg5H8OvZngpSIY5HDlESLq4" "7XXq8RbeVRw9uI+TW1e47cBBrApc2LjMoSN3MNnZpix3We4eZHdng16/h1Ipo50tsqyD94Gi" "mLLW7dGPu2xsTxhfvMz9C4qJlUyMoVSORRURGkNQms5gmXZiufDE8+yMt2hcC5q59kjFtGYe" "Ml1WM5RUZFl3bqBZ13OLCOcIziHVnKFobYuSgu3pLo+8sMGBY4usrR1hb29K6Ka88QPv4fyl" "dS5eOkcx2ePUi8/ytu/6EIuDjGpU0ZQxQqdYW6DjGOkToiDoLy6wb/8SX/jyZ1A65unnnudf" "/cuf5L/8Mz/Owv41ppMZRV1S1zV1WWJaQ38Y0XpDVU9J8h7eWdrWE1D4ACKSWO+RUhKJgGsK" "cA1ettgA/SwliwP9TiDPQeu5NYJUCnyYHwC1xsmEsc+Y+Zi6KSBojJMEqxFBoiJHp6eJOwEt" "KoRrSCJIRBfEiKATRjvr9DuS9GhEVT5FUw/Ry+8gyVJEDMsLHbRU1LUBBUU1QuCRcUTWn7cP" "9y4FPv57F+juO8Tyvi4IBzpC6RTvJN4GtqeOslaMlnOOrxV0+5KlpYQPDs/x0u6UM+XtTG1F" "6xXWB8oq0BqFlRkkgcWuZn/ikNZyaRcuxcsMPfT7McePVAwXGzZnt1OKPkGAjjTdLGapn9NJ" "I3AB2UqqyNJKjaElVjGdNF9kPk3obwKyxA3Ws/Ay69O1rcLvmKzCWwDrjwdkvdzO8q2AqVe6" "7S2bhtd4PbHh+MRj6wC8/Y4eD925wN/++c/wO09e4Ed/eI0hihNKs3BghZ3LT7DnHMvLPRJn" "KWeWSxtjVrKSuK7JuzEXd6ec3Sk4PhjQt5oLV0ZcuDzi2LG3shsMJ+5QNGdaZJkxKQ2jiaQ3" "VDQ7lvfdvY/m7iOMZ4anz25T1g0//K4H+Yl/+WUAzm036FzyP//bJ/ietw4ITcOHf/skf+p7" "DrG1fZGVY8vcd0eHU1fgU185hXgdJYcZ63HSk8Y5SmoaN8V5j7dzx3BvLUnWQe8/wtJgkdnO" "GaogkDLQ73RZE/u5uLfJnXfejsozzm9vIqViZXGNclzghCdeWmE2mZF2c5ybT8510phVkVMX" "sDet8Y1jtb/I2I+pQoMxAklC3Okguot0e0vsPXGRS6fOcXpvh16vS2nq+VhXECg9j67xQSBU" "hHHzgGnJXH8lEHMtlvdorSBI2tmMWELAkrsJs+1NdsYT1MKQbGmVt3/o3Vx44SVq23Lm9Gke" "+exHedt7f5DhMGM2KYizFNGE+QKd9NCZ5/CxQ+yNdnn6hScAQ6fb4cKFS/zUP/9JfuBH/hSH" "TpxgUo4pp3uUs4JLl3apakOSxSilmU2mqDwnyjOMM/gAVgq8C7R4pJeY2S5aghcWF0WE4GiM" "IaBI0ugP2pY+zJ3jg9R45RjXgktjQysiRBSj8LSmwoS5J5dSGpMmdGLIVEvwLZEM2LbFFJZ6" "WmOjiIc+9ADLbxyyvbfBKDxF6fcR9HEWehlpqnDeo7QjTTTWRrQW8A4VQ0gsOjQczQwHB4at" "UCFDihQJQUKc9VAuxhQllc94Yd2xOx1z24GGtZUx3a7hgf1bHK3GPFd6zoZDGAyuLdCAERlX" "ZgbrLYsdWBykeFUxmQV2TI+qEAyXHWv7FYeimpNbBqdgkCmG/Zy15QUUHlc3BOPIlhaQRjGb" "laNnXnz20c9+9ZFPhxAS5lorx80tFm62Ft0McF07VXit6P3VCN5vAaxbIOumbBbcXG/1rUxP" "3AJUr6P6lU+fAaDbi/hz37fGx568yK984TTWep5rR/zAvQeJz1RMmlPEa/DRjYpPff5FPvDg" "Cu+8o893HVHsXbaoSJB3IuJZTF3VzJqWhV7MwV7MldJz5MAqv/bRL/DAWw8RhEMLQesd222g" "Y3N6tWL8zAV+67FzbG7tcuex/fyFH3qAJ09VXFifG4DuTlq6/VVQZ/g7/+IJ/vl//x52yi7/" "3597ih/90CJNXbC8sMKDd0jOnNPMitePDmtmWnbNhIGXZFkfGUUkvQ5FUxOQzGzEYOUQSyce" "wM4c4sJLCN1QuJYs6hLriG6Wc2F9g8P7VplIw/mqIs4HzKopq/sOsLu9Q5rlSCmp6oJ+mtMz" "gdXGYfLAS2fP8c4IvHVUQBZJFA6MpnaKznAFaRRXHnsRbyVWaBCeWMfEUURpLFmnT2laIKCj" "BIFAKYlOE1pnmaPegJSKqpiitUZFirasuOt4j4OHFpnNxtT1lMzmZN2MB979VoYrCywtd3nm" "0ad54stf5syLX2Lttjeh1jUq6ZB3h5TVjLapyLOIlcUFPvbJX6eqpnR6Paxt0Spld1rxi7/0" "i7zlzW/iwXseRswCO1cmzIqCpmmIophuPsA5R2VqZCVBCnQ09xATCII1WKHngEUJvAgoqdGx" "JEnUHDgikPKqYaid5x46AmUbMF4SpTltkuPaYj7VF8VAik4FDFJcEmOMJRYVMngy7zBFiUsC" "vYMnyA736b/jdnRaIbuPI+oRibqEE0OSaD/iquu+9zVS1qSxw3tB8AbjHEI54kXBfb1F7lzr" "8dzelOemE8TwNkKeYtKcyCdgJK5pMa7mwmjARim4rehx79GLLA4FvV7Em3ovslw6EnWUs9OI" "ajTBe4mwEef3Atszx1IP8o6jFyWU3jMuwW3HxDKnv+w5stYyqQO9RNNLk/lrxNPsTNg8P+Lc" "xvrG008/++QTTz35lVPnz5wmBAOkwHz64uZtuldrjn19FI/n5vrlV2oj3gJYt0DWNwGrVwO0" "bgGp79B674ND7lyq+cu/ch571VC0zB31nWs8NXmEO/s9ZNvwof1T/sknp/zu01v85Q8c4q++" "c4nL0wLt5oHB/VQwHgk2i5rjvYjvP3GYx9qGXlTx3odvQ6eCfKlPMprS6SiK1nKpcETS8+a7" "h/zgQ5Kf+t0dXjy9wSC/g3/3q4/9wWtM04Rf/tQ6z53coKg8f/0ffoGf/O/exoHhbfwvP/si" "99yecvthy1seWuJN9y/wqS9tvW4+/2ld4rwB7SiLArzB4wkaQtsSbEN0236Wjxxi8ugmeusS" "cR7YGzVsjUakqWZ5eZEaz4uXL7K6sp/9WeCFC+dYXTkAV4FOpCOm0z3i6Go7SnjyLEP0urS0" "DJSgqQKbtsH0BINugggQOkvki4fZvbTHqceexQvFvuEK57fOkud9AoIsSXA+oGWEl9AYQ6Kv" "TrL5OdAIIZAlKc4YFAINeCWRfsQDd5/AYimn27RtRdQO8N7T73c5cu9dTLe3+dCPfj+33Xmc" "F556gXo2RpoGEQSNc1jvUDJw++FVzl84wwsXXiTWEh8gkikuhLn1gZN84fNfYuvCRd5298N0" "Wod0njhO0FJjjCFPInp5zPZ4hpMxSaTx1tE6h5QBVzmCC4BBR46kk6GkJ9Zz/ysCCClQSGQk" "aA3MGktlAl7OBwNQCqEVBEEQCqVzol6KXshwytOEQOQ8PWnoeYvdqVFHjiEefAu925bpDCGS" "DSI4wuQCLS1CrWOcx9VDpEjQ0s2d+L0k8jlJfxHdFlRVQzLoopuM7bJkJbWcGtW0tkTEAywa" "H8eIvsDvOKwVBARtIzi5uQ8rOtwhJ6wuKNIocOfiNotZyUI4zJNPVVTljLJo8CiMz5i1gZUh" "HN6Xk9ASG0MrY4oiRcYx3bUFOiqm9p7xrEW3BYkPXDm5yW/83iee+cJjX/7t7e2tS0ALJMy9" "sCw3FrOLmzBNgZtn5V4Lsr7GWslrWofXs1mvC4f3W07uf7QgK3yLt72ZE/sfxpn9lpP766SW" "F3L+Tx9Y5LEXap6+8HXX88NLkvPVmP/m157m93f2KDsC9ISf+JP7ecORBX7skOSrL27ytz85" "44JbIDIFOgmUzrJTtVycwQcPrfLXPng7D6ylbFWCZ1tF6NSkqkHQEnDUbcvWzHPlwoT75Fwz" "dfzoAhe3ppy9Uv3B69nYnvITP/n7nL+yB8D2XsNf+X9+lntv6/F/+fMP8eI5y299apN/+x8v" "kCeK4SB53WwD21p6cUqiI5SU2LYmThLiJCNKU5aOHeDoO9+P9gn1Zx/FVCPquiLNOxSmZVqW" "IDxZooiSmJObGzjn6XX6JEnOrJiQph1m0wlCa7SOqJoS3TqkEZx88RRx8PTSPlt7I9qZYbw5" "Yjjs0x1kpKsHyPMhLzx3kifOPkftGnKdspQtIoTH2AZrG6QQtM7QWouQ87adcxYlJWkco5XC" "OIdOIlSkURKCrbj/9kWWlpYwdUO7u4WtJpimoJpOaZqG7qDP04+f4/d/8zMMlhd5z/e+h6Wl" "HE2La6bU1YhIOE4cWSKOpnz2q79HJ+/QG+4nyftEcWduuCojgock6vLc6Uv89mc+TmYq7llZ" "JJUgQ8A5g/MWLT39TkyioZlNsdWMTizpxhpNIJKeIz3PIJLEQJZIomi+vs4dIeagVimFUuAJ" "SAVSBnxwc9G/FAQFTglkLycedtFJRBRlCJlgnUC2JbqxOHqkD7+P/l33kQ6WCSJD6S4Li3dx" "eN8bOZCvsKrG7NMv0A3PIttLaFeQSEsaa/IkI+sOyAdDxNVW7vZ0TGEFTikOrMyNUIOrcb6h" "URFh+RDxgcPIfh+RdFFJTlARFybLPLNxJ2eLA0ybHEfOaj/m7ce3edM9mn5PEImGphrh2gJB" "yrjqcXkPxkUgTzSrq4rFRT//zGxGLDoYJ+d5mhb2diqeO3O6+MpTX/nS9vbWzlVglTPXXl3r" "5n793zdydlfXXKeuOUlu7PL+co7ur+Tw/pqpWwDrtQnMXgkoBb49IOxW/TFXHCn+5o/dy5G1" "lH/9iPmDOJyFFA4swsWNC6zvNPzkp09SDWArWeXUxoRf+gu3sWta/sZvXeFH3nof77n/Hvbq" "ZZ58aYfKgglingm3CweiAV9+ZpuTL1zh3oWUk1SsHR4wjDTeNbTeMLGO01uOtyZdvvfOFaT2" "fPKRi9/wWuvGUl8XQbM78fy1f/Qk07Llz/3po+xbzTh7ccZHP73OeNK8brZDbeaTlcY0pGmC" "lmr+E7qzSNM/xMJbPsCdb3obzeefpn/5USqzw9RapnVNbRr2ypKiqjBtw1p/QCLg5NlTdLIB" "ddMwXFikqitaU5OoiMhLzKxgv1MkccLZrUvc1jZ0y5Z6ZkhKz6IfUI882YGD5Iduo58PuXhu" "m82m5MzGOVpvWFrdj/UOrTXGGrRWeO8RgJIKqeScLRNf02BBcJZgPD54lNYMOx3uOHovRaup" "t3eZXN6gnu5RzDaYjLbZ293Dupo3vvsNPPulC/z9v/VP+N1f+AjlrOLA7css9DXLueTISsah" "/RlfffIRPJCmHVSUIFVMUBFZd5lObwWHx/pAFKVsjCZ8/LHH2d68wn0rCwy0x9UFztQ0tsV5" "QxwFIuVQwmHKKa6ucL5F4MgInDhyFwMBppjgvKW1jq9lhAspsB6c92RxINaOgIPg8L7B0eBx" "6DxBD7pEWY5SMQHQQRBhWZE1otohHF5jcNd9dBZWCHLArIgZjyVl5REyJ8sPkGeH6Kf7WUsj" "DkWbLPmTZM0VVLuHsBMy1ZIlEWm/h8g0E9OyvjdhVNfkg4w8UyQJ6DSi9ZbSFNh+H3XsDrjt" "OCyt4qOURmdsNH2e3j7EyeoI280ShcmII89b7xN84M01dx4Z0VNT2ukmrq0pa9jYEezOUiZ1" "CiQkmSbt9cm7A5aGHVYWMhYHHUJnifXRhL3RxqSuq+IGICq65u8bAaqvXVbX/f9moOp6cPVy" "UTmCV544vAWw/jNjssIf8j7fKrC6BbZeR7U0iPnA2xb57TNLfPm5vT+4fm0Y0RMFX/zqKQCe" "vVDw1J7ixPGjvDRO+PATu/yVj+yxVcADBzPS1T75Uock6hMCCCUwpmFaCS6fKfnUMzP+/Ltu" "4+KVbf7x759ntqBZ66fEMsYJT2kNoxbOX3L8H99wO+MpPPrc9qt6D2Vl+KlffI5f/e3zvO+t" "B7n79oX5zvo62vNcCLTBEWtBU4wRBMrdMdVsSvfoCU586EOYSzXlxz5HY/dohJt7k4V5RE2n" "n5LkKc55PI59i6t0e4vUbYsSgVQnmKYgTWK8acmzjEgFmlnJyFYUbU3fS1SANtOUSuNEQlGA" "6g8ZLKwga8N4Z5fQGopixmhymSiCTtQlljlKRhRliXN+3j5zARHAu4B1lrYxhBCItKQ1Fbap" "kW3NfQeW6HcGWAObZ86ye+4CTTHFNGPK2RZVOaWqK1aOLHH/G95IjOazv/MIX/gPH6XYvcQD" "Dx3jjgND9g8lTz77JOfWN5BC4oIj0jmRzsmyPlk2xDlBJ10mjrpINEIkjCclX3j2FE8+/zyH" "c8Vdyx1UcDStoW1bnGlIYkWkQHqLsC2RaxlqTypjcB0Or66gTYmpZ4BHSoEQIK92jLQCJRzO" "GhQOLSzeGYw3ICBKYnQUYyUIKZFSY0NAENGNOoSoS37izeQL/fm0pIxp2pidHcHGpmQyibAu" "Qag+UvZJ4gX6+YD9vYTDnQlr4iy5vwLNZayvSHSKDIo6QGE8hbUIqUjTFJV3yYcLpJ0Mj6e0" "DTaOSRf2IZbWEEv78FmPyieM6pRT4yWeGC9xshqwbSI8jrtv7/H+tyvecr8jjz1VNaIYbTHe" "2aNoNJXrM9nLKesUnwxRWUpvkDIYxCSZIulmHLr7LqbTWVEUpb0OWF3LVEUvc7qewbrZ6VsB" "XOK1CqZuVLc0WP/pwZb4Q97vD7tT3bJpeA3Xle2Kv/UvnmL/6tI3XP/G4x3KVvCxJ0fzjefh" "lz97ie998DB/7oMn+DP/8HM0rWO5H3NwTbO9/jSuGLMyXJgH1cqItqzYKQz+nOPP3rHKvm7E" "v3v2Cqk3zKJAPbP0iNgyFi8cY9lwZip4806X7z/c5d9s7H1L72V3z/BbHz/LkQPD1+W2GJcl" "dw3XmBlDayydJEFEOcc/8CFWVm9j/FP/kby4wLY1FHicCNSmwVCjsy5Sg/GesjZMfUAnOW1b" "k+UR48kuvbxDWc7QUcSsLhmUno7U7ExbEBEKzSRWnCoce8Ej6padyw3lZ5/l4IPvZGd6jq3T" "L5IrTZoofGiZTLYZ9LrsTKboKGHcNgg51yB57+bC8CAI/uuHHykjlLLoNKarBYtZTlXMkEYy" "ubJBUxVEYQh4QvAEPMYHdBLR6QmWIrjn8GHOnD/PlUefZPfsRUSSMqp3+crJp1E6hSDpdFeI" "oh5BepIkpWkqkryDbS04hw8VSiUEqYlFzNnz65R7W9x12xFOLK1wvoCxtbRViYo6XxfomBol" "HIuxo5suoeIOeafPibUltuuSpqnIc40x81gl7z3Be5QIV9uHhgiDDAYRHFKBijVSCRIUAn91" "+lDSlYJU5bTxkHztDrIsIo4SZrMZWZYig8e1kmkVqOuYLDLEkSdSAa1SdNRFxw1pvMdyu82k" "vMzpWcKF7Smbl3apdgt6K8uI2GFci1DQZhlRd0BsNMpIIuYu+t63qDSeC/LbBttYvIXRLGZU" "Rey0OSuZ4ki6yWrUkGZ93vzG+bTqo8+UjMdTvGkpaFHtMmJ5SJR28ZmipaZtY2yIkLIG0VKt" "XyQE01no9/Zv7Y4u+RDqq5tAv0Kb7kZSF/8qSYLrrRpupr16XZiP3gJYf3wtQPFteIxb9R1Q" "n/zyFSK98Q3XHT24wtkKzu3Wf3Ddb3/5HH8zhr/8A8dY6GeMxyX/rz99B4fyit85OWaxv8S9" "LlCZhLKZD+PUdcvUR5gLgVHjGdWBu4+s8InnT/KmhYR7ov24zV32vKM1np0QeHo98L79y3x0" "sMH6uP2W3sustDx7cvt1uR2sa1iQkKQx0lri1nLsQz/KofvfTvszv8XK9Ek2xYg6NHgZqG2N" "1JKYCCRMywkBiZQK05SYxhOnKYRArMTcLDNYRFBs72zwUNwhWEXNvIXmGtgaj1kZ9ummCZV0" "XCwu89yjL5L/m5/mxANvohlvESKBTjRaK6xrqZ2isS1Z3sNJTWUagpCIAFKIeUSNAIEgjjQ4" "i7eOXMdYM0US422gnY1pmhZSjXcBqTVRFM0n4rzDB9AisBYP2dzc5NCJ47zpzW/mf/nZ/x8T" "b8g7KULFc4CnEkKQODxKaJq2QQiBD2K+0koJ6mpeYhjR1DOGvSGXtne4vP4k73njcW5fO8a2" "hK1myqTyhCgm1hEKRyxbMjT79p+ge2CNdDEjHuSsJoqqaijqirYxKK2vMlLzjE8lAkp5QmgJ" "tAjvUEohgrsKrCwEUMHQ91MOxhVJU8Hq+7E2oITCtC04R5YkJEriTIRzDt/WtK2jbROkiMgS" "SGOLEBEqHiB1h4V4gTd3N7gz22CpTnimqVhc7pN2oKp3UUmGiiOUTOnmXXwAFebM5qQoqYyh" "NQEf5j5tXkBRt4DAh4jdnS4bkeFQvsGKaoltYGXQcPtyYN1obGGw25fAlBjbMGlaepWlWq0Y" "Dw+j6CFo0DhENeXNb7137Z7bjvzIhXPnzz99+uyLL5y9dLqomvHVNSh6he6Lv+ZcX3P52tO1" "bNW14c/XC94FNxe4v2b9sW4BrNc30LpV3yH1Ne3V1+rDn99Ey4BzXz9uhBD4hc+c5cunt9ke" "Vfz5tw54z6GWL750hZ/45fO86fCM//uDx+nvemyAKOvRzlqEh7qC8xdG9BcUR2LYmlo+csnw" "5iOOY26BYrJL7TzWWbYaw1I14L+97zb+p0dO0pr/POIshQx0ZQBjMVKRKcm+M5eR/+ZnWQyX" "2KZgRo1XDicC1pt5CPBwMNc/RRpjPVXbInVCFDWYtsK6mjSOGPYH7O0aPvv44zSm4S33fBet" "80xENQ8kbg39vMsYz+50jBgO6d7zQe5+/7voy13Wn/409w67XFGSONYQAsZYxuUueX+Zsp3n" "DaZJivGOJM7xwdH6FiklaZrNQ5IlhOCpjeFAklLuTUmGHcLWCGdaZJagpUDHEUmeE2UxQicE" "oJk1XCnGjCNHT0o+/Du/xvp0k8WlJUSwBO/QWZ8o7RI8iDBnj6I4Ik0SrGnxOqKczdAqJziL" "jrs01ZjGBJyRHNq3TDcNbJ55huHiIvvWFrlSR6wXltCWJFLQVzG7eyXPPfkEbxsu0T28RNRJ" "kbGiP+hhjGEym1JXJcVsRt20NNaBt8jg0Qo0gdY7fJDIENDeo4WgsSVRJBgqWKDFqf3EC8ew" "1rK5sYcQASUlWguEinFq7o7vY41rBaataWpD0zgkDi0DSQKRVijRA6XJe13e9bYLHOo8yZlL" "NS4a0BEdrJ2iXAkCOnGGItA6i0KwKDqMZhXBNpSNx3mLlAIXPMEGbAkSyWbZZ3dP0bUJi2GT" "xERkiWG5rymNJwRFEhQDPFm5S3/U0FEjlJ3SZsu0OsfLFJd2Cd0eohLZXcfvuOvO224/sb61" "ceXzTzz91SdPnnvWOl9egyFuxkZde34jgHXt9eoGj3Gz9uDNTLtfUyzWLYB1C2jdqtdgnbo4" "vun/zlyaAfCuu3s8Nsr4Hz56mUlp2dib4Fdzsq2ajlQ4C72lIdX2iNGs5Mg+xRvv7PL85h4/" "++kRo9Ix+f43wvOGuBphhAc/b3O9sDvlnsUBf+ONx/mHj52mbd13/Gc+qxqiNGJQCFLt5z+r" "LzyOPrDEldQz8y0yiQnOUZUlLhhU5Ml7C8xmDq0jamvYLWp2ij1AMa4anjt5km6Wsjhc4PTF" "i8zKcv58oz2G3TUmo22K2S7D7gpJUFya7bGVpswMhDaQDY9x4kM/whU5YOGR32E8u0yaRHNA" "laZE1qOkoGpKhI7QSUokYzyW1tREUTw3IDUNSomrjuYCGSSLUYcozaG0uNLijfwDJyIlNUGA" "ERB0TLCevdGUy36CVILnzj/HerFL1k1Qav4DQChFFEVIHdE0LbHM5623JMVai1YxlalIshwl" "DdPJiCTrk8YZuYKuFLz3rcc5dGCNsxfX2d7dxpYVK52cpVRSloG6bqFsqQvFC+vnGE8+wttn" "7+au97+FNEkIeIg0i8M+ZaypbUs9LSmKmqadT1WGYAi2wQdHCILYWoLXeDnPaox9YDGJEWFA" "cuS7iPMeUimapiXSAhVLtFRoLQlRjLOOoMFriY4ydNTQNhVtI5lWgr09h8ARaUknr0i0QMj9" "LB3dw5iLXLjSgkrJVE0jNqnCKtYvzuOsdITzgTYEBp2YIAKN8zSmJjQWIcXcZNUGIqkwHmqX" "Mm6G7FpJVOwhyxFKCVwmiVSG0IEoEmRa0xGBXmnRs1NkvUuQdzGhSxUSmiCYhobLsxmCSC4v" "rBz8nre9dd+RfWvHPvvk05/bHk0u3wAY+Ruc+2uYrBuxWIpvNBoNN2CvXi4F5TXZKrwFsG4B" "rVv1Oq2/+xubbE3Xqep5G+9PvecEJmoxsiSPc2rjCW1D2slpdgq2zxt6SvHRKwWj0vHWE0tE" "Q8GX1x/nvYO7eGY8YuIVzjkmZcWL1vDgwS5/87vv5+//3rPYxnxHf55WWPQgxs8MsYVsmFI7" "w7oZ0RDQaYxWAWtbbKgZLKd0uinWtmRZRlUXpGmXc1vnePzFkyilKesaYyyj6ZSLm9/oC9bt" "LLA6XGW/1qyP1lEqsL475ZKrueJqqnKKLHaY/HRA9f9bDj/8XVz8xJdZH72AG01ZXFpGOU/j" "HJOdKwQpifL06qHE4lxAKoGOFBqJ1wGBxNqGSMCSE6zqDrPRBLm7iVIOEyRSCqqqpltXeG8J" "kSZKEspRxcXT52isBz+fwhsMFpCmwpgapWM63SFS6znDE2mcM2idYtoWY1qMUAgkKlJIoYnb" "Eh0CHd3jTccOUYw2OXHHnYS4x4HsCAfSHpPRDluXLzHavIAsxiQhAavJFxdoQsSlK5v84s//" "Ag+fP8f3/vgPs3JgmbKssNZgfCDv9glS4MxFJnsjmlmFawvC1bbg3BTUEFqFt4aechxJGg6G" "CSY6xu0PvYHpaEKcKbzzyEgTa4mSAq3neEHKgHf6ajC1IIoikiSjtY60a2irhmI0ZWN7k7pq" "SeIZiz3BINrHyoJDb2+xd2VGHQvE/ksMFyIMKUrdSaoDPlgIAR88caLIXEzVKoyr0FJhsVR1" "hVLxXDkuNF5oKhfTii46CghXUokGV09ZGAyJnEAHjSuhaEr62hPLkjAd44ImjrokKqJjBTKS" "XDGSiRHEOtX3Hjj2wLLKD3z+pec+/uzFS099CwDra0zVK7UKxTVg62ZTha95qcwtgPXaBVrc" "Aly36uXq/NbX9VnvvHvIe++OePz5c6gYhjajUWDrkiTt0XhIRES2K3j7cIHPZCP+4rtWGW1d" "oD8Q7BMC1V/gs5u7JJ0MYSSz1vLsest9Rwb8tQ/ewz/+2NO47+B2YdFafBahlnLqUUHwNbU0" "NMoTdwcUVYlwlqA8vX7MwmKfKIoZjcfUTU1RO8ajbZ49c45ZWb8KQCfJ85S97TGVbfji+gZB" "wLqb0VpwMlAGjbr4GDtPPsbRH/5+9IGH0BefwtkZ4/EMbQLGOoJXyDShmBZESYbQkjzvUDYV" "5XSG1poojQnOkOoYV7fcEffoRT0uX7pItHMBkwpsEpMkGUrG+LbCuIosTujkOeeefpFyOsHY" "FqssIgR0cGRpThCeOO7ghIcgECGQp118mEf1BA9S6KueVBpnHQJFrzvA1Q0LqeLokaNUK0vo" "AJubu2yPW3r7j5L2DnLo7mMcudeyfvI5zr1whki2ZNLT6eaMJhXeB770iS/z0snTfPePfYg3" "vfPtaKUp6ymzpkUpzfLaftp2xoWzGxTbNcIYtAUlAWNAS7QODKThaFTjxiX2QJ+zL53mjrtu" "Q0aSWVEQRxKtJEqJuW4LkFcHC6x06EgjCFglQWniJCVOs3kmYqzY3U0Yba9z6cJpFtxlDsSK" "frXEQpD4ssPuSwVhbRe3+ihTW1D5+0FI4ijGBcg8TKQlCD/3OGMO/KzxOGPQOsYpUFFGbS1Z" "mhJ1O0TjEdoIZu2UqjaYEFH7jLYMdLTg0MKA2dRg2pZBrokNCGqUyhiEilgHxl7SGEvZGAa6" "s/Tddz70JxayLP/cSye/yNzVXVwDoPQ1gEpfA7gcN24VXstmwTd7Q16vwboWjPEyoOuPjcW6" "BbBeX4DrFti6Vd9U73n4AH/9u/cx2dvmkYs7LGY9/vxtKzz74h5bCDIvWF1e5B0Pn2DYG3L3" "k5/h4J9MWXK7bOxV7D+4wqgjWSo7rI2vME06RJ0BxWiCqeHMpYJ33ruMff/D/JOPP46335kg" "a2s05cylC5xYPoivJaOiQHVzWgK2nuARNG3JcKVL3h1inKVpmrlIWkpK1/DIi+fYHU9f1fOd" "3LvM4W5//sXWmgsykPqAMIJEBNI0ZbNsCCqgdy9Q1xUL997O9Ks9dv02ZVORRzH7FpZYGe5n" "tzZcGm/T2BaJxoeAdx5CQEhJcPOIJEJAucBwOGRjVjGdTFlF44THqZjpyJINIhCBEGuiJMc5" "x8lHnqNuSmQk2N3boZdlJFlG6wxJnIEQJEmOD5Io0kRxhLWOSGuMsWgV4UNAq7nw3DTNXBwe" "YGtW8dKVK7z77W9kcVEhd2fsPnOK3/v1X8fpjOHSEitrqzz05ndy4NiD7F5a58rpFynLLeKs" "T2grhDYUO1N+6V/+DBdffIk3ve99LK2tUm1t0zQlIXg6ywscPdJhur1H0TbgHNILEhEhrKUr" "G47lNVERmMoDJPk+BJ6yalBWECs1z3IM86EBAogwxwEhXP1eKIEUklgLVCQISCLniOOI/tKA" "/YfWKMZrjM532XzKcOriZbSvmLYlq2LA8iRCnMvI85LhwuOMTcWWfRhETBrFtDaglCBJNKGS" "eG9xGOJkPlygog5BaIwDobvIGGSuiPNtoklFHDd4CXVrWHc1joip0uzMHN6BEin9TLOYCXq6" "ZV8nAJoIw0LwOBRNqpl5z2hG54ceevC7F5ajzjMX9p4HOSsbM94bjSatMeEmzNX1bUJ3DbDy" "1zFYN2oT3ghsvRKY+mMBWbcA1uub3boFum4Vf/rdR0jMFV4cF3zs+Zq6nvI9+5e5b99+Stew" "duAY3e2adOpp9QRizfuPHmCqco4dk/z6sxMO9xOePfMS+3oSX02ooojesIMtDa1tOX+h4r2H" "97P1htv5uUdOfkd+jtbDhe1tji4vYhOYFS2RjyGWFGWBiiJ6Sxk6U3OHdBswpsb4wKRqOLu5" "xUvnLrzq55uEiouTLRARvXQeMm3a+WRiU7csdDM8gWo2w22fw0x3GC4PGHYHRG6ZjXpG6yx7" "kxFKRkRRzpvuvJfnL55nXE+pypKmKel2esymBZ1+B2MtTTPjznTIzEme37zIUrHDkeVlXqj3" "aIOAKEIoC1LhBksk3QV2z25y6fkXuTTZpLGW8WRMCIKl5RVc7TDBI7UiOHcVWLVIqdFxxNxR" "Xf8B02PsXAzf1BWhbWnLkjTVXLx8gcjcRtI/QS8Z8NDCAdZ3Cow0dDo55196nunGSd7wtvew" "cvAAtg5MSnCMERKqxiFF4IE77uWRzzzB//yvPsxf/As/zge//z2U3jCbTTDNmCSBo2sR3lXs" "TgTCCYRpibznYN+zaGqmVY44ch9x1kPHmtlkRt6J0Z2YeZbjfPkXV5d8AWg9N3YlBJTSCKmJ" "rulsOW+xbU0zK5js7RDbmOMPvg8Tw6Q4xwtnvsypzQ12soAq9ljdu53l3grDdJfUPMGuPczU" "rhJpRZ5qhO5TK0M22yCOWiY6ZYc+UnXRKqI1HucCtW1JhEQM+ri0i9/eo9uJ2ZdptoCdyhHL" "nEnZ4oVEAhOreXEi6GlJP1J0sogDec1CZOlLiZABFWvyboKZFfFf+t7ve9/qO973TmPZ+53f" "//gj/+xf/7vP7Y5GhhtrrtR1YEpdx2DJ6/6+tk14bXzOt6LF+mOpWwDrOxN03ar/jOqZUzu8" "8R09fu4j22yP5u7pV7oZD9y+xNrlK0TjEU1cs3nlNEU5Ie5n+PGIr/qURy/t8Z6jHfL2CgsL" "FUtLa9ytDvHI6SvMEgV5TjsTGA/nTm/wwf4C5o3H+Q+Pnwb/nbfrff7UBU4c2s+w20c0GhFb" "OsMBopDEWUQUq7m+xXsCAhckm+OC9XHBY8+fwbpXPwzQek+jBTLOiIsYH6CuWgQCoSTjnT2G" "K4vYSuCaMRkFcdZn0OkRZglLHcU0GISUbI1HBFWwW8wIWiOcpG5Ksm6Hqq7JezlSzkOdcx2R" "y5TWWsZVxXEyOloidTK3mdAaoT1OxmTdJWIV8+Lnn2Zrd4PSGqpqxqC/jMXihSTvLjCtCgQK" "pROE1CQ6RqoYazxRLEHM42kipZBS4lpHrDVNWYF1NFXD6rKmI0u2z52iUjn5/oO89bu/i9l4" "xF0P3M72hQfYPPMSxXgLRUZ/JaO/06dqK5yz9DtDIiVJVMrjZ8+yvbPL3/8H/4TPf/ER/sJf" "/DHSpKUarVPuTnDBsn8xJRGWaVmibODoMLBPCWamR3ToTUS9FeIkRUmFUl9DUyCEQCmBVgIl" "5+t9CAGtr5EIiav+mQLa2rC7Owe8vql5+uOf4cIzT3Pn8iEeeP97CKs9Om2PvJfx/2fvv6Mt" "O8/zTvD3hZ1OvLlyFQpAIRMAAQaQFCGKiqSsZLvdltsay7bGs8Y97m67xz3L09Nt2e622yOH" "kd2yLVuyHKRWaGUxiGKEQBIkQRK5CoXKVTenk3f8wvxxClSxWFUAGACQ3L9ae517zj337nP2" "vnX3c5/3/Z53e/cU1k2obI9BKcl7TfbNB3TUiG7wNJvmdnKznyTQSCURjRi9uUs8HDE332W2" "6Vj3mkJ0UELiLFR5xe6kQDYg2XcYMS4p0l063SbHopAjHcVIRGxe1ioGy04lKX3ChhVczDU6" "Czk/CZmXYw6GnvlYE0UeW2S41NA7tU3r5nX10adOnP+V3/jN5y+Lq+Dy9ela4aL6CvdKXeVw" "XRnTcHVgtrvCWLiWiyVewox4VX9p1QKrpuabmNlOyA9/V8QvfmKXz53KXqxQMHdwD6dJ+YI/" "wVvlIof3zeN9g7TXY3l8kcOd/Yw2+3zgM2d5oL2PVrPik72Stx5IOTCruc/M89zGJjZQjAOJ" "UBLnNcPNlD9/5DDRg5JffvzUt9zxXBvlPLfe57vfsIewOaHVbiCUZGa+Q5anIKbuEgKUDrmw" "1ed9jz1FVpakWfaK9tXL06mzMhnihcILgZcapSAQUOYZk9GEpL2AcY6qMkTNgLZWVGgq4VBB" "hJOSwjl6ecrIOxZnD5MVmxAFKB0QCUEjaVIUGd1OGzkpkGVO2I4IhhMOB7NgPUGgUaFC6WkV" "pxIxQdKh2M3YOHWByaRPORmSJE1aQUDlCvKyRCtPEjaoTAV+GhWg9HTfXlSUVYm63KNU2Wpa" "jpOSIAohibHFBGs8S0ttvBTIanoco1bMXUcPcOH0Gmlueft73sWk9wCTwYjnv3iG1XNbBKGk" "0WhMvz+evbMdHjt9nNOrK186zp/85Ge4eO4if+m/fC+H9sXYrEBYcOWYblPSCg3SVxyebTC/" "583kfgEftnB4lFZIpRAClJQoIYkiTRAotJ6WAsVl4YUQCCQOj6kMWZqztT5ge3OE0xFxM2DP" "/BIPfN87ueeOW4ifuER49hJWLyEWGsx29hFHAuO20WqJnUnJmbVdsG3cbEAngsXgNLJVcsou" "kU4qFAUuL9hZXkYryU2x5VAr5azbw8VyH4X1CFchy5JhVuF9SdSZQSrBuHIo6ZntKDrtkFYc" "UU0ylAxpZYIVEzGsApzwOCfZLQN6rsPF3LNUGA6Ehr1ekHQbpHnKr//Kf1j+Dx/93B+Xxg75" "k9wrew0xdaWgutrhujrJ/VqrCa/Ve3W14VAHjdbU1HxtSAn/399c5uOPD7kc181sO2L/TMhn" "v3iGi6GB0Qp77trLYHWVuJVQlB1+5osr/MCxhH//Iwv4csKlTPG/Pzbh4/N9/s2fXcK1G3R2" "QyYOmnGEFRInIgQGMbT8vfseZNRL+b3TK99yx3Sj12N30qcwOX7iUFrSas6glMCUBV5qJmnJ" "pd0tPv7k8+wMBl/VftIyp7IVRVFSWtBhgNAaR0XUbiFCjbMGLUB3ZogXF0msoic8SgiaTuJN" "hcHRTGK8ELiwwWDYRwUS5z1ZNqbd7pJlE7TS5FXBrPN0ujNc2M0o0pxuJ2Gzl7NcjOkcnmF+" "NiCIJGJ2D0l7iUuPneLMhXPEcUKRpzSaTbqdJsYErK8vM7NwEC6nnysE3vnLI4NKrLEgJEpJ" "PB7nPVVVTq+c1uIqQ7vVJjK7HNjbJM0m5PkE291DWeZ477np9oM89/nn+T9+/v1ECRzY16QR" "hTTDgG67iXQeV+UkYch2NuAjT37uK471xZVV/tHP/hI/8J0P8K4330HYDMnSPs6mxIEkMAXd" "maMcuvONbG4XjEc5Tk5XVOLBO49SEEeKKFREYYDS0967F10tYxxlWTEZ9kmzCZ3uLO25FpnR" "GBlMXa8Qbrr3Jsqb9jLUAlYvUW4tY6oZREsSNBcQYQtrh0SNNaysOL81oSxi5rox880xs+EF" "7ur0OF4lLPeHxEJidUCeFmQjwUxUcH/LcSQesl202ehJdn3AsAe2TGmWhm7YIi0yeuvb9MYB" "nQVHHHeYne1QZBndrGBS5WReomSItZ7STEN0nYO1UpM6z0gE7EsgNGOWgrk933X3XW964sKl" "L+4MBj3r/ZXiSvOVze1XO1fXElpXOlnwlasKrxZUr6vYhlpg1dR8E7PTL/n45748bf3BW2ZZ" "8Ct0mimbRjMod3lgskbn6CJnnj3B3cduZ9VM+I9Pb/N/vSsilpbcJPzVtyV87IxmlBSMRxM2" "bZ9b2zfRH1tSFYKruPeNb6S7PmG/WuRf/MC99D6Y8sdneq+nPxq/ZgZlQW+SYk01bTYnJE1H" "OBlijGNcjnh2eYPPnzjHYJJ/1fuxSqBaITZLsQ58WYAQlFmJkR4dacIwphKO6NARok4HsZMi" "ohgvoBFFlIXFakdWZgRRg8wbnDF4KUmzMSoMmUxGKCUJQo2oHHNW4b1mdZjSDBsURcnuaEzp" "LcWgpNFsoRe6NA/eSqRbHD9xkuXRNnOtNs2ojZKebqPB9k6GUg3GoyFJsw1IhHIEgUZ4ifCO" "IAixziKURytFUVZ4B6Upp0nzgSAOQ25t76PR6lLlFcVgAxuGTHa3CBstWrMz3PngHewu9/jg" "r/4GN+1b4txOn16RsW/pALONFt1QsTne4Zc//tEv5YxdjbGW933scZ45eZ4fePhebtrXxRQh" "2aiH64/QJqDdbbOzW6C0QgcaKSVCQBRpGo2QRisiTiKCKEBKhffgrKUqS7y3KOXozrVZjOeI" "woiiNIRJysbmmEYkmJ+NAYlqJiRvuYdqtYsY9ylPruAu7JLc2kEc3IuPFylKuHnPLpIJzy9P" "2Nmp6M0q9s4bus0eb1wY0swsG97i4wYmM+xsZBgb0XUl7cgzHxXctginghZnrSLre1JnCWXE" "XGeOSMJwd53JpEQEfeI4YmZ2ljgJWTAG49zUwbIeZwTWOlASX0HPaQYoVqqCvUpxOGrH777p" "Dd8RykR/9OQzjxhj7XRck7VXuVdXO1dXCqwXn3dlafDK+1f2ZN2oH8vzOmh4rwVWTc23GO+5" "DRKxyudXcn7rsXX2z0kevnWd77j9CK27D3Kmt8I7bj/I45sVn1zP+JH7mrS8ZrIbctv+AO/H" "9N2EbbXFPckh7u7uY3swYGnfIgeO3YRVZzmTnWbYVfyr/+JuHl25yO54QBAlnDqfE0cg2zBK" "HB/7nOH8hfSb6vhd2upzYr2PEJ4o8Cy0u0TCEkaCSzsjnjp7lvMb2/ivsQctMxVpXuCMQ0lN" "nk0wWYGrDDpUFLYApWguhMRLB2nEDUw2wDmLChRZVSC0pNtqMBjuMBnnSBUTNlv0x9Nohjhu" "UJmSMAyxpkSnY/bqPeSTCflgkz2Xc64q4ZhrdMhHkA88S2+7jfbSYWajmElRUiHIq5zbj97B" "8QtfRPgF4jCh1RH0+tvEjTaVLYl0gFSKqswJ4gAhwRmLQE8dLgEi0NiqQCmNDpuU+YSF1hJO" "dil21hlsrBEmAb7XRkYJaZmzdHA/tz94G2ufvRVfGT71zCc4s7kJQBzFJGHIKJ28rB64Cytb" "/PyvfpS7ji7yjrsOcWs3ICoT8mcvMnnDOs1Wmzx3qEAThwHNZkCnE9FqR8RJSBAG02HWVYWz" "BiEsSawJo8blcqKcqgHn8IFncb5Bq6EJlCdJ4qnFYgxBs4XbfwjyeYJGmxMf+UPyTx/nTT/w" "IPH8QZzqIoXilj2SUGzw9AXHuQuC7V7A3iXFYjtjKbT4ecFaX5EPCzY2UzY2GyStIYuHFlhY" "TGg3Em5u5ewPBJthxnGj2JoI4qpkrtUhlJaGkljn2JgM6a2N2T+3h5lmCzeckMSCXa3ZVYph" "AQKP9+C9QCrJxAo2RYPIlswWZfDGxcNvK0zqPnX29CcEQe4DH1WmoqqM8/ire66ubnjXvPQI" "natT3V+XKe61wKqp+Rbj5v0z3HdTl+dWdvjtTw4Bz2rfcToruWfnPAtzi6yIXXpmhx+8VfHc" "puI/nbIsb4x4w6EGP3gsId3ewGWG2cP7eHZwju/a0+aW+VmSqM3ypU9SmDGtA01+98wajxxf" "4133dXjPm1vc1MlR91U04hbRrUcZz0744b9xgvMXvrmOYZpXfOLpF3DOEgcBrSTGGEsYhvTH" "GXmRf132M8oLKsBbMK5CCAkIwlaLoshozrZBSlQcT2fUSU3v3Dpl2SdzFUYIqqoitJI7jxzi" "qQsX6OdDWmFElo9pdWexVYlWEukdlSvxeU4zEaSupBjv0jUhKtC0u21WJxnWR2xcgsUqYd/s" "LHY0YLS2QjbcwUlFMgcH5vezvLHFkf0HWeltIbSmqAoQIIUiyyaEQYRSCnl5VVpVVgRBiNaa" "sqguOz8GZICrKsosw4xSsuVNxoOcxsIQutvko1lEMyatDO19C8x39nLi+BM8fNdd6Cji5KVL" "5EX+VZ2T4+e2OH5ui9uWOrxrzxzvummRydnnCY7dh6IgVIpOW9Od0bSaklBPy5pVPkFrhZKS" "qKEJgvjygG2PtRYv3HShghDTOYgaoijEO4exHiHA2ml+GTpEJopoX8ix9/4wv/YL/5ZnfuXD" "vOu7jnHszjtRQYSVcxzd55ltDXj+bMXzZ3fYvqRZnPfsbUIjEuzZ51mrSqISBBYzKDkzWmF1" "tsPsfJs9801iodjXdnSPWHZGKZO+ZDyKaOqEkBxhK27pdnA4nMjpNiJipxiOxuwNE1ZCxbac" "ZppFWlE6kMKAdljvGZYVGMfRRqx/7PY7vuO2uw93Xtgenert9NfOnru4sjsYGaZlw6tXE17d" "h3W9ZPirG94tXzkUGl5ew3stsGpqal4+USj4W//lA+w/IHnkBcF273LpznkeOZnx7lu34NwW" "eRLwoWXPdy8Y3nF7gFwtePT5ktNbAQ+9W6JjwS375vgnnxiyv1Vw75F1qtaYLB3RPHSItRd6" "zG6v8n+/bz/7Ggv8kz88y89+UHPPTYp33delG89wsL/NrccWWN/45jyWpjJTsWVL0vzFEuzk" "67qP0hrGZYYUkigOQDbY6o2J2w2iZkLpSrQIcPEczT0HMKWl2lhHaYtXkirN8FqgkhBR5Nw8" "O8fp/pDeYAuJAOcRgLUlQjhkkdO0it4oZRQ16aUFSTOiEpJzO7sUKiaKIlZX+ox+61ME0TyL" "Bw9RjraJFHTaLda2N3jDHXfzyJOPMdddpBW32B31KcqcOGngvcEZj5MSV2p8UALT14FzCKEI" "g4BQSWyWYdKUQASYLKVY2yS9sIZLPDazUBmkBIcjLw2NbgcTRvSqjLgV8MChPRyc7/DsxRU2" "dvtf9Xl4YXPIC5tDHt0c8D/feRP72hdpByFxM6AZGyJZ4gyYoiKKukRJQBhGSBVcXi0o8M5S" "lRXGGFAarTTOO6x9cbNTgVUZjK3IsoqyqGg0psOdvXXMzC3xA3/2J/n3P/eL/OIvPc1dd5/n" "+77vIPsOHMOJLt1E8sBNGxxpWL7w/ITjT6esx5pDSyFJpFnc1yZQE+TIEgcOHwdslRnnTvVY" "uZCwZ7ZLqx0x04QjMx7ZDun3SkYDQVVYAucJJgUzMw10KFF4TKDB5lBCJ0zIdAVKUFlH5i1R" "EGAcDPNq2heoQ5LSs392Vr33b/23bzTN5j2//zvve/x/++f/Ytt7N+baGVgviqory4hXB49e" "a3zO1SVCeOnQ0VfV4aoFVk3NtwhF5fmF9z/Dob94lN9/yuPdn5RLTix71ieOI7HH90dMBp6f" "u5Tw0z+yyB2R4b6dLd5w9CBzyRonBzHlhmOSTnh0W/Dn31FQRQlGNlm9cIr9Rw/y9PEncCsF" "f+qmBb7jp47wgbMp//mRTf7JfxoC5wHBTCegP6rqE3MjgZWlzCddtAow1iNDTZEVxBJ0GCJ0" "QDi7h2hmAdNPEVtrSJ9g3DYVFcaVmDLE+oAAz1Krye5ogAxeHKQcU6UZg3REYBxLfp6Bc6Q4" "LIJSSnppSTOI6R65BRMFpL2zFKnlU7/6AY694SiJELTbLRoaJr5ibXuVe269kxMXTrEwv5dy" "OyVudQFBmo6YmV1A6RCpJBIJEoyzOKbN7uLypS0Mp/MUldLopEk5sdhS4KTDjjL8ZILNxvgi" "Q1iDFZKgnRB1WiSzTYajXfa0Ytq3H+L4asILF9a/pmvmiY0e//5Dj/IP/kybsD2LNA7GA4pD" "S7T37qU906XZbiKVngorP51baI1hMpmQjifT0pkAKadBrwgIA40OgmlOmPdkg4qicDSaDZJm" "gjUGj6AsHY25RQ4cPMxjx0/wiUu7nDz+At/58AXe+paDdJtdhJDMzzZ4+52exUbAhZ2cjb5F" "VCkLzZBOI4YiRVWOVqTY21EcaMS8sJ2zvhFQLpd0ZwL2zAUcnvV025pGLCkLTT5MKbbGjHqG" "ZquDVgpXOITQpLklsCWh9mgZYL1jXoeEcrpqsopCJCENqWhlhtCEPPvE8a33fe7xz/2fv/U7" "T27v7mRAyFfGNlzZBH+jMuGNSoXXanp/JWKrFlg1NTUvgYftwYTPXRjwqWe+vC43Tis+8GzF" "//CeWWQ35i/cafmtzxX8v369z//wo/t46Jjj4FJEM2jwyNMFe2LF0X0Jn/rMhAubKXfMVVS6" "y0QIJsMNFvbMcra/Q9zuI9t7uX2/4u+8t2QzD/idJwZ8/uSQ/rCsz8mNXDLnyY3BKUfYiFFW" "0ui0Gfa2cCYj6C4ggpDm0btIujNkT53Dbe8wmgypnMVg8U4QxZpGK2G0uYX04FyFFB4pBNZZ" "vvD886xtbfD2Y7czM3OAjTTDND3tTkxmQUcJ7U6XldEQqWYI7v8z3Pznvp+gytj9g99kj9fY" "MKYqcuaW9nBpe4M33/s27AvPc+bceSwOVVWoIEQKgRTTchneT0fkIBAWtJaXZ6BIHGZ6RfSO" "vULScgFmOAAVAQZKRbY9RCbrBDNLKC/wIgAlydKcsNVGRg1Gk12SRsgDt+xDCcsLF7ex7quf" "NPDo8XOcv+t5Hth3GxU72DsOEy/upzkzSxgH09R27y4LLLCmoDIVQkCgBFVZoXSEBYJgKjKD" "UE3T/sVlXSAUQRzTaEZIISjtdNxOacE4x+EjN3G6s0Bvd4PtVcHv//4Kn35shbe9scEDd83R" "7rQJ8RydK1hqRWz1LStbjnSSkvsQLQSBtIRlShw2ONzWzDcjVjPPpa2S1fWS1a2YrXnBgfmE" "RgixhDiRyNmY0SDHl5LAOipb4aIIITQ5nrIsyIe7BMhpiVRrqLxz0lZaeWdE4NPSlU9unV3+" "1T/61U9f7O+uAFYg9PSn4ZqZWFf3ZF0rE+t629XxDZaX35P1DXexaoFVU/MtxPJmxj/8pdNf" "5l69yNOXDIWCxnyLf/nBAd/zxoj52QP8rV84y5FFz59+m4Zum9/9zCb/4CeOYX2K++RTPH0+" "4+4OGDuiNdPi3NaEw7Oe5eZ+fvvELu9984S1rXVObTsO7414+E1LnLiUMhrX7tWNsMai4wZB" "GFLkOUmjS6vZpBgNcGVKPimx3VlaN99GwyrKJz6PsJuYYIIQgnQwojPbQccBUk4dIe+hEccU" "l8s3Jy+c59TFqdhe29mhszdgkBlyAOlYUgkzrTZns5SeqxgNJ2ycWWd0xvCW99xHMjKI3/tF" "FlyfvogQQUQjTNhcX6aZzHDi/HFm5zp4OSEIQlQYk+cZKghIpAblsM7hnUMJgXNmmnLuHaFS" "FKZgj+5Sbgzx4x1QjklRYXZykiggbOyi9mfoIMIWjuFOn8IbTl96Ae8yJumQ9swRQuW4eU8T" "5yteuNjH+6/uuplXns+cPc2DrSUKpVD75ogbzalYtBYvLQiLQOC8xFozbfbWmiBp4KXBGIcU" "An3ZsXpxSLOWgJeEkUaH0+cYY7DWorQmTmKysmLP4cPcfNc9nP6cY3u8S16UTFLB+vaEzzyZ" "cfshxRtubrN3poHUEt3yzEUBk8yxsmPZ3R1SlQXWRVPHSXdpR4JjDceBluB8D84ONdtjw7io" "iETG0cWIpg4wskTHAq8MszMLlFnFbjamGUq80lReEgWetJxQBPDY+ROXzm1vnM3LcizwpZSy" "Msaku9lkUFmbCSEigaj89IRcq9/q6t6ra33+etlYV6e8w5dnZL3mCe+1wKqp+ZZysfzlPtKv" "5Oxqyi8/tsPNNzf57Q+tsbY5yz/7fzjefte9/NrHtvmdL4655UADayqe72vuv+0Y8BQ7PiGZ" "6bC7vkPsBpwqOjz51BbvOab44Kpj9bEuf/mts+yZ3cQ1My6elozGrj4XL4PKWqQUFFlKqCO0" "ljS6TUa7OUkzYeYd7+LWB++neuQ5ks2L9F2JkQIdBnRm5rCuZDzOOLn+PLfdcjfZeDw1V6qK" "rMp5/vy5PxF0ZYWvBFEYkY0zRllJtTBdjXhxtMtqIJkUOWVhOPn7v0HQ+LO88aG7Wf7YXQzX" "LmIiz6zSHJlZJEiauL2acWW5sH4eFYAS6vJ7CPBCYLxF2mnKeRJHeGux3iGxaDVtjF9oz9IM" "26wvT4h2S2ZmQ4aVR48dbmRoFgWmrIjiJtnGgPVLK1jvqKwhjmMS6fHOEEQh8605/D7N2k7K" "cFx81efkQ+c2+aF9l+i22lQ76zh7G1iBt2Dl1H+TXiBEgFRyWim0btq476erB521FM6hlAA0" "Go0TEiE8YaBwTiClnLp1eKIoRGqFHI5J2g2O3HWMwfI2ajtgLMbEcy2KfMR2r2K7L/n005vc" "eyTk3tsOMt9RJIEglgGdoGLUUOyOFL1BSmoS7KRPnHq6swt0m5K7OwFHvWd3oji3bpjknpXd" "nBBLIC1z7QZKKMbVgETOELuEsc0xoqC0JTLQdBcWSWaXeG5rK1s9c2K1rKrs8pt5MZLBA4H3" "vvJ4eYU7ZW/gYmmuHdtwo1LhtWYUvhJR9Q11sWT9K66m5tsDYx0feWKC0AatJU+eSHn85AjM" "Gi9c2OaF831+/UMnAbj10B6eOH4egNQZ/Pws54sZbDbmPbfDZPYwn1r2/LW3KpbXLvEzH9O0" "F2/jseclb3/jDP/x//MG7r9tvj7oL8Ha7halTRHC4agYT3oEQUirM0Mwf4i7vvNd+BNrqM8/" "ihVDVBxTWENRlVhpiNoxThuC7jzntjZYG/UpnEVHEZ979lm2ev0v7WucZczPdljodgi1ItSK" "Z/MBX8yGrIUVE0qkcriqT3b+06x8/tNMjKH71ndg/SLDQcqZc+foTcZs93fJ0hFHD+zhjXfd" "S6vZYjgZg1TTHiXrpyGjQKAUpiqx3pJNRnjhMKYEqYgKh/eS9ayiEhpbWSod0Sssa5tjsqxC" "dTqEjRarzyyzvbNFo9lkfn6JvJyWoFvNJlVVUhU5M80Z4iD4ms7Jic2Un3v2JMIXVBfPUYz7" "2KrAViXG2GmIqrPgHVJNVxO+OEZHK4HWGqUUOIMxFbaqMKWhKi67W1KglMQ6i3Oey4tHEVKh" "lKDZjth/82H2HN1Dq6nYt9Bh/037uPWhe9lz/20kB5ewYYvPni74g8/3+czzQ9aGMZPcEAYR" "8zOzHJlvcOveBu2GB5uTjYbsXDpP2RsSWsNSI+bWRc/bjzkeviPg7sOaZlNRAVuDguX+hNV0" "yGq+TUpBGMeEYYTQU0EZh7OU45J33nbfLe994B33t5KkfVkgBZdv9XUElL6BuLrRx1cKrKvv" "i6uE1dW38JXRDrWDVVNT8/Xl3Krl6TM5C3MB65sF/+I3VvjHf+NWfuBt8/zML52gMlPn6bGn" "zvCJz54A4OLukA3T41y/4I23LRE2m/yFt3j++r+W6Lku/++/JPmjL5b83IcGPHhrk3/3B6v8" "xPcs8fYHDvLkCzv1Qb8BO+MhYRyRZynOWAKh0VqRzO1n/rt+iE4ZEn34DzCjC/SDAd6HID2F" "naAjRbMdU1YFM60G5zZ20M0OuqrYnZScW1n7sn3lxrLS6yGCFoNiQhIlbJSGbZvhwxib5lQK" "Go0Oo9xg18+RbW6xdMdB5haPIrZW6RvH7m4fHQUU3pFYS5ZOyAqL1orhuE8YRgRBAEIihcAZ" "g5ACbyuUdGRpivSOJE4IpaZAUinHjI4RJqeSUHmHsILceqp2F0rJhedOkWuDtYbMpOQmQwlH" "nqUEgcZ6h7D+Fc2DvB6/f26b+w6NeLi1Q7p8kTiKkMID0WW/wyLki/aHmF69lZymyjNdUVfa" "CucMQsSAx/vL02PENBHdOocQIIXEuWlVK4pCbCPGLc0wf2ie9YsB3hQs7Okyd9/NyFYXUxpG" "65tsnb1AhGN55Sw7Zye0QmgHhm5YMh86GkmHFjEZJV4lKOtw231GFzeJ5ucID88w1+jiY8lC" "ExYTQW8g2M0EqzsTsiIn1JKwdHSqCCkVoRIs7D1CmVuKYkBzYUG9/Z6H7pif3xd+4LMfe3Jt" "Z6N3DTHkuHHQ6NWxDfaqr7vy+1xZAryem/VKVxTWAqumpuZrxzn45d8dvfgHN6fPp/ytf3ae" "73nLAlGoqczUFfjgI0986WteOD9h6Ht88ESP997R4dHnYg7v9/zrv/0WfuEPTvCZ38t55z1N" "/vR3tvn59++wvj7ip/6Xrdfg78VvPipjmWkGlKbB9u6QKEoIpKbdmGFmd0Lzt/8TkR6wzQAj" "DMJAnMT0Rhs0w4Rmu4Ubeda3dhFKsrq5zurukBNnL00jA67cl3A8P9lgLvF4FEKHeO8Yjgt8" "XmGcJcsNURxRZgXFYJdsNEHfsZ/2/kWCcYOmiMi3d5hptxEaVoZDystDjcM4RGlFUaUgY6rS" "EIYhkQrwXoIzhFFEVRoCpWGS0aJLmqZU6ZiFpMnYKEIpQNhpKGccYZMG1aji7PnT9MohVTom" "SRLiOCJUkrLMKFJDFLTZHO5Qmq+9PG2s4+//8ef5kZ2b+KljSzT3LKHlVBgJFU6b7v1laeWn" "JUGcwxmLsyXOGEyZT5fMaYsTEoRFXv46ax1SSnSg8V5QVAVaBQRaEUcaYwJmFpfozM2zc2kd" "Py7oNJp0Z+dJtKLau4/q7jvI+isM1uaZ9FPKsaW/vs7K1phAwWxiWUhSOkFCV4cElESxRnvJ" "eKtgsL7O7syQ5r69tOc6zDTGNELNXBExE8Eo1TgTUvUqyklGhKAZRRQr68goZn5pD8YFNJtt" "8dCxuVsiYfUvf+IDnx1Oxn2+sqz3YtTCtZrarye6rhRa1+vFulap8ForCq8UXK9as3stsGpq" "vg25sgf4wsqQX/yd4XWfO84Mx5cLlrdyHrsYsZmN+A+f2OJD/2yJX/r7f46f+7WP8//8V0/z" "X/+52/lL75nh7//82vR3o6+P80uRFiWVSbl53xKuAKFjpJO0+z0OnvwY7X0tdmRJpVJsweUm" "6oqZ2Tmkrhj2e+hAMdttcO7UKk+cuchmb0RVfeUCg6IqGdsRMteEQQNdBaTZhLJ0GFtM4wWE" "xmUTWq0W4946k2EPn2ii/XsQ52OcmTDfSlDCsm9xP0GzwXPnT1OmPWZaMdJ5kGDt9PxrKVFa" "UpY5CEcUzRAGlgBIvKNZSopQkniwpWDiK/JxRtKNiITDSlBJl3SUMRjt0h/tEEcKa0uwjk67" "STYe4azAmjE6aJAX5utybqx1/PbTZ5l/dIb/5o7bCaKEII5BKrzQOC/wpsIZB85RpROsMVhj" "KIoMqTVCCPxlp8q56UxDj6esDEJI9OVypvcCf9mTCUJF4gI6C7N0Z2bobQ4odjPk9hbJwgLd" "pE0gBbYKoH0LxfwerK0YD4ZsrS6ys7LMZJCxuTPkwlbO3hZE5Zgj8y0O64SGDOnEkoM6YaOQ" "PPf8gKSxRnfOo8MIV02Y8YqWdjgRUDYDTBQReFDGUY7GREbC+VUyl7MlSqK5BvcdOXJEfud3" "q9/53Kc+s7K9vc2X51ldq5FdXyGi9FVu1fVcLs+Xh5Fe6WZdLbSudK9ek2T3WmDV1NS8JP/0" "F3YYZ5bf+NyQ2w/B8xczfvGD6/zkj/b47U/1MWXBz/7yc/zVH1vkp35sP//0P12cJlXX3NhR" "9J6z27vcNbPE4W6L/sQRGM9tsxHNecVKuk0VXZ4jKD3jbIIMPZ1OE+sk1pRIGVHZjDOrW6xs" "7t5QMKRZSrPZBBdOS1VBRKPjqYqcrMgxRU7amxAtRTiTkk+GhFowc88RxucP0j99nP2dFkXg" "kEoQBp57jx2lmWg2B2OUijCVQYcaqQRZkWJdSJYN6HZmMGVKoEKKqiL2GukEg+E26XDIpoRd" "ldIfj4iikJmFGJE0UWGT4eaEwmQoLEppJtmY+ZkOM80WiQoYjMfkZcEwHX/dz9FvffwE73zw" "Gd4xu4eq0UJ7ifMvKqbp7Mje5hplMaHZmcU6kFKhdDDtwXIW4T0Ch3EeISRCQqAl/rJm8M6R" "ZzkCCJME6w1JKyGJG0RxQiADZA6kObLhaCRNiDXWGEKt8N4xMzvP0oG9VPfew+7mgJXlVS6d" "PUuZGs4McjbKFmuthKN33o6qmnTOjZiJDHfNL3Fxe4VL59dJdzeRYZuoHZKEikBJAqkRpkJ4" "QYAiCAPUJKfVaLN/doFJS7GuDDsby7zh4IGDc+3v+o5f+PBHPrm629vky8t71xr0fK38q6uD" "R6/V8H61sPI3cLCulY/1qrhYtcCqqal5Scbp1BV4+lzJMxemF/Gf/fVztNv7efbUyouXcP7z" "+7Z4+M17UVrVAutlcn57gLpDMtuAcFQyu9QmaGu27YhUWKQPwFbT1WqUNNstwkiRF47xJGWQ" "ljxxZo3jF1ZuuJ8gjlBhk15/AK3gS3ECBIpW1EYXAa43wDnH7nBEEHWQSuG8oFwdEEwqbl9c" "4mJvk6qqaCwKotQyKFNajSZbgxHeG7LJgEBppBB4IbBAaQyI6co5h6csM2wusbrJVlExth4p" "PMqFtJIZsCHSBzQXDtBpdLALklQlBGGLNO0RJyFaOmzl0EpNG8WVRIUR1n19f+52hxl/91//" "Hv/20AHund+DcyC8wJnpdT1NJwy2N5DK0+jMTC+sUXR5JqHBesF0OqHF2elKQq0kYSiozLR3" "y1lPNk4R0pI0IqSUJO2Emf17Cc6tMp4UKNkiidqkkwG2HDM3u0jS7GLDBnk+xtqCIAgJ45BG" "d4a9Nx1g/y2HuXh+g6x1iUlq+fTyNo/1XiA58h20b3mYYJSSbK/SLCViOGJcCJS1dBKFJ0BJ" "GPR2UFKhkQQWQiHo6AjlQ5TXJHGbOOvhqpL+zphb9x7a+9e+97vf+W8+/JFPrO/2t5i+/Wv1" "Y7mrHKsrN3mFyJI32Nx1XKuXK6xqB6umpub1g78c5DgYF/zdn38UU/3JBa2sLB/59Ep9kF4B" "670+/XzIYreF8CUuUQyloQolsrJIBZUtsd7QbAfEsQbvCIMQHUds9Eu+cPalj7nznrxyhCJg" "PElRQYTwEuvACod1ntbcLFVZMS4sUdhkfnEOZzzZ8VM0NjdQYcnepEM+2ODS2bPcetsxVnZ2" "yCtDUeSMC0u71cSUKV4ItI6pXIHwIKQGIfFSgbG4cc6OGrCyO8JJSaxDZpOE0mdIJGkWsNCc" "o2k9g94GRdojL3M0kvlmm4aMmYxHdOcWaMYe43fZ2d3+hpyjc2sDfu8PP8bd995DETQIZRMx" "HXsMAhrdGaQS6CCiKosrEpgkzoE1DpSY5mNJSRBMjZfpKkKJDjXWWCpT4JwBHCoWzN+8QOeF" "eUa9HcaDCUIGxHFENu6zWaywML+XMIpoJF2sK7C2oixzjDFoAa1mgyN33U7r6O2cv7COPXeR" "M6cu0PvYB0gObNG87ftp3v0jtA69HdV8iubpT7PXnCdSlsmkwrmMsuRyKr2YRoUGip2wZNMN" "cds92F2lpMQrh7Eh65u7HJyf2fOXv+9d3/Gv3//hR/qjyQ5CXA4F+4oYhiujGW4U2XD1kOgr" "e7KuLhXC9ecRvqoiq45pqKmp+apJs+rr0lT87cywqDi+swPaELQb5FVBKSwVBhUElKbEiJK4" "GxE2A8oipapyBJbeJOPDjz/Ldm/wkvspq4qtrI9VCo/Amop0nJJnGc55jJds9Qek1qJxNGZn" "IGrjNnbR4yEze+aYKM/JtfMsLO1lttvi2ZMn6DYS9rTajNOSJ05d4IkXzrG1u0ueTnDOUFUF" "jUaHqiwp0xHKWWye05ABzoFCgq8Io4BilKFKEDakP1Q8/chn2P78p9n43Mdo+B5F2uPogcM0" "ohilNWhNlufkZcVaL+fkpV38N+jy+QePPMXFM8/iy5Qqy3DeIaQkbreZ3XeA7sJegiiahqLa" "aaC4UFNdUZXVtPFReDwOIQXGOpxzCO+QwmOcwRjLaJRdLnnmNJe6tOfajHdzNp/fIl2fAIog" "bDApMlY3L7G7s0ORZcjLOWSdZpckSvCX/1+mWUqsLIsLXW6+7S5uvvs+5pYW8L01dh77dZb/" "+He4ZDQr3/lfcPZH/g7n7/tJdjhKoffhVYLWAVIGaB0QxDFxFKF0yCQvGaUpg9GQMi+JdEwo" "A/IswyK4++DeAz/xXW99x0yrOYN/scPsuo7VjbaroxkE145ouFFUw8tZcvN1X5ajfvqnf/qG" "T/h7f+/v1b8BvzX4UeD++jDU1Lz+UIHkwWN78SKEIMRqyMqczBTopqYx28IJSxRFWA9KKfqT" "kg9+9lleWN58mX9OC27de5iZpA1aE2qJ847xaIT10Op0yPKcMp0gA83sPQ9xx7vejfzEZ4gv" "PEdW7hI2AloL85xcvsDC0gzdzixPnjrJ4uISG5MJZ9e2KMqKjd0BWVUCgsFogA5jTFkgnSNM" "YoQxzKsEbEDPGMhzFnZSfBzh5mdxYUgpJduDHbLd86S7l9ja6aOjiDAKaTc6jLMJndYM40GP" "nTTlE0+epDLfOHNiMDbsn5e8+f578DJGCIlUEq01QaDRWiMA5xylmS4yEFJMXSolES+uQsQR" "aIUxFmM9zlpMVZJlOZWxOO+Y5AVpbkBGpNspy6eWyYcFLWPQ7QQROrxwlLakLAuMM1S2xJgC" "pULs1FhDKUFlSoaTEcPdPpubPbJMUOYKX0mkLQnzTUZr66zvCtLuXoJ7HqA/dx/DoaU53iTE" "UuoOAkmgxWVRCEGYEEYxYRgSBDH68pDrMJJ4PKYsuPnQvpkk0c2TyxvrlbE5X5pEibvCWXo5" "25VN7lc+5q9wpK6+5arPf81C6qX00tXUJcKampqa15jlnQG5iGiGmswUZEWBDBWRDoibESpQ" "GCvI8wn9Xk7pLM9c2uXpc+svex/OOoqyJJmLoTKYYirYglBTFgWD3W2EMSgZIIMmnf1HaS4s" "MtoasXvpLFUwJGmFBK0G+w/s4eL2BgudWW45cjOPnzlOlEjkZUFRGsuljR7LG308nujsCkkU" "0Ixjmo0GBzod3rB4ByUaLQReSWYP7CXTkucHu9DZwy3f8xMsHlwifuo36a09S571WZw7xLn1" "ddo3dyjLClMZRKBZXl7H2G985eeX/+DzvPf7jnPkzhlsJQCH9wFKKryzeGeQQiI9WGfRKkBp" "ifUe6y3CCaSSWOMxxl8WWIaqygm0YGKqqXKwgixXZKXDyAARKnqDMVsnlpESWm+8ida8Rkcx" "pS0xZYayhkA4JpMJ4vI/ryRJFDKHBqfY7Q252Bsw3hpjjSQfjLFFjtBbtC+dIDt+iOfu+EFu" "fssDdH7oL3P6qTew8NTHaKereDFG2QnOVOiggVAJ1qRopQhkiPAWa0qEkmT9MaX26NDxtqP7" "b51k92a/9+knH8vLasxXRjXcqA/rWpla10p4v166+7V6s161EmEtsGpqampeY3ZGOS+cX+ZN" "tx4mFgGqEaAbLSbFBO8sRWZx3mOMZVDC+x97kpXdySvez3bep6LCVhbvHUopojCmzIcUWY5X" "EhG3EbNHiO+4n3xisOOCvbcu8PzZNdJJirAVRhpIYs7vrNJqNJmZ7fK5zz7J1S0u/vLHRVlS" "lCX90QTY4ayUvOedt7I/muVMkbLqBIPZFpPxiDxpcnY358Iq/NiPv5NqtyQ4s8GRRU+j0eLC" "8hrLy2vcfHAfW6MdLu4MePbcq9P3d3Yl5ZHHHucnjt4GYgEkuGI6DEZ4fznhHaTw074lKVDi" "cg+WA4tFSUFlDJWdruw0VUlVZIRaoAPHzjBnkkJagC8tk36O1gG2NAw2RnQOehpVh/GOoTHn" "Ua0IhKRyFi80xhp8mWPyCoeiFILKCZQS3HR4nkhKbG/IxfN9yqxEeodgRDMqUbtrjD55kVMX" "Hmb2zd/FTfe9lXOLN9P69MdZWv8sLVcR2xFUCqMbhDoBZ7BliVQeHSqEs0gBwnmGmz1asxHv" "ue/We2xVlb/32Wc+Uxmb8uW9Vdfqw7qW6Hrx+eKK+1cOenZcu0z4mlH3YNXU1NS8xjjn+eKF" "S2Qmh0BihKEwGQJL5SR5XrHdH/L7j53gVz72Wc5vDqjMK897Gpcl1nmknDZoO1sglUBIhfSC" "VrsDUQcxc4Bb7zmGPb/KbH6KYTWkuThL0GgwLPqMqoxnTr+AbnbYmIzxBIRR93Ii+UtjnOP0" "YHuaFuEzxvmET5x/gWcGWwzKFFNusfP8Jxns5lRzBxmXDUSl0GXFO++4k5vmO3SjkDTLOHlp" "HfsqLlj9pd/6FCsXnsTlY1yVgy3BmWmyuwC8QQcKhMBWZpqDxbTBXQmFs1CWDmsd1ljKvMQY" "g/eOdiMg0oZxUTAcTNi5tMlgZwcVBaAlE1viRICuYooNx2Rn2g8eRNN8rgqBUQFGhxglGGQj" "ttc2WXvhIpvn1+j1xsStmLvvO8gdd82zNNsgEAGR8ChXoUVFK+wzs/pHFB/6lzz5K7/BbinI" "f/DHOHnPn2Nd7qVSLbyI0c5Q5SXOXXbL7FRoCQzycq+ZM4ZskuKLXPzg/cfuf++b7nyjlDK4" "QiTJ6zhW13K0xBWfu1JYXdmXdbVrJa+hc24kwL6uoqx2sGpqampeBzyz3OfZC9vcuneJzFrK" "yRipAgajikHl+MKpMzx5evlr2scoy3BK4pzB45FKo+R0hItuxgSdDi5vsu+BNxGFCfljH2R/" "NGBzlHFi6wLz7VkW9+6nn+5w72238rFnnuItb3gTXzhxktXt3it6LefSAfvSHeI4ZLERMRoP" "SWUT7QRNOaS/c4L+5hZ7O83pKJ+xp7+zgWx1mOvO0Gp2uWX/EZ66tPmqnqfnz4745d/6MH/7" "rx+jFIoghlAlX2ovEkqgpSDwUBmHZxpPIQSXVx5C5T346arbrCyx1uK9RwnJbDtgZ5Cy1h8x" "OH+R4fYGg/6I3mgAqkI99wUGW7skc4s0JguYsEtrnwelkVpTVQVlVeGkRCQxbmIYDYf0tiaU" "laESIAJFuxty+KYZNmXGJK2QOsZaQaQcuSix2TLq5G+xvPocvTf/KEtvfguX9i0gH38/+/NL" "CLuFEh7tLNJPS54iBIGcjgFSDluVkMNkLGl32uqH77/9TeNJmn70mbNP8JUrAl9uifDq8qC4" "hpN1ZT7WlcLpVV1NWAusmpqamtcB46zkA0+e5sffOUc+GRDEMYUvef/jz3Bus4e1X/tqzWGa" "sjXu06oCsnQCXtHptgkCyfYgxcuA2Tvv5Z4f/FOM3/c5Dp38JOlMRafdZJ/bz9MXnuHeJOTY" "0Vu5tHmBB2+/mbMrp2i2u+TFxVf0WgpgMEkRUUDcaGCsocJR5BOiZodGlOCqClNpIu+ZAD4M" "GU1GDIoRzXxAt9vl5j0LnLq4/qqeq1/67Wf5roc+y1ve/n3YSuLCAOUFSmucNeAccaBwzmGc" "IwgChJTT1ZoOcB7roDCGvDKX+7UUUgiiuMX+OcOmX+fMxTPsbG2RjisimxLH0Jtska8ZWD/D" "3GA/e6s7qao9NA+1iMMAFQVYpxhNtlEEyG6bzs2S3FqGp3tsbu5SlBXNmRkCFdNpJUih6I8K" "KuPxYrqy0ZUTmklAWZwg+4LndJpx9w++k632DPaR93OkeJYWPTApQkGURCAMtnIIPR1obYRD" "CoUtS/LJBKFl+IP3HnlonKb9z55ZP82XB4per0QoX8Z2vRWE4ipB9apmY9UCq6ampuZ1wvmt" "Hr/6yS9weHEWLwQr27ucXf/6Dcw21rIx2WJ+4RBVpRAInKtQgWZ2roOYW2Dve36M5kZG41Pv" "J1hIyZWgMhatBPtml3jm0nG68zN0Z+YoA8m8D3j0iZOv+LUM8gnMCryXRI02WZbTaDbp9Xfw" "3mHRGO/xkxJVeWIt2Bn2GTrPnoUjNOKIskyxVT4dSeNfvQzJ4cTxb3/94zxw/wOo9v7LDe4K" "oRRIDa4CHGGgsUWJt5ZQawyOqvJYC0VlyIuKLK9w1hFpSahDnFUo1eDggubpcoAZDtFVwd6Z" "iLv2HyUzsDIcYMqKbHeb3ukzKKkoRwWtm+dIFtrESUDpNb1+j3ScIjzMHJwnGzkGo5SiqJjs" "jvEUlBa8CAnjGOMqytIiNcx0GxTGIXBQrWC/+HucUoZ73vOdbD78XuyjjmPZUyTCoqhw1qKU" "RTqJJEBhCXWAxIIrcE4QyIi9zbD1l9927O0747R3emO4eZXIeqkoh2u5V1c3ul8dPiqu4WJd" "by7h11V01T1YNTU1Na8nkbW5zR8/d4pHn32Bs+tf/+DMM2trkBjiZDpqBQE6DOi2JPtuvo89" "VYf2H/wqs91tJiKlcEMyU2BNzlyzzd6lRT7y1B/hvSAvUqw3rG4NXvHr6I2H9LMxw7IAoRCB" "pnIGnUQYU6FDRXemjRgMkKYijgNarSYy1Gz2t3n+4ik2ej2se20uY488fpEnnvgskgpnDHg3" "HX0jpoOcpRSEYUAUxRhTYaxBiGkvlrWWSVYynuQMBzlVAd6GVFaTF5o89YRxk7uPHOBgo8n+" "mSZhINgebdGNAm6a34MWHqqKfNBn9/wFeicusfP4Cv0LffLKoqMWMlnCBHMMyoi1XYPotJg7" "sERndgalFYFWBEFAVXnKgunrUwIp7DRQ2CuaTU1DFTTYwHzht3jqd9+PPDBH9t4f5dLcmyll" "ByEkiqlD551ECQXWTFdRSoEWkkB4GkoQesttS529Dx5Z2s+X91XdqB9L8vKdrOs5Wlzn/jeM" "WmDV1NTUfBvRH6XkZc6+xfblVYQhnSikFSV0Vi4w/0e/xP7GaYpwQuYzCgxeOpQOCKKA2w8e" "5eiBm/ncmc9h0Tx/9iLVV9Fk3k8njE2BV5IgaaK0YjpxWiNkiGx26bQbJKMhUVgisKAUgXfs" "63aY77Q5fvE0MlIopV714zjJHP/xd/4YO1nDmmq6ghB/2U1zl0cDeeIoJNQRZV7gnEfiEd5h" "jGecVvTSFCcklZGkqWDQqxiuD8k2hizMdNnXTohFBaJiXAxZ3j2PJmNvu4WvcgIkvsgpRj0u" "PfMcqx99lsHpbdLCEMVNdLREER+gHy+ytjsmM47G/Cyt+Rl0rJmf79JoJFhj8NYhsTQaId1W" "jPaOIhVIrwgVdOQA8dTv8uRv/ha9qMHuu3+QUzNvIhUNhJJIpaeNTwK01khAS4V3UE1y8uGE" "JEnw83N+o/gK1+qViKqrg0dfanulfF1EWC2wampqar7N2NnZ5ZbFJkcPzhKFgjjQBGnFzOA8" "czPbbI63ydwYQoUHjMmJo2mIZGUK7jhwjK2e4T//4Rd46tT2l8YnvRLSqmRgM0aDPpPJGCx4" "Z3DWo4KYpJPgK1ArzxOKAolBOE8oBIEU7JuZ4/6bb+PMxUuYr2JF5deDDzx6kU9+9nE0dpre" "7v3lGYTTyAbvpintURwikJR5MY3HEI44BGsNeVYwGqRMxhX9zZTeqS0GL6wwOL/OeHNCtznD" "vsY8LREiBRQmZWPnPMoVLLbaUOSMN9fwacHO6ioXTp6g/9mzZE9coBzmiKCNCmeQ7b2wdAsD" "r9jpDdFxg2a3S1lNVzEqUaKVJdYSU+RMxiPiSBAEksp5AiVIIk0sxyTn/pAzH/wgqQqYfPef" "4mTzAVIb4LxACY+oDAKPAkyW48oMU2QYNyHoeGQrsruZKbj+ykHJjSMbXqmLJbl+j9Y3zNGq" "BVZNTU3Ntxmb/R62GHHzYoPFRkBUWg5Lzd7FhF7VJ5MGK6ZOhBfTlXBBrGh2GiAFEocxEYNR" "/jU13zvnkFpS5jk6CsnHA0yRUxjP3E3HCPKSsNxAa0M3TuhEMUKClAKLpdloMNuZf82OY17C" "L/7Go4wHy1hT4Z2dqgGtkZdHEgEEGhrNBO8dpizRUtGKNHPdhADB+pltLj61waXHL7DxhRfY" "Pn2J3bUBW8s79HaHSBUzl8yzlMyy2JynHTYpijHKO2biDtqCH01ohQmT0YATT36Ozc+cIPvi" "ecwkR8mYbjJDMH8TwcIt2DBhd5zihaLRbNKdjQhDdXkhpKUopxEhjVghqAi1xHuwlSMKNJHS" "xBc+z9N/+DEmUUjv7X+Ks80344IuUimCWCP1dOVkECiarQaNVkiShOBhkqZGYuHLe6iuJbZe" "SbP7SzlYr3pGVt3kXlNTU/NtxtooZyfNONRJWHQVZWlozMb0yCisBK2midyTPjLwNLodKlvQ" "aLQgG+N8xVy78TW9BmMdJY6k2cJOKpwvQSl01CaN5jn6pncgt/v4KsNog9KKubkFMmmphMOV" "JUGU0GmFr+mx/PCnL/Gbf/AJfvIn9mNMiJQKpQOEUDjncd4hhSTQikbcYJKlSKVIgoA9cw51" "+0G2ZY/Tf/AUZ54/TZ5v04gUc/MtJmlOWVkiPAsJRIEmbEa4ssIEMZN0B7yjI2Kq0oCfBplK" "BcvLLzCfZixODO7gzYxaS1QiZNK6jXJOoAfn2Z3s0A4SoiggaUb4SUmZVwRK4a1j2BtjCRE6" "xHuF1tF04LOrUHKIW3uCs4/P8cC738Qo+CEufSrjiD+LZIQqJ6jLw61dmaG1QDgwxnFpJ2eQ" "mfCyBjG8vFLgSzlW11tRePVWD3uuqampqfnGsDPOeGpzk6HN0XMhzUNNBkHBQBpcpMhdSX/c" "pzApuiGIGhohPdZPSz9lmbM7Hn/Nr6NfZPSzXbR2FGWKl9OZfSiB6sxSHj8BZoANJIUpKMuC" "JEwItaDT6TIuCrYGo9f8eP7irz3K2uoKmApbGayxU6GlFEJcnkcoIAwDAh1iqgqlNM1Gm/1L" "+3nDW+/m4XfdxZsWGjTNgOFklc3tVVLnyIRkbKFfCvqlpDesGBWOwkuEihmmu1ClNLwkNo7Q" "OqQHFQTsbC2z/fzz+KdPwm4KwRxV4xDj2TdQNI4QJvPspI7lrQmTEmQYETWaOBReSCxT52ra" "P2awzoGXKCEJZEWDnOT8s5z8/EmyvfOcP/wOdqo9uCJEEYATmLKalk29x5qKYb/Hsa5t/LUf" "uu+OOI5Crh86eq3b67lZ4ga31xNc17p/JV+zy1U7WDU1NTXfbnj44so2tx5YYk+zi/MVpZyu" "ga9wlN7ipKfRjujMzCC8JAgDqipHa8nJ1T5fPLn2Nb+MjeEOB2ZiQhEQBgHpIKcZJYRzs5iR" "oDr1PIUtIFBIHVEVE2a7HbbTPnk+YVKWnF7efM0P58lz23zww4/xU3/5JowxBFpPg1zl1MOY" "Rkh4lIJmI4G0wDsIgxjrLGWZs7CwwNvvvZvbF+e5uLPM8/kOu37qg5S5YbdwhAREXhEIj7M5" "vvLEYUJuMpp+lsNhC1EU7FqPVxJf5Yy2LpJYTThx+DveQTh/BLHYZUjEZCfDFwJnJS6fEGhF" "FGhUKDCVIIwiikKgdUBpBDKQeCHQgSaKYqzPyXpn6T+ec2m2xb1vewPnh326OztEfoLwJVEU" "kSQCHQucr9CBRuuY2w605juNMM7zIr+Oc2WvIbJebu/V9Ubn+Ffz56IWWDU1NTXfhgwmOVtp" "SqAihPdUGAQKn42JGhGNJCFONFVlkMIhJVhnOLs55AOPnSLNv/bGcmMNXhgqlzEzu8RwOCQr" "cuZvfQPJ+i4z2TIuqDDeYL2hcgVH9h5hvJZxcX2DJ87tUlX+dXE8f//Dn+Iv/NnvJ+rsw1uP" "DARKS/Rl98Y78B6CUCIDRVEKhNCESrJ16izLH/gku+dXEKZkrtXiDTfPsLJQsqkkO+dGlGe3" "kF6idATO4Ny0F21SVERhE2cLAlNxsNFG5xk7aYHXCl+VjDbPILa36BpBfltEtHiEfOl2Bjsl" "cvuPib0hCDxVVWIqi1IBHok1YipLHHjn8caA0pjKUOYjlAYdhMTDiwyee5rJTYs03nY/Kx96" "lo7s0ZBT104pkL7AmgJTVkxGu4wLK70zLwonz8uPYriR4LqWuBLcOHT0eiGkXzN1ibCmpqbm" "25CiLNkajOnnJb2iYJCVZN7hGxFEAU5KirKiKEqsL8nNmCfPXeC3H3mW1a2vT1lukld4ofGi" "Au0IGw2ihYMcvO1+ks99nERuoQMD0lOUE4JEMykmHNi3yPHlLb74/MVXNWD0Rnz+mRU+8vFP" "onE4a/F+OhrnxVKh0hIdKLRWxHFIoxGi9TQmo724wNPr23zo4jk+sbnOcp5jKdgxGfLNb2bh" "T/8o3Xe/G9VsUWUFxeUZgMY4JmnBOK0oC082zpGFZ67SzNqA2EpsUSKdROQTxLknmXvsA3RO" "PseeTpP27Q+SH30nJlikdAkVCUbE5JmnqgTWKRARziu0CvEonHG4yl5WK4o4jmnGAn3mSZ5/" "6hxirsPKwTcyEguEcYyXmsqDkzEq6mLxeK24tDvJ0txcOeT5Rk3ukhvnY109l/BaJcLrCa4r" "g0ev5msqE9YOVk1NTc23pcCyrPSGxEmDJIzxtsIqjZFgvCOOFJIS7wtyB3/42Rd4/PgmX89E" "hKKyVD7kwMIspqzotlq0jt7L/tUhC6vPUixYDAUyCJEoZpbmGY7HzMgue2dmgJXXzfEsK88v" "/B9/yLsffojmws1Y6/CVQ1iB99MyoZACxHSkjg6mpb/JxgBtcu646yAqULRbklv2tJi/aR89" "YbmQzzMzd4jg7UfZbewl+9Dv49McZwp8leNwpCZnlCpC4ylDh7eODp6kFTFpNDFekVeOMhui" "yuPMVykj7Wg+8B3INz3EtlKELzyCLLeJAo3DE8jLA6udJEpa5Ok0x0sHiqoqiMMGYZwghEYH" "AWSWbPkcm7cdRh++m1Nb6+xlRCj6GGcwaQnCIGOJLXLOrQ0nk7yq+PIU9us5VS+3VPhyeq/8" "NQTUN0Sl1wKrpqam5tsQ72F5e0QYRcy0OihhiUxFmEmacUhSKeKGYH13wiefOM8LF4bfkNex" "3t/lwaMHyPoFqhuyIEo6T76fxqxhEDlUGGCtI4wiGs2Ezd4Ol871yCrxujumjz21ygc/8sf8" "xb94C0VRURUG60EGASrQeOem2VgGAh0iBBQrFzC7G9x+7zwH7zpAHDQJAkUZKe6MIybbOemk" "pN1qU97zIJUPMY9+ELe9RpVXeGPJZMFQSJqVQkUReIfIS0Ln8aJNEQhEoCjzMdIaGsNzhE9/" "iG3jOPLmh2m89e1csh7x/Cew1QAtNUkowYFB4YRAhSG4CucMKgzQQYL3ijI36EQRB5DtrrCx" "1uP+25e4cPpmbht/nqWoD5UjCDU4jzOOqnTMNuOGEGjvb7iK8KspD15dJpR85dDnV4VaYNXU" "1NR8m7LTTynNBvsWpllHEkcUaqSUCAmTfMzZSwMm2TcuyPPs2gaTY0dY0CFRqGhtnWPffIdx" "bLFkBCpBekvYTMALjuw7xEc/8hh/9PkXXpei9d/96kf4nne9m+bMAdCaoNEgiEOiKEJKibWW" "PK/IswzvBcnhBcxMQFxZul5jTEDupy6i8pa790ie2BoxGGtmZ2YJHno7l3KP++hv4UcZsW4i" "tGdSWZQQRGWJRmOFQBuH3O5BLCFuEFiHKSZQViRKs+fJ97GZF+x/9/ejv+tdPGumTlZT7EDg" "EQ4kiqosKEtH3Aix1qB1hLWGaaSqpChSpBQEZY/R8nlW983g9yxyqreHuXALJSZY45DS44yl" "TD3HlpKF/fPN+ZXtyTLXbnC/XgDpy3WyXioP6+q+q+v1YH3VvVm1wKqpqan5NmY0LpikGwjB" "l+IEnHv1+pqK0nBus8exW25luDOmKTVVR5K7jDBJcMKjY0GrM8PWxjKZc2wNy1d5PdjL54vP" "rfOxRz7Lf/UX/itSJwjiiCAMkFKgFSil0FpShJYidzQWlsibXcpxQZmXlMZSGEdZVEwKCzLg" "aFvx/PYuqQqIOjPsffgdXBgXiI//KlhDEDaxpk8lJANT0Q6nTepVUaGkIJExUikK79E6QGqF" "zwcEOPadeoRNGTL70Du4+3vfyQtS4c9+Ahnk2DLDOTttrK8y8jInaSXEQUR/0ENKTRTEmMog" "A0nQkkzSjHObGftRbJsliiqmHYS4aoJWEqSizEtmZ4L4xx8+fP+/fN8LvaK041foYr2UsLpW" "mRCun4n1DWl0r5vca2pqar7Ncc5Ne4a8f1XF1YucWt9CFCn7YsfckS7jsGBYDvASRCBRQYB3" "Hh3FnFhfY21n8Lo+nv/4X/82H3j/H+GyMcKDVgqlPF44hPBICUkckDQCSlsySnNGpiJVUGiJ" "CxUiCjAotgcGkzv2h5J0c5Nxf8BCO2D2Xe9i/U0/zlAfIHUhheqQWUvhKyonELJBaRyF8ZjS" "EKJo6Qhtp/MQQzzaFkSTZfac+Ajyjz7EgaDk3vc+DG/8EcZ6P8YnEHbxQiPDCB0lWOvJyowo" "joiiAONKrLV4pzBWIRB0AoFIQkbJQTazJsY6EArnwZnpZIAiN7z1aPvIT373obfFoU74k5WE" "1xoA/UoGPb/c+YTf8FT3WmDV1NTU1LymLO/2GdmMVtOSmgGWkqTTYZLv4l3JeDJgONjEOsXH" "n7zE+nbvdf1+zi5v8RN/8x/yj/7Jv4QyBQ9CBggVgQwRQoK3gEOFCpkEiEgiQolTEuMFVmhk" "1MB5xe5WhhmUzJqC8fo6l1Z2aCQR7e/+YVbf/Tc5Ez/ANnsY6YjCK0ZVQemAIAYVMBqOSYdD" "Qq+IvEBZh8IjnSWwOUm5wv71xzEf+jBiY4c7H34Tydt+hGFyC4WNQYcEUQxIjC2pqgylNQ6B" "8XbakxUG2KyPN/l0+HUgKKMWuyakcBLjHMYZEAKMpyoqiknF975h8fafes/Rt7US3bhCl1yr" "p+p6QaPiGuLqeuXCV5W6RFhTU1NT85pSGMPZbMwthw5grEUGnlAGeBUShCHaZAz6a1wcac6t" "Dr853lPl+Bf/8QPsPzDPf/ff/02saKBCAIf3BusqjPXoQJI0Ioyz2MKBhMpbcuOxCIJmgt/O" "2bq4hisnxJ0OmwXYtiJpLqDuuxNz632sPPIo5uz7YLKKMEOaSQtZRlCVBHjcuMCFniSQGDd1" "kizTOAnKMWF+in3VhGTzLJP738WRe+6n0fwhLn34j7DDCwRhhNIVLs/weIx1FEWFlxDqgKJy" "CF2giwFVURJ1YlIBmZUUTqK9w7oCFAg5ndnonKQqDd//wOyd5zYnq+9/bPUZbtx7dS0Bpfjq" "5hG+eP8bViasBVZNTU1NzWvOybUN3nXPERQxXnoQJQ7LpByhdMyza30eP9P/pnpP3sPf/Zlf" "Zs/SPH/+J34SYyRhKBCXhY0QHnBIKQmihMpZTFlROktWeirnMFagGwmVF2ycuYC3BnXsTnot" "z6RhKaOKZGkvnf/Lj7Ly9INkj7yPezeeJIw8sU6pdi+iZUCuQ8oyQ6OJ4hDhBaWp8BqkDgi1" "xFYbdCYjxl9Y48LOJke/9/uJ/vSPcfpjH8ZvnSfyPQwBQZBMA0e9w1tLXlaAQAQR2hYwHpM2" "G5TGU3lPbkALgXYesAhR4YxBBAqNItCB0Eoqrl/m+2rmE14tpK5eTfgNCxitBVZNTU1NzeuG" "1WHKIBvSDiOCqAlCojzkWcoXVrb4lUfPT3t5vsnIS8/f/J/+Fc1Wi/f+0I9SOIXWAi8kXnBZ" "cHikUqAlBAInHOMiny5A6FdUuUGGHUQU01s5j3j2KVpL6xjRZKdzJ9tbfWYO38Itb72ZweG/" "wvEPfpzg3DPcKi6QRLv4bEweNRhJQTHpEwhJ0ozQCIx1OAk6khTGIuyIWSMILnyc87/bJ7z3" "ndzzQz/E8nNnGT71MbzdQkUKV6VEcQNjc7x3qDBChQnSe9zuDiMfkowHSFdSVoZMCCIEWkuE" "0Ez9OYXWmk8e75371DNbl7jx8GfBy18tePXz3VVi61oOFl9vsVULrJqampqa15zl7QlntoY8" "dOwgFRatNFJYSlfy2ZPfnOLqRXrDkv/bf/9P+KUo4F3f/QOUlUaEkspCaQXGS+zly7oXAqcD" "dOJIt3MuroyYbPdoY2h0FknHEya7q4TZNoEIUeIs7e6t2JWLnBk8xJ0P3ob8qz/E05++m9Gj" "H+QNg11aOqDZbNGcOHajkiwvCeOIMNTMCoULQrK8uJy47lFhxqLepLH7cS780Sl273kvtzz4" "Rlb3LbF7/Fns8tPk4zEeUDpACpCBRiLxeYoY9whUTDDeRFYphBVeOJwQVJVHCY+ONEprHjs1" "vvDvPnzmsXFapZfdKXcdMXW9nqtrPX4tIXWjxvaXElZflfCqBVZNTU1NzWuOtY7PvHCJt73h" "GHbUx5YeFUou7gw4tbr7Tf/+dvs5f+Pv/HN+/mc1D77lezFOYv105V1VGKzxeAtV5RmNDVUF" "SasFakJvY5etSZ/FbkSnM4v2Jdn2RUKfMSNztiZjmumQjbUNPj96L296+C5u//5bOLXnxzn3" "+J0cfvJDHNUDZquKskzR0iAbIaqZQFYRyQBhLcZVWBzCFti0QsqAw0g6T76f85fOM3fvG2jd" "ez/F4QPsLl9guLpMUPUJqahkgA1biKRL0J1FmZSm2yaRY6QzoDyV9XhfoShJpOfpi+Pez3/0" "wufT3GRAAFQ3cKdeKlj05Ta5X+u2LhHW1NTU1HzrcmJ5wIXtHvtaAVWRIkh44lyfrLDfEu/v" "4qU+f/1v/lN+4d/s59gd9zHKKxSOqj9g+3wP252nL0LyYUleVJgS9uxfYrA1YfXZHTaqHnMN" "T9zo4LpLUPRZrCq6TcludQk53oHPKz5XOA7fezudfU3CP/OdPHv3nSyfeI49z36QhWqbjutT" "VhlRGSK8J5aWMEroVprcGaz3SBUQIBB2wFxo0cMRG488T3/2EPr+d7Dw0ENsjzKy7S3M5jrG" "epyOWZjvEMQxxYWLBK5HIAucs5SuQnmD8wWh8DgEzy0Pt9PcjC9rEfsKHKobjca5ljvFdZyr" "r6tjVQusmpqamprXJaO04sNfOMmPP3wPo0pwcbPH0+e2v6Xe47kLu/zX/81P88/+t7/Lzcfu" "Io4UIlCIMmPwuefxOchhhZybI6skExuw76bDKAkrTz/O6vaIvbMx3dl5XJUw2V1DV0OshZ1y" "i3R9guttcPz5t9B663dw971zHLhzkcmRd3F632FWHjvCXSvvI5SCpoWmNKBDvBRUrgJvEYRU" "uaVyHu8kVg6RoqDrdkg2Vxh+9CyTffcwd+/9JDctkS21GacVRWWRDtR2nz27J9jjz9NUOd4a" "bDVBao8IBEiLFwKlvpR9dXUflLiBoIKXzrySLyG8rhc0+nVFvNQkciFE/b/+W4P/APyl+jDU" "1NS8nmnFiu+49zAnL+6wOSyYpMW35Ps8tH+Jn/2H/zMPvfWtbJw9R3phi8HTK0xObbG9ssO2" "jjD7DrNZCMqF/Yi9BxgNd1k+/jlC1+fgUotYgS0n5JMBCsnYGF7olwztIipaYNx5EHPrQ9z+" "zluYmdNoBJOJJ330i8w/+VGO2SdIqk1i5wgCNe0D8xWm8ngHUbuFqSSVlZRIKioqL1CNLmW8" "j1Gyl0Eyi2klVCqiFAGusszvnGF/9gxtvUkocxQF0mcgSoLQEzUcjWbAFy6NLv7Soyuf8p6K" "ae+VYVomNEB5+ePy8lZc47a4xuNXfl11xfezV9zay/t7cfP8SSO8v2K7Ev9SeqkWWLXAqqmp" "qal5HXD/7Tfxn//X/4n80iZrnzpFEM+zfnKV7fVNJsWEcGkvGzZiJEJcZw/+5rsoAsHWiU/S" "FAOiUNBuhCjhKNIReBgbx9ntjGFaIjp3kjbfxC43MfvQA9zywAyH9im8g8HZAfrRj3Dg9Ifo" "pM/R1BKtE6ypsMYilabRalJVnqL0WCfIbEXpHGHSRCSzVF5TOEuGQoUR+eXQg1mRMhOOwA/A" "jlA+Bz9BKEMUC4IEokRwoZ9v/u8fXX2krHx+hQAyVwijKwVWeQ1BdbXIKq8SWVcKrKtF1tUC" "61rb1ySw6hJhTU1NTU3Na8CTJ8/zz//5v+S/e8ubmJy7SDhjyX2BSxSL3X3IwuHLPkU2wqa7" "DMa7qKN3c/DIfQw2nqXIt4kjT9TqgA5wVUUjm3Cg7RGmZDg+xUJnFrV9nv77zvPMmXs5vXeB" "m7/jKLff1qXY/2c488H9HP7svyFWawQS8A4dhygpydMcCPAelJZESiOMRQqBMAWmGhEpiZIh" "0qS0lUZ4RyIKyEuEL8GXKOkQWiLlNGTUOYf3msLYCi8808CKlzvW5upS4dWPwY1H4rxqqe61" "wKqpqampqXmN+PefepK4cPzYwiFGox0cllE1ptvu0AxC1i9t4CcDEBrlStLntvH7jhHpmKwo" "qDJLqTSqERM1m3gtaCnHTWHC5jBDVGdoLswR7CyTP/8k/U81+eIz38P2D72Ne+5uMP8Db+N8" "aYm+8IvM+Isoa5F4ZJxgrMXaCpQG63DOME0D9SA8WiuqqkQqj5IaWRUE0oArQRh8maOEwcqM" "QDqktmjlUYHAYd0Xzo9WS+MMLy99XV5HaF2v94qrBBfXEWEvl1fcp1ULrJqampqamteQf/X5" "p2m9yfCn9x2gnARsjw3ZZAcTNzChZFHOYQPFgShhK5uws3ESEwcErqAYTWf/6TKhNTePSlqI" "IsUpx1y3iwgDRqYkEbv46iKznTkGJ7e4mJ0h/1N/jofeMo97x0M8cX6FN23/Pt24jxQlIHFC" "YrxHCjDWTs0mAYGYptFbawiD6UxCnEXaAqk8iAqERWARGEKtEMIQBBJPQag1p/vV4Ivns9Xr" "iBxxAxF1tfC60RicG4mrr1o4vVzqYc81NTU1NTWvMT/75PN8qhhw65EWc62YcdqncCkmrog6" "IfuihNYoQ096lL0L2PEa5ahP1uvhJxPMcMB4dxcvJFFnjnh+hqDTRODoJtAKDMpN8LZH3Omx" "334a/7Ff4lMfOI1xMH7L93FR34c3EmskWVZSWgOBxklF5QVeCLyAqsyZjHtf0iUCi3Il3hSA" "nYotb6ZOmPII4ZASvHBorZHKcXoj3y2Ny/nyYNAbhYHyMkQWXL8s6G/wfb8h1AKrpqampqbm" "NaYwjv/pE1/ksxur3LwYEVIhbUXYiBgUI6w09GWGiCNmwyZx6Zgx0DbAYIgfDnFZn7S/hQ5j" "Ot05WrMddBwiMOybaXDTwiytoKSj+8R+SHv0ReRj/4Gz7/sMw1SxHdxK7gMqC5UTeKnxSmOk" "otSaQgicCnBaI5RGBxrvwTs37d0KY4RWOCwei9QKHDhvQTqEN1NHS0oW2zoR4ktVtJfKrrpR" "Kjsv43u8qsLqReoSYU1NTU1NzeuAtDT8w08+yc88dC8HFj2baUYYJfTI2JUpfZVTWc982GKv" "V1RlynZVkApBPhpjXEVYGQqpIGpSOZBhgC9K0JJmEnJIenbSgjDMcMIizdNU57dh9T7afoCV" "ZjoXEYVDY4OIwguskngtsFUxLRFqTeE9Ujjc5XmGgfDg/HTKtXAorXClRUqJkB6pBMJPe9rv" "O5TMLHWDxka/GrzMwyNu8PhX87laYNXU1NTU1Hy7sDIc89OfP84/ePA2DoSezcIwakaMfQoh" "pJMJM1bTSRpslmNMlWNsQKAVYmJRakJhV6mExAUx8dwScZLgkoRsNELj2RsplJZMipI4UAh6" "UHyEhBxDhdcSJTQOiVUKryKECHBYtNYoIXAerLNYU6DiACk8uAKkI2zECOtwLsUrjxB+Gvkk" "QGuF1p7doRuVxr/Y4O6vEkQ3GmtzI+H0SpvWa4FVU1NTU1Pz7cILu33+f8fP8ncfuJ3ZSlDu" "RKyPxoSNhNQp+maERFBKQSduEAlJqBReSwaTHFtmCKWwDEk9qNYMcbuBChWJT0gnIyTQiAPK" "0pPIEu+LqfukQlQUkOYVVbNJ1ZynEhIfBJR5gTIViXR4N63viSLAOYvCor1HSIfDIJzDuYog" "kFhvwHsCIYlDz8mdcufnH9l5sjc2GTfuuYKX7sniZYiv18TJqgVWTU1NTU3N64zHVrb5XzD8" "zPe+kTsakvGlCWWR0Q4ku9UIbTVOOuJ2yJxqI5xjYzjA2RLhJUY4irxEIfBVySgNCVotZCAJ" "kgCtJNJ7FBWaEqUDyHNSHIEPKLrzlEfvxO29CWsMMgpxWUqxtUVRZWjjaKQpAQJsBVrhbQnG" "44XDC4tWl0MdJEghiEKwmur//Hz/eG9shoBiGvr5LUktsGpqampqal6HfHKlz9/+xDP8/e95" "E8fKFi8s77LQaTM2EwZ5j72L+7HO4YFykhMFksWoy8Q7CufwylEMJxjr0M2YNJ2goog4ibDe" "EiUR3pVYkxMHjWncVWXJvCHav4fxnqNMojaFdsRhSNTooBpNtDdgPPlgSLG5jBr3aXhFVeQk" "KkRSTecronFWoIVCa4gbJU9t5VtnNoqdy+LKv0qH0r+K+6oFVk1NTU1NzeteZJ3f5n/8yJP8" "jw/fxVHXZGM7Z64Rs2bHlFVOGDXJihwReYS3zDcTlkpNfzBCqJCdyZjCTBBlgWom2Dwlm4Bs" "tvCBAu8QzqApiSNFGEf4oqJYP0PPddkNlzBKEXZmkMIz302IkxAnNcHeA2TtWdi5hN1Zx+Ua" "J8EjwIc4X6C0xEuHCiVSBXz29M4m0/E0rzR/yr/EY/4ViqlvuOCqBVZNTU1NTc3rmMfOr/O3" "s5z/9q03szTr8dZR+ZhR1gfhccYSaI0IFZN8QDeaxZsxUZkzK0AqTWU9wyzHKEGVWwJnKZ3B" "44miAGxJ5SGKmoSRZpJNUOvHkXoTZEy22cSHEUWzSa/TRSYdbjo8T7y0hyJuMKgqGqbCZCO0" "k+hA4yrQKkBrRxgKcmvt6sCm/Ek/lH8Z4sdfJZ5uJKL86+m81QKrpqampqbmdc6JjT7/4JHn" "+etvvZVj+zrE/YwXXEaaDwiDBnmW0oxaWOEYk1OFFm09h2bnkQa28wnDSQFSEgpBrAGfIpVA" "KYFwDukVYSyJowinIpreMEy3KX2CFCPyymCUwC/MQmuRC1VFZ3aGUAWIfUfJhERvnkOVmkBp" "hA9wLkVoCVKgNV4KyWWB5a4hjjzXdqVeSjxdczjzdcTZq0YdNFpTU1NTU/NNwMog5R/98Qme" "mow5fCBkcS7GKTBY4ljjzIS5uRmCQNHodIg7AVEokFqipWJBRSzomK4OCLwncAJdecLc0iw1" "TRegjSUvcrbznNIanDPgLd7lSF+BM5jxhGpng92TT7Fy6gV2BylF0KRcvIm0vYiRGovHSYHS" "Gus8eW6RQqpuooJrCCFeQjx9rcLJ8xqsIqwFVk1NTU1NzTcJ47ziH//xcf79M8tUoaLSFZ6M" "mVaMUxVpNYTAMzZ9GrNN8irFSQs2Z6nT5Ja5Wea0RmYViQ9JjESOChpWEThI05Td4RDvHOPc" "4ITCuZKynOBsBj6jHG1T7K5R9tdJ184wWFulKD2VDhk1Zim0xuLwl0fsGOspjcNbJ954pLkg" "xFeIHf8K719PbHED8XWt575cgVYLrJqampqamm91jPX8wfF1fuHzZzk+mDDTDWkHOUszirTa" "xIsSo0rG1YCw04JIIRNFFCs6OiA2npYXzAcRXRkQeYH2EmkgSzM8fjpHUAqE0uDE5ZE3hjwb" "Y8sJVTHCO4fLC8Zrl+hvbpFmOWUYk+oWpZGUBozXWDSlsaSF5b7Dzb2H5+MOr9y5upZIejkl" "xSsfEzf4HF/F66kFVk1NTU1NzbcaG6OCL6z0+MRqHxNbblpQHF1q0R8tM9/uMs7HDModgkjT" "bLcZ5WOGVUbuC7rNmLk4pKUlDaWIhMQWObYqCJUmjJvMzC8SxzEqCAnCCI/EmmksBF5MBzrb" "HNIe2fpFfJFjEYxkzNAGGB/gfUhpAKFJc09DyeThO9pHw0Aqbuww+Rts1xI813oOX4Og+pqp" "BVZNTU1NTc03Kx4ev7jFzz2xwnNDONgJ2TsjKeyEpZkl0mLCIN8lakSoZsCAMa19M8hY0Igj" "qjIFUyClIQoVnbjJTKvFnj0HOXLfg3QPH0MnbYSOCHWMuhweGuoA4Q3e5QTKIIoe1bCHKSom" "FeQyQagIJyRCaLybmkd5ZXjj4c6BNxyM9zFtdH+5guh64ouXIby4gVCrBVZNTU1NTU3Ntbmw" "O+KffvoFfu30LnsXO8wkBSE5h7pthlmPYdajO9MljhVCOkQ7ZihzRKeBa0SEzQiVhFTCU9mK" "UAtmb9rHLW++G72wD6dijFcIEYLUeKkuzyN0OFvhywH5YAtbGWTUwpCAVwgPUii8m8oNKQO6" "zTj4vnvnbmmEMryBiOIlxNT1hBNfpYj6uguuWmDV1NTU1NR8C5Aby68/fYl/8fgKPg5Y6mQc" "apfcsdihn24xGu8y251hXA4RgWdsc2QzhKZmLHJ0JyTXFVtpj93+Cr2LF4naTWZuOYqanYdQ" "I5RGSoWxHusqcBZjKpyzlJMxJksRIsCqBtZKcBJvAOcRKKTSNFtNBqUcGefddQTOSzWq8zKE" "1FfjVn1dna06B6umpqampuZbiCdX+6yOcn74zr08vCfmwcUmnoJnN9cI0XSbLXrpNq3OElkx" "obMww3DcI8DQnU0Y5iW5G2FOf4HZTpubjxwiHe6DYoecAqxHaokgQEiojME6j3AOUxT4JEZp" "jS8EUgi8dWggkiFJ4HjyUn/tVz+58VxpvIFrrih8Oc3sL8fJup6Iuvrjb0i5sHawampqampq" "vsXYHOX8wuPn+edfXOXZvEjfdv8Rf+eeiN54BW8K2jpiON7Ca0FhSuJmk1GWIQV0Gg3KNGWy" "tUz/+JMENuWuOw8yf8ttJHv2E7S7BM0WIgohCEAF6KRDNDNL3O6SRAGxMITCoJ0lcpYIRyQd" "26kd/sePX3ymNypeTHS/njDyL2PjJcTVlY+5a4ixb2g2Vu1g1dTU1NTUfCvi4YmVPqd3R4/9" "lbcfCb771v0PL6xnrOxMUD5EItiZDNBK0kzahKVmu79J0ppDCElVGQbry2SnTrN0193cef/t" "HO/MsnPhPG7URxqDQKE96NYsYXuOZpTQrIbE1YQOjg4CiSNWYG3hfvcL2y+s7eQDpgaPvULw" "cJ2PX27/Fdf4uq/hyNUCq6ampqampuYGjDLb+4VPXXp09vu73Tcf6t4nLYyzkoXGLHY8Ijc5" "aSYQ3iEVpPmIqNFFa8EkG1OcO85AaubvuZt777qJM60Wu1t9isyCB60FjVaHbjNGFSnq7DLd" "MqNTGRq+IlCKTgTnjU2fujja5MsHPV/LmXLXET1XiyvHS7teNxJO39DVhLXAqqmpqamp+dZG" "TPJq8E8//Nyv/ZV33r770L7GO9IL43A02WIxnmE1z8l9CgJiEWGDEIFHK4HSip3dVXpVjilS" "GjffxqE9Myx2G4xSj0URaU0zCtGmILu4gd65yIzLaeqKwFniAJqxRyJtUTnD9eMVrhQ7jhtn" "YF0trG4U+XC9gdEvl69KhNUCq6ampqam5lsbB7hRWg5+9o+e+f2T9x65+CO37PveeDzevzIc" "0Q4SxlVBpQQWT6c5g1eKrDAEgaClPemohzt3nGxjGQ7ehlg8RFtpHFCNC+zmGn5r+f/P3nmH" "2XFUaf9X1d03Ts4axVF0kJxzzhnbYBvbYGMwGDCwhIVlYRcWPljSEtYs7JIWsMksOTsnbGPj" "JFu2LNnKeUaTZ27s7qrvj+4ZzYzuvXNHmpFGcr16+rmaG7qrqqur3nrPqXOo2baGyuxOaskR" "Fy5OxCOREMgYzInYiQXNFTUvbxvYzmgVqxiZKScEQ7l+WqUI05SoWIZgGRgYGBgYHNzwABdQ" "aNw7n9v45IrNXVtet3TeqUfOaT5O96UqBr0KNg72IJ0oEQs8rZBoctkUkghRLcl0dRDt78fq" "7SMfW49TNxOyKVTPDqx8hup8mqROU4kmbrk42sWWGmlrtCOpiXnO1Uc1tH2pI70z7/luCYKk" "SpArRWkzIZQXgHTKYQiWgYGBgYHBwQ0F5EOSpQFna/fgjq89/MLvD5/V8NyFbfXHHT+7+rD5" "c5prV2ztpXegB1dbaCyETKA8D6kjOFLjuRkkHhUqR52dw+7YiXRTWNIiLm0iliAiFbblY1ke" "wtIgfaJR0BKOnxWdccTsRMNT6we2sEvFKmYOHEu61BhipYqQtLGESjN6xyJFSNekkjBDsAwM" "DAwMDA5u+GMI1tD877+4pXP9i1s7t7XVVzxxxpIZh50yt/K4mlSuYVu3h9BRbMfCdcHz8tjS" "QgqQfoaoHSE52EOtl0dpTdQRxBKaeCKHn0sh8IlEfYQlsCMSx7aJVVdSGc/bJyxOz3hq/cBW" "CodOGC+uVTnhHKD8qO7Gyd3AwMDAwMBgjwmWG5KssWqRg8Zd3zm4fX3nKx33vxRb9cYT2047" "bG7VkQMDaacjk0PLOHlbgBTkXR9bRvAGB9HkiVdEiEob2/JJVOaprBNY8SjZ3CBKRfA9D2n5" "4HhE4h6R2mqamwakJYXwg0juqkCZCilaY7839u9CBGxPSJWJ5G5gYGBgYGBQNsEaq2AximQF" "EJu7szu+dPeqP5y9pHHNZUubT13aIGev2ybpz0ryQpMKY3P6nqabQepsm7rqOBoP7bhkXY+q" "epvaxirsmIXnDeLlBVk3kl/d5w08+OSGbb97YvNaX2l/TBmKESjF+DsKCxGrcqPBTzqxMgTL" "wMDAwMDg1UmwSilFtufr3D0rO577+8beTW86Zf7ZZ85tPa6zxxeup8lJSXt/DteyiQjJlnQG" "3/KZVSOJ10vqWir464a+3j/duXF9VTLigq/60llvW3d2cFNHemAg5WZHlEuNKcfYVwoQJlXg" "/6UIGFNNpAzBMjAwMDAwePUSrCET4Xh+SsM+Wn2pfM83H3j5L+4ZZK44at5p5D1pxSIMuoKd" "3R69PQpfRclqnwE3T30sjhu18z95ZP1TT6zctjHkGEPpaMSI/49UqPwir2PVq3KUrHLCN+wz" "GIJlYGBgYGBwcGPsLsLxlJ+hzxzXU5n/fWj1fVlP5a86vu20RKUVizuChgZBJm3jpV38nIfj" "RKiscvjdczvWP7Fy+ybAGkOqKHBtNc57PoXNhIWI10jVS02HRjcEy8DAwMDA4ODGkII10kQI" "5SVUtj1f5+746ysPvbCtb+NRbbXzZzQma3JKqnXbet05tcmq49uqmubPqqp+qTPd/dMHXnke" "tBfyC12AYEFxBWsssRqPTBWLk1WsbhQoiyFYBgYGBgYGBnsENYZgMYaEjCQnhXydbK01T61p" "X/PUmvb1QIQgWbMEnKbaePVh8xrqVm/p69++sz9N4DTvsUvBKqVi+WNIlj+GfJUyHRYr73gh" "Hibik7XHRMwQLAMDAwMDg4MbQwqWR+lde6VMcHZ4yPA8QyZAr6Mn09PRs7k3/HwsuZIFCMvQ" "MZZYFSNapXyyxiNZUNz/ygQaNTAwMDAwMNhjKEamyxk/Ynqx90aSraHPrBHHkFI2pG4VU7AY" "Q5D8sHyFiJYah2yNPAqRqUL/H/qOYAod4A3BMjAwMDAwOPgJ1kgFizGkYzyiZY95LeUfJUOy" "VYxgwe4KlF/g8Iq8P5KAjS2rT3F1qxThmhIYgmVgYGBgYHDwEyyP0SbC8UyD5Sha9ojfWCMI" "lhpBsEaaCAXFFayRxMorQarG/q5cBWs8c6EhWAYGBgYGBgYTJlh+EYI1XnLlQoc15v9jD5/x" "zYSFHN1HKleFiJZHeebCQsQQTBwsAwMDAwMDg0nESMJSaqddObGxihEsewRZGmkilGPIlaCw" "aW+sgjX2GI9oFVPgoHSohinLS2gIloGBgYGBwcENPYKoUII82ZSnYI0kU04BslVMwRJjrl/I" "TOiNeXUpvMOwmOlwvNQ5Y/8WTFFgUkOwDAwMDAwMDn6CNURGCElQuTsJS5kJhwjZWBOhHHMU" "MhGOPX8hFculsLmwlGlwPGJVqG2mBIZgGRgYGBgcvBCCSF0LTnUjkdpmnKo6rGQt0nZAgHJd" "vIEu8t07yHdvJ9e5DZVLH2ytoMYQrCFSBMXDMYxVtSbihzUewRoq08iyjQ3XMJJkjSVc/jiE" "q1jQVCjuj2XCNBgcoGOcZaOVAj2+Eitk8Dxq5ZuGK95ICCnRvmfawsBgDCJ1LVQsPJqqw04h" "MXsJkdoW7Ko6hLRACIQYHftSawVa4+fS5Lu2k92+jv6XHmfg5afIbHn5YGiSkQRpLKkaT80q" "5oc1cgehDP+eCMEqpGAVI1nF/i5XzaJAvaeMWBmCZbBPUL3sDBrPuJp460L8XJpcxyZS61cw" "uO45cu0b8XMZpBMJBsTFx1Ex/0jiM+YjnCi9y+9n2x+/hcqmDv4Job6VhlOvJDlvKTIaxxvo" "CVbVPe24Az14A91IJ0akoZX4jAXEZy1CRuJktr7Mjr98l9SGF01nM3jVL+KqjziDuhMvo2rJ" "8URqm4PZ0/fQykN7Hhp3nHWLTayljfjMRdSdcDFeqo/Uhhfoefpuep+5l3xPx4FMsEbGiRpr" "OpMF3i8UMd0uomLJEX8PkapCsbCKmQlHEiWPwg7vI9WrQv5XPuMHUZ2IarXXxEtoXfocQgjz" "5B4cuB24aV9esOmcNzD3xn9DSAvl5hFCIKyA06t8Fm+wB2+wFxlL4lTWYcUrhgdEABlP0n73" "D9hw+8cP6htTe+z5zL3xE0TqZgR112pYoUKIYILwPYSQCDsCWgXvaY0VS5DevIqV/34tfqrf" "9HKDVyWxqj/lCprOupZk27JwvMlNirorpIVwIghpke/eTvdTd9Fx30/I7lh/oDXTvwCfA6oY" "vbtvrNpkFTnsAiqVPeazckyEhUI1UEDBKqZilToKmQ0LKVwUUfLGJVjj8SWjYBnso1FPUHfC" "xQhh4WcGd/VWNzf8uV1Zh13VEBAGpfDHKFXKc2k4/XV0//0v9K987KBspqqlp7HgHV9CWA5+" "uhhBEiDGtF8IP+URqW8l2jSH9PoXTL8zeFWh8pATmfnaf6BqyfFopVBuDvTkWXy08tG5TDBZ" "VtTScsGbaTjlCnY+/Eu2/+k7eIM9B5KCNZLUFFOSKKH8WBT2z/JHvBYiWOUoWHoPSFahtDrl" "KFf7LCaWIVgGU/Q4a1IbXqBm2Zn4udTug57W468wtUJKm5ZL3sbAqscDH66DCE51Q6Dw2RFU" "Plt6bNSjV+xCWiAl0o6Qbd+Ee+CaLgwMJgwrlmTma99H09nXIZwIfjY9vhJlO4HvlRDDi5ah" "sWhIndC+i/a8gnOv9j38zCDSiTHj0ndQvfQ01n/3X0htOCAWNkMV8keQnnLCGIy3i3CsmXCs" "iVCMo2AV2004HskqZiosFLKhFKGa0qjuhmAZTBl23Pl9ErOWUHXoiYFpKxyktOeW7cDu5zNU" "HXoSVYefQt+KRw6q9mm94j3EZ8zHTw+U9X1hRxCWTb5rG/nedlQ+i9vXScd9P8btNQTL4NWB" "eOtC5t38GSoXH4fKDqJChWn3B0YgnShCWrgD3WTXryWzbS3Z9g14gz2BST1U0p2aJuIzF5KY" "vYRo4xyk7aDymYKLOq18/HQ/8VlLWPLB77L6K28jtX7FgahgDflNjSU7coxaZZUgW0Pq1RDB" "8sskWIXInKK0w3ux/08kXEO5hGpSSJchWAZTBre3g5f/8xYqFhxN5ZLjiTbPJdY4h/ishdjJ" "mqID2FilS1o2TWe/4aAiWMkFR9Jw6mvxM+U58MtoguyODey487v0PvcgXn+X2WVp8KpD1WEn" "0/a2LxCtaylhUgcZiYHWDLz8NN1P/JG+Fx8l17F5fGUsUUXFwqNoOOVKao46GxlNFA3ZoHJp" "7Moa5r7pk6z6/A3Fid70ghpDdMQIUlVM3RlpFpTsnth5pHP7SAXLZ/dI7oWCjcLoiO5D//co" "nQi6lGI1XoqcfZKP0BAsg6ldNvk+Ay8/xcDLTwVPl2UTbZ5H0xlX03jWdQjbQXuld/b4uQzV" "S0+ncvGxDLz89MGhXl1yC1Y0vpvfWcFBP1ZB34uPsv67HyXfvd10KoNXJSoPPYmF77oNK15Z" "9LkRQiKjcQbXPc+2P3yD3uUPlBUaZnisSffT9/zD9D3/MBULj2LWVf9I1aEnF3ZzAPxshuS8" "pVQvPY2ep+85UBSssWSrmJI1Vr1iBMEaSaxUEQVL7gHBUiWUrGJHMWf2cmJgTWngUWkeW4N9" "S7g8stvWsOlnn2ftN96PymWGdxYW/5FCRCK0XPiWg6INKhYfR/URZ+KPu+LVWLEkfSsfZc3X" "32PIlcGrFvFZi1jwji9jJaoCR/ZC5MpywLLY9sdvseoLb6L32fsmRK7GYnDNcl7+ytvY/pfv" "IJ3YbrGzhp5RaTnEmucdMEMwu+fsK0RqhghLoZAJYwN/ukB+zP/LPdwCryOPsdcrFv+qWAT6" "sSRuUgnUeBhXwdJam6f7IMB0DLfR+9yDbPnVfzL3xo+P6/CuchmqjzjzoFCxWi58C9KJjqte" "STtKesvLrPvWPw3vxDQweLXBilXQ9pbPEKluwC9irhO2g3bzbLjj3+h6/I+Tdm3l5tn88//A" "z2WYdeU/FHlm9YGwAWesYiMY7Y9VTGGSY4jZSOIyNtTDkIo19H6hHYTFgo1SgvCNVbOK/T32" "94XqXahNpozzGAXLYL9i58O/ILVhJcKJjsf0kU6U5gNcxao+/FRqjzyr6EQxekVu037PHbh9" "O01HMXjVouWSt1Gx6Nji5Mqy0V6eNd/4wKSSq5HY9ruv0/PMvcho4kBvzlJKTzFSM1bBKuRs" "PlLNKvb/QurW0HfyY34z3s5Bj9JmwUIJn2F8R/dJVZQMwTLYv0+75zKw6gmkNb47oJ/LUHPE" "mVQsOvbArKyQtFz8VrDs8WP1CIHyXNIbXzKdxOBVi2jTHJrOvh5VNAyDQNgOm3/+Rfqef2gK" "ByrN5l9+CT/TH4RIGXF95Xt4qd4DmWxBaZNhKV+oYmZDj8KmQ7fAkS9AxEaep5zo7WPzD1Lk" "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" "HG9xjM6OAAAAAElFTkSuQmCC") getMantis_logo_splashData = Mantis_logo_splash.GetData getMantis_logo_splashImage = Mantis_logo_splash.GetImage getMantis_logo_splashBitmap = Mantis_logo_splash.GetBitmap #----------------------------------------------------------------------- sl = PyEmbeddedImage( "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") getslData = sl.GetData getslImage = sl.GetImage getslBitmap = sl.GetBitmap getslIcon = sl.GetIcon ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/mantis.py0000664000175000017500000006466314700316175017432 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2013 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . import sys import os import numpy as np import matplotlib from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas #from matplotlib.backends.backend_qt5agg import FigureCanvas from matplotlib.figure import Figure from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable import matplotlib.colorbar as colorbar #Internal imports from . import data_struct from . import data_stack from . import analyze from . import nnma from . import henke from . import tomo_reconstruction from . import file_plugins from .file_plugins import file_xrm from .file_plugins import file_bim from .file_plugins import file_dataexch_hdf5 from .file_plugins import file_ncb from .file_plugins import file_json from .file_plugins import file_tif from .file_plugins import file_stk from .file_plugins import file_csv PlotH = 4.0 PlotW = PlotH*1.61803 #---------------------------------------------------------------------- def save_keyeng(key_engs, odir, filename, stk, anlz, png, pdf, svg): SaveFileName = os.path.join(odir,filename) matplotlib.rcParams['pdf.fonttype'] = 42 fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() odtotal = stk.od3d.sum(axis=0) odtotal = odtotal.sum(axis=0)/(stk.n_rows*stk.n_cols) odtotal /= odtotal.max()/0.7 specplot = axes.plot(stk.ev,odtotal) for i in range(len(key_engs)): axes.axvline(x=key_engs[i], color = 'g', alpha=0.5) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') if png == 1: ext = 'png' fileName_img = SaveFileName+"_keyengs."+ext fig.savefig(fileName_img, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_img = SaveFileName+"_keyengs."+ext fig.savefig(fileName_img, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_img = SaveFileName+"_keyengs."+ext fig.savefig(fileName_img, pad_inches = 0.0) #Save text file with list of energies textfilepath = SaveFileName+'_keyenergies.csv' f = open(textfilepath, 'w') print('********************* Key Energies ********************', file=f) for i in range(len(key_engs)): print('%.6f' %(key_engs[i]), file=f) f.close() return #---------------------------------------------------------------------- def save_spa(odir, filename, stk, anlz, png, pdf, svg): SaveFileName = os.path.join(odir,filename) matplotlib.rcParams['pdf.fonttype'] = 42 colors=['#FF0000','#000000','#FFFFFF'] spanclrmap=matplotlib.colors.LinearSegmentedColormap.from_list('spancm',colors) for i in range (anlz.n_target_spectra): #Save composition maps if anlz.pca_calculated == 0: tsmapimage = anlz.target_svd_maps[:,:,i] else: tsmapimage = anlz.target_pcafit_maps[:,:,i] fig = matplotlib.figure.Figure(figsize =(PlotH, PlotH)) canvas = FigureCanvas(fig) fig.clf() axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) min_val = np.min(tsmapimage) max_val = np.max(tsmapimage) bound = np.max((np.abs(min_val), np.abs(max_val))) im = axes.imshow(tsmapimage, cmap=spanclrmap, vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") if png == 1: ext = 'png' fileName_img = SaveFileName+"_TSmap_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_img = SaveFileName+"_TSmap_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_img = SaveFileName+"_TSmap_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) #Save spectra for i in range (anlz.n_target_spectra): tspectrum = anlz.target_spectra[i, :] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() line1 = axes.plot(stk.ev,tspectrum, color='black', label = 'Raw data') if anlz.pca_calculated == 1: tspectrumfit = anlz.target_pcafit_spectra[i, :] diff = np.abs(tspectrum-tspectrumfit) line2 = axes.plot(stk.ev,tspectrumfit, color='green', label = 'Fit') line3 = axes.plot(stk.ev,diff, color='grey', label = 'Abs(Raw-Fit)') fontP = matplotlib.font_manager.FontProperties() fontP.set_size('small') axes.legend(loc=4, prop = fontP) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') if png == 1: ext = 'png' fileName_spec = SaveFileName+"_Tspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if pdf == 1: ext = 'pdf' fileName_spec = SaveFileName+"_Tspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if svg == 1: ext = 'svg' fileName_spec = SaveFileName+"_Tspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) fileName_spec = SaveFileName+"_Tspectrum_" +str(i+1)+".csv" cname = "Tspectrum_" +str(i+1) stk.write_csv(fileName_spec, stk.ev, tspectrum, cname = cname) return #---------------------------------------------------------------------- def MakeColorTable(): maxclcolors = 11 colors_i = np.linspace(0, maxclcolors, maxclcolors+1) colors=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', '#FF470A','#33FFFF','#006600','#CCCC99','#993300', '#000000'] clusterclrmap1=matplotlib.colors.LinearSegmentedColormap.from_list('clustercm',colors) bnorm1 = matplotlib.colors.BoundaryNorm(colors_i, clusterclrmap1.N) colors_i = np.linspace(0,maxclcolors+2,maxclcolors+3) #use black color for clusters > maxclcolors, the other 2 colors are for background colors2=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', '#FF470A','#33FFFF','#006600','#CCCC99','#993300', '#000000','#FFFFFF','#EEEEEE'] clusterclrmap2=matplotlib.colors.LinearSegmentedColormap.from_list('clustercm2',colors2) bnorm2 = matplotlib.colors.BoundaryNorm(colors_i, clusterclrmap2.N) return clusterclrmap1, bnorm1, clusterclrmap2, bnorm2 #---------------------------------------------------------------------- #If png_pdg = 1 save png, if =2 save pdf, if =3 save svg def SaveScatt(SaveFileName, stk, anlz, png_pdf = 1): colors=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', '#FF470A','#33FFFF','#006600','#CCCC99','#993300', '#000000'] od_reduced = anlz.pcaimages[:,:,0:anlz.numsigpca] od_reduced = np.reshape(od_reduced, (stk.n_cols*stk.n_rows,anlz.numsigpca), order='F') clindices = anlz.cluster_indices clindices = np.reshape(clindices, (stk.n_cols*stk.n_rows), order='F') if png_pdf == 1: ext = 'png' elif png_pdf == 2: ext = 'pdf' elif png_pdf == 3: ext = 'svg' nplots = 0 for ip in range(anlz.numsigpca): for jp in range(anlz.numsigpca): if jp >= (ip+1): nplots = nplots+1 nplotsrows = np.ceil(nplots/2) plotsize = 2.5 if nplots > 1 : fig = matplotlib.figure.Figure(figsize =(6.0,plotsize*nplotsrows)) fig.subplots_adjust(wspace = 0.4, hspace = 0.4) else: fig = matplotlib.figure.Figure(figsize =(3.0,2.5)) fig.subplots_adjust(bottom = 0.2, left = 0.2) canvas = FigureCanvas(fig) #axes = fig.gca() matplotlib.rcParams['font.size'] = 6 pplot = 1 for ip in range(anlz.numsigpca): for jp in range(anlz.numsigpca): if jp >= (ip+1): x_comp = od_reduced[:,ip] y_comp = od_reduced[:,jp] if nplots > 1 : axes = fig.add_subplot(nplotsrows,2, pplot) else: axes = fig.add_subplot(1,1,1) pplot = pplot+1 for i in range(anlz.nclusters): thiscluster = np.where(clindices == i) axes.plot(x_comp[thiscluster], y_comp[thiscluster],'.',color=colors[i],alpha=0.5) axes.set_xlabel('Component '+str(ip+1)) axes.set_ylabel('Component '+str(jp+1)) fileName_sct = SaveFileName+"_CAscatterplots."+ext matplotlib.rcParams['pdf.fonttype'] = 42 fig.savefig(fileName_sct) #---------------------------------------------------------------------- def save_ca(odir, filename, stk, anlz, png, pdf, svg): clusterclrmap1, bnorm1, clusterclrmap2, bnorm2 = MakeColorTable() maxclcolors = 11 colors=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', '#FF470A','#33FFFF','#006600','#CCCC99','#993300', '#000000'] SaveFileName = os.path.join(odir,filename) matplotlib.rcParams['pdf.fonttype'] = 42 #Save image with composite cluster indices fig = matplotlib.figure.Figure(figsize = (float(stk.n_rows)/30, float(stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(anlz.cluster_indices, cmap=clusterclrmap1, norm=bnorm1) axes.axis("off") if png == 1: ext = 'png' fileName_caimg = SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=300, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_caimg = SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=300, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_caimg = SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=300, pad_inches = 0.0) #Save individual cluster images for i in range (anlz.nclusters): indvclusterimage = np.zeros((anlz.stack.n_cols, anlz.stack.n_rows))+20. ind = np.where(anlz.cluster_indices == i) colorcl = min(i,9) indvclusterimage[ind] = colorcl fig = matplotlib.figure.Figure(figsize =(float(stk.n_rows)/30, float(stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(indvclusterimage, cmap=clusterclrmap2, norm=bnorm2) axes.axis("off") if png == 1: ext = 'png' fileName_img = SaveFileName+"_CAimg_" +str(i+1)+"."+ext fig.savefig(fileName_img, dpi=300, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_img = SaveFileName+"_CAimg_" +str(i+1)+"."+ext fig.savefig(fileName_img, dpi=300, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_img = SaveFileName+"_CAimg_" +str(i+1)+"."+ext fig.savefig(fileName_img, dpi=300, pad_inches = 0.0) for i in range (anlz.nclusters): clusterspectrum = anlz.clusterspectra[i, ] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() if i >= maxclcolors: clcolor = colors[maxclcolors-1] else: clcolor = colors[i] specplot = axes.plot(anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') if png == 1: ext = 'png' fileName_spec = SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if pdf == 1: ext = 'pdf' fileName_spec = SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if svg == 1: ext = 'svg' fileName_spec = SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) #Save all spectra in one plot fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() for i in range (anlz.nclusters): clusterspectrum = anlz.clusterspectra[i-1, ]/np.amax(anlz.clusterspectra[i-1, ]) if i >= maxclcolors: clcolor = colors[maxclcolors-1] else: clcolor = colors[i] specplot = axes.plot(anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') if png == 1: ext = 'png' fileName_spec = SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) if pdf == 1: ext = 'pdf' fileName_spec = SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) if svg == 1: ext = 'svg' fileName_spec = SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) for i in range (anlz.nclusters): clusterspectrum = anlz.clusterspectra[i, ] fileName_spec = SaveFileName+"_CAspectrum_" +str(i+1)+".csv" cname = "CAspectrum_" +str(i+1) stk.write_csv(fileName_spec, anlz.stack.ev, clusterspectrum, cname=cname) if png: SaveScatt(SaveFileName, stk, anlz, png_pdf = 1) if pdf: SaveScatt(SaveFileName, stk, anlz, png_pdf = 2) if svg: SaveScatt(SaveFileName, stk, anlz, png_pdf = 3) return #---------------------------------------------------------------------- def save_pca(odir, filename, stk, anlz, png, pdf, svg): SaveFileName = os.path.join(odir,filename) matplotlib.rcParams['pdf.fonttype'] = 42 #Save evals evalmax = np.min([stk.n_ev, 40]) pcaevals = anlz.eigenvals[0:evalmax] fig = Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() evalsplot = axes.semilogy(np.arange(1,evalmax+1), pcaevals,'b.') axes.set_xlabel('Principal Component') axes.set_ylabel('Log(Eigenvalue)') if png == 1: ext = 'png' fileName_evals = SaveFileName+"_PCAevals."+ext fig.savefig(fileName_evals, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_evals = SaveFileName+"_PCAevals."+ext fig.savefig(fileName_evals, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_evals = SaveFileName+"_PCAevals."+ext fig.savefig(fileName_evals, pad_inches = 0.0) for i in range(10): pcaimage = anlz.pcaimages[:,:,i] fig = Figure(figsize =(PlotH*1.15, PlotH)) canvas = FigureCanvas(fig) axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) bound = anlz.pcaimagebounds[i] im = axes.imshow(pcaimage, cmap=matplotlib.colormaps["seismic_r"], vmin = -bound, vmax = bound) cbar = colorbar(im, orientation='vertical', cax=ax_cb) axes.axis("off") if png == 1: ext = 'png' fileName_img = SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if pdf == 1: ext = 'pdf' fileName_img = SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if svg == 1: ext = 'svg' fileName_img = SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) for i in range(10): pcaspectrum = anlz.eigenvecs[:,i] fig = Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(stk.ev, pcaspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') if png == 1: ext = 'png' fileName_spec = SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if pdf == 1: ext = 'pdf' fileName_spec = SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if svg == 1: ext = 'svg' fileName_spec = SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) for i in range (10): pcaspectrum = anlz.eigenvecs[:,i] fileName_spec = SaveFileName+"_PCAspectrum_" +str(i+1)+".csv" cname = "PCAspectrum_" +str(i+1) stk.write_csv(fileName_spec, stk.ev, pcaspectrum, cname=cname) return #---------------------------------------------------------------------- def batch_mode(): verbose = 1 settingsfile = 'Mantis_batch_settings.txt' version = '2.0.5' wdir = '' outdir = 'MantisResults' filename = '' save_hdf5 = 0 align_stack = 0 i0_file = '' i0_histogram = 0 run_pca = 0 n_spca = 4 run_ca = 0 nclusters = 5 ca_thickness = 0 run_sa = 0 sa_spectra = [] sa_use_clspectra = 0 run_keyengs = 0 kengs_thresh = 0.10 save_png = 1 save_pdf = 0 save_svg = 0 try: f = open(settingsfile, 'rt') for line in f: if ':' in line : slist = line.split(':') tag = slist[0] value = ':'.join(slist[1:]) if tag == 'VERSION': version = float(value) elif tag == 'WORK_DIR' : wdir = value.strip() elif tag == 'OUTPUT_DIR_NAME' : outdir = value.strip() elif tag == 'FILENAME' : filename = value.strip() elif tag == 'ALIGN_STACK' : align_stack = value.strip() elif tag == 'I0_FILE' : i0_file = value.strip() elif tag == 'I0_HISTOGRAM' : i0_histogram = int(value) elif tag == 'SAVE_HDF5' : save_hdf5 = int(value) elif tag == 'RUN_PCA' : run_pca = int(value) elif tag == 'N_SPCA' : n_spca = int(value) elif tag == 'RUN_CLUSTER_ANALYSIS' : run_ca = int(value) elif tag == 'N_CLUSTERS' : nclusters = int(value) elif tag == 'THICKNESS_CORRECTION' : ca_thickness = int(value) elif tag == 'RUN_SPECTRAL_ANALYSIS' : run_sa = int(value) elif tag == 'SA_SPECTRUM' : spname = value.strip() if len(spname) > 0 : sa_spectra.append(spname) elif tag == 'SA_USE_CA_SPECTRA' : sa_use_clspectra = int(value) elif tag == 'RUN_KEY_ENGS' : run_keyengs = int(value) elif tag == 'KE_THRESHOLD' : kengs_thresh = float(value) elif tag == 'SAVE_PNG' : save_png = int(value) elif tag == 'SAVE_PDF' : save_pdf = int(value) elif tag == 'SAVE_SVG' : save_svg = int(value) f.close() except: print('Error: Could not read in Mantis_batch_settings.txt.') return wdir = os.path.normpath(wdir) if verbose: print('Version: ', version) print('Working directory: ', wdir) if not os.path.exists(wdir): print('Error - Directory ', wdir, ' does not exist. Please specify working directory.') return outdir = os.path.join(wdir, outdir) if not os.path.exists(outdir): os.makedirs(outdir) if not os.path.exists(outdir): print('Error: Did not find and could not create a new output directory.') return if save_png == 1: print("Save .png images") if save_pdf == 1: print("Save .pdf images") datastruct = data_struct.h5() stk = data_stack.data(datastruct) anlz = analyze.analyze(stk) print('Reading file:', filename) basename, extension = os.path.splitext(filename) filepath = os.path.join(wdir, filename) supported_filters = file_plugins.supported_filters filter_list = file_plugins.filter_list action = 'read' data_type = 'stack' print (filter_list[action][data_type]) plugin = file_plugins.identify(filepath) file_plugins.load(filepath, stack_object=stk, plugin=plugin, selection=None, json=None) if align_stack: print('Aligning the stack') xshifts = np.zeros((stk.n_ev)) yshifts = np.zeros((stk.n_ev)) referenceimage = stk.absdata[:,:,0].copy() for i in range(stk.n_ev): img2 = stk.absdata[:,:,i] if i==0: xshift, yshift, ccorr = stk.register_images(referenceimage, img2, have_ref_img_fft = False) else: xshift, yshift, ccorr = stk.register_images(referenceimage, img2, have_ref_img_fft = True) # #Limit the shifts to MAXSHIFT chosen by the user # if (self.maxshift > 0): # if (abs(xshift) > self.maxshift): # xshift = npy.sign(xshift)*self.maxshift # if (abs(yshift) > self.maxshift): # yshift = npy.sign(yshift)*self.maxshift xshifts[i] = xshift yshifts[i] = yshift #Apply shifts for i in range(stk.n_ev): img = stk.absdata[:,:,i] if (abs(xshifts[i])>0.02) or (abs(yshifts[i])>0.02): shifted_img = stk.apply_image_registration(img, xshifts[i], yshifts[i]) stk.absdata[:,:,i] = shifted_img if datastruct.spectromicroscopy.normalization.white_spectrum is not None: print("I0 loaded") else: print("Loading I0") if i0_histogram == 1: print('Getting I0 from the histogram') stk.calc_histogram() averagefluxmax = np.max(stk.histogram) histmin = 0.98*averagefluxmax histmax = averagefluxmax stk.i0_from_histogram(histmin, histmax) elif len(i0_file) > 0: print('Reading I0 from file:', i0_file) i0basename, i0extension = os.path.splitext(i0_file) i0filepath = os.path.join(wdir, i0_file) stk.read_stk_i0(i0filepath, i0extension) else: print("Please either set I0_HISTOGRAM to 1 to calculate I0 or specify I0 file.") return if datastruct.spectromicroscopy.normalization.white_spectrum is None: print('Error: I0 not loaded') return if save_hdf5 == 1: fnameh5 = os.path.join(wdir,basename+'_MantisBatch.hdf5') stk.write_h5(fnameh5, data_struct) print('Saving data to HDF5 file:', fnameh5) pca_calculated = 0 if run_pca == 1: print("Running PCA Analysis") anlz.calculate_pca() print("Chosen number of significant components:", n_spca) print("Suggested number of significant components:", anlz.numsigpca) pca_calculated = 1 anlz.numsigpca = n_spca save_pca(outdir, filename, stk, anlz, save_png, save_pdf, save_svg) ca_calculated = 0 if run_ca == 1: if pca_calculated == 0: anlz.calculate_pca() print("Running Cluster Analysis") print("Number of clusters", nclusters) if ca_thickness == 1: print("Thickness correction enabled") nclusters = anlz.calculate_clusters(nclusters, ca_thickness) ca_calculated = 1 save_ca(outdir, filename, stk, anlz, save_png, save_pdf, save_svg) if run_sa == 1: print("Running Spectral Analysis") if len(sa_spectra) > 0: print("Loading spectra:", sa_spectra) for i in range(len(sa_spectra)): sppath = os.path.join(wdir, sa_spectra[i]) anlz.read_target_spectrum(filename=sppath) if sa_use_clspectra == 1: if ca_calculated == 1: print("Loading cluster spectra") anlz.add_cluster_target_spectra() else: print("Please set RUN_CLUSTER_ANALYSIS to 1 to calculate cluster spectra.") if anlz.n_target_spectra > 1: save_spa(outdir, filename, stk, anlz, save_png, save_pdf, save_svg) if run_keyengs == 1: if pca_calculated == 0: anlz.calculate_pca() print("Finding key energies") print("Threshold for finding key energies:", kengs_thresh) key_engs= anlz.calc_key_engs(kengs_thresh) save_keyeng(key_engs, outdir, filename, stk, anlz, save_png, save_pdf, save_svg) if (save_hdf5 == 1) and (pca_calculated == 1) : fnameh5 = os.path.join(wdir,basename+'_MantisBatch.hdf5') stk.write_results_h5(fnameh5, data_struct, anlz) print("Finished doing Mantis analysis") return """ ------------------------------------------------------------------------------------------------""" def main(): verbose = True print('Running Mantis in batch mode.') batch_mode() sys.exit() if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/mantis_qt.py0000664000175000017500000240627414700316175020136 0ustar00wattswatts# coding: utf8 # # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2013 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division from __future__ import print_function from __future__ import absolute_import import sys import os import re import time import copy import numpy as np import getopt from PyQt5 import QtCore, QtGui, QtWidgets, uic from PyQt5.QtCore import Qt, QCoreApplication, pyqtSignal, pyqtSlot if hasattr(Qt, "HighDpiScaleFactorRoundingPolicy"): QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) QtWidgets.QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, False) from PIL import Image from scipy import ndimage from scipy.stats import linregress from scipy.interpolate import interp1d, griddata from skimage.registration import phase_cross_correlation from skimage import img_as_ubyte, exposure, filters from importlib.metadata import version as version_check from packaging.version import parse as parse_version from queue import SimpleQueue, Empty import threading from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_qt5agg import ( NavigationToolbar2QT as NavigationToolbar) from matplotlib.figure import Figure import matplotlib from mpl_toolkits.axes_grid1 import make_axes_locatable from matplotlib.widgets import LassoSelector matplotlib.interactive( True ) matplotlib.rcParams['svg.fonttype'] = 'none' import pyqtgraph as pg import pyqtgraph.exporters from lxml import etree #Internal imports from . import data_struct from . import data_stack from . import analyze from . import nnma from . import henke from . import tomo_reconstruction from .helpers import resource_path from .helpers import PDFExporter from . import file_plugins from .file_plugins import file_xrm from .file_plugins import file_bim from .file_plugins import file_dataexch_hdf5 from .file_plugins import file_ncb from .file_plugins import file_json from .file_plugins import file_tif from .file_plugins import file_stk from .file_plugins import file_csv from .__init__ import __version__ as version welcome_string = "Welcome to MANTiS {0}".format(version) print("="*len(welcome_string)) print(welcome_string) print("="*len(welcome_string)) from .helpers import check_for_updates check_for_updates(version) print("\nPlease report issues to https://github.com/mlerotic/spectromicroscopy/issues \n") ## Global Stylesheet qsspath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'stylesheet_global.qss') Winsizex = 1620 Winsizey = 920 PlotH = 4.0 PlotW = PlotH*1.61803 ImgDpi = 40 verbose = False showtomotab = 1 #showmaptab = 1 def rebin(a, shape): sh = shape[0],a.shape[0]//shape[0],shape[1],a.shape[1]//shape[1] return a.reshape(sh).mean(-1).mean(1) #---------------------------------------------------------------------- class common: def __init__(self): self.stack_loaded = 0 self.stack_4d = 0 self.i0_loaded = 0 self.pca_calculated = 0 self.pca4D_calculated = 0 self.cluster_calculated = 0 self.spec_anl_calculated = 0 self.spec_anl4D_calculated = 0 self.ica_calculated = 0 self.xpf_loaded = 0 self.white_scale_bar = 0 self.path = '' self.filename = '' self.font = '' """ ------------------------------------------------------------------------------------------------""" class PageTomo(QtWidgets.QWidget): def __init__(self, common, data_struct, stack, anlz): super(PageTomo, self).__init__() self.initUI(common, data_struct, stack, anlz) #---------------------------------------------------------------------- def initUI(self, common, data_struct, stack, anlz): self.data_struct = data_struct self.stack = stack self.com = common self.anlz = anlz self.tr = tomo_reconstruction.Ctomo(self.stack) self.theta = [] self.algonames = ['Compressed Sensing', 'SIRT'] self.algo = 0 self.fulltomorecdata = [] self.ncomponents = 0 self.tomo_calculated = 0 self.full_tomo_calculated = 0 self.energiesloaded = 0 self.datanames = [] self.haveROI = 0 self.ROIarray = [] self.ROIvol = [] self.select1 = 0 self.icomp = 0 self.islice = 0 self.maxIters = 10 self.beta = 0.5 self.engpar = 0.001 self.useengreg = 0 self.samplethick = 0 self.nonnegconst = 1 self.nprocessors = 6 #panel 1 sizer1 = QtWidgets.QGroupBox('Tomo Data') vbox1 = QtWidgets.QVBoxLayout() self.button_spcomp = QtWidgets.QPushButton('Load Tomo Data for Spectral Components') self.button_spcomp.clicked.connect( self.OnLoadTomoComponents) self.button_spcomp.setEnabled(False) vbox1.addWidget(self.button_spcomp) self.button_engdata = QtWidgets.QPushButton('Load Tomo Data for each Energy') self.button_engdata.clicked.connect( self.OnLoadTomoEng) self.button_engdata.setEnabled(False) vbox1.addWidget(self.button_engdata) self.button_expdata = QtWidgets.QPushButton('Export Tomo Data as .mrc') self.button_expdata.clicked.connect( self.OnExportData) self.button_expdata.setEnabled(False) vbox1.addWidget(self.button_expdata) line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) vbox1.addStretch(1) vbox1.addWidget(line) vbox1.addStretch(1) self.button_loadmrc = QtWidgets.QPushButton('Load Single Tomo Dataset') self.button_loadmrc.clicked.connect( self.OnLoadSingleMrc) vbox1.addWidget(self.button_loadmrc) sizer1.setLayout(vbox1) #panel 2 sizer2 = QtWidgets.QGroupBox('Tomo Reconstruction') vbox2 = QtWidgets.QVBoxLayout() self.button_calc1 = QtWidgets.QPushButton( 'Calculate One Dataset') self.button_calc1.clicked.connect( self.OnCalcTomo1) self.button_calc1.setEnabled(False) vbox2.addWidget(self.button_calc1) hbox21 = QtWidgets.QHBoxLayout() tc1 = QtWidgets.QLabel(self) hbox21.addWidget(tc1) tc1.setText('Choose Tomo Dataset:') self.combonames = QtWidgets.QComboBox(self) self.combonames.activated[int].connect(self.OnSelect1Comp) hbox21.addWidget(self.combonames) vbox2.addLayout(hbox21) hbox22 = QtWidgets.QHBoxLayout() tc2 = QtWidgets.QLabel(self) hbox22.addWidget(tc2) tc2.setText('Binning: ') self.combobin = QtWidgets.QComboBox(self) self.combobin.addItems(['1','2','4','8']) hbox22.addWidget(self.combobin) vbox2.addLayout(hbox22) self.button_calcall = QtWidgets.QPushButton( 'Calculate All Datasets') self.button_calcall.clicked.connect( self.OnCalcTomoFull) self.button_calcall.setEnabled(False) vbox2.addWidget(self.button_calcall) self.button_save = QtWidgets.QPushButton( 'Save as .mrc') self.button_save.clicked.connect( self.OnSave) self.button_save.setEnabled(False) vbox2.addWidget(self.button_save) self.button_saveall = QtWidgets.QPushButton( 'Save All as .mrc') self.button_saveall.clicked.connect( self.OnSaveAll) self.button_saveall.setEnabled(False) vbox2.addWidget(self.button_saveall) line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) vbox2.addStretch(1) vbox2.addWidget(line) vbox2.addStretch(1) # hbox21 = QtWidgets.QHBoxLayout() # tc1 = QtWidgets.QLabel(self) # hbox21.addWidget(tc1) # tc1.setText('Choose Tomo Dataset: ') self.comboalgos = QtWidgets.QComboBox(self) self.comboalgos.activated[int].connect(self.OnSelectAlgo) self.comboalgos.addItems(self.algonames) vbox2.addWidget(self.comboalgos) hbox22 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText('Number of iterations') hbox22.addWidget(text1) hbox22.addStretch(1) self.ntc_niterations = QtWidgets.QLineEdit(self) self.ntc_niterations.setFixedWidth(65) self.ntc_niterations.setValidator(QtGui.QIntValidator(1, 99999, self)) self.ntc_niterations.setAlignment(QtCore.Qt.AlignRight) self.ntc_niterations.setText(str(self.maxIters)) hbox22.addWidget(self.ntc_niterations) vbox2.addLayout(hbox22) hbox23 = QtWidgets.QHBoxLayout() self.tc_par = QtWidgets.QLabel(self) self.tc_par.setText("CS Parameter Beta") hbox23.addWidget(self.tc_par) self.ntc_beta = QtWidgets.QLineEdit(self) self.ntc_beta.setFixedWidth(65) self.ntc_beta.setValidator(QtGui.QDoubleValidator(0, 99999, 2, self)) self.ntc_beta.setAlignment(QtCore.Qt.AlignRight) hbox23.addStretch(1) self.ntc_beta.setText(str(self.beta)) hbox23.addWidget(self.ntc_beta) vbox2.addLayout(hbox23) hbox25 = QtWidgets.QHBoxLayout() self.cb_ereg = QtWidgets.QCheckBox('CS Energy Reg Parameter', self) self.cb_ereg.setChecked(False) self.cb_ereg.stateChanged.connect(self.OnCBEngReg) hbox25.addWidget(self.cb_ereg) self.ntc_ereg = QtWidgets.QLineEdit(self) self.ntc_ereg.setFixedWidth(65) self.ntc_ereg.setValidator(QtGui.QDoubleValidator(0, 99999, 2, self)) self.ntc_ereg.setAlignment(QtCore.Qt.AlignRight) hbox25.addStretch(1) self.ntc_ereg.setText(str(self.engpar)) self.ntc_ereg.setEnabled(False) hbox25.addWidget(self.ntc_ereg) vbox2.addLayout(hbox25) hbox24 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText('Sample Thickness') hbox24.addWidget(text1) hbox24.addStretch(1) self.ntc_samplethick = QtWidgets.QLineEdit(self) self.ntc_samplethick.setFixedWidth(65) self.ntc_samplethick.setValidator(QtGui.QDoubleValidator(0, 99999, 2, self)) self.ntc_samplethick.setAlignment(QtCore.Qt.AlignRight) self.ntc_samplethick.setText(str(self.samplethick)) hbox24.addWidget(self.ntc_samplethick) vbox2.addLayout(hbox24) self.cb_nneg = QtWidgets.QCheckBox('Non-Negativity Constraints', self) self.cb_nneg.setChecked(True) self.cb_nneg.stateChanged.connect(self.OnNonNegConst) vbox2.addWidget(self.cb_nneg) line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) vbox2.addStretch(1) vbox2.addWidget(line) vbox2.addStretch(1) vbox2.addWidget(QtWidgets.QLabel('Multiprocessing:')) hbox26 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText('Number of processors') hbox26.addWidget(text1) hbox26.addStretch(1) self.ntc_processors = QtWidgets.QLineEdit(self) self.ntc_processors.setFixedWidth(65) self.ntc_processors.setValidator(QtGui.QIntValidator(1, 1000, self)) self.ntc_processors.setAlignment(QtCore.Qt.AlignRight) self.ntc_processors.setText(str(self.nprocessors)) hbox26.addWidget(self.ntc_processors) vbox2.addLayout(hbox26) sizer2.setLayout(vbox2) #panel 3 sizer3 = QtWidgets.QGroupBox('ROI') vbox3 = QtWidgets.QVBoxLayout() self.button_roi = QtWidgets.QPushButton( 'Select ROI') self.button_roi.clicked.connect(self.OnSelectROI) self.button_roi.setEnabled(False) vbox3.addWidget(self.button_roi) self.button_roihist = QtWidgets.QPushButton( 'Histogram ROI selection...') self.button_roihist.clicked.connect(self.OnROIHistogram) self.button_roihist.setEnabled(False) vbox3.addWidget(self.button_roihist) self.button_roispec = QtWidgets.QPushButton( 'Show ROI Spectrum') self.button_roispec.clicked.connect(self.OnShowROISpec) self.button_roispec.setEnabled(False) vbox3.addWidget(self.button_roispec) self.button_roidel = QtWidgets.QPushButton( 'Reset ROI') self.button_roidel.clicked.connect(self.OnResetROI) self.button_roidel.setEnabled(False) vbox3.addWidget(self.button_roidel) self.button_saveroi = QtWidgets.QPushButton( 'Save ROI as .mrc') self.button_saveroi.clicked.connect(self.OnSaveROI) self.button_saveroi.setEnabled(False) vbox3.addWidget(self.button_saveroi) self.button_loadroi = QtWidgets.QPushButton( 'Load ROI from .mrc') self.button_loadroi.clicked.connect(self.OnLoadROI) self.button_loadroi.setEnabled(False) vbox3.addWidget(self.button_loadroi) sizer3.setLayout(vbox3) #panel 5 vbox5 = QtWidgets.QVBoxLayout() vbox5.addStretch(1) self.tc_imagecomp = QtWidgets.QLabel(self) self.tc_imagecomp.setText("Dataset: ") vbox5.addWidget(self.tc_imagecomp) gridsizer5 = QtWidgets.QGridLayout() gridsizer5.setSpacing(5) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.absimgfig = Figure((PlotH*0.9, PlotH*0.9)) self.AbsImagePanel = FigureCanvas(self.absimgfig) self.AbsImagePanel.setParent(self) self.cid1 = self.AbsImagePanel.mpl_connect('button_press_event', self.OnPointImage) fbox.addWidget(self.AbsImagePanel) frame.setLayout(fbox) gridsizer5.addWidget(frame, 1, 1, QtCore .Qt. AlignLeft) self.slider_slice = QtWidgets.QScrollBar(QtCore.Qt.Vertical) self.slider_slice.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_slice.valueChanged[int].connect(self.OnScrollSlice) self.slider_slice.setRange(0, 100) gridsizer5.addWidget(self.slider_slice, 1, 0, QtCore .Qt. AlignLeft) self.slider_comp = QtWidgets.QScrollBar(QtCore.Qt.Horizontal) self.slider_comp.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_comp.valueChanged[int].connect(self.OnScrollComp) self.slider_comp.setRange(0, 100) self.slider_comp.setEnabled(True) self.tc_comp = QtWidgets.QLabel(self) self.tc_comp.setText("Component: ") hbox51 = QtWidgets.QHBoxLayout() hbox51.addWidget(self.tc_comp) hbox51.addWidget(self.slider_comp) gridsizer5.addLayout(hbox51, 0, 1) frame2 = QtWidgets.QFrame() frame2.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox2 = QtWidgets.QHBoxLayout() self.absimgfig2 = Figure((PlotH*0.9, PlotH*0.9)) self.AbsImagePanel2 = FigureCanvas(self.absimgfig2) self.AbsImagePanel2.setParent(self) fbox2.addWidget(self.AbsImagePanel2) frame2.setLayout(fbox2) gridsizer5.addWidget(frame2, 2, 1, QtCore .Qt. AlignLeft) frame3 = QtWidgets.QFrame() frame3.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox3 = QtWidgets.QHBoxLayout() self.absimgfig3 = Figure((PlotH*0.9, PlotH*0.9)) self.AbsImagePanel3 = FigureCanvas(self.absimgfig3) self.AbsImagePanel3.setParent(self) fbox3.addWidget(self.AbsImagePanel3) frame3.setLayout(fbox3) gridsizer5.addWidget(frame3, 2, 2, QtCore .Qt. AlignLeft) gridsizer5.addWidget(sizer3, 1, 2, QtCore .Qt. AlignCenter) vbox5.addLayout(gridsizer5) vbox5.addStretch(1) vboxtop = QtWidgets.QVBoxLayout() hboxtop = QtWidgets.QHBoxLayout() vboxt1 = QtWidgets.QVBoxLayout() vboxt1.addStretch (1) vboxt1.addWidget(sizer1) vboxt1.addStretch (1) vboxt1.addWidget(sizer2) vboxt1.addStretch (1) #vboxt1.addWidget(sizer3) #vboxt1.addStretch (1) hboxtop.addStretch (5) hboxtop.addLayout(vboxt1) hboxtop.addStretch (5) hboxtop.addLayout(vbox5) hboxtop.addStretch (5) vboxtop.addStretch (5) vboxtop.addLayout(hboxtop) vboxtop.addStretch (9) vboxtop.setContentsMargins(20,20,20,20) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnLoadTomoEng(self, event): self.fulltomorecdata = [] self.tomo_calculated = 0 self.full_tomo_calculated = 0 self.NewStackClear() self.button_save.setEnabled(False) self.tc_comp.setText('Component: ') self.slider_comp.setEnabled(False) self.tomodata = self.stack.od4d self.theta = self.stack.theta self.n_cols = self.stack.n_cols self.n_rows = self.stack.n_rows self.datanames = [] for i in range(self.stack.n_ev): self.datanames.append(str(self.stack.ev[i])) self.ncomponents = self.stack.n_ev self.combonames.clear() self.combonames.addItems(self.datanames) self.tc_imagecomp.setText("Dataset: Energies") self.button_calcall.setEnabled(True) self.button_calc1.setEnabled(True) self.button_roi.setEnabled(False) self.energiesloaded = 1 self.button_expdata.setEnabled(True) #---------------------------------------------------------------------- def OnLoadTomoComponents(self, event): self.fulltomorecdata = [] self.tomo_calculated = 0 self.full_tomo_calculated = 0 self.NewStackClear() self.button_save.setEnabled(False) self.tc_comp.setText('Component: ') self.slider_comp.setEnabled(False) self.ncomponents = self.anlz.n_target_spectra self.tomodata = np.zeros((self.stack.n_cols, self.stack.n_rows, self.ncomponents, self.stack.n_theta)) for i in range (self.stack.n_theta): if self.window().page4.showraw == True: self.tomodata[:,:,:,i] = self.anlz.target_svd_maps4D[i] else: self.tomodata[:,:,:,i] = self.anlz.target_pcafit_maps[i] self.theta = self.stack.theta self.n_cols = self.stack.n_cols self.n_rows = self.stack.n_rows self.datanames = [] for i in range(self.ncomponents): self.datanames.append(str(self.anlz.tspec_names[i])) self.combonames.clear() self.combonames.addItems(self.datanames) self.tc_imagecomp.setText("Dataset: Spectral Components") self.button_calcall.setEnabled(True) self.button_calc1.setEnabled(True) self.button_roi.setEnabled(False) self.energiesloaded = 0 self.button_expdata.setEnabled(True) #---------------------------------------------------------------------- def OnLoadSingleMrc(self, event): wildcard = "Supported 4D formats (*.mrc *.ali *.ncb);;Mrc files (*.mrc *.ali);;NCB files (*.ncb);;" OpenFileName, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Load Tomo Dataset', '', wildcard, None, QtWidgets.QFileDialog.DontUseNativeDialog) OpenFileName = str(OpenFileName) if OpenFileName == '': return basename, extension = os.path.splitext(OpenFileName) if extension == '.mrc': data = tomo_reconstruction.load_mrc(OpenFileName) dims = data.shape #Read energies from file wildcard = "Angle files (*.*);;" OpenFileName2, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Load Angle data', '', wildcard, None, QtWidgets.QFileDialog.DontUseNativeDialog) OpenFileName2 = str(OpenFileName2) if OpenFileName2 == '': return f = open(str(OpenFileName2),'r') tlist = [] for line in f: if line.startswith("*"): pass else: t = line if t.strip() == '': continue tlist.append(float(t)) self.theta = np.array(tlist) f.close() ntheta = len(self.theta) if ntheta != dims[2]: data = np.swapaxes(data, 0, 2) print('angle num:', ntheta) dims = data.shape print('Data shape', dims) self.n_cols = dims[0] self.n_rows = dims[1] self.tomodata = np.zeros((dims[0], dims[1], 1, dims[2])) self.tomodata[:,:,0,:] = data elif extension == '.ncb' : data, thetalist = file_ncb.read_ncb_data(self, OpenFileName) dims = data.shape self.theta = np.array(thetalist) ntheta = len(self.theta) if ntheta != dims[2]: data = np.swapaxes(data, 0, 2) dims = data.shape self.n_cols = dims[0] self.n_rows = dims[1] self.tomodata = np.zeros((dims[0], dims[1], 1, dims[2])) self.tomodata[:,:,0,:] = data self.fulltomorecdata = [] self.tomo_calculated = 0 self.full_tomo_calculated = 0 self.NewStackClear() self.button_save.setEnabled(False) self.tc_comp.setText('Component: ') self.slider_comp.setEnabled(False) basename = os.path.basename(str(OpenFileName)) self.datanames = [] self.datanames.append(str(basename)) self.ncomponents = 1 self.combonames.clear() #self.combonames.addItems(self.datanames) self.tc_imagecomp.setText("Dataset:" + OpenFileName) self.button_calcall.setEnabled(False) self.button_calc1.setEnabled(True) self.button_roi.setEnabled(False) self.energiesloaded = 0 self.button_expdata.setEnabled(True) #---------------------------------------------------------------------- def OnCalcTomoFull(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) value = self.ntc_niterations.text() self.maxIters = int(value) value = self.ntc_beta.text() self.beta = float(value) value = self.ntc_samplethick.text() self.samplethick = int(value) self.nprocessors = int(self.ntc_processors.text()) self.fulltomorecdata = [] binningfactor = int (self.combobin.currentText()) dims = self.tomodata[:,:,0,:].shape if (self.useengreg == 1) and (self.algo == 0): value = self.ntc_ereg.text() beta2 = float(value) print('Calculate initial reconstructions') initrecs = [] for i in range(self.ncomponents): print('Progress ',i+1,' / ',self.ncomponents) if binningfactor > 1: shape = (int(dims[0]/binningfactor), int(dims[1]/binningfactor)) projdata = np.zeros((shape[0], shape[1], dims[2])) #print 'Binning factor:', binningfactor #print 'Binned data dims', shape for j in range(dims[2]): projdata[:,:,j] = rebin(self.tomodata[0:shape[0]*binningfactor,0:shape[1]*binningfactor,i,j], shape) else: projdata = self.tomodata[:,:,i,:] self.tr.calc_tomo(projdata, self.theta, self.maxIters, self.beta, 0, algorithm = self.algo, nonnegconst = self.nonnegconst, nprocessors = self.nprocessors) initrecs.append(np.swapaxes(np.array(self.tr.tomorec.copy()), 0, 1)) for i in range(self.ncomponents): print('Progress ',i+1,' / ',self.ncomponents) if binningfactor > 1: shape = (int(dims[0]/binningfactor), int(dims[1]/binningfactor)) projdata = np.zeros((shape[0], shape[1], dims[2])) #print 'Binning factor:', binningfactor #print 'Binned data dims', shape for j in range(dims[2]): projdata[:,:,j] = rebin(self.tomodata[0:shape[0]*binningfactor,0:shape[1]*binningfactor,i,j], shape) else: projdata = self.tomodata[:,:,i,:] self.tr.calc_tomo(projdata, self.theta, self.maxIters, self.beta, self.samplethick, algorithm = 2, x0=initrecs, comp = i, beta2=beta2, nonnegconst = self.nonnegconst, nprocessors = self.nprocessors) self.fulltomorecdata.append(self.tr.tomorec.copy()) else: for i in range(self.ncomponents): print('Progress ',i+1,' / ',self.ncomponents) if binningfactor > 1: shape = (int(dims[0]/binningfactor), int(dims[1]/binningfactor)) projdata = np.zeros((shape[0], shape[1], dims[2])) #print 'Binning factor:', binningfactor #print 'Binned data dims', shape for j in range(dims[2]): projdata[:,:,j] = rebin(self.tomodata[0:shape[0]*binningfactor,0:shape[1]*binningfactor,i,j], shape) else: projdata = self.tomodata[:,:,i,:] self.tr.calc_tomo(projdata, self.theta, self.maxIters, self.beta, self.samplethick, algorithm = self.algo, nonnegconst = self.nonnegconst, nprocessors = self.nprocessors) self.fulltomorecdata.append(self.tr.tomorec.copy()) self.tr.tomorec = self.fulltomorecdata[self.icomp] self.full_tomo_calculated = 1 self.tomo_calculated = 1 dims = self.tr.tomorec.shape self.nslices = dims[2] self.islice = int(dims[2]/2) self.slider_slice.setRange(0, dims[2]-1) self.ROIvol = [[]]* dims[2] self.ROIarray = np.zeros((dims[0], dims[1], dims[2])) self.tc_comp.setText('Component: '+self.datanames[self.icomp]) self.slider_slice.setValue(self.islice) self.slider_comp.setRange(0, self.ncomponents-1) self.slider_comp.setEnabled(True) self.slider_comp.setValue(self.icomp) self.button_save.setEnabled(True) self.button_saveall.setEnabled(True) self.button_roi.setEnabled(True) self.button_roihist.setEnabled(True) self.button_loadroi.setEnabled(True) self.ShowImage() QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def OnCalcTomo1(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) value = self.ntc_niterations.text() self.maxIters = int(value) value = self.ntc_beta.text() self.beta = float(value) value = self.ntc_samplethick.text() self.samplethick = int(value) self.nprocessors = int(self.ntc_processors.text()) dims = self.tomodata[:,:,self.select1,:].shape print('Data dims = ', dims) projdata = self.tomodata[:,:,self.select1,:] binningfactor = int (self.combobin.currentText()) if binningfactor > 1: binneddata = [] shape = (int(dims[0]/binningfactor), int(dims[1]/binningfactor)) projdata = np.zeros((shape[0], shape[1], dims[2])) print('Binning factor:', binningfactor) print('Binned data dims', shape) for i in range(dims[2]): projdata[:,:,i] = rebin(self.tomodata[0:shape[0]*binningfactor,0:shape[1]*binningfactor,self.select1,i], shape) self.tr.calc_tomo(projdata, self.theta, self.maxIters, self.beta, self.samplethick, algorithm = self.algo, nonnegconst=self.nonnegconst, nprocessors = self.nprocessors) self.tomo_calculated = 1 self.full_tomo_calculated = 0 dims = self.tr.tomorec.shape self.nslices = dims[2] self.ROIvol = [[]]* dims[2] self.ROIarray = np.zeros((dims[0], dims[1], dims[2])) self.button_roispec.setEnabled(False) self.islice = int(dims[2]/2) self.slider_slice.setValue(self.islice) self.slider_slice.setRange(0, dims[2]-1) self.tc_comp.setText('Component: '+self.datanames[self.select1]) self.slider_comp.setRange(0, 0) self.button_roi.setEnabled(True) self.button_save.setEnabled(True) self.button_roispec.setEnabled(False) self.button_roi.setEnabled(True) self.button_loadroi.setEnabled(True) self.button_roihist.setEnabled(True) self.ShowImage() QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def OnSelect1Comp(self, value): item = value self.select1 = item #---------------------------------------------------------------------- def OnSelectAlgo(self, value): item = value self.algo = item # 0 - CS if self.algo == 0: self.tc_par.setEnabled(True) self.ntc_beta.setEnabled(True) self.cb_ereg.setEnabled(True) self.ntc_ereg.setEnabled(True) else: self.tc_par.setEnabled(False) self.ntc_beta.setEnabled(False) self.cb_ereg.setEnabled(False) self.ntc_ereg.setEnabled(False) #---------------------------------------------------------------------- def OnCBEngReg(self, state): if state == QtCore.Qt.Checked: self.useengreg = 1 self.ntc_ereg.setEnabled(True) else: self.useengreg = 0 self.ntc_ereg.setEnabled(False) #---------------------------------------------------------------------- def OnNonNegConst(self, state): if state == QtCore.Qt.Checked: self.nonnegconst = 1 else: self.nonnegconst = 0 #---------------------------------------------------------------------- def OnScrollSlice(self, value): self.islice = value self.ShowImage() #---------------------------------------------------------------------- def OnScrollComp(self, value): self.icomp = value if self.full_tomo_calculated == 0: return self.tr.tomorec = self.fulltomorecdata[self.icomp] self.tc_comp.setText('Component: '+self.datanames[self.icomp]) self.ShowImage() #---------------------------------------------------------------------- def OnPointImage(self, evt): if self.energiesloaded == 0 or self.full_tomo_calculated == 0: return x = evt.xdata y = evt.ydata if (x == None) or (y == None): return ix = int(np.floor(x)) iy = self.n_rows-1-int(np.floor(y)) if ix<0 : ix=0 if ix>self.n_cols-1 : ix=self.n_cols-1 if iy<0 : iy=0 if iy>self.n_rows-1 : iy=self.n_rows-1 spectrum = [] for i in range(self.ncomponents): spectrum.append(self.fulltomorecdata[i][ix,iy,self.islice]) title = 'Point [{0:d}, {0:d}, {0:d}]'.format(ix,iy,self.islice) plot = PlotFrame(self, self.stack.ev, spectrum, title=title) plot.show() #---------------------------------------------------------------------- def OnExportData(self, event): wildcard = "Mrc files (*.mrc);;" SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Tomo Reconstructions', '', wildcard) SaveFileName = str(SaveFileName) if SaveFileName == '': return basename, extension = os.path.splitext(SaveFileName) for i in range(self.ncomponents): data = self.tomodata[:,:,i,:] savefn = basename + '_TiltS_'+self.datanames[i]+extension self.tr.save_mrc(savefn, data) savefn2 = basename + '_Angles.txt' f = open(str(savefn2),'wt') for i in range(len(self.theta)): print(self.theta[i], file=f) f.close() #---------------------------------------------------------------------- def OnSave(self, event): wildcard = "Mrc files (*.mrc);;" SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Tomo Reconstructions', '', wildcard) SaveFileName = str(SaveFileName) if SaveFileName == '': return data = self.tr.tomorec self.tr.save_mrc(SaveFileName, data.T) #---------------------------------------------------------------------- def OnSaveAll(self, event): wildcard = "Mrc files (*.mrc);;" SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Tomo Reconstructions', '', wildcard) SaveFileName = str(SaveFileName) if SaveFileName == '': return basename, extension = os.path.splitext(SaveFileName) for i in range(self.ncomponents): data = self.fulltomorecdata[i] savefn = basename + '_'+self.datanames[i]+extension self.tr.save_mrc(savefn, data.T) #---------------------------------------------------------------------- def OnSaveROI(self, event): wildcard = "Mrc files (*.mrc);;" SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save ROI Selection', '', wildcard) SaveFileName = str(SaveFileName) if SaveFileName == '': return data = self.ROIarray self.tr.save_mrc(SaveFileName, data) #---------------------------------------------------------------------- def OnLoadROI(self, event): wildcard = "Mrc files (*.mrc);;" OpenFileName, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Load ROI Selection', '', wildcard) OpenFileName = str(OpenFileName) if OpenFileName == '': return self.ROIarray = tomo_reconstruction.load_mrc(OpenFileName) for i in range(self.nslices): ROIpix = np.ma.array(self.ROIarray[:,:,i]) ROIpix_masked = np.ma.masked_values(ROIpix, 0) self.ROIvol[i] = ROIpix_masked self.haveROI = 1 if self.full_tomo_calculated == 1: self.button_roispec.setEnabled(True) self.button_roidel.setEnabled(True) self.button_saveroi.setEnabled(True) self.ShowImage() #---------------------------------------------------------------------- def OnSelectLasso(self,verts): # self.lasso.disconnect_events() # path = matplotlib.path.Path(verts) # self.ind = np.nonzero(path.contains_points(self.xys))[0] # print 'Selected '+str(len(self.ind))+' points' # # indices = path.contains_points(self.xys) # # mask = np.array([ path.contains_point((j,i)) for i in range(self.stack.n_rows) for j in range(self.stack.n_cols)]).reshape(self.stack.n_cols,self.stack.n_rows) # # self.ROIvol[:,:,self.islice] = mask # # print np.sum(self.ROIvol) path = matplotlib.path.Path(verts) #find pixels inside the polygon ROIpix = np.zeros((self.n_cols,self.n_rows)) for i in range(self.n_cols): for j in range(self.n_rows): Pinside = path.contains_point((i,j)) if Pinside == True: ROIpix[i, self.n_rows-1-j] = 255 ROIpix = np.ma.array(ROIpix) ROIpix_masked = np.ma.masked_values(ROIpix, 0) self.ROIvol[self.islice] = ROIpix_masked self.ROIarray[:,:,self.islice] = ROIpix[:] self.haveROI = 1 self.ShowImage() #---------------------------------------------------------------------- def OnSelectROI(self, event): self.AbsImagePanel.mpl_disconnect(self.cid1) if self.full_tomo_calculated == 1 and self.energiesloaded == 1: self.button_roispec.setEnabled(True) self.button_roidel.setEnabled(True) self.button_roihist.setEnabled(True) self.button_saveroi.setEnabled(True) lineprops = dict(color='red', linestyle='-', linewidth = 1, alpha=1) self.lasso = LassoSelector(self.axes, onselect=self.OnSelectLasso, useblit=False, props=lineprops) #---------------------------------------------------------------------- def OnROIHistogram(self, event): #self.window().Hide() image = self.tr.tomorec[:,:,self.islice].copy() histogram = ROIHistogram(self, image, self.n_cols, self.n_rows) histogram.show() #---------------------------------------------------------------------- def OnResetROI(self, event): self.button_roispec.setEnabled(False) self.button_roidel.setEnabled(False) self.button_roihist.setEnabled(True) self.button_saveroi.setEnabled(False) self.ROIvol = [[]]*self.nslices self.haveROI = 0 self.cid1 = self.AbsImagePanel.mpl_connect('button_press_event', self.OnPointImage) self.ShowImage() #---------------------------------------------------------------------- def CalcROISpectrum(self): ROIspectrum = np.zeros((self.stack.n_ev)) for ie in range(self.stack.n_ev): indices = np.where(self.ROIarray == 255) numroipix = self.ROIarray[indices].shape[0] if numroipix > 0: roivoxels = self.fulltomorecdata[ie] ROIspectrum[ie] = np.sum(roivoxels[indices])/numroipix return ROIspectrum #---------------------------------------------------------------------- def OnShowROISpec(self): spectrum = self.CalcROISpectrum() title = 'ROI spectrum' plot = PlotFrame(self, self.stack.ev, spectrum, title=title) plot.show() #---------------------------------------------------------------------- def ShowImage(self): if self.tomo_calculated == 0: return image = self.tr.tomorec[:,:,self.islice].copy() fig = self.absimgfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() fig.patch.set_alpha(1.0) im = axes.imshow(np.rot90(image), cmap=matplotlib.colormaps["gray"]) if self.haveROI == 1: if len(self.ROIvol[self.islice]) > 0: im_red = axes.imshow(np.rot90(self.ROIvol[self.islice]), cmap=matplotlib.colormaps["autumn"]) # if self.window().page1.show_scale_bar == 1: # #Show Scale Bar # if self.com.white_scale_bar == 1: # sbcolor = 'white' # else: # sbcolor = 'black' # startx = int(self.stk.n_cols*0.05) # starty = self.stk.n_rows-int(self.stk.n_rows*0.05)-self.stk.scale_bar_pixels_y # um_string = ' $\mathrm{\mu m}$' # microns = '$'+self.stk.scale_bar_string+' $'+um_string # axes.text(self.stk.scale_bar_pixels_x+startx+1,starty+1, microns, horizontalalignment='left', verticalalignment='center', # color = sbcolor, fontsize=14) # #Matplotlib has flipped scales so I'm using rows instead of cols! # p = matplotlib.patches.Rectangle((startx,starty), self.stk.scale_bar_pixels_x, self.stk.scale_bar_pixels_y, # color = sbcolor, fill = True) # axes.add_patch(p) axes.axis("off") self.AbsImagePanel.draw() self.axes = axes self.xys = np.dstack(np.meshgrid(np.arange(self.n_cols), np.arange(self.n_rows))).reshape(-1,2) #Show orthogonal slices dims = self.tr.tomorec.shape image2 = self.tr.tomorec[:,int(dims[1]/2),:] fig = self.absimgfig2 fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes2 = fig.gca() fig.patch.set_alpha(1.0) im = axes2.imshow(np.rot90(image2), cmap=matplotlib.colormaps["gray"]) axes2.axis("off") self.AbsImagePanel2.draw() image3 = self.tr.tomorec[int(dims[0]/2),:,:].T fig = self.absimgfig3 fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes3 = fig.gca() fig.patch.set_alpha(1.0) im = axes3.imshow(np.rot90(image3), cmap=matplotlib.colormaps["gray"]) axes3.axis("off") self.AbsImagePanel3.draw() #---------------------------------------------------------------------- def MakeHistogramROI(self, histmin, histmax): for i in range(self.nslices): hist_indices = np.where((histmin 0: self.loadSpectrum() self.ShowFitParams() #---------------------------------------------------------------------- def OnSpectraListClick(self): item = self.tc_speclist.currentRow() sel = item self.i_spec = sel+1 if self.com.xpf_loaded == 1: self.loadSpectrum() self.slider_spec.setValue(self.i_spec) #---------------------------------------------------------------------- def OnFitSpectrum(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.SetFitParams() fitted_spectrum, separate_peaks = self.anlz.fit_spectrum(self.i_spec-1, self.nsteps[self.i_spec-1], self.npeaks[self.i_spec-1]) self.fits[self.i_spec-1] = fitted_spectrum self.fits_sep[self.i_spec-1] = separate_peaks self.spectrumfitted[self.i_spec-1] = 1 self.loadSpectrum() QtWidgets.QApplication.restoreOverrideCursor() self.updatewidgets() #---------------------------------------------------------------------- def OnSave(self, event): #Save images wildcard = "Portable Network Graphics (*.png);;Adobe PDF Files (*.pdf);; SVG (*.svg)" fileName = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Fit Plot', '', wildcard) fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext != 'png' and ext != 'pdf' and ext != 'svg': error_message = ( 'Only the PNG, PDF and SVG image formats are supported.\n' 'A file extension of `png\' or `pdf\' must be used.') QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % error_message) return try: matplotlib.rcParams['pdf.fonttype'] = 42 fig = self.Specfig fig.savefig(fileName) except IOError as e: if e.strerror: err = e.strerror else: err = e QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #Save text file with fit info textfilepath = path+'_'+self.anlz.xfspec_names[self.i_spec-1]+'_fitinfo.txt' f = open(textfilepath, 'w') print('********************* Fit Results ********************', file=f) print('\n', file=f) print('Base:\t\t'+'{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].base), file=f) print('\n', file=f) text = 'Step Inflection Point [eV]:\t' for i in range(self.nsteps[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].stepfitparams[i*3]) + ', ' print(text, file=f) text = 'Step Height:\t' for i in range(self.nsteps[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].stepfitparams[i*3+1]) + ', ' print(text, file=f) text = 'Step FWHM:\t' for i in range(self.nsteps[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].stepfitparams[i*3+2]) + ', ' print(text, file=f) print('\n', file=f) text = 'Peak Positions:\t' for i in range(self.npeaks[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].gauss_fp_m[i]) + ', ' print(text, file=f) text = 'Peak Sigma:\t' for i in range(self.npeaks[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].gauss_fp_s[i]) + ', ' print(text, file=f) text = 'Peak Amplitude:\t' for i in range(self.npeaks[self.i_spec-1]): text = text + '{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].gauss_fp_a[i]) + ', ' print(text, file=f) f.close() return #---------------------------------------------------------------------- def OnShowFitParams(self, event): fitparams = [self.anlz.xfitpars[self.i_spec-1].base, self.anlz.xfitpars[self.i_spec-1].stepfitparams, self.anlz.xfitpars[self.i_spec-1].gauss_fp_a, self.anlz.xfitpars[self.i_spec-1].gauss_fp_m, self.anlz.xfitpars[self.i_spec-1].gauss_fp_s] fitparswin = FitParams(self.window(), 'Fit Parameters', fitparams, True, False) fitparswin.show() #---------------------------------------------------------------------- def OnNstepsspin(self, value): num = value self.nsteps[self.i_spec-1] = num #---------------------------------------------------------------------- def OnNgaussspin(self, value): num = value self.npeaks[self.i_spec-1] = num #---------------------------------------------------------------------- def OnShowEngs(self, state): if state == QtCore.Qt.Checked: self.showevlines = 1 else: self.showevlines = 0 if self.anlz.n_xrayfitsp > 0: self.loadSpectrum() #---------------------------------------------------------------------- def updatewidgets(self): self.button_fitspec.setEnabled(True) for i in range(12): self.tc_peakid[i].setText('') peaknames = [] if len(self.npeaks) > 0: for i in range(self.npeaks[self.i_spec-1]): i_eng=(np.abs(self.peak_engs-self.anlz.xfitpars[self.i_spec-1].gauss_fp_m[i])).argmin() diff = np.abs(self.peak_engs[i_eng]-self.anlz.xfitpars[self.i_spec-1].gauss_fp_m[i]) if np.abs(diff) < 0.5: peaknames.append(self.peak_names[i_eng] + '\t({0:01.1f})'.format(diff)) else: peaknames.append('unknown') if self.spectrumfitted[self.i_spec-1] == 1: self.button_save.setEnabled(True) for i in range(self.npeaks[self.i_spec-1]): text = '\t{0:04.3f}'.format(self.anlz.xfitpars[self.i_spec-1].gauss_fp_m[i]) self.tc_peakid[i].setText(text + '\t' + peaknames[i]) self.tc_peakid[i].setStyleSheet('color: rgb({0:}, {1}, {2})'.format(int(255*self.plotcolors[i][0]), int(255*self.plotcolors[i][1]), int(255*self.plotcolors[i][2]))) else: self.button_save.setEnabled(False) self.ShowFitParams() #---------------------------------------------------------------------- def loadSpectrum(self): spectrum = self.anlz.xrayfitspectra[self.i_spec-1, :] fig = self.Specfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() line1 = axes.plot(self.stk.ev,spectrum, color='black') if self.spectrumfitted[self.i_spec-1] == 1: offset = self.fits_sep[self.i_spec-1][0] line3 = axes.plot(self.stk.ev, offset, color = 'blue') for i in range(1, 1+self.nsteps[self.i_spec-1]): y = self.fits_sep[self.i_spec-1][i]+offset line3 = axes.plot(self.stk.ev, y, color = 'green') for i in range(0, self.npeaks[self.i_spec-1]): y = self.fits_sep[self.i_spec-1][i+self.nsteps[self.i_spec-1]+1]+offset line3 = axes.plot(self.stk.ev, y, color = self.plotcolors[i]) line2 = axes.plot(self.stk.ev,self.fits[self.i_spec-1], color='red') lines = axes.get_lines() self.colors = [] for i, line in enumerate(lines): self.colors.append(line.get_color()) if self.showevlines: peak_engs = self.window().page5.peak_engs for i in range(len(peak_engs)): if (peak_engs[i]>self.stk.ev[0]) and (peak_engs[i] self.stk.ev[self.stk.n_ev-1]: sel_ev = self.stk.n_ev-1 else: indx = np.abs(self.stk.ev - x).argmin() sel_ev = indx self.i_eng=(np.abs(self.peak_engs-self.stk.ev[sel_ev])).argmin() self.ShowImage() self.ShowODPlot() self.slider_eng.setValue(self.i_eng) #---------------------------------------------------------------------- def ShowODPlot(self): if (self.spectrum_loaded == 0) and (self.com.i0_loaded == 0): return if (self.com.i0_loaded == 0): spectrum = self.anlz.xrayfitspectra[self.i_spectrum-1, :] self.tc_1.setText("X-ray Spectrum: " + self.anlz.xfspec_names[self.i_spectrum-1]) else: if (self.i_spectrum == 1): spectrum = self.stk.od3d.sum(axis=0) spectrum = spectrum.sum(axis=0)/(self.stk.n_rows*self.stk.n_cols) self.tc_1.setText("X-ray Spectrum: Average OD") else: spectrum = self.anlz.xrayfitspectra[self.i_spectrum-2, :] self.tc_1.setText("X-ray Spectrum: " + self.anlz.xfspec_names[self.i_spectrum-2]) fig = self.kespecfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev,spectrum) for i in range(len(self.peak_engs)): if (self.peak_engs[i]>self.stk.ev[0]) and (self.peak_engs[i]self.stk.ev[0]) and (self.peak_engs[self.i_eng] 0: self.anlz.target_pcafit_maps = self.anlz.target_pcafit_maps4D[self.itheta] self.anlz.original_fit_maps = self.anlz.original_fit_maps4D[self.itheta] self.anlz.target_pcafit_coeffs = self.anlz.target_pcafit_coeffs4D[self.itheta] self.anlz.target_pcafit_spectra = self.anlz.target_pcafit_spectra4D[self.itheta] self.slider_theta.setVisible(True) self.tc_imagetheta.setVisible(True) self.slider_theta.setRange(0, self.stk.n_theta-1) self.slider_theta.setValue(self.itheta) self.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) self.button_calc4d.setEnabled(True) self.ShowSpectraList() self.loadTSpectrum() self.loadTargetMap() QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'Could not calculate 4D spectra.') self.window().refresh_widgets() #---------------------------------------------------------------------- def Save(self, filename, path, spec_png = True, spec_pdf = False, spec_svg = False, spec_csv = False, img_png = True, img_pdf = False, img_svg = False, img_tif = False): self.SaveFileName = os.path.join(path,filename) try: if img_png: self.SaveMaps(imgformat=1) if img_pdf: self.SaveMaps(imgformat=2) if img_svg: self.SaveMaps(imgformat=3) if img_tif: self.SaveMaps(imgformat=0, savetif=True) if spec_png: self.SaveSpectra(imgformat=1) if spec_pdf: self.SaveSpectra(imgformat=2) if spec_pdf: self.SaveSpectra(imgformat=3) if spec_csv: self.SaveSpectra(imgformat=0, savecsv = True) except IOError as e: if e.strerror: err = e.strerror else: err = e QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #---------------------------------------------------------------------- def SaveSpectra(self, imgformat=1, savecsv = False): from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas matplotlib.rcParams['pdf.fonttype'] = 42 colors=['#FF0000','#000000','#FFFFFF'] spanclrmap=matplotlib.colors.LinearSegmentedColormap.from_list('spancm',colors) if savecsv: for i in range (self.anlz.n_target_spectra): #Save spectra tspectrum = self.anlz.target_spectra[i, :] fileName_spec = self.SaveFileName+"_Tspectrum_" +str(i+1)+".csv" cname = 'Tspectrum_' +str(i+1) self.stk.write_csv(fileName_spec, self.stk.ev, tspectrum, cname = cname) if imgformat == 0: return if imgformat == 1: ext = 'png' elif imgformat == 2: ext = 'pdf' elif imgformat == 3: ext = 'svg' suffix = "." + ext for i in range (self.anlz.n_target_spectra): #Save spectra tspectrum = self.anlz.target_spectra[i, :] fig = matplotlib.figure.Figure(figsize =(PlotW*1.21, PlotH*0.48)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() line1 = axes.plot(self.stk.ev,tspectrum, color='black', label = 'Raw data') if self.com.pca_calculated == 1: tspectrumfit = self.anlz.target_pcafit_spectra[i, :] diff = np.abs(tspectrum-tspectrumfit) line2 = axes.plot(self.stk.ev,tspectrumfit, color='green', label = 'Fit') line3 = axes.plot(self.stk.ev,diff, color='grey', label = 'Abs(Raw-Fit)') fontP = matplotlib.font_manager.FontProperties() fontP.set_size('small') axes.legend(loc=4, prop = fontP) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_Tspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) #Save combined: fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() for i in range (self.anlz.n_target_spectra): #Save spectra tspectrum = self.anlz.target_spectra[i, :] line1 = axes.plot(self.stk.ev,tspectrum, color='black', label = 'Raw data') if self.com.pca_calculated == 1: tspectrumfit = self.anlz.target_pcafit_spectra[i, :] diff = np.abs(tspectrum-tspectrumfit) line2 = axes.plot(self.stk.ev,tspectrumfit, color='green', label = 'Fit') line3 = axes.plot(self.stk.ev,diff, color='grey', label = 'Abs(Raw-Fit)') fontP = matplotlib.font_manager.FontProperties() fontP.set_size('small') axes.legend(loc=4, prop = fontP) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_Tspectra_composite"+"."+ext fig.savefig(fileName_spec) #---------------------------------------------------------------------- def SaveMaps(self, imgformat=1, savetif = False): from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas matplotlib.rcParams['pdf.fonttype'] = 42 colors=['#FF0000','#000000','#FFFFFF'] spanclrmap=matplotlib.colors.LinearSegmentedColormap.from_list('spancm',colors) if imgformat > 0 : if imgformat == 1: ext = 'png' elif imgformat == 2: ext = 'pdf' elif imgformat == 3: ext = 'svg' suffix = "." + ext for i in range (self.anlz.n_target_spectra): #Save composition maps if self.showraw == True: tsmapimage = self.anlz.target_svd_maps[:,:,i] else: tsmapimage = self.anlz.target_pcafit_maps[:,:,i] fig = matplotlib.figure.Figure(figsize =(PlotH, PlotH)) canvas = FigureCanvas(fig) fig.clf() axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) min_val = np.min(tsmapimage) max_val = np.max(tsmapimage) bound = np.max((np.abs(min_val), np.abs(max_val))) if self.show_scale_bar == 1: um_string = ' $\mathrm{\mu m}$' microns = '$'+self.stk.scale_bar_string+' $'+um_string axes.text(self.stk.scale_bar_pixels_x+10,self.stk.n_cols-9, microns, horizontalalignment='left', verticalalignment='center', color = 'white', fontsize=14) #Matplotlib has flipped scales so I'm using rows instead of cols! p = matplotlib.patches.Rectangle((5,self.stk.n_cols-10), self.stk.scale_bar_pixels_x, self.stk.scale_bar_pixels_y, color = 'white', fill = True) axes.add_patch(p) im = axes.imshow(np.rot90(tsmapimage), cmap=spanclrmap, vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") fileName_img = self.SaveFileName+"_TSmap_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) else: for i in range (self.anlz.n_target_spectra): #Save composition maps if self.showraw == True: tsmapimage = self.anlz.target_svd_maps[:,:,i] else: tsmapimage = self.anlz.target_pcafit_maps[:,:,i] fileName_img = self.SaveFileName+"_TSmap_" +str(i+1)+".tif" img1 = Image.fromarray(tsmapimage) img1.save(fileName_img) #---------------------------------------------------------------------- def OnEditSpectraListClick(self): item = self.tc_speclist.currentItem() self.tc_speclist.editItem(item) self.anlz.tspec_names[self.i_tspec-1] = item.data() self.loadTSpectrum() #---------------------------------------------------------------------- def OnSpectraListClick(self): item = self.tc_speclist.currentRow() sel = item self.i_tspec = sel+1 if self.com.spec_anl_calculated == 1: self.loadTSpectrum() self.loadTargetMap() self.slider_tspec.setValue(self.i_tspec) #---------------------------------------------------------------------- def OnTSScroll(self, value): sel = value self.i_tspec = sel self.tc_speclist.setCurrentRow(self.i_tspec-1) if self.com.spec_anl_calculated == 1: self.loadTSpectrum() self.loadTargetMap() #---------------------------------------------------------------------- def OnScrollTheta(self, value): if self.com.spec_anl4D_calculated == 0: return self.itheta = value self.anlz.target_svd_maps = self.anlz.target_svd_maps4D[self.itheta] self.anlz.original_svd_maps = self.anlz.original_svd_maps4D[self.itheta] if len(self.anlz.eigenvecs4D) > 0: self.anlz.target_pcafit_maps = self.anlz.target_pcafit_maps4D[self.itheta] self.anlz.original_fit_maps = self.anlz.original_fit_maps4D[self.itheta] self.anlz.target_pcafit_coeffs = self.anlz.target_pcafit_coeffs4D[self.itheta] self.anlz.target_pcafit_spectra = self.anlz.target_pcafit_spectra4D[self.itheta] self.tc_imagetheta.setText("4D Data Angle: "+'{0:5.2f}\t'.format(self.stk.theta[self.itheta])) self.loadTSpectrum() self.loadTargetMap() self.window().page0.itheta = self.itheta self.window().page0.slider_theta.setValue(self.itheta) self.window().page1.itheta = self.itheta self.window().page1.slider_theta.setValue(self.itheta) #---------------------------------------------------------------------- def OnRBRawFit(self, enabled): state = enabled if state: self.showraw = True else: self.showraw = False if self.com.spec_anl_calculated == 1: self.loadTSpectrum() self.loadTargetMap() #---------------------------------------------------------------------- def OnShowScale(self, state): if state == QtCore.Qt.Checked: self.show_scale_bar = 1 else: self.show_scale_bar = 0 if self.com.spec_anl_calculated == 1: self.loadTSpectrum() self.loadTargetMap() #---------------------------------------------------------------------- def OnRemoveSpectrum(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.anlz.remove_spectrum(self.i_tspec-1) self.com.spec_anl_calculated = 1 self.i_tspec = self.i_tspec-1 if self.i_tspec<0: self.i_tspec=0 self.slider_tspec.setMaximum(self.anlz.n_target_spectra) self.slider_tspec.setValue(self.i_tspec) if self.anlz.tspectrum_loaded == 1: if self.com.spec_anl4D_calculated == 1: self.anlz.calculate_targetmaps_4D() self.loadTSpectrum() self.loadTargetMap() self.ShowSpectraList() else: self.com.spec_anl_calculated = 0 self.com.spec_anl4D_calculated = 0 self.ClearWidgets() QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def OnMoveSpectrumDown(self, event): if self.i_tspec < self.anlz.n_target_spectra: self.anlz.move_spectrum(self.i_tspec-1, self.i_tspec) self.i_tspec += 1 self.slider_tspec.setValue(self.i_tspec) if self.com.spec_anl4D_calculated == 1: self.anlz.calculate_targetmaps_4D() self.loadTSpectrum() self.loadTargetMap() self.ShowSpectraList() #---------------------------------------------------------------------- def OnMoveSpectrumUp(self, event): if self.i_tspec > 1: self.anlz.move_spectrum(self.i_tspec-1, self.i_tspec-2) if self.com.spec_anl4D_calculated == 1: self.anlz.calculate_targetmaps_4D() self.i_tspec -= 1 self.slider_tspec.setValue(self.i_tspec) self.loadTSpectrum() self.loadTargetMap() self.ShowSpectraList() #---------------------------------------------------------------------- def ClearWidgets(self): fig = self.mapfig fig.clf() self.MapPanel.draw() fig = self.TSpecfig fig.clf() self.TSpectrumPanel.draw() self.tc_tspec.setText("Target Spectrum: ") self.tc_speclist.clear() self.tc_spfitlist.clear() self.com.spec_anl_calculated = 0 self.com.spec_anl4D_calculated = 0 self.i_tspec = 1 self.showraw = True self.rb_raw.setChecked(True) self.slider_tspec.setValue(self.i_tspec) self.button_calc4d.setEnabled(False) self.button_calc4d.setVisible(False) self.textctrl_sp1.setText('Common Name: \n') self.textctrl_sp2.setText('RMS Error: ') self.itheta = 0 self.slider_theta.setVisible(False) self.tc_imagetheta.setVisible(False) self.window().refresh_widgets() #---------------------------------------------------------------------- def ShowFitWeights(self): self.tc_spfitlist.clear() norm_factor = 100./np.sum(np.absolute(self.anlz.target_pcafit_coeffs[self.i_tspec-1, :])) for i in range(self.anlz.numsigpca): textitem = '{0}: {1:5.2f} %'.format(i+1, norm_factor *abs(self.anlz.target_pcafit_coeffs[self.i_tspec-1, i])) self.tc_spfitlist.addItem(textitem) self.tc_spfitlist.setCurrentRow(0) #---------------------------------------------------------------------- def ShowSpectraList(self): self.tc_speclist.clear() for i in range(self.anlz.n_target_spectra): self.tc_speclist.addItem(self.anlz.tspec_names[i]) #---------------------------------------------------------------------- def loadTargetMap(self): if self.showraw == True: tsmapimage = self.anlz.target_svd_maps[:,:,self.i_tspec-1] else: tsmapimage = self.anlz.target_pcafit_maps[:,:,self.i_tspec-1] colors=['#FF0000','#000000','#FFFFFF'] spanclrmap=matplotlib.colors.LinearSegmentedColormap.from_list('spancm',colors) fig = self.mapfig fig.clf() axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) min_val = np.min(tsmapimage) max_val = np.max(tsmapimage) bound = np.max((np.abs(min_val), np.abs(max_val))) if self.show_scale_bar == 1: um_string = ' $\mathrm{\mu m}$' microns = '$'+self.stk.scale_bar_string+' $'+um_string axes.text(self.stk.scale_bar_pixels_x+10,self.stk.n_cols-9, microns, horizontalalignment='left', verticalalignment='center', color = 'white', fontsize=14) #Matplotlib has flipped scales so I'm using rows instead of cols! p = matplotlib.patches.Rectangle((5,self.stk.n_cols-10), self.stk.scale_bar_pixels_x, self.stk.scale_bar_pixels_y, color = 'white', fill = True) axes.add_patch(p) im = axes.imshow(np.rot90(tsmapimage), cmap=spanclrmap, vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") self.MapPanel.draw() #---------------------------------------------------------------------- def loadTSpectrum(self): if self.anlz.tspectrum_loaded == 0: return tspectrum = self.anlz.target_spectra[self.i_tspec-1, :] fig = self.TSpecfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() line1 = axes.plot(self.stk.ev,tspectrum, color='black', label = 'Raw data') if self.com.pca_calculated == 1: tspectrumfit = self.anlz.target_pcafit_spectra[self.i_tspec-1, :] diff = np.abs(tspectrum-tspectrumfit) line2 = axes.plot(self.stk.ev,tspectrumfit, color='green', label = 'Fit') line3 = axes.plot(self.stk.ev,diff, color='grey', label = 'Abs(Raw-Fit)') fontP = matplotlib.font_manager.FontProperties() fontP.set_size('small') axes.legend(loc=4, prop = fontP) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') self.TSpectrumPanel.draw() self.tc_tspec.setText("Target Spectrum: " + self.anlz.tspec_names[self.i_tspec-1]) self.textctrl_sp1.setText('Common Name: '+ self.anlz.tspec_names[self.i_tspec-1]) if self.com.pca_calculated == 1: self.textctrl_sp2.setText('RMS Error: '+ str('{0:7.5f}').format(self.anlz.target_rms[self.i_tspec-1])) self.ShowFitWeights() #---------------------------------------------------------------------- class ShowCompositeRBGmap(QtWidgets.QDialog): def __init__(self, parent, common, analz): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(630, 400) self.setWindowTitle('Composite RBG Map') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.com = common self.anlz = analz self.show_info = 0 self.n_cols = self.anlz.stack.n_cols self.n_rows = self.anlz.stack.n_rows self.rgbimage = np.zeros((self.n_cols, self.n_rows, 3), dtype=float) self.minr = 0 self.maxr = 100 self.weightr = 100 self.ming = 0 self.maxg = 100 self.weightg = 100 self.minb = 0 self.maxb = 100 self.weightb = 100 self.r_spec = 0 self.g_spec = 1 self.b_spec = 2 sizer1 = QtWidgets.QGroupBox('Red spectrum') fgs1 = QtWidgets.QGridLayout() r = QtWidgets.QLabel(self) r.setText('Red') rl = QtWidgets.QLabel(self) rl.setText('Limits') rw = QtWidgets.QLabel(self) rw.setText('Weight') self.combor = QtWidgets.QComboBox(self) self.combor.addItems(self.anlz.tspec_names) self.combor.activated[int].connect(self.OnSelectR) #self.combor.SetToolTip(wx.ToolTip("select spectrum from dropdown-list")) self.combor.setCurrentIndex(self.r_spec) hbox12 = QtWidgets.QHBoxLayout() self.tcrmin = QtWidgets.QSpinBox() self.tcrmin.setRange(0,100) self.tcrmin.setValue(0) self.tcrmin.valueChanged[int].connect(self.OnLimitMinR) self.tcrmax = QtWidgets.QSpinBox() self.tcrmax.setRange(0,100) self.tcrmax.setValue(100) self.tcrmax.valueChanged[int].connect(self.OnLimitMaxR) hbox12.addWidget(self.tcrmin) hbox12.addWidget(self.tcrmax) self.tcrweight = QtWidgets.QSpinBox() self.tcrweight.setRange(0,100) self.tcrweight.setValue(100) self.tcrweight.valueChanged[int].connect(self.OnWeightR) fgs1.addWidget(r, 0, 0) fgs1.addWidget(self.combor, 0, 1) fgs1.addWidget(rl, 1, 0) fgs1.addLayout(hbox12, 1, 1) fgs1.addWidget(rw, 2, 0) fgs1.addWidget(self.tcrweight, 2, 1) sizer1.setLayout(fgs1) sizer2 = QtWidgets.QGroupBox('Green spectrum') fgs2 = QtWidgets.QGridLayout() g = QtWidgets.QLabel(self) g.setText('Green') gl = QtWidgets.QLabel(self) gl.setText('Limits') gw = QtWidgets.QLabel(self) gw.setText('Weight') self.combog = QtWidgets.QComboBox(self) self.combog.addItems(self.anlz.tspec_names) self.combog.activated[int].connect(self.OnSelectG) #self.combor.SetToolTip(wx.ToolTip("select spectrum from dropdown-list")) self.combog.setCurrentIndex(self.g_spec) hbox12 = QtWidgets.QHBoxLayout() self.tcgmin = QtWidgets.QSpinBox() self.tcgmin.setRange(0,100) self.tcgmin.setValue(0) self.tcgmin.valueChanged[int].connect(self.OnLimitMinG) self.tcgmax = QtWidgets.QSpinBox() self.tcgmax.setRange(0,100) self.tcgmax.setValue(100) self.tcgmax.valueChanged[int].connect(self.OnLimitMaxG) hbox12.addWidget(self.tcgmin) hbox12.addWidget(self.tcgmax) self.tcgweight = QtWidgets.QSpinBox() self.tcgweight.setRange(0,100) self.tcgweight.setValue(100) self.tcgweight.valueChanged[int].connect(self.OnWeightG) fgs2.addWidget(g, 0, 0) fgs2.addWidget(self.combog, 0, 1) fgs2.addWidget(gl, 1, 0) fgs2.addLayout(hbox12, 1, 1) fgs2.addWidget(gw, 2, 0) fgs2.addWidget(self.tcgweight, 2, 1) sizer2.setLayout(fgs2) sizer3 = QtWidgets.QGroupBox('Blue spectrum') fgs3 = QtWidgets.QGridLayout() b = QtWidgets.QLabel(self) b.setText('Blue') bl = QtWidgets.QLabel(self) bl.setText('Limits') bw = QtWidgets.QLabel(self) bw.setText('Weight') self.combob = QtWidgets.QComboBox(self) self.combob.addItems(self.anlz.tspec_names) self.combob.activated[int].connect(self.OnSelectB) #self.combor.SetToolTip(wx.ToolTip("select spectrum from dropdown-list")) self.combob.setCurrentIndex(self.b_spec) hbox12 = QtWidgets.QHBoxLayout() self.tcbmin = QtWidgets.QSpinBox() self.tcbmin.setRange(0,100) self.tcbmin.setValue(0) self.tcbmin.valueChanged[int].connect(self.OnLimitMinB) self.tcbmax = QtWidgets.QSpinBox() self.tcbmax.setRange(0,100) self.tcbmax.setValue(100) self.tcbmax.valueChanged[int].connect(self.OnLimitMaxB) hbox12.addWidget(self.tcbmin) hbox12.addWidget(self.tcbmax) self.tcbweight = QtWidgets.QSpinBox() self.tcbweight.setRange(0,100) self.tcbweight.setValue(100) self.tcbweight.valueChanged[int].connect(self.OnWeightB) fgs3.addWidget(b, 0, 0) fgs3.addWidget(self.combob, 0, 1) fgs3.addWidget(bl, 1, 0) fgs3.addLayout(hbox12, 1, 1) fgs3.addWidget(bw, 2, 0) fgs3.addWidget(self.tcbweight, 2, 1) sizer3.setLayout(fgs3) vbox = QtWidgets.QVBoxLayout() hbox1 = QtWidgets.QHBoxLayout() vbox1 = QtWidgets.QVBoxLayout() vbox1.addWidget(sizer1) vbox1.addWidget(sizer2) vbox1.addWidget(sizer3) self.show_info_cb = QtWidgets.QCheckBox( 'Show Info on the Image', self) self.show_info_cb.stateChanged.connect(self.OnShowInfo) vbox1.addWidget(self.show_info_cb) hbox1.addLayout(vbox1) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.RGBImagefig = Figure((PlotH, PlotH)) self.RGBImagePanel = FigureCanvas(self.RGBImagefig) fbox.addWidget(self.RGBImagePanel) frame.setLayout(fbox) hbox1.addWidget(frame) vbox.addLayout(hbox1) hbox2 = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save image') button_save.clicked.connect( self.OnSave) hbox2.addWidget(button_save) button_close = QtWidgets.QPushButton('Dismiss') button_close.clicked.connect( self.close) hbox2.addWidget(button_close) vbox.addLayout(hbox2) self.setLayout(vbox) self.CalcR() self.CalcG() self.CalcB() self.draw_image() #---------------------------------------------------------------------- def OnSelectR(self, value): item = value self.r_spec = item self.CalcR() self.draw_image() #---------------------------------------------------------------------- def CalcR(self): if self.parent.page4.showraw == True: tsmap = self.anlz.target_svd_maps[:,:,self.r_spec].copy() else: tsmap = self.anlz.target_pcafit_maps[:,:,self.r_spec].copy() uscale_min = tsmap.min() uscale_max = tsmap.max() scale_min = uscale_min + (uscale_max-uscale_min)*float(self.minr)/100. scale_max = uscale_min + (uscale_max-uscale_min)*float(self.maxr)/100. if scale_min >= scale_max: tsmap = np.zeros((self.n_cols, self.n_rows), dtype=float) else: tsmap = tsmap.clip(min=scale_min, max=scale_max) tsmap = (tsmap -scale_min) / (scale_max - scale_min) indices = np.where(tsmap < 0) tsmap[indices] = 0.0 indices = np.where(tsmap > 1) tsmap[indices] = 1.0 self.rgbimage[:,:,0] = tsmap*float(self.weightr)/100. #---------------------------------------------------------------------- def OnLimitMinR(self, value): self.minr = value #print 'self.minr=', self.minr self.CalcR() self.draw_image() #---------------------------------------------------------------------- def OnLimitMaxR(self, value): self.maxr = value #print 'self.maxr=', self.maxr self.CalcR() self.draw_image() #---------------------------------------------------------------------- def OnWeightR(self, value): self.weightr = value #print 'self.weightr=', self.weightr self.CalcR() self.draw_image() #---------------------------------------------------------------------- def OnSelectG(self, value): item = value self.g_spec = item self.CalcG() self.draw_image() #---------------------------------------------------------------------- def CalcG(self): if self.parent.page4.showraw == True: tsmap = self.anlz.target_svd_maps[:,:,self.g_spec].copy() else: tsmap = self.anlz.target_pcafit_maps[:,:,self.g_spec].copy() uscale_min = tsmap.min() uscale_max = tsmap.max() scale_min = uscale_min + (uscale_max-uscale_min)*float(self.ming)/100. scale_max = uscale_min + (uscale_max-uscale_min)*float(self.maxg)/100. if scale_min >= scale_max: tsmap = np.zeros((self.n_cols, self.n_rows), dtype=float) else: tsmap = tsmap.clip(min=scale_min, max=scale_max) tsmap = (tsmap - scale_min) / (scale_max - scale_min) indices = np.where(tsmap < 0) tsmap[indices] = 0.0 indices = np.where(tsmap > 1) tsmap[indices] = 1.0 self.rgbimage[:,:,1] = tsmap*float(self.weightg)/100. #---------------------------------------------------------------------- def OnLimitMinG(self, value): self.ming = value #print 'self.ming=', self.ming self.CalcG() self.draw_image() #---------------------------------------------------------------------- def OnLimitMaxG(self, value): self.maxg = value #print 'self.maxg=', self.maxg self.CalcG() self.draw_image() #---------------------------------------------------------------------- def OnWeightG(self, value): self.weightg = value #print 'self.weightg=', self.weightg self.CalcG() self.draw_image() #---------------------------------------------------------------------- def OnSelectB(self, value): item = value self.b_spec = item self.CalcB() self.draw_image() #---------------------------------------------------------------------- def CalcB(self): if self.parent.page4.showraw == True: tsmap = self.anlz.target_svd_maps[:,:,self.b_spec].copy() else: tsmap = self.anlz.target_pcafit_maps[:,:,self.b_spec].copy() uscale_min = tsmap.min() uscale_max = tsmap.max() scale_min = uscale_min + (uscale_max-uscale_min)*float(self.minb)/100. scale_max = uscale_min + (uscale_max-uscale_min)*float(self.maxb)/100. if scale_min >= scale_max: tsmap = np.zeros((self.n_cols, self.n_rows), dtype=float) else: tsmap = tsmap.clip(min=scale_min, max=scale_max) tsmap = (tsmap - scale_min) / (scale_max - scale_min) indices = np.where(tsmap < 0) tsmap[indices] = 0.0 indices = np.where(tsmap > 1) tsmap[indices] = 1.0 self.rgbimage[:,:,2] = tsmap*float(self.weightb)/100. #---------------------------------------------------------------------- def OnLimitMinB(self, value): self.minb = value #print 'self.minb=', self.minb self.CalcB() self.draw_image() #---------------------------------------------------------------------- def OnLimitMaxB(self, value): self.maxb = value #print 'self.maxb=', self.maxb self.CalcB() self.draw_image() #---------------------------------------------------------------------- def OnWeightB(self, value): self.weightb = value #print 'self.weightb=', self.weightb self.CalcB() self.draw_image() #---------------------------------------------------------------------- def OnShowInfo(self, state): if state == QtCore.Qt.Checked: self.show_info = 1 else: self.show_info = 0 self.draw_image() #---------------------------------------------------------------------- def draw_image(self): fig = self.RGBImagefig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() fig.patch.set_alpha(1.0) im = axes.imshow(np.rot90(self.rgbimage)) axes.axis("off") if self.show_info == 1: startx = int(self.n_rows*0.02) starty = self.n_cols-int(self.n_cols*0.15) info = 'R:%s [%d] \nG:%s [%d] \nB:%s [%d]' % (self.anlz.tspec_names[self.r_spec], self.weightr, self.anlz.tspec_names[self.g_spec], self.weightg, self.anlz.tspec_names[self.b_spec], self.weightb) axes.text(+startx+1,starty+1, info, horizontalalignment='left', verticalalignment='center', color = 'white', fontsize=8) self.RGBImagePanel.draw() #---------------------------------------------------------------------- def OnSave(self, evt): wildcard = "Portable Network Graphics (*.png);;Adobe PDF Files (*.pdf);;" SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Plot', '', wildcard) SaveFileName = str(SaveFileName) if SaveFileName == '': return path, ext = os.path.splitext(SaveFileName) ext = ext[1:].lower() if ext != 'png' and ext != 'pdf': error_message = ( 'Only the PNG and PDF image formats are supported.\n' 'A file extension of `png\' or `pdf\' must be used.') QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % error_message) return matplotlib.rcParams['pdf.fonttype'] = 42 fig = self.RGBImagefig fig.savefig(SaveFileName, pad_inches = 0.0) #---------------------------------------------------------------------- class ShowMapHistogram(QtWidgets.QDialog): def __init__(self, parent, common, analz): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.limit = 1 self.histmax = None self.resize(600, 500) self.setWindowTitle('Histogram') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.com = common self.anlz = analz vbox = QtWidgets.QVBoxLayout() frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.histfig = Figure((6.0, 4.2)) self.HistogramPanel = FigureCanvas(self.histfig) self.HistogramPanel.setParent(self) self.HistogramPanel.mpl_connect('button_press_event', self.OnClick) fbox.addWidget(self.HistogramPanel) frame.setLayout(fbox) vbox.addWidget(frame) vbox1 = QtWidgets.QVBoxLayout() sizer1 = QtWidgets.QGroupBox('Histogram Cutoff') st = QtWidgets.QLabel(self) st.setText('Select a cutoff values on the histogram. All the values outside the defined limits will be set to cutoff limit value.') vbox1.addWidget(st) hbox1 = QtWidgets.QHBoxLayout() self.rb_min = QtWidgets.QRadioButton( 'Lower Limit', self) self.rb_max = QtWidgets.QRadioButton('Upper Limit',self) self.rb_min.setChecked(True) self.rb_min.toggled.connect(self.OnRb_limit) hbox1.addWidget(self.rb_min) hbox1.addWidget(self.rb_max) hbox1.addStretch (1) vbox1.addLayout(hbox1) self.tl_cutmin = QtWidgets.QLabel(self) self.tl_cutmin.setText('Lower Cutoff Value: ') vbox1.addWidget(self.tl_cutmin) self.tl_cutmax = QtWidgets.QLabel(self) self.tl_cutmax.setText('Upper Cutoff Value: ') vbox1.addWidget(self.tl_cutmax) sizer1.setLayout(vbox1) vbox.addWidget(sizer1) hbox2 = QtWidgets.QHBoxLayout() self.button_ok = QtWidgets.QPushButton('Accept') self.button_ok.clicked.connect(self.OnAccept) self.button_ok.setEnabled(False) hbox2.addWidget(self.button_ok) button_cancel = QtWidgets.QPushButton('Cancel') button_cancel.clicked.connect(self.close) hbox2.addWidget(button_cancel) vbox.addLayout(hbox2) self.setLayout(vbox) if len(self.anlz.original_svd_maps4D) == 0: self.draw_histogram() else: self.draw_histogram4D() #---------------------------------------------------------------------- def draw_histogram(self): fig = self.histfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) self.axes = fig.gca() #target_svd_maps;target_pcafit_maps if self.parent.page4.showraw == True: self.histogram = self.anlz.original_svd_maps else: self.histogram = self.anlz.original_fit_maps histdata = np.reshape(self.histogram, (self.anlz.stack.n_cols*self.anlz.stack.n_rows*self.anlz.n_target_spectra), order='F') self.n, self.bins, patches = self.axes.hist(histdata, 200, normed=1, facecolor='green', alpha=0.75) self.axes.set_xlabel('Thickness per Pixel in Spectral Maps') self.axes.set_ylabel('Percentage of Pixels') self.HistogramPanel.draw() #---------------------------------------------------------------------- def draw_histogram4D(self): fig = self.histfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) self.axes = fig.gca() #target_svd_maps;target_pcafit_maps if self.parent.page4.showraw == True: self.histogram = self.anlz.original_svd_maps4D else: self.histogram = self.anlz.original_fit_maps4D histdata = np.reshape(self.histogram, (self.anlz.stack.n_cols*self.anlz.stack.n_rows*self.anlz.n_target_spectra*self.anlz.stack.n_theta), order='F') self.n, self.bins, patches = self.axes.hist(histdata, 200, normed=1, facecolor='green', alpha=0.75) self.axes.set_xlabel('Thickness per Pixel in Spectral Maps') self.axes.set_ylabel('Percentage of Pixels') self.HistogramPanel.draw() #---------------------------------------------------------------------- def OnClick(self, evt): x1 = evt.xdata if x1 == None: return if self.limit == 1: self.tl_cutmin.setText('Lower Cutoff Value: '+str(x1)) self.histmin = x1 else: self.tl_cutmax.setText('Upper Cutoff Value: '+str(x1)) self.histmax = x1 self.button_ok.setEnabled(True) #---------------------------------------------------------------------- def OnRb_limit(self, enabled): state = enabled if state: self.limit = 1 else: self.limit = 2 #---------------------------------------------------------------------- def OnAccept(self, evt): if self.parent.page4.showraw == True: self.anlz.svd_map_threshold(self.histmin, self.histmax, svd=True) else: self.anlz.svd_map_threshold(self.histmin, self.histmax, pca=True) self.parent.page4.loadTargetMap() self.close() #---------------------------------------------------------------------- class SaveWinP4(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(400, 300) self.setWindowTitle('Save') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.com = self.parent.common path, ext = os.path.splitext(self.com.filename) ext = ext[1:].lower() suffix = "." + ext path, fn = os.path.split(self.com.filename) filename = fn[:-len(suffix)] self.path = self.com.path self.filename = filename vboxtop = QtWidgets.QVBoxLayout() vboxtop.setContentsMargins(20,20,20,20) gridtop = QtWidgets.QGridLayout() gridtop.setVerticalSpacing(20) fontb = QtGui.QFont() fontb.setBold(True) st1 = QtWidgets.QLabel(self) st1.setText('Save') st1.setFont(fontb) st2 = QtWidgets.QLabel(self) st2.setText('.pdf') st2.setFont(fontb) st3 = QtWidgets.QLabel(self) st3.setText('.png') st3.setFont(fontb) st4 = QtWidgets.QLabel(self) st4.setText('.svg') st4.setFont(fontb) st5 = QtWidgets.QLabel(self) st5.setText('.csv') st5.setFont(fontb) st8 = QtWidgets.QLabel(self) st8.setText('.tif (data)') st8.setFont(fontb) st6 = QtWidgets.QLabel(self) st6.setText('_spectrum') self.cb11 = QtWidgets.QCheckBox('', self) self.cb11.setChecked(True) self.cb12 = QtWidgets.QCheckBox('', self) self.cb13 = QtWidgets.QCheckBox('', self) self.cb14 = QtWidgets.QCheckBox('', self) st7 = QtWidgets.QLabel(self) st7.setText('_images') self.cb21 = QtWidgets.QCheckBox('', self) self.cb21.setChecked(True) self.cb22 = QtWidgets.QCheckBox('', self) self.cb23 = QtWidgets.QCheckBox('', self) self.cb24 = QtWidgets.QCheckBox('', self) gridtop.addWidget(st1, 0, 0) gridtop.addWidget(st2, 0, 1) gridtop.addWidget(st3, 0, 2) gridtop.addWidget(st4, 0, 3) gridtop.addWidget(st5, 0, 4) gridtop.addWidget(st8, 0, 5) gridtop.addWidget(st6, 1, 0) gridtop.addWidget(self.cb11, 1, 1) gridtop.addWidget(self.cb12, 1, 2) gridtop.addWidget(self.cb13, 1, 3) gridtop.addWidget(self.cb14, 1, 4) gridtop.addWidget(st7, 2, 0) gridtop.addWidget(self.cb21, 2, 1) gridtop.addWidget(self.cb22, 2, 2) gridtop.addWidget(self.cb23, 2, 3) gridtop.addWidget(self.cb24, 2, 5) vboxtop.addStretch(1) vboxtop.addLayout(gridtop) vboxtop.addStretch(2) hbox0 = QtWidgets.QHBoxLayout() stf = QtWidgets.QLabel(self) stf.setText('Filename:\t') self.tc_savefn = QtWidgets.QLineEdit(self) self.tc_savefn.setText(self.filename) hbox0.addWidget(stf) hbox0.addWidget(self.tc_savefn) hbox1 = QtWidgets.QHBoxLayout() stp = QtWidgets.QLabel(self) stp.setText('Path: \t') self.tc_savepath = QtWidgets.QLineEdit(self) self.tc_savepath.setReadOnly(True) self.tc_savepath.setText(self.path) self.tc_savepath.setMinimumWidth(100) hbox1.addWidget(stp) hbox1.addWidget(self.tc_savepath) button_path = QtWidgets.QPushButton('Browse...') button_path.clicked.connect(self.OnBrowseDir) hbox1.addWidget(button_path) hbox2 = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save') button_save.clicked.connect(self.OnSave) hbox2.addWidget(button_save) button_cancel = QtWidgets.QPushButton('Cancel') button_cancel.clicked.connect(self.close) hbox2.addWidget(button_cancel) vboxtop.addLayout(hbox0) vboxtop.addLayout(hbox1) vboxtop.addStretch(1) vboxtop.addLayout(hbox2) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnBrowseDir(self, evt): directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", self.path, QtWidgets.QFileDialog.ShowDirsOnly|QtWidgets.QFileDialog.ReadOnly) if directory == '': return directory = str(directory) self.com.path = directory self.path = directory self.tc_savepath.setText(self.path) #---------------------------------------------------------------------- def OnSave(self, evt): self.filename = str(self.tc_savefn.text()) sp_pdf = self.cb11.isChecked() sp_png = self.cb12.isChecked() sp_svg = self.cb13.isChecked() sp_csv = self.cb14.isChecked() im_pdf = self.cb21.isChecked() im_png = self.cb22.isChecked() im_svg = self.cb23.isChecked() im_tif = self.cb24.isChecked() self.close() self.parent.page4.Save(self.filename, self.path, spec_png = sp_png, spec_pdf = sp_pdf, spec_svg = sp_svg, spec_csv = sp_csv, img_png = im_png, img_pdf = im_pdf, img_svg = im_svg, img_tif = im_tif) """ ------------------------------------------------------------------------------------------------""" class PageCluster(QtWidgets.QWidget): def __init__(self, common, data_struct, stack, anlz): super(PageCluster, self).__init__() self.initUI(common, data_struct, stack, anlz) #---------------------------------------------------------------------- def initUI(self, common, data_struct, stack, anlz): self.data_struct = data_struct self.stk = stack self.com = common self.anlz = anlz self.selcluster = 1 self.numclusters = 0 self.init_nclusters = 5 self.wo_1st_pca = 0 self.sigma_split = 0 self.showallspectra = 0 self.pcscalingfactor = 0.0 self.MakeColorTable() #panel 1 sizer1 = QtWidgets.QGroupBox('Cluster analysis') vbox1 = QtWidgets.QVBoxLayout() self.button_calcca = QtWidgets.QPushButton('Calculate Clusters') self.button_calcca.clicked.connect( self.OnCalcClusters) self.button_calcca.setEnabled(False) vbox1.addWidget(self.button_calcca) self.button_scatterplots = QtWidgets.QPushButton('Show scatter plots...') self.button_scatterplots.clicked.connect( self.OnShowScatterplots) self.button_scatterplots.setEnabled(False) self.button_savecluster = QtWidgets.QPushButton('Save CA Results...') self.button_savecluster.clicked.connect( self.OnSave) self.button_savecluster.setEnabled(False) vbox1.addStretch(1) hbox11 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText('Number of clusters') hbox11.addWidget(text1) self.nclusterspin = QtWidgets.QSpinBox() self.nclusterspin.setRange(2,20) self.nclusterspin.setValue(self.init_nclusters) self.nclusterspin.valueChanged[int].connect(self.OnNClusterspin) hbox11.addWidget(text1) hbox11.addWidget(self.nclusterspin) vbox1.addLayout(hbox11) hbox12 = QtWidgets.QHBoxLayout() text1a = QtWidgets.QLabel(self) text1a.setText("Number of clusters found") hbox12.addWidget(text1a) self.ntc_clusters_found = QtWidgets.QLabel(self) self.ntc_clusters_found.setText(str(self.numclusters)) hbox12.addWidget(self.ntc_clusters_found) vbox1.addLayout(hbox12) hbox13 = QtWidgets.QHBoxLayout() self.remove1stpcacb = QtWidgets.QCheckBox('Reduce thickness effects', self) self.remove1stpcacb.stateChanged.connect(self.OnRemove1stpca) hbox13.addWidget(self.remove1stpcacb) vbox1.addLayout(hbox13) hbox14 = QtWidgets.QHBoxLayout() self.cb_splitclusters = QtWidgets.QCheckBox('Divide clusters with large Sigma', self) self.cb_splitclusters.stateChanged.connect(self.OnSplitClusters) hbox14.addWidget(self.cb_splitclusters) vbox1.addLayout(hbox14) hbox14a = QtWidgets.QHBoxLayout() tc1 = QtWidgets.QLabel(self) tc1.setText("PC scaling factor") hbox14a.addWidget(tc1) self.ntc_pcscaling = QtWidgets.QLineEdit(self) self.ntc_pcscaling.setFixedWidth(65) self.ntc_pcscaling.setValidator(QtGui.QDoubleValidator(0, 99999, 2, self)) self.ntc_pcscaling.setAlignment(QtCore.Qt.AlignRight) self.ntc_pcscaling.setText(str(self.pcscalingfactor)) hbox14a.addWidget(self.ntc_pcscaling) hbox14a.addStretch(1) vbox1.addLayout(hbox14a) line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) vbox1.addStretch(1) vbox1.addWidget(line) vbox1.addStretch(1) vbox1.addWidget(self.button_scatterplots) vbox1.addWidget(self.button_savecluster) sizer1.setLayout(vbox1) #panel 2 vbox2 = QtWidgets.QVBoxLayout() tc_clustercomp = QtWidgets.QLabel(self) tc_clustercomp.setText("Composite cluster image") vbox2.addWidget(tc_clustercomp) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.clusterimgfig = Figure((PlotH, PlotH)) self.ClusterImagePan = FigureCanvas(self.clusterimgfig) self.ClusterImagePan.mpl_connect('button_press_event', self.OnPointClusterImage) self.ClusterImagePan.setParent(self) fbox.addWidget(self.ClusterImagePan) frame.setLayout(fbox) vbox2.addWidget(frame) #panel 3 vbox3 = QtWidgets.QVBoxLayout() fgs = QtWidgets.QGridLayout() self.tc_cluster = QtWidgets.QLabel(self) self.tc_cluster.setText("Cluster ") fgs.addWidget(self.tc_cluster, 0, 0, QtCore .Qt. AlignLeft) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() #self.clusterindvimgfig = Figure((PlotH*0.73, PlotH*0.73)) self.clusterindvimgfig = Figure((PlotH, PlotH)) self.ClusterIndvImagePan = FigureCanvas(self.clusterindvimgfig) self.ClusterIndvImagePan.setParent(self) fbox.addWidget(self.ClusterIndvImagePan) frame.setLayout(fbox) fgs.addWidget(frame, 1, 0, QtCore .Qt. AlignLeft) self.slidershow = QtWidgets.QScrollBar(QtCore.Qt.Vertical) self.slidershow.setFocusPolicy(QtCore.Qt.StrongFocus) self.slidershow.setEnabled(False) self.slidershow.valueChanged[int].connect(self.OnClusterScroll) self.slidershow.setRange(1, 20) fgs.addWidget(self.slidershow, 1, 1, QtCore .Qt. AlignLeft) text3 = QtWidgets.QLabel(self) text3.setText('Cluster Error Map') fgs.addWidget(text3, 0, 2, QtCore .Qt. AlignLeft) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.clusterdistmapfig = Figure((PlotH, PlotH)) self.ClusterDistMapPan = FigureCanvas(self.clusterdistmapfig) self.ClusterDistMapPan.setParent(self) fbox.addWidget(self.ClusterDistMapPan) frame.setLayout(fbox) fgs.addWidget(frame, 1, 2, QtCore .Qt. AlignLeft) vbox3.addLayout(fgs) #panel 4 vbox4 = QtWidgets.QVBoxLayout() self.tc_clustersp = QtWidgets.QLabel(self) self.tc_clustersp.setText("Cluster spectrum") vbox4.addWidget(self.tc_clustersp) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.clusterspecfig = Figure((PlotW, PlotH)) self.ClusterSpecPan = FigureCanvas(self.clusterspecfig) self.ClusterSpecPan.setParent(self) fbox.addWidget(self.ClusterSpecPan) frame.setLayout(fbox) vbox4.addWidget(frame) #panel 5 sizer5 = QtWidgets.QGroupBox('Display') vbox5 = QtWidgets.QVBoxLayout() hbox51 = QtWidgets.QHBoxLayout() self.showallspectracb = QtWidgets.QCheckBox('Show all spectra', self) self.showallspectracb.stateChanged.connect(self.OnShowallspectra) hbox51.addWidget(self.showallspectracb) vbox5.addLayout(hbox51) sizer5.setLayout(vbox5) vboxtop = QtWidgets.QVBoxLayout() hboxtopL = QtWidgets.QHBoxLayout() vboxtopL = QtWidgets.QVBoxLayout() vboxtopL.addWidget(sizer1) vboxtopL.addWidget(sizer5) hboxtopL.addLayout(vboxtopL) hboxtopL.addStretch(1) gridsizertop = QtWidgets.QGridLayout() gridsizertop.setContentsMargins(15,0,0,0) gridsizertop.addLayout(hboxtopL, 0, 0, QtCore .Qt. AlignLeft) gridsizertop.addLayout(vbox2, 1, 0, QtCore .Qt. AlignLeft) gridsizertop.addLayout(vbox3, 0, 1, QtCore .Qt. AlignLeft) gridsizertop.addLayout(vbox4, 1, 1, QtCore .Qt. AlignLeft) vboxtop.addStretch(1) vboxtop.addLayout(gridsizertop) vboxtop.addStretch(1) self.setLayout(vboxtop) #---------------------------------------------------------------------- def MakeColorTable(self): self.maxclcolors = 11 colors_i = np.linspace(0,self.maxclcolors,self.maxclcolors+1) # self.colors=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', # '#FF470A','#33FFFF','#006600','#CCCC99','#993300', # '#000000'] # self.colors=['#D98619','#ED2024','#98CC31','#861F78','#007FFF', # '#6FDBDB','#5C3F32','#FF6EC7','#CCCC99','#993300', # '#000000'] self.colors=['#007FFF','#ED2024','#98CC31','#861F78','#D98619', '#6FDBDB','#5C3F32','#FF6EC7','#CCCC99','#993300', '#000000'] self.clusterclrmap1=matplotlib.colors.LinearSegmentedColormap.from_list('clustercm',self.colors) self.bnorm1 = matplotlib.colors.BoundaryNorm(colors_i, self.clusterclrmap1.N) colors_i = np.linspace(0,self.maxclcolors+2,self.maxclcolors+3) #use black color for clusters > maxclcolors, the other 2 colors are for background # colors2=['#0000FF','#FF0000','#DFE32D','#36F200','#B366FF', # '#FF470A','#33FFFF','#006600','#CCCC99','#993300', # '#000000','#FFFFFF','#EEEEEE'] # colors2=['#D98619','#ED2024','#98CC31','#861F78','#007FFF', # '#6FDBDB','#5C3F32','#FF6EC7','#CCCC99','#993300', # '#000000','#FFFFFF','#EEEEEE'] colors2=['#007FFF','#ED2024','#98CC31','#861F78','#D98619', '#6FDBDB','#5C3F32','#FF6EC7','#CCCC99','#993300', '#000000','#FFFFFF','#EEEEEE'] self.clusterclrmap2=matplotlib.colors.LinearSegmentedColormap.from_list('clustercm2',colors2) self.bnorm2 = matplotlib.colors.BoundaryNorm(colors_i, self.clusterclrmap2.N) #---------------------------------------------------------------------- def OnCalcClusters(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.calcclusters = False try: value = self.ntc_pcscaling.text() #try: self.pcscalingfactor = float(value) # except: # self.pcscalingfactor = 0.0 # self.ntc_pcscaling.setText('0.0') self.CalcClusters() self.calcclusters = True self.selcluster = 1 self.slidershow.setValue(self.selcluster) self.slidershow.setMaximum(self.numclusters) self.showClusterImage() self.showClusterSpectrum() self.showIndvClusterImage() self.showClusterDistanceMap() self.com.cluster_calculated = 1 QtWidgets.QApplication.restoreOverrideCursor() except Exception as e: self.com.cluster_calculated = 0 QtWidgets.QApplication.restoreOverrideCursor() print(e) self.window().refresh_widgets() #---------------------------------------------------------------------- def OnNClusterspin(self, value): num = value self.init_nclusters = num #---------------------------------------------------------------------- def OnClusterScroll(self, value): sel = value self.selcluster = sel if self.com.cluster_calculated == 1: self.showClusterSpectrum() self.showIndvClusterImage() #---------------------------------------------------------------------- def OnClusterSpinUp(self, event): if (self.com.cluster_calculated == 1) and (self.selcluster > 1): self.selcluster = self.selcluster - 1 self.slidershow.setValue(self.selcluster) self.showClusterSpectrum() self.showIndvClusterImage() #---------------------------------------------------------------------- def OnClusterSpinDown(self, event): if (self.com.cluster_calculated == 1) and (self.selcluster < self.numclusters): self.selcluster = self.selcluster + 1 self.slidershow.setValue(self.selcluster) self.showClusterSpectrum() self.showIndvClusterImage() #---------------------------------------------------------------------- def OnPointClusterImage(self, evt): x = evt.xdata y = evt.ydata if (x == None) or (y == None): return if self.com.cluster_calculated == 1: try: self.ix = int(np.floor(x)) self.iy = self.stk.n_rows-1-int(np.floor(y)) if self.ix<0 : self.ix=0 if self.ix>self.stk.n_cols-1 : self.ix=self.stk.n_cols-1 if self.iy<0 : self.iy=0 if self.iy>self.stk.n_rows-1 : self.iy=self.stk.n_rows-1 self.selcluster = self.anlz.cluster_indices[self.ix,self.iy] + 1 self.slidershow.setValue(self.selcluster) self.showClusterSpectrum() self.showIndvClusterImage() except: pass #---------------------------------------------------------------------- def CalcClusters(self): nclusters = self.anlz.calculate_clusters(self.init_nclusters, self.wo_1st_pca, self.sigma_split, pcscalingfactor = self.pcscalingfactor) #nclusters = self.anlz.calculate_clusters_kmeansangle(self.init_nclusters, self.wo_1st_pca, self.sigma_split) self.numclusters = nclusters self.ntc_clusters_found.setText(str(self.numclusters)) #---------------------------------------------------------------------- #Show composite cluster image def showClusterImage(self): self.clusterimage = self.anlz.cluster_indices #print self.selpca fig = self.clusterimgfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() im = axes.imshow(np.rot90(self.clusterimage), cmap=self.clusterclrmap1, norm=self.bnorm1) axes.axis("off") #cbar = axes.figure.colorbar(im) self.ClusterImagePan.draw() #---------------------------------------------------------------------- #Show composite cluster image def showIndvClusterImage(self): indvclusterimage = self.anlz.cluster_indices.copy() indvclusterimage[indvclusterimage!=self.selcluster-1] = 20. fig = self.clusterindvimgfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() im = axes.imshow(np.rot90(indvclusterimage), cmap=self.clusterclrmap2, norm=self.bnorm2) axes.axis("off") self.ClusterIndvImagePan.draw() self.tc_cluster.setText("Cluster " + str(self.selcluster)) #---------------------------------------------------------------------- def showClusterSpectrum(self): clusterspectrum = self.anlz.clusterspectra[self.selcluster-1, ] fig = self.clusterspecfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() if self.showallspectra == 0: if self.selcluster >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[self.selcluster-1] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) self.tc_clustersp.setText("Cluster " + str(self.selcluster)+ " spectrum" ) else: #Show all spectra for i in range(1, self.numclusters+1): if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i-1] clusterspectrum = self.anlz.clusterspectra[i-1, ]/np.amax(self.anlz.clusterspectra[i-1, ]) specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) self.tc_clustersp.setText(" Normalized Cluster spectra" ) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') self.ClusterSpecPan.draw() #---------------------------------------------------------------------- def showClusterDistanceMap(self): mapimage = self.anlz.cluster_distances #print self.selpca fig = self.clusterdistmapfig fig.clf() fig.add_axes((0.02,0.02,0.96,0.96)) axes = fig.gca() # divider = make_axes_locatable(axes) # axcb = divider.new_horizontal(size="3%", pad=0.03) # fig.add_axes(axcb) # axes.set_position([0.03,0.03,0.8,0.94]) im = axes.imshow(np.rot90(mapimage), cmap=matplotlib.colormaps["gray"]) #cbar = axes.figure.colorbar(im, orientation='vertical',cax=axcb) axes.axis("off") self.ClusterDistMapPan.draw() #---------------------------------------------------------------------- def OnRemove1stpca(self, state): if state == QtCore.Qt.Checked: self.wo_1st_pca = 1 else: self.wo_1st_pca = 0 #---------------------------------------------------------------------- def OnSplitClusters(self, state): if state == QtCore.Qt.Checked: self.sigma_split = 1 else: self.sigma_split = 0 #---------------------------------------------------------------------- def OnShowallspectra(self, state): if state == QtCore.Qt.Checked: self.showallspectra = 1 else: self.showallspectra = 0 if self.com.cluster_calculated == 1: self.showClusterSpectrum() #---------------------------------------------------------------------- def OnSave(self, event): savewin = SaveWinP3(self.window()) savewin.show() #---------------------------------------------------------------------- def Save(self, filename, path, spec_png = True, spec_pdf = False, spec_svg = False, spec_csv = False, img_png = True, img_pdf = False, img_svg = False, img_tif = False, indimgs_png = True, indimgs_pdf = False, indimgs_svg = False, indimgs_tif = False, scatt_png = True, scatt_pdf = False, scatt_svg = False): self.SaveFileName = os.path.join(path,filename) from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas matplotlib.rcParams['pdf.fonttype'] = 42 try: if img_png: ext = 'png' fig = matplotlib.figure.Figure(figsize = (float(self.stk.n_rows)/10, float(self.stk.n_cols)/10)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(np.rot90(self.clusterimage), cmap=self.clusterclrmap1, norm=self.bnorm1) axes.axis("off") fileName_caimg = self.SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=ImgDpi, pad_inches = 0.0) if img_pdf: ext = 'pdf' suffix = "." + ext fig = matplotlib.figure.Figure(figsize = (float(self.stk.n_rows)/30, float(self.stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(np.rot90(self.clusterimage), cmap=self.clusterclrmap1, norm=self.bnorm1) axes.axis("off") fileName_caimg = self.SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=300, pad_inches = 0.0) if img_svg: ext = 'svg' suffix = "." + ext fig = matplotlib.figure.Figure(figsize = (float(self.stk.n_rows)/30, float(self.stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.clf() fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(np.rot90(self.clusterimage), cmap=self.clusterclrmap1, norm=self.bnorm1) axes.axis("off") fileName_caimg = self.SaveFileName+"_CAcimg."+ext fig.savefig(fileName_caimg, dpi=300, pad_inches = 0.0) if img_tif: fileName_caimg = self.SaveFileName+"_CAcimg.tif" img1 = Image.fromarray(self.clusterimage) #ToDo: Recently throws an error. Possible conflict in module PIL. "Cannot handle this data type: (1, 1), = self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) #Save all spectra in one plot fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() for i in range(1, self.numclusters+1): clusterspectrum = self.anlz.clusterspectra[i-1, ]/np.amax(self.anlz.clusterspectra[i-1, ]) if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i-1] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) if spec_csv: for i in range (self.numclusters): clusterspectrum = self.anlz.clusterspectra[i, ] fileName_spec = self.SaveFileName+"_CAspectrum_" +str(i+1)+".csv" cname = 'CAspectrum_' +str(i+1) self.stk.write_csv(fileName_spec, self.anlz.stack.ev, clusterspectrum, cname=cname) ext = 'pdf' suffix = "." + ext if indimgs_pdf: for i in range (self.numclusters): indvclusterimage = np.zeros((self.anlz.stack.n_cols, self.anlz.stack.n_rows))+20. ind = np.where(self.anlz.cluster_indices == i) colorcl = min(i,9) indvclusterimage[ind] = colorcl fig = matplotlib.figure.Figure(figsize =(float(self.stk.n_rows)/30, float(self.stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(np.rot90(indvclusterimage), cmap=self.clusterclrmap2, norm=self.bnorm2) axes.axis("off") fileName_img = self.SaveFileName+"_CAimg_" +str(i+1)+"."+ext fig.savefig(fileName_img, dpi=300, pad_inches = 0.0) if spec_pdf: for i in range (self.numclusters): clusterspectrum = self.anlz.clusterspectra[i, ] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) #Save all spectra in one plot fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() for i in range(1, self.numclusters+1): clusterspectrum = self.anlz.clusterspectra[i-1, ]/np.amax(self.anlz.clusterspectra[i-1, ]) if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i-1] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) ext = 'svg' suffix = "." + ext if indimgs_svg: for i in range (self.numclusters): indvclusterimage = np.zeros((self.anlz.stack.n_cols, self.anlz.stack.n_rows))+20. ind = np.where(self.anlz.cluster_indices == i) colorcl = min(i,9) indvclusterimage[ind] = colorcl fig = matplotlib.figure.Figure(figsize =(float(self.stk.n_rows)/30, float(self.stk.n_cols)/30)) canvas = FigureCanvas(fig) fig.add_axes((0.0,0.0,1.0,1.0)) axes = fig.gca() im = axes.imshow(np.rot90(indvclusterimage), cmap=self.clusterclrmap2, norm=self.bnorm2) axes.axis("off") fileName_img = self.SaveFileName+"_CAimg_" +str(i+1)+"."+ext fig.savefig(fileName_img, dpi=300, pad_inches = 0.0) if spec_svg: for i in range (self.numclusters): clusterspectrum = self.anlz.clusterspectra[i, ] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) #Save all spectra in one plot fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() for i in range(1, self.numclusters+1): clusterspectrum = self.anlz.clusterspectra[i-1, ]/np.amax(self.anlz.clusterspectra[i-1, ]) if i >= self.maxclcolors: clcolor = self.colors[self.maxclcolors-1] else: clcolor = self.colors[i-1] specplot = axes.plot(self.anlz.stack.ev,clusterspectrum, color = clcolor) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_CAspectra"+"."+ext fig.savefig(fileName_spec) if indimgs_tif: for i in range (self.numclusters): indvclusterimage = np.zeros((self.anlz.stack.n_cols, self.anlz.stack.n_rows))+20. ind = np.where(self.anlz.cluster_indices == i) colorcl = min(i,9) indvclusterimage[ind] = colorcl fileName_img = self.SaveFileName+"_CAimg_" +str(i+1)+".tif" img1 = Image.fromarray(indvclusterimage) img1.save(fileName_img) if scatt_png: self.SaveScatt(png_pdf = 1) if scatt_pdf: self.SaveScatt(png_pdf = 2) if scatt_svg: self.SaveScatt(png_pdf = 3) except IOError as e: if e.strerror: err = e.strerror else: err = e print(e) QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #---------------------------------------------------------------------- #If png_pdg = 1 save png, if =2 save pdf, if =3 save svg def SaveScatt(self, png_pdf = 1): od_reduced = self.anlz.pcaimages[:,:,0:self.anlz.numsigpca] od_reduced = np.reshape(od_reduced, (self.stk.n_cols*self.stk.n_rows,self.anlz.numsigpca), order='F') clindices = self.anlz.cluster_indices clindices = np.reshape(clindices, (self.stk.n_cols*self.stk.n_rows), order='F') path, ext = os.path.splitext(self.SaveFileName) ext = ext[1:].lower() if png_pdf == 1: ext = 'png' elif png_pdf == 2: ext = 'pdf' elif png_pdf == 3: ext = 'svg' try: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) suffix = "." + ext nplots = 0 for ip in range(self.anlz.numsigpca): for jp in range(self.anlz.numsigpca): if jp >= (ip+1): nplots = nplots+1 nplotsrows = np.ceil(nplots/2) plotsize = 2.5 from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas if nplots > 1 : fig = matplotlib.figure.Figure(figsize =(6.0,plotsize*nplotsrows)) fig.subplots_adjust(wspace = 0.4, hspace = 0.4) else: fig = matplotlib.figure.Figure(figsize =(3.0,2.5)) fig.subplots_adjust(bottom = 0.2, left = 0.2) canvas = FigureCanvas(fig) #axes = fig.gca() matplotlib.rcParams['font.size'] = 6 pplot = 1 for ip in range(self.anlz.numsigpca): for jp in range(self.anlz.numsigpca): if jp >= (ip+1): x_comp = od_reduced[:,ip] y_comp = od_reduced[:,jp] if nplots > 1 : axes = fig.add_subplot(int(nplotsrows),int(2), int(pplot)) else: axes = fig.add_subplot(1,1,1) pplot = pplot+1 for i in range(self.numclusters): thiscluster = np.where(clindices == i) axes.plot(x_comp[thiscluster], y_comp[thiscluster],'.',color=self.colors[i],alpha=0.5) axes.set_xlabel('Component '+str(ip+1)) axes.set_ylabel('Component '+str(jp+1)) fileName_sct = self.SaveFileName+"_CAscatterplot_" +str(i+1)+"."+ext matplotlib.rcParams['pdf.fonttype'] = 42 fig.savefig(fileName_sct) QtWidgets.QApplication.restoreOverrideCursor() except IOError as e: QtWidgets.QApplication.restoreOverrideCursor() if e.strerror: err = e.strerror else: err = e QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #---------------------------------------------------------------------- def OnShowScatterplots(self, evt): scattplwin = Scatterplots(self.window(), self.com, self.anlz) scattplwin.show() #---------------------------------------------------------------------- class Scatterplots(QtWidgets.QDialog): def __init__(self, parent, common, analz): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(600, 470) self.setWindowTitle('Scatter plots') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.colors = self.parent.page3.colors self.com = common self.anlz = analz self.numsigpca = self.anlz.numsigpca self.ncols = self.anlz.stack.n_cols self.nrows = self.anlz.stack.n_rows self.od_reduced = self.anlz.pcaimages[:,:,0:self.numsigpca] self.od_reduced = np.reshape(self.od_reduced, (self.ncols*self.nrows,self.numsigpca), order='F') self.clindices = self.anlz.cluster_indices self.clindices = np.reshape(self.clindices, (self.ncols*self.nrows), order='F') self.numclusters = self.parent.page3.numclusters self.pca_y = 1 self.pca_x = 1 vbox = QtWidgets.QVBoxLayout() grid1 = QtWidgets.QGridLayout() frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.scattplfig = Figure((5.0, 4.8)) self.ScatterPPanel = FigureCanvas(self.scattplfig) self.ScatterPPanel.setParent(self) fbox.addWidget(self.ScatterPPanel) frame.setLayout(fbox) self.slidershow_y = QtWidgets.QSlider(QtCore.Qt.Vertical) self.slidershow_y.setFocusPolicy(QtCore.Qt.StrongFocus) self.slidershow_y.setRange(1, self.numsigpca) self.slidershow_y.setValue(self.pca_y) self.slidershow_y.valueChanged[int].connect(self.OnSliderScroll_y) grid1.addWidget(self.slidershow_y, 0, 0) grid1.addWidget(frame, 0, 1) self.slidershow_x = QtWidgets.QSlider(QtCore.Qt.Horizontal) self.slidershow_x.setFocusPolicy(QtCore.Qt.StrongFocus) self.slidershow_x.setRange(1, self.numsigpca) self.slidershow_x.setValue(self.pca_x) self.slidershow_x.valueChanged[int].connect(self.OnSliderScroll_x) #grid1.addWidget(wx.StaticText(panel, -1, '')) grid1.addWidget(self.slidershow_x, 1, 1) hbox = QtWidgets.QVBoxLayout() button_close = QtWidgets.QPushButton('Close') button_close.clicked.connect( self.close) hbox.addStretch(1) hbox.addWidget(button_close) vbox.addLayout(grid1) vbox.addLayout(hbox) self.setLayout(vbox) self.draw_scatterplot() #---------------------------------------------------------------------- def OnSliderScroll_x(self, value): self.pca_x = value self.draw_scatterplot() #---------------------------------------------------------------------- def OnSliderScroll_y(self, value): self.pca_y = value self.draw_scatterplot() #---------------------------------------------------------------------- def draw_scatterplot(self): x_comp = self.od_reduced[:,self.pca_x-1] y_comp = self.od_reduced[:,self.pca_y-1] fig = self.scattplfig fig.clf() axes = fig.gca() for i in range(self.numclusters): thiscluster = np.where(self.clindices == i) axes.plot(x_comp[thiscluster], y_comp[thiscluster],'.',color=self.colors[i],alpha=0.5) axes.set_xlabel('Component '+str(self.pca_x)) axes.set_ylabel('Component '+str(self.pca_y)) self.ScatterPPanel.draw() #---------------------------------------------------------------------- class SaveWinP3(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(400, 300) self.setWindowTitle('Save') pal = QtGui.QPalette() self.setAutoFillBackground(True) #pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.com = self.parent.common path, ext = os.path.splitext(self.com.filename) ext = ext[1:].lower() suffix = "." + ext path, fn = os.path.split(self.com.filename) filename = fn[:-len(suffix)] self.path = self.com.path self.filename = filename vboxtop = QtWidgets.QVBoxLayout() vboxtop.setContentsMargins(20,20,20,20) gridtop = QtWidgets.QGridLayout() gridtop.setVerticalSpacing(20) fontb = QtGui.QFont() fontb.setBold(True) st1 = QtWidgets.QLabel(self) st1.setText('Save') st1.setFont(fontb) st2 = QtWidgets.QLabel(self) st2.setText('.pdf') st2.setFont(fontb) st3 = QtWidgets.QLabel(self) st3.setText('.png') st3.setFont(fontb) st4 = QtWidgets.QLabel(self) st4.setText('.svg') st4.setFont(fontb) st5 = QtWidgets.QLabel(self) st5.setText('.csv') st5.setFont(fontb) st10 = QtWidgets.QLabel(self) st10.setText('.tif (data)') st10.setFont(fontb) st6 = QtWidgets.QLabel(self) st6.setText('_spectrum') self.cb11 = QtWidgets.QCheckBox('', self) self.cb11.setChecked(True) self.cb12 = QtWidgets.QCheckBox('', self) self.cb13 = QtWidgets.QCheckBox('', self) self.cb14 = QtWidgets.QCheckBox('', self) st7 = QtWidgets.QLabel(self) st7.setText('_composite_images') self.cb21 = QtWidgets.QCheckBox('', self) self.cb21.setChecked(True) self.cb22 = QtWidgets.QCheckBox('', self) self.cb23 = QtWidgets.QCheckBox('', self) self.cb24 = QtWidgets.QCheckBox('', self) st8 = QtWidgets.QLabel(self) st8.setText('_individual_images') self.cb31 = QtWidgets.QCheckBox('', self) self.cb31.setChecked(True) self.cb32 = QtWidgets.QCheckBox('', self) self.cb33 = QtWidgets.QCheckBox('', self) self.cb34 = QtWidgets.QCheckBox('', self) st9 = QtWidgets.QLabel(self) st9.setText('_scatter_plots') self.cb41 = QtWidgets.QCheckBox('', self) self.cb41.setChecked(True) self.cb42 = QtWidgets.QCheckBox('', self) self.cb43 = QtWidgets.QCheckBox('', self) gridtop.addWidget(st1, 0, 0) gridtop.addWidget(st2, 0, 1) gridtop.addWidget(st3, 0, 2) gridtop.addWidget(st4, 0, 3) gridtop.addWidget(st5, 0, 4) gridtop.addWidget(st10, 0, 5) gridtop.addWidget(st6, 1, 0) gridtop.addWidget(self.cb11, 1, 1) gridtop.addWidget(self.cb12, 1, 2) gridtop.addWidget(self.cb13, 1, 3) gridtop.addWidget(self.cb14, 1, 4) gridtop.addWidget(st7, 2, 0) gridtop.addWidget(self.cb21, 2, 1) gridtop.addWidget(self.cb22, 2, 2) gridtop.addWidget(self.cb23, 2, 3) gridtop.addWidget(self.cb24, 2, 5) gridtop.addWidget(st8, 3, 0) gridtop.addWidget(self.cb31, 3, 1) gridtop.addWidget(self.cb32, 3, 2) gridtop.addWidget(self.cb33, 3, 3) gridtop.addWidget(self.cb34, 3, 5) gridtop.addWidget(st9, 4, 0) gridtop.addWidget(self.cb41, 4, 1) gridtop.addWidget(self.cb42, 4, 2) gridtop.addWidget(self.cb43, 4, 3) vboxtop.addStretch(1) vboxtop.addLayout(gridtop) vboxtop.addStretch(2) hbox0 = QtWidgets.QHBoxLayout() stf = QtWidgets.QLabel(self) stf.setText('Filename:\t') self.tc_savefn = QtWidgets.QLineEdit(self) self.tc_savefn.setText(self.filename) hbox0.addWidget(stf) hbox0.addWidget(self.tc_savefn) hbox1 = QtWidgets.QHBoxLayout() stp = QtWidgets.QLabel(self) stp.setText('Path: \t') self.tc_savepath = QtWidgets.QLineEdit(self) self.tc_savepath.setReadOnly(True) self.tc_savepath.setText(self.path) self.tc_savepath.setMinimumWidth(100) hbox1.addWidget(stp) hbox1.addWidget(self.tc_savepath) button_path = QtWidgets.QPushButton('Browse...') button_path.clicked.connect(self.OnBrowseDir) hbox1.addWidget(button_path) hbox2 = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save') button_save.clicked.connect(self.OnSave) hbox2.addWidget(button_save) button_cancel = QtWidgets.QPushButton('Cancel') button_cancel.clicked.connect(self.close) hbox2.addWidget(button_cancel) vboxtop.addLayout(hbox0) vboxtop.addLayout(hbox1) vboxtop.addStretch(1) vboxtop.addLayout(hbox2) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnBrowseDir(self, evt): directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", self.path, QtWidgets.QFileDialog.ShowDirsOnly|QtWidgets.QFileDialog.ReadOnly) if directory == '': return directory = str(directory) self.com.path = directory self.path = directory self.tc_savepath.setText(self.path) #---------------------------------------------------------------------- def OnSave(self, evt): self.filename = str(self.tc_savefn.text()) sp_pdf = self.cb11.isChecked() sp_png = self.cb12.isChecked() sp_svg = self.cb13.isChecked() sp_csv = self.cb14.isChecked() im_pdf = self.cb21.isChecked() im_png = self.cb22.isChecked() im_svg = self.cb23.isChecked() im_tif = self.cb24.isChecked() indim_pdf = self.cb31.isChecked() indim_png = self.cb32.isChecked() indim_svg = self.cb33.isChecked() indim_tif = self.cb34.isChecked() scatt_pdf = self.cb41.isChecked() scatt_png = self.cb42.isChecked() scatt_svg = self.cb43.isChecked() self.close() self.parent.page3.Save(self.filename, self.path, spec_png = sp_png, spec_pdf = sp_pdf, spec_svg = sp_svg, spec_csv = sp_csv, img_png = im_png, img_pdf = im_pdf, img_svg = im_svg, img_tif = im_tif, indimgs_png = indim_png, indimgs_pdf = indim_pdf, indimgs_svg = indim_svg, indimgs_tif = indim_tif, scatt_png = scatt_png, scatt_pdf = scatt_pdf, scatt_svg = scatt_svg) """ ------------------------------------------------------------------------------------------------""" class PagePCA(QtWidgets.QWidget): def __init__(self, common, data_struct, stack, anlz): super(PagePCA, self).__init__() self.initUI(common, data_struct, stack, anlz) #---------------------------------------------------------------------- def initUI(self, common, data_struct, stack, anlz): self.com = common self.data_struct = data_struct self.stk = stack self.anlz = anlz self.selpca = 1 self.numsigpca = 2 self.itheta = 0 #panel 1 vbox1 = QtWidgets.QVBoxLayout() self.tc_PCAcomp = QtWidgets.QLabel(self) self.tc_PCAcomp.setText("PCA component ") vbox1.addWidget(self.tc_PCAcomp) hbox11 = QtWidgets.QHBoxLayout() frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.pcaimgfig = Figure((PlotH*1.10, PlotH)) self.PCAImagePan = FigureCanvas(self.pcaimgfig) self.PCAImagePan.setParent(self) fbox.addWidget(self.PCAImagePan) frame.setLayout(fbox) hbox11.addWidget(frame) self.slidershow = QtWidgets.QScrollBar(QtCore.Qt.Vertical) self.slidershow.setFocusPolicy(QtCore.Qt.StrongFocus) self.slidershow.setRange(1,20) self.slidershow.setEnabled(False) self.slidershow.valueChanged[int].connect(self.OnPCAScroll) hbox11.addWidget(self.slidershow) vbox1.addLayout(hbox11) self.slider_theta = QtWidgets.QScrollBar(QtCore.Qt.Horizontal) self.slider_theta.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) self.slider_theta.setRange(0, 100) self.slider_theta.setMinimumWidth(250) self.slider_theta.setVisible(False) self.tc_imagetheta = QtWidgets.QLabel(self) self.tc_imagetheta.setText("4D Data Angle: ") self.tc_imagetheta.setVisible(False) hbox51 = QtWidgets.QHBoxLayout() hbox51.addWidget(self.tc_imagetheta) hbox51.addStretch(1) hbox51.addWidget(self.slider_theta) hbox51.addStretch(1) vbox1.addLayout(hbox51) #panel 2 vbox2 = QtWidgets.QVBoxLayout() vbox2.setContentsMargins(20,20,20,20) sizer2 = QtWidgets.QGroupBox('PCA') vbox21 = QtWidgets.QVBoxLayout() self.button_calcpca = QtWidgets.QPushButton('Calculate PCA') self.button_calcpca.clicked.connect( self.OnCalcPCA) self.button_calcpca.setEnabled(False) vbox21.addWidget(self.button_calcpca) self.button_savepca = QtWidgets.QPushButton('Save PCA Results...') self.button_savepca.clicked.connect( self.OnSave) self.button_savepca.setEnabled(False) vbox21.addWidget(self.button_savepca) hbox21 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText('Number of significant components') self.npcaspin = QtWidgets.QSpinBox() self.npcaspin.setRange(1,20) self.npcaspin.valueChanged[int].connect(self.OnNPCAspin) hbox21.addWidget(text1) hbox21.addWidget(self.npcaspin) vbox21.addLayout(hbox21) hbox22 = QtWidgets.QHBoxLayout() text2 = QtWidgets.QLabel(self) text2.setText( 'Cumulative variance') self.vartc = QtWidgets.QLabel(self) self.vartc.setText('0%') hbox22.addWidget(text2) hbox22.addWidget(self.vartc) vbox21.addLayout(hbox22) self.button_movepcup = QtWidgets.QPushButton('Move PC up') self.button_movepcup.clicked.connect( self.OnMovePCUP) self.button_movepcup.setEnabled(False) vbox21.addWidget(self.button_movepcup) # line = QtWidgets.QFrame() # line.setFrameShape(QtWidgets.QFrame.HLine) # line.setFrameShadow(QtWidgets.QFrame.Sunken) # vbox21.addWidget(line) self.button_calcpca4D = QtWidgets.QPushButton('Calculate PCA for all angles') self.button_calcpca4D.clicked.connect( self.OnCalcPCA4D) self.button_calcpca4D.setEnabled(False) self.button_calcpca4D.setVisible(False) vbox21.addWidget(self.button_calcpca4D) sizer2.setLayout(vbox21) vbox2.addStretch(1) vbox2.addWidget(sizer2) vbox2.addStretch(3) #panel 3 vbox3 = QtWidgets.QVBoxLayout() self.text_pcaspec = QtWidgets.QLabel(self) self.text_pcaspec.setText("PCA spectrum ") vbox3.addWidget(self.text_pcaspec) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.pcaspecfig = Figure((PlotW, PlotH)) self.PCASpecPan = FigureCanvas(self.pcaspecfig) self.PCASpecPan.setParent(self) fbox.addWidget(self.PCASpecPan) frame.setLayout(fbox) vbox3.addWidget(frame) #panel 4 vbox4 = QtWidgets.QVBoxLayout() text4 = QtWidgets.QLabel(self) text4.setText("PCA eigenvalues ") vbox4.addWidget(text4) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.pcaevalsfig = Figure((PlotW, PlotH*0.75)) self.PCAEvalsPan = FigureCanvas(self.pcaevalsfig) self.PCAEvalsPan.setParent(self) self.PCAEvalsPan.mpl_connect('button_press_event', self.OnPointEvalsImage) fbox.addWidget(self.PCAEvalsPan) frame.setLayout(fbox) vbox4.addWidget(frame) vboxtop = QtWidgets.QVBoxLayout() gridsizertop = QtWidgets.QGridLayout() gridsizertop.addLayout(vbox2, 0, 0, QtCore .Qt. AlignLeft) gridsizertop.addLayout(vbox4, 0, 1) gridsizertop.addLayout(vbox1, 1, 0, QtCore .Qt. AlignLeft) gridsizertop.addLayout(vbox3, 1, 1) vboxtop.addStretch(1) vboxtop.addLayout(gridsizertop) vboxtop.addStretch(1) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnCalcPCA(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.calcpca = False self.selpca = 1 self.numsigpca = 2 self.slidershow.setValue(self.selpca) scrollmax = np.min([self.stk.n_ev, 20]) self.slidershow.setMaximum(scrollmax) #try: self.CalcPCA() self.calcpca = True self.loadPCAImage() self.loadPCASpectrum() self.showEvals() self.com.pca_calculated = 1 QtWidgets.QApplication.restoreOverrideCursor() #except: # pass # self.com.pca_calculated = 0 # QtWidgets.QApplication.restoreOverrideCursor() # QtWidgets.QMessageBox.warning(self, 'Error', 'PCA not calculated.') self.window().refresh_widgets() #---------------------------------------------------------------------- def OnCalcPCA4D(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.calcpca = False self.selpca = 1 self.numsigpca = 2 self.slidershow.setValue(self.selpca) scrollmax = np.min([self.stk.n_ev, 20]) self.slidershow.setMaximum(scrollmax) try: self.CalcPCA4D() self.calcpca = True self.loadPCAImage() self.loadPCASpectrum() self.showEvals() self.com.pca_calculated = 1 self.com.pca4D_calculated = 1 self.slider_theta.setVisible(True) self.tc_imagetheta.setVisible(True) self.slider_theta.setRange(0, self.stk.n_theta-1) self.slider_theta.setValue(self.itheta) self.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) self.anlz.pcaimages = self.anlz.pcaimages4D[self.itheta] self.anlz.eigenvals = self.anlz.eigenvals4D[self.itheta] self.anlz.eigenvecs = self.anlz.eigenvecs4D[self.itheta] self.anlz.variance = self.anlz.variance4D[self.itheta] self.anlz.pcaimagebounds = self.anlz.pcaimagebounds4D[self.itheta] QtWidgets.QApplication.restoreOverrideCursor() except: self.com.pca_calculated = 0 QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'PCA not calculated.') self.window().refresh_widgets() #---------------------------------------------------------------------- def OnNPCAspin(self, value): num = value self.numsigpca = num if self.com.pca_calculated == 1: self.anlz.numsigpca = self.numsigpca # cumulative variance var = self.anlz.variance[:self.numsigpca].sum() self.vartc.setText(str(var.round(decimals=2)*100)+'%') #---------------------------------------------------------------------- def OnMovePCUP(self): thiscomponent = self.selpca-1 if thiscomponent == 0: return self.anlz.move_pc_up(thiscomponent) self.selpca = self.selpca-1 self.loadPCAImage() self.loadPCASpectrum() self.showEvals() self.slidershow.setValue(self.selpca) #---------------------------------------------------------------------- def CalcPCA(self): self.anlz.calculate_pca() #Scree plot criterion self.numsigpca = self.anlz.numsigpca self.npcaspin.setValue(self.numsigpca) # cumulative variance var = self.anlz.variance[:self.numsigpca].sum() self.vartc.setText(str(var.round(decimals=2)*100)+'%') #---------------------------------------------------------------------- def CalcPCA4D(self): if self.com.stack_4d == 1: self.anlz.calculate_pca_4D() else: return #Scree plot criterion self.numsigpca = self.anlz.numsigpca self.npcaspin.setValue(self.numsigpca) # cumulative variance var = self.anlz.variance[:self.numsigpca].sum() self.vartc.setText(str(var.round(decimals=2)*100)+'%') if self.com.spec_anl4D_calculated == 1: self.anlz.calculate_targetmaps_4D() #---------------------------------------------------------------------- def OnPCAScroll(self, value): self.sel = value self.selpca = self.sel if self.calcpca == True: self.loadPCAImage() self.loadPCASpectrum() #---------------------------------------------------------------------- def OnScrollTheta(self, value): if self.com.pca4D_calculated == 0: return self.itheta = value self.anlz.pcaimages = self.anlz.pcaimages4D[self.itheta] self.anlz.eigenvals = self.anlz.eigenvals4D[self.itheta] self.anlz.eigenvecs = self.anlz.eigenvecs4D[self.itheta] self.anlz.variance = self.anlz.variance4D[self.itheta] self.anlz.pcaimagebounds = self.anlz.pcaimagebounds4D[self.itheta] self.tc_imagetheta.setText("4D Data Angle: "+'{0:5.2f}\t'.format(self.stk.theta[self.itheta])) var = self.anlz.variance[:self.numsigpca].sum() self.vartc.setText(str(var.round(decimals=2)*100)+'%') self.loadPCAImage() self.loadPCASpectrum() self.showEvals() self.window().page0.itheta = self.itheta self.window().page0.slider_theta.setValue(self.itheta) self.window().page1.itheta = self.itheta self.window().page1.slider_theta.setValue(self.itheta) #---------------------------------------------------------------------- def OnPointEvalsImage(self, evt): x = evt.xdata y = evt.ydata if self.com.pca_calculated == 1: #Find the closest point to the point clicked on the plot self.selpca = int(np.round(x)) if self.selpca < 1: self.selpca = 1 self.loadPCAImage() self.loadPCASpectrum() #---------------------------------------------------------------------- def OnSave(self, event): savewin = SaveWinP2(self.window()) savewin.show() #---------------------------------------------------------------------- def Save(self, filename, path, spec_png = True, spec_pdf = False, spec_svg = False, spec_csv = False, img_png = True, img_pdf = False, img_svg = False, img_tif = False, evals_png = True, evals_pdf = False, evals_svg = False): self.SaveFileName = os.path.join(path,filename) try: matplotlib.rcParams['pdf.fonttype'] = 42 if evals_png: ext = 'png' suffix = "." + ext fileName_evals = self.SaveFileName+"_PCAevals."+ext fig = self.pcaevalsfig fig.savefig(fileName_evals) if evals_pdf: ext = 'pdf' suffix = "." + ext fileName_evals = self.SaveFileName+"_PCAevals."+ext fig = self.pcaevalsfig fig.savefig(fileName_evals) if evals_svg: ext = 'svg' suffix = "." + ext fileName_evals = self.SaveFileName+"_PCAevals."+ext fig = self.pcaevalsfig fig.savefig(fileName_evals) from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas matplotlib.rcParams['pdf.fonttype'] = 42 ext = 'png' suffix = "." + ext if img_png: for i in range (self.numsigpca): self.pcaimage = self.anlz.pcaimages[:,:,i] fig = matplotlib.figure.Figure(figsize =(PlotH*1.15, PlotH)) canvas = FigureCanvas(fig) axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) bound = self.anlz.pcaimagebounds[i] im = axes.imshow(np.rot90(self.pcaimage), cmap=matplotlib.colormaps["seismic_r"], vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") fileName_img = self.SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if spec_png: for i in range (self.numsigpca): pcaspectrum = self.anlz.eigenvecs[:,i] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev, pcaspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if spec_csv: for i in range (self.numsigpca): pcaspectrum = self.anlz.eigenvecs[:,i] fileName_spec = self.SaveFileName+"_PCAspectrum_" +str(i+1)+".csv" cname = "PCAspectrum_" +str(i+1) self.stk.write_csv(fileName_spec, self.stk.ev, pcaspectrum, cname = cname) ext = 'pdf' suffix = "." + ext if img_pdf: for i in range (self.numsigpca): self.pcaimage = self.anlz.pcaimages[:,:,i] fig = matplotlib.figure.Figure(figsize =(PlotH*1.15, PlotH)) canvas = FigureCanvas(fig) axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) bound = self.anlz.pcaimagebounds[i] im = axes.imshow(np.rot90(self.pcaimage), cmap=matplotlib.colormaps["seismic_r"], vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") fileName_img = self.SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if spec_pdf: for i in range (self.numsigpca): self.pcaspectrum = self.anlz.eigenvecs[:,i] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev,self.pcaspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) ext = 'svg' suffix = "." + ext if img_svg: for i in range (self.numsigpca): self.pcaimage = self.anlz.pcaimages[:,:,i] fig = matplotlib.figure.Figure(figsize =(PlotH*1.15, PlotH)) canvas = FigureCanvas(fig) axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) bound = self.anlz.pcaimagebounds[i] im = axes.imshow(np.rot90(self.pcaimage), cmap=matplotlib.colormaps["seismic_r"], vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") fileName_img = self.SaveFileName+"_PCA_" +str(i+1)+"."+ext fig.savefig(fileName_img, pad_inches = 0.0) if spec_svg: for i in range (self.numsigpca): self.pcaspectrum = self.anlz.eigenvecs[:,i] fig = matplotlib.figure.Figure(figsize =(PlotW, PlotH)) canvas = FigureCanvas(fig) fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev,self.pcaspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') fileName_spec = self.SaveFileName+"_PCAspectrum_" +str(i+1)+"."+ext fig.savefig(fileName_spec) if img_tif: for i in range (self.numsigpca): self.pcaimage = self.anlz.pcaimages[:,:,i] fileName_img = self.SaveFileName+"_PCA_" +str(i+1)+".tif" img1 = Image.fromarray(self.pcaimage) img1.save(fileName_img) except IOError as e: if e.strerror: err = e.strerror else: err = e print(err) QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #---------------------------------------------------------------------- def showEvals(self): evalmax = np.min([self.stk.n_ev, 40]) self.pcaevals = self.anlz.eigenvals[0:evalmax] fig = self.pcaevalsfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() evalsplot = axes.semilogy(np.arange(1,evalmax+1), self.pcaevals,'b.') axes.set_xlabel('Principal Component') axes.set_ylabel('Log(Eigenvalue)') self.PCAEvalsPan.draw() #---------------------------------------------------------------------- def loadPCAImage(self): self.tc_PCAcomp.setText("PCA component " + str(self.selpca)) self.text_pcaspec.setText("PCA spectrum "+ str(self.selpca)) self.pcaimage = self.anlz.pcaimages[:,:,self.selpca-1] fig = self.pcaimgfig fig.clf() axes = fig.gca() divider = make_axes_locatable(axes) ax_cb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(ax_cb) axes.set_position([0.03,0.03,0.8,0.94]) bound = self.anlz.pcaimagebounds[self.selpca-1] im = axes.imshow(np.rot90(self.pcaimage), cmap=matplotlib.colormaps["seismic_r"], vmin = -bound, vmax = bound) cbar = axes.figure.colorbar(im, orientation='vertical',cax=ax_cb) axes.axis("off") self.PCAImagePan.draw() #---------------------------------------------------------------------- def loadPCASpectrum(self): self.pcaspectrum = self.anlz.eigenvecs[:,self.selpca-1] fig = self.pcaspecfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev,self.pcaspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') self.PCASpecPan.draw() #---------------------------------------------------------------------- class SaveWinP2(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(400, 300) self.setWindowTitle('Save') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.com = self.parent.common path, ext = os.path.splitext(self.com.filename) ext = ext[1:].lower() suffix = "." + ext path, fn = os.path.split(self.com.filename) filename = fn[:-len(suffix)] self.path = self.com.path self.filename = filename vboxtop = QtWidgets.QVBoxLayout() vboxtop.setContentsMargins(20,20,20,20) gridtop = QtWidgets.QGridLayout() gridtop.setVerticalSpacing(20) fontb = QtGui.QFont() fontb.setBold(True) st1 = QtWidgets.QLabel(self) st1.setText('Save') st1.setFont(fontb) st2 = QtWidgets.QLabel(self) st2.setText('.pdf') st2.setFont(fontb) st3 = QtWidgets.QLabel(self) st3.setText('.png') st3.setFont(fontb) st4 = QtWidgets.QLabel(self) st4.setText('.svg') st4.setFont(fontb) st9 = QtWidgets.QLabel(self) st9.setText('.tif (data)') st9.setFont(fontb) st5 = QtWidgets.QLabel(self) st5.setText('.csv') st5.setFont(fontb) st6 = QtWidgets.QLabel(self) st6.setText('_spectrum') self.cb11 = QtWidgets.QCheckBox('', self) self.cb11.setChecked(True) self.cb12 = QtWidgets.QCheckBox('', self) self.cb13 = QtWidgets.QCheckBox('', self) self.cb14 = QtWidgets.QCheckBox('', self) st7 = QtWidgets.QLabel(self) st7.setText('_image') self.cb21 = QtWidgets.QCheckBox('', self) self.cb21.setChecked(True) self.cb22 = QtWidgets.QCheckBox('', self) self.cb23 = QtWidgets.QCheckBox('', self) self.cb24 = QtWidgets.QCheckBox('', self) st8 = QtWidgets.QLabel(self) st8.setText('_eigenvals') self.cb31 = QtWidgets.QCheckBox('', self) self.cb32 = QtWidgets.QCheckBox('', self) self.cb33 = QtWidgets.QCheckBox('', self) gridtop.addWidget(st1, 0, 0) gridtop.addWidget(st2, 0, 1) gridtop.addWidget(st3, 0, 2) gridtop.addWidget(st4, 0, 3) gridtop.addWidget(st9, 0, 5) gridtop.addWidget(st5, 0, 4) gridtop.addWidget(st6, 1, 0) gridtop.addWidget(self.cb11, 1, 1) gridtop.addWidget(self.cb12, 1, 2) gridtop.addWidget(self.cb13, 1, 3) gridtop.addWidget(self.cb14, 1, 4) gridtop.addWidget(st7, 2, 0) gridtop.addWidget(self.cb21, 2, 1) gridtop.addWidget(self.cb22, 2, 2) gridtop.addWidget(self.cb23, 2, 3) gridtop.addWidget(self.cb24, 2, 5) gridtop.addWidget(st8, 3, 0) gridtop.addWidget(self.cb31, 3, 1) gridtop.addWidget(self.cb32, 3, 2) gridtop.addWidget(self.cb33, 3, 3) vboxtop.addStretch(1) vboxtop.addLayout(gridtop) vboxtop.addStretch(2) hbox0 = QtWidgets.QHBoxLayout() stf = QtWidgets.QLabel(self) stf.setText('Filename:\t') self.tc_savefn = QtWidgets.QLineEdit(self) self.tc_savefn.setText(self.filename) hbox0.addWidget(stf) hbox0.addWidget(self.tc_savefn) hbox1 = QtWidgets.QHBoxLayout() stp = QtWidgets.QLabel(self) stp.setText('Path: \t') self.tc_savepath = QtWidgets.QLineEdit(self) self.tc_savepath.setReadOnly(True) self.tc_savepath.setText(self.path) self.tc_savepath.setMinimumWidth(100) hbox1.addWidget(stp) hbox1.addWidget(self.tc_savepath) button_path = QtWidgets.QPushButton('Browse...') button_path.clicked.connect(self.OnBrowseDir) hbox1.addWidget(button_path) hbox2 = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save') button_save.clicked.connect(self.OnSave) hbox2.addWidget(button_save) button_cancel = QtWidgets.QPushButton('Cancel') button_cancel.clicked.connect(self.close) hbox2.addWidget(button_cancel) vboxtop.addLayout(hbox0) vboxtop.addLayout(hbox1) vboxtop.addStretch(1) vboxtop.addLayout(hbox2) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnBrowseDir(self, evt): directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", self.path, QtWidgets.QFileDialog.ShowDirsOnly|QtWidgets.QFileDialog.ReadOnly) if directory == '': return directory = str(directory) self.com.path = directory self.path = directory self.tc_savepath.setText(self.path) #---------------------------------------------------------------------- def OnSave(self, evt): self.filename = str(self.tc_savefn.text()) sp_pdf = self.cb11.isChecked() sp_png = self.cb12.isChecked() sp_svg = self.cb13.isChecked() sp_csv = self.cb14.isChecked() im_pdf = self.cb21.isChecked() im_png = self.cb22.isChecked() im_svg = self.cb23.isChecked() im_tif = self.cb24.isChecked() ev_pdf = self.cb31.isChecked() ev_png = self.cb32.isChecked() ev_svg = self.cb33.isChecked() self.close() self.parent.page2.Save(self.filename, self.path, spec_png = sp_png, spec_pdf = sp_pdf, spec_svg = sp_svg, spec_csv = sp_csv, img_png = im_png, img_pdf = im_pdf, img_svg = im_svg, img_tif = im_tif, evals_png = ev_png, evals_pdf = ev_pdf, evals_svg = ev_svg) """ ------------------------------------------------------------------------------------------------""" class PageStack(QtWidgets.QWidget): def __init__(self, common, data_struct, stack): super(PageStack, self).__init__() uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pagestack.ui'), self) self.show() self.cmaps = [('Perceptually Uniform Sequential', [ 'viridis', 'plasma', 'inferno', 'magma']), ('Sequential', [ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), ('Sequential (2)', [ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper']), ('Diverging', [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), ('Qualitative', [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c']), ('Miscellaneous', [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] self.initUI(common, data_struct, stack) #---------------------------------------------------------------------- def initUI(self, common, data_struct, stack): self.spectrum_plotwidget.setBackground("w") self.data_struct = data_struct self.stk = stack self.com = common self.filename = " " self.ix = 0 self.iy = 0 self.iev = 0 self.itheta = 0 self.showflux = True self.show_colorbar = 0 self.dispbrightness_min = 0 self.dispbrightness_max = 100 self.displaygamma = 10.0 self.defaultdisplay = 1.0 self.brightness_min = 0.0 self.brightness_max = 1.0 self.gamma = 1.0 self.colortable = "gray" self.addroi = 0 self.showROImask = 0 self.line = None self.ROIpix = None self.show_scale_bar = 1 self.white_scale_bar = 0 self.movie_playing = 0 self.mean_visible = 0 #Panel Preprocess #Align stack... self.button_align.clicked.connect(self.OnAlignImgsDialog) self.button_align.setEnabled(False) #Crop stack 3D/4D... self.button_multicrop.clicked.connect( self.OnMultiCrop) self.button_multicrop.setEnabled(False) #Artefacts && Leveling self.button_artefacts.clicked.connect( self.OnArtefacts) self.button_artefacts.setEnabled(False) #Dark signal subtraction... self.button_darksig.clicked.connect(self.OnDarkSignal) self.button_darksig.setEnabled(False) #Save processed stack self.button_savestack.clicked.connect(self.OnSaveStack) self.button_savestack.setEnabled(False) #Panel Normalize #Select I0... self.button_i0.clicked.connect( self.OnI0histogram) self.button_i0.setEnabled(False) #I0 from file... self.button_i0ffile.clicked.connect(self.OnI0FFile) self.button_i0ffile.setEnabled(False) #Show I0... self.button_showi0.clicked.connect( self.OnShowI0) self.button_showi0.setEnabled(False) #Use pre-normalized data self.button_prenorm.clicked.connect(self.OnPreNormalizedData) self.button_prenorm.setEnabled(False) #Load Reference Images self.button_refimgs.clicked.connect(self.OnRefImgs) self.button_refimgs.setEnabled(False) self.button_meanflux.clicked.connect( self.OnShowMean) self.button_meanflux.setEnabled(False) self.button_meanflux.setChecked(False) self.button_slideshow.clicked.connect( self.OnSlideshow) self.button_slideshow.setEnabled(False) #Save images... self.button_save.clicked.connect( self.OnSave) self.button_save.setEnabled(False) #vbox22.addWidget(self.button_save) #hbox20.addLayout(vbox22) #vbox2.addLayout(hbox20) #vbox2.addSpacing(5) #hbox21 = QtWidgets.QHBoxLayout() #sizer22 = QtWidgets.QGroupBox('Image') #vbox23 = QtWidgets.QVBoxLayout() #self.rb_flux = QtWidgets.QRadioButton( 'Flux', self) #self.rb_od = QtWidgets.QRadioButton('Optical Density',self) #self.rb_flux.setChecked(True) #self.rb_flux.toggled.connect(self.OnRb_fluxod) #vbox23.addWidget(self.rb_flux) #vbox23.addWidget(self.rb_od) #vbox23.addStretch (1) #self.rb_flux.setEnabled(False) #self.rb_od.setEnabled(False) #self.add_colbar_cb = QtWidgets.QCheckBox('Colorbar', self) #self.add_colbar_cb.stateChanged.connect(self.OnShowColBar) #vbox23.addWidget(self.add_colbar_cb) #sizer22.setLayout(vbox23) #hbox21.addWidget(sizer22) #hbox21.addSpacing(5) #vbox2.addLayout(hbox21) #sizer23 = QtWidgets.QGroupBox('Display settings') #hbox23 = QtWidgets.QHBoxLayout() #fgs21 = QtWidgets.QGridLayout() #self.tc_min = QtWidgets.QLabel(self) #self.tc_min.setText('Minimum: \t{0:5d}%'.format(int(100*self.brightness_min))) #self.tc_max = QtWidgets.QLabel(self) #self.tc_max.setText('Maximum:{0:5d}%'.format(int(100*self.brightness_max))) #self.slider_brightness_min = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) #self.slider_brightness_min.setRange(0,49) #self.slider_brightness_min.setValue(self.dispbrightness_min) #self.slider_brightness_min.setFocusPolicy(QtCore.Qt.StrongFocus) #self.slider_brightness_min.valueChanged[int].connect(self.OnScrollBrightnessMin) #self.slider_brightness_max = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) #self.slider_brightness_max.setRange(50,120) #self.slider_brightness_max.setValue(self.dispbrightness_max) #self.slider_brightness_max.setFocusPolicy(QtCore.Qt.StrongFocus) #self.slider_brightness_max.valueChanged[int].connect(self.OnScrollBrightnessMax) #self.tc_gamma = QtWidgets.QLabel(self) #self.tc_gamma.setText('Gamma: \t{0:5.2f}'.format(self.gamma)) #self.slider_gamma = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) #self.slider_gamma.setRange(1,20) #self.slider_gamma.setValue(self.displaygamma) #self.slider_gamma.setFocusPolicy(QtCore.Qt.StrongFocus) #self.slider_gamma.valueChanged[int].connect(self.OnScrollGamma) #fgs21.addWidget(self.tc_min, 0, 0) #fgs21.addWidget(self.slider_brightness_min, 0, 1) #fgs21.addWidget(self.tc_max, 1, 0) #fgs21.addWidget(self.slider_brightness_max, 1, 1) #fgs21.addWidget(self.tc_gamma, 2, 0) #fgs21.addWidget(self.slider_gamma, 2, 1) #hbox23.addLayout(fgs21) # ToDo: Restore Despike (if needed) #vbox24 = QtWidgets.QVBoxLayout() #self.button_despike = QtWidgets.QPushButton('Despike') #self.button_despike.clicked.connect( self.OnDespike) #self.button_despike.setEnabled(False) #vbox24.addWidget(self.button_despike) #self.button_resetdisplay = QtWidgets.QPushButton( 'Reset') #self.button_resetdisplay.clicked.connect( self.OnResetDisplaySettings) #self.button_resetdisplay.setEnabled(False) #vbox24.addWidget(self.button_resetdisplay) #self.button_displaycolor = QtWidgets.QPushButton('Color Table... ') #self.button_displaycolor.clicked.connect( self.OnSetColorTable) #self.button_displaycolor.setEnabled(False) #vbox24.addWidget(self.button_displaycolor) #hbox23.addSpacing(20) #hbox23.addLayout(vbox24) #sizer23.setLayout(hbox23) #hbox21.addWidget(sizer23) #sizer2.setLayout(vbox2) #Panel Region of Interest #ROI Dose Calculation... self.button_ROIdosecalc.clicked.connect( self.OnROI_DoseCalc) self.button_ROIdosecalc.setEnabled(False) #Spectral ROI... self.button_spectralROI.clicked.connect( self.OnSpectralROI) self.button_spectralROI.setEnabled(False) #panel 4 #vbox4 = QtWidgets.QVBoxLayout() gridsizer4 = QtWidgets.QGridLayout() self.tc_imageeng = QtWidgets.QLabel(self) self.tc_imageeng.setText("Image at energy: ") gridsizer4.addWidget(self.tc_imageeng, 0, 0, QtCore .Qt. AlignLeft) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.absimgfig = ImgFig(self,self.canvas) self.specfig = SpecFig(self, self.spectrum_plotwidget) #self.slider_eng = QtWidgets.QScrollBar(QtCore.Qt.Vertical) #self.slider_eng.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) #self.slider_eng.setRange(0, 100) #gridsizer4.addWidget(self.slider_eng, 1, 1, QtCore .Qt. AlignLeft) #self.slider_theta = QtWidgets.QScrollBar(QtCore.Qt.Horizontal) #self.slider_theta.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) #self.slider_theta.setRange(0, 100) self.slider_theta.setVisible(False) #self.tc_imagetheta = QtWidgets.QLabel(self) #self.tc_imagetheta.setText("4D Data Angle: ") #self.tc_imagetheta.setVisible(False) #hbox41 = QtWidgets.QHBoxLayout() #hbox41.addWidget(self.tc_imagetheta) #hbox41.addWidget(self.slider_theta) #gridsizer4.addLayout(hbox41, 2, 0) self.pb_copy_img.clicked.connect(self.absimgfig.OnCopy) self.pb_copy_img.setEnabled(False) self.pb_copy_specimg.clicked.connect(self.specfig.OnCopy) self.pb_copy_specimg.setEnabled(False) #self.MetricCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) #self.ZeroOriginCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(False, True,self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.setVisible(True) self.ScalebarCheckBox.toggled.connect(lambda: self.absimgfig.OnUpdateScale(self.ScalebarCheckBox.isChecked())) self.CMCatBox.addItems([self.cmaps[0][0],self.cmaps[1][0],self.cmaps[2][0],self.cmaps[3][0],self.cmaps[4][0],self.cmaps[5][0]]) self.CMMapBox.addItems(self.cmaps[2][1]) self.CMCatBox.setCurrentIndex(2) self.CMMapBox.setCurrentIndex(3) self.CMCatBox.currentIndexChanged.connect(self.absimgfig.OnCatChanged) self.CMMapBox.currentIndexChanged.connect(lambda: self.absimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.StepSpin.valueChanged.connect(lambda: self.absimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.ROIShapeBox.addItems(["Lasso", "Rectangle", "Circle", "Ellipse", "Polygon", "Histogram"]) self.ROIShapeBox.currentTextChanged.connect(self.absimgfig.OnROIShapeChanged) self.button_lockspectrum.setEnabled(False) self.button_clearlastroi.setEnabled(False) self.button_mergeroi.setEnabled(True) self.button_subtractroi.setEnabled(True) self.button_clearspecfig.setEnabled(False) self.ROIShapeBox.setEnabled(False) self.ROIvisibleCheckBox.setEnabled(False) self.ROIvisibleCheckBox.stateChanged.connect(self.absimgfig.OnROIVisibility) #---------------------------------------------------------------------- def OnI0FFile(self, event): try: wildcard = "I0 CSV files (*.csv);; I0 files (*.xas);;SDF I0 files (*.hdr)" filepath, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '', wildcard) filepath = str(filepath) if filepath == '': return filepath_i0 = os.path.dirname(str(filepath)) self.filename = os.path.basename(str(filepath)) basename, extension = os.path.splitext(self.filename) if extension == '.hdr': QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) x=self.stk.n_cols y=self.stk.n_rows z=self.iev self.ix = int(x/2) self.iy = int(y/2) self.stk.read_sdf_i0(filepath) self.com.i0_loaded = 1 #self.loadSpectrum(self.ix, self.iy) self.absimgfig.loadNewImage() QtWidgets.QApplication.restoreOverrideCursor() elif extension == '.xas': QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) x=self.stk.n_cols y=self.stk.n_rows z=self.iev self.ix = int(x/2) self.iy = int(y/2) self.stk.read_stk_i0(filepath, extension) self.com.i0_loaded = 1 #self.loadSpectrum(self.ix, self.iy) self.absimgfig.loadNewImage() QtWidgets.QApplication.restoreOverrideCursor() elif extension == '.csv': QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) x=self.stk.n_cols y=self.stk.n_rows z=self.iev self.ix = int(x/2) self.iy = int(y/2) self.stk.read_stk_i0(filepath, extension) self.com.i0_loaded = 1 self.absimgfig.loadNewImageWithROI() self.specfig.ClearandReload() self.button_i0.disconnect() self.button_i0.setText("Reset I0") self.button_i0.clicked.connect(self.OnI0Reset) self.window().refresh_widgets() self.button_i0ffile.setEnabled(False) self.button_prenorm.setEnabled(False) self.button_refimgs.setEnabled(False) QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.com.i0_loaded = 0 QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self,'Error',"I0 file not loaded.") import sys; print(sys.exc_info()) #---------------------------------------------------------------------- def OnI0histogram(self, event): self.specfig.OnI0Histogram() # ---------------------------------------------------------------------- def OnI0Reset(self, event): self.specfig.OnI0Reset() #---------------------------------------------------------------------- def I0histogramCalculated(self): self.com.i0_loaded = 1 if self.com.stack_4d == 1: self.stk.od3d = self.stk.od4d[:,:,:,self.itheta] self.stk.od = self.stk.od3d.copy() self.stk.od = np.reshape(self.stk.od, (self.stk.n_cols*self.stk.n_rows, self.stk.n_ev), order='F') self.absimgfig.loadNewImageWithROI() self.specfig.ClearandReload() self.window().refresh_widgets() #---------------------------------------------------------------------- def OnShowI0(self, event): self.specfig.toggleI0Spectrum() #plot = PlotFrame(self, self.stk.evi0,self.stk.i0data) #plot.show() #----------------------------------------------------------------------- def OnArtefacts(self, event): # self.window().Hide() artefacts = ShowArtefacts(self.window(), self.com, self.stk) artefacts.show() # ---------------------------------------------------------------------- def OnPreNormalizedData(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.stk.UsePreNormalizedData() self.com.i0_loaded = 1 #self.loadSpectrum(self.ix, self.iy) self.absimgfig.loadNewImageWithROI() self.specfig.ClearandReload() #self.parent.I0histogramCalculated() self.button_i0.disconnect() self.button_i0.setText("Reset I0") self.button_i0.clicked.connect(self.OnI0Reset) self.window().refresh_widgets() self.button_showi0.setEnabled(False) self.button_i0ffile.setEnabled(False) self.button_prenorm.setEnabled(False) self.button_refimgs.setEnabled(False) QtWidgets.QApplication.restoreOverrideCursor() #----------------------------------------------------------------------- def OnRefImgs(self, event): #Load .xrm reference images try: #if True: wildcard = "Reference images (*.xrm)" filepaths, _filter = QtWidgets.QFileDialog.getOpenFileNames(self, 'Select reference files', '', wildcard) #Check reference files if len(filepaths) != self.stk.n_ev: QtWidgets.QMessageBox.warning(self,'Error',"Wrong number of Reference image files.") return QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.stk.read_xrm_ReferenceImages(filepaths) x=self.stk.n_cols y=self.stk.n_rows self.ix = int(x/2) self.iy = int(y/2) self.com.i0_loaded = 1 self.absimgfig.loadNewImageWithROI() self.specfig.ClearandReload() self.button_i0.disconnect() self.button_i0.setText("Reset I0") self.button_i0.clicked.connect(self.OnI0Reset) self.window().refresh_widgets() self.button_i0ffile.setEnabled(False) self.button_prenorm.setEnabled(False) self.button_refimgs.setEnabled(False) QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.com.i0_loaded = 0 QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self,'Error',"Reference image file not loaded.") import sys; print(sys.exc_info()) #---------------------------------------------------------------------- def OnSaveStack(self, event): self.window().SaveProcessedStack() #---------------------------------------------------------------------- def OnSave(self, event): savewin = SaveWin(self, self.com, self.stk) savewin.show() # ---------------------------------------------------------------------- def OnMultiCrop(self, evt): multicropwin = MultiCrop(self.window(), self.com, self.stk) multicropwin.show() # ---------------------------------------------------------------------- def OnAlignImgsDialog(self, event): # self.window().Hide() imgregwin = ImageRegistrationDialog(self.window(),self.com) imgregwin.show() #---------------------------------------------------------------------- def OnAlignImgs(self, event): #self.window().Hide() imgregwin = ImageRegistrationManual(self.window(), self.com, self.stk) imgregwin.show() # ---------------------------------------------------------------------- def OnAlignImgs2(self, event): imgreg2 = ImageRegistrationFFT(self.window(), self.com, self.stk) imgreg2.show() #---------------------------------------------------------------------- def OnDarkSignal(self, event): dswin = DarkSignal(self.window(), self.com, self.stk) dswin.show() #---------------------------------------------------------------------- def OnShowMean(self): if (self.com.stack_loaded == 1): if (self.mean_visible == 1): self.mean_visible = 0 self.OnScrollEng(self.iev) return self.mean_visible = 1 if self.com.i0_loaded == 0: # Show flux mean image = np.nanmean(self.stk.absdata, axis=2) self.absimgfig.draw(image) self.absimgfig.imageplot.setTitle("Mean flux") else: # Show OD mean image = np.nanmean(self.stk.od3d, axis=2) self.absimgfig.draw(image) self.absimgfig.imageplot.setTitle("Mean OD") #---------------------------------------------------------------------- def OnSlideshow(self, event): if (self.com.stack_loaded == 1) and (self.addroi == 0): if (self.movie_playing == 1): self.movie_playing = 0 self.button_meanflux.setEnabled(True) return self.button_meanflux.setEnabled(False) self.button_slideshow.setText("Stop stack movie") self.movie_playing = 1 # #self.loadSpectrum(self.ix, self.iy) self.updateRate = int(max((2,np.rint(5000/self.stk.n_ev)))) # 5ms min update rate or approximately 5 seconds total duration def displayNextImage(): QtWidgets.qApp.processEvents() if self.movie_playing == 0: self.button_slideshow.setText("Play stack movie") return self.OnScrollEng(self.iev) # at end of stack, start from scratch if self.iev == self.stk.n_ev-1: self.iev = 0 else: self.iev = (self.iev + 1) QtCore.QTimer.singleShot(self.updateRate, displayNextImage) displayNextImage() #self.loadSpectrum(self.ix, self.iy) #----------------------------------------------------------------------- def OnScrollEng(self, value): self.iev = value self.button_meanflux.setChecked(False) self.mean_visible = 0 if self.com.stack_loaded == 1: self.slider_eng.setValue(value) if self.defaultdisplay == 1.0: # use a pointer to the data not a copy if self.com.i0_loaded == 0: # Show flux image image = self.stk.absdata[:, :, self.iev] # .copy() else: # Show OD image image = self.stk.od3d[:, :, self.iev] # .copy() else: # Adjustment to the data display setting has been made so make a copy #if self.showflux: if self.com.i0_loaded == 0: image = self.stk.absdata[:, :, self.iev].copy() else: image = self.stk.od3d[:, :, self.iev].copy() self.absimgfig.draw(image) #self.specfig.setLineIndicator(self.iev) self.specfig.LineIndicator.setValue(self.stk.ev[self.iev]) #---------------------------------------------------------------------- def OnScrollTheta(self, value): self.itheta = value self.button_meanflux.setChecked(False) self.mean_visible = 0 if self.com.stack_loaded == 1: self.slider_theta.setValue(value) if self.com.i0_loaded == 0: self.stk.absdata = self.stk.stack4D[:, :, :, self.itheta].copy() image = self.stk.absdata[:, :, int(self.slider_eng.value())].copy() else: self.stk.od3d = self.stk.od4d[:, :, :, self.itheta].copy() image = self.stk.od3d[:, :, int(self.slider_eng.value())].copy() #self.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) #self.p1.setTitle("
Image at {0:5.2f} eV and {1:5.1f}°
".format(float(self.stk.ev[self.iev]), # float(self.stk.theta[self.itheta]))) self.absimgfig.draw(image) self.specfig.ClearandReload() #----------------------------------------------------------------------- def OnPointSpectrum(self, evt): x = evt.xdata y = evt.ydata if (self.com.stack_loaded == 1) and (self.addroi == 0): if x < self.stk.ev[0]: sel_ev = 0 elif x > self.stk.ev[self.stk.n_ev-1]: sel_ev = self.stk.n_ev-1 else: indx = np.abs(self.stk.ev - x).argmin() sel_ev = indx self.iev = sel_ev self.loadSpectrum(self.ix, self.iy) self.loadImage() self.slider_eng.setValue(self.iev) #---------------------------------------------------------------------- def OnRb_fluxod(self, enabled): state = enabled if state: self.showflux = True else: self.showflux = False self.ResetDisplaySettings() self.loadImage() #---------------------------------------------------------------------- def OnShowScale(self,state): if state == QtCore.Qt.Checked: self.show_scale_bar = 1 else: self.show_scale_bar = 0 if self.com.stack_loaded == 1: self.loadImage() #---------------------------------------------------------------------- def OnWhiteScale(self,state): if state == QtCore.Qt.Checked: self.white_scale_bar = 1 else: self.white_scale_bar = 0 self.com.white_scale_bar = self.white_scale_bar if self.com.stack_loaded == 1: self.loadImage() #self.window().page0.ShowImage() #---------------------------------------------------------------------- def OnShowColBar(self, state): if state == QtCore.Qt.Checked: self.show_colorbar = 1 else: self.show_colorbar = 0 if self.com.stack_loaded == 1: self.loadImage() #---------------------------------------------------------------------- def OnResetDisplaySettings(self, event): self.ResetDisplaySettings() self.loadImage() #---------------------------------------------------------------------- def OnScrollBrightnessMin(self, value): self.dispbrightness_min = value self.brightness_min = float(self.dispbrightness_min)/100.0 self.defaultdisplay = 0.0 self.tc_min.setText('Minimum: \t{0:5d}%'.format(int(100*self.brightness_min))) if self.com.stack_loaded == 1: self.loadImage() #---------------------------------------------------------------------- def OnScrollBrightnessMax(self, value): self.dispbrightness_max = value self.brightness_max = float(self.dispbrightness_max)/100.0 self.defaultdisplay = 0.0 self.tc_max.setText('Maximum:{0:5d}%'.format(int(100*self.brightness_max))) if self.com.stack_loaded == 1: self.loadImage() #---------------------------------------------------------------------- def OnScrollGamma(self, value): self.displaygamma = value self.gamma = float(self.displaygamma)/10.0 self.defaultdisplay = 0.0 self.tc_gamma.setText('Gamma: \t{0:5.2f}'.format(self.gamma)) if self.com.stack_loaded == 1: self.loadImage() #---------------------------------------------------------------------- def OnDespike(self, evt): image = self.stk.absdata[:,:,self.iev] image = self.stk.despike(image) self.stk.data_struct.exchange.data = self.stk.absdata if self.com.i0_loaded: self.stk.calculate_optical_density() self.loadImage() #---------------------------------------------------------------------- def OnSetColorTable(self, event): colorwin = ColorTableFrame(self.window()) colorwin.show() # ----------------------------------------------------------------------- # def loadNewImage(self): # fig = self.absimgfig # fig.clear() # # if self.defaultdisplay == 1.0: # # # use a pointer to the data not a copy # # if self.showflux: # # # Show flux image # # image = self.stk.absdata[:, :, self.iev] # .copy() # # else: # # # Show OD image # # image = self.stk.od3d[:, :, self.iev] # .copy() # # else: # # # Adjustment to the data display setting has been made so make a copy # # if self.showflux: # # image = self.stk.absdata[:, :, self.iev].copy() # # else: # # image = self.stk.od3d[:, :, self.iev].copy() # fig.loadData() # #print("setimage") # #fig.OnScrollEng(self.iev) # #fig.i_item.setImage(image) # #im = axes.imshow(np.rot90(image), cmap=matplotlib.cm.get_cmap(self.colortable)) # # self.tc_imageeng.setText("Image at energy: {0:5.2f} eV".format(float(self.stk.ev[self.iev]))) #----------------------------------------------------------------------- # def loadImage(self): # # # if self.defaultdisplay == 1.0: # #use a pointer to the data not a copy # if self.showflux: # #Show flux image # image = self.stk.absdata[:,:,self.iev]#.copy() # else: # #Show OD image # image = self.stk.od3d[:,:,self.iev]#.copy() # else: # #Adjustment to the data display setting has been made so make a copy # if self.showflux: # image = self.stk.absdata[:,:,self.iev].copy() # else: # image = self.stk.od3d[:,:,self.iev].copy() # # # # fig = self.absimgfig # fig.clf() # # if self.show_colorbar == 0: # fig.add_axes(((0.0,0.0,1.0,1.0))) # axes = fig.gca() # # else: # axes = fig.gca() # divider = make_axes_locatable(axes) # axcb = divider.new_horizontal(size="3%", pad=0.03) # # fig.add_axes(axcb) # # axes.set_position([0.03,0.03,0.8,0.94]) # # self.axes = axes # fig.patch.set_alpha(1.0) # # if (self.line != None) and (self.addroi == 1): # axes.add_line(self.line) # # # if self.defaultdisplay == 1.0: # im = axes.imshow(np.rot90(image), cmap=matplotlib.cm.get_cmap(self.colortable)) # else: # imgmax = np.amax(image) # imgmin = np.amin(image) # if (self.gamma != 1.0) or (imgmin < 0.0): # image = (image-imgmin)/(imgmax-imgmin) # imgmax = 1.0 # imgmin = 0.0 # if (self.gamma != 1.0): # image = np.power(image, self.gamma) # vmin=(imgmin+imgmax*self.brightness_min) # vmax=imgmax*self.brightness_max # if vmin > vmax : vmax = vmin + 0.1 # im = axes.imshow(np.rot90( image), cmap=matplotlib.cm.get_cmap(self.colortable), # vmin=vmin,vmax=vmax) # # # if (self.showROImask == 1) and (self.addroi == 1): # im_red = axes.imshow(np.rot90( self.ROIpix_masked), cmap=matplotlib.colormaps["autumn"]) # # # # axes.axis("off") # # if self.show_colorbar == 1: # cbar = axes.figure.colorbar(im, orientation='vertical',cax=axcb) # # if self.show_scale_bar == 1: # if self.white_scale_bar == 1: # sbcolor = 'white' # else: # sbcolor = 'black' # startx = int(self.stk.n_cols*0.05) # starty = self.stk.n_rows-int(self.stk.n_rows*0.05)-self.stk.scale_bar_pixels_y # um_string = ' $\mathrm{\mu m}$' # microns = '$'+self.stk.scale_bar_string+' $'+um_string # axes.text(self.stk.scale_bar_pixels_x+startx+1,starty+1, microns, horizontalalignment='left', verticalalignment='center', # color = sbcolor, fontsize=14) # #Matplotlib has flipped scales so I'm using rows instead of cols! # p = matplotlib.patches.Rectangle((startx,starty), self.stk.scale_bar_pixels_x, self.stk.scale_bar_pixels_y, # color = sbcolor, fill = True) # axes.add_patch(p) # # self.AbsImagePanel.draw() # # self.tc_imageeng.setText("Image at energy: {0:5.2f} eV".format(float(self.stk.ev[self.iev]))) #----------------------------------------------------------------------- def loadSpectrum(self, xpos, ypos): # fig = self.specfig # fig.clf() # fig.add_axes((0.15,0.15,0.75,0.75)) # axes = fig.gca() axes.set_xlabel('Photon Energy [eV]') if self.com.i0_loaded == 1: self.spectrum = self.stk.od3d[int(xpos),int(ypos), :] axes.set_ylabel('Optical Density') else: self.spectrum = self.stk.absdata[int(xpos),int(ypos), :] axes.set_ylabel('Flux') specplot = axes.plot(self.stk.ev,self.spectrum) axes.axvline(x=self.stk.ev[self.iev], color = 'g', alpha=0.5) self.SpectrumPanel.draw() self.tc_spec.setText('Spectrum at pixel [{0}, {1}] or position [{2:5.2f}, {3:5.2f}]'.format(str(xpos), str(ypos), float(self.stk.x_dist[int(xpos)]), float(self.stk.y_dist[int(ypos)]))) #---------------------------------------------------------------------- def ResetDisplaySettings(self): self.defaultdisplay = 1.0 self.dispbrightness_min = 0 self.dispbrightness_max = 100 self.displaygamma = 10.0 self.brightness_min = 0.0 self.brightness_max = 1.0 self.gamma = 1.0 #self.slider_brightness_max.setValue(self.dispbrightness_max) #self.slider_brightness_min.setValue(self.dispbrightness_min) #self.slider_gamma.setValue(self.displaygamma) #self.tc_min.setText('Minimum: \t{0:5d}%'.format(int(100*self.brightness_min))) #self.tc_max.setText('Maximum:{0:5d}%'.format(int(100*self.brightness_max))) #self.tc_gamma.setText('Gamma: \t{0:5.2f}'.format(self.gamma)) #---------------------------------------------------------------------- def CalcROISpectrum(self): self.ROIspectrum = np.zeros((self.stk.n_ev)) indices = np.where(self.ROIarray == 255) numroipix = self.ROIarray[indices].shape[0] if self.com.i0_loaded == 1: for ie in range(self.stk.n_ev): thiseng_od = self.stk.od3d[:,:,ie] self.ROIspectrum[ie] = np.sum(thiseng_od[indices])/numroipix else: for ie in range(self.stk.n_ev): thiseng_abs = self.stk.absdata[:,:,ie] self.ROIspectrum[ie] = np.sum(thiseng_abs[indices])/numroipix #----------------------------------------------------------------------- def ShowROISpectrum(self): self.CalcROISpectrum() fig = self.specfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() specplot = axes.plot(self.stk.ev,self.ROIspectrum) axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Optical Density') self.SpectrumPanel.draw() self.tc_spec.setText("Average ROI Spectrum: ") #----------------------------------------------------------------------- def OnAcceptROI(self, evt): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.roixdata.append(self.start_point[0]) self.roiydata.append(self.start_point[1]) self.line.set_data(self.roixdata,self.roiydata) #self.loadImage() #find pixels inside the polygon if self.ROIpix == None: self.ROIpix = np.zeros((self.stk.n_cols,self.stk.n_rows)) #print(self.stk.n_cols, self.stk.n_rows) for i in range(self.stk.n_cols): for j in range(self.stk.n_rows): Pinside = self.point_in_poly(i, j, self.roixdata, self.stk.n_rows-1-self.roiydata) if Pinside == True: self.ROIpix[i, j] = 255 self.ROIpix = np.ma.array(self.ROIpix) self.ROIpix_masked = np.ma.masked_values(self.ROIpix, 0) self.showROImask = 1 self.line = None self.previous_point = [] self.start_point = [] self.end_point = [] self.roixdata = [] self.roiydata = [] self.button_saveROIspectr.setEnabled(True) self.button_setROII0.setEnabled(True) self.button_ROIdosecalc.setEnabled(True) self.window().refresh_widgets() self.loadImage() if (self.com.i0_loaded == 1): self.ShowROISpectrum() QtWidgets.QApplication.restoreOverrideCursor() #----------------------------------------------------------------------- def OnSelectLasso(self,verts): path = matplotlib.path.Path(verts) #find pixels inside the polygon ROIpix = np.zeros((self.stk.n_cols,self.stk.n_rows)) for i in range(self.stk.n_cols): for j in range(self.stk.n_rows): Pinside = path.contains_point((i,j)) if Pinside == True: ROIpix[i, self.stk.n_rows-1-j] = 255 ROIpix = np.ma.array(ROIpix) self.ROIpix_masked = np.ma.masked_values(ROIpix, 0) self.ROIarray = ROIpix[:] self.showROImask = 1 self.line = None self.previous_point = [] self.start_point = [] self.end_point = [] self.roixdata = [] self.roiydata = [] self.button_saveROIspectr.setEnabled(True) self.button_setROII0.setEnabled(True) self.button_ROIdosecalc.setEnabled(True) self.window().refresh_widgets() self.loadImage() if (self.com.i0_loaded == 1): self.ShowROISpectrum() QtWidgets.QApplication.restoreOverrideCursor() #----------------------------------------------------------------------- def CalcROI_I0Spectrum(self): self.ROIspectrum = np.zeros((self.stk.n_ev)) indices = np.where(self.ROIarray == 255) numroipix = self.ROIarray[indices].shape[0] for ie in range(self.stk.n_ev): thiseng_abs = self.stk.absdata[:,:,ie] self.ROIspectrum[ie] = np.sum(thiseng_abs[indices])/numroipix #---------------------------------------------------------------------- def OnSetROII0(self, evt): self.CalcROI_I0Spectrum() self.stk.set_i0(self.ROIspectrum, self.stk.ev) plot = PlotFrame(self, self.stk.evi0,self.stk.i0data) plot.show() x=self.stk.n_cols y=self.stk.n_rows self.ix = int(x/2) self.iy = int(y/2) self.com.i0_loaded = 1 self.addroi = 0 self.showROImask = 0 self.ROIpix = None self.loadSpectrum(self.ix, self.iy) self.loadImage() self.button_acceptROI.setEnabled(False) self.button_setROII0.setEnabled(False) self.button_resetROI.setEnabled(False) self.button_saveROIspectr.setEnabled(False) self.button_ROIdosecalc.setEnabled(False) self.window().refresh_widgets() #---------------------------------------------------------------------- def OnROI_DoseCalc(self, event): self.CalcROISpectrum() dosewin = DoseCalculation(self, self.stk, self.ROIspectrum) dosewin.show() #---------------------------------------------------------------------- def OnSpectralROI(self, evt): specroiwin = ShowODMap(self.window(), self.com, self.data_struct, self.stk) specroiwin.show() #---------------------------------------------------------------------- class SaveWin(QtWidgets.QDialog): def __init__(self, parent, com, stk): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dialogsave.ui'), self) self.parent = parent self.setWindowTitle('Save') self.com = com self.stk = stk self.cb11.setChecked(True) self.cb21.setChecked(True) if not hasattr(parent, 'specfig'): self.label_spectrum.setVisible(False) self.label_csv.setVisible(False) self.cb11.setVisible(False) self.cb11.setChecked(False) self.cb12.setVisible(False) self.cb13.setVisible(False) self.cb14.setVisible(False) path, ext = os.path.splitext(self.com.filename) # currently empty? ext = ext[1:].lower() suffix = "." + ext path, fn = os.path.split(self.com.filename) self.filename = fn[:-len(suffix)] self.tc_savefn.setText(self.filename) self.path = self.com.path self.tc_savepath.setText(self.path) self.button_path.clicked.connect(self.OnBrowseDir) self.buttonBox.accepted.connect(self.OnSave) self.buttonBox.rejected.connect(self.close) #---------------------------------------------------------------------- def OnBrowseDir(self, evt): directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", self.path, QtWidgets.QFileDialog.ShowDirsOnly|QtWidgets.QFileDialog.ReadOnly) if directory == '': return directory = str(directory) self.com.path = directory self.path = directory self.tc_savepath.setText(self.path) #---------------------------------------------------------------------- def OnSave(self): self.filename = str(self.tc_savefn.text()) sp_pdf = self.cb11.isChecked() sp_png = self.cb12.isChecked() sp_svg = self.cb13.isChecked() sp_csv = self.cb14.isChecked() im_pdf = self.cb21.isChecked() im_png = self.cb22.isChecked() im_svg = self.cb23.isChecked() im_tif = self.cb25.isChecked() im_all = self.cb32.isChecked() im_all_tif = self.cb35.isChecked() self.close() self.Save(self.filename, self.path, spec_png = sp_png, spec_pdf = sp_pdf, spec_svg = sp_svg, sp_csv = sp_csv, img_png = im_png, img_pdf = im_pdf, img_svg = im_svg, img_tif = im_tif, img_all = im_all, img_all_tif = im_all_tif) #---------------------------------------------------------------------- def Save(self, filename, path, spec_png = True, spec_pdf = False, spec_svg = False, sp_csv = False, img_png = True, img_pdf = False, img_svg = False, img_tif = False, img_all = False, img_all_tif = False): self.SaveFileName = os.path.join(path,filename) try: ext = 'png' suffix = "." + ext if spec_png: fileName_spec = self.SaveFileName+"_spectrum."+ext fig = self.parent.specfig fig.SaveFig(fileName_spec) if img_png: fileName_img = self.SaveFileName+"_" +str(self.stk.ev[self.parent.iev])+"eV."+ext fig = self.parent.absimgfig fig.SaveFig(fileName_img) #Save all images in the stack if img_all: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) for i in range (self.stk.n_ev): if self.parent.showflux: #Show flux image image = self.stk.absdata[:,:,i] else: #Show OD image image = self.stk.od3d[:,:,i] image = img_as_ubyte(exposure.rescale_intensity(image)) img = Image.fromarray(np.rot90(image)) fileName_img = self.SaveFileName + "_" + str(self.stk.ev[i])+"eV_"+ str(i + 1) + "." + ext img.save(fileName_img) QtWidgets.QApplication.restoreOverrideCursor() #Save all images in the stack if img_all_tif: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) for i in range (self.stk.n_ev): if self.parent.showflux: #Show flux image image = self.stk.absdata[:,:,i] else: #Show OD image image = self.stk.od3d[:,:,i] fileName_img = self.SaveFileName+"_" +str(self.stk.ev[i])+"eV_" +str(i+1)+".tif" img = Image.fromarray(np.rot90(image)) img.save(fileName_img) QtWidgets.QApplication.restoreOverrideCursor() ext = 'pdf' suffix = "." + ext if spec_pdf: fileName_spec = self.SaveFileName+"_spectrum."+ext fig = self.parent.specfig fig.SaveFig(fileName_spec) if img_pdf: fileName_img = self.SaveFileName+"_" +str(self.stk.ev[self.parent.iev])+"eV."+ext fig = self.parent.absimgfig fig.SaveFig(fileName_img) if sp_csv: evdata = self.parent.specfig.plotitem.items[1].xData.tolist() fileName_spec = self.SaveFileName + "_spectrum.csv" if self.parent.button_showi0.isChecked(): name = "I0 data" data = (self.parent.specfig.plotitem.items[-1].yData.tolist()) else: name = "ROI spectra" plot_num = len(self.parent.specfig.plotitem.items) data = [] for i in range(plot_num - 1): data.append(self.parent.specfig.plotitem.items[i+1].yData.tolist()) self.stk.write_csv(fileName_spec, evdata, data, cname=name) ext = 'svg' suffix = "." + ext if spec_svg: fileName_spec = self.SaveFileName+"_spectrum."+ext fig = self.parent.specfig fig.SaveFig(fileName_spec) if img_svg: fileName_img = self.SaveFileName+"_" +str(self.stk.ev[self.parent.iev])+"eV."+ext fig = self.parent.absimgfig fig.SaveFig(fileName_img) if img_tif: fileName_img = self.SaveFileName+"_" +str(self.stk.ev[self.parent.iev])+"eV.tif" #if self.parent.mean_visible: if self.parent.showflux: image = self.stk.absdata[:,:,self.parent.iev] else: image = self.stk.od3d[:,:,self.parent.iev] img1 = Image.fromarray(np.rot90(image)) img1.save(fileName_img) # Textimage # ext = 'txt' # if img_txt: # np.savetxt(fileName, np.rot90(image), delimiter='\t', newline='\n', fmt='%.5f') except IOError as e: if e.strerror: err = e.strerror else: err = e QtWidgets.QMessageBox.warning(self,'Error','Could not save file: %s' % err) #---------------------------------------------------------------------- class ShowArtefacts(QtWidgets.QDialog): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'showartefacts.ui'), self) self.parent = parent self.com = common self.stack = stack self.i0_h_warnflag = False self.i0_v_warnflag = False self.pglayout = pg.GraphicsLayout(border=None) self.canvas.setBackground("w") # canvas is a pg.GraphicsView widget self.canvas.setCentralWidget(self.pglayout) self.vb = self.pglayout.addViewBox() self.vb.setAspectLocked() self.i_item = pg.ImageItem(border="k",parent= self) self.button_ok.clicked.connect(self.OnAccept) self.button_cancel.clicked.connect(self.close) self.bg_level.toggled.connect(self.ShowImage) self.remove_outliers.toggled.connect(self.ShowImage) self.cb_h.stateChanged.connect(self.ShowImage) self.cb_v.stateChanged.connect(self.ShowImage) # I initially wanted to use Qt.QueuedConnection to create a non-blocking Slider. It is good enough. self.weight_slider.valueChanged.connect(self.ShowCalcImage) self.weight_slider_2.valueChanged.connect(self.ShowCalcImage) self.slider_eng.sliderPressed.connect(self.ShowImage) self.slider_eng.sliderReleased.connect(self.ShowImage) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.setWindowTitle('Artefacts & Leveling') self.slider_eng.setRange(0, self.stack.n_ev - 1) self.vb.setMouseEnabled(x=False, y=False) self.vb.addItem(self.i_item, ignoreBounds=False) self.stack.absdata_level = self.stack.absdata.copy() if self.com.i0_loaded: self.rb_median_i0.setCheckable(True) self.rb_median_i0.setChecked(True) self.label_4.setText('I0 mask contribution:') else: self.sliderWidget.hide() self.rb_median.toggled.connect(self.ShowImage) self.OnScrollEng(0) def ShowCalcImage(self): a = self.stack.absdata[:, :, self.slider_eng.value()].astype('float64') if self.bg_level.isChecked(): if any([self.cb_h.isChecked(),self.cb_v.isChecked()]): a, wf = self.LevelCalc(a) if self.rb_median_i0.isChecked(): self.label_3.setText(str('{:d}').format(int(wf))+' %') if self.remove_outliers.isChecked(): a, wf = self.OutlierCalc(a) self.label_7.setText(str('

exceeding background level ± {:d} * σ

').format(int(wf))) self.i_item.setImage(a) def CorrectionArray(self,array,axis,wf,mask = None,): # calculate median along given axis, ignoring nans if present array_i = np.nanmedian(array, axis=axis, keepdims=False) if mask is not None and wf != 0.0: array = np.where(mask, array, np.nan) # replace masked pixels by nans # calculate median along given axis, ignoring nans if present array = np.nanmedian(array, axis=axis, keepdims=False) bg_median = np.nanmedian(array, axis=0, keepdims=False) if mask is None or wf == 0.0: # returns the median filtered array neglecting the i0 mask return (array_i / np.nanmedian(array_i, axis=0, keepdims=False)) elif wf == 1: return ((array / bg_median)*(wf)) else: return ((array / bg_median)*(wf) + (array_i / bg_median)*(1-wf)) def LevelCalc(self,a,final=False): wf = 0.0 mask = None factor_v = 1 factor_h = 1 if self.cb_h.isChecked(): wf = (self.weight_slider.value()) / 100 if self.rb_median_i0.isChecked(): mask = self.stack.i0_mask if final: mask = mask[:, :, None] factor_h = self.CorrectionArray(a, 0, float(wf), mask)[None, :] #a += diff[None, :] if self.cb_v.isChecked(): wf = (self.weight_slider.value()) / 100 if self.rb_median_i0.isChecked(): mask = self.stack.i0_mask[:, :] if final: mask = mask[:, :, None] factor_v = self.CorrectionArray(a, 1, float(wf), mask)[: ,None] a = a/factor_h a = a/factor_v return(a,int(wf*100)) def CorrectOutliers(self,array,wf,mask = None,): # calculate median ignoring nans if present if mask is not None: array_nan = np.where(mask, array, np.nan) # replace masked pixels by nans std = np.nanstd(array_nan, keepdims=False) bg_median = np.nanmedian(array_nan, keepdims=False) else: std = np.nanstd(array, keepdims=False) bg_median = np.nanmedian(array, keepdims=False) array[(array < (bg_median-wf*int(std))) | (array > (bg_median+wf*int(std)))] = np.nan x_idx, y_idx = np.meshgrid(np.arange(0, array.shape[1]),np.arange(0, array.shape[0])) array = np.ma.masked_invalid(array) # mask nans x = x_idx[~array.mask] y = y_idx[~array.mask] z = array[~array.mask] try: returnarray = griddata((x, y), z.ravel(),(x_idx, y_idx), method='nearest') #interpolate array of counts except IndexError: pass return (returnarray) def OutlierCalc(self,a,final=False): wf = self.weight_slider_2.value() mask = None #diff_v = 0 #diff_h = 0 if self.com.i0_loaded: mask = self.stack.i0_mask #if final: # mask = mask[:, :, None] if final: for ev in range(a.shape[-1]): a[:, :, ev] = self.CorrectOutliers(a[:,:,ev], wf, mask) else: a = self.CorrectOutliers(a, wf, mask) return(a,wf) # ---------------------------------------------------------------------- def OnScrollEng(self, value): self.slider_eng.setValue(value) self.iev = value self.ShowImage() def ShowImage(self): if (self.slider_eng.isSliderDown()): self.i_item.setImage(self.stack.absdata[:, :, int(self.iev)]) elif any([self.remove_outliers.isChecked(), self.bg_level.isChecked()]): self.ShowCalcImage() else: self.i_item.setImage(self.stack.absdata[:, :, int(self.iev)]) self.label_3.setText(str('')) self.groupBox.setTitle(str('Stack Browser | Image at {0:5.2f} eV').format(float(self.stack.ev[self.iev]))) #---------------------------------------------------------------------- def OnAccept(self, evt): a, wf = self.LevelCalc(self.stack.absdata.astype('float64'),final=True) if self.remove_outliers.isChecked(): a, wf = self.OutlierCalc(a,final=True) if not np.array_equal(self.stack.absdata, a, equal_nan=False): self.stack.absdata = a self.parent.page0.absimgfig.loadNewImage() # Load new image on PageLoadData self.parent.page1.specfig.OnI0Reset() # Reset I0 on PageStack self.close() #---------------------------------------------------------------------- class MultiCrop(QtWidgets.QDialog, QtWidgets.QGraphicsScene): evlistchanged = pyqtSignal([object]) thetalistchanged = pyqtSignal([object]) def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'showmulticrop.ui'), self) self.parent = parent self.stack = stack self.com = common self.iev = 0 self.itheta = 0 #self.stack.absdata_shifted_cropped = self.stack.absdata_shifted.copy() self.poolthread = QtCore.QThread() self.aligned = False self.button_ok.setEnabled(True) self.setWindowTitle('Stack Cropping') self.pglayout = pg.GraphicsLayout(border=None) self.canvas.setBackground("w") # canvas is a pg.GraphicsView widget self.canvas.setCentralWidget(self.pglayout) self.vb = self.pglayout.addViewBox() self.vb.setAspectLocked() self.i_item = pg.ImageItem(border="k",parent= self) self.vb.setMouseEnabled(x=False, y=False) self.vb.addItem(self.i_item, ignoreBounds=False) self.button_ok.clicked.connect(self.OnAccept) self.button_cancel.clicked.connect(self.OnCancel) if self.com.stack_loaded == 1: self.cb_od_per_px.setVisible(False) self.cb_od_per_px.setChecked(True) self.cb_od_per_px.stateChanged.connect(self.RedrawPlots) self.label_theta_range.setVisible(False) self.slider_theta.setVisible(False) self.cb_remove_theta.setVisible(False) self.groupBox_theta.setVisible(False) if self.com.stack_4d == 1: self.label_theta_range.setVisible(True) self.slider_theta.setVisible(True) self.cb_remove_theta.setVisible(True) self.groupBox_theta.setVisible(True) self.slider_theta.setRange(0, self.stack.n_theta - 1) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) self.SetupListTheta() # self.maskedvals = [True] * int(self.stack.n_ev) # self.spinBoxError.setEnabled(False) self.slider_eng.sliderPressed.connect(self.ShowImage) self.slider_eng.sliderReleased.connect(self.ShowImage) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.slider_eng.setRange(0, self.stack.n_ev - 1) self.pb_selectall.clicked.connect(self.OnSelectAll) self.pb_clearall.clicked.connect(self.OnClearAll) self.evlistchanged.connect(lambda row: self.qListChangeHandler(row, "energy")) self.thetalistchanged.connect(lambda row: self.qListChangeHandler(row, "theta")) #self.ev_widget.itemClicked.connect(lambda item: self.OnItemClicked(item)) self.ev_widget.mousePressEvent = self.mouseEventOnEVList self.ev_widget.mouseMoveEvent = self.mouseEventOnEVList self.theta_widget.mousePressEvent = self.mouseEventOnThetaList self.theta_widget.mouseMoveEvent = self.mouseEventOnThetaList #self.ev_widget.itemSelectionChanged.connect(lambda item: self.OnItemClicked(item)) self.SetupListEV() self.OnScrollEng(0) self.SetupROI() self.SetupPlot() def mouseEventOnEVList(self, e): if e.type() == QtCore.QEvent.MouseMove or e.type() == QtCore.QEvent.MouseButtonPress: qlist = self.ev_widget pos = qlist.mapFromGlobal(QtGui.QCursor.pos()) row = qlist.indexAt(pos).row() item = qlist.itemAt(pos) #print(row,self.latest_row) if row >= 0: if e.type() != QtCore.QEvent.MouseMove or row != self.latest_row: if e.buttons() == QtCore.Qt.LeftButton: qlist.setCurrentRow(row) self.evlistchanged.emit(item) self.latest_row = row return def mouseEventOnThetaList(self, e): if e.type() == QtCore.QEvent.MouseMove or e.type() == QtCore.QEvent.MouseButtonPress: qlist = self.theta_widget pos = qlist.mapFromGlobal(QtGui.QCursor.pos()) row = qlist.indexAt(pos).row() item = qlist.itemAt(pos) #print(row,self.latest_row) if row >= 0: if e.type() != QtCore.QEvent.MouseMove or row != self.latest_row: if e.buttons() == QtCore.Qt.LeftButton: qlist.setCurrentRow(row) self.thetalistchanged.emit(item) self.latest_row = row return def OnSelectionChanged(self): self.RedrawNewPlot() #self.UpdateIndices() self.region.blockSignals(True) if self.idx_selected: self.region.setRegion([self.stack.ev[min(self.idx_selected)], self.stack.ev[max(self.idx_selected)]]) self.region.blockSignals(False) self.spectrum_plotwidget.setXRange(*self.region.getRegion()) self.spectrum_plotwidget.setYRange(np.min(self.plotitem_new.yData),np.max(self.plotitem_new.yData)) return def UpdateIndices(self): self.idx_selected = sorted([self.ev_widget.row(i) for i in self.ev_selected]) if self.com.stack_4d: self.thetaidx_selected = sorted([self.theta_widget.row(i) for i in self.theta_selected]) def RedrawPlots(self): x,y = self.GenerateSpectrum(list(range(self.stack.n_ev))) self.plotitem.setData(x,y) self.OnSelectionChanged() def RedrawNewPlot(self): self.UpdateIndices() x,y = self.GenerateSpectrum(self.idx_selected) self.plotitem_new.setData(x,y) if self.idx_selected: self.region.show() def qListChangeHandler(self,row, dimension): if dimension == "theta": selection = self.theta_selected widget = self.theta_widget elif dimension == "energy": selection = self.ev_selected widget = self.ev_widget if row in selection: selection.remove(row) row.setBackground(QtGui.QColor(0, 0, 0, 0)) else: selection.append(row) row.setBackground(QtGui.QColor('#beaed4')) if dimension == "theta": self.OnScrollTheta(widget.row(row)) elif dimension == "energy": self.OnScrollEng(widget.row(row)) self.OnSelectionChanged() def SetupPlot(self): plot = self.spectrum_plotwidget plot.setBackground("w") plot.setMouseEnabled(x=False, y=False) plot.showGrid(y=True) plot.showAxis("top", show=True) plot.showAxis("right", show=True) by = plot.getAxis("right") bx = plot.getAxis("top") by.setStyle(showValues=False,tickLength=0) bx.setStyle(showValues=False,tickLength=0) self.ay = plot.getAxis("left") ax = plot.getAxis("bottom") ax.setLabel(text="Photon energy [eV]") x,y = self.GenerateSpectrum(list(range(self.stack.n_ev))) self.region = pg.LinearRegionItem(brush=[255,0,0,45],bounds=[np.min(x),np.max(x)]) plot.addItem(self.region, ignoreBounds=False) self.region.setZValue(10) self.spectrum_plotwidget.setBackground("w") self.plotitem = plot.plot(x, y, pen=pg.mkPen(color=0.8, width=2)) self.plotitem_new = plot.plot(x, y, pen=pg.mkPen(color="b", width=2)) self.refmarker = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(color="b", width=2, style=QtCore.Qt.DashLine)) plot.addItem(self.refmarker, ignoreBounds=True) self.region.setRegion((min(x),max(x))) self.region.sigRegionChangeFinished.connect(lambda region: self.UpdateEVRegion(region)) # ---------------------------------------------------------------------- def getDataClosestToRegion(self,region,plotitem,snapregion=False): minidx, maxidx = region.getRegion() data = plotitem.getData()[0] index = lambda x: np.argmin(np.abs(data - x)) minidx = index(minidx) maxidx = index(maxidx) if minidx == maxidx: minidx = 0 maxidx = np.argmax(data) mindata = data[minidx] maxdata = data[maxidx] if snapregion: region.blockSignals(True) region.setRegion([mindata, maxdata]) # snap region to data points region.blockSignals(False) return minidx, maxidx, mindata, maxdata def UpdateEVRegion(self, region): min_idx, max_idx,*_ = self.getDataClosestToRegion(region,self.plotitem, True) y_vals = self.plotitem.yData[min_idx:max_idx + 1] x_vals = self.plotitem.xData[min_idx:max_idx + 1] self.spectrum_plotwidget.setRange(yRange=[np.min(y_vals), np.max(y_vals)], xRange=[np.min(x_vals), np.max(x_vals)], disableAutoRange=True, padding=0.05) if min_idx not in self.idx_selected: for idx in range(int(min_idx),np.min(self.idx_selected)): self.ev_selected.append(self.ev_widget.item(idx)) self.ev_widget.item(idx).setBackground(QtGui.QColor('#beaed4')) else: for idx in range(np.min(self.idx_selected),int(min_idx)): try: self.ev_selected.remove(self.ev_widget.item(idx)) except ValueError: pass self.ev_widget.item(idx).setBackground(QtGui.QColor(0, 0, 0, 0)) if max_idx not in self.idx_selected: for idx in range(np.max(self.idx_selected)+1,int(max_idx)+1): try: self.ev_selected.append(self.ev_widget.item(idx)) except ValueError: pass self.ev_widget.item(idx).setBackground(QtGui.QColor('#beaed4')) else: for idx in range(int(max_idx)+1,np.max(self.idx_selected)+1): try: self.ev_selected.remove(self.ev_widget.item(idx)) except ValueError: pass self.ev_widget.item(idx).setBackground(QtGui.QColor(0, 0, 0, 0)) self.RedrawNewPlot() def OnSelectAll(self): self.ev_widget.clear() self.SetupListEV() if self.com.stack_4d: self.theta_widget.clear() self.SetupListTheta() self.region.setRegion((min(self.stack.ev), max(self.stack.ev))) self.region.show() self.RedrawNewPlot() def OnClearAll(self): #self.ev_widget.clear() #self.SetupListEV() self.region.hide() for idx in self.idx_selected: self.ev_widget.item(idx).setBackground(QtGui.QColor(0, 0, 0, 0)) self.ev_selected = [] self.idx_selected = [] if self.com.stack_4d: for idx in self.thetaidx_selected: self.theta_widget.item(idx).setBackground(QtGui.QColor(0, 0, 0, 0)) self.theta_selected = [] self.thetaidx_selected = [] self.RedrawNewPlot() def SetupListTheta(self): self.theta_selected = [] self.thetaidx_selected = [] for i,e in enumerate(self.stack.theta): # Fill QList with energies #self.stk.shifts.append([1,0,(0.0,0.0)]) #checked [0,1]; pre, post, undefined state for map [-1,1,0],(xshift [float],yshift [float]) item = QtWidgets.QListWidgetItem(str(int(i)).zfill(4)+" at " + format(e, '.1f') + "°") self.theta_widget.addItem(item) self.theta_selected.append(item) self.thetaidx_selected.append(i) item.setBackground(QtGui.QColor('#beaed4')) item.setForeground(QtGui.QColor(0, 0, 0, 128)) def SetupListEV(self): self.ev_selected = [] self.idx_selected = [] for i,e in enumerate(self.stack.ev): # Fill QList with energies #self.stk.shifts.append([1,0,(0.0,0.0)]) #checked [0,1]; pre, post, undefined state for map [-1,1,0],(xshift [float],yshift [float]) item = QtWidgets.QListWidgetItem(str(int(i)).zfill(4)+" at " + format(e, '.2f') + " eV") self.ev_widget.addItem(item) self.ev_selected.append(item) self.idx_selected.append(i) item.setBackground(QtGui.QColor('#beaed4')) item.setForeground(QtGui.QColor(0, 0, 0, 128)) def OnScrollTheta(self, value): self.slider_theta.setValue(value) self.ResetAllItems(self.theta_widget) self.itheta = value #self.stack.absdata = self.stack.stack4D[:,:,:,self.itheta].copy() self.ShowImage() self.theta_widget.setCurrentRow(self.itheta) if self.com.stack_loaded == 1: self.theta_widget.item(value).setForeground(QtGui.QColor(0, 0, 0, 255)) self.RedrawPlots() def OnScrollEng(self, value): self.slider_eng.setValue(value) self.ResetAllItems(self.ev_widget) self.iev = value self.ShowImage() try: self.refmarker.setValue(self.stack.ev[self.iev]) except: pass self.ev_widget.setCurrentRow(self.iev) if self.com.stack_loaded == 1: self.ev_widget.item(value).setForeground(QtGui.QColor(0, 0, 0, 255)) def ShowImage(self): if self.com.stack_4d == 1: self.i_item.setImage(self.stack.stack4D[:, :, int(self.iev),int(self.itheta)]) self.groupBox.setTitle(str('Stack Browser | Image at {0:5.2f} eV and {1:5.1f}°').format(float(self.stack.ev[self.iev]),float(self.stack.theta[self.itheta]), )) else: if self.com.i0_loaded: self.i_item.setImage(self.stack.od3d[:, :, int(self.iev)]) else: self.i_item.setImage(self.stack.absdata[:, :, int(self.iev)]) self.groupBox.setTitle(str('Stack Browser | Image at {0:5.2f} eV').format(float(self.stack.ev[self.iev]))) ## Setup a ROI for an alignment rectangle. By default the whole image area is used. def SetupROI(self): #self.stack.absdata = self.stack.absdata.copy() self.box = pg.RectROI(self.i_item.boundingRect().topLeft(), self.i_item.boundingRect().bottomRight(), pen=(5, 8), handlePen=QtGui.QPen(QtGui.QColor(255, 0, 128, 255)), centered=False, sideScalers=False, removable=False, scaleSnap=True, translateSnap=True, maxBounds=self.i_item.boundingRect()) self.vb.addItem(self.box, ignoreBounds=False) self.box.sigRegionChangeFinished.connect(self.RedrawPlots) self.box.sigRegionChangeStarted.connect(self.OnBoxChanging) self.button_rstroi.clicked.connect(self.OnResetROI) ## The ROI is limited to the visible image area. OnMouseMoveOutside handles the behavior when def OnResetROI(self): self.box.setPos(0, 0, update=False, finish=False) self.box.setSize(self.i_item.boundingRect().bottomRight() - self.box.pos(), update=True, snap=True, finish=True) self.box.show() def OnBoxChanging(self): self.boxsize = self.box.size() self.proxy = pg.SignalProxy(self.vb.scene().sigMouseMoved, rateLimit=30, slot=self.OnMouseMoveOutside) def GetRegion(self): try: self.proxy.disconnect() except AttributeError: pass left = int(self.box.pos().x()) right = left + int(self.box.size().x()) bottom = int(self.box.pos().y()) top = bottom + int(self.box.size().y()) return (left,right,top,bottom) def GenerateSpectrum(self, evselection): left,right,top,bottom = self.GetRegion() if self.com.i0_loaded: self.cb_od_per_px.setVisible(True) if self.cb_od_per_px.isChecked(): self.ay.setLabel(text="Optical density per px") else: self.ay.setLabel(text="Sum of optical densities in ROI") if self.com.stack_4d: total = self.stack.od4d[left:right, bottom:top, :, int(self.itheta)].copy() else: total = self.stack.od3d[left:right, bottom:top, :].copy() else: self.ay.setLabel(text="Photon flux per px [cps]") if self.com.stack_4d == 1: t = [self.stack.theta[i] for i in self.thetaidx_selected] self.label_theta_range.setText( "Theta range: [ " + str(min(t, default=0)) + "° .. " + str( max(t, default=0)) + "° ], # values: " + str( len(t))) total = self.stack.stack4D[left:right, bottom:top, :, int(self.itheta)].copy() else: total = self.stack.absdata[left:right, bottom:top, :].copy() if self.cb_od_per_px.isChecked(): total = total.sum(axis=(0,1)) / (int(self.box.size().x()) * int(self.box.size().y())) else: total = total.sum(axis=(0,1)) x = [self.stack.ev[i] for i in evselection] y = [total[i] for i in evselection] self.label_spatial_range.setText("Stack size: [ "+str(int(self.box.size().x()))+" x "+str(int(self.box.size().y()))+" ] px²") self.label_ev_range.setText( "Energy range: [ " + str(min(x, default=0)) + " .. " + str(max(x, default=0)) + " ] eV, # values: "+ str(len(x))) return (x, y) def ResetAllItems(self,widget): for i in range(widget.count()): widget.item(i).setForeground(QtGui.QColor(0, 0, 0, 128)) def OnMouseMoveOutside(self, ev): mousepos = self.vb.mapSceneToView(ev[0]) if not self.vb.itemBoundingRect(self.i_item).contains(mousepos): maxrect = self.i_item.boundingRect().bottomRight() # if bounds exceeded out_x = max((mousepos-maxrect).x(),0) out_y = max((mousepos-maxrect).y(), 0) if self.box.size() != self.boxsize: # prevents taking action when the box is just dragged and not resized if out_x and out_y: self.box.setSize(self.i_item.boundingRect().bottomRight()-self.box.pos(), update=True, snap=True, finish=False) elif out_x: self.box.setSize([self.i_item.boundingRect().right()-self.box.pos().x(),mousepos.y()-self.box.pos().y()], update=True, snap=True, finish=False) elif out_y: self.box.setSize([mousepos.x()-self.box.pos().x(),self.i_item.boundingRect().bottom()-self.box.pos().y()], update=True, snap=True, finish=False) # ---------------------------------------------------------------------- def OnCancel(self, evt): self.close() # ---------------------------------------------------------------------- def OnAccept(self, evt): if self.cb_croptoroi.isChecked(): left, right, top, bottom = self.GetRegion() else: left, right, top, bottom = (None,None,None,None) if self.cb_remove_evs.isChecked(): selection = self.idx_selected if len(selection) == 0: QtWidgets.QMessageBox.warning(self, 'Error', 'Please select at least one energy value!') return self.stack.n_ev = np.array(len(selection)) self.stack.ev = self.stack.ev[selection] self.stack.data_dwell = self.stack.data_dwell[selection] else: selection = list(range(self.stack.n_ev)) self.stack.absdata = self.stack.absdata[left:right,bottom:top,selection] self.stack.n_cols = self.stack.absdata.shape[0] self.stack.n_rows = self.stack.absdata.shape[1] self.parent.page1.ix = int(self.stack.n_cols/2) self.parent.page1.iy = int(self.stack.n_rows/2) self.stack.scale_bar() if self.com.stack_4d: if self.cb_remove_theta.isChecked(): thetas = self.thetaidx_selected if len(thetas) == 0: QtWidgets.QMessageBox.warning(self, 'Error', 'Please select at least one theta value!') return self.stack.n_theta = len(thetas) self.stack.theta = self.stack.theta[thetas] else: thetas = list(range(self.stack.n_theta)) self.stack.stack4D = self.stack.stack4D[left:right, bottom:top, selection, :] self.stack.stack4D = self.stack.stack4D[:,:, :, thetas] if self.com.i0_loaded: self.stack.i0_mask = self.stack.i0_mask[left:right,bottom:top] if self.com.stack_4d: self.stack.od4d = self.stack.od4d[left:right,bottom:top,selection, thetas] else: self.stack.od3d = self.stack.od3d[left:right,bottom:top,selection] self.stack.od = self.stack.od3d.copy() self.stack.od = np.reshape(self.stack.od, (self.stack.n_rows * self.stack.n_cols, self.stack.n_ev), order='F') self.parent.page1.specfig.I0Update() self.stack.fill_h5_struct_from_stk() if self.com.i0_loaded == 1: self.stack.fill_h5_struct_normalization() # Fix the slider on Page 1! if self.com.stack_4d: self.parent.page1.slider_theta.setRange(0, self.stack.n_theta - 1) self.parent.page1.itheta = 0 self.parent.page1.slider_theta.blockSignals(True) self.parent.page1.slider_theta.setValue(int(self.parent.page1.itheta)) self.parent.page1.slider_theta.blockSignals(False) self.parent.page0.slider_theta.setRange(0, self.stack.n_theta - 1) self.parent.page0.itheta = 0 self.parent.page0.slider_theta.blockSignals(True) self.parent.page0.slider_theta.setValue(int(self.parent.page1.itheta)) self.parent.page0.slider_theta.blockSignals(False) #self.parent.page1.slider_eng.setRange(0, self.stack.n_ev - 1) #self.parent.page1.iev = 0 #self.parent.page1.slider_eng.setValue(int(self.parent.page1.iev)) #self.parent.page0.slider_eng.setRange(0, self.stack.n_ev - 1) #self.parent.page0.iev = 0 #self.parent.page0.slider_eng.setValue(int(self.parent.page1.iev)) # self.parent.page1.loadSpectrum(self.parent.page1.ix, self.parent.page1.iy) #self.parent.page1.Clear() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page0.absimgfig.loadNewImage() self.parent.page1.specfig.ClearandReload() #if showmaptab: # self.parent.page9.Clear() # self.parent.page9.loadData() self.close() class ImageRegistrationDialog(QtWidgets.QDialog): def __init__(self, parent, common): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dialogalign.ui'), self) self.parent = parent self.com = common # Currently disabled for Tomo data # if self.com.stack_4d == 1: # self.bt_align2.setEnabled(True) # else: # self.bt_align2.setEnabled(True) self.bt_align.clicked.connect(self.parent.page1.OnAlignImgs) self.bt_align2.clicked.connect(self.parent.page1.OnAlignImgs2) self.bt_align.clicked.connect(self.done) self.bt_align2.clicked.connect(self.done) #---------------------------------------------------------------------- class ImageRegistrationManual(QtWidgets.QDialog): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(1050, 750) self.setWindowTitle('Stack Alignment') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.stack = stack self.com = common self.have_ref_image = 0 self.regist_calculated = 0 self.iev = 0 self.itheta = 0 self.ref_image_index = 0 self.ref_image_index_theta = 0 self.ref_image = 0 self.man_align = 0 self.man_xref = 0 self.man_yref = 0 self.maxshift = 0 self.auto = True self.xleft = 0 self.xright = self.stack.n_cols self.ybottom = 0 self.ytop = self.stack.n_rows if self.com.stack_4d == 0: self.aligned_stack = self.stack.absdata.copy() self.man_xs = np.zeros((self.stack.n_ev)) self.man_ys = np.zeros((self.stack.n_ev)) self.xshifts = np.zeros((self.stack.n_ev)) self.yshifts = np.zeros((self.stack.n_ev)) else: self.aligned_stack = self.stack.stack4D.copy() self.man_xs = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.man_ys = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.xshifts = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.yshifts = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.minxs = 0 self.maxxs = 0 self.minys = 0 self.maxys = 0 self.showccorr = 0 self.edgee = 0 self.subregion = 0 self.sr_x1 = 0 self.sr_x2 = 0 self.sr_y1 = 0 self.sr_y2 = 0 self.patch = None #panel 1 vbox1 = QtWidgets.QVBoxLayout() self.tc_imageeng = QtWidgets.QLabel(self) self.tc_imageeng.setText("Image at energy: ") gridsizertop = QtWidgets.QGridLayout() frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.absimgfig = Figure((4.0, 4.0)) self.AbsImagePanel = FigureCanvas(self.absimgfig) self.AbsImagePanel.setParent(self) self.AbsImagePanel.mpl_connect('button_press_event', self.OnPointCorrimage) self.AbsImagePanel.setCursor(Qt.CrossCursor) fbox.addWidget(self.AbsImagePanel) frame.setLayout(fbox) gridsizertop.addWidget(frame, 0, 0, QtCore .Qt. AlignLeft) self.slider_eng = QtWidgets.QScrollBar(QtCore.Qt.Vertical) self.slider_eng.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.slider_eng.setRange(0, self.stack.n_ev-1) self.slider_eng.setValue(self.iev) gridsizertop.addWidget(self.slider_eng, 0, 1, QtCore .Qt. AlignLeft) self.slider_theta = QtWidgets.QScrollBar(QtCore.Qt.Horizontal) self.slider_theta.setFocusPolicy(QtCore.Qt.StrongFocus) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) self.slider_theta.setRange(0, self.stack.n_theta-1) self.tc_imagetheta = QtWidgets.QLabel(self) self.tc_imagetheta.setText("4D Data Angle: ") if self.com.stack_4d == 0 : self.tc_imagetheta.setVisible(False) self.slider_theta.setVisible(False) hbox51 = QtWidgets.QHBoxLayout() hbox51.addWidget(self.tc_imagetheta) hbox51.addWidget(self.slider_theta) gridsizertop.addLayout(hbox51, 1, 0) vbox1.addWidget(self.tc_imageeng) vbox1.addLayout(gridsizertop) self.tc_shift1 = QtWidgets.QLabel(self) self.tc_shift2 = QtWidgets.QLabel(self) vbox1.addWidget(self.tc_shift1) vbox1.addWidget(self.tc_shift2) vbox1.addStretch(1) if self.com.stack_4d == 0: self.tc_shift1.setText('X shift: {0:5.2f} pixels'.format(self.xshifts[self.iev])) self.tc_shift2.setText('Y shift: {0:5.2f} pixels'.format(self.yshifts[self.iev])) else: self.tc_shift1.setText('X shift: {0:5.2f} pixels'.format(self.xshifts[self.iev,self.itheta])) self.tc_shift2.setText('Y shift: {0:5.2f} pixels'.format(self.yshifts[self.iev,self.itheta])) #panel 2 vbox2 = QtWidgets.QVBoxLayout() tc2 = QtWidgets.QLabel(self) tc2.setText('Cross-correlation') frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.cscorrfig = Figure((2.4, 2.4)) self.CscorrPanel = FigureCanvas(self.cscorrfig) self.CscorrPanel.setParent(self) fbox.addWidget(self.CscorrPanel) frame.setLayout(fbox) vbox2.addWidget(tc2) vbox2.addWidget(frame) #panel 3 vbox3 = QtWidgets.QVBoxLayout() tc3= QtWidgets.QLabel(self) tc3.setText('Image shifts') frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.shiftsfig = Figure((4.0, 2.4)) self.ShiftsPanel = FigureCanvas(self.shiftsfig) self.ShiftsPanel.setParent(self) self.ShiftsPanel.mpl_connect('button_press_event', self.OnPlotShifts) fbox.addWidget(self.ShiftsPanel) frame.setLayout(fbox) vbox3.addWidget(tc3) vbox3.addWidget(frame) #panel 9 vbox9 = QtWidgets.QVBoxLayout() vbox9.setSpacing(0) groupBox9 = QtWidgets.QGroupBox() self.rb_auto = QtWidgets.QRadioButton( 'Automatic Alignment') self.rb_man = QtWidgets.QRadioButton('Manual Alignment') self.rb_auto.setChecked(True) self.rb_auto.toggled.connect(self.Onrb_automanual) vbox9.addWidget(self.rb_auto) vbox9.addWidget(self.rb_man) groupBox9.setLayout(vbox9) #panel 8 sizer8 = QtWidgets.QGroupBox('This Image') vbox8 = QtWidgets.QVBoxLayout() vbox8.setSpacing(0) self.button_refimg = QtWidgets.QPushButton('Set as Reference Image') self.button_refimg.clicked.connect(self.SetRefImage) vbox8.addWidget(self.button_refimg) self.button_refimgsave = QtWidgets.QPushButton('Save Reference Image') self.button_refimgsave.clicked.connect(self.SaveRefImage) self.button_refimgsave.setEnabled(False) vbox8.addWidget(self.button_refimgsave) self.button_refimgsload = QtWidgets.QPushButton('Load Reference Image') self.button_refimgsload.clicked.connect(self.LoadRefImage) vbox8.addWidget(self.button_refimgsload) line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) vbox8.addSpacing(5) vbox8.addWidget(line) vbox8.addSpacing(5) self.button_remove = QtWidgets.QPushButton('Remove energy from stack') self.button_remove.clicked.connect(self.OnRemoveImage) vbox8.addWidget(self.button_remove) sizer8.setLayout(vbox8) #panel 4 sizer4 = QtWidgets.QGroupBox('Automatic Alignment') vbox4 = QtWidgets.QVBoxLayout() self.button_register = QtWidgets.QPushButton('Calculate image shifts') self.button_register.clicked.connect(self.OnCalcRegistration) self.button_register.setEnabled(False) vbox4.addWidget(self.button_register) #vbox4.addStretch(1) self.button_subregion = QtWidgets.QPushButton('Select subregion on reference') self.button_subregion.clicked.connect(self.OnSelectSubregion) self.button_subregion.setEnabled(False) vbox4.addWidget(self.button_subregion) self.button_delsubregion = QtWidgets.QPushButton('Remove subregion selection') self.button_delsubregion.clicked.connect(self.OnDeleteSubregion) self.button_delsubregion.setEnabled(False) vbox4.addWidget(self.button_delsubregion) vbox4.addStretch(1) groupBox4 = QtWidgets.QGroupBox() hbox43 = QtWidgets.QHBoxLayout() self.cb_edgeenh = QtWidgets.QCheckBox('Edge Enhancement', self) self.cb_edgeenh.stateChanged.connect(self.OnEdgeE) hbox43.addWidget(self.cb_edgeenh) self.rb_sobel = QtWidgets.QRadioButton( 'Sobel') self.rb_prewitt = QtWidgets.QRadioButton('Prewitt') self.rb_prewitt.setChecked(True) self.rb_sobel.setEnabled(False) self.rb_prewitt.setEnabled(False) hbox43.addWidget(self.rb_prewitt) hbox43.addWidget(self.rb_sobel) groupBox4.setLayout(hbox43) vbox4.addWidget(groupBox4) hbox42 = QtWidgets.QHBoxLayout() text1 = QtWidgets.QLabel(self) text1.setText(' Max shift [pixels]: ') self.tc_maxshift = QtWidgets.QSpinBox() self.tc_maxshift.setMinimum(0) self.tc_maxshift.valueChanged[int].connect(self.OnSetMaxShift) hbox42.addWidget(text1) hbox42.addWidget(self.tc_maxshift) vbox4.addLayout(hbox42) vbox4.addStretch(1) self.showcscor_cb = QtWidgets.QCheckBox('Show Cross-correlation', self) self.showcscor_cb.stateChanged.connect(self.OnShowCCorr) vbox4.addWidget(self.showcscor_cb) vbox4.addStretch() sizer4.setLayout(vbox4) #panel 5 vbox5 = QtWidgets.QVBoxLayout() self.tc_refimg = QtWidgets.QLabel(self) self.tc_refimg.setText('Reference image') frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.refimgfig = Figure((4.0, 4.0)) self.RefImagePanel = FigureCanvas(self.refimgfig) self.RefImagePanel.setParent(self) self.RefImagePanel.setCursor(Qt.CrossCursor) fbox.addWidget(self.RefImagePanel) frame.setLayout(fbox) self.RefImagePanel.mpl_connect('button_press_event', self.OnPointRefimage) self.RefImagePanel.mpl_connect('button_release_event', self.OnSelection) vbox5.addWidget(self.tc_refimg) vbox5.addWidget(frame) vbox5.addStretch(1) #panel 6 sizer6 = QtWidgets.QGroupBox('Manual Alignment') vbox6 = QtWidgets.QVBoxLayout() vbox6.setSpacing(0) self.button_manalign = QtWidgets.QPushButton('Pick a point on reference image') self.button_manalign.clicked.connect(self.OnPickRefPoint) self.button_manalign.setEnabled(False) vbox6.addWidget(self.button_manalign) self.button_pick2ndpoint = QtWidgets.QPushButton('This image: click on same point') self.button_pick2ndpoint.clicked.connect(self.OnPickCorrPoint) self.button_pick2ndpoint.setEnabled(False) vbox6.addWidget(self.button_pick2ndpoint) self.button_applyman = QtWidgets.QPushButton('Apply manual shifts') self.button_applyman.clicked.connect(self.OnApplyManShifts) self.button_applyman.setEnabled(False) vbox6.addWidget(self.button_applyman) self.textctrl_ms1 = QtWidgets.QLabel(self) self.textctrl_ms2 = QtWidgets.QLabel(self) vbox6.addWidget(self.textctrl_ms1) vbox6.addWidget(self.textctrl_ms2) self.textctrl_ms1.setText('X manual shift: ') self.textctrl_ms2.setText('Y manual shift: ') sizer6.setLayout(vbox6) #panel 7 vbox7 = QtWidgets.QVBoxLayout() vbox7.setSpacing(0) self.button_saveimg = QtWidgets.QPushButton('Save image shifts plot') self.button_saveimg.clicked.connect(self.OnSaveShiftsPlot) self.button_saveimg.setEnabled(False) vbox7.addWidget(self.button_saveimg) self.button_saveshifts = QtWidgets.QPushButton('Save image shifts') self.button_saveshifts.clicked.connect(self.OnSaveShifts) self.button_saveshifts.setEnabled(False) vbox7.addWidget(self.button_saveshifts) self.button_loadshifts = QtWidgets.QPushButton('Load image shifts') self.button_loadshifts.clicked.connect(self.OnLoadShifts) vbox7.addWidget(self.button_loadshifts) self.button_crop = QtWidgets.QPushButton('Crop aligned images') self.button_crop.clicked.connect(self.OnCropShifts) self.button_crop.setEnabled(False) vbox7.addWidget(self.button_crop) self.button_accept = QtWidgets.QPushButton('Apply Alignment') self.button_accept.clicked.connect(self.OnAccept) self.button_accept.setEnabled(False) vbox7.addWidget(self.button_accept) self.button_close = QtWidgets.QPushButton('Dismiss') self.button_close.clicked.connect(self.close) vbox7.addWidget(self.button_close) hboxtop = QtWidgets.QHBoxLayout() vboxL = QtWidgets.QVBoxLayout() vboxR = QtWidgets.QVBoxLayout() vboxL.addWidget(sizer8) vboxL.addStretch(1) vboxL.addWidget(groupBox9) vboxL.addStretch(1) vboxL.addWidget(sizer4) vboxL.addStretch(1) vboxL.addWidget(sizer6) vboxL.addStretch(1) vboxL.addLayout(vbox7) hboxRT = QtWidgets.QHBoxLayout() hboxRB = QtWidgets.QHBoxLayout() hboxRT.addLayout(vbox1) hboxRT.addLayout(vbox5) hboxRB.addLayout(vbox3) hboxRB.addLayout(vbox2) hboxRB.addStretch(1) vboxR.addStretch(1) vboxR.addLayout(hboxRT) vboxR.addStretch(5) vboxR.addLayout(hboxRB) vboxR.addStretch(5) hboxtop.addLayout(vboxL) hboxtop.addStretch(1) hboxtop.addLayout(vboxR) hboxtop.setContentsMargins(20,20,20,20) self.setLayout(hboxtop) self.ShowImage() #---------------------------------------------------------------------- def ShowImage(self): if self.com.stack_4d == 0: image = self.aligned_stack[:,:,self.iev] else: image = self.aligned_stack[:,:,self.iev,self.itheta] fig = self.absimgfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() _im = axes.imshow(np.rot90(image), cmap=matplotlib.colormaps["gray"]) axes.autoscale(False) # if self.man_align == 2: # lx=matplotlib.lines.Line2D([self.man_yref-self.man_ys[self.iev], # self.man_yref-self.man_ys[self.iev]], # [0,self.stack.n_cols],color='red') # ly=matplotlib.lines.Line2D([0,self.stack.n_rows], # [self.man_xref-self.man_xs[self.iev], # self.man_xref-self.man_xs[self.iev]] ,color='red') # axes.add_line(lx) # axes.add_line(ly) axes.axis("off") self.AbsImagePanel.draw() self.tc_imageeng.setText('Image at energy: {0:5.2f} eV'.format(float(self.stack.ev[self.iev]))) if self.com.stack_4d == 0: self.tc_shift1.setText('X shift: {0:5.2f} pixels'.format(self.xshifts[self.iev])) self.tc_shift2.setText('Y shift: {0:5.2f} pixels'.format(self.yshifts[self.iev])) else: self.tc_shift1.setText('X shift: {0:5.2f} pixels'.format(self.xshifts[self.iev,self.itheta])) self.tc_shift2.setText('Y shift: {0:5.2f} pixels'.format(self.yshifts[self.iev,self.itheta])) if (self.man_align == 2): if self.com.stack_4d == 0: self.textctrl_ms1.setText('X manual shift: {0:5.2f} pixels'.format(self.man_xs[self.iev])) self.textctrl_ms2.setText('Y manual shift: {0:5.2f} pixels'.format(self.man_ys[self.iev])) else: self.textctrl_ms1.setText('X manual shift: {0:5.2f} pixels'.format(self.man_xs[self.iev,self.itheta])) self.textctrl_ms2.setText('Y manual shift: {0:5.2f} pixels'.format(self.man_ys[self.iev,self.itheta])) #---------------------------------------------------------------------- def OnScrollEng(self, value): self.iev = value self.ShowImage() #---------------------------------------------------------------------- def OnScrollTheta(self, value): self.itheta = value self.tc_imagetheta.setText("4D Data Angle: "+str(self.stack.theta[self.itheta])) self.ShowImage() #---------------------------------------------------------------------- def SetRefImage(self): self.ref_image_index = self.iev self.ref_image_index_theta = self.itheta if self.com.stack_4d == 0: self.ref_image = self.aligned_stack[:,:,self.iev].copy() else: self.ref_image = self.aligned_stack[:,:,self.iev,self.itheta].copy() self.ShowRefImage() self.have_ref_image = 1 self.UpdateWidgets() #---------------------------------------------------------------------- def SaveRefImage(self): wildcard = "TIFF File (*.tif);;" fileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Reference Image', '', wildcard) fileName = str(fileName) if fileName == '': return img1 = Image.fromarray(self.ref_image) img1.save(fileName) #---------------------------------------------------------------------- def LoadRefImage(self): wildcard = "TIFF File (*.tif);;" fileName, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Save Reference Image', '', wildcard) fileName = str(fileName) if fileName == '': return from PIL import Image img = Image.open(fileName) self.ref_image = np.array((img)) self.ShowRefImage() self.have_ref_image = 1 self.UpdateWidgets() #---------------------------------------------------------------------- def ShowRefImage(self): fig = self.refimgfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() _im = axes.imshow(np.rot90(self.ref_image), cmap=matplotlib.colormaps["gray"]) if (self.subregion == 1): from matplotlib.path import Path import matplotlib.patches as patches verts = [ (self.sr_x1, self.sr_y1), # left, bottom (self.sr_x1, self.sr_y2), # left, top (self.sr_x2, self.sr_y2), # right, top (self.sr_x2, self.sr_y1), # right, bottom (self.sr_x1, self.sr_y1), # ignored ] codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY, ] path = Path(verts, codes) self.patch = patches.PathPatch(path, facecolor='red') self.patch.set_alpha(0.3) axes.add_patch(self.patch) if self.man_align > 0: lx=matplotlib.lines.Line2D([self.man_xref,self.man_xref], [0,self.stack.n_rows],color='green') ly=matplotlib.lines.Line2D([0,self.stack.n_cols], [self.man_yref,self.man_yref] ,color='green') axes.add_line(lx) axes.add_line(ly) axes.axis("off") self.RefImagePanel.draw() #---------------------------------------------------------------------- def Onrb_automanual(self, enabled): state = enabled if state: self.auto = True self.man_align = 0 else: self.auto = False if self.com.stack_4d == 0: self.aligned_stack = self.stack.absdata.copy() self.man_xs = np.zeros((self.stack.n_ev)) self.man_ys = np.zeros((self.stack.n_ev)) self.xshifts = np.zeros((self.stack.n_ev)) self.yshifts = np.zeros((self.stack.n_ev)) else: self.aligned_stack = self.stack.stack4D.copy() self.man_xs = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.man_ys = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.xshifts = np.zeros((self.stack.n_ev,self.stack.n_theta)) self.yshifts = np.zeros((self.stack.n_ev,self.stack.n_theta)) if self.have_ref_image == 1: fig = self.shiftsfig fig.clf() self.ShiftsPanel.draw() fig = self.cscorrfig fig.clf() self.CscorrPanel.draw() self.ShowImage() self.ShowRefImage() self.UpdateWidgets() #---------------------------------------------------------------------- def ShowCrossCorrelation(self, ccorr, xshift, yshift): fig = self.cscorrfig fig.clf() fig.add_axes(((0.0,0.0,1.0,1.0))) axes = fig.gca() im = axes.imshow(np.rot90(ccorr), cmap=matplotlib.colormaps["gray"]) nx = ccorr.shape[0] ny = ccorr.shape[1] xcenter = xshift + float(nx)/2.0 ycenter = yshift + float(ny)/2.0 xl = xcenter-10 if xl<0: xl=0 xr = xcenter+10 if xr>nx-1: xr=nx-1 yl = ycenter-10 if yl<0: yl=0 yr = ycenter+10 if yr>ny-1: yr=ny-1 lx=matplotlib.lines.Line2D([xl,xr], [ycenter,ycenter], color='green') ly=matplotlib.lines.Line2D([xcenter,xcenter], [yl,yr], color='green') axes.add_line(lx) axes.add_line(ly) axes.axis("off") self.CscorrPanel.draw() #---------------------------------------------------------------------- def OnShowCCorr(self, state): if state == QtCore.Qt.Checked: self.showccorr = 1 else: self.showccorr = 0 #---------------------------------------------------------------------- def OnEdgeE(self, state): if state == QtCore.Qt.Checked: self.edgee = 1 self.rb_sobel.setEnabled(True) self.rb_prewitt.setEnabled(True) else: self.edgee = 0 self.rb_sobel.setEnabled(False) self.rb_prewitt.setEnabled(False) #---------------------------------------------------------------------- def OnSetMaxShift(self, value): self.maxshift = value #---------------------------------------------------------------------- def OnRemoveImage(self, event): self.stack.absdata = np.delete(self.stack.absdata, self.iev, axis=2) self.aligned_stack = np.delete(self.aligned_stack, self.iev, axis=2) if self.com.stack_4d == 1: self.stack.stack4D = np.delete(self.stack.stack4D, self.iev, axis=2) self.stack.n_ev = self.stack.n_ev - 1 self.stack.ev = np.delete(self.stack.ev, self.iev) if self.com.stack_4d == 0: self.stack.data_struct.exchange.data = self.stack.absdata self.xshifts = np.delete(self.xshifts, self.iev) self.yshifts = np.delete(self.yshifts, self.iev) else: self.stack.data_struct.exchange.data = self.stack.stack4D self.xshifts = np.delete(self.xshifts, self.iev, axis=0) self.yshifts = np.delete(self.yshifts, self.iev, axis=0) self.stack.data_struct.exchange.energy = self.stack.ev if self.com.i0_loaded == 1: self.stack.calculate_optical_density() self.iev = self.iev-1 if self.iev < 0: self.iev = 0 #self.slider_eng.setRange(0, self.stack.n_ev-1) #self.parent.page1.slider_eng.setRange(0, self.stack.n_ev-1) #self.parent.page1.iev = self.stack.n_ev/2 #self.parent.page1.slider_eng.setValue(self.parent.page1.iev) #self.parent.page1.loadSpectrum(self.parent.page1.ix, self.parent.page1.iy) #self.parent.page1.loadImage() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page0.absimgfig.loadNewImage() self.parent.page1.specfig.ClearandReload() self.ShowImage() #---------------------------------------------------------------------- def OnCalcRegistration(self, event): if self.com.stack_4d == 1: self.CalcRegistration4D() return QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) #Get Edge enhancement info edge = 0 if self.edgee > 0: if self.rb_sobel.isChecked(): edge = 1 else: edge = 2 #Subregion selection on a reference image if self.subregion == 0: self.sr_x1 = 0 self.sr_x2 = 0 self.sr_y1 = 0 self.sr_y2 = 0 referenceimage = self.ref_image else: referenceimage = self.ref_image[self.sr_x1:self.sr_x2, self.sr_y2:self.sr_y1] for i in range(self.stack.n_ev): if self.subregion == 0: img2 = self.aligned_stack[:,:,i] else: img2 = self.aligned_stack[self.sr_x1:self.sr_x2, self.sr_y2:self.sr_y1, i] if i==0: xshift, yshift, ccorr = self.stack.register_images(referenceimage, img2, have_ref_img_fft = False, edge_enhancement = edge) elif i==self.ref_image_index: xshift = 0 yshift = 0 else: xshift, yshift, ccorr = self.stack.register_images(referenceimage, img2, have_ref_img_fft = True, edge_enhancement = edge) #Limit the shifts to MAXSHIFT chosen by the user if (self.maxshift > 0): if (abs(xshift) > self.maxshift): xshift = np.sign(xshift)*self.maxshift if (abs(yshift) > self.maxshift): yshift = np.sign(yshift)*self.maxshift self.xshifts[i] = xshift self.yshifts[i] = yshift self.PlotShifts() if self.showccorr == 1: self.ShowCrossCorrelation(ccorr, xshift, yshift) QCoreApplication.processEvents() #Apply shifts for i in range(self.stack.n_ev): img = self.aligned_stack[:,:,i] if (abs(self.xshifts[i])>0.02) or (abs(self.yshifts[i])>0.02): shifted_img = self.stack.apply_image_registration(img, self.xshifts[i], self.yshifts[i]) self.aligned_stack[:,:,i] = shifted_img self.regist_calculated = 1 self.iev = 0 self.ShowImage() self.slider_eng.setValue(self.iev) self.UpdateWidgets() min_xshift = np.min(self.xshifts) max_xshift = np.max(self.xshifts) min_yshift = np.min(self.yshifts) max_yshift = np.max(self.yshifts) if min_xshift < self.minxs : self.minxs = min_xshift if max_xshift > self.maxxs : self.maxxs = max_xshift if min_yshift < self.minys : self.minys = min_yshift if max_yshift > self.maxys : self.maxys = max_yshift QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def CalcRegistration4D(self): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) #Get Edge enhancement info edge = 0 if self.edgee > 0: if self.rb_sobel.isChecked(): edge = 1 else: edge = 2 #Subregion selection on a reference image if self.subregion == 0: self.sr_x1 = 0 self.sr_x2 = 0 self.sr_y1 = 0 self.sr_y2 = 0 referenceimage = self.ref_image else: referenceimage = self.ref_image[self.sr_x1:self.sr_x2, self.sr_y2:self.sr_y1] temptheta = self.itheta for j in range(self.stack.n_theta): self.itheta = j for i in range(self.stack.n_ev): if self.subregion == 0: img2 = self.aligned_stack[:,:,i,j] else: img2 = self.aligned_stack[self.sr_x1:self.sr_x2, self.sr_y2:self.sr_y1, i, j] if i==0 and j==0: xshift, yshift, ccorr = self.stack.register_images(referenceimage, img2, have_ref_img_fft = False, edge_enhancement = edge) elif i==self.ref_image_index and j==self.ref_image_index_theta: xshift = 0 yshift = 0 else: xshift, yshift, ccorr = self.stack.register_images(referenceimage, img2, have_ref_img_fft = True, edge_enhancement = edge) #Limit the shifts to MAXSHIFT chosen by the user if (self.maxshift > 0): if (abs(xshift) > self.maxshift): xshift = np.sign(xshift)*self.maxshift if (abs(yshift) > self.maxshift): yshift = np.sign(yshift)*self.maxshift self.xshifts[i,j] = xshift self.yshifts[i,j] = yshift self.PlotShifts() if self.showccorr == 1: self.ShowCrossCorrelation(ccorr, xshift, yshift) QCoreApplication.processEvents() #Apply shifts for i in range(self.stack.n_ev): for j in range(self.stack.n_theta): img = self.aligned_stack[:,:,i,j] if (abs(self.xshifts[i,j])>0.02) or (abs(self.yshifts[i,j])>0.02): shifted_img = self.stack.apply_image_registration(img, self.xshifts[i,j], self.yshifts[i,j]) self.aligned_stack[:,:,i,j] = shifted_img self.itheta = temptheta self.regist_calculated = 1 self.iev = 0 self.ShowImage() self.slider_eng.setValue(self.iev) self.UpdateWidgets() min_xshift = np.min(self.xshifts) max_xshift = np.max(self.xshifts) min_yshift = np.min(self.yshifts) max_yshift = np.max(self.yshifts) if min_xshift < self.minxs : self.minxs = min_xshift if max_xshift > self.maxxs : self.maxxs = max_xshift if min_yshift < self.minys : self.minys = min_yshift if max_yshift > self.maxys : self.maxys = max_yshift QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def OnCropShifts(self, event): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.aligned_stack, self.xleft, self.xright, self.ybottom, self.ytop, = self.stack.crop_registed_images(self.aligned_stack, self.minxs, self.maxxs, self.minys, self.maxys) self.iev = 0 self.ShowImage() self.slider_eng.setValue(self.iev) QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- def OnPickRefPoint(self, event): self.man_align = 1 #---------------------------------------------------------------------- def OnPointRefimage(self, evt): x = evt.xdata y = evt.ydata if (x == None) or (y == None): return if self.subregion == 1: self.sr_x1 = x self.sr_y1 = y self.button_pressed = True self.mousemoveconn = self.RefImagePanel.mpl_connect('motion_notify_event', self.OnSelectionMotion) return if self.man_align == 0: pass if (self.man_align == 1): self.man_xref = int(np.floor(x)) self.man_yref = int(np.floor(y)) if self.man_xref<0 : self.man_xref=0 if self.man_xref>self.stack.n_cols : self.man_xref=self.stack.n_cols if self.man_yref<0 : self.man_yref=0 if self.man_yref>self.stack.n_rows : self.man_yref=self.stack.n_rows self.UpdateWidgets() self.ShowRefImage() #---------------------------------------------------------------------- def OnPickCorrPoint(self): self.man_align = 2 #---------------------------------------------------------------------- def OnPointCorrimage(self, evt): x = evt.xdata y = evt.ydata if (self.man_align == 2): xcorr = float(x) ycorr = float(y) if xcorr<0 : xcorr=0 if xcorr>self.stack.n_cols : xcorr=self.stack.n_cols if ycorr<0 : ycorr=0 if ycorr>self.stack.n_rows : ycorr=self.stack.n_rows if self.com.stack_4d == 0: self.man_xs[self.iev] = self.man_xref - xcorr self.man_ys[self.iev] = -1.0*(self.man_yref - ycorr) self.textctrl_ms1.setText('X manual shift: {0:5.2f} pixels\n'.format(self.man_xs[self.iev])) self.textctrl_ms2.setText('Y manual shift: {0:5.2f} pixels'.format(self.man_ys[self.iev])) else: self.man_xs[self.iev, self.itheta] = self.man_xref - xcorr self.man_ys[self.iev, self.itheta] = -1.0*(self.man_yref - ycorr) self.textctrl_ms1.setText('X manual shift: {0:5.2f} pixels\n'.format(self.man_xs[self.iev, self.itheta])) self.textctrl_ms2.setText('Y manual shift: {0:5.2f} pixels'.format(self.man_ys[self.iev, self.itheta])) self.iev = self.iev + 1 if self.iev > (self.stack.n_ev-1): self.iev = 0 self.slider_eng.setValue(self.iev) self.ShowImage() self.UpdateWidgets() #---------------------------------------------------------------------- def OnApplyManShifts(self): if self.com.stack_4d == 0: for i in range(self.stack.n_ev): img = self.aligned_stack[:,:,i] if (abs(self.man_xs[i])>0.02) or (abs(self.man_ys[i])>0.02): shifted_img = self.stack.apply_image_registration(img, self.man_xs[i], self.man_ys[i]) self.aligned_stack[:,:,i] = shifted_img self.xshifts[i] = self.xshifts[i] + self.man_xs[i] self.yshifts[i] = self.yshifts[i] + self.man_ys[i] self.man_xs[i] = 0 self.man_ys[i] = 0 else: for i in range(self.stack.n_ev): for j in range(self.stack.n_theta): img = self.aligned_stack[:,:,i,j] if (abs(self.man_xs[i,j])>0.02) or (abs(self.man_ys[i,j])>0.02): shifted_img = self.stack.apply_image_registration(img, self.man_xs[i,j], self.man_ys[i,j]) self.aligned_stack[:,:,i,j] = shifted_img self.xshifts[i,j] = self.xshifts[i,j] + self.man_xs[i,j] self.yshifts[i,j] = self.yshifts[i,j] + self.man_ys[i,j] self.man_xs[i,j] = 0 self.man_ys[i,j] = 0 self.regist_calculated = 1 self.man_align = 0 self.ShowRefImage() self.PlotShifts() self.textctrl_ms1.setText('X manual shift: \n') self.textctrl_ms2.setText('Y manual shift: ') self.UpdateWidgets() min_xshift = np.min(self.xshifts) max_xshift = np.max(self.xshifts) min_yshift = np.min(self.yshifts) max_yshift = np.max(self.yshifts) if min_xshift < self.minxs : self.minxs = min_xshift if max_xshift > self.maxxs : self.maxxs = max_xshift if min_yshift < self.minys : self.minys = min_yshift if max_yshift > self.maxys : self.maxys = max_yshift self.ShowImage() #---------------------------------------------------------------------- def OnAccept(self): if self.com.stack_4d == 0: self.stack.absdata = self.aligned_stack self.stack.data_struct.exchange.data = self.stack.absdata else: self.stack.stack4D = self.aligned_stack self.stack.absdata = self.stack.stack4D[:,:,:,self.itheta] self.stack.data_struct.exchange.data = self.stack.stack4D datadim = np.int32(self.stack.absdata.shape) self.stack.n_cols = datadim[0].copy() self.stack.n_rows = datadim[1].copy() self.stack.xshifts = self.xshifts self.stack.yshifts = self.yshifts if self.com.i0_loaded == 1: if self.com.stack_4d == 0: #Resize optical density for i in range(self.stack.n_ev): img = self.stack.od3d[:,:,i] shifted_img = self.stack.apply_image_registration(img, self.xshifts[i], self.yshifts[i]) self.stack.od3d[:,:,i] = shifted_img self.stack.od3d = self.stack.od3d[self.xleft:self.xright, self.ybottom:self.ytop, :] self.stack.od = self.stack.od3d.copy() self.stack.od = np.reshape(self.stack.od, (self.stack.n_cols*self.stack.n_rows, self.stack.n_ev), order='F') else: #Resize optical density for 4D stack for i in range(self.stack.n_ev): for j in range(self.stack.n_theta): img = self.stack.od4d[:,:,i,j] shifted_img = self.stack.apply_image_registration(img, self.xshifts[i,j], self.yshifts[i,j]) self.stack.od4d[:,:,i,j] = shifted_img self.stack.od4d = self.stack.od4d[self.xleft:self.xright, self.ybottom:self.ytop, :, :] self.stack.od3d = self.stack.od4d[:,:,:,self.itheta] self.stack.od = self.stack.od3d.copy() n_pixels = self.stack.n_cols*self.stack.n_rows self.stack.od = np.reshape(self.stack.od, (n_pixels, self.stack.n_ev), order='F') self.stack.data_struct.spectromicroscopy.optical_density = self.stack.od self.stack.data_struct.exchange.energy = self.stack.ev self.stack.data_struct.spectromicroscopy.xshifts = self.xshifts self.stack.data_struct.spectromicroscopy.yshifts = self.yshifts #self.parent.page1.slider_eng.setRange(0,self.stack.n_ev-1) #self.parent.page1.iev = int(self.stack.n_ev/2) #self.parent.page1.slider_eng.setValue(self.parent.page1.iev) #self.parent.page1.ix = int(self.stack.n_cols/2) #self.parent.page1.iy = int(self.stack.n_rows/2) #self.parent.page1.loadSpectrum(self.parent.page1.ix, self.parent.page1.iy) #self.parent.page1.loadImage() #self.parent.page9.loadImage() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page0.absimgfig.loadNewImage() self.parent.page1.specfig.ClearandReload() self.close() #---------------------------------------------------------------------- def PlotShifts(self, ): fig = self.shiftsfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) axes = fig.gca() #Matplotlib has inverted axes! axes.set_xlabel('Photon Energy [eV]') axes.set_ylabel('Shifts (x-red, y-green) [pixels]') if self.com.stack_4d == 0: plot = axes.plot(self.stack.ev,self.xshifts, color='green') plot = axes.plot(self.stack.ev,self.yshifts, color='red') else: plot = axes.plot(self.stack.ev,self.xshifts[:,self.itheta], color='green') plot = axes.plot(self.stack.ev,self.yshifts[:,self.itheta], color='red') self.ShiftsPanel.draw() #---------------------------------------------------------------------- def OnPlotShifts(self, evt): x = evt.xdata y = evt.ydata if self.xshifts.any(): if x < self.stack.ev[0]: sel_ev = 0 elif x > self.stack.ev[self.stack.n_ev-1]: sel_ev = self.stack.n_ev-1 else: indx = np.abs(self.stack.ev - x).argmin() sel_ev = indx self.iev = sel_ev self.ShowImage() self.slider_eng.setValue(self.iev) #---------------------------------------------------------------------- def OnSaveShiftsPlot(self, evt): wildcard = "Portable Network Graphics (*.png);;Adobe PDF Files (*.pdf);;" self.SaveFileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Image Shifts Plot', '', wildcard) self.SaveFileName = str(self.SaveFileName) if self.SaveFileName == '': return path, ext = os.path.splitext(self.SaveFileName) ext = ext[1:].lower() try: matplotlib.rcParams['pdf.fonttype'] = 42 if ext == 'png': fig = self.shiftsfig fig.savefig(self.SaveFileName) if ext =='pdf': fig = self.shiftsfig fig.savefig(self.SaveFileName) except: pass #---------------------------------------------------------------------- def OnSelectSubregion(self, event): self.subregion = 1 self.sr_x1 = 0 self.sr_x2 = 0 self.sr_y1 = 0 self.sr_y2 = 0 self.button_delsubregion.setEnabled(True) #---------------------------------------------------------------------- def OnDeleteSubregion(self, event): self.subregion = 0 self.sr_x1 = 0 self.sr_x2 = 0 self.sr_y1 = 0 self.sr_y2 = 0 self.button_pressed = False self.patch = None self.RefImagePanel.mpl_disconnect(self.mousemoveconn) self.ShowRefImage() #---------------------------------------------------------------------- def OnSelection(self, evt): if (self.man_align > 0) and (self.subregion == 0): return x2, y2 = evt.xdata, evt.ydata if (x2 == None) or (y2 == None): return self.sr_x1 = int(self.sr_x1) self.sr_x2 = int(x2) self.sr_y2 = int(self.sr_y1) self.sr_y1 = int(y2) if self.sr_x1 > self.sr_x2: temp = self.sr_x1 self.sr_x1 = self.sr_x2 self.sr_x2 = temp if self.sr_y2 > self.sr_y1: temp = self.sr_y1 self.sr_y1 = self.sr_y2 self.sr_y2 = temp self.button_pressed = False self.RefImagePanel.mpl_disconnect(self.OnSelectionMotion) self.ShowRefImage() #---------------------------------------------------------------------- def OnSelectionMotion(self, event): x2, y2 = event.xdata, event.ydata if (x2 == None) or (y2 == None): return if self.button_pressed == False: return self.sr_x2 = int(x2) self.sr_y2 = int(y2) fig = self.refimgfig axes = fig.gca() from matplotlib.path import Path import matplotlib.patches as patches verts = [ (self.sr_x1, self.sr_y1), # left, bottom (self.sr_x1, self.sr_y2), # left, top (self.sr_x2, self.sr_y2), # right, top (self.sr_x2, self.sr_y1), # right, bottom (self.sr_x1, self.sr_y1), # ignored ] codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY, ] path = Path(verts, codes) if self.patch != None: self.patch.remove() self.patch = patches.PathPatch(path, facecolor='red') self.patch.set_alpha(0.3) axes.add_patch(self.patch) axes.axis("off") self.RefImagePanel.draw() #---------------------------------------------------------------------- def OnSaveShifts(self, evt): wildcard = "CSV files (*.csv)" filepath, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Please select an alignment file (.csv)', '', wildcard) filepath = str(filepath) if filepath == '': return f = open(filepath, 'w') if self.com.stack_4d == 0: print('********************* Alignment file ********************', file=f) print('*** for ', self.com.filename, file=f) print('*** ev, xshift, yshift', file=f) for ie in range(self.stack.n_ev): print('%.6f, %.6f, %.6f' %(self.stack.ev[ie], self.xshifts[ie], self.yshifts[ie]), file=f) else: print('********************* Alignment file ********************', file=f) print('*** for ', self.com.filename, file=f) print('*** ev, theta, xshift, yshift', file=f) for i in range(self.stack.n_ev): for j in range(self.stack.n_theta): print('%.6f, %.6f, %.6f, %.6f' %(self.stack.ev[i], self.stack.theta[j], self.xshifts[i,j], self.yshifts[i,j]), file=f) f.close() #---------------------------------------------------------------------- def OnLoadShifts(self, evt): wildcard = "I0 CSV files (*.csv)" filepath, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Please select an alignment file (.csv)', '', wildcard) filepath = str(filepath) if filepath == '': return f = open(str(filepath),'r') elist = [] xshiftlist = [] yshiftlist = [] for line in f: if line.startswith('*'): continue else: e, xs, ys = [float (x) for x in line.split(',')] elist.append(e) xshiftlist.append(xs) yshiftlist.append(ys) f.close() self.xshifts = np.zeros((self.stack.n_ev)) self.yshifts = np.zeros((self.stack.n_ev)) for ie in range(self.stack.n_ev): engfl = '%.6f' % ( self.stack.ev[ie]) eng = float(engfl) if eng in elist: ind = elist.index(eng) self.xshifts[ie] = xshiftlist[ind] self.yshifts[ie] = yshiftlist[ind] #Apply shifts self.PlotShifts() for i in range(self.stack.n_ev): img = self.aligned_stack[:,:,i] if (abs(self.xshifts[i])>0.02) or (abs(self.yshifts[i])>0.02): shifted_img = self.stack.apply_image_registration(img, self.xshifts[i], self.yshifts[i]) self.aligned_stack[:,:,i] = shifted_img self.regist_calculated = 1 self.iev = 0 self.ShowImage() self.slider_eng.setValue(self.iev) self.regist_calculated = 1 self.man_align = 0 self.textctrl_ms1.setText('X manual shift: \n') self.textctrl_ms2.setText('Y manual shift: ') self.UpdateWidgets() min_xshift = np.min(self.xshifts) max_xshift = np.max(self.xshifts) min_yshift = np.min(self.yshifts) max_yshift = np.max(self.yshifts) if min_xshift < self.minxs : self.minxs = min_xshift if max_xshift > self.maxxs : self.maxxs = max_xshift if min_yshift < self.minys : self.minys = min_yshift if max_yshift > self.maxys : self.maxys = max_yshift #---------------------------------------------------------------------- def UpdateWidgets(self): if self.auto: self.button_manalign.setEnabled(False) if self.have_ref_image == 1: self.button_register.setEnabled(True) self.button_subregion.setEnabled(True) self.button_refimgsave.setEnabled(True) else: self.button_register.setEnabled(False) self.button_subregion.setEnabled(False) self.button_refimgsave.setEnabled(False) self.button_delsubregion.setEnabled(False) if self.regist_calculated == 1: self.button_crop.setEnabled(True) self.button_accept.setEnabled(True) self.button_saveshifts.setEnabled(True) self.button_saveimg.setEnabled(True) else: self.button_crop.setEnabled(False) self.button_accept.setEnabled(False) self.button_saveshifts.setEnabled(False) self.button_saveimg.setEnabled(False) else: self.button_register.setEnabled(False) self.button_delsubregion.setEnabled(False) self.button_subregion.setEnabled(False) if self.have_ref_image == 1: self.button_manalign.setEnabled(True) self.button_refimgsave.setEnabled(True) else: self.button_manalign.setEnabled(False) self.button_refimgsave.setEnabled(False) if self.regist_calculated == 1: self.button_crop.setEnabled(True) self.button_accept.setEnabled(True) self.button_saveshifts.setEnabled(True) else: self.button_crop.setEnabled(False) self.button_accept.setEnabled(False) self.button_saveshifts.setEnabled(False) if self.man_align == 0: self.button_pick2ndpoint.setEnabled(False) self.button_applyman.setEnabled(False) elif self.man_align == 1: self.button_pick2ndpoint.setEnabled(True) elif self.man_align == 2: self.button_applyman.setEnabled(True) class GeneralPurposeSignals(QtCore.QObject): finished = pyqtSignal() ithetaprogress = pyqtSignal(int) # ---------------------------------------------------------------------- class GeneralPurposeProcessor(QtCore.QRunnable): def __init__(self, parent, queue): super(GeneralPurposeProcessor, self).__init__() self.signals = GeneralPurposeSignals() self.funcdict = {"ShiftImg": self.ShiftImg, "AlignReferenced": self.AlignReferenced} self.parent = parent self.queue = queue self.current_itheta = 0 @pyqtSlot() def run(self): #print('worker', threading.get_ident()) while True: #print(self.parent.pool.pool.activeThreadCount()) #print(QtCore.QThreadPool.activeThreadCount()) try: workerfunc, *args = self.queue.get(False) self.funcdict[workerfunc](*args[0]) #print("busy with task {}".format(workerfunc)) except Empty: # if queue empty break def AlignReferenced(self, data, itheta): if self.current_itheta != itheta: self.current_itheta = itheta self.signals.ithetaprogress.emit(itheta) ref_img = self.EdgeDetect(self.Gauss(self.parent.stack.absdata_cropped[:, :, data[0],itheta])) mov_img = self.EdgeDetect(self.Gauss(self.parent.stack.absdata_cropped[:, :, data[1],itheta])) upsampling = self.UpsamplingFactor() drift, error, _ = phase_cross_correlation(ref_img, mov_img,upsample_factor=upsampling,normalization=None) self.parent.stack.shiftsdict[itheta]["errors"][data[0]] = round(error,4) if self.parent.cb_upsampling.isChecked(): self.parent.stack.shiftsdict[itheta]["xdots"][data[0]] = round(drift[0],2) self.parent.stack.shiftsdict[itheta]["ydots"][data[0]] = round(drift[1],2) else: self.parent.stack.shiftsdict[itheta]["xdots"][data[0]] = round(drift[0],0) self.parent.stack.shiftsdict[itheta]["ydots"][data[0]] = round(drift[1],0) def ShiftImg(self, row,x,y,itheta): borders, padded = self.PadImg(self.parent.stack.absdata4d[:, :, row,itheta],-x,-y) if self.parent.cb_upsampling.isChecked(): shifted = ndimage.fourier_shift(np.fft.fft2(padded), [float(-x),float(-y)]) else: shifted = ndimage.fourier_shift(np.fft.fft2(padded), [int(round(-x,0)),int(round(-y,0))]) shifted = np.fft.ifft2(shifted) self.parent.stack.absdata4d_shifted[:, :, row, itheta] = shifted.real[borders[0]:padded.shape[0]-borders[1],borders[2]:padded.shape[1]-borders[3]] return def PadImg(self, img, x, y): default = 10 # minimum expansion of image borders = 4*[default] if x<0: borders[1] = abs(int(np.floor(x)))+default elif x>0: borders[0] = abs(int(np.ceil(x)))+default if y<0: borders[3] = abs(int(np.floor(y)))+default elif y>0: borders[2] = abs(int(np.ceil(y)))+default padded = np.pad(img,((borders[0],borders[1]),(borders[2],borders[3])),mode = "edge") return (borders, padded) def Gauss(self, im): gaussed = ndimage.gaussian_filter(im, self.parent.spinBoxGauss.value()) return gaussed def EdgeDetect(self, im): if self.parent.cb_edgedetect.isChecked(): im = filters.farid(im) return im def UpsamplingFactor(self): if self.parent.cb_upsampling.isChecked(): fac = 20 ## equal to 0.05 px precision else: fac = 5 ## equal to 0.2 px precision return fac # ---------------------------------------------------------------------- class TaskDispatcher(QtCore.QObject): def __init__(self,parent): print("0 - Task dispatcher called, pool initiated.") super(TaskDispatcher, self).__init__() try: self.worker.signals.ithetaprogress.disconnect() self.worker.signals.finished.disconnect() except: pass self.pool = QtCore.QThreadPool.globalInstance() try: cpus = len(os.sched_getaffinity(0)) # number of cpu threads. not supported on some platforms. except: cpus = os.cpu_count() self.pool.setMaxThreadCount(cpus) self.queue = SimpleQueue() self.parent = parent @pyqtSlot() def run(self): #print(self.queue.qsize()) #print('pool', threading.get_ident()) # if self.parent.com.stack_4d == 0: qsize = int(self.queue.qsize()) maxthreads = self.pool.maxThreadCount() preferred_thread_number = min(maxthreads, qsize) print("2 - Starting threads: ",preferred_thread_number, "threads needed, number of tasks:",qsize) while int(self.queue.qsize()) and self.pool.activeThreadCount() < preferred_thread_number: #start as many threads as needed. #print("active threads"+str(self.pool.activeThreadCount())+" qsize "+str(int(self.queue.qsize()))) worker = GeneralPurposeProcessor(self.parent,self.queue) worker.signals.ithetaprogress.connect(self.parent.IThetaProgress) self.pool.start(worker) time.sleep(0.02) # artifical delay to start threads with a little time separation. Otherwise ShiftImgs freezes in Win10 and MacOS for small stacks! self.pool.waitForDone() print("3 - All tasks are finished.") worker.signals.finished.connect(self.parent.ThreadPoolComplete) worker.signals.finished.emit() #add a task to the queue def enqueuetask(self, func, *args, **kargs): self.queue.put((func, args, kargs)) # ---------------------------------------------------------------------- class ImageRegistrationFFT(QtWidgets.QDialog, QtWidgets.QGraphicsScene): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'showalign2.ui'), self) self.parent = parent self.stack = stack self.com = common self.iev = 0 self.itheta = 0 if self.com.stack_loaded == 1: if self.com.stack_4d: self.stack.absdata4d = self.stack.stack4D.copy() #self.button_alignbatch.setVisible(True) self.button_align.setText("Align Batch") self.thetathread = QtCore.QThread() else: self.stack.absdata4d = np.expand_dims(self.stack.absdata.copy(), axis=3) #self.button_alignbatch.setVisible(False) self.button_align.setText("Align") self.stack.absdata_cropped = self.stack.absdata4d.copy() # This image stack is used by the alignment/shift routine and cropped to the ROI rectangle self.stack.absdata4d_shifted = self.stack.absdata4d.copy() # This is the full-sized output of the alignment/shift routine self.stack.absdata4d_shifted_cropped = self.stack.absdata4d.copy() # This is the output cropped to the common region self.poolthread = QtCore.QThread() self.aligned = False self.SetupUI() def SetupUI(self): self.button_ok.setEnabled(False) self.button_rstroi.setEnabled(False) self.button_rstalign.setEnabled(False) self.button_preview.setEnabled(True) self.slider_theta.setVisible(False) self.setWindowTitle('FFT Stack Alignment') self.pglayout = pg.GraphicsLayout(border=None) self.canvas.setBackground("w") # canvas is a pg.GraphicsView widget self.canvas.setCentralWidget(self.pglayout) self.vb = self.pglayout.addViewBox() self.vb.setAspectLocked() self.i_item = pg.ImageItem(border="k",parent= self) self.vb.setMouseEnabled(x=False, y=False) self.vb.addItem(self.i_item, ignoreBounds=False) self.DriftsWidget.setBackground("w") self.py = self.DriftsWidget.addPlot(row=0, col=0, rowspan=1, colspan=1) self.py.setMouseEnabled(x=True, y=True) #self.i_item = pg.ImageItem(border="k") #self.py.setAspectLocked(lock=True, ratio=1) self.py.showAxis("top", show=True) self.py.showAxis("bottom", show=True) self.py.showAxis("left", show=True) self.py.showAxis("right", show=True) ay1 = self.py.getAxis("left") by1 = self.py.getAxis("right") ax1 = self.py.getAxis("bottom") bx1 = self.py.getAxis("top") ay1.setLabel(text="y-drift", units="px") ay1.enableAutoSIPrefix(enable=True) ay1.setWidth(w=60) ax1.setLabel(text="Photon Energy",units="eV") ax1.enableAutoSIPrefix(enable=True) ay1.setStyle(tickLength=8) ax1.setStyle(tickLength=0) #ax1.setHeight(h=46.2) ax1.setStyle(tickLength=8) by1.setStyle(showValues=False, tickLength=0) bx1.setStyle(showValues=False, tickLength=0) self.px = self.DriftsWidget.addPlot(row=1, col=0, rowspan=1, colspan=1) self.px.setXLink(self.py) self.px.setMouseEnabled(x=True, y=True) #self.i_item = pg.ImageItem(border="k") #self.px.setAspectLocked(lock=True, ratio=1) self.px.showAxis("top", show=True) self.px.showAxis("bottom", show=True) self.px.showAxis("left", show=True) self.px.showAxis("right", show=True) ay2 = self.px.getAxis("left") by2 = self.px.getAxis("right") ax2 = self.px.getAxis("bottom") bx2 = self.px.getAxis("top") ay2.setLabel(text="x-drift",units="px") ay2.enableAutoSIPrefix(enable=True) ay2.setWidth(w=60) ax2.setLabel(text="Photon Energy",units="eV") ax2.enableAutoSIPrefix(enable=True) ay2.setStyle(tickLength=8) ax2.setStyle(tickLength=8) by2.setStyle(showValues=False,tickLength=0) bx2.setStyle(showValues=False,tickLength=0) self.button_ok.clicked.connect(self.OnAccept) self.button_cancel.clicked.connect(self.OnCancel) if self.com.stack_loaded == 1: if self.com.stack_4d: self.slider_theta.setVisible(True) self.slider_theta.setRange(0, self.stack.n_theta - 1) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) #self.maskedvals = [True] * int(self.stack.n_ev) self.spinBoxError.setEnabled(False) self.slider_eng.sliderPressed.connect(self.ShowImage) self.slider_eng.sliderReleased.connect(self.ShowImage) self.button_preview.clicked.connect(self.ShowImage) self.spinBoxGauss.valueChanged.connect(self.ShowImage) self.cb_edgedetect.toggled.connect(self.ShowImage) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.slider_eng.setRange(0, self.stack.n_ev - 1) self.refmarkerx = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(color="b", width=2, style=QtCore.Qt.DashLine)) self.refmarkery = pg.InfiniteLine(angle=90, movable=False, pen=pg.mkPen(color="b", width=2, style=QtCore.Qt.DashLine)) self.px.addItem(self.refmarkerx, ignoreBounds=True) self.py.addItem(self.refmarkery, ignoreBounds=True) self.fit_x = pg.PlotCurveItem(pen=pg.mkPen(color="c", width=2)) self.fit_y = pg.PlotCurveItem(pen=pg.mkPen(color="c", width=2)) self.fit_x.setZValue(200) self.fit_y.setZValue(200) self.px.addItem(self.fit_x, ignoreBounds=True) self.py.addItem(self.fit_y, ignoreBounds=True) self.OnScrollEng(0) self.SetupROI() self.button_align.clicked.connect(self.ComposeAlignQueue) self.xregion = pg.LinearRegionItem(brush=[255, 0, 0, 45], bounds=[self.stack.ev[0], self.stack.ev[-1]]) self.yregion = pg.LinearRegionItem(brush=[255, 0, 0, 45], bounds=[self.stack.ev[0], self.stack.ev[-1]]) self.xregion.setRegion([self.stack.ev[0], self.stack.ev[-1]]) self.yregion.setRegion([self.stack.ev[0], self.stack.ev[-1]]) self.yregion.setZValue(100) self.xregion.setZValue(100) self.px.addItem(self.xregion, ignoreBounds=False) self.py.addItem(self.yregion, ignoreBounds=False) self.xregion.sigRegionChangeFinished.connect(lambda region: self.OnLinRegion(region)) self.yregion.sigRegionChangeFinished.connect(lambda region: self.OnLinRegion(region)) self.xscatter = pg.ScatterPlotItem(pxMode=False) self.yscatter = pg.ScatterPlotItem(pxMode=False) self.InitShiftsDict() self.px.addItem(self.xscatter) self.py.addItem(self.yscatter) self.MakeNewScatterPlots() #self.shifts = self.stack.shifts.copy() self.xscatter.sigClicked.connect(self.OnPointClicked) self.yscatter.sigClicked.connect(self.OnPointClicked) self.cb_autocrop.toggled.connect(self.OnAutoCrop) def InitShiftsDict(self): outer_keys = range(max(self.stack.n_theta,1)) inner_keys = ["xdots", "ydots", "xshifts", "yshifts", "errors", "errormaskedx","errormaskedy","manualmaskedx","manualmaskedy"] self.stack.shiftsdict = {intkey : {key: [False] * int(self.stack.n_ev) for key in inner_keys} for intkey in outer_keys} single_keys = ["filter", "method", "autoquality", "extrapolation", "threshold", "regionlimitx", "regionlimity"] for intkey in outer_keys: self.stack.shiftsdict[intkey].update({key: False for key in single_keys}) def CreateScatterDots(self,shifts,mask): scatterdots = [{'pos': tup[0:2], 'size': 10, #'pen': {'color': 'w', 'width': 2}, 'brush': QtGui.QColor('red')} if tup[2] else {'pos': tup[0:2], 'size': 10, #'pen': {'color': 'w', 'width': 2}, 'brush': QtGui.QColor('blue')} for tup in list(zip(self.stack.ev, shifts, mask))] return scatterdots def OnPointClicked(self, obj, points): # Manually add/remove points to/from fit if in selected region selectscatter = {self.xscatter: ["errormaskedx","manualmaskedx",self.xregion], self.yscatter: ["errormaskedy","manualmaskedy",self.yregion]} idx = points[0].index() mask1 = self.stack.shiftsdict[self.itheta][selectscatter[obj][1]] mask2 = self.stack.shiftsdict[self.itheta][selectscatter[obj][0]] min_idx, max_idx, *_ = self.getDataClosestToRegion(selectscatter[obj][2], obj) if min_idx <= idx <= max_idx : mask1[idx] = not np.logical_or(mask1[idx],mask2[idx]) mask2[idx] = mask1[idx] self.ColorizeScatterDots() self.OnScrollEng(points[0].index()) if self.aligned: self.ComposeShiftQueue() def OnAligned(self): self.aligned = True self.cb_autoerror.stateChanged.disconnect() self.cb_extrapolate.stateChanged.disconnect() self.spinBoxFiltersize.valueChanged.disconnect() self.spinBoxError.valueChanged.disconnect() self.comboBox_approx.currentIndexChanged.disconnect() self.MakeNewScatterPlots() self.OnLinRegion(self.xregion, update=False) self.OnLinRegion(self.yregion, update=False) self.ComposeShiftQueue(init=True) # ---------------------------------------------------------------------- def MaskedScatterDotsArray(self,region): selection = {self.xregion : ["errormaskedx","manualmaskedx"], self.yregion : ["errormaskedy","manualmaskedy"]} array = np.logical_or(self.stack.shiftsdict[self.itheta][selection[region][0]], self.stack.shiftsdict[self.itheta][selection[region][1]]) return array def MakeNewScatterPlots(self): errorthreshold = self.stack.shiftsdict[self.itheta]["threshold"] errors = self.stack.shiftsdict[self.itheta]["errors"] self.MaskScatterDotsAboveErrorThreshold((errors,errorthreshold,self.itheta)) maskedx = self.MaskedScatterDotsArray(self.xregion) maskedy = self.MaskedScatterDotsArray(self.yregion) self.cb_autoerror.stateChanged.connect(self.OnAutoError) self.cb_extrapolate.stateChanged.connect(self.OnExtrapolate) self.spinBoxFiltersize.valueChanged.connect(self.OnFilter) self.spinBoxError.valueChanged.connect(lambda value: self.OnSpinBoxError(value)) self.comboBox_approx.currentIndexChanged.connect(lambda: self.ComposeShiftQueue(init=False)) self.spinBoxError.blockSignals(True) self.spinBoxError.setDecimals(4) self.spinBoxError.setMinimum(np.partition(errors, 1)[1]) # makes sure that at least two elements are selected self.spinBoxError.setStepType(QtWidgets.QAbstractSpinBox.AdaptiveDecimalStepType) self.OnAutoError() self.spinBoxError.blockSignals(False) xdots = self.stack.shiftsdict[self.itheta]["xdots"] ydots = self.stack.shiftsdict[self.itheta]["ydots"] if self.rB_consecutive.isChecked(): # pixel shifts relative to neighboring image xdots = self.ConvertToCumsum(xdots) ydots = self.ConvertToCumsum(ydots) self.xscatter.setData(spots=self.CreateScatterDots(xdots,maskedx), pxMode=True) self.yscatter.setData(spots=self.CreateScatterDots(ydots,maskedy), pxMode=True) def ConvertToCumsum(self,shifts): # Converts list of shifts relative to neighboring images to the cumulative sum of shifts relative to the reference image f = [i for i, x in enumerate(shifts) if isinstance(x, bool)][0] # A little bit hackish. Returns the index of the first boolean value from a list, i.e., the reference image. ll = np.flip(shifts[:f]) # shifts on the left, lower energy side of the reference frame rl= shifts[f:] # shifts on the right, higher energy side of the reference frame lcs = np.flip(np.cumsum(ll)) rcs = np.cumsum(rl) return np.concatenate((lcs, rcs)) def OnAutoError(self): # print("onautoerror",self.stack.shiftsdict[self.itheta]["threshold"]) self.stack.shiftsdict[self.itheta]["autoquality"] = self.cb_autoerror.isChecked() self.stack.shiftsdict[self.itheta]["threshold"] = round(np.mean(self.stack.shiftsdict[self.itheta]["errors"]), 4) if self.cb_autoerror.isChecked() and self.aligned: self.spinBoxError.setEnabled(True) self.spinBoxError.setValue(self.stack.shiftsdict[self.itheta]["threshold"]) else: self.spinBoxError.setEnabled(False) self.spinBoxError.setValue(1) def OnSpinBoxError(self,value): # print("onspinboxerror") self.stack.shiftsdict[self.itheta]["threshold"] = value # print(self.stack.shiftsdict[self.itheta]["threshold"]) self.MaskScatterDotsAboveErrorThreshold((self.stack.shiftsdict[self.itheta]["errors"], value, self.itheta)) self.ColorizeScatterDots() self.ComposeShiftQueue(init=False) def OnExtrapolate(self): self.ComposeShiftQueue(init=False) def OnFilter(self): self.ComposeShiftQueue(init=False) def ColorizeScatterDots(self): brushesx = [QtGui.QColor('red') if bool else QtGui.QColor('blue') for bool in self.MaskedScatterDotsArray(self.xregion)] brushesy = [QtGui.QColor('red') if bool else QtGui.QColor('blue') for bool in self.MaskedScatterDotsArray(self.yregion)] self.xscatter.setBrush(brushesx,update=True) self.yscatter.setBrush(brushesy,update=True) def MaskScatterDotsAboveErrorThreshold(self, errorvals=None): errors, errorthreshold, itheta = errorvals if self.cb_autoerror.isChecked(): self.stack.shiftsdict[itheta]["errormaskedx"] = (errors > np.float64(errorthreshold)) self.stack.shiftsdict[itheta]["errormaskedy"] = (errors > np.float64(errorthreshold)) else: self.stack.shiftsdict[itheta]["errormaskedx"] = [False] * len(errors) self.stack.shiftsdict[itheta]["errormaskedy"] = [False] * len(errors) # ---------------------------------------------------------------------- def initParams(self, itheta): self.stack.shiftsdict[itheta]["regionlimitx"] = self.getDataClosestToRegion(self.xregion,self.xscatter,False)[2:] self.stack.shiftsdict[itheta]["regionlimity"] = self.getDataClosestToRegion(self.yregion,self.yscatter,False)[2:] self.stack.shiftsdict[itheta]["filter"] = self.spinBoxFiltersize.value() self.stack.shiftsdict[itheta]["method"] = self.comboBox_approx.currentIndex() #print(self.stack.shiftsdict[itheta]["method"]) #self.stack.shiftsdict[itheta]["autoquality"] = self.cb_autoerror.isChecked() self.stack.shiftsdict[itheta]["extrapolation"] = self.cb_extrapolate.isChecked() self.stack.shiftsdict[itheta]["threshold"] = round(np.mean(self.stack.shiftsdict[itheta]["errors"]), 4) def restoreParams(self,itheta): self.xregion.blockSignals(True) self.yregion.blockSignals(True) self.cb_autoerror.blockSignals(True) self.cb_extrapolate.blockSignals(True) self.comboBox_approx.blockSignals(True) self.spinBoxFiltersize.blockSignals(True) self.xregion.setRegion(self.stack.shiftsdict[itheta]["regionlimitx"]) self.yregion.setRegion(self.stack.shiftsdict[itheta]["regionlimity"]) self.cb_autoerror.setChecked(self.stack.shiftsdict[itheta]["autoquality"]) self.spinBoxError.blockSignals(True) #self.spinBoxError.setValue(self.stack.shiftsdict[self.itheta]["threshold"]) if self.cb_autoerror.isChecked() and self.aligned: self.spinBoxError.setEnabled(True) self.spinBoxError.setValue(self.stack.shiftsdict[self.itheta]["threshold"]) else: self.spinBoxError.setEnabled(False) self.spinBoxError.setValue(1) self.spinBoxError.blockSignals(False) self.cb_extrapolate.setChecked(self.stack.shiftsdict[itheta]["extrapolation"]) self.comboBox_approx.setCurrentIndex(self.stack.shiftsdict[itheta]["method"]) if self.comboBox_approx.currentIndex() == 0: self.spinBoxFiltersize.setEnabled(True) elif self.comboBox_approx.currentIndex() == 1: # linear regression self.spinBoxFiltersize.setEnabled(False) self.spinBoxFiltersize.setValue(self.stack.shiftsdict[itheta]["filter"]) # if limits: # region.setRegion(list(limits)) # else: # region.setRegion([self.stack.ev[0],self.stack.ev[-1]]) #self.spinBoxError.blockSignals(False) self.xregion.blockSignals(False) self.yregion.blockSignals(False) self.cb_autoerror.blockSignals(False) self.cb_extrapolate.blockSignals(False) self.comboBox_approx.blockSignals(False) self.spinBoxFiltersize.blockSignals(False) def getDataClosestToRegion(self,region,plotitem,snapregion=False): selectregion = {self.xregion : "regionlimitx", self.yregion : "regionlimity"} limits = selectregion[region] minidx, maxidx = region.getRegion() data = plotitem.getData()[0] index = lambda x: np.argmin(np.abs(data - x)) minidx = index(minidx) maxidx = index(maxidx) if minidx == maxidx: minidx = 0 maxidx = np.argmax(data) mindata = data[minidx] maxdata = data[maxidx] if snapregion: self.stack.shiftsdict[self.itheta][limits] = (mindata,maxdata) # snap region to data points region.setRegion((mindata,maxdata)) return minidx, maxidx, mindata, maxdata def ApplyApproximationFunction(self,region): boolarray = self.MaskedScatterDotsArray(region) selectscatter = {self.xregion: [self.xscatter, "x"] , self.yregion: [self.yscatter, "y"]} selectfit= {self.xregion : self.fit_x, self.yregion : self.fit_y} scatter = selectscatter[region][0] fit = selectfit[region] #min_idx, max_idx, *_ = self.getDataClosestToRegion(region,scatter) selected = np.count_nonzero(boolarray == False) xdata, ydata = scatter.getData() #xdata = xdata[min_idx:max_idx] xdata = xdata[~boolarray] #ydata = ydata[min_idx:max_idx] ydata = ydata[~boolarray] #print(selected) if selected < 2: return [] if self.comboBox_approx.currentIndex() == 0: # moving average self.spinBoxFiltersize.setEnabled(True) approximated= ndimage.filters.uniform_filter1d(ydata,self.spinBoxFiltersize.value(),mode = "nearest") elif self.comboBox_approx.currentIndex() == 1: # linear regression self.spinBoxFiltersize.setEnabled(False) reg = linregress([xdata,ydata]) approximated = [reg.slope * i + reg.intercept for i in xdata] if self.cb_extrapolate.isChecked(): fillval= "extrapolate" else: fillval=(approximated[0],approximated[-1]) interpolate_func = interp1d(xdata, approximated,kind="linear",fill_value=fillval,bounds_error=False) fitdata = [self.stack.ev,np.around(interpolate_func(self.stack.ev),2)] # round fit to two decimals, i.e., 1/self.upsampling of a px fit.setData(x=fitdata[0], y=fitdata[1]) fit.show() return fitdata[1] def resetPoolThread(self): try: self.poolthread.started.disconnect() self.poolthread.quit() self.poolthread.wait() except: pass self.pool = TaskDispatcher(self) self.pool.moveToThread(self.poolthread) # GUI is not blocking during calculation due to this self.poolthread.started.connect(self.pool.run) def IThetaProgress(self,itheta): #print("ithetaprogress") # Each thread calls this function. The condition prevents multiple calls. if self.slider_theta.value() != itheta: #self.slider_theta.blockSignals(True) self.slider_theta.setValue(itheta) #self.slider_theta.blockSignals(False) #print(self.pool.pool.activeThreadCount()) def ThreadPoolComplete(self): #print(str(self.pool.pool.activeThreadCount())+" THREADS REMAINING FROM POOL.") #self.slider_theta.setValue(0) if not self.aligned and self.pool.pool.activeThreadCount() == 0: print("-------------") self.OnAligned() elif self.aligned and self.pool.pool.activeThreadCount() == 0: self.OnAutoCrop() print("-------------") QtWidgets.QApplication.restoreOverrideCursor() def ComposeAlignQueue(self): self.button_align.setEnabled(False) ref_idx = self.iev # Reset reference img: self.resetPoolThread() # Check scikit-image version: if parse_version(version_check('scikit-image')) >= parse_version('0.19.1'): AlignFunc = "AlignReferenced" else: print("scikit-image version <= 0.19.1 is not supported") return itheta = 0 #necessary work around for 3d stacks and if 4d stack is loaded with LoadStack() if self.com.stack_4d != 1: ntheta = 1 else: ntheta = max(self.stack.n_theta, 1) while itheta < ntheta: idx = copy.copy(self.stack.n_ev) while idx: # Generate pairs of indices starting at reference image index. next = ref_idx + (self.stack.n_ev - idx) prev = ref_idx - (self.stack.n_ev - idx) running = 2 if self.rB_referenced.isChecked(): # compare reference frame to next and previous frame. if next < self.stack.n_ev-1: self.pool.enqueuetask(AlignFunc, (next + 1, ref_idx), itheta) else: running -= 1 if prev > 0: self.pool.enqueuetask(AlignFunc, (prev - 1, ref_idx), itheta) else: running -= 1 if running: idx -= 1 else: break elif self.rB_consecutive.isChecked(): # compare frame to next and previous frame. if next < self.stack.n_ev-1: self.pool.enqueuetask(AlignFunc, (next + 1, next), itheta) else: running -= 1 if prev > 0: self.pool.enqueuetask(AlignFunc, (prev - 1, prev), itheta) else: running -= 1 if running: idx -= 1 else: break itheta = itheta + 1 if not self.pool.queue.empty(): print("1 - Task queue composed. Starting poolthread for displacement estimation.") QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.poolthread.start() def ComposeShiftQueue(self, init=False): # print("composeshift initial?",str(init)) self.resetPoolThread() #array = np.logical_or(self.MaskedScatterDotsArray(self.xregion),self.MaskedScatterDotsArray(self.yregion)) if not init: ntheta = [self.itheta] else: if self.com.stack_4d != 1: ntheta = range(1) else: ntheta = range(max(self.stack.n_theta,1)) # necessary work around for 3d stacks and if 4d stack is loaded with LoadStack() for itheta in ntheta: self.initParams(itheta) for itheta in ntheta: #print(itheta) self.stack.shiftsdict[self.itheta]["filter"] = self.spinBoxFiltersize.value() self.stack.shiftsdict[self.itheta]["method"] = self.comboBox_approx.currentIndex() self.slider_theta.setValue(itheta) #time.sleep(0.2) xshifts = self.ApplyApproximationFunction(self.xregion) yshifts = self.ApplyApproximationFunction(self.yregion) # if empty arrays, i.e., if less than 2 dots selected, do nothing if not any(xshifts) and not any(yshifts): return for ev in range(self.stack.n_ev): # Only enqueue after reset or if new shift value is different to previous shift value if (isinstance(self.stack.shiftsdict[itheta]["xshifts"][ev], bool) or isinstance(self.stack.shiftsdict[itheta]["yshifts"][ev], bool) or (self.stack.shiftsdict[itheta]["xshifts"][ev],self.stack.shiftsdict[itheta]["yshifts"][ev]) != (xshifts[ev],yshifts[ev])): self.stack.shiftsdict[itheta]["xshifts"][ev] = xshifts[ev] self.stack.shiftsdict[itheta]["yshifts"][ev] = yshifts[ev] self.pool.enqueuetask("ShiftImg", ev, xshifts[ev], yshifts[ev],itheta) if not self.pool.queue.empty(): print("1 - Task queue composed. Starting poolthread for image shifting.") QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.poolthread.start() # ---------------------------------------------------------------------- def GetIndexPairs(self): idxtuplelst = [(i+1,i) for i in range(self.stack.n_ev-1)] return idxtuplelst # ---------------------------------------------------------------------- def OnAutoCrop(self): if self.aligned: self.button_preview.blockSignals(True) self.button_preview.setChecked(False) self.button_preview.blockSignals(False) self.cb_autocrop.blockSignals(True) self.CropStack4D() self.cb_autocrop.blockSignals(False) self.OnScrollEng(self.iev) self.button_rstroi.setEnabled(True) self.button_rstalign.setEnabled(True) self.button_ok.setEnabled(True) def CropStack4D(self): self.stack.absdata4d_shifted_cropped = self.stack.absdata4d_shifted.copy() if self.cb_autocrop.isChecked(): ntheta = range(max(self.stack.n_theta,1)) # necessary work around for 3d stacks and if 4d stack is loaded with LoadStack() globalminx = min([j for i in [self.stack.shiftsdict[theta]["xshifts"] for theta in ntheta] for j in i]) globalmaxx = max([j for i in [self.stack.shiftsdict[theta]["xshifts"] for theta in ntheta] for j in i]) globalminy = min([j for i in [self.stack.shiftsdict[theta]["yshifts"] for theta in ntheta] for j in i]) globalmaxy = max([j for i in [self.stack.shiftsdict[theta]["yshifts"] for theta in ntheta] for j in i]) if globalminx == globalmaxx == globalminy == globalmaxy == False: return #if not self.com.stack_4d: self.box.hide() l = -int(np.floor(globalminx)) r = -int(np.ceil(globalmaxx)) cr = r if r < 0 else None if l < 0: l = 0 cr = cr - l b = -int(np.floor(globalminy)) t = -int(np.ceil(globalmaxy)) ct = t if t < 0 else None if b < 0: b = 0 ct = ct - b if 0 in self.stack.absdata4d_shifted_cropped[l:cr,b:ct,:,self.itheta].shape: QtWidgets.QMessageBox.warning(self, 'Error', 'The alignment failed. Cropping would result in a zero-dimensional image. Please check your settings. Auto-crop has been disabled. You can re-enable it manually.') self.cb_autocrop.setChecked(False) self.OnResetAlignROI() else: self.stack.absdata4d_shifted_cropped = self.stack.absdata4d_shifted_cropped[l:cr,b:ct,:,:] if self.com.i0_loaded == 1: self.stack.i0_mask = self.stack.i0_mask[l: cr, b: ct] print("4 - Stack & I0 mask cropped to common region: ", globalminx, globalmaxx, globalminy, globalmaxy) return print("4 - Stack cropped to common region: ", globalminx, globalmaxx, globalminy, globalmaxy) else: self.box.show() def OnScrollEng(self, value): self.slider_eng.setValue(value) self.iev = value self.ShowImage() self.refmarkerx.setValue(self.stack.ev[self.iev]) self.refmarkery.setValue(self.stack.ev[self.iev]) def OnScrollTheta(self, value): #if value != self.itheta: #print("Theta slider" + str(value)) self.slider_theta.setValue(value) self.itheta = value #self.ClearShifts() self.ShowImage() if self.aligned: #self.MakeNewScatterPlots() errorthreshold = self.stack.shiftsdict[self.itheta]["threshold"] errors = self.stack.shiftsdict[self.itheta]["errors"] if not errorthreshold: errorthreshold = round(np.mean(errors), 4) self.restoreParams(self.itheta) self.MaskScatterDotsAboveErrorThreshold((errors, errorthreshold, self.itheta)) # if self.cb_autoerror.isChecked(): # self.spinBoxError.blockSignals(True) # self.spinBoxError.setValue(errorthreshold) # self.spinBoxError.blockSignals(False) xdots = self.stack.shiftsdict[self.itheta]["xdots"] ydots = self.stack.shiftsdict[self.itheta]["ydots"] maskedx = self.MaskedScatterDotsArray(self.xregion) maskedy = self.MaskedScatterDotsArray(self.yregion) self.xscatter.setData(spots=self.CreateScatterDots(xdots, maskedx), pxMode=True) self.yscatter.setData(spots=self.CreateScatterDots(ydots, maskedy), pxMode=True) self.fit_x.setData(x=self.stack.ev, y=self.stack.shiftsdict[self.itheta]["xshifts"]) self.fit_y.setData(x=self.stack.ev, y=self.stack.shiftsdict[self.itheta]["yshifts"]) #min_idx, max_idx, min_ev, max_ev = self.getDataClosestToRegion(self.xregion, self.xscatter, True) #min_idx, max_idx, min_ev, max_ev = self.getDataClosestToRegion(self.yregion, self.yscatter, True) #self.fit_x.show() self.OnLinRegion(self.xregion, update=False) self.OnLinRegion(self.yregion, update=False) def ShowImage(self): self.stack.absdata_shifted_cropped = self.stack.absdata4d_shifted_cropped[:, :, :, int(self.itheta)] im = self.stack.absdata_shifted_cropped[:, :, int(self.iev)] if self.button_preview.isChecked(): im = ndimage.gaussian_filter(im, self.spinBoxGauss.value()) if self.cb_edgedetect.isChecked(): im = filters.farid(im) self.i_item.setImage(im) if self.com.stack_4d == 1: self.groupBox.setTitle(str('Stack Browser | Image at {0:5.2f} eV and {1:5.1f}°').format(float(self.stack.ev[self.iev]),float(self.stack.theta[self.itheta]), )) else: self.groupBox.setTitle(str('Stack Browser | Image at {0:5.2f} eV').format(float(self.stack.ev[self.iev]))) ## Setup a ROI for an alignment rectangle. By default the whole image area is used. def SetupROI(self): self.box = pg.RectROI(self.i_item.boundingRect().topLeft(), self.i_item.boundingRect().bottomRight(), pen=(5, 8), handlePen=QtGui.QPen(QtGui.QColor(255, 0, 128, 255)), centered=False, sideScalers=False, removable=False, scaleSnap=True, translateSnap=True, maxBounds=self.i_item.boundingRect()) self.vb.addItem(self.box, ignoreBounds=False) self.box.sigRegionChangeFinished.connect(self.OnBoxChanged) self.box.sigRegionChangeStarted.connect(self.OnBoxChanging) self.button_rstroi.clicked.connect(self.OnResetAlignROI) self.button_rstalign.clicked.connect(self.OnResetAlign) ## The ROI is limited to the visible image area. OnMouseMoveOutside handles the behavior when def ClearShifts(self): self.aligned = False self.cb_autoerror.stateChanged.disconnect() self.fit_x.hide() self.fit_y.hide() self.InitShiftsDict() self.button_align.setEnabled(True) self.MakeNewScatterPlots() def OnResetAlignROI(self): self.ClearShifts() self.stack.absdata4d_shifted_cropped = self.stack.absdata4d.copy() self.OnScrollEng(self.iev) self.box.setPos(0, 0, update=False, finish=False) self.box.setSize(self.i_item.boundingRect().bottomRight() - self.box.pos(), update=True, snap=True, finish=True) self.box.show() self.button_rstroi.setEnabled(False) self.button_rstalign.setEnabled(False) self.button_ok.setEnabled(False) def OnResetAlign(self): self.ClearShifts() self.stack.absdata4d_shifted_cropped = self.stack.absdata4d.copy() self.OnScrollEng(self.iev) self.box.show() self.button_rstalign.setEnabled(False) self.button_ok.setEnabled(False) def OnBoxChanging(self): self.boxsize = self.box.size() self.proxy = pg.SignalProxy(self.vb.scene().sigMouseMoved, rateLimit=30, slot=self.OnMouseMoveOutside) def OnBoxChanged(self): try: self.proxy.disconnect() except AttributeError: pass left = int(self.box.pos().x()) right = left + int(self.box.size().x()) bottom = int(self.box.pos().y()) top = bottom + int(self.box.size().y()) self.stack.absdata_cropped = self.stack.absdata4d[left:right, bottom:top, :,:].copy() def OnLinRegion(self, region, update=True): #print("OnLinRegion", str(update)) selectregion= {self.xregion : "manualmaskedx", self.yregion : "manualmaskedy"} selectscatter = {self.xregion : self.xscatter, self.yregion : self.yscatter} selectregionlimit= {self.xregion : "regionlimitx", self.yregion : "regionlimity"} selectplot = {self.xregion : self.px, self.yregion : self.py} #print("onlinregion "+ selectregion[region]) scatter = selectscatter[region] min_idx, max_idx, min_ev, max_ev = self.getDataClosestToRegion(region,scatter,update) selection = [*range(min_idx, max_idx+1)] for idx,val in enumerate(self.stack.shiftsdict[self.itheta]["manualmaskedx"]): if idx not in selection: self.stack.shiftsdict[self.itheta][selectregion[region]][idx] = True else: self.stack.shiftsdict[self.itheta][selectregion[region]][idx] = False y_vals= selectscatter[region].data["y"][min_idx:max_idx + 1] selectplot[region].setRange(yRange=[np.min(y_vals), np.max(y_vals)], disableAutoRange=True, padding=0.1) self.ColorizeScatterDots() #filter = [True if idx in selection else False for idx,bool in enumerate(self.stack.shiftsdict[self.itheta]["manualmaskedx"])] #print(selection,filter) #self.UpdateScatterPlots(region, id) if not self.aligned: for theta in range(self.stack.n_theta): self.stack.shiftsdict[theta][selectregionlimit[region]] = (min_ev, max_ev) if self.aligned and update: self.stack.shiftsdict[self.itheta][selectregionlimit[region]] = (min_ev, max_ev) #print("ComposeShiftQueue"+selectregion[region]) self.ComposeShiftQueue() def OnMouseMoveOutside(self, ev): mousepos = self.vb.mapSceneToView(ev[0]) if not self.vb.itemBoundingRect(self.i_item).contains(mousepos): maxrect = self.i_item.boundingRect().bottomRight() # if bounds exceeded out_x = max((mousepos-maxrect).x(),0) out_y = max((mousepos-maxrect).y(), 0) if self.box.size() != self.boxsize: # prevents taking action when the box is just dragged and not resized if out_x and out_y: self.box.setSize(self.i_item.boundingRect().bottomRight()-self.box.pos(), update=True, snap=True, finish=False) elif out_x: self.box.setSize([self.i_item.boundingRect().right()-self.box.pos().x(),mousepos.y()-self.box.pos().y()], update=True, snap=True, finish=False) elif out_y: self.box.setSize([mousepos.x()-self.box.pos().x(),self.i_item.boundingRect().bottom()-self.box.pos().y()], update=True, snap=True, finish=False) # ---------------------------------------------------------------------- def OnCancel(self, evt): self.stack.absdata_shifted_cropped = self.stack.absdata self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page0.absimgfig.loadNewImage() #if showmaptab: # self.parent.page9.Clear() # self.parent.page9.loadData() self.close() # ---------------------------------------------------------------------- def OnAccept(self, evt): if self.com.stack_4d == 0: self.stack.absdata = self.stack.absdata4d_shifted_cropped[:,:,:,0] self.stack.data_struct.exchange.data = self.stack.absdata else: self.stack.stack4D = self.stack.absdata4d_shifted_cropped self.stack.absdata = self.stack.stack4D[:, :, :, self.itheta] self.stack.data_struct.exchange.data = self.stack.stack4D #QtWidgets.QMessageBox.warning(self, 'Error', '4D stack not yet supported.') datadim = np.int32(self.stack.absdata.shape) self.stack.n_cols = datadim[0].copy() self.stack.n_rows = datadim[1].copy() if self.com.i0_loaded == 1: self.parent.page1.specfig.I0Update() self.stack.data_struct.exchange.energy = self.stack.ev #ToDo: Handshake shift data for spectral roi #self.stack.data_struct.spectromicroscopy.xshifts = self.x_shiftstemp #self.stack.data_struct.spectromicroscopy.yshifts = self.y_shiftstemp #self.parent.page1.slider_eng.setRange(0,self.stack.n_ev-1) #self.parent.page1.iev = int(self.stack.n_ev/2) #self.parent.page1.slider_eng.setValue(self.parent.page1.iev) self.parent.page0.absimgfig.loadNewImage() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page1.specfig.ClearandReload() #self.parent.page1.ix = int(self.stack.n_cols/2) #self.parent.page1.iy = int(self.stack.n_rows/2) #self.parent.page1.loadSpectrum(self.parent.page1.ix, self.parent.page1.iy) #self.parent.page1.loadImage() #if showmaptab: # self.parent.page9.Clear() # self.parent.page9.loadData() self.close() #----------------------------------------------------------------------- class SpectralImageMap(QtWidgets.QDialog): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'showspectralroi.ui'), self) self.parent = parent self.stack = stack self.com = common self.iev = 0 self.itheta = 0 self.setWindowTitle('Spectral Image Map') self.parent.pbSelfromSpec.setEnabled(False) self.button_close.clicked.connect(self.closeEvent) if self.com.stack_loaded == 1: self.SetupPlot() def OnSelectionChanged(self): self.region_i0.blockSignals(True) if self.idx_selected: self.region_i0.setRegion([self.stack.ev[min(self.idx_selected)], self.stack.ev[max(self.idx_selected)]]) self.region_i0.blockSignals(False) return def GenerateSpectrum(self, evselection): if self.com.i0_loaded == 1: if self.com.stack_4d == 1: total = self.stack.od4d[:, :, :, int(self.itheta)].copy() else: total = self.stack.od3d[:, :, :].copy() else: if self.com.stack_4d == 1: # t = [self.stack.theta[i] for i in self.thetaidx_selected] # self.label_theta_range.setText( # "Theta range: [ " + str(min(t, default=0)) + "° .. " + str( # max(t, default=0)) + "° ], # values: " + str( # len(t))) total = self.stack.stack4D[:, :, :, int(self.itheta)].copy() else: total = self.stack.absdata[:, :, :].copy() total = total.sum(axis=(0,1)) #/ (int(self.box.size().x()) * int(self.box.size().y())) x = self.stack.ev y = total return (x, y) def SetupPlot(self): x, y = self.GenerateSpectrum(list(range(self.stack.n_ev))) self.spectrum_plotwidget.setBackground("w") self.region_i0 = pg.LinearRegionItem(brush=QtGui.QColor('#88beaed4'),hoverBrush=QtGui.QColor('#ccbeaed4'), bounds=[np.min(x), np.max(x)]) self.region_i0.setZValue(10) self.region_i = pg.LinearRegionItem(brush=QtGui.QColor('#887fc97f'),hoverBrush=QtGui.QColor('#cc7fc97f'), bounds=[np.min(x), np.max(x)]) self.region_i.setZValue(11) plot = self.spectrum_plotwidget plot.setBackground("w") plot.addItem(self.region_i0, ignoreBounds=False) plot.addItem(self.region_i, ignoreBounds=False) plot.setMouseEnabled(x=True, y=True) plot.showGrid(y=True) plot.showAxis("top", show=True) plot.showAxis("right", show=True) by = plot.getAxis("right") bx = plot.getAxis("top") by.setStyle(showValues=False, tickLength=0) bx.setStyle(showValues=False, tickLength=0) ay = plot.getAxis("left") ax = plot.getAxis("bottom") ax.setLabel(text="Photon energy [eV]") if self.com.i0_loaded: ay.setLabel(text="Sum of optical densities") else: ay.setLabel(text="Photon flux [cps]") self.plotitem = plot.plot(x, y, pen=pg.mkPen(color="b", width=2),symbolBrush=(0,0,255),symbolPen=('k')) tristate_list = [item for row in self.stack.shifts for item in row][1::3] self.ColorizeScatterDots(tristate_list) self.plotitem.setZValue(1) self.region_i0.setRegion((min(x), min(x))) self.region_i.setRegion((max(x), max(x))) self.label_i0 = pg.InfLineLabel(self.region_i0.lines[0], text="I0 / pre-edge", movable=False,angle=90, position=0.05, anchors=(0,0),color=(0, 0, 0)) self.label_i = pg.InfLineLabel(self.region_i.lines[1], text="I / on-edge", movable=False,angle=90, position=0.75, anchors=(0,0),color=(0, 0, 0)) self.region_i0.sigRegionChanged.connect(lambda region: self.UpdateZvalue(region)) self.region_i.sigRegionChanged.connect(lambda region: self.UpdateZvalue(region)) self.region_i0.sigRegionChangeFinished.connect(lambda region: self.UpdateSelection(region)) self.region_i.sigRegionChangeFinished.connect(lambda region: self.UpdateSelection(region)) # ---------------------------------------------------------------------- # Colorize scatter dots according to the pre/on-edge color def ColorizeScatterDots(self,list): brushes = [QtGui.QColor('#ffbeaed4') if val == -1 else QtGui.QColor('blue') if val == 0 else QtGui.QColor('#ff7fc97f') for val in list] self.plotitem.scatter.setBrush(brushes,update=True) # ---------------------------------------------------------------------- # Bring active region to front def UpdateZvalue(self,region): otherregion = {self.region_i: self.region_i0, self.region_i0: self.region_i} region.setZValue(11) otherregion[region].setZValue(10) # ---------------------------------------------------------------------- def getDataClosestToRegion(self, region, plotitem, snapregion=True): otherregion = {self.region_i : self.region_i0, self.region_i0: self.region_i} otherregion = otherregion[region] minidx, maxidx = region.getRegion() data = plotitem.getData()[0] index = lambda x: np.argmin(np.abs(data - x)) minidx = index(minidx)+1 if minidx > data[index(minidx)] else index(minidx) maxidx = index(maxidx)-1 if maxidx < data[index(maxidx)] else index(maxidx) mindata = (data[minidx] + data[max((minidx-1,0))])/2 maxdata = (data[maxidx] + data[min((maxidx+1,np.argmax(data)))])/2 #push regions. regions may not overlap! if region.zValue() > otherregion.zValue() : #choose the active/just dragged region othermin, othermax = otherregion.getRegion() if othermin <= mindata <= othermax: otherregion.setBounds((othermin, mindata)) #otherregion.setRegion([othermin, mindata]) elif othermin <= maxdata <= othermax: otherregion.setBounds((maxdata, othermax)) if snapregion: region.blockSignals(True) otherregion.setBounds((min(data), max(data))) region.setRegion([mindata, maxdata]) # snap region to data points region.blockSignals(False) #self.parent.OnSelectionChanged() return minidx, maxidx, mindata, maxdata def UpdateSelection(self,region): otherregion = {self.region_i : self.region_i0, self.region_i0: self.region_i} otherregion = otherregion[region] if region == self.region_i0: mini, maxi, mindi, maxdi = self.getDataClosestToRegion(otherregion,self.plotitem) mini0, maxi0, mindi0, maxdi0 = self.getDataClosestToRegion(region,self.plotitem) else: mini, maxi, mindi, maxdi = self.getDataClosestToRegion(region,self.plotitem) mini0, maxi0, mindi0, maxdi0 = self.getDataClosestToRegion(otherregion,self.plotitem) #self.region_i0.blockSignals(True) #self.region_i.blockSignals(True) #self.region_i0.setBounds((min(self.plotitem.getData()[0]),mindi)) #self.region_i.setBounds((maxdi0,max(self.plotitem.getData()[0]))) #self.region_i0.blockSignals(False) #self.region_i.blockSignals(False) qlist = self.parent.MapSelectWidget1 for row in range(qlist.count()): if row in range(mini0, maxi0+1): self.stack.shifts[row][1]= -1 qlist.item(row).setBackground(QtGui.QColor('#beaed4')) elif row in range(mini, maxi+1): self.stack.shifts[row][1]= 1 qlist.item(row).setBackground(QtGui.QColor('#7fc97f')) else: self.stack.shifts[row][1]= 0 qlist.item(row).setBackground(QtGui.QColor(0, 0, 0, 0)) tristate_list = [item for row in self.stack.shifts for item in row][1::3] self.ColorizeScatterDots(tristate_list) self.parent.OnSelectionChanged() # ---------------------------------------------------------------------- def closeEvent(self, event): self.close() self.parent.pbSelfromSpec.setEnabled(True) # ---------------------------------------------------------------------- class SpectralROI(QtWidgets.QDialog): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.stack = stack self.com = common self.resize(630, 700) self.setWindowTitle('Spectral Regions of Interest') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.stack = stack self.com = common self.imin = 0 self.imax = 0 self.i0min = 0 self.i0max = 0 self.iselected = 0 self.i0selected = 0 self.odtotal = self.stack.od3d.sum(axis=0) self.odtotal = self.odtotal.sum(axis=0)/(self.stack.n_rows*self.stack.n_cols) self.image_i0 = np.zeros((self.stack.n_cols, self.stack.n_rows)) self.image_i = np.zeros((self.stack.n_cols, self.stack.n_rows)) self.odthickmap = np.zeros((self.stack.n_cols, self.stack.n_rows)) vbox = QtWidgets.QVBoxLayout() text = QtWidgets.QLabel(self) text.setText('First select I0 region below the edge, then select I region above the edge:') vbox.addWidget(text) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.specfig = Figure((6.0, 4.2)) self.SpectrumPanel = FigureCanvas(self.specfig) self.SpectrumPanel.setParent(self) self.SpectrumPanel.mpl_connect('button_press_event', self.OnSelection1) self.SpectrumPanel.mpl_connect('button_release_event', self.OnSelection2) fbox.addWidget(self.SpectrumPanel) frame.setLayout(fbox) vbox.addWidget(frame) hbox2 = QtWidgets.QHBoxLayout() sizer2 =QtWidgets.QGroupBox('Selected Spectral Regions') sizer2.setMinimumWidth(350) vbox2 = QtWidgets.QVBoxLayout() text = QtWidgets.QLabel(self) text.setText('I Selection (red): ') self.textctrl1 = QtWidgets.QLabel(self) self.textctrl1.setText('[ ]' ) vbox2.addWidget(text) vbox2.addWidget(self.textctrl1) text = QtWidgets.QLabel(self) text.setText('I0 Selection (green): ') self.textctrl2 = QtWidgets.QLabel(self) self.textctrl2.setText('[ ]' ) vbox2.addWidget(text) vbox2.addWidget(self.textctrl2) sizer2.setLayout(vbox2) text = QtWidgets.QLabel(self) text.setText('Optical density map') vbox.addWidget(text) frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.odmfig = Figure((2.4,2.4)) self.ODMImagePanel = FigureCanvas(self.odmfig) self.ODMImagePanel.setParent(self) fbox.addWidget(self.ODMImagePanel, stretch=0) frame.setLayout(fbox) hbox2.addWidget(frame, stretch=0) hbox2.addStretch(1) hbox2.addWidget(sizer2) vbox.addLayout(hbox2) hbox = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save') button_save.clicked.connect(self.OnSave) hbox.addWidget(button_save) button_cancel = QtWidgets.QPushButton('Dismiss') button_cancel.clicked.connect(self.close) hbox.addWidget(button_cancel) vbox.addLayout(hbox) self.setLayout(vbox) self.draw_spectrum() #---------------------------------------------------------------------- def draw_spectrum(self): fig = self.specfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) self.axes = fig.gca() specplot = self.axes.plot(self.stack.ev,self.odtotal) if self.i0selected == 1: self.axes.axvspan(self.stack.ev[self.i0min], self.stack.ev[self.i0max], facecolor='g', alpha=0.5) if self.iselected == 1: self.axes.axvspan(self.stack.ev[self.imin], self.stack.ev[self.imax], facecolor='r', alpha=0.5) self.axes.set_xlabel('Photon Energy [eV]') self.axes.set_ylabel('Optical Density') self.SpectrumPanel.draw() #---------------------------------------------------------------------- def draw_image(self): fig = self.odmfig fig.clf() fig.add_axes((0.02,0.02,0.96,0.96)) axes = fig.gca() divider = make_axes_locatable(axes) axcb = divider.new_horizontal(size="3%", pad=0.03) fig.add_axes(axcb) axes.set_position([0.03,0.03,0.8,0.94]) im = axes.imshow(np.rot90(self.odthickmap), cmap=matplotlib.colormaps["gray"]) cbar = axes.figure.colorbar(im, orientation='vertical',cax=axcb) #Show Scale Bar startx = int(self.stack.n_rows*0.05) starty = self.stack.n_cols-int(self.stack.n_cols*0.05)-self.stack.scale_bar_pixels_y um_string = ' $\mathrm{\mu m}$' microns = '$'+self.stack.scale_bar_string+' $'+um_string axes.text(self.stack.scale_bar_pixels_x+startx+1,starty+1, microns, horizontalalignment='left', verticalalignment='center', color = 'white', fontsize=14) #Matplotlib has flipped scales so I'm using rows instead of cols! p = matplotlib.patches.Rectangle((startx,starty), self.stack.scale_bar_pixels_x, self.stack.scale_bar_pixels_y, color = 'white', fill = True) axes.add_patch(p) axes.axis("off") self.ODMImagePanel.draw() #---------------------------------------------------------------------- def OnSelection1(self, evt): x1 = evt.xdata self.button_pressed = True self.patch = None self.conn = self.SpectrumPanel.mpl_connect('motion_notify_event', self.OnSelectionMotion) if x1 == None: return self.x1 = x1 #---------------------------------------------------------------------- def OnSelection2(self, evt): x2 = evt.xdata self.button_pressed = False self.SpectrumPanel.mpl_disconnect(self.conn) if x2 == None: return x1 = self.x1 if (self.i0selected == 1) and (self.iselected ==1): self.i0selected = 0 self.iselected = 0 if self.i0selected == 0: self.i0min = np.abs(self.stack.ev - x1).argmin() self.i0max = np.abs(self.stack.ev - x2).argmin() self.image_i0 = np.sum(self.stack.absdata[:, :, self.i0min:self.i0max+1], axis=2)/(self.i0max+1-self.i0min) self.textctrl1.setText('Selection: [ '+str(self.stack.ev[self.i0min]) + ' eV, '+ str(self.stack.ev[self.i0max])+' eV ]' ) self.i0selected = 1 elif self.iselected == 0: self.imin = np.abs(self.stack.ev - x1).argmin() self.imax = np.abs(self.stack.ev - x2).argmin() self.image_i = np.sum(self.stack.absdata[:, :, self.imin:self.imax+1], axis=2)/(self.imax+1-self.imin) self.textctrl2.setText('Selection: [ '+str(self.stack.ev[self.imin]) + ' eV, '+ str(self.stack.ev[self.imax])+' eV ]' ) self.iselected = 1 if (self.i0selected == 1) and (self.iselected ==1): nonzeroind = self.image_i0.nonzero() self.odthickmap = np.zeros((self.stack.n_cols, self.stack.n_rows)) self.odthickmap[nonzeroind] = - np.log(self.image_i[nonzeroind]/self.image_i0[nonzeroind]) self.draw_image() self.draw_spectrum() #---------------------------------------------------------------------- def OnSelectionMotion(self, event): x2 = event.xdata if x2 == None: return x1 = self.x1 fig = self.specfig axes = fig.gca() if self.patch != None: self.patch.remove() self.patch = self.axes.axvspan(x1, x2, facecolor='w', alpha=0.5) self.SpectrumPanel.draw() #---------------------------------------------------------------------- def OnSave(self, evt): #Save images wildcard = "Portable Network Graphics (*.png);;Adobe PDF Files (*.pdf);;TIFF File (*.tif);;" fileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save OD Map', '', wildcard) fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext != 'png' and ext != 'pdf' and ext != 'tif': error_message = ( 'Only the PNG and PDF image formats are supported.\n' 'A file extension of `png\' or `pdf\' must be used.') QtWidgets.QMessageBox.warning(self, 'Error', 'Error - Could not save file.') return if ext == 'tif': from PIL import Image img1 = Image.fromarray(self.odthickmap) img1.save(fileName) else: try: matplotlib.rcParams['pdf.fonttype'] = 42 fig = self.odmfig fig.savefig(fileName) except IOError as e: if e.strerror: err = e.strerror else: err = e QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save file: %s' % err) #----------------------------------------------------------------------- class DoseCalculation(QtWidgets.QDialog): def __init__(self, parent, stack, ROIspectrum): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(300, 170) self.setWindowTitle('Dose Calculation') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.stack = stack self.ROIspectrum = ROIspectrum vboxtop = QtWidgets.QVBoxLayout() gridtop = QtWidgets.QGridLayout() #fontb = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) #fontb.SetWeight(wx.BOLD) st1 = QtWidgets.QLabel(self) st1.setText('Detector efficiency [%]:') #st1.SetFont(fontb) st2 = QtWidgets.QLabel(self) st2.setText('I region composition:') #st2.SetFont(fontb) # st3 = QtWidgets.QLabel(self,'Xray absorption length:') # st3.SetFont(fontb) st4 = QtWidgets.QLabel(self) st4.setText('Dose [Gray]:') #st4.SetFont(fontb) self.tc_1 = QtWidgets.QLineEdit(self) self.tc_1.setText('30') self.tc_2 = QtWidgets.QLineEdit(self) # self.tc_3 = wx.TextCtrl(panel1, -1, size=((200,-1)), style=wx.TE_RICH|wx.VSCROLL|wx.TE_READONLY, # value=' ') self.tc_4 = QtWidgets.QLabel(self) gridtop.addWidget(st1, 0,0) gridtop.addWidget( self.tc_1, 0,1) gridtop.addWidget(st2, 1,0) gridtop.addWidget( self.tc_2, 1,1) # gridtop.addWidget(st3, 0) # gridtop.addWidget( self.tc_3, 0) gridtop.addWidget(st4, 2,0) gridtop.addWidget( self.tc_4, 2,1) button_calcdose = QtWidgets.QPushButton('Calculate Dose') button_calcdose.clicked.connect(self.OnCalcDose) button_cancel = QtWidgets.QPushButton('Dismiss') button_cancel.clicked.connect(self.close) vboxtop.addLayout(gridtop) vboxtop.addWidget(button_calcdose) vboxtop.addWidget(button_cancel) self.setLayout(vboxtop) #---------------------------------------------------------------------- def CalcDose(self): try: detector_eff = 0.01*float(self.tc_1.text()) except: QtWidgets.QMessageBox.warning(self, 'Error', 'Please enter numeric number for detector efficiency.') print('Please enter numeric number for detector efficiency.') return i_composition = str(self.tc_2.text()) dose = 0. Chenke = henke.henke() #Check if composition array is recognizable try: z_array, atwt = Chenke.compound(i_composition,1.0) except: QtWidgets.QMessageBox.warning(self, 'Error', "Please enter new compound.") return try: dose = Chenke.dose_calc(self.stack, i_composition, self.ROIspectrum, self.stack.i0data, detector_eff) except: QtWidgets.QMessageBox.warning(self, 'Error', "Could not calculate dose. Please enter new compound.") return self.tc_4.setText(str(dose)) return #---------------------------------------------------------------------- def OnCalcDose(self, evt): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.CalcDose() QtWidgets.QApplication.restoreOverrideCursor() #---------------------------------------------------------------------- class DarkSignal(QtWidgets.QDialog): def __init__(self, parent, common, stack): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.stack = stack self.com = common self.resize(300, 170) self.setWindowTitle('Dark Signal Correction') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) vboxtop = QtWidgets.QVBoxLayout() gridtop = QtWidgets.QGridLayout() #fontb = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) #fontb.SetWeight(wx.BOLD) st1 = QtWidgets.QLabel(self) st1.setText('Dark Signal Value:') self.ntc_ds = QtWidgets.QLineEdit(self) self.ntc_ds.setFixedWidth(150) self.ntc_ds.setValidator(QtGui.QDoubleValidator(-99999, 99999, 2, self)) self.ntc_ds.setAlignment(QtCore.Qt.AlignRight) self.ntc_ds.setText(str(0.0)) gridtop.addWidget(st1, 0,0) gridtop.addWidget(self.ntc_ds, 0,1) button_dscalc = QtWidgets.QPushButton('Subtract Dark Signal') button_dscalc.clicked.connect(self.OnDSCalc) button_cancel = QtWidgets.QPushButton('Dismiss') button_cancel.clicked.connect(self.close) vboxtop.addLayout(gridtop) vboxtop.addWidget(button_dscalc) vboxtop.addWidget(button_cancel) self.setLayout(vboxtop) #---------------------------------------------------------------------- def OnDSCalc(self): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) darksig = 0.0 try: value = self.ntc_ds.text() darksig = float(value) except: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'Please enter numeric number for dark signal.') return self.stack.absdata = self.stack.absdata - darksig if self.com.i0_loaded == 1: self.stack.calculate_optical_density() self.stack.fill_h5_struct_from_stk() if self.com.i0_loaded == 1: self.stack.fill_h5_struct_normalization() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page0.absimgfig.loadNewImage() self.parent.page1.specfig.ClearandReload() QtWidgets.QApplication.restoreOverrideCursor() self.close() return #---------------------------------------------------------------------- class PlotFrame(QtWidgets.QDialog): def __init__(self, parent, datax, datay, title = "I0 data"): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.title = title self.datax = datax self.datay = datay self.resize(630, 500) self.setWindowTitle(title) pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) vbox = QtWidgets.QVBoxLayout() frame = QtWidgets.QFrame() frame.setFrameStyle(QtWidgets.QFrame.StyledPanel|QtWidgets.QFrame.Sunken) fbox = QtWidgets.QHBoxLayout() self.plotfig = Figure((6.0, 4.2)) self.PlotPanel = FigureCanvas(self.plotfig) self.PlotPanel.setParent(self) fbox.addWidget(self.PlotPanel) frame.setLayout(fbox) vbox.addWidget(frame) hbox = QtWidgets.QHBoxLayout() button_save = QtWidgets.QPushButton('Save Spectrum') button_save.clicked.connect(self.OnSave) hbox.addWidget(button_save) button_close = QtWidgets.QPushButton('Close') button_close.clicked.connect(self.close) hbox.addWidget(button_close) vbox.addLayout(hbox) self.setLayout(vbox) self.draw_plot(datax,datay) #---------------------------------------------------------------------- def draw_plot(self, datax, datay): fig = self.plotfig fig.clf() fig.add_axes((0.15,0.15,0.75,0.75)) self.axes = fig.gca() plot = self.axes.plot(datax,datay) self.axes.set_xlabel('Photon Energy [eV]') self.axes.set_ylabel('I0 Flux') self.PlotPanel.draw() #---------------------------------------------------------------------- def OnSave(self, event): try: wildcard = "CSV files (*.csv)" filepath, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save plot as .csv (.csv)', '', wildcard) filepath = str(filepath) if filepath == '': return QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.Save(filepath) QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', "Could not save .csv file.") self.close() return #---------------------------------------------------------------------- def Save(self, filename): f = open(filename, 'w') print('********************* X-ray Absorption Data ********************', file=f) print('*', file=f) print('* Formula: ', file=f) print('* Common name: ', self.title, file=f) print('* Edge: ', file=f) print('* Acquisition mode: ', file=f) print('* Source and purity: ', file=f) print('* Comments: ', file=f) print('* Delta eV: ', file=f) print('* Min eV: ', file=f) print('* Max eV: ', file=f) print('* Y axis: ', file=f) print('* Contact person: ', file=f) print('* Write date: ', file=f) print('* Journal: ', file=f) print('* Authors: ', file=f) print('* Title: ', file=f) print('* Volume: ', file=f) print('* Issue number: ', file=f) print('* Year: ', file=f) print('* Pages: ', file=f) print('* Booktitle: ', file=f) print('* Editors: ', file=f) print('* Publisher: ', file=f) print('* Address: ', file=f) print('*--------------------------------------------------------------', file=f) dim = self.datax.shape n=dim[0] for ie in range(n): print('{0:06.2f}, {1:06f}'.format(self.datax[ie], self.datay[ie]), file=f) f.close() #---------------------------------------------------------------------- class ColorTableFrame(QtWidgets.QDialog): def __init__(self, parent): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.resize(200, 430) self.setWindowTitle('Pick Color Table') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.colors= ["gray","jet","autumn","bone", "cool","copper", "flag","hot","hsv","pink", "prism","spring","summer","winter", "spectral"] vboxtop = QtWidgets.QVBoxLayout() sizer1 = QtWidgets.QGroupBox('Color Tables') vbox = QtWidgets.QVBoxLayout() self.rb_grey = QtWidgets.QRadioButton(self.colors[0], self) self.rb_jet = QtWidgets.QRadioButton(self.colors[1], self) self.rb_autumn = QtWidgets.QRadioButton(self.colors[2], self) self.rb_bone = QtWidgets.QRadioButton(self.colors[3], self) self.rb_cool = QtWidgets.QRadioButton(self.colors[4], self) self.rb_copper = QtWidgets.QRadioButton(self.colors[5], self) self.rb_flag = QtWidgets.QRadioButton(self.colors[6], self) self.rb_hot = QtWidgets.QRadioButton(self.colors[7], self) self.rb_hsv = QtWidgets.QRadioButton(self.colors[8], self) self.rb_pink = QtWidgets.QRadioButton(self.colors[9], self) self.rb_prism = QtWidgets.QRadioButton(self.colors[10], self) self.rb_spring = QtWidgets.QRadioButton(self.colors[11], self) self.rb_summer = QtWidgets.QRadioButton(self.colors[12], self) self.rb_winter = QtWidgets.QRadioButton(self.colors[13], self) self.rb_spectral = QtWidgets.QRadioButton(self.colors[14], self) self.radios = [] self.radios.append(self.rb_grey) self.radios.append(self.rb_jet) self.radios.append(self.rb_autumn) self.radios.append(self.rb_bone) self.radios.append(self.rb_cool) self.radios.append(self.rb_copper) self.radios.append(self.rb_flag) self.radios.append(self.rb_hot) self.radios.append(self.rb_hsv) self.radios.append(self.rb_pink) self.radios.append(self.rb_prism) self.radios.append(self.rb_spring) self.radios.append(self.rb_summer) self.radios.append(self.rb_winter) self.radios.append(self.rb_spectral) self.ct_dict = dict([(self.colors[x], self.radios[x]) for x in range(len(self.colors))]) self.rb_grey.clicked.connect(self.OnColorTable) self.rb_jet.clicked.connect(self.OnColorTable) self.rb_autumn.clicked.connect(self.OnColorTable) self.rb_bone.clicked.connect(self.OnColorTable) self.rb_cool.clicked.connect(self.OnColorTable) self.rb_copper.clicked.connect(self.OnColorTable) self.rb_flag.clicked.connect(self.OnColorTable) self.rb_hot.clicked.connect(self.OnColorTable) self.rb_hsv.clicked.connect(self.OnColorTable) self.rb_pink.clicked.connect(self.OnColorTable) self.rb_prism.clicked.connect(self.OnColorTable) self.rb_spring.clicked.connect(self.OnColorTable) self.rb_summer.clicked.connect(self.OnColorTable) self.rb_winter.clicked.connect(self.OnColorTable) self.rb_spectral.clicked.connect(self.OnColorTable) vbox.addWidget(self.rb_grey) vbox.addWidget(self.rb_jet) vbox.addWidget(self.rb_autumn) vbox.addWidget(self.rb_bone) vbox.addWidget(self.rb_cool) vbox.addWidget(self.rb_copper) vbox.addWidget(self.rb_flag) vbox.addWidget(self.rb_hot) vbox.addWidget(self.rb_hsv) vbox.addWidget(self.rb_pink) vbox.addWidget(self.rb_prism) vbox.addWidget(self.rb_spring) vbox.addWidget(self.rb_summer) vbox.addWidget(self.rb_winter) vbox.addWidget(self.rb_spectral) sizer1.setLayout(vbox) vboxtop.addWidget(sizer1) button_close = QtWidgets.QPushButton('Close') button_close.clicked.connect(self.close) vboxtop.addWidget(button_close) self.setLayout(vboxtop) self.ct_dict[self.parent.page1.colortable].setChecked(True) #---------------------------------------------------------------------- def OnColorTable(self, event): for radioButton in self.findChildren(QtWidgets.QRadioButton): if radioButton.isChecked(): radioButtonText = radioButton.text() break self.parent.page1.colortable = str(radioButtonText) self.parent.page1.loadImage() """ ------------------------------------------------------------------------------------------------""" class PageLoadData(QtWidgets.QWidget): def __init__(self, common, data_struct, stack): super(PageLoadData, self).__init__() uic.loadUi(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pageloaddata.ui'), self) self.show() self.cmaps = [('Perceptually Uniform Sequential', [ 'viridis', 'plasma', 'inferno', 'magma']), ('Sequential', [ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), ('Sequential (2)', [ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper']), ('Diverging', [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), ('Qualitative', [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c']), ('Miscellaneous', [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] self.initUI(common, data_struct, stack) #---------------------------------------------------------------------- def initUI(self, common, data_struct, stack): #self.scale = 0.000001 self.data_struct = data_struct self.stk = stack self.com = common self.filename = " " self.showflux = True self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.slider_theta.valueChanged[int].connect(self.OnScrollTheta) self.iev = 0 self.itheta = 0 #self.pglayout = pg.GraphicsLayout(border=None) #self.canvas.setBackground("w") # canvas is a pg.GraphicsView widget #self.canvas.setCentralWidget(self.pglayout) self.absimgfig = ImgFig(self, self.canvas) self.button_multiload.clicked.connect( self.OnLoadMulti) self.button_multiload.setToolTip('Supported Formats .hdf .hdf5 .ncb .nxs .hdr .stk .tif .tiff .txrm') #self.button_multiload.setToolTip('Supported Formats .hdf .hdf5 .ncb .npy .nxs .hdr .stk .tif .tiff .txrm') self.button_4d.setToolTip('Supported Formats .hdf5 .ncb') self.button_4d.clicked.connect( self.OnLoad4D) self.button_sm.setToolTip('Supported Formats .sm, .tif, .xrm') self.button_sm.clicked.connect( self.OnBuildStack) self.pb_copy_img.clicked.connect(self.absimgfig.OnCopy) self.pb_copy_img.setEnabled(False) self.pb_copy_specimg.setVisible(False) self.MetricCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.ZeroOriginCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.toggled.connect(lambda: self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.setVisible(False) self.ScalebarCheckBox.toggled.connect(lambda: self.absimgfig.OnUpdateScale(self.ScalebarCheckBox.isChecked())) self.CMCatBox.addItems([self.cmaps[0][0],self.cmaps[1][0],self.cmaps[2][0],self.cmaps[3][0],self.cmaps[4][0],self.cmaps[5][0]]) self.CMMapBox.addItems(self.cmaps[2][1]) self.CMCatBox.setCurrentIndex(2) self.CMMapBox.setCurrentIndex(3) self.CMCatBox.currentIndexChanged.connect(self.absimgfig.OnCatChanged) self.CMMapBox.currentIndexChanged.connect(lambda: self.absimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.StepSpin.valueChanged.connect(lambda: self.absimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.pb_rotate.clicked.connect(self.OnRotate) self.pb_mirror.clicked.connect(self.OnMirror) self.button_save.clicked.connect( self.OnSave) self.button_save.setEnabled(False) self.tc_file.setText('File name') self.tc_path.setText('D:/') self.slider_theta.setVisible(False) def keyPressEvent(self, e): if e.key() == 67 and (e.modifiers() & QtCore.Qt.ControlModifier): self.OnCopy() # ---------------------------------------------------------------------- def OnSave(self, event): savewin = SaveWin(self, self.com, self.stk) savewin.show() # ---------------------------------------------------------------------- def OnMirror(self): if self.com.stack_loaded == 1: if self.com.stack_4d == 1: self.stk.stack4D = np.flip(self.stk.stack4D, axis=0) self.stk.absdata = self.stk.stack4D[:, :, :, self.itheta].copy() else: self.stk.absdata = np.flip(self.stk.absdata, axis=0) if self.com.i0_loaded: if self.com.stack_4d: self.stk.od4d = np.flip(self.stk.od4d, axis=0) else: self.stk.od3d = np.flip(self.stk.od3d, axis=0) self.stk.od = self.stk.od3d.copy() self.stk.od = np.reshape(self.stk.od, (self.stk.n_rows * self.stk.n_cols, self.stk.n_ev), order='F') self.stk.fill_h5_struct_from_stk() if self.com.i0_loaded == 1: self.stk.fill_h5_struct_normalization() self.OnScrollEng(self.iev) # Update/Refresh widgets: #if showmaptab: # self.window().page9.Clear() # self.window().page9.loadData() self.window().page1.absimgfig.loadNewImageWithROI() self.window().page1.specfig.ClearandReload() return def OnRotate(self): if self.com.stack_loaded == 1: if self.com.stack_4d == 1: self.stk.stack4D = np.rot90(self.stk.stack4D, 3) self.stk.absdata = self.stk.stack4D[:, :, :, self.itheta].copy() else: self.stk.absdata = np.rot90(self.stk.absdata, 3) # Swap x/y constants: self.stk.n_cols, self.stk.n_rows = self.stk.n_rows, self.stk.n_cols self.stk.x_pxsize, self.stk.y_pxsize = self.stk.y_pxsize, self.stk.x_pxsize self.stk.x_start, self.stk.y_start = self.stk.y_start, self.stk.x_start self.stk.x_dist, self.stk.y_dist = self.stk.y_dist, self.stk.x_dist if self.com.i0_loaded: if self.com.stack_4d: self.stk.od4d = np.rot90(self.stk.od4d, 3) else: self.stk.od3d = np.rot90(self.stk.od3d, 3) self.stk.od = self.stk.od3d.copy() self.stk.od = np.reshape(self.stk.od, (self.stk.n_rows * self.stk.n_cols, self.stk.n_ev), order='F') self.stk.fill_h5_struct_from_stk() if self.com.i0_loaded == 1: self.stk.fill_h5_struct_normalization() # Update/Refresh widgets: self.OnScrollEng(self.iev) self.absimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked()) #if showmaptab: # self.window().page9.Clear() # self.window().page9.loadData() self.window().page1.ix = int(self.stk.n_cols / 2) self.window().page1.iy = int(self.stk.n_rows / 2) #self.window().page1.loadSpectrum(self.window().page1.ix, self.window().page1.iy) self.window().page1.absimgfig.loadNewImageWithROI() self.window().page1.specfig.ClearandReload() return #----------------------------------------------------------------------- def OnLoadMulti(self, event): self.window().LoadStack() #---------------------------------------------------------------------- def OnLoad4D(self, event): self.window().LoadStack4D() #---------------------------------------------------------------------- def OnBuildStack(self, event): self.window().BuildStack() #---------------------------------------------------------------------- def OnScrollEng(self, value): self.iev = value if self.com.stack_loaded == 1: self.slider_eng.setValue(value) image = self.stk.absdata[:, :, int(self.iev)] self.absimgfig.draw(image) # if self.com.stack_loaded == 1: # image = self.stk.absdata[:, :, int(self.iev)].copy() # if self.com.stack_4d == 1: # self.p1.setTitle("
Image at {0:5.2f} eV and {1:5.1f}°
".format(float(self.stk.ev[self.iev]), # float(self.stk.theta[self.itheta]))) # else: # self.p1.setTitle("
Image at energy {0:5.2f} eV
".format(float(self.stk.ev[self.iev]))) # self.ODmin = np.min(image) # self.ODmax = np.max(image) # self.i_item.setImage(image) # self.OnColormap(map=self.CMMapBox.currentText(),colors=self.StepSpin.value()) #----------------------------------------------------------------------- def OnScrollTheta(self, value): self.slider_theta.setValue(value) self.itheta = value self.stk.absdata = self.stk.stack4D[:,:,:,self.itheta].copy() image = self.stk.absdata[:, :, int(self.slider_eng.value())].copy() #self.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) #if self.com.stack_loaded == 1: # self.p1.setTitle("
Image at {0:5.2f} eV and {1:5.1f}°
".format(float(self.stk.ev[self.iev]), # float(self.stk.theta[self.itheta]))) # self.ODmin = np.min(image) # self.ODmax = np.max(image) # self.i_item.setImage(image) # self.OnColormap(map=self.CMMapBox.currentText(),colors=self.StepSpin.value()) self.absimgfig.draw(image) #self.window().page1.itheta = self.itheta #self.window().page1.slider_theta.setValue(self.itheta) #self.window().page2.itheta = self.itheta #self.window().page2.slider_theta.setValue(self.itheta) #---------------------------------------------------------------------- def ShowInfo(self, filename, filepath): self.tc_file.setText(filename) self.tc_path.setText(filepath) # ---------------------------------------------------------------------- class ShowODMap(QtWidgets.QWidget): qlistchanged = pyqtSignal([tuple]) def __init__(self, parent, common, data_struct, stack): super(ShowODMap, self).__init__() dir_path = os.path.dirname(os.path.realpath(__file__)) uic.loadUi(os.path.join(dir_path,'showodmap.ui'), self) self.show() self.cmaps = [('Perceptually Uniform Sequential', [ 'viridis', 'plasma', 'inferno', 'magma']), ('Sequential', [ 'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']), ('Sequential (2)', [ 'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper']), ('Diverging', [ 'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']), ('Qualitative', [ 'Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c']), ('Miscellaneous', [ 'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'hsv', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])] self.initUI(parent, common, data_struct, stack) # self.Clear() self.odimgfig.loadNewImage() self.loadData() #----------------------------------------------------------------------- def initUI(self, parent, common, data_struct, stack): self.scale = 0.000001 self.data_struct = data_struct self.stk = stack self.com = common self.parent = parent self.iev = 0 self.latest_row = -1 self.xoffset = 0 self.yoffset = 0 self.odimgfig = ImgFig(self, self.canvas) self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.unitlabel = self.odimgfig.bar.getAxis("right") self.pbExpData.clicked.connect(self.OnSaveData) self.pbExpImg.clicked.connect(self.OnSaveImage) self.pbCopy.clicked.connect(self.OnCopy) self.MetricCheckBox.toggled.connect(lambda: self.odimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.ZeroOriginCheckBox.toggled.connect(lambda: self.odimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.toggled.connect(lambda: self.odimgfig.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(),self.SquarePxCheckBox.isChecked())) self.SquarePxCheckBox.setVisible(False) self.ScalebarCheckBox.toggled.connect(lambda: self.odimgfig.OnUpdateScale(self.ScalebarCheckBox.isChecked())) #self.CropCheckBox.toggled.connect(lambda: self.OnCropCB(self.CropCheckBox.isChecked())) #self.cropflag = True #self.ShiftLabel.setText("x = %0.1f \ny = %0.1f" % (0, 0)) #self.ODHighSpinBox.valueChanged.connect(lambda: self.setODlimits(self.ODLowSpinBox.value(),self.ODHighSpinBox.value())) #self.ODLowSpinBox.valueChanged.connect(lambda: self.setODlimits(self.ODLowSpinBox.value(),self.ODHighSpinBox.value())) self.pbRSTOD.clicked.connect(lambda: self.ShowMap(self.prelst, self.postlst)) # self.pbClrShifts.clicked.connect(self.OnClrShifts) self.pbClrSel.clicked.connect(self.OnClrSelection) self.CMCatBox.addItems([self.cmaps[0][0],self.cmaps[1][0],self.cmaps[2][0],self.cmaps[3][0],self.cmaps[4][0],self.cmaps[5][0]]) self.CMMapBox.addItems(self.cmaps[2][1]) self.CMCatBox.setCurrentIndex(2) self.CMMapBox.setCurrentIndex(14) self.CMCatBox.currentIndexChanged.connect(self.odimgfig.OnCatChanged) self.CMMapBox.currentIndexChanged.connect(lambda: self.odimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.StepSpin.valueChanged.connect(lambda: self.odimgfig.OnColormapChange(map=self.CMMapBox.currentText(),num_colors=self.StepSpin.value())) self.filterSpinBox.valueChanged.connect(lambda: self.ShowMap(self.prelst, self.postlst)) self.filterSpinBox.setEnabled(False) self.rb_filterlee.setEnabled(False) self.rb_filteruniform.setEnabled(False) self.rb_filterlee.toggled.connect(lambda: self.ShowMap(self.prelst, self.postlst)) self.MapSelectWidget1.mousePressEvent = self.mouseEventOnQList self.MapSelectWidget1.mouseMoveEvent = self.mouseEventOnQList self.parent.page1.button_spectralROI.setEnabled(False) def OnScrollEng(self, value): self.iev = value if self.com.stack_loaded == 1: self.slider_eng.setValue(value) if self.com.i0_loaded == 0: # Show flux image image = self.stk.absdata[:, :, self.iev] # .copy() else: # Show OD image image = self.stk.od3d[:, :, self.iev] # .copy() self.odimgfig.draw(image) def mouseEventOnQList(self, e): if e.type() == QtCore.QEvent.MouseMove or e.type() == QtCore.QEvent.MouseButtonPress: qlist = self.MapSelectWidget1 pos = qlist.mapFromGlobal(QtGui.QCursor.pos()) row = qlist.indexAt(pos).row() #print(row,self.latest_row) if self.stk.shifts: params = [self.stk.shifts[row][0],self.stk.shifts[row][1],self.stk.shifts[row][2]] if row >= 0: if e.type() != QtCore.QEvent.MouseMove or row != self.latest_row: if e.buttons() == QtCore.Qt.RightButton: qlist.setCurrentRow(row) params[1] = 1 elif e.buttons() == QtCore.Qt.LeftButton: qlist.setCurrentRow(row) params[1] = -1 self.qlistchanged.emit((row,params)) self.latest_row = row return def qListChangeHandler(self,paramtup): qlist = self.MapSelectWidget1 row, params = paramtup if self.stk.shifts[row][1] == params[1]: #print("deselect it!") qlist.item(row).setBackground(QtGui.QColor(0, 0, 0, 0)) self.stk.shifts[row][1] = 0 elif self.stk.shifts[row][1] != params[1] and params[1] == 1: # select right #print("select right!") qlist.item(row).setBackground(QtGui.QColor('#7fc97f')) self.stk.shifts[row][1] = 1 elif self.stk.shifts[row][1] != params[1] and params[1] == -1: # select left #print("select left!") qlist.item(row).setBackground(QtGui.QColor('#beaed4')) self.stk.shifts[row][1] = -1 self.OnSelectionChanged() #print(row, params) def OnSelectionChanged(self): self.prelst = [index for index, value in enumerate([x[1] for x in self.stk.shifts]) if value == -1] self.postlst = [index for index, value in enumerate([x[1] for x in self.stk.shifts]) if value == 1] #print(self.prelst,self.postlst) if len(self.prelst) == 0 or len(self.postlst) == 0: if len(self.prelst + self.postlst) == 0: self.pbClrSel.setEnabled(False) else: self.pbClrSel.setEnabled(True) #self.odimgfig.loadData() self.odimgfig.OnColormapChange() self.OnScrollEng(self.MapSelectWidget1.currentRow()) # self.cm.clear() self.odimgfig.imageplot.titleLabel.setText("
Select at least one pre- and post-edge image!
",size='10pt') if self.com.i0_loaded == 0: self.unitlabel.setLabel(text="counts", units="") #self.ODHighSpinBox.setEnabled(False) #self.ODLowSpinBox.setEnabled(False) self.pbRSTOD.setEnabled(False) self.filterSpinBox.setEnabled(False) self.rb_filterlee.setEnabled(False) self.rb_filteruniform.setEnabled(False) self.pbExpData.setEnabled(False) self.pbExpImg.setEnabled(False) else: self.pbClrSel.setEnabled(True) #self.InfWarning = False self.unitlabel.setLabel(text="OD", units="") self.ShowMap(self.prelst,self.postlst) # self.OnScrollEng(self.MapSelectWidget1.currentRow()) #self.OnMetricScale(self.MetricCheckBox.isChecked(), self.ZeroOriginCheckBox.isChecked(), # self.SquarePxCheckBox.isChecked()) return # ---------------------------------------------------------------------- def OnSaveData(self,event): #Save Data wildcard = "TIFF Float32 File (*.tif);; TXT Image (*.txt);;" fileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save OD Map', '', wildcard) fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext == '': ext = _filter.split()[0].lower()[0:3] fileName = fileName+'.'+ext print(ext) if ext != 'tif' and ext != 'txt': error_message = ( 'Only the TIF and TXT data formats are supported.\n' 'A file extension of `tif\' or `txt\' must be used.') QtWidgets.QMessageBox.warning(self, 'Error', 'Error - Could not save file.') return if ext == 'tif': from PIL import Image img1 = Image.fromarray(np.rot90(self.OD)) img1.save(fileName) if ext == 'txt': np.savetxt(fileName, np.rot90(self.OD), delimiter='\t', newline='\n',fmt='%.5f') def OnCopy(self): self.exp = pg.exporters.ImageExporter(self.odimgfig.imageplot) self.exp.export(copy=True) return def OnSaveImage(self, event): wildcard = "TIFF (*.tif);;PNG (*.png);;JPG (*.jpg);;SVG (*.svg);;" fileName, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save OD Map', '', wildcard) fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext == '': ext = _filter.split()[0].lower()[0:3] fileName = fileName+'.'+ext if ext == 'svg': exp = pg.exporters.SVGExporter(self.odimgfig.imageplot) else: exp = pg.exporters.ImageExporter(self.odimgfig.imageplot) if ext in ['tif','png','jpg','svg']: exp.export(fileName) def OnCatChanged(self): self.CMMapBox.blockSignals(True) self.CMMapBox.clear() self.CMMapBox.blockSignals(False) self.CMMapBox.addItems(self.cmaps[self.CMCatBox.currentIndex()][1]) def setODlimits(self,low, high): #self.ODHighSpinBox.setMaximum(self.ODmax) #self.ODHighSpinBox.setMinimum(low) #self.ODLowSpinBox.setMaximum(high) #self.ODLowSpinBox.setMinimum(self.ODmin) OD = np.clip(self.OD,low,high) #self.OnColormap(map=self.CMMapBox.currentText(), colors=self.StepSpin.value()) #self.setODbar(low, high) self.odimgfig.draw(OD,False,True) #self.m_item.setImage(OD) # def setCrosshair(self): # if hasattr(self, 'vLine'): # self.p2.removeItem(self.vLine) # self.p2.removeItem(self.vLine) # self.canvas.removeItem(self.ODlabel) # # self.vLine = pg.InfiniteLine(angle=90, movable=False, pen="0000") # self.hLine = pg.InfiniteLine(angle=0, movable=False,pen="0000") # self.ODlabel = pg.LabelItem(justify="left") # self.canvas.addItem(self.ODlabel) # self.p2.addItem(self.vLine, ignoreBounds=True) # self.p2.addItem(self.hLine, ignoreBounds=True) # self.proxy = pg.SignalProxy(self.p2.scene().sigMouseMoved, rateLimit=30, slot=self.OnMouseHover) # def OnMouseHover(self,evt): # pos = evt[0] # #print(pos) # if hasattr(self, 'OD'): # if self.p2.getViewBox().itemBoundingRect(self.m_item).contains(self.p2.getViewBox().mapSceneToView(pos)): # self.vLine.setPen("r") # self.hLine.setPen("r") # self.vLine.setZValue(1000) # self.hLine.setZValue(1000) # mousePoint = self.p2.getViewBox().mapSceneToView(pos) # if self.MetricCheckBox.isChecked(): # x_min = (self.p2.getViewBox().itemBoundingRect(self.m_item).x() / (self.scale * self.stk.x_pxsize)) # minimum x value in px # y_min = (self.p2.getViewBox().itemBoundingRect(self.m_item).y() / (self.scale * self.stk.y_pxsize)) # minimum y value in px # x_off = x_min - int(x_min) # y_off = y_min - int(y_min) # x_pos = mousePoint.x() / (self.scale * self.stk.x_pxsize) +1 #x mousepos in number of rows # y_pos = mousePoint.y() / (self.scale * self.stk.y_pxsize) +1 #y mousepos in number of rows # # if mousePoint.x() < 0: # xi = int(x_pos - x_off-1) # else: # xi = int(x_pos - x_off) # if mousePoint.y() < 0: # yi = int(y_pos - y_off -1) # else: # yi = int(y_pos - y_off) # # x = (xi - 0.5 + x_off) * self.scale * self.stk.x_pxsize # y = (yi - 0.5 + y_off) * self.scale * self.stk.y_pxsize # pt = QtCore.QPointF(x,y) # self.ODlabel.setText(" x = %0.3f µm, y = %0.3f µm, OD = %0.2f" % (x/self.scale - 0.5 * self.stk.x_pxsize, y/self.scale - 0.5 * self.stk.y_pxsize, self.OD[xi-1-int(x_min),yi-1-int(y_min)])) # else: # x = int(mousePoint.x()) + 0.5 # y = int(mousePoint.y()) + 0.5 # pt = QtCore.QPointF(x,y) # self.ODlabel.setText(" x = %0.0f, y = %0.0f, OD = %0.2f" % (x, y,self.OD[int(x),int(y)])) # #print(x,y) # self.vLine.setPos(x) # self.hLine.setPos(y) # else: # self.ODlabel.setText("") # self.vLine.setZValue(-1000) # self.hLine.setZValue(-1000) # self.vLine.setPen("0000") # self.hLine.setPen("0000") # def setODbar(self,min=None,max=None): # self.cm.setRange(xRange=[0,1], yRange=[min,max], update=False, disableAutoRange=True,padding=0) # self.cmimg.setRect(QtCore.QRectF(0,min,1,max-min)) # ToDo: Restore fine-alignment by arrow keys # def keyPressEvent(self, e): # modifiers = QtWidgets.QApplication.keyboardModifiers() # if modifiers == QtCore.Qt.ShiftModifier: # noshift = False # elif modifiers == QtCore.Qt.KeypadModifier: # noshift = True # elif modifiers == (QtCore.Qt.KeypadModifier | # QtCore.Qt.ShiftModifier): # noshift = False # else: # noshift = True # if e.key() == 67 and (e.modifiers() & QtCore.Qt.ControlModifier): # self.OnCopy() # if e.key() == Qt.Key_Up or (e.key() == QtCore.Qt.Key_8): # if noshift: # self.pbUU.click() # else: # self.pbU.click() # elif e.key() == Qt.Key_Down or (e.key() == QtCore.Qt.Key_2): # if noshift: # self.pbDD.click() # else: # self.pbD.click() # elif e.key() == Qt.Key_Left or (e.key() == QtCore.Qt.Key_4): # if noshift: # self.pbLL.click() # else: # self.pbL.click() # elif e.key() == Qt.Key_Right or (e.key() == QtCore.Qt.Key_6): # if noshift: # self.pbRR.click() # else: # self.pbR.click() # elif e.key() == QtCore.Qt.Key_Home: # if noshift: # self.pbLLUU.click() # else: # self.pbLU.click() # elif e.key() == QtCore.Qt.Key_PageUp: # if noshift: # self.pbRRUU.click() # else: # self.pbRU.click() # elif e.key() == QtCore.Qt.Key_End: # if noshift: # self.pbLLDD.click() # else: # self.pbLD.click() # elif e.key() == QtCore.Qt.Key_PageDown: # if noshift: # self.pbRRDD.click() # else: # self.pbRD.click() # elif e.key() == QtCore.Qt.Key_Clear: # self.pbRST.click() # def Clear(self): # try: # self.slider_eng.valueChanged.disconnect() # self.qlistchanged.disconnect() # self.pbSelfromSpec.disconnect() # except: # pass # self.prelst = [] # self.postlst =[] # self.stk.shifts = [] # self.stk.absdata_shifted= [] # # self.p1.clear() # self.p2.clear() # self.cm.clear() # self.MapSelectWidget1.clear() def loadData(self): # Called when fresh data are loaded. self.stk.shifts = [] #self.slider_eng.setRange(0, self.stk.n_ev - 1) #self.ODHighSpinBox.setEnabled(False) #self.ODLowSpinBox.setEnabled(False) #self.pbRSTOD.setEnabled(False) self.filterSpinBox.setEnabled(False) self.pbExpData.setEnabled(False) self.pbExpImg.setEnabled(False) self.pbClrSel.setEnabled(False) self.stk.absdata_shifted = self.stk.absdata.copy() #self.p1.addItem(self.i_item) for i,e in enumerate(self.stk.ev): # Fill QList with energies self.stk.shifts.append([1,0,(0.0,0.0)]) #checked [0,1]; pre, post, undefined state for map [-1,1,0],(xshift [float],yshift [float]) item = QtWidgets.QListWidgetItem(str(int(i)).zfill(3)+" at " + format(e, '.2f') + " eV "+"+0.0"+" +0.0") self.MapSelectWidget1.addItem(item) #self.slider_eng.valueChanged[int].connect(self.OnScrollEng) self.qlistchanged.connect(self.qListChangeHandler) self.odimgfig.OnColormapChange() #self.OnScrollEng(0) # Plot first image & set Scrollbar #self.OnMetricScale(self.MetricCheckBox.isChecked(), True, False) self.pbSelfromSpec.setEnabled(True) self.pbSelfromSpec.clicked.connect(self.OnSelfromSpec) # def UpdateEntry(self,row): # self.MapSelectWidget1.item(row).setText(str(int(row)).zfill(3)+" at " + format(self.stk.ev[row], '.2f') + " eV "+format(self.stk.shifts[row][2][0], '+.1f')+" "+format(self.stk.shifts[row][2][1], '+.1f')) # #self.MapSelectWidget1.addItem(self.MapSelectWidget1.item(row)) # self.i_item.setImage(self.Shift(row)) # def ResetAllItems(self,widget): # for i in range(widget.count()): # widget.item(i).setForeground(QtGui.QColor(0, 0, 0, 128)) # def OnScrollEng(self, value): # self.slider_eng.blockSignals(True) # self.slider_eng.setValue(value) # self.slider_eng.blockSignals(False) # self.MapSelectWidget1.setCurrentRow(value) # self.ResetAllItems(self.MapSelectWidget1) # self.iev = value # self.p1.titleLabel.item.setTextWidth(self.p1.width() * 0.7) # self.p2.titleLabel.item.setTextWidth(self.p2.width() * 0.7) # #self.canvas.resizeEvent(None) # if self.com.stack_loaded == 1: # self.p1.titleLabel.setText("
Image at energy {0:5.2f} eV
".format(float(self.stk.ev[self.iev])),size='10pt') # self.i_item.setImage(self.Shift(int(self.iev))) # self.MapSelectWidget1.item(value).setForeground(QtGui.QColor(0, 0, 0, 255)) # def OnCropCB(self, value=True): # if self.com.stack_loaded == 1: # if value == True: # self.cropflag = True # else: # self.cropflag = False # self.ShowMap(self.prelst, self.postlst) def OnClrSelection(self): for row in self.prelst + self.postlst: self.MapSelectWidget1.item(row).setBackground(QtGui.QColor(0, 0, 0, 0)) self.stk.shifts[row][1] = 0 self.OnSelectionChanged() def OnSelfromSpec(self): #print(len(self.stk.shifts)) #ToDo: Allow multiple map windows. Task: prevent that stk.shifts is appended each time the window is opened. spectralimgmap = SpectralImageMap(self, self.com, self.stk) spectralimgmap.show() # def OnClrShifts(self): # for row in [index for index, value in enumerate([x[2] for x in self.stk.shifts]) if value != (0.0,0.0)]: # self.stk.shifts[row].pop(2) # remove tuple # self.stk.shifts[row].insert(2, (0.0, 0.0)) # self.stk.absdata_shifted[:, :, row] = self.stk.absdata[:, :, row] # self.MapSelectWidget1.item(row).setText( # str(int(row)).zfill(3) + " at " + format(self.stk.ev[row], '.2f') + " eV " + format( # self.stk.shifts[row][2][0], '+.1f') + " " + format(self.stk.shifts[row][2][1], '+.1f')) # if hasattr(self, 'prelst') and hasattr(self, 'postlst'): # if len(self.prelst) != 0 and len(self.postlst) != 0: # self.ShowMap(self.prelst,self.postlst) # if self.stk.shifts: # self.UpdateEntry(self.MapSelectWidget1.currentRow()) # # def setShifts(self,shift_x, shift_y): # #print("setshifts called") # # if hasattr(self, "OD"): # if self.stk.shifts: # row = self.MapSelectWidget1.currentRow() # xoffset, yoffset = self.stk.shifts[row][2] # current offset stored as tuple in table stk.shifts # #print(self.stk.shifts[self.MapSelectWidget1.currentRow()]) # #yoffset = self.stk.shifts[self.MapSelectWidget1.currentRow()][2][1] # if shift_x == 0 and shift_y == 0: # if reset button pressed # if xoffset != 0 or yoffset != 0: # self.stk.shifts[row].pop(2) # remove tuple # self.stk.shifts[row].insert(2,(0.0,0.0)) # else: # print("Reset has no effect") # return # else: # self.stk.shifts[row].pop(2) # xoffset = round(xoffset + shift_x,1) # yoffset = round(yoffset + shift_y,1) # self.stk.shifts[row].insert(2,(xoffset, yoffset)) # #current_img = self.stk.absdata[:, :, self.MapSelectWidget1.currentRow()] # #print(type(self.stk.absdata), self.stk.shifts[self.MapSelectWidget1.currentRow()]) # #self.Shift(row) # self.UpdateEntry(row) # if hasattr(self, 'prelst') and hasattr(self, 'postlst'): # if len(self.prelst) != 0 and len(self.postlst) != 0: # self.ShowMap(self.prelst,self.postlst) # def Shift(self,row): # #current_img = self.stk.absdata_shifted[:, :, row] # original_img = self.stk.absdata[:, :, row] # xoffset, yoffset = self.stk.shifts[row][2] # x and y offsets of current image # if xoffset == 0 and yoffset == 0: # self.stk.absdata_shifted[:, :, row] = original_img # replace with original if no shift is applied # #self.ShiftLabel.setText("x = %0.1f \ny = %0.1f" % (self.xoffset, self.yoffset)) # #return original_img # else: # shifted = ndimage.fourier_shift(np.fft.fft2(original_img), [float(xoffset), float(yoffset)]) # shifted = np.fft.ifft2(shifted) # shifted_real = shifted.real # self.stk.absdata_shifted[:, :, row] = shifted_real # return self.stk.absdata_shifted[:,:, row] #self.ShiftLabel.setText("x = %0.1f \ny = %0.1f" % (xoffset, yoffset)) def CalcODMap(self,im_idx1,im_idx2): if len(im_idx1) == 1 and len(im_idx2) == 1: im1 = self.stk.absdata_shifted[:, :, int(im_idx1[0])] im2 = self.stk.absdata_shifted[:, :, int(im_idx2[0])] else: im1 = np.mean([self.stk.absdata_shifted[:, :, i] for i in im_idx1], axis=0) im2 = np.mean([self.stk.absdata_shifted[:, :, i] for i in im_idx2], axis=0) OD = np.log(im1/im2) # if self.cropflag: # shiftlst = [self.stk.shifts[i][2] for i in im_idx1 + im_idx2] # #print(shiftlst) # # print(max(shiftlst, key=itemgetter(0))[0]) # possible alternative to lambda function # #Calculate crops # l = int(np.floor(min(shiftlst, key=lambda item: item[0])[0])) # cl = l if l < 0 else None # r = int(np.ceil(max(shiftlst, key=lambda item: item[0])[0])) # t = int(np.ceil(max(shiftlst, key=lambda item: item[1])[1])) # b = int(np.floor(min(shiftlst, key=lambda item: item[1])[1])) # cb = b if b < 0 else None # # Crop map # OD = OD[r:cl,t:cb] # #print(r,cl,t,cb) inf_idx = np.where(np.isinf(OD)) nan_idx = np.where(np.isnan(OD)) if np.any(inf_idx) or np.any(nan_idx): #if not self.InfWarning: # self.InfWarning = True # QtWidgets.QMessageBox.warning(self, 'Warning!', "The OD map contained infinite or nan values. Please note that they have been zeroed.") OD[inf_idx] = 0 #infinite values get replaced by zero. This is an ugly work around, but with nans the image is not displayed correctly. OD[nan_idx] = 0 return OD # def calcBinSize(self,i,N): # return int(round(256*(i+1)/N) - round(256*i/N)) # def OnColormap(self,map="afmhot", colors=256): # if hasattr(self, "OD"): # colormap = cm.get_cmap(map, colors) # colormap = colormap(np.arange(colors)) # cm_lst = [[colormap[idx][0], colormap[idx][1], colormap[idx][2], colormap[idx][3]] for idx in range(np.shape(colormap)[0])] #convert to r,g,b,a list # cm_lst = [item for sub in [[cm_lst[i]]*self.calcBinSize(i,colors) for i in range(colors)] for item in sub] #fills 256 bins as equal as possible with n colors # cm_array = np.array([np.asarray(cm_lst)]) #vertical colorbar # cm_lst.extend((cm_lst[-1],cm_lst[-1],cm_lst[-1])) # lut = np.asarray(cm_lst) # lut = (lut * 255).view(np.ndarray) #lut for OD map # self.cmimg.setImage(cm_array) # self.m_item.setLookupTable(lut) # if hasattr(self, "OD"): # self.setODbar(self.ODmin, self.ODmax) def ShowMap(self, preidx, postidx): # self.p2.clear() # self.p2.addItem(self.m_item) # self.cm.clear() # self.cm.addItem(self.cmimg) #self.setCrosshair() try: ## Optional sorting switched off for convenience. Allows maps to range to negative OD #selection = preidx + postidx # selection.sort() if len(preidx + postidx) == 2: self.odimgfig.imageplot.titleLabel.setText("
Binary map from energies " + str(round(self.stk.ev[preidx[0]], 2)) + " and " + str( round(self.stk.ev[postidx[0]], 2)) + " eV
", size='10pt') elif len(preidx + postidx) <= 6: self.odimgfig.imageplot.titleLabel.setText("
Map from energies " + str([round(self.stk.ev[e], 2) for e in preidx]).strip('[]') + " and " + str([round(self.stk.ev[e], 2) for e in postidx]).strip('[]') + " eV
", size='10pt') else: self.odimgfig.imageplot.titleLabel.setText("
Map from "+ str(len(preidx + postidx)) +" energies: " + str(round(self.stk.ev[preidx[0]], 2)) + ' ... ' + str(round(self.stk.ev[preidx[-1]], 2)) + " and " + str(round(self.stk.ev[postidx[0]], 2)) + ' ... ' + str(round(self.stk.ev[postidx[-1]], 2)) + " eV
", size='10pt') self.OD = self.CalcODMap(preidx, postidx) if self.filterSpinBox.value() > 1: if self.rb_filteruniform.isChecked(): self.OD = ndimage.filters.uniform_filter(self.OD, size=self.filterSpinBox.value(), mode='nearest') elif self.rb_filterlee.isChecked(): self.OD = self.leeFilter(self.OD, size=self.filterSpinBox.value()) self.ODmin = np.min(self.OD) self.ODmax = np.max(self.OD) #self.ODHighSpinBox.setEnabled(True) #self.ODLowSpinBox.setEnabled(True) #self.ODHighSpinBox.blockSignals(True) #self.ODLowSpinBox.blockSignals(True) #self.ODHighSpinBox.setMaximum(self.ODmax) #self.ODHighSpinBox.setMinimum(self.ODmin) #self.ODLowSpinBox.setMaximum(self.ODmax) #self.ODLowSpinBox.setMinimum(self.ODmin) #self.ODLowSpinBox.setValue(self.ODmin) #self.ODHighSpinBox.setValue(self.ODmax) self.setODlimits(self.ODmin, self.ODmax) #self.ODHighSpinBox.blockSignals(False) #self.ODLowSpinBox.blockSignals(False) self.pbRSTOD.setEnabled(True) self.filterSpinBox.setEnabled(True) self.rb_filterlee.setEnabled(True) self.rb_filteruniform.setEnabled(True) self.pbExpData.setEnabled(True) self.pbExpImg.setEnabled(True) # #self.pglayout.layout.setColumnMaximumWidth(1, self.p1.width()) except IndexError: pass # self.p2.clear() # self.cm.clear() #self.p2.setTitle("Please select a second image!") #print("Select a second image!") def leeFilter(self, img, size): mean = ndimage.filters.uniform_filter(img, (size, size),mode='nearest') sqrmean = ndimage.filters.uniform_filter(img ** 2, (size, size),mode='nearest') var = sqrmean - mean ** 2 totvar = np.var(img) weights = var / (var + totvar) img = mean + weights * (img - mean) return img def closeEvent(self, event): #self.Clear() self.close() self.parent.page1.button_spectralROI.setEnabled(True) #----------------------------------------------------------------------- class StackListFrame(QtWidgets.QDialog): def __init__(self, parent, filepath, com, stack, data_struct): QtWidgets.QWidget.__init__(self, parent) self.parent = parent self.data_struct = data_struct self.stk = stack self.common = com self.resize(600, 500) self.setWindowTitle('Stack File List') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) self.filepath = str(filepath) self.have1st = 0 self.havelast = 0 self.file1st = ' ' self.filelast = ' ' self.filetype = '' vbox = QtWidgets.QVBoxLayout() self.textt = QtWidgets.QLabel(self) self.textt.setText('Select first stack file') vbox.addStretch(1) vbox.addWidget(self.textt) self.filelist = QtWidgets.QTableWidget() self.filelist.setMinimumHeight(450) self.filelist.setColumnCount(4) self.filelist.setHorizontalHeaderLabels(('File list', 'X', 'Y', 'eV')) self.filelist.setShowGrid(False) self.filelist.verticalHeader().setVisible(False) self.filelist.setColumnWidth(0,400) self.filelist.setColumnWidth(1,50) self.filelist.setColumnWidth(2,50) self.filelist.setColumnWidth(3,50) self.filelist.setRowCount(0) self.filelist.cellClicked.connect(self.OnFileList) self.filelist.horizontalHeader().setSortIndicatorShown(True) self.filelist.horizontalHeader().sectionClicked.connect(self.OnSort) vbox.addWidget(self.filelist) vbox.addStretch(1) self.tc_first = QtWidgets.QLabel(self) self.tc_first.setText('First stack file: ') self.tc_last = QtWidgets.QLabel(self) self.tc_last.setText('Last stack file: ') vbox.addWidget(self.tc_first) vbox.addWidget(self.tc_last) vbox.addStretch(1) hbox = QtWidgets.QHBoxLayout() self.button_accept = QtWidgets.QPushButton('Accept') self.button_accept.setEnabled(False) self.button_accept.clicked.connect( self.OnAccept) hbox.addWidget(self.button_accept) button_cancel = QtWidgets.QPushButton('Cancel') button_cancel.clicked.connect( self.close) hbox.addWidget(button_cancel) vbox.addLayout(hbox) vbox.addStretch(1) self.setLayout(vbox) self.ShowFileList() # ---------------------------------------------------------------------- def OnSort(self): self.sm_files = [self.filelist.item(i,0).text() for i in range(self.filelist.rowCount())] self.have1st = 0 self.havelast = 0 self.file1st = ' ' self.filelast = ' ' self.button_accept.setEnabled(False) self.tc_first.setText('First stack file: ') self.tc_last.setText('Last stack file: ') #---------------------------------------------------------------------- def OnFileList(self, row, column): if (self.have1st == 1) and (self.havelast==1): self.have1st = 0 self.havelast = 0 self.button_accept.setEnabled(False) self.tc_first.setText('First stack file: ') self.tc_last.setText('Last stack file: ') item = self.filelist.item(row, 0) fn = item.text() if self.have1st == 0: self.tc_first.setText('First stack file: ' + fn) self.textt.setText('Select last stack file') self.file1st = fn self.have1st = 1 elif self.havelast == 0: self.tc_last.setText('Last stack file: ' + fn) self.textt.setText('Select first stack file') self.filelast = fn self.button_accept.setEnabled(True) self.havelast = 1 #---------------------------------------------------------------------- def ShowFileList(self): filepath = str(self.filepath) self.sm_files = [x for x in os.listdir(filepath) if x.endswith('.sm')] if self.sm_files: self.filetype = 'sm' try: from netCDF4 import Dataset from file_plugins import file_sm_netcdf except: QtWidgets.QMessageBox.warning(self, 'Error', "Could not import netCDF4 library.") return count = 0 for i in range(len(self.sm_files)): filename = self.sm_files[i] thisfile = os.path.join(filepath, filename) filever, ncols, nrows, iev = file_sm_netcdf.read_sm_header(thisfile) if filever > 0: self.filelist.insertRow(count) self.filelist.setRowHeight(count,20) self.filelist.setItem(count, 0, QtWidgets.QTableWidgetItem(filename)) self.filelist.setItem(count, 1, QtWidgets.QTableWidgetItem(str(ncols))) self.filelist.setItem(count, 2, QtWidgets.QTableWidgetItem(str(nrows))) self.filelist.setItem(count, 3, QtWidgets.QTableWidgetItem('{0:5.2f}'.format(iev))) count += 1 else: continue self.xrm_files = [x for x in os.listdir(filepath) if x.endswith('.xrm')] if self.xrm_files: self.filetype = 'xrm' count = 0 for i in range(len(self.xrm_files)): filename = self.xrm_files[i] thisfile = os.path.join(filepath, filename) ncols, nrows, iev = file_xrm.read_xrm_fileinfo(thisfile) if ncols > 0: self.filelist.insertRow(count) self.filelist.setRowHeight(count,20) self.filelist.setItem(count, 0, QtWidgets.QTableWidgetItem(filename)) self.filelist.setItem(count, 1, QtWidgets.QTableWidgetItem(str(ncols))) self.filelist.setItem(count, 2, QtWidgets.QTableWidgetItem(str(nrows))) self.filelist.setItem(count, 3, QtWidgets.QTableWidgetItem('{0:5.2f}'.format(iev))) count += 1 self.sm_files = self.xrm_files self.bim_files = [x for x in os.listdir(filepath) if x.endswith('.bim')] if self.bim_files: self.filetype = 'bim' count = 0 for i in range(len(self.bim_files)): #print sm_files filename = self.bim_files[i] thisfile = os.path.join(filepath, filename) ncols, nrows, iev = file_bim.read_bim_info(thisfile) if ncols >0 : self.filelist.insertRow(count) self.filelist.setRowHeight(count,20) self.filelist.setItem(count, 0, QtWidgets.QTableWidgetItem(filename)) self.filelist.setItem(count, 1, QtWidgets.QTableWidgetItem(str(ncols))) self.filelist.setItem(count, 2, QtWidgets.QTableWidgetItem(str(nrows))) self.filelist.setItem(count, 3, QtWidgets.QTableWidgetItem('{0:5.2f}'.format(iev))) count += 1 self.sm_files = self.bim_files self.tif_files = [x for x in os.listdir(filepath) if x.endswith('.tif')] if self.tif_files: self.filetype = 'tif' for i in range(len(self.tif_files)): filename = self.tif_files[i] thisfile = os.path.join(filepath, filename) ncols, nrows = file_tif.read_tif_info(thisfile) #auto-read energies when the following syntax applies "_XXX.XeV_XX.tif" fnlist = filename.split('_') try: ind =[ m for m, j in enumerate(fnlist) if re.search('\deV', j)][0] iev = float(fnlist[ind][:-2]) except IndexError: iev = i self.filelist.insertRow(i) self.filelist.setRowHeight(i, 20) self.filelist.setItem(i, 0, QtWidgets.QTableWidgetItem(filename)) self.filelist.setItem(i, 1, QtWidgets.QTableWidgetItem(str(ncols))) self.filelist.setItem(i, 2, QtWidgets.QTableWidgetItem(str(nrows))) self.filelist.setItem(i, 3, QtWidgets.QTableWidgetItem('{0:5.2f}'.format(iev))) self.sm_files = self.tif_files self.filelist.setSortingEnabled(True) return #---------------------------------------------------------------------- def OnAccept(self, evt): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) if self.common.stack_loaded == 1: self.parent.new_stack_refresh() self.stk.new_data() ind1st = self.sm_files.index(self.file1st) indlast = self.sm_files.index(self.filelast) if indlast < ind1st: filelist = self.sm_files[indlast:ind1st+1] filelist.reverse() else: filelist = self.sm_files[ind1st:indlast+1] if self.filetype == 'sm': from file_plugins import file_sm_netcdf file_sm_netcdf.read_sm_list(self, filelist, self.filepath, self.data_struct) elif self.filetype == 'xrm': file_xrm.read_xrm_list(self, filelist, self.filepath, self.data_struct) elif self.filetype == 'bim': file_bim.read_bim_list(self, filelist, self.filepath, self.data_struct) elif self.filetype == 'tif': file_tif.read_tif_list(self, filelist, self.filepath, self.data_struct) else: print('Wrong file type') return #fill the gui structure data self.stk.absdata = self.data_struct.exchange.data datadim = np.int32(self.stk.absdata.shape) self.stk.n_cols = datadim[0].copy() self.stk.n_rows = datadim[1].copy() self.stk.ev = self.data_struct.exchange.energy self.stk.n_ev = np.int32(self.stk.ev.shape[0]).copy() npixels = self.stk.n_cols*self.stk.n_rows*self.stk.n_ev self.stk.x_dist = self.data_struct.exchange.x self.stk.y_dist = self.data_struct.exchange.y self.stk.data_dwell = self.data_struct.spectromicroscopy.data_dwell self.stk.fill_h5_struct_from_stk() self.stk.setScale() #self.parent.page1.iev = int(self.stk.n_ev/3) #Is this correct? self.parent.ix = int(self.stk.n_cols/2) self.parent.iy = int(self.stk.n_rows/2) self.common.stack_loaded = 1 self.parent.page0.absimgfig.loadNewImage() directory = os.path.dirname(str(self.filepath)) self.parent.page0.ShowInfo(os.path.basename(str(self.filepath)), directory) # self.page1.ResetDisplaySettings() self.parent.page1.absimgfig.loadNewImageWithROI() self.parent.page1.button_multicrop.setText('Crop stack 3D...') # print (x,y), (self.ix,self.iy), self.stk.absdata.shape self.parent.page1.specfig.ClearandReload() # self.parent.refresh_widgets() # self.parent.page1.ResetDisplaySettings() # self.parent.page1.filename = filelist[0] # # self.parent.page1.textctrl.setText(filelist[0]) # # self.parent.page0.slider_eng.setRange(0,self.stk.n_ev-1) # #self.parent.page0.iev = int(self.stk.n_ev/2) # self.parent.page0.slider_eng.setValue(self.parent.page1.iev) # # self.parent.page1.slider_eng.setRange(0,self.stk.n_ev-1) # #self.parent.page1.iev = self.stk.n_ev/2 # self.parent.page1.slider_eng.setValue(self.parent.page1.iev) # # self.parent.page1.specfig.loadNewSpectrum() # self.parent.page1.absimgfig.loadNewImageWithROI() # # self.parent.page0.ShowInfo(filelist[0], self.filepath) self.parent.page5.updatewidgets() self.parent.refresh_widgets() QtWidgets.QApplication.restoreOverrideCursor() self.close() #---------------------------------------------------------------------- class InputRegionDialog(QtWidgets.QDialog): def __init__(self, parent, nregions, title='Multi Region Stack'): QtWidgets.QWidget.__init__(self, parent) self.parent = parent mainLayout = QtWidgets.QVBoxLayout() layout = QtWidgets.QHBoxLayout() label = QtWidgets.QLabel() label.setText('Select Region to load:') layout.addWidget(label) combo = QtWidgets.QComboBox(self) combo.addItem("All") for i in range(nregions): combo.addItem(str(i+1)) combo.setCurrentIndex(1) self.parent.loadregion = 1 combo.activated[int].connect(self.OnSelectRegion) layout.addWidget(combo) mainLayout.addLayout(layout) layout = QtWidgets.QHBoxLayout() button = QtWidgets.QPushButton("Submit") #string or icon self.connect(button, QtCore.SIGNAL("clicked()"), self.close) layout.addWidget(button) mainLayout.addLayout(layout) self.setLayout(mainLayout) self.resize(250, 60) self.setWindowTitle(title) #---------------------------------------------------------------------- def OnSelectRegion(self, value): item = value selregion = item self.parent.loadregion = selregion #---------------------------------------------------------------------- class AboutFrame(QtWidgets.QDialog): def __init__(self, parent = None, title='About'): QtWidgets.QWidget.__init__(self, parent) self.resize(360, 660) self.setWindowTitle('About Mantis') pal = QtGui.QPalette() self.setAutoFillBackground(True) pal.setColor(QtGui.QPalette.Window,QtGui.QColor('white')) self.setPalette(pal) vbox = QtWidgets.QVBoxLayout() self.image = QtGui.QImage(resource_path(os.path.join('images','Mantis_logo_about.png'))) self.imageLabel = QtWidgets.QLabel() self.imageLabel.setBackgroundRole(QtGui.QPalette.Base) self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(self.image)) vbox.addWidget(self.imageLabel) text1 = QtWidgets.QLabel(self) text1.setText("www.2ndlookconsulting.com") text1.setStyleSheet('color: rgb(53,159,217);font-size: 14pt; font-family: SansSerif;') #text1.setFont(QtGui.QFont('SansSerif', 14)) #font2 = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL) text2 = QtWidgets.QLabel(self) text2.setText('Mantis '+version) text2.setStyleSheet('color: rgb(0,0,0);font-size: 12pt') #text2.SetFont(font2) #font3 = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL) text3 = QtWidgets.QLabel(self) text3.setText( ''' Developed by Mirna Lerotic, based on earlier programs by Mirna Lerotic and Chris Jacobsen. Initial development supported by Argonne National Laboratory LDRD 2010-193-R1 9113. ''') text3.setStyleSheet('color: rgb(0,0,0)') #text3.SetFont(font3) #font4 = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL) text4 = QtWidgets.QLabel(self) text4.setText( ''' Mantis 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 any later version. Mantis 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 http://www.gnu.org/licenses/.''') text4.setStyleSheet('color: rgb(0,0,0)') #text4.SetFont(font4) vbox.addStretch(1) hbox = QtWidgets.QHBoxLayout() hbox.addStretch(1) vbox2 = QtWidgets.QVBoxLayout() vbox2.addWidget(text1) vbox2.addStretch(1) vbox2.addWidget(text2) vbox2.addWidget(text3) vbox2.addWidget(text4) hbox.addLayout(vbox2) hbox.addStretch(1) vbox.addLayout(hbox) vbox.addStretch(1) button_close = QtWidgets.QPushButton('Close') button_close.clicked.connect( self.close) vbox.addWidget(button_close) vbox.addStretch(1) self.setLayout(vbox) # ---------------------------------------------------------------------- class SpecFig(): def __init__(self,parent,plotwidget): self.parent = parent self.plot = plotwidget self.plotitem = self.plot.getPlotItem() self.plot.setBackground("w") pi = self.plot.getPlotItem() pi.layout.setSpacing(12) pi.layout.setContentsMargins(10,10,40,10) pi.showGrid(y=True) pi.showAxis("top", show=True) pi.showAxis("right", show=True) by = pi.getAxis("right") bx = pi.getAxis("top") by.setStyle(showValues=False,tickLength=0) bx.setStyle(showValues=False,tickLength=0) self.ay = pi.getAxis("left") self.ay.setLabel(text="counts") self.ax = pi.getAxis("bottom") self.ax.setLabel(text="Photon energy [eV]") pi.setTitle("") pi.addLegend() self.LineIndicator = pg.InfiniteLine(angle=90, movable=True, markers=None, pen=pg.mkPen(color=QtGui.QColor(0, 0, 0, 128), width=1.5, style=QtCore.Qt.DashLine)) self.LineIndicatorLabel = pg.InfLineLabel(self.LineIndicator, " ") self.parent.button_lockspectrum.clicked.connect(self.OnLockSpectrum) self.parent.button_clearspecfig.clicked.connect(self.ClearandReload) self.parent.button_clearlastroi.clicked.connect(self.ClearLast) self.parent.button_mergeroi.clicked.connect(self.mergeROI) self.parent.button_subtractroi.clicked.connect(self.subtractROI) def ClearLast(self): #self.plot.blockSignals(True) i = 0 if self.parent.ROIShapeBox.currentText() == "Histogram": i = 2 roiitems = [image for image in self.parent.absimgfig.imageplot.items if isinstance(image, pg.ImageItem)] try: if roiitems[-1] != self.parent.absimgfig.ROImask and len(self.parent.absimgfig.imageplot.items) > 2: self.plotitem.removeItem(self.plotitem.items[-1 - i]) self.parent.absimgfig.imageplot.removeItem(roiitems[-1]) except IndexError: # if previously removed, ignore pass #self.plot.blockSignals(False) def ClearandReload(self): self.roicolor = (0, 0, 255, 255) self.plot.blockSignals(True) self.plotitem.setMouseEnabled(x=True, y=True) iterator = len(self.plotitem.items)-1 # The expression "for item in self.plotitem.items: " does not work! Instead we count the items and iterate through them for i in range(iterator,-1,-1): self.plotitem.removeItem(self.plotitem.items[i]) # remove spectra try: # remove locked ROIs from imageplot roiitem = self.parent.absimgfig.imageplot.items[i+1]# +1 because transparent selection "ROImask" exists. if isinstance(roiitem, pg.ImageItem) and len(self.parent.absimgfig.imageplot.items) > 2: self.parent.absimgfig.imageplot.removeItem(roiitem) except IndexError: # if previously removed, ignore pass try: self.LineIndicator.sigPositionChangeFinished.disconnect() self.LineIndicator.sigPositionChanged.disconnect() self.plot.sigRangeChanged.disconnect() self.plot.scene().sigMouseClicked.disconnect() self.parent.absimgfig.roi.sigRegionChanged.disconnect() except: pass self.plot.blockSignals(False) self.loadNewSpectrum() vb = self.plotitem.items[1].getViewBox() vb.enableAutoRange() if self.parent.ROIShapeBox.currentText() != "Lasso": self.parent.absimgfig.OnROIVisibility(self.parent.ROIvisibleCheckBox.checkState()) def ClearforHistogram(self): self.plot.blockSignals(True) self.plotitem.setMouseEnabled(x=False, y=False) iterator = len(self.plotitem.items)-1 # The expression "for item in self.plotitem.items: " does not work! Instead we count the items and iterate through them for i in range(iterator,-1,-1): self.plotitem.removeItem(self.plotitem.items[i]) # remove spectra try: # remove locked ROIs from imageplot roiitem = self.parent.absimgfig.imageplot.items[i+1]# +1 because transparent selection "ROImask" exists. if isinstance(roiitem, pg.ImageItem) and len(self.parent.absimgfig.imageplot.items) > 3: self.parent.absimgfig.imageplot.removeItem(roiitem) except IndexError: # if previously removed, ignore pass try: self.LineIndicator.sigPositionChangeFinished.disconnect() self.LineIndicator.sigPositionChanged.disconnect() self.plot.sigRangeChanged.disconnect() self.plot.scene().sigMouseClicked.disconnect() self.parent.absimgfig.roi.sigRegionChanged.disconnect() except: pass self.plot.blockSignals(False) def setPlotItemVisibility(self, show): iterator = len(self.plotitem.items) - 1 if show: for i in range(iterator, -1, -1): self.plotitem.items[i].show() else: for i in range(iterator, -1, -1): self.plotitem.items[i].hide() def toggleI0Spectrum(self): if self.parent.button_showi0.isChecked(): self.formatAxesLabels(type="showi0") self.parent.ROIvisibleCheckBox.setEnabled(False) self.parent.ROIShapeBox.setEnabled(False) self.parent.button_lockspectrum.setEnabled(False) self.parent.button_clearspecfig.setEnabled(False) self.parent.button_clearlastroi.setEnabled(False) self.parent.button_mergeroi.setEnabled(False) self.parent.button_subtractroi.setEnabled(False) self.setPlotItemVisibility(False) x, y = (self.parent.stk.evi0, self.parent.stk.i0data) curve = pg.PlotCurveItem(x,y, pen=({'color': "#ff7700", 'width': 2}), skipFiniteCheck=True, name="I0") self.plotitem.addItem(curve) #Show I0 region and hide roi selection indices = np.where(self.parent.stk.i0_mask) self.drawROImask(None,indices= indices,color=(255,119,0,255)) self.parent.absimgfig.OnROIVisibility(QtCore.Qt.Unchecked) self.parent.absimgfig.ROImask.show() else: self.setPlotItemVisibility(True) if len(self.plotitem.items) > 2: self.plotitem.removeItem( self.plotitem.items[-1]) self.formatAxesLabels() self.parent.ROIvisibleCheckBox.setEnabled(True) self.parent.ROIShapeBox.setEnabled(True) self.parent.button_lockspectrum.setEnabled(True) self.parent.button_clearspecfig.setEnabled(True) self.parent.button_clearlastroi.setEnabled(True) self.parent.button_mergeroi.setEnabled(True) self.parent.button_subtractroi.setEnabled(True) # Restore ROI self.parent.absimgfig.OnROIVisibility(self.parent.ROIvisibleCheckBox.checkState()) self.updatePlotData() def OnI0Histogram(self): self.parent.OnShowMean() self.parent.button_meanflux.setChecked(True) self.parent.ROIvisibleCheckBox.setEnabled(False) self.parent.button_lockspectrum.setEnabled(False) self.parent.button_clearspecfig.setEnabled(False) self.parent.button_clearlastroi.setEnabled(False) self.parent.button_mergeroi.setEnabled(False) self.parent.button_subtractroi.setEnabled(False) self.roicolor = (255,119,0,255) self.parent.absimgfig.OnROIShapeChanged("Histogram") #self.parent.ROIShapeBox.setCurrentText("Histogram") self.parent.ROIShapeBox.setStyleSheet("color: #ff7700;"); self.parent.label_roitype.setText("I0 type") self.parent.label_roitype.setStyleSheet("color: #ff7700;"); self.parent.button_i0.disconnect() self.parent.button_i0.setText("Accept I0") self.parent.button_i0.setStyleSheet("color: #ff7700;"); self.parent.button_i0.clicked.connect( self.OnI0Accept) # ---------------------------------------------------------------------- def OnI0Accept(self, evt): QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.parent.OnShowMean() self.parent.button_meanflux.setChecked(False) self.parent.ROIvisibleCheckBox.setEnabled(True) self.parent.button_lockspectrum.setEnabled(True) self.parent.button_clearspecfig.setEnabled(True) self.parent.button_clearlastroi.setEnabled(True) self.parent.button_mergeroi.setEnabled(True) self.parent.button_subtractroi.setEnabled(True) self.parent.stk.i0_mask = np.sum(self.parent.absimgfig.ROIrgba, axis=2)[:, :] > 0 self.I0Update() # ---------------------------------------------------------------------- def OnI0Reset(self): self.parent.stk.reset_i0() self.parent.com.i0_loaded = 0 self.roicolor = (0,0,255,255) self.parent.showflux = True # self.rb_flux.setChecked(True) self.parent.absimgfig.loadNewImageWithROI() self.ClearandReload() self.parent.window().refresh_widgets() self.parent.button_i0.disconnect() self.parent.button_i0.setText("Select I0") self.parent.button_i0.clicked.connect(self.OnI0Histogram) self.parent.button_i0ffile.setEnabled(True) self.parent.button_prenorm.setEnabled(True) self.parent.button_refimgs.setEnabled(True) # ---------------------------------------------------------------------- def I0Update(self): bool = np.where(self.parent.stk.i0_mask == True) if bool[0].size and bool[1].size: self.parent.stk.i0_from_histogram(bool) self.parent.I0histogramCalculated() self.parent.button_i0.disconnect() self.parent.button_i0.setText("Reset I0") self.parent.button_i0.setStyleSheet(""); # pass an empty string to return to default style self.parent.ROIShapeBox.setStyleSheet(""); self.parent.button_i0.clicked.connect(self.OnI0Reset) self.parent.label_roitype.setText("ROI type") self.parent.label_roitype.setStyleSheet(""); QtWidgets.QApplication.restoreOverrideCursor() else: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self.parent, 'Error', 'I0 region is empty!') # ---------------------------------------------------------------------- def GetNextROINumberandColor(self): #MANTiS unique Light qualitative color scheme lut = ['#6699DD','#EE7733','#ABCC44','#99DDFF','#FFAABB','#BAAA00','#AB2622','#44BB99','#AA4499','#EEDD89'] hues = len(lut) index = len(self.plotitem.items) if self.parent.ROIShapeBox.currentText() == "Histogram": index = index - 2 name = "ROI " + str(index - 1) color = QtGui.QColor(lut[(index - 2) % hues]) return name, color def OnLockSpectrum(self): name, color = self.GetNextROINumberandColor() color.setAlpha(200) curve = pg.PlotCurveItem(pen=({'color': color, 'width': 2}), skipFiniteCheck=True, name=name) curve.hide() x = self.plotitem.items[1].xData y = self.plotitem.items[1].yData self.plotitem.addItem(curve) if self.parent.ROIShapeBox.currentText() == "Histogram": self.plotitem.items.remove(curve) self.plotitem.items.insert(-2, curve) else: curve.show() self.parent.absimgfig.addLockedROI(color) curve.setData(x,y) def removeLast2ROI(self,i,roiitems): self.plotitem.removeItem(self.plotitem.items[-1 - i]) self.plotitem.removeItem(self.plotitem.items[-1 - i]) self.parent.absimgfig.imageplot.removeItem(roiitems[-1]) self.parent.absimgfig.imageplot.removeItem(roiitems[-2]) def mergeROI(self): i = 0 if self.parent.ROIShapeBox.currentText() == "Histogram": i = 2 self.plot.blockSignals(True) roiitems = [image for image in self.parent.absimgfig.imageplot.items if isinstance(image, pg.ImageItem)] try: if isinstance(roiitems[-2], pg.ImageItem) and len(roiitems) > 3: b1 = np.sum(roiitems[-1].image, axis = 2)[:,:] > 0 b2 = np.sum(roiitems[-2].image, axis = 2)[:,:] > 0 boolmask = ~np.logical_or(b1, b2) indices = np.where(boolmask == False) self.removeLast2ROI(i,roiitems) roi = np.zeros([*boolmask.shape, 4], dtype=np.uint8) name, color = self.GetNextROINumberandColor() color.setAlpha(200) roi[indices] = color.getRgb() lockedroi = pg.ImageItem(image=roi, border="k", opacity=0.5) self.parent.absimgfig.imageplot.addItem(lockedroi, ignoreBounds=True) data = self.prefilterData() x, y = self.getSpecfromROI(data,boolmask) curve = pg.PlotCurveItem(pen=({'color': color, 'width': 2}), skipFiniteCheck=True, name=name) curve.hide() self.plotitem.addItem(curve) if self.parent.ROIShapeBox.currentText() == "Histogram": self.plotitem.items.remove(curve) self.plotitem.items.insert(-2, curve) else: curve.show() curve.setData(x, y) except IndexError: pass self.plot.blockSignals(False) def subtractROI(self): i = 0 if self.parent.ROIShapeBox.currentText() == "Histogram": i = 2 self.plot.blockSignals(True) roiitems = [image for image in self.parent.absimgfig.imageplot.items if isinstance(image, pg.ImageItem)] try: if roiitems[-2] != self.parent.absimgfig.ROImask and len(self.parent.absimgfig.imageplot.items) > 2: b1 = np.sum(roiitems[-1].image, axis = 2)[:,:] > 0 b2 = np.sum(roiitems[-2].image, axis = 2)[:,:] > 0 #boolmask = ~np.logical_and(b1, b2) # intersect boolmask = ~np.logical_and(~b1, b2) # subtract if not np.any(~boolmask): self.ClearLast() self.ClearLast() #! return indices = np.where(boolmask == False) self.removeLast2ROI(i,roiitems) roi = np.zeros([*boolmask.shape, 4], dtype=np.uint8) name, color = self.GetNextROINumberandColor() color.setAlpha(200) roi[indices] = color.getRgb() lockedroi = pg.ImageItem(image=roi, border="k", opacity=0.5) self.parent.absimgfig.imageplot.addItem(lockedroi, ignoreBounds=True) data = self.prefilterData() x, y = self.getSpecfromROI(data,boolmask) curve = pg.PlotCurveItem(pen=({'color': color, 'width': 2}), skipFiniteCheck=True, name=name) curve.hide() self.plotitem.addItem(curve) if self.parent.ROIShapeBox.currentText() == "Histogram": self.plotitem.items.remove(curve) self.plotitem.items.insert(-2, curve) else: curve.show() curve.setData(x, y) elif roiitems[-2] == self.parent.absimgfig.ROImask: self.ClearLast() except IndexError: pass self.plot.blockSignals(False) def loadNewSpectrum(self): if self.plotitem.items: try: self.ClearandReload() except IndexError: pass curve = pg.PlotCurveItem(pen=({'color': pg.intColor(6), 'width': 2}), skipFiniteCheck=True) self.plotitem.addItem(self.LineIndicator, ignoreBounds=True) self.plotitem.addItem(curve) try: self.LineIndicator.sigPositionChangeFinished.disconnect() self.LineIndicator.sigPositionChanged.disconnect() self.plot.sigRangeChanged.disconnect() self.plot.scene().sigMouseClicked.disconnect() self.parent.absimgfig.roi.sigRegionChanged.disconnect() except: pass self.parent.ROIvisibleCheckBox.setEnabled(True) self.parent.absimgfig.OnROIShapeChanged(self.parent.ROIShapeBox.currentText()) if self.parent.ROIShapeBox.currentText() == "Lasso": self.plotitem.items[1].hide() self.parent.absimgfig.OnROIVisibility(self.parent.ROIvisibleCheckBox.checkState()) self.LineIndicator.addMarker("o") self.dot = self.LineIndicator.markers[0][0] self.LineIndicator.setZValue(10) self.ypos = self.getIntersectionY() self.LineIndicator.sigPositionChanged.connect(self.OnUpdateLineIndicator) self.LineIndicator.sigPositionChangeFinished.connect(self.SnapIndicatorToEV) self.plot.sigRangeChanged.connect(self.OnUpdateLineIndicator) self.plot.scene().sigMouseClicked.connect(self.OnMouseClick) self.OnUpdateLineIndicator() def updatePlotData(self): data = self.prefilterData() mask = self.createROImask() self.drawROImask(mask, color = self.roicolor) x, y = self.getSpecfromROI(data,self.parent.absimgfig.boolmask) self.plot.blockSignals(True) self.plotitem.items[1].setData(x, y) self.LineIndicator.setPos(QtCore.QPointF(self.plotitem.items[1].xData[self.parent.iev], self.plotitem.items[1].yData[self.parent.iev])) self.plot.blockSignals(False) def updatePlotDataOnROIShapeChange(self,type): color = self.roicolor self.plot.blockSignals(True) self.formatAxesLabels(type=type) self.plotitem.removeItem(self.plotitem.items[1]) #self.region.sigRegionChanged.disconnect() try: self.plotitem.removeItem(self.region) self.plotitem.removeItem(self.histogram) except: pass if type == "Histogram": self.setPlotItemVisibility(False) self.region = pg.LinearRegionItem(brush=color, bounds=[np.min(self.parent.stk.hist_data_x), np.max(self.parent.stk.hist_data_x)]) #self.region.setZValue(10) self.region.setOpacity(0.3) self.plotitem.addItem(self.region, ignoreBounds=False) curve = pg.PlotCurveItem(pen=({'color': color, 'width': 2}), skipFiniteCheck=True) self.histogram = pg.PlotCurveItem(self.parent.stk.hist_data_x, self.parent.stk.hist_data_y, pen=({'color': color, 'width': 1}), skipFiniteCheck=True, name="histogram", stepMode=True, fillLevel=0, brush=color) self.plotitem.setMouseEnabled(x=False, y=False) self.plotitem.addItem(self.histogram) curve.hide() else: self.setPlotItemVisibility(True) data = self.prefilterData() mask = self.createROImask() self.drawROImask(mask, color=color) x, y = self.getSpecfromROI(data,self.parent.absimgfig.boolmask) curve = pg.PlotCurveItem(x, y, pen=({'color': color, 'width': 2}), skipFiniteCheck=True) self.plotitem.setMouseEnabled(x=True, y=True) self.plotitem.addItem(curve) self.plotitem.items.remove(curve) self.plotitem.items.insert(1, curve) self.plot.blockSignals(False) if hasattr(self, "dot"): self.OnUpdateLineIndicator() def update(region): self.region.setZValue(10) minX, maxX = region i0_indices = np.where((minX <= self.parent.stk.histogram) & (self.parent.stk.histogram <= maxX)) data = self.prefilterData() self.drawROImask(None,indices = i0_indices, color=color) x, y = self.getSpecfromROI(data,self.parent.absimgfig.boolmask) self.plotitem.items[1].setData(x, y) if type == "Histogram": self.region.sigRegionChanged.connect(lambda: update(self.region.getRegion())) self.region.setRegion((self.parent.stk.histmin, self.parent.stk.histmax)) def formatAxesLabels(self,type=None): if type == "Histogram": #self.plotitem.getViewBox().setLimits()#yMin=0, yMax=np.max(y)) self.ax.setLabel(text="Average Flux") self.ay.setLabel(text="log10 (Number of pixels)") return elif type == "showi0": self.ay.setLabel(text="Flux in selected I0 area [counts]") return self.ax.setLabel(text="Photon energy [eV]") if self.parent.com.i0_loaded: self.ay.setLabel(text="Optical density per px inside ROI") else: self.ay.setLabel(text="Flux per px inside ROI [counts]") def getIntersectionY(self): x_newgrid = self.plotitem.items[1].xData y_newgrid = self.plotitem.items[1].yData if len(self.plotitem.items[1].xData)> 2: func = interp1d(x_newgrid, self.plotitem.items[1].yData) x_newgrid = np.linspace(min(x_newgrid), max(x_newgrid), num=min(5000,(40*len(x_newgrid))), endpoint=True) y_newgrid = func(x_newgrid) diff = np.abs(x_newgrid - self.LineIndicator.value()) idx = np.argmin(diff) vb = self.plotitem.items[1].getViewBox() ypos = 1 + (y_newgrid[idx] - vb.viewRect().bottom()) / ( vb.viewRect().bottom() - vb.viewRect().top()) return ypos def SnapIndicatorToEV(self): diff = np.abs(self.plotitem.items[1].xData - self.LineIndicator.value()) idx = np.argmin(diff) self.parent.slider_eng.blockSignals(True) self.parent.OnScrollEng(idx) self.parent.slider_eng.blockSignals(False) def OnUpdateLineIndicator(self): if self.parent.absimgfig.currentroishape == "Histogram": return self.ypos = self.getIntersectionY() self.LineIndicator.markers = [(self.dot, self.ypos, 10)] self.LineIndicator.update() self.LineIndicatorLabel.setPosition(self.ypos) self.LineIndicatorLabel.setFormat(" "+str(round(self.LineIndicator.value(),2)) + " eV ") def OnMouseClick(self,e): if e.double(): vb = self.plotitem.items[1].getViewBox() pos = vb.mapSceneToView(e.scenePos()).x() self.LineIndicator.blockSignals(True) self.LineIndicator.setPos(pos) self.LineIndicator.blockSignals(False) self.LineIndicator.sigPositionChangeFinished.emit(self) def GetRegion(self,box): left = int(box.pos().x()) right = left + int(box.size().x()) bottom = int(box.pos().y()) top = bottom + int(box.size().y()) left = max(0,left) bottom = max(0,bottom) top = min(self.parent.stk.n_rows,max(0,top)) right = min(self.parent.stk.n_cols,max(0,right)) return (left,right,top,bottom) def createROImask(self): if isinstance(self.parent.absimgfig.roi, type(None)): return None cols = self.parent.stk.n_cols rows = self.parent.stk.n_rows angle = self.parent.absimgfig.roi.angle() offsetx = 0 offsety = 0 boolmask = np.full((cols , rows), True) if self.parent.absimgfig.roi.boundingRect().width() > 1000: print("ROI wider than 1000 px is not supported. Refer to _getArrayRegionForArbitraryShape().") return None mask, coords = self.parent.absimgfig.roi.getArrayRegion(boolmask,self.parent.absimgfig.imageitem, axes=(0,1), returnMappedCoords=True) if mask.size == 0: return None # calculate offsets to avoid mismatch of roi and selected pixels if angle: offsetx = np.clip(np.sin(0.785398) * np.cos(np.radians(angle) + 0.785398) - 0.5, -1, 0) offsety = np.clip(np.sin(0.785398) * np.cos(np.radians(angle) - 0.785398) - 0.5, -1, 0) x = np.rint(coords[0] + offsetx).astype(int).flatten() y = np.rint(coords[1] + offsety).astype(int).flatten() # Delete indices outside image region if isinstance(self.parent.absimgfig.roi, pg.RectROI): idcs = np.where((x >= cols) | (x < 0) | (y >= rows) | (y < 0)) else: mask = mask.astype(bool).flatten() idcs = np.where((x > cols) | (x < 0) | (y > rows) | (y < 0) | ~mask) x = np.delete(x, idcs) y = np.delete(y, idcs) boolmask[x, y] = False # Handle edge cases and gaps. Fill holes in data due to rounding errors by applying and reverting a binary dilation. boolmask = np.pad(boolmask, ((1, 1), (1, 1)), 'constant', constant_values=True) boolmask = ndimage.binary_dilation(~ndimage.binary_dilation(~boolmask))[1:-1, 1:-1] return boolmask def getSpecfromROI(self, data, boolmask): cols = self.parent.stk.n_cols rows = self.parent.stk.n_rows ev = self.parent.stk.n_ev if boolmask is None: return [self.parent.stk.ev[i] for i in list(range(ev))], np.zeros(ev) valid_pixel_count = (cols * rows) - np.count_nonzero(boolmask) if not valid_pixel_count: return [self.parent.stk.ev[i] for i in list(range(ev))], np.zeros(ev) mask = np.broadcast_to(np.expand_dims(boolmask, axis=2), (cols, rows, ev)) spectrum = np.ma.array(data, mask=mask).sum(axis=(0,1)) / max(valid_pixel_count,1) x = [self.parent.stk.ev[i] for i in list(range(ev))] y = [spectrum[i] for i in list(range(ev))] return x, y def drawROImask(self, boolmask, indices=None, color = (0,0,255,255)): roirgba = self.parent.absimgfig.ROIrgba roirgba[:, :] = [0, 0, 0, 0] roimask = self.parent.absimgfig.ROImask if not indices: cols = self.parent.stk.n_cols rows = self.parent.stk.n_rows valid_pixel_count = (cols * rows) - np.count_nonzero(boolmask) if not valid_pixel_count or boolmask is None: self.parent.absimgfig.boolmask[:,:] = True else: self.parent.absimgfig.boolmask = boolmask indices = ~self.parent.absimgfig.boolmask else: self.parent.absimgfig.boolmask[:, :] = True self.parent.absimgfig.boolmask[indices] = False roirgba[indices] = color roimask.setImage(roirgba) def prefilterData(self): if self.parent.com.i0_loaded: #self.cb_od_per_px.setVisible(True) #if self.cb_od_per_px.isChecked(): if self.parent.com.stack_4d: data = self.parent.stk.od4d[:, :, :, int(self.parent.itheta)].copy() else: data = self.parent.stk.od3d else: if self.parent.com.stack_4d == 1: #t = [self.parent.stk.theta[i] for i in self.parent.itheta] #self.label_theta_range.setText( # "Theta range: [ " + str(min(t, default=0)) + "° .. " + str( # max(t, default=0)) + "° ], # values: " + str( # len(t))) data = self.parent.stk.stack4D[:, :, :, int(self.parent.itheta)] else: data = self.parent.stk.absdata # self.label_spatial_range.setText("Stack size: [ "+str(int(self.box.size().x()))+" x "+str(int(self.box.size().y()))+" ] px²") # self.label_ev_range.setText( # "Energy range: [ " + str(min(x, default=0)) + " .. " + str(max(x, default=0)) + " ] eV, # values: "+ str(len(x))) return data def OnCopy(self): # self.exp = pg.exporters.ImageExporter(self.plotitem) # just plot self.exp = pg.exporters.ImageExporter(self.plot.scene()) # plot and axes, i.e., complete viewbox self.exp.export(copy=True) return def SaveFig(self,fileName): fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext == 'svg': exp = pg.exporters.SVGExporter(self.plot.scene()) elif ext== 'pdf': exp = PDFExporter(self.plot.scene()) else: exp = pg.exporters.ImageExporter(self.plot.scene()) if ext in ['tif','png','jpg','svg','pdf']: exp.export(fileName) # ---------------------------------------------------------------------- class ImgFig(): def __init__(self,parent,canvas): self.scale = 0.000001 self.parent = parent canvas.setBackground("w") # canvas is a pg.GraphicsLayoutWidget self.imageplot = canvas.addPlot() self.imageplot.setMouseEnabled(x=False, y=False) self.imageitem = pg.ImageItem(border="k") self.scalebar = pg.ScaleBar(size=1, suffix="m") self.imageplot.setAspectLocked(lock=True, ratio=1) self.imageplot.showAxis("top", show=True) self.imageplot.showAxis("bottom", show=True) self.imageplot.showAxis("left", show=True) self.imageplot.showAxis("right", show=True) self.ay1 = self.imageplot.getAxis("left") by1 = self.imageplot.getAxis("right") self.ax1 = self.imageplot.getAxis("bottom") bx1 = self.imageplot.getAxis("top") self.ay1.setLabel(text="y",units="px") self.ay1.enableAutoSIPrefix(enable=True) self.ax1.setLabel(text="x",units="px") self.ax1.enableAutoSIPrefix(enable=True) self.ay1.setStyle(tickLength=8) self.ax1.setStyle(tickLength=8) by1.setStyle(showValues=False,tickLength=0) bx1.setStyle(showValues=False,tickLength=0) self.imageplot.setTitle("No data loaded") self.map = "gray" cm = pg.colormap.get(self.map, source="matplotlib") self.bar = pg.ColorBarItem(values=(0, 1), colorMap=cm, rounding=0.0001) # init color bar self.mousepressed = False def loadNewImage(self): self.clear() self.parent.iev = 0 self.loadData() def loadNewImageWithROI(self): self.parent.stk.calc_histogram() self.clear() self.parent.iev = 0 self.loadData() self.currentroishape = "Lasso" self.addROI((0,0),(self.imageitem.boundingRect().width(), self.imageitem.boundingRect().height()), self.currentroishape) self.parent.ROIShapeBox.blockSignals(True) self.parent.ROIShapeBox.setCurrentText(self.currentroishape) self.parent.ROIShapeBox.blockSignals(False) def clear(self): self.imageplot.removeItem(self.scalebar) self.imageplot.removeItem(self.imageitem) self.imageplot.clear() def OnROIVisibility(self, state): if state == QtCore.Qt.Checked: if not isinstance(self.roi, type(None)): self.roi.show() self.parent.specfig.plotitem.items[1].show() # curve self.parent.specfig.plotitem.items[0].show() # line & dot try: self.parent.specfig.histogram.show() self.parent.specfig.region.show() except: pass self.ROImask.show() self.parent.ROIShapeBox.setEnabled(True) self.parent.button_lockspectrum.setEnabled(True) self.parent.button_clearspecfig.setEnabled(True) self.parent.button_clearlastroi.setEnabled(True) self.parent.button_mergeroi.setEnabled(True) self.parent.button_subtractroi.setEnabled(True) else: if not isinstance(self.roi, type(None)): self.roi.hide() self.parent.specfig.plotitem.items[1].hide() self.parent.specfig.plotitem.items[0].hide() try: self.parent.specfig.histogram.hide() self.parent.specfig.region.hide() except: pass self.ROImask.hide() vb = self.parent.specfig.plotitem.getViewBox() vb.updateAutoRange() self.parent.ROIShapeBox.setEnabled(False) self.parent.button_lockspectrum.setEnabled(False) def OnROIShapeChanged(self, shape): self.parent.ROIShapeBox.blockSignals(True) self.parent.ROIShapeBox.setCurrentText(shape) self.parent.ROIShapeBox.blockSignals(False) #self.parent.specfig.plot.blockSignals(True) if isinstance(self.roi, pg.PolyLineROI): pos = np.rint(self.roi.pos()) state = self.roi.getState() points = state['points'] try: minx= min(points, key=lambda item: item[0])[0] miny = min(points, key=lambda item: item[1])[1] x = (max(points, key=lambda item: item[0])[0] - minx) y = (max(points, key=lambda item: item[1])[1] - miny) pos = QtCore.QPointF(np.rint(pos[0]+minx),np.rint(pos[1]+miny)) except ValueError: # of no points found expand to image dimension. x,y = (self.imageitem.boundingRect().width(), self.imageitem.boundingRect().height()) pos = QtCore.QPointF(0,0) pass size = np.rint((x,y)) elif isinstance(self.roi, type(None)): pos = QtCore.QPointF(0, 0) size = np.rint((self.imageitem.boundingRect().width(), self.imageitem.boundingRect().height())) else: pos = np.rint(self.roi.pos()) size = np.rint(self.roi.size()) try: self.roi.disconnect() except: pass self.imageplot.removeItem(self.ROImask) self.imageplot.removeItem(self.roi) self.currentroishape = shape self.addROI(pos, size, shape) self.parent.specfig.updatePlotDataOnROIShapeChange(shape) def onMousePress(self,e): if not self.parent.button_showi0.isChecked() and self.parent.ROIvisibleCheckBox.isChecked(): self.mousepressed = True def onMouseRelease(self,e): if self.mousepressed: self.mousepressed = False if len(self.roi.handles) > 2: self.proxy.disconnect() self.roi.addSegment(self.roi.handles[-1]['item'], self.roi.handles[0]['item']) self.imageitem.mousePressEvent = self.imageplot.mousePressEvent self.imageitem.mouseReleaseEvent = self.imageplot.mouseReleaseEvent def onMouseMoved(self,e): pos = self.vb.mapSceneToView(e[0]) roipos = pos-self.vb.mapFromViewToItem(self.roi,pos) if self.mousepressed and self.vb.itemBoundingRect(self.imageitem).contains(pos): pos = (np.rint(pos.x()-roipos.x()), np.rint(pos.y()-roipos.y())) try: if self.roi.handles[-1]['pos'] != QtCore.QPointF(*pos): # if same point as before, do not add handle self.roi.addFreeHandle(pos) self.roi.addSegment(self.roi.handles[-2]['item'], self.roi.handles[-1]['item']) except IndexError: # first handle self.parent.specfig.plotitem.items[1].show() self.parent.specfig.plotitem.items[0].show() self.roi.addFreeHandle(pos) def addROI(self,pos, size, shape): self.imageitem.mousePressEvent = self.imageplot.mousePressEvent self.imageitem.mouseReleaseEvent = self.imageplot.mouseReleaseEvent kwargs= {'pen': (5, 8), 'handlePen' : QtGui.QPen(QtGui.QColor(255, 0, 128, 255)), 'resizable' : True, 'removable' : False, 'movable' : True, 'scaleSnap' : True, 'translateSnap' : True} selection = {"Rectangle": pg.RectROI(pos,size,**kwargs), "Circle": pg.CircleROI(pos,size,**kwargs), "Ellipse": pg.EllipseROI(pos,size,**kwargs), "Polygon": pg.PolyLineROI(positions= [(0,0),(0,size[1]),(size[0],size[1]),(size[0],0)], pos=pos, closed=True,**kwargs), "Lasso": pg.PolyLineROI(positions= [], pos=pos, closed=True,**kwargs), "Histogram":None} self.roi = selection[shape] if not shape == "Histogram": self.imageplot.addItem(self.roi, ignoreBounds=True) if shape == "Lasso": try: self.parent.specfig.plotitem.items[1].hide() self.parent.specfig.plotitem.items[0].hide() except IndexError: # if first roi, spectra are not existing at this point pass self.roi.handlePen = QtGui.QPen(QtGui.QColor(0, 0, 0, 0)) # Make handles invisible self.imageitem.mousePressEvent = self.onMousePress self.imageitem.mouseReleaseEvent = self.onMouseRelease self.proxy = pg.SignalProxy(self.vb.scene().sigMouseMoved, rateLimit=15, slot=self.onMouseMoved) else: try: self.parent.specfig.plotitem.items[1].show() self.parent.specfig.plotitem.items[0].show() except IndexError: # if first roi, spectra are not existing at this point pass self.ROImask = pg.ImageItem(border="k", opacity=0.5) self.imageplot.addItem(self.ROImask) self.ROIrgba = np.zeros([*self.imageitem.image.shape, 4], dtype=np.uint8) self.boolmask = np.full((self.parent.stk.n_cols, self.parent.stk.n_rows), True) # items are appended to the items list. relocate the two freshly added items, otherwise roi removal fails. self.imageplot.items.remove(self.ROImask) self.imageplot.items.insert(1, self.ROImask) if not shape == "Histogram": self.imageplot.items.remove(self.roi) self.imageplot.items.insert(1, self.roi) self.roi.setZValue(10) # make sure ROI is drawn above image self.roi.sigRegionChanged.connect(self.parent.specfig.updatePlotData) def addLockedROI(self,color): roi = np.zeros([*self.imageitem.image.shape, 4], dtype=np.uint8) indices = np.where(self.boolmask == False) roi[indices] = color.getRgb() lockedroi = pg.ImageItem(image=roi, border="k", opacity=0.5) self.imageplot.addItem(lockedroi, ignoreBounds=True) lockedroi.setZValue(11) # make sure ROI is drawn above image if self.parent.ROIShapeBox.currentText() == "Lasso": # remove lasso ROI after function call self.OnROIShapeChanged("Lasso") def loadData(self): # Called when fresh data are loaded. try: self.vb.sigRangeChanged.disconnect() except: pass self.imageplot.addItem(self.imageitem) self.vb = self.imageitem.getViewBox() rightlabel = self.bar.getAxis("right") if self.parent.com.i0_loaded: rightlabel.setLabel(text="OD", units="") else: rightlabel.setLabel(text="counts", units="") #self.parent.slider_eng.blockSignals(True) self.parent.slider_eng.setRange(0, self.parent.stk.n_ev - 1) self.OnColormapChange(map=self.parent.CMMapBox.currentText(),num_colors=self.parent.StepSpin.value()) self.parent.OnScrollEng(self.parent.iev) # Plot image & set Scrollbar self.bar.setImageItem(self.imageitem, insert_in=self.imageplot) try: self.OnMetricScale(self.parent.MetricCheckBox.isChecked(), True, False) except AttributeError: self.OnMetricScale(False, True, self.parent.SquarePxCheckBox.isChecked()) self.OnShowScale() self.vb.sigRangeChanged.connect(lambda: self.OnUpdateScale(self.parent.ScalebarCheckBox.isChecked())) def draw(self,image,setlabel=True,setlut=False): if setlut: self.OnColormapChange(map=self.parent.CMMapBox.currentText(),num_colors=self.parent.StepSpin.value()) self.imageitem.setImage(image) if setlabel: if self.parent.com.stack_4d == 1: self.imageplot.setTitle("
Image at {0:5.2f} eV and {1:5.1f}°
".format(float(self.parent.stk.ev[self.parent.iev]), float(self.parent.stk.theta[ self.parent.itheta]))) else: self.imageplot.setTitle("
Image at energy {0:5.2f} eV
".format(float(self.parent.stk.ev[self.parent.iev]))) min = np.nanmin(image) # ignoring nans max = np.nanmax(image) if not np.isnan(min) and not np.isnan(max): self.bar.setLevels(low=min, high=max) def OnMetricScale(self, setmetric= True, zeroorigin= True, square= False): if self.parent.com.stack_loaded == 1: if setmetric==True: self.parent.SquarePxCheckBox.setVisible(False) self.parent.ZeroOriginCheckBox.setVisible(True) self.imageplot.setAspectLocked(lock=True, ratio=1) #self.p2.setAspectLocked(lock=True, ratio=1) if not zeroorigin: x_start = self.parent.stk.x_start*self.scale y_start = self.parent.stk.y_start*self.scale else: x_start = 0 y_start = 0 self.ay1.setLabel(text="y", units="m") self.ax1.setLabel(text="x", units="m") #self.ay2.setLabel(text="y", units="m") #self.ax2.setLabel(text="x", units="m") self.imageitem.setRect(QtCore.QRectF(x_start, y_start, self.scale*self.parent.stk.n_cols*self.parent.stk.x_pxsize, self.scale*self.parent.stk.n_rows*self.parent.stk.y_pxsize)) #if hasattr(self, "OD"): # self.m_item.setRect(QtCore.QRectF(x_start, y_start, self.scale*np.shape(self.OD)[0]*self.stk.x_pxsize, self.scale*np.shape(self.OD)[1]*self.stk.y_pxsize)) # self.setCrosshair() else: try: self.parent.ZeroOriginCheckBox.setVisible(False) except AttributeError: pass self.parent.SquarePxCheckBox.setVisible(True) if square == True: aspect = 1 self.parent.ScalebarCheckBox.setVisible(False) else: self.parent.ScalebarCheckBox.setVisible(True) aspect = self.parent.stk.x_pxsize/self.parent.stk.y_pxsize #print(aspect) self.imageplot.setAspectLocked(lock=True, ratio=aspect) self.ay1.setLabel(text="y", units="px") self.ax1.setLabel(text="x", units="px") self.imageitem.setRect(QtCore.QRectF(0, 0, self.parent.stk.n_cols, self.parent.stk.n_rows)) def OnCatChanged(self): self.parent.CMMapBox.blockSignals(True) self.parent.CMMapBox.clear() self.parent.CMMapBox.blockSignals(False) self.parent.CMMapBox.addItems(self.parent.cmaps[self.parent.CMCatBox.currentIndex()][1]) def OnColormapChange(self, map="gray", num_colors=256): self.map = map cm = pg.colormap.get(self.map, source="matplotlib") lut = cm.getLookupTable(0, 1, num_colors) if self.parent.com.stack_loaded == 1: try: lut = np.ascontiguousarray(lut) self.imageitem.setLookupTable(lut) lut = np.expand_dims(lut, axis=1) qimg = pg.functions.ndarray_to_qimage(lut, QtGui.QImage.Format.Format_RGB888) self.bar.bar.setPixmap(QtGui.QPixmap.fromImage(qimg).scaled(1, 256)) except AttributeError: self.bar.bar.setLookupTable(lut) self.imageitem.setLookupTable(lut) def OnShowScale(self): suffix = "m" self.scalebar.text.setText(pg.siFormat(float(self.parent.stk.scale_bar_string)*self.scale, suffix=suffix)) self.scalebar.setParentItem(self.imageplot.getViewBox()) self.scalebar.anchor((1, 1), (1, 1), offset=(-20, -20)) self.scalebar.hide() self.OnUpdateScale(self.parent.ScalebarCheckBox.isChecked()) def OnUpdateScale(self, set): if hasattr(self.parent.stk, "scale_bar_string"): if not set or self.parent.SquarePxCheckBox.isChecked(): self.scalebar.hide() return if not hasattr(self.parent, "MetricCheckBox"): self.scalebar.size = float(self.parent.stk.scale_bar_string) / self.parent.stk.x_pxsize elif not self.parent.MetricCheckBox.isChecked(): self.scalebar.size = float(self.parent.stk.scale_bar_string) / self.parent.stk.x_pxsize elif self.parent.MetricCheckBox.isChecked(): self.scalebar.size = float(self.parent.stk.scale_bar_string)*self.scale self.scalebar.updateBar() self.scalebar.show() def OnCopy(self): # self.exp = pg.exporters.ImageExporter(self.imageitem) # just image self.exp = pg.exporters.ImageExporter(self.imageplot) # image and axes, i.e., complete viewbox self.exp.export(copy=True) return def SaveFig(self,fileName): fileName = str(fileName) if fileName == '': return path, ext = os.path.splitext(fileName) ext = ext[1:].lower() if ext == 'svg': exp = pg.exporters.SVGExporter(self.imageplot) # The SVG output is clean. # Display errors (line widths, etc.) likely result from external software not properly handling SVG. elif ext== 'pdf': exp = PDFExporter(self.imageplot) else: exp = pg.exporters.ImageExporter(self.imageplot) if ext in ['tif','png','jpg','svg','pdf']: exp.export(fileName) #----------------------------------------------------------------------- class MainFrame(QtWidgets.QMainWindow): def __init__(self): super(MainFrame, self).__init__() self.initUI() #---------------------------------------------------------------------- def initUI(self): self.data_struct = data_struct.h5() self.stk = data_stack.data(self.data_struct) self.anlz = analyze.analyze(self.stk) self.nnma = nnma.nnma(self.stk) self.common = common() self.resize(Winsizex, Winsizey) self.setWindowTitle('Mantis v.{0}'.format(version)) self.initToolbar() ico = QtGui.QIcon(resource_path(os.path.join('images','logo-2l-32.ico'))) self.setWindowIcon(ico) tabs = QtWidgets.QTabWidget() # create the page windows as tabs self.page0 = PageLoadData(self.common, self.data_struct, self.stk) self.page1 = PageStack(self.common, self.data_struct, self.stk) self.page2 = PagePCA(self.common, self.data_struct, self.stk, self.anlz) self.page3 = PageCluster(self.common, self.data_struct, self.stk, self.anlz) self.page4 = PageSpectral(self.common, self.data_struct, self.stk, self.anlz) self.page5 = PagePeakID(self.common, self.data_struct, self.stk, self.anlz) self.page6 = PageXrayPeakFitting(self.common, self.data_struct, self.stk, self.anlz) self.page7 = PageNNMA(self.common, self.data_struct, self.stk, self.anlz, self.nnma) tabs.addTab(self.page0,"Load Data") tabs.addTab(self.page1, "Preprocess Data") tabs.addTab(self.page2,"PCA") tabs.addTab(self.page3,"Cluster Analysis") tabs.addTab(self.page4,"Spectral Maps") tabs.addTab(self.page7, "NNMA Analysis") tabs.addTab(self.page5,"Peak ID") tabs.addTab(self.page6, "XrayPeakFitting") if showtomotab: self.page8 = PageTomo(self.common, self.data_struct, self.stk, self.anlz) tabs.addTab(self.page8, "Tomography") # if showmaptab: # self.page9 = PageMap(self.common, self.data_struct, self.stk) # tabs.addTab(self.page9, "Image Maps") if sys.platform == 'win32': tabs.setMinimumHeight(750) else: tabs.setMinimumHeight(400) # print Qt colours BackgroundColour = self.palette().color(self.palette().Background) DarkBackgroundFlag = (BackgroundColour.red()+BackgroundColour.green()+BackgroundColour.blue()) < 375 if DarkBackgroundFlag: tabs.tabBar().setTabTextColor(0, QtGui.QColor('lightgreen')) tabs.tabBar().setTabTextColor(1, QtGui.QColor('lightgreen')) tabs.tabBar().setTabTextColor(2, QtGui.QColor('tomato')) tabs.tabBar().setTabTextColor(3, QtGui.QColor('tomato')) tabs.tabBar().setTabTextColor(4, QtGui.QColor('tomato')) tabs.tabBar().setTabTextColor(5, QtGui.QColor('tomato')) tabs.tabBar().setTabTextColor(6, QtGui.QColor('orchid')) tabs.tabBar().setTabTextColor(7, QtGui.QColor('orchid')) if showtomotab: tabs.tabBar().setTabTextColor(8, QtGui.QColor('dodgerblue')) else: tabs.tabBar().setTabTextColor(0, QtGui.QColor('green')) tabs.tabBar().setTabTextColor(1, QtGui.QColor('green')) tabs.tabBar().setTabTextColor(2, QtGui.QColor('darkRed')) tabs.tabBar().setTabTextColor(3, QtGui.QColor('darkRed')) tabs.tabBar().setTabTextColor(4, QtGui.QColor('darkRed')) tabs.tabBar().setTabTextColor(5, QtGui.QColor('darkRed')) tabs.tabBar().setTabTextColor(6, QtGui.QColor('purple')) tabs.tabBar().setTabTextColor(7, QtGui.QColor('purple')) if showtomotab: tabs.tabBar().setTabTextColor(8, QtGui.QColor('darkblue')) #if showmaptab: # tabs.tabBar().setTabTextColor(9, QtGui.QColor('darkblue')) # Only add "expert" pages if option "--key" is given in command line try: options, extraParams = getopt.getopt(sys.argv[1:], '', ['wx', 'batch', 'nnma', 'ica', 'keyeng']) except: print('Error - wrong command line option used. Available options are --wx, --batch and --nnma') return # for opt, arg in options: # if opt in '--nnma': # if verbose: print "Running with NNMA." # self.page7 = PageNNMA(self.common, self.data_struct, self.stk, self.anlz, self.nnma) # tabs.addTab(self.page7, "NNMA Analysis") layout = QtWidgets.QVBoxLayout() layout.addWidget(tabs) #self.setCentralWidget(tabs) self.scrollArea = QtWidgets.QScrollArea() #self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark) self.scrollArea.setWidget(tabs) #self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) #self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollArea.setWidgetResizable(True) self.setCentralWidget(self.scrollArea) screen = QtWidgets.QDesktopWidget().screenGeometry() center = screen.center() winrect = self.frameGeometry() winrect.moveCenter(center) self.move(winrect.topLeft()) if screen.height() <= 1080 - 50 | screen.width() <= 1920: self.showMaximized() self.show() if sys.platform == "darwin": self.raise_() #---------------------------------------------------------------------- def initToolbar(self): self.actionOpen = QtWidgets.QAction(self) self.actionOpen.setObjectName('actionOpen') self.actionOpen.setIcon(QtGui.QIcon(resource_path(os.path.join('images','document-open.png')))) self.toolbar = self.addToolBar('actionOpen') self.toolbar.addAction(self.actionOpen) self.actionOpen.triggered.connect(self.LoadStack) self.actionOpenSL = QtWidgets.QAction(self) self.actionOpenSL.setObjectName('actionOpenSL') self.actionOpenSL.setIcon(QtGui.QIcon(resource_path(os.path.join('images','open-sl.png')))) self.toolbar.addAction(self.actionOpenSL) self.actionOpenSL.triggered.connect(self.BuildStack) self.actionSave = QtWidgets.QAction(self) self.actionSave.setObjectName('actionSave') self.actionSave.setIcon(QtGui.QIcon(resource_path(os.path.join('images','media-floppy.png')))) self.toolbar.addAction(self.actionSave) self.actionSave.triggered.connect(self.onSaveResultsToH5) self.actionSave.setEnabled(False) self.actionInfo = QtWidgets.QAction(self) self.actionInfo.setObjectName('actionInfo') self.actionInfo.setIcon(QtGui.QIcon(resource_path(os.path.join('images','help-browser.png')))) self.toolbar.addAction(self.actionInfo) self.actionInfo.triggered.connect(self.onAbout) #---------------------------------------------------------------------- def LoadStack(self): """ Browse for a stack file: """ filepath, plugin = File_GUI.SelectFile('read','stack') if filepath is not None: if plugin is None: # auto-assign appropriate plugin plugin = file_plugins.identify(filepath) FileStruct = file_plugins.GetFileStructure(filepath, plugin=plugin) FileInternalSelection = [(0,0)] if FileStruct is not None: dlg = File_GUI.DataChoiceDialog(filepath=filepath, filestruct=FileStruct, plugin=plugin) if not dlg.exec_(): return # do nothing if GUI is cancelled FileInternalSelection = dlg.selection if dlg.filepath != filepath: filepath = dlg.filepath plugin = file_plugins.identify(dlg.filepath) if plugin is None: QtWidgets.QMessageBox.warning(self, 'Error!', "Unknown file type") QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) if self.common.stack_loaded == 1: self.new_stack_refresh() self.stk.new_data() self.anlz.delete_data() try: #if checkboxes exist, return checked/unchecked JSONconvert = dlg.jsoncheck.isChecked() ringnorm = dlg.ringnormcheck.isChecked() except UnboundLocalError: #if checkboxes missing, i.e. for prenormalized data, *.ncb, etc. print("DataChoiceDialog skipped") JSONconvert = None ringnorm = None file_plugins.load(filepath, stack_object=self.stk, plugin=plugin, selection=FileInternalSelection,json=JSONconvert,inorm=ringnorm) directory = os.path.dirname(str(filepath)) self.page1.filename = os.path.basename(str(filepath)) #Update widgets x=self.stk.n_cols y=self.stk.n_rows self.page1.imgrgb = np.zeros(x*y*3,dtype = "uint8") self.page1.maxval = np.amax(self.stk.absdata) self.ix = int(x/2) self.iy = int(y/2) self.page1.ix = self.ix self.page1.iy = self.iy #self.iev = 0 #self.page0.slider_eng.setRange(0,self.stk.n_ev-1) #self.page0.iev = self.iev #self.page0.slider_eng.setValue(self.iev) #self.page1.slider_eng.setRange(0,self.stk.n_ev-1) #self.page1.iev = self.iev #self.page1.slider_eng.setValue(self.iev) #if showmaptab: # self.page9.Clear() # self.page9.slider_eng.setRange(0,self.stk.n_ev-1) self.stk.setScale() self.common.stack_loaded = 1 self.common.path = directory if self.stk.data_struct.spectromicroscopy.normalization.white_spectrum is not None: self.common.i0_loaded = 1 self.stk.calculate_optical_density() self.stk.fill_h5_struct_normalization() #self.page0.Clear() self.page0.absimgfig.loadNewImage() self.page0.ShowInfo(self.page1.filename, directory) #self.page1.ResetDisplaySettings() self.page1.absimgfig.loadNewImageWithROI() self.page1.button_multicrop.setText('Crop stack 3D...') #print (x,y), (self.ix,self.iy), self.stk.absdata.shape self.page1.specfig.ClearandReload() #self.page1.textctrl.setText(self.page1.filename) self.page5.updatewidgets() QtWidgets.QApplication.restoreOverrideCursor() #if showmaptab: # self.page9.Clear() # self.page9.loadData() self.refresh_widgets() #----------------------------------------------------------------------- def BuildStack(self): """ Browse for .sm files """ #try: if True: directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Choose a directory", '', QtWidgets.QFileDialog.ShowDirsOnly|QtWidgets.QFileDialog.ReadOnly ) if directory == '': return self.common.path = directory stackframe = StackListFrame(self, directory, self.common, self.stk, self.data_struct) stackframe.show() # except: # print 'Error could not build stack list.' # self.common.stack_loaded = 0 # self.common.i0_loaded = 0 # self.new_stack_refresh() # self.refresh_widgets() # # QtWidgets.QMessageBox.warning(self,'Error',"Error could not build stack list") # import sys; print sys.exc_info() #----------------------------------------------------------------------- def LoadStack4D(self): try: #if True: wildcard = "Supported 4D formats (*.hdf5 *.ncb);;HDF5 files (*.hdf5);;NCB files (*.ncb);;" filepath, _filter = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open files', '', wildcard) if filepath == '': return filenames = [] for name in filepath: filenames.append(str(name)) directory = os.path.dirname(str(filenames[0])) self.page1.filename = os.path.basename(filenames[0]) QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) basename, extension = os.path.splitext(self.page1.filename) extension = extension.strip() self.common.path = directory self.common.filename = self.page1.filename if extension == '.hdf5': if self.common.stack_loaded == 1: self.new_stack_refresh() self.stk.new_data() self.anlz.delete_data() self.stk.read_h54D(filenames[0]) print('Finished reading 4D stack', filenames[0]) elif extension == '.ncb': if self.common.stack_loaded == 1: self.new_stack_refresh() self.stk.new_data() #self.stk.data_struct.delete_data() self.anlz.delete_data() self.stk.read_ncb4D(filenames) #Get energy list wildcard = "Text file (*.txt);;" engfilepath, _filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', directory, wildcard) self.stk.read_ncb4Denergy(str(engfilepath)) #Update widgets if not hasattr(self.stk, 'theta') or type(self.stk.theta) == int: raise TypeError("Not a 4D stack") self.common.stack_4d = 1 x=self.stk.n_cols y=self.stk.n_rows self.page1.imgrgb = np.zeros(x*y*3,dtype = "uint8") self.page1.maxval = np.amax(self.stk.absdata) self.ix = int(x/2) self.iy = int(y/2) self.page1.ix = self.ix self.page1.iy = self.iy self.iev = int(self.stk.n_ev/2) #self.page0.slider_eng.setRange(0,self.stk.n_ev-1) self.page0.iev = self.iev #self.page0.slider_eng.setValue(self.iev) #self.page1.slider_eng.setRange(0,self.stk.n_ev-1) self.page1.iev = self.iev #self.page1.slider_eng.setValue(self.iev) self.page0.slider_theta.setVisible(True) #self.page0.tc_imagetheta.setVisible(True) self.itheta = 0 self.page0.slider_theta.setRange(0,self.stk.n_theta-1) self.page0.itheta = self.itheta self.page0.slider_theta.setValue(self.itheta) #self.page0.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) self.page1.slider_theta.setVisible(True) #self.page1.tc_imagetheta.setVisible(True) self.page1.slider_theta.setRange(0,self.stk.n_theta-1) self.page1.itheta = self.itheta self.page1.slider_theta.setValue(self.itheta) #self.page1.tc_imagetheta.setText("4D Data Angle: "+str(self.stk.theta[self.itheta])) self.page2.button_calcpca4D.setVisible(True) self.page4.button_calc4d.setVisible(True) self.common.stack_loaded = 1 if self.stk.data_struct.spectromicroscopy.normalization.white_spectrum is not None: self.common.i0_loaded = 1 self.page0.absimgfig.loadNewImage() self.page0.ShowInfo(self.page1.filename, directory) #self.page1.ResetDisplaySettings() self.page1.absimgfig.loadNewImageWithROI() self.page1.button_multicrop.setText('Crop stack 4D...') self.page1.specfig.ClearandReload() # self.page1.textctrl.setText(self.page1.filename) self.page5.updatewidgets() QtWidgets.QApplication.restoreOverrideCursor() #if showmaptab: # self.page9.Clear() # self.page9.loadData() except: self.common.stack_loaded = 0 self.common.i0_loaded = 0 self.new_stack_refresh() self.page1.button_multicrop.setText('Crop stack 3D/4D...') QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'Image stack not loaded.') import sys print(sys.exc_info()) self.refresh_widgets() #---------------------------------------------------------------------- def OnSaveProcessedStack(self, event): self.SaveProcessedStack() #---------------------------------------------------------------------- def SaveProcessedStack(self): """ Export processed stack to file """ filepath, plugin = File_GUI.SelectFile('write','stack') if filepath is not None and plugin is not None: QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) try: file_plugins.save(filepath, self.stk, 'stack', plugin=plugin) QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save processed stack file.') return #---------------------------------------------------------------------- def onSaveResultsToH5(self, event): self.SaveResultsToH5() #---------------------------------------------------------------------- def SaveResultsToH5(self): """ Browse for .hdf5 file """ try: #print self.data_struct.exchange.data.shape #print self.data_struct.exchange.data_axes wildcard = "HDF5 files (*.hdf5)" filepath, _filter = QtWidgets.QFileDialog.getSaveFileName(self, 'Save as .hdf5', '', wildcard) filepath = str(filepath) if filepath == '': return directory = os.path.dirname(str(filepath)) self.page1.filename = os.path.basename(str(filepath)) QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(Qt.WaitCursor)) self.common.path = directory self.common.filename = self.page1.filename file_dataexch_hdf5.write_results_h5(filepath, self.data_struct, self.anlz) QtWidgets.QApplication.restoreOverrideCursor() except: QtWidgets.QApplication.restoreOverrideCursor() QtWidgets.QMessageBox.warning(self, 'Error', 'Could not save HDF5 file.') self.refresh_widgets() return #---------------------------------------------------------------------- def onAbout(self): self.popup = AboutFrame(self) self.popup.show() #---------------------------------------------------------------------- def refresh_widgets(self): if self.common.stack_loaded == 0: self.page1.button_i0ffile.setEnabled(False) self.page1.button_i0.setEnabled(False) self.page1.button_artefacts.setEnabled(False) self.page1.button_prenorm.setEnabled(False) self.page1.button_refimgs.setEnabled(False) self.page1.button_multicrop.setEnabled(False) #self.page1.button_limitev.setEnabled(False) #self.page1.button_subregion.setEnabled(False) self.page1.button_darksig.setEnabled(False) self.page1.button_save.setEnabled(False) self.page1.button_savestack.setEnabled(False) self.page1.button_align.setEnabled(False) self.page1.button_meanflux.setEnabled(False) self.page1.button_slideshow.setEnabled(False) self.page1.button_spectralROI.setEnabled(False) self.page1.button_lockspectrum.setEnabled(False) self.page1.button_clearlastroi.setEnabled(False) self.page1.button_mergeroi.setEnabled(False) self.page1.button_subtractroi.setEnabled(False) self.page1.button_clearspecfig.setEnabled(False) self.page1.ROIShapeBox.setEnabled(False) self.page1.ROIvisibleCheckBox.setEnabled(False) #self.page1.button_resetdisplay.setEnabled(False) #self.page1.button_despike.setEnabled(False) #self.page1.button_displaycolor.setEnabled(False) self.actionSave.setEnabled(False) else: self.page1.button_i0ffile.setEnabled(True) self.page1.button_i0.setEnabled(True) self.page1.button_artefacts.setEnabled(True) self.page1.button_prenorm.setEnabled(True) self.page1.button_multicrop.setEnabled(True) #self.page1.button_limitev.setEnabled(True) if self.common.stack_4d == 0: self.page1.button_refimgs.setEnabled(True) #self.page1.button_subregion.setEnabled(True) self.page1.button_darksig.setEnabled(True) else: self.page1.button_refimgs.setEnabled(False) #self.page1.button_subregion.setEnabled(False) self.page1.button_darksig.setEnabled(False) self.page0.button_save.setEnabled(True) self.page1.button_save.setEnabled(True) self.page0.pb_copy_img.setEnabled(True) self.page1.pb_copy_img.setEnabled(True) self.page1.pb_copy_specimg.setEnabled(True) self.page1.button_savestack.setEnabled(True) self.page1.button_align.setEnabled(True) self.page1.button_meanflux.setEnabled(True) self.page1.button_slideshow.setEnabled(True) self.page1.button_spectralROI.setEnabled(True) self.page1.button_lockspectrum.setEnabled(True) self.page1.button_clearlastroi.setEnabled(True) self.page1.button_mergeroi.setEnabled(True) self.page1.button_subtractroi.setEnabled(True) self.page1.button_clearspecfig.setEnabled(True) self.page1.ROIShapeBox.setEnabled(True) self.page1.ROIvisibleCheckBox.setEnabled(True) #self.page1.button_resetdisplay.setEnabled(True) #self.page1.button_despike.setEnabled(True) #self.page1.button_displaycolor.setEnabled(True) self.actionSave.setEnabled(True) if self.common.i0_loaded == 0: self.page1.button_showi0.setEnabled(False) self.page1.button_showi0.setChecked(False) #self.page1.rb_flux.setEnabled(False) #self.page1.rb_od.setEnabled(False) #self.page1.button_reseti0.setEnabled(False) #self.page1.button_saveod.setEnabled(False) self.page2.button_calcpca.setEnabled(False) self.page2.button_calcpca4D.setEnabled(False) self.page4.button_loadtspec.setEnabled(False) self.page4.button_addflat.setEnabled(False) self.page5.button_save.setEnabled(False) if self.page7: self.page7.button_calcnnma.setEnabled(False) self.page7.button_mufile.setEnabled(False) self.page7.button_murand.setEnabled(False) if showtomotab: self.page8.button_engdata.setEnabled(False) else: self.page1.button_showi0.setEnabled(True) #self.page1.rb_flux.setEnabled(True) #self.page1.rb_od.setEnabled(True) #self.page1.button_reseti0.setEnabled(True) #self.page1.button_saveod.setEnabled(True) self.page2.button_calcpca.setEnabled(True) self.page2.button_calcpca4D.setEnabled(True) self.page4.button_loadtspec.setEnabled(True) self.page4.button_addflat.setEnabled(True) self.page5.button_save.setEnabled(True) if self.page7: self.page7.button_calcnnma.setEnabled(True) self.page7.button_mufile.setEnabled(True) self.page7.button_murand.setEnabled(True) if showtomotab: self.page8.button_engdata.setEnabled(True) if self.common.pca_calculated == 0: self.page2.button_savepca.setEnabled(False) self.page2.slidershow.setEnabled(False) self.page2.button_movepcup.setEnabled(False) self.page3.button_calcca.setEnabled(False) self.page4.rb_fit.setEnabled(False) else: self.page2.button_savepca.setEnabled(True) self.page2.slidershow.setEnabled(True) self.page2.button_movepcup.setEnabled(True) self.page3.button_calcca.setEnabled(True) self.page4.rb_fit.setEnabled(True) if self.common.cluster_calculated == 0: self.page3.button_scatterplots.setEnabled(False) self.page3.button_savecluster.setEnabled(False) self.page3.slidershow.setEnabled(False) self.page4.button_addclspec.setEnabled(False) self.page5.button_addclspec.setEnabled(False) if self.page7: self.page7.button_mucluster.setEnabled(False) else: self.page3.button_scatterplots.setEnabled(True) self.page3.button_savecluster.setEnabled(True) self.page3.slidershow.setEnabled(True) self.page4.button_addclspec.setEnabled(True) self.page5.button_addclspec.setEnabled(True) if self.page7: self.page7.button_mucluster.setEnabled(True) if self.common.spec_anl_calculated == 0: self.page4.button_removespec.setEnabled(False) self.page4.button_movespdown.setEnabled(False) self.page4.button_movespup.setEnabled(False) self.page4.button_save.setEnabled(False) self.page4.button_showrgb.setEnabled(False) self.page4.button_histogram.setEnabled(False) self.page4.button_calc4d.setEnabled(False) else: self.page4.button_removespec.setEnabled(True) self.page4.button_movespdown.setEnabled(True) self.page4.button_movespup.setEnabled(True) self.page4.button_save.setEnabled(True) self.page4.button_showrgb.setEnabled(True) self.page4.button_histogram.setEnabled(True) self.page4.button_calc4d.setEnabled(True) if showtomotab: if self.common.spec_anl4D_calculated == 0: self.page8.button_spcomp.setEnabled(False) else: self.page8.button_spcomp.setEnabled(True) if self.page6 != None: if self.common.cluster_calculated == 0: self.page6.button_addclspec.setEnabled(False) else: self.page6.button_addclspec.setEnabled(True) if self.common.xpf_loaded == 1: self.page6.slider_spec.setEnabled(True) else: self.page6.slider_spec.setEnabled(False) # ToDo: RestoreResetDisplaysetting functionality # Really needed? #self.page1.ResetDisplaySettings() #----------------------------------------------------------------------- def new_stack_refresh(self): self.common.i0_loaded = 0 self.common.pca_calculated = 0 self.common.cluster_calculated = 0 self.common.spec_anl_calculated = 0 self.common.xpf_loaded = 0 self.common.stack_4d = 0 self.common.pca4D_calculated = 0 self.common.spec_anl4D_calculated = 0 self.refresh_widgets() #page 0 self.page0.slider_theta.setVisible(False) #self.page0.tc_imagetheta.setVisible(False) #page 1 self.page1.button_i0.disconnect() self.page1.button_i0.setText("Select I0") self.page1.button_i0.clicked.connect(self.page1.specfig.OnI0Histogram) #self.page1.rb_flux.setChecked(True) #self.page1.rb_od.setChecked(False) #self.page1.showflux = True #fig = self.page1.specfig #fig.clf() #self.page1.SpectrumPanel.draw() #self.page1.tc_spec.setText("Spectrum at point: ") #fig = self.page1.absimgfig #fig.clf() #self.page1.AbsImagePanel.draw() self.page1.tc_imageeng.setText("Image at energy: ") # self.page1.textctrl.setText(' ') self.page1.ResetDisplaySettings() #page 0 self.page1.slider_theta.setVisible(False) #self.page1.tc_imagetheta.setVisible(False) #page 2 fig = self.page2.pcaevalsfig fig.clf() self.page2.PCAEvalsPan.draw() fig = self.page2.pcaimgfig fig.clf() self.page2.PCAImagePan.draw() fig = self.page2.pcaspecfig fig.clf() self.page2.PCASpecPan.draw() self.page2.vartc.setText('0%') self.page2.npcaspin.setValue(1) self.page2.tc_PCAcomp.setText("PCA component ") self.page2.text_pcaspec.setText("PCA spectrum ") self.page2.selpca = 1 self.page2.numsigpca = 2 self.page2.slidershow.setValue(self.page2.selpca) self.page2.slider_theta.setVisible(False) self.page2.tc_imagetheta.setVisible(False) self.page2.button_calcpca4D.setVisible(False) #page 3 fig = self.page3.clusterimgfig fig.clf() self.page3.ClusterImagePan.draw() fig = self.page3.clusterindvimgfig fig.clf() self.page3.ClusterIndvImagePan.draw() fig = self.page3.clusterspecfig fig.clf() self.page3.ClusterSpecPan.draw() fig = self.page3.clusterdistmapfig fig.clf() self.page3.ClusterDistMapPan.draw() self.page3.selcluster = 1 self.page3.slidershow.setValue(self.page3.selcluster) self.page3.numclusters = 5 self.page3.nclusterspin.setValue(self.page3.numclusters) self.page3.tc_cluster.setText("Cluster ") self.page3.tc_clustersp.setText("Cluster spectrum") self.page3.wo_1st_pca = 0 self.page3.remove1stpcacb.setChecked(False) #page 4 self.page4.ClearWidgets() #page 5 fig = self.page5.kespecfig fig.clf() self.page5.KESpecPan.draw() fig = self.page5.absimgfig fig.clf() self.page5.AbsImagePanel.draw() #page 6 if self.page6 != None: fig = self.page6.Specfig fig.clf() self.page6.SpectrumPanel.draw() self.page6.slider_spec.setEnabled(False) #page8 if showtomotab: self.page8.NewStackClear() # ---------------------------------------------------------------------- def main(): app = QtWidgets.QApplication(sys.argv) #print('main', threading.get_ident()) with open(qsspath, "r") as stylesheet: app.setStyleSheet(stylesheet.read()) frame = MainFrame() sys.exit(app.exec_()) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/nnma.py0000664000175000017500000002054614700316175017060 0ustar00wattswattsimport numpy as np import scipy as sp try: from scipy.integrate import trapezoid except ImportError: # Fallback for scipy < 1.6 from scipy.integrate import trapz as trapezoid import sys, time from . import analyze class nnma(): def __init__(self, stkdata): self.stack = stkdata # Set default values for various NNMA parameters self.kNNMA = 5 # use no. of significant PCA components, otherwise default = 5 self.maxIters = 10 # default no. of iterations self.deltaErrorThreshold = 1e-3 # default threshold for change in reconstruction error self.initMatrices = 'Random' # default initialization for matrices self.lambdaSparse = 0. # sparseness regularization param self.lambdaClusterSim = 0. # cluster spectra similarity regularization param self.lambdaSmooth = 0. # smoothness regularization param # Fill initial matrices depending on user-specified initialization method def setParameters(self, kNNMA = 5, maxIters = 10, deltaErrorThreshold = 1e-3, initMatrices = 'Random', lambdaSparse = 0., lambdaClusterSim = 0., lambdaSmooth = 0.): # Set the values for various NNMA parameters self.kNNMA = kNNMA self.maxIters = maxIters self.deltaErrorThreshold = deltaErrorThreshold self.initMatrices = initMatrices self.lambdaSparse = lambdaSparse self.lambdaClusterSim = lambdaClusterSim self.lambdaSmooth = lambdaSmooth def setClusterSpectra(self, clusterspectra): self.clusterspectra = clusterspectra.T.copy() def setStandardsSpectra(self, standspectra): self.standspectra = standspectra #---------------------------------------------------------------------------------------- # Define some helper functions for NNMA #---------------------------------------------------------------------------------------- # Fill initial matrices depending on user-specified initialization method def fillInitMatrices(self): print ('Init method:', self.initMatrices) if self.initMatrices == 'Random': muInit = np.random.rand(self.nEnergies, self.kNNMA) tInit = np.random.rand(self.kNNMA, self.nPixels) elif self.initMatrices == "Cluster": muInit = self.clusterspectra tInit = np.random.rand(self.kNNMA, self.nPixels) # use SVD on mu to find t instead? elif self.initMatrices == "Standards": muInit = self.standspectra tInit = np.random.rand(self.kNNMA, self.nPixels) # use SVD on mu to find t instead? return muInit, tInit # Update t def tUpdate(self, mu, t): tUpdateFactor = np.dot(mu.T, self.OD) / ( np.dot(mu.T, np.dot(mu, t)) + self.lambdaSparse + 1e-9 ) tUpdated = t * tUpdateFactor return tUpdated # Update mu def muUpdate(self, mu, tUpdated): dCostSmooth_dMu = self.calcDCostSmooth_dMu(mu) muDiff = mu - self.muCluster muUpdateFactor = np.dot(self.OD, tUpdated.T) / ( np.dot(mu, np.dot(tUpdated, tUpdated.T)) + self.lambdaSmooth*dCostSmooth_dMu + self.lambdaClusterSim*2*muDiff + 1e-9 ) muUpdated = mu * muUpdateFactor # Normalize each column of mu return muUpdated # Calculate sparseness contribution (JSparse) to cost function def calcCostSparse(self, t): costSparse = np.sum(np.sum(np.abs(t))) return costSparse # Calculate cluster spectra similarity contribution (JClusterSim) to cost function def calcCostClusterSim(self, mu): muDiff = mu - self.muCluster costClusterSim = (np.linalg.norm(muDiff))**2 return costClusterSim # Calculate smoothness contribution (JSmooth) to cost function def calcCostSmooth(self, mu): return 0. # Calculate dJSmooth/dMu needed in mu update algorithm def calcDCostSmooth_dMu(self, mu): return 0. # Calculate integral of each column in mu for normalization def calcMuColNorm(self, mu, muRefNorm): for k in range(self.kNNMA): muNorm = trapezoid(mu[:, k], x=self.energies) mu[:, k] = (mu[:, k] / muNorm) * muRefNorm[k] return mu # Calculate current total cost function, fill cost function array def calcCostFn(self, mu, t, count): D = np.dot(mu, t) costDataMatch = 0.5 * (np.linalg.norm(self.OD - D))**2 costSparse = self.calcCostSparse(t) costClusterSim = self.calcCostClusterSim(mu) costSmooth = self.calcCostSmooth(mu) costTotal = ( costDataMatch + self.lambdaSparse*costSparse + self.lambdaClusterSim*costClusterSim + self.lambdaSmooth*costSmooth ) if count > 0: deltaError = self.costFnArray[count, 1] - self.costFnArray[count-1, 1] elif count == 0: deltaError = -1e-13 self.costFnArray[count, :] = np.array([costTotal, deltaError, costSparse, costClusterSim, costSmooth]) return costTotal, deltaError #---------------------------------------------------------------------------------------- # Calculate NNMA #---------------------------------------------------------------------------------------- def calcNNMA(self, initmatrices = 'Random'): print ('calculating nnma') self.initMatrices = initmatrices self.OD = self.stack.od.copy() self.energies = self.stack.ev self.nEnergies = self.stack.n_ev self.nCols = self.stack.n_cols self.nRows = self.stack.n_rows self.nPixels = self.nCols * self.nRows # Transpose optical density matrix since NNMA needs dim NxP self.OD = self.OD.T # Zero out negative values in OD matrix negInd = np.where(self.OD < 0.) if negInd: self.OD[negInd] = 0. self.muRecon = np.zeros((self.nEnergies, self.kNNMA)) self.tRecon = np.zeros((self.kNNMA, self.nPixels)) self.DRecon = np.zeros((self.nEnergies, self.nPixels)) self.costFnArray = np.zeros((self.maxIters+1, 5)) self.costFnArray[0, 0] = 1e99 self.costTotal = 0. # stores current value of total cost function self.deltaError = 0 # stores current value of cost function change # If doing cluster spectra similarity regularization, import cluster spectra if initmatrices == 'Cluster': self.muCluster = self.clusterspectra else: self.muCluster = 0. self.timeTaken = 0. # stores time taken to complete NNMA analysis # Initialize matrices muInit, tInit = self.fillInitMatrices() self.muinit = muInit.copy() muCurrent = muInit tCurrent = tInit count = 0 costCurrent, deltaErrorCurrent = self.calcCostFn(muCurrent, tCurrent, count) # Start NNMA startTime = time.time() while ((count < self.maxIters) and (self.deltaError < self.deltaErrorThreshold)): # Store values from previous iterations before updating self.tRecon = tCurrent self.muRecon = muCurrent self.costTotal = costCurrent self.deltaError = deltaErrorCurrent # Now do NNMA update tUpdated = self.tUpdate(muCurrent, tCurrent) muUpdated = self.muUpdate(muCurrent, tUpdated) # Zero out any negative values in t and mu negIndT = np.where(tUpdated < 0.) if negIndT: tUpdated[negIndT] = 0. negIndMu = np.where(muUpdated < 0.) if negIndMu: muUpdated[negIndMu] = 0. tCurrent = tUpdated muCurrent = muUpdated # Calculate cost function costCurrent, deltaErrorCurrent = self.calcCostFn(muCurrent, tCurrent, count) count = count + 1 print ('Iteration number {0}/{1}'.format(count,self.maxIters)) endTime = time.time() self.timeTaken = endTime - startTime if count < self.maxIters: self.maxIters = count self.tRecon = np.reshape(self.tRecon, (self.kNNMA, self.nCols, self.nRows), order='F') return 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/pageloaddata.ui0000775000175000017500000010255614700316175020527 0ustar00wattswatts Form 0 0 1024 880 0 0 Form 0 0 12 6 0 0 260 0 Ubuntu 8 12 QLayout::SetDefaultConstraint 1 1 1 250 0 250 16777215 Ubuntu 8 Load Data QLayout::SetMinimumSize Ubuntu 8 Load XANES Stack Ubuntu 8 Load 4D Stack TOMO-XANES 250 0 250 16777215 Ubuntu 8 Build a stack from a set of files QLayout::SetMinimumSize 0 0 Ubuntu 8 Select a directory [.sm, .xrm, .tif] Ubuntu 8 Qt::Vertical 20 40 0 0 Ubuntu 8 Preview 0 0 0 0 0 QLayout::SetDefaultConstraint 0 0 0 0 0 Ubuntu 8 background: white 0 0 0 0 0 0 Ubuntu 8 false Qt::Vertical false Ubuntu 8 Qt::Horizontal 0 0 6 1 1 1 1 0 QLayout::SetDefaultConstraint 0 0 0 0 16777215 70 Ubuntu 8 File 0 8 0 6 0 Ubuntu 8 IBeamCursor TextLabel Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse 0 0 16777215 70 Ubuntu 8 Path 0 8 0 6 0 true 0 25 Ubuntu 8 IBeamCursor TextLabel Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse 0 0 16777215 150 Ubuntu 8 Transform 0 0 60 25 Qt::LeftToRight images/rotate.pngimages/rotate.png 16 16 60 25 false images/mirror.pngimages/mirror.png 16 16 0 0 270 116 270 116 Ubuntu 8 Display Options 9 9 9 0 0 0 0 0 23 16777215 23 Ubuntu 8 Colormap Category Colormap Steps 6 0 0 100 0 120 16777215 Ubuntu 8 Ubuntu 8 40 16777215 Ubuntu 8 2 256 256 10 6 5 80 0 80 16777215 Ubuntu 8 Metric scale true 100 0 100 16777215 Ubuntu 8 Origin to zero true 100 0 100 16777215 Ubuntu 8 false Square px true false false false false true Scalebar Ubuntu 8 Qt::Horizontal 40 20 true 0 0 220 116 220 116 Ubuntu 8 Export QLayout::SetDefaultConstraint 0 15 5 0 24 Save... true 0 0 0 24 Image false 0 0 0 15 Copy to Clipboard Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Export dialog true 0 0 0 24 Spectrum Qt::Vertical 20 40 GraphicsLayoutWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/pagemap.ui0000755000175000017500000016755414332463747017553 0ustar00wattswatts Jan-David Förster Form 0 0 900 750 0 0 Form 0 0 250 0 250 16777215 Ubuntu 8 Qt::ClickFocus QTabWidget::North QTabWidget::Rounded 0 0 0 Map Creator 0 0 100 0 Ubuntu 8 Select by Left & Right Click Image # Energy [eV] Δx [px] Δy [px] 0 2 2 2 2 0 0 16777215 16777207 85 255 255 0 0 0 85 255 255 0 0 0 51 153 255 255 255 255 Ubuntu 8 Qt::NoFocus QFrame::NoFrame QFrame::Plain 0 0 Qt::ScrollBarAlwaysOff QAbstractItemView::NoEditTriggers false QAbstractItemView::NoDragDrop Qt::CopyAction false QAbstractItemView::NoSelection QAbstractItemView::SelectItems QListView::Static false QListView::ListMode false true 3 3 3 3 false 0 0 0 20 Ubuntu 8 Select from Spectrum false 0 0 0 20 Ubuntu 8 Clear selection true 1 1 0 0 0 0 Ubuntu 8 Alignment Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter false false 9 0 0 0 6 0 0 0 0 0 120 6 0 0 0 0 0 0 0 20 Ubuntu 8 Crop true 0 0 50 35 50 35 Ubuntu 8 Clear all shifts Qt::Vertical 20 40 0 0 150 150 150 150 Ubuntu 8 QFrame::StyledPanel QFrame::Sunken 1 0 0 0 0 0 0 3 1 3 0 4 1 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus false 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus false false false 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 Ubuntu 11 Qt::ClickFocus 0 0 20 20 Ubuntu 10 Qt::ClickFocus 0 0 20 20 0 0 Ubuntu 10 Qt::ClickFocus 1 1 0 0 16777215 16777215 Ubuntu 8 Display Options 16777215 15 Ubuntu 8 Colormap Category Colormap Steps 0 0 0 0 100 16777215 Ubuntu 8 Ubuntu 8 40 16777215 Ubuntu 8 2 256 256 10 1 80 0 80 16777215 Ubuntu 8 Metric scale true 100 0 100 16777215 Ubuntu 8 Origin to zero true 100 0 100 16777215 Ubuntu 8 false Force square px true false false false false Ubuntu 8 Qt::Horizontal 40 20 6 QLayout::SetDefaultConstraint 0 QLayout::SetDefaultConstraint 6 0 false 0 18 60 16777215 Ubuntu 8 3 -99.989999999999995 0.010000000000000 false 0 20 60 16777215 Ubuntu 8 3 -99.989999999999995 0.010000000000000 0 20 Ubuntu 8 Lower OD limit 0 20 Ubuntu 8 Upper OD limit false 0 0 20 20 Ubuntu 8 Qt::LeftToRight Reset OD limits Qt::Horizontal true 40 16777215 Ubuntu 8 1 Uniform filter size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 120 Ubuntu 8 Export false Ubuntu 8 OD Data (*.tif, *.txt) false Ubuntu 8 OD Image (*.svg,*.tif,*.png*.jpg) Copy to Clipboard (Ctrl+C) 0 0 Ubuntu 8 false QFrame::StyledPanel QFrame::Raised 1 0 6 9 0 0 Ubuntu 8 background: white 0 0 Ubuntu 8 Qt::Vertical GraphicsView QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/pagestack.ui0000664000175000017500000012024214700316175020050 0ustar00wattswatts Form 0 0 1024 880 0 0 Form 0 0 12 QLayout::SetDefaultConstraint 6 0 0 260 620 Ubuntu 8 12 QLayout::SetDefaultConstraint 9 1 1 1 0 0 250 0 250 16777215 Ubuntu 8 Preprocess 6 QLayout::SetMinimumSize Ubuntu 8 Align stack... Ubuntu 8 Crop stack 3D/4D... Artifacts && Leveling Dark signal substraction Save processed stack 0 0 250 0 250 16777215 Ubuntu 8 Normalize 6 QLayout::SetMinimumSize 0 0 0 24 Ubuntu 8 Select I0 0 24 16777215 24 Show I0 true false 0 24 I0 from file... 0 24 Use pre-normalized data 0 24 Load reference images [.xrm] 0 0 250 0 250 16777215 Ubuntu 8 Region of Interest 6 QLayout::SetMinimumSize ROI dose calculation Spectral ROI Qt::Vertical QSizePolicy::MinimumExpanding 20 0 0 0 Ubuntu 8 Image Stack 0 QLayout::SetMaximumSize 0 0 0 0 QLayout::SetDefaultConstraint 0 0 0 Ubuntu 8 Qt::Horizontal 0 0 Ubuntu 8 background: white 0 0 0 0 0 0 Ubuntu 8 false Qt::Vertical false 0 0 Ubuntu 8 Spectrum 0 0 0 0 0 0 0 0 0 6 QLayout::SetDefaultConstraint 1 1 1 1 Qt::Horizontal 40 20 0 0 120 0 16777215 116 Ubuntu 8 Image Stack Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft 6 QLayout::SetDefaultConstraint 8 3 6 0 6 0 24 16777215 24 Show mean true 0 24 16777215 24 Play stack movie Qt::Vertical QSizePolicy::Fixed 20 32 0 0 120 0 16777215 116 Ubuntu 8 Spectrum Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft 6 QLayout::SetDefaultConstraint 9 9 9 9 0 0 0 18 16777215 10 ROI visible true 0 18 16777215 10 ROI type true 0 24 16777215 24 Lock to ROI Merge ROI 0 24 Clear last ROI true 0 24 16777215 24 Clear all ROI Subtract ROI 0 0 270 0 270 116 Ubuntu 8 Display Options Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft 9 9 0 0 0 23 16777215 23 Ubuntu 8 Colormap Category Colormap Steps 6 0 0 100 0 120 16777215 Ubuntu 8 Ubuntu 8 40 16777215 Ubuntu 8 2 256 256 10 6 5 80 0 80 16777215 Ubuntu 8 false Square px true false false false false Scalebar Ubuntu 8 Qt::Horizontal 40 10 0 0 220 116 220 116 Ubuntu 8 Export Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft 6 9 9 9 9 QLayout::SetDefaultConstraint 0 15 5 0 24 Save... true 0 0 0 24 Image false 0 0 0 15 Copy to Clipboard Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter Export dialog true 0 0 0 24 Spectrum Qt::Vertical 20 40 GraphicsLayoutWidget QGraphicsView
pyqtgraph
PlotWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/showalign2.ui0000664000175000017500000006365614700316175020202 0ustar00wattswatts Jan-David Förster Alignment 0 0 913 500 Form 0 0 100 100 Ubuntu 8 Stack Browser 0 0 0 0 0 0 0 Ubuntu 8 Ubuntu 8 Qt::Vertical Qt::Horizontal 0 0 Ubuntu 8 Drifts 0 0 0 0 0 Ubuntu 8 0 6 6 0 0 0 Ubuntu 8 Accept Ubuntu 8 Cancel Ubuntu 8 Displacement approximation && refinements 0 0 0 0 0 10 0 Auto-crop stack true Fit extrapolation false Size: 0 25 16777215 25 Method: QFrame::Plain Qt::Vertical 50 16777215 1 1 Moving average Linear regression QFrame::Plain Qt::Vertical 0 QLayout::SetNoConstraint 0 0 16777215 25 Auto quality filter false 0 0 16777215 16777215 | Threshold: 60 16777215 4 1.000000000000000 0.002000000000000 1.000000000000000 Qt::Horizontal 40 20 0 0 6 0 0 16777215 120 Ubuntu 8 Pre-alignment settings 0 0 0 0 0 10 0 6 0 25 60 25 1 0.500000000000000 1.000000000000000 Qt::Horizontal QSizePolicy::Maximum 5 20 0 25 16777215 25 (Gaussian sigma) 0 0 95 0 Filter preview true 0 0 0 18 16777215 25 Noise ruggedness: 0 0 0 18 16777215 25 Correlation anchor: QLayout::SetDefaultConstraint 0 0 0 25 200 16777215 reference image true true 0 25 16777215 16777215 neighbor false Qt::Horizontal 40 20 high accuracy true edge detection true QFrame::Plain Qt::Vertical QFrame::Plain Qt::Vertical 6 8 Reset alignment && ROI Ubuntu 8 Reset alignment true Ubuntu 8 Align GraphicsView QGraphicsView
pyqtgraph
GraphicsLayoutWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/showartefacts.ui0000775000175000017500000004275114700316175020776 0ustar00wattswatts Artefacts 0 0 900 500 Form Accept Cancel 0 0 100 100 Ubuntu 8 Stack Browser 0 0 0 0 0 Ubuntu 8 Keep left mouse button pressed to toggle view Qt::Vertical 0 0 250 16777215 Ubuntu 8 Artefacts && Leveling Ubuntu 8 Background leveling true false 6 0 0 0 20 16777215 20 Ubuntu 8 Method: 0 15 16777215 15 Ubuntu 8 Median true 16777215 15 Ubuntu 8 Median (I0 masked) false 0 20 16777215 20 Ubuntu 8 Direction: 0 15 16777215 15 Ubuntu 8 Horizontal false 0 15 16777215 15 Ubuntu 8 Vertical Ubuntu 8 0 0 0 0 0 Ubuntu 8 Textlabel 8 Ubuntu 8 100 1 100 Qt::Horizontal 30 0 30 16777215 Ubuntu 8 TextLabel Qt::Horizontal Ubuntu 8 Remove outliers true false Ubuntu 8 0 0 0 0 0 Ubuntu 8 Textlabel 8 Ubuntu 8 100 1 20 Qt::Horizontal Qt::Vertical 20 40 GraphicsView QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/showmulticrop.ui0000664000175000017500000005402614700316175021033 0ustar00wattswatts MultiLimit 0 0 1024 500 Form Qt::Horizontal 0 0 100 100 Ubuntu 8 Stack Browser 0 0 0 0 0 0 Ubuntu 8 Energy range: 140 0 140 16777215 Ubuntu 8 Accept 140 0 140 16777215 Ubuntu 8 Reset ROI Ubuntu 8 Stack size: 0 0 Ubuntu 8 Spectrum inside ROI 0 0 0 0 0 140 0 16777215 16777215 Ubuntu 8 Cancel 0 Qt::Vertical QSizePolicy::Maximum 8 15 Ubuntu 8 Keep left mouse button pressed to toggle view Qt::Vertical Ubuntu 8 Theta range: 0 0 16777215 16777215 Qt::LeftToRight QLayout::SetNoConstraint 1 0 0 0 0 0 16777215 16777215 Ubuntu 8 Image # Energy [eV] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 2 2 2 2 0 0 16777215 16777215 85 255 255 0 0 0 85 255 255 0 0 0 51 153 255 255 255 255 Ubuntu 8 Qt::NoFocus QFrame::NoFrame QFrame::Plain 0 0 Qt::ScrollBarAlwaysOff QAbstractItemView::NoEditTriggers false QAbstractItemView::NoDragDrop Qt::CopyAction false QAbstractItemView::NoSelection QAbstractItemView::SelectItems QListView::Static false QListView::ListMode false true 0 0 16777215 16777215 Ubuntu 8 Angle # Theta [°] Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 2 2 2 2 0 0 QFrame::NoFrame QFrame::Plain 0 Qt::ScrollBarAlwaysOff QAbstractItemView::NoEditTriggers false QAbstractItemView::NoSelection true 0 0 0 Ubuntu 8 Select all 0 0 Ubuntu 8 Clear all 0 0 Ubuntu 8 OD values per px true 0 0 Ubuntu 8 Crop stack to ROI true 0 0 Ubuntu 8 Remove unsel. energies true 0 0 Ubuntu 8 Remove unsel. angles true GraphicsView QGraphicsView
pyqtgraph
PlotWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/showodmap.ui0000664000175000017500000006305714700316175020121 0ustar00wattswatts Jan-David Förster SpectralROI 0 0 1200 750 0 0 Form 0 0 Ubuntu 8 false QFrame::StyledPanel QFrame::Raised 1 0 0 0 0 0 0 0 0 Ubuntu 8 background: white Qt::Vertical 0 0 285 0 285 16777215 Ubuntu 8 Qt::ClickFocus 0 0 100 0 Ubuntu 8 Select by Left & Right Click Image # Energy [eV] Δx [px] Δy [px] 0 2 2 2 2 0 0 16777215 16777207 85 255 255 0 0 0 85 255 255 0 0 0 51 153 255 255 255 255 Ubuntu 8 Qt::NoFocus QFrame::NoFrame QFrame::Plain 0 0 Qt::ScrollBarAlwaysOn Qt::ScrollBarAlwaysOff QAbstractItemView::NoEditTriggers false QAbstractItemView::NoDragDrop Qt::CopyAction false QAbstractItemView::NoSelection QAbstractItemView::SelectItems QListView::Static false QListView::ListMode false true 3 3 3 3 false 0 0 0 20 Ubuntu 8 Select from Spectrum false 0 0 0 20 Ubuntu 8 Clear selection 1 1 0 0 16777215 16777215 Ubuntu 8 Display Options 16777215 15 Ubuntu 8 Colormap Category Colormap Steps 0 0 0 0 100 16777215 Ubuntu 8 Ubuntu 8 40 16777215 Ubuntu 8 2 256 256 10 1 80 0 80 16777215 Ubuntu 8 Metric scale true 100 0 100 16777215 Ubuntu 8 Origin to zero true 100 0 100 16777215 Ubuntu 8 false Square px true false false false false Scalebar Ubuntu 8 Qt::Horizontal 40 20 6 QLayout::SetDefaultConstraint 0 false 0 0 20 20 Ubuntu 8 Qt::LeftToRight Reset OD scaling Qt::Horizontal Lee filter (speckle noise) true Uniform filter (mean) size: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true 40 16777215 Ubuntu 8 1 0 0 0 120 Ubuntu 8 Export false Ubuntu 8 OD Data (*.tif, *.txt) false Ubuntu 8 OD Image (*.svg,*.tif,*.png*.jpg) Copy to Clipboard (Ctrl+C) GraphicsLayoutWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/showspectralroi.ui0000664000175000017500000000607114700316175021341 0ustar00wattswatts SpectralMap 0 0 600 500 Form 0 0 Ubuntu 8 ROI selection from Spectrum 0 0 0 0 0 0 Qt::Horizontal 40 20 140 0 16777215 16777215 Ubuntu 8 Close PlotWidget QGraphicsView
pyqtgraph
././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1667917799.0 mantis_xray-3.2.2/mantis_xray/stylesheet_global.qss0000644000175000017500000000054014332463747022015 0ustar00wattswattsQPushButton { font: 11px "Ubuntu"; } QTabWidget { font: 11px "Ubuntu"; } QGroupBox { font: 11px "Ubuntu"; } QComboBox { font: 11px "Ubuntu"; } QCheckBox { font: 11px "Ubuntu"; } QLabel { font: 11px "Ubuntu"; } QSpinBox { font: 11px "Ubuntu"; } QDoubleSpinBox { font: 11px "Ubuntu"; } QWidget { font: 11px "Ubuntu"; }././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/mantis_xray/tomo_reconstruction.py0000664000175000017500000004433114700316175022244 0ustar00wattswatts# # This file is part of Mantis, a Multivariate ANalysis Tool for Spectromicroscopy. # # Copyright (C) 2015 Mirna Lerotic, 2nd Look # http://2ndlookconsulting.com # License: GNU GPL v3 # # Mantis 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 # any later version. # # Mantis 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 . from __future__ import division from __future__ import absolute_import import os import numpy as np import scipy as sp from time import time import multiprocessing from functools import partial try: from skimage.transform import iradon, radon except: print ('SIRT reconstruction requires skimage library.') from .TomoCS.forward_backward_tv import fista_tv,gfb_tv, gfb_tv_weng from .TomoCS.projections import build_projection_operator from .TomoCS.util import generate_synthetic_data from .TomoCS import sirt as st from . import Mrc #----------------------------------------------------------------------- def write_mrc(stack, mrcfn): #stackf32 = stack.astype(np.float32) Mrc.save(stack, mrcfn,ifExists='overwrite',calcMMM=False) #----------------------------------------------------------------------- def load_mrc( mrcfn): stack = Mrc.load(mrcfn) return stack #----------------------------------------------------------------------- def calc_sirt(R, theta, Rmin, Rmax, nonnegconst, maxiter, nrows): R = (R-Rmin)/(Rmax-Rmin) S1 = np.sum(R) At = iradon(R, theta=theta, output_size=nrows) S2 = np.sum(At) At = (At/S2)*S1 xk = At for k in range(maxiter): t = iradon(radon(xk,theta),theta) #normalize St = np.sum(t) t = (t/St)*S1 #update using (At g - At A x_k) #new xk = xk + difference between reconstruction At_starting - t_previuous_step xk = xk + At - t if nonnegconst == 1: #delete values <0 aka not real! xk = xk.clip(min=0) return xk #----------------------------------------------------------------------- def calc_cs(R, theta, Rmin, Rmax, l, beta, initx0, nonnegconst, maxiter): R = (R-Rmin)/(Rmax-Rmin) proj = R.ravel()[:, np.newaxis] proj_operator = build_projection_operator(l, n_dir=len(theta), angles=theta) res, engs = gfb_tv(proj, beta, maxiter, H=proj_operator, x0=initx0, nonnegconst=nonnegconst) return res[-1] #----------------------------------------------------------------------- class Ctomo: def __init__(self, stkdata): self.stack = stkdata self.tomorec = [] #----------------------------------------------------------------------- # Calculate tomo reconstruction # Algorithm = 0 : CS reconstruction for 1 dataset # Algorithm = 1 : SIRT reconstruction for 1 dataset def calc_tomo(self, tomodata, theta, maxiter, beta, samplethickness, algorithm = 0, x0=[], comp = 0, beta2 = 0, nonnegconst = 1, nprocessors=1): tomodata = tomodata #Algorithm if algorithm == 0: if nprocessors <= 1: self.calc_tomo_cs(tomodata.astype(np.float32), theta, maxiter, beta, samplethickness, nonnegconst = nonnegconst) else: self.calc_tomo_cs_multi(tomodata.astype(np.float32), theta, maxiter, beta, samplethickness, nonnegconst = nonnegconst, nprocessors=nprocessors) elif algorithm == 1: self.calc_tomo_sirt(tomodata.astype(np.float32), theta, maxiter, beta, samplethickness, nonnegconst = nonnegconst, nprocessors=nprocessors) else: self.calc_tomo_cs_tveng(tomodata.astype(np.float32), theta, maxiter, beta, samplethickness, x0, comp, beta2, nonnegconst = nonnegconst) #----------------------------------------------------------------------- # Calculate tomo - CS reconstruction for 1 dataset with multiprocessing def calc_tomo_cs_multi(self, tomodata, theta, maxiter, beta, samplethickness, nonnegconst=1, nprocessors=6): print ('Compressed sensing TV regression') #print ('Angles ', theta) print ('TV beta ', beta) print ('MAX iterations ', maxiter) print ("Sample thickness ", samplethickness) #Check if we have negative angles, if yes convert to 0-360deg for i in range(len(theta)): if theta[i] < 0: theta[i] = theta[i] + 360 #print ('Angles in 0-360 range: ', theta) nang = len(theta) #print ('Number of angles ', nang) dims = tomodata.shape print ('Dimensions ', dims, tomodata.dtype) stack = np.swapaxes(tomodata, 0, 1) theta = np.deg2rad(theta) dims = stack.shape ncols = dims[0] nrows = dims[1] l = dims[1] Rmax = np.amax(stack) Rmin = np.amin(stack) recondata = [] projections = [] initx0 = np.zeros((nrows,nrows), dtype=np.float32) t1 = time() R = stack[int(ncols/2),:, :].T R = (R-Rmin)/(Rmax-Rmin) proj = R.ravel()[:, np.newaxis] l = dims[1] proj_operator = build_projection_operator(l, n_dir=len(theta), angles=theta) res, engs = gfb_tv(proj, beta, maxiter, H=proj_operator, x0=initx0, nonnegconst = nonnegconst) initx0 = res[-1] for j in range(ncols): projections.append(stack[j,:, :].T) Rmax = np.amax(stack) Rmin = np.amin(stack) partial_calc_cs = partial(calc_cs, theta=theta, Rmin=Rmin, Rmax=Rmax, l=l, beta=beta, initx0=initx0, nonnegconst=nonnegconst, maxiter=maxiter) pool = multiprocessing.Pool(processes=nprocessors) res = pool.map(partial_calc_cs, projections) pool.close() pool.join() recondata = res t2 = time() print ("reconstruction done in %f s" %(t2 - t1)) #Save the 3D tomo reconstruction to a HDF5 file recondata=np.array(recondata) dims = recondata.shape print ('final dims', dims) #Crop the data is sample thickness is defined if (samplethickness > 0) and (samplethickness 0) and (samplethickness 0) and (samplethickness 0) and (samplethickness 0) and (samplethickness=3 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: PyQt5>=5.15.9 Requires-Dist: numpy Requires-Dist: scipy>=1.14.0 Requires-Dist: matplotlib>=3.6.0 Requires-Dist: h5py Requires-Dist: Pillow Requires-Dist: lxml Requires-Dist: pyqtgraph>=0.13.7 Requires-Dist: scikit-image>=0.19.1 Provides-Extra: netcdf Requires-Dist: netcdf4-python; extra == "netcdf" # Spectromicroscopy # [Spectromicroscopy](http://spectromicroscopy.com) combines spectral data with microscopy, where typical datasets consist of a stack of microscopic images taken across an energy range. Due to the data complexity, manual analysis can be time consuming and inefficient, whereas multivariate analysis tools not only reduce the time needed but also can uncover hidden trends in the data. # Mantis # [MANTiS](http://spectromicroscopy.com) is Multivariate ANalysis Tool for Spectromicroscopy developed in Python by [2nd Look Consulting](http://2ndlookconsulting.com). It uses principal component analysis and cluster analysis to classify pixels according to spectral similarity. ## Download ## Mantis package and binaries can be downloaded from [spectromicroscopy.com](http://spectromicroscopy.com). Alternatively, you can install [Python](https://www.python.org/downloads/) and then run the command: `python3 -m pip install mantis-xray` ## Update ## You can upgrade to the latest package release with the command: `pip3 install mantis-xray -U`. It is recommended that you also upgrade the dependencies with: `pip3 install mantis-xray -U --upgrade-strategy "eager"` ## Run ## Installation via pip provides the `mantis-xray` command (alternatively `python3 -m mantis_xray`) to start the Mantis GUI. ## User Guide ## Mantis User Guide can be found at [https://docs.spectromicroscopy.com/](https://docs.spectromicroscopy.com/). ## References ## Please use the following reference when quoting Mantis Lerotic M, Mak R, Wirick S, Meirer F, Jacobsen C. MANTiS: a program for the analysis of X-ray spectromicroscopy data. J. Synchrotron Rad. 2014 Sep; 21(5); 1206–1212 [http://dx.doi.org/10.1107/S1600577514013964] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728159324.0 mantis_xray-3.2.2/mantis_xray.egg-info/SOURCES.txt0000664000175000017500000000365714700317134021133 0ustar00wattswattsLICENSE MANIFEST.in README.md setup.py mantis_xray/Mantis_batch_settings.txt mantis_xray/Mrc.py mantis_xray/__init__.py mantis_xray/__main__.py mantis_xray/analyze.py mantis_xray/data_stack.py mantis_xray/data_struct.py mantis_xray/dialogalign.ui mantis_xray/dialogsave.ui mantis_xray/helpers.py mantis_xray/henke.py mantis_xray/henke.xdr mantis_xray/logos.py mantis_xray/mantis.py mantis_xray/mantis_qt.py mantis_xray/nnma.py mantis_xray/pageloaddata.ui mantis_xray/pagemap.ui mantis_xray/pagestack.ui mantis_xray/showalign2.ui mantis_xray/showartefacts.ui mantis_xray/showmulticrop.ui mantis_xray/showodmap.ui mantis_xray/showspectralroi.ui mantis_xray/stylesheet_global.qss mantis_xray/tomo_reconstruction.py mantis_xray.egg-info/PKG-INFO mantis_xray.egg-info/SOURCES.txt mantis_xray.egg-info/dependency_links.txt mantis_xray.egg-info/entry_points.txt mantis_xray.egg-info/requires.txt mantis_xray.egg-info/top_level.txt mantis_xray/TomoCS/__init__.py mantis_xray/TomoCS/_rank_order.py mantis_xray/TomoCS/forward_backward_tv.py mantis_xray/TomoCS/projections.py mantis_xray/TomoCS/sirt.py mantis_xray/TomoCS/tv_denoising.py mantis_xray/TomoCS/util.py mantis_xray/file_plugins/__init__.py mantis_xray/file_plugins/file_bim.py mantis_xray/file_plugins/file_csv.py mantis_xray/file_plugins/file_dataexch_hdf5.py mantis_xray/file_plugins/file_json.py mantis_xray/file_plugins/file_ncb.py mantis_xray/file_plugins/file_nexus_hdf5.py mantis_xray/file_plugins/file_sdf.py mantis_xray/file_plugins/file_sm_netcdf.py mantis_xray/file_plugins/file_stk.py mantis_xray/file_plugins/file_tif.py mantis_xray/file_plugins/file_xrm.py mantis_xray/images/Mantis_logo.icns mantis_xray/images/Mantis_logo.ico mantis_xray/images/Mantis_logo_about.png mantis_xray/images/document-open.png mantis_xray/images/help-browser.png mantis_xray/images/logo-2l-32.ico mantis_xray/images/media-floppy.png mantis_xray/images/mirror.png mantis_xray/images/open-sl.png mantis_xray/images/rotate.png././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728159324.0 mantis_xray-3.2.2/mantis_xray.egg-info/dependency_links.txt0000664000175000017500000000000114700317134023302 0ustar00wattswatts ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728159324.0 mantis_xray-3.2.2/mantis_xray.egg-info/entry_points.txt0000664000175000017500000000006714700317134022535 0ustar00wattswatts[gui_scripts] mantis-xray = mantis_xray.mantis_qt:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728159324.0 mantis_xray-3.2.2/mantis_xray.egg-info/requires.txt0000664000175000017500000000020514700317134021631 0ustar00wattswattsPyQt5>=5.15.9 numpy scipy>=1.14.0 matplotlib>=3.6.0 h5py Pillow lxml pyqtgraph>=0.13.7 scikit-image>=0.19.1 [netCDF] netcdf4-python ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728159324.0 mantis_xray-3.2.2/mantis_xray.egg-info/top_level.txt0000664000175000017500000000001414700317134021761 0ustar00wattswattsmantis_xray ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1728159324.0486972 mantis_xray-3.2.2/setup.cfg0000664000175000017500000000004614700317134015025 0ustar00wattswatts[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1728158845.0 mantis_xray-3.2.2/setup.py0000664000175000017500000000243014700316175014721 0ustar00wattswattsimport setuptools import mantis_xray with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() setuptools.setup( name="mantis-xray", version=mantis_xray.__version__, author="Mirna Lerotic", author_email="mirna@2ndlookconsulting.com", description="MANTiS is a Multivariate ANalysis Tool for x-ray Spectromicroscopy", long_description=long_description, long_description_content_type="text/markdown", url="https://spectromicroscopy.com/", project_urls={ "Code": "https://github.com/mlerotic/spectromicroscopy", "Documentation": "https://docs.spectromicroscopy.com", }, install_requires=['PyQt5>=5.15.9','numpy', 'scipy>=1.14.0', 'matplotlib>=3.6.0', 'h5py', 'Pillow', 'lxml', 'pyqtgraph>=0.13.7', "scikit-image>=0.19.1"], extras_require={ "netCDF": "netcdf4-python"}, entry_points={ "gui_scripts": "mantis-xray = mantis_xray.mantis_qt:main"}, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Topic :: Scientific/Engineering", ], packages=setuptools.find_packages(), include_package_data=True, python_requires=">=3", )