pax_global_header00006660000000000000000000000064151272020140014504gustar00rootroot0000000000000052 comment=e5d06371cb79c1302638ebb46bedeb8b76ae88b5 PynPoint-PynPoint-e5d0637/000077500000000000000000000000001512720201400154015ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/.codecov.yml000066400000000000000000000002271512720201400176250ustar00rootroot00000000000000coverage: status: project: default: threshold: 100% if_not_found: success patch: no comment: off ignore: - tests/* PynPoint-PynPoint-e5d0637/.github/000077500000000000000000000000001512720201400167415ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/.github/pull_request_template.md000066400000000000000000000024121512720201400237010ustar00rootroot00000000000000Thank you for your contribution to the PynPoint repo! Before submitting this PR, please make sure: - [ ] To read the documentation page on the [Python guidelines](https://pynpoint.readthedocs.io/en/latest/python.html). - [ ] That your branch of the PR is synced with the main branch of the PynPoint/PynPoint repo. - [ ] To update the dependencies to the latest versions with `pip install --upgrade -r requirements.txt`. - [ ] To run both `pycodestyle` and `pylint` on the code that has been added and/or changed. - [ ] That the documentation is successfully build after running `make docs` in your local repo folder. This requires the installation of `sphinx` and `sphinx_book_theme`. - [ ] That all unit tests are finishing after running `make test` in your local repo folder. This requires the installation of `pytest` and `pytest-cov`. - [ ] To add unit tests in case there are new pipeline modules and/or functionalities added. - [ ] That only text files have been added and changed in the commits of the PR. Binary files will clutter up the repo because even after removing such files they will remain in the repo history. - [ ] To add and/or update the docstrings (including the parameters, returns, types, and descriptions). - [ ] To add and/or update the typehints and typechecks. PynPoint-PynPoint-e5d0637/.github/workflows/000077500000000000000000000000001512720201400207765ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/.github/workflows/main.yml000066400000000000000000000022531512720201400224470ustar00rootroot00000000000000name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.11', '3.12', '3.13'] steps: - uses: actions/checkout@v2 - name: Setup Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | sudo apt-get install pandoc pip install --upgrade pip pip install .[docs,test] - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Build documentation run: | make docs - name: Run pytest run: | make test - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} PynPoint-PynPoint-e5d0637/.gitignore000066400000000000000000000012331512720201400173700ustar00rootroot00000000000000# Python *.py[cod] # Distribution dist/ build/ htmlcov/ .eggs/ # Testing coverage.xml .coverage .coverage.* .idea/ .tox/ junit-docs-ci.xml junit-py27.xml junit-py36.xml junit-py37.xml .pytest_cache/ *.nbi *.nbc pytest.ini # Sphinx docs/_build # Mac .DS_Store .vscode .vscode/settings.json # pip install -e pynpoint/_version.py pynpoint.egg-info pynpoint.egg-info/* # Vim .tags # Tutorials docs/tutorials/PynPoint_config.ini docs/tutorials/PynPoint_database.hdf5 docs/tutorials/betapic_naco_mp.hdf5 docs/tutorials/hd142527_zimpol_h-alpha.tgz docs/tutorials/input docs/tutorials/.ipynb_checkpoints docs/tutorials/*.fits docs/tutorials/*.dat docs/tutorials/*.npy PynPoint-PynPoint-e5d0637/.readthedocs.yml000066400000000000000000000003061512720201400204660ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py build: os: ubuntu-22.04 tools: python: "3.12" python: install: - method: pip path: . extra_requirements: - docs PynPoint-PynPoint-e5d0637/LICENSE000066400000000000000000001046371512720201400164210ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Pipeline for processing and analysis of high-contrast imaging data Copyright (C) 2014-2023 Tomas Stolker, Markus Bonse, Sascha Quanz, and Adam Amara This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: PynPoint Copyright (C) 2014-2023 Tomas Stolker, Markus Bonse, Sascha Quanz, and Adam Amara This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . PynPoint-PynPoint-e5d0637/Makefile000066400000000000000000000030411512720201400170370ustar00rootroot00000000000000.PHONY: help pypi pypi-test test coverage docs clean clean-build clean-python clean-test help: @echo "pypi - submit to PyPI server" @echo "pypi-check - check the distribution for PyPI" @echo "pypi-test - submit to TestPyPI server" @echo "docs - generate Sphinx documentation" @echo "coverage - check code coverage" @echo "test - run unit tests" @echo "clean - remove artifacts" pypi: python -m build twine upload dist/* pypi-check: python -m build twine check dist/* pypi-test: python -m build twine upload -r testpypi dist/* docs: rm -f docs/pynpoint.core.rst rm -f docs/pynpoint.readwrite.rst rm -f docs/pynpoint.processing.rst rm -f docs/pynpoint.util.rst sphinx-apidoc -o docs pynpoint cd docs/ $(MAKE) -C docs clean $(MAKE) -C docs html test: pytest --cov=pynpoint/ --cov-report=xml coverage: coverage run --rcfile .coveragerc -m py.test coverage combine coverage report -m coverage html clean: rm -rf dist/ rm -rf build/ rm -rf htmlcov/ rm -rf .eggs/ rm -rf docs/_build rm -rf docs/tutorials/PynPoint_config.ini rm -rf docs/tutorials/PynPoint_database.hdf5 rm -rf docs/tutorials/betapic_naco_mp.hdf5 rm -rf docs/tutorials/hd142527_zimpol_h-alpha.tgz rm -rf docs/tutorials/input rm -rf docs/tutorials/.ipynb_checkpoints rm -rf docs/tutorials/*.fits rm -rf docs/tutorials/*.dat rm -rf docs/tutorials/*.npy rm -f coverage.xml rm -f .coverage rm -f .coverage.* rm -rf .tox/ rm -rf pynpoint.egg-info/ rm -f junit-docs-ci.xml rm -f junit-py27.xml rm -f junit-py36.xml rm -f junit-py37.xml rm -rf .pytest_cache/ PynPoint-PynPoint-e5d0637/README.rst000066400000000000000000000064671512720201400171050ustar00rootroot00000000000000PynPoint ======== **Pipeline for processing and analysis of high-contrast imaging data** .. container:: |PyPI Status| |Python Versions| |CI Status| |Docs Status| |Code Coverage| |Code Quality| |License| PynPoint is a generic, end-to-end pipeline for the reduction and analysis of high-contrast imaging data of exoplanets. The pipeline uses principal component analysis (PCA) for the subtraction of the stellar PSF and supports post-processing with ADI, RDI, and SDI techniques. The package is stable, extensively tested, and actively maintained. Documentation ------------- Documentation is available at `http://pynpoint.readthedocs.io `_, including installation instructions, details on the pipeline architecture, and several notebook tutorials. Attribution ----------- If you use PynPoint in your publication then please cite `Stolker et al. (2019) `_. Please also cite `Amara & Quanz (2012) `_ as the origin of PynPoint, which focused initially on the use of PCA as a PSF subtraction method. In case you use specifically the PCA-based background subtraction module or the wavelet based speckle suppression module, please give credit to `Hunziker et al. (2018) `_ or `Bonse et al. (preprint) `_, respectively. Contributing ------------ Contributions in the form of bug fixes, new or improved functionalities, and pipeline modules are highly appreciated. Please consider forking the repository and creating a pull request to help improve and extend the package. Instructions for `coding of a pipeline module `_ are available in the documentation. Bugs can be reported by creating an `issue `_ on the Github page. License ------- Copyright 2026 Tomas Stolker, Markus Bonse, Sascha Quanz, Adam Amara, and `contributors `_. PynPoint is distributed under the GNU General Public License v3. See the `LICENSE `_ file for the terms and conditions. .. |PyPI Status| image:: https://img.shields.io/pypi/v/pynpoint :target: https://pypi.python.org/pypi/pynpoint .. |Python Versions| image:: https://img.shields.io/pypi/pyversions/pynpoint :target: https://pypi.python.org/pypi/pynpoint .. |CI Status| image:: https://github.com/PynPoint/PynPoint/actions/workflows/main.yml/badge.svg?branch=main :target: https://github.com/PynPoint/PynPoint/actions/workflows/main.yml .. |Docs Status| image:: https://img.shields.io/readthedocs/pynpoint :target: http://pynpoint.readthedocs.io .. |Code Coverage| image:: https://codecov.io/gh/PynPoint/PynPoint/branch/main/graph/badge.svg?token=LSSCPMJ5JH :target: https://codecov.io/gh/PynPoint/PynPoint .. |Code Quality| image:: https://img.shields.io/codefactor/grade/github/PynPoint/PynPoint :target: https://www.codefactor.io/repository/github/PynPoint/PynPoint .. |License| image:: https://img.shields.io/github/license/PynPoint/PynPoint :target: https://github.com/PynPoint/PynPoint/blob/main/LICENSE PynPoint-PynPoint-e5d0637/docs/000077500000000000000000000000001512720201400163315ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/docs/Makefile000066400000000000000000000011041512720201400177650ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)PynPoint-PynPoint-e5d0637/docs/_static/000077500000000000000000000000001512720201400177575ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/docs/_static/betapic_center.png000066400000000000000000000640071512720201400234430ustar00rootroot00000000000000‰PNG  IHDRýÖi#ªgAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<GPLTEÿÿÿ‰‰‰¢¢¢ )»»»DW2@EXEZE[F \DUF ^DTF _ªªªUUUwwwWWWFaGbGcGfHkGiGjGeHnHoH"sH qHpHlGgH#tG%uH!rG&vF0}>Hˆ9T‹;QŠC;ƒC<„,rŽ£†8¹v#¨ƒ'|Ž>I‰E5€*wŽyÑQúæ"¥Ú5äã�ÓL({ŽB=„=K‰"§„�ÖDšØ<Âß"·Ý)'­€:R‹5]ŒÇàÏá•×?ýç$GÀn6ZŒG'w/³{ŸÙ8ˆÕG×â—Ø>)¯=L‰%ƒ�øæ!ÜâbÊ_(zŽE6�=J‰+±}3¶y!¦…AA†D7�?¿@^¹_ä§!¹åEŠ5ï{ÁO¼ÏHŽÊWiq�ü)yâu…书„ä•ëµñ”×ë<¼X ýhñÓéeùQùqÊ£E9}åäJµÏr7Ño»³ŠµW¯Rôó3’A¿ÍsÑý䮡ŸläúŒòÐïý–£òã”GÑýäz|Ðo3è‡~£ _±³ŠµW¯Rôó3’A¿ÍsÑý䮡ŸläúŒòÐïý–£òã”GÑýäz|Ðo3è‡~£ _±³ŠµW¯Rôó3’A¿ÍsÑý䮡ŸläúŒòÐïý–£òã”GÑýäz|Ðo3è‡~£ _±³ŠµW¯Rôó3’A¿ÍsÑý䮡ŸläúŒòÐïý–£òã”GÑo¯~1¾wréŽÖ9ï©iÏ“z#¹ßèzwøxÖŠÕrRÉ«q²‘.ko¯~FOéeÿª¯“ßì_Ÿ¯ŸQÖO>3ö;õ—åøåŒú)ò£!|MúO“ÈÏ—¯#ù•ô†Dý¼ èWÙ©{ôC¿t5ô‹A?ô“×Y®6ú‰ùî5Ê�ð)WF?ô›3è·º�?Š~7\ýÐoΠßêvþ(úÝpeôC¿9ƒ~«Ûù£èwÕÑýæ ú­nç�¢ß WF?ô›3è·º�?Š~7\ýÐoΠßêvþ(úÝpeôC¿93z¯‡¯‡ö)ö‹¯Reù`Gœ‘ﻼã²ï|ïÈböGsdÊ}ñVäÛݰû²þsïøM÷Ó|1­»;?M>(­xJÕ�5X,šäů¤Mv–,Œ\è‡~•MÏG?ôÛ,ªÎÝû4ÙY²0r= úU6=OýÐo³¨n8wïÓdgÉÂÈõ€~èWÙôMv–,Œ\è‡~•MÏG?ôÛ,ªÎÝû4ÙY²0r= úU6=OýÐo³¨n8wïÓdgÉÂÈõ€~èWÙôMv–,Œ\è‡~•MÏG?ôÛ,ªÎÝû4ÙY²0r= úU6=Oýލ_qs7k)o¥\[yå ¬·¢~4ÏÈï£ìJù·Õ<¾„¼œyrT²³ò:ËsýÑâ•ë’»ÒÍÕ"§é‡ç_ÍëúùjÏ3’)6…œ¯Ÿ…Ü—>£þ*ó”Ú@?ôC?ôC?ôC?ôÛ úÅ ú¡ú¡ú¡Ÿo“Žæ¡ú¡ú¡ú=åÊè‡~è‡~è×jA¿ôC?ôC?ôC?ôómrÃÑ<#ôC?ô›X¿óåcä‘»�Ç®GÎÏ¡_±ïÒ ,.X/ß\-~£eïøú—;Þ”úÝÀéÞú—£Êƒ¹áÊy×d%xŽäíú»H¼`ñ=5~Iûƒ6ró¹rvùÝ=1Åw ÔëÙ×ä³”S¾—,ÔÍ¡V¦öœú]>)ö>Cû>d…\8rÙ¾ ú¡ú¡ßæP+S{Fý‚bI¿ó]ÌetZ’ýÐýÐïØú}Rï’¿û½¬¡ú¡ú½!ý.Ÿ¿®»,¿Ä;Öðœ¿.D?ôC?ô;„~ÜqúE/ ãÿWùâýÐýÐos¨•©=£~Ñ3ñÐY>aAá¹ðÅú¡ú¡ßæP+Sû"ú-¿¦‹.¿üýÐýÐoy¯cë·þ’.üïêÉË/ÑýÐýŽ­ßÚ´ð¥ ú°ÿâoôÉöÅ —;•+<>³˜¿r$¶ïŽ\ºi_�PßÓ<Ù³²ÒL©Ç»È+çÕð[#ÏÍ�¼Ûz+�šœ¦oä¿V¾ñ}åÈæ5°ÇôJ“Õ›×'^°ø¾‰ûrrÙû”‘ÛáÏ5#‰¯ó‹ë§ÿ­ú¡ú麒A¿†~è‡~è‡~êw§ô»,ÿkðTôC?ô3A¿vý"qç¥~g{òGñB¾E¿AÐýÐozý¾YêçOî_† Ÿ ú¡ú¡ú¡ú¡ú� ýÐýÐo~ý~òsôýÐý¦×oõ3ßóê©üÌýÐýÞˆ~ò÷ý>š—>Ö€ß÷C?ôÓu%ƒ~íˆúiíÐýÐOו úµ¯U?¹z}O}J£Šmâ�Þ_ÿ²|úÛ�jÉkû0ø3afFE¬â]ä,zö˜…q‘;_VÄ=9/ž„~ë^@?ôC¿6³~w)Ë/?'|w{^ÙÂýJîo‡~è‡~_N¿%‹Ú;ïÂýJîo‡~è‡~_P¿Àßeõs�óe~ ý¤`¹n7+Ó4Q\pY¹tý}å¹ù]²°ã|Ÿ¢Aqâ^]_‡ýÜzwø¡úmõ]iÚ$zž×¶þêæ9zJåøôs£«yð²6ü•‹b×—ÈLÍo–ÔÏWŽ¼Ë¸žñßýBVÊ]>üƒ >óÑså&è‡~fUý:£ú½”~_$è‡~fUý:£ú¡_C?ôC?ôC?ôC¿åò¢_C¿)ƒ~ègVÕ¯3ú¡ú5ôC?ôC?ôC?ô[./ú5ô›2è‡~fUý:£ú½IýŠÅÙŸ÷´Xº¾†‹[éwß7”Üq¹y¾ñ܇?qß³*ϲ’êØÑÅ~÷›%waïFËå²äáÉ=’=k^åíüËÍ ¯Å–‘+éãã÷È÷‚Ü#ôC??qôC?ô›!è‡~« ú¡Ÿ ú¡Ÿ_yôC¿ùƒ~è·ú¡ú™ úù•G?ô›?è‡~« ú¡Ÿ ú¡Ÿ_yôC¿ùƒ~è·ú¡ú™ úù•G?ô›?è‡~« ú¡Ÿ ú¡Ÿ_yôC¿ùõ‹Ù»Œr Þ½sWÎ�Èsåžú[Ç\U¾²LŽYJ²wÑä•ýÄo¸r± ŠÃóíVßßü¢àï,¾ _7¥`r5úÑþ7Ñü|ålÞÎì |u“·ð[™�¦Ïm@¿ívC?ôkè7î£ngvýÐO6/úm.¾_@?<ÏK}ÑýÐo³£‹cF?ôÛ^C?ôÛŠßôkè7ॾ¿è‡~è·ÙÑÅ1£úm¯¡úmÅoú5ôðRß_ôC?ôÛìèâ˜Ñý6‡×Ðý¶â·ýú x©ï/ú¡ßËéWDCÖ¿ÔÀïi¯¹§òjyƒ²ÚåJ¢Ÿl ôC?ôC¿©‚~è‡~E"Ðýú¡ú�}óGÑož ú¡_‘ôC¿†~è‡~cßüQô›'è‡~èW$ýЯ¡ú¡ßØ7ýæ ú¡ú‰@?ôkè‡~è7öÍE¿y‚~è‡~E"Ðýú¡ú�}óGÑožlê'WÏ÷�_䌀_R_VŽY¶y^+¹z¾–ò}嘋VH…ä•ý:÷ÏÈàl6”ïèüI>¾+å}óF¿Ûzç~. ?y®•yi–§È1ûE“+“gä+ǯ�3­ú¡ß°8ÑýÐo† úå•D??y.ú5ôC?ôC?ô{mÙЯXêè×ÐýÆA?ôkè‡~è‡~è‡~è·<ýú¡ú¡ú½¶lèW,uôkè‡~ã| úÉ•»fêGv‡ïJYºOÙ5iT®‡ºÅùúâ,v´³¬Có¢ ;Úï‘ïJïs<·H¢ÛÀ׆¼š¯«¢~r­üú—â.\Œ?ê×JŽÙß×ïú¡ú¡úÍôC?ôC?ô“5éw ýÐýÐo9;ôC?ôC?ô›:è‡~è‡~è'kÒïú¡ú¡ßrvè‡~è‡~è7uÐýÐýÐO®�ì;_K½üŠû"áÚÛ€^žÓÖÇ’ûú÷ í]«ú˜}ïxpöö»Üš¾h߇ä[DÁüì2°¾ïnX+ùªd&çî�Ç¿÷Õð´¥n>eTcÙd¿D}³ü}oh=ôC?ô³Ý�~è7EÐoUºè‡~è7ºú¡_C?ôC¿á¡ú¡Ÿ[gôC¿Wú­JýÐýF·@?ôkè‡~è7\"ôC?ôsëŒ~è÷êA¿Ué¢ú¡ßèè‡~ ýÐý†K„~è‡~n�Ñý^=è·*]ôC?ôÝâ ëçkX®­\“¼YrõŒ´ñhUñïªßGÙï¾0<°fâ2ñÜ>ùZà×Y²f–è†RÏû¡¾¾ƒù”S9ÅF.•c–ƒÏÊgö[ĺòWöñûk†'Sƒ¯vôC?ùLYiè'ç‹~›A?ô“§ ú�ö«xý6Ÿ‰~è‡~è‡~=è‡~ò™²ÒÐOÎý6ƒ~è'OA?ôíWñ(úm>ýÐýÐýzÐýä3e¥¡Ÿœ/úmýÐOž‚~è7Ú¯âQôÛ|æÓO®mq‘}ßù¿ Ù[i²Ueuùz(^Yîxny®oÀ~®|Dzº†ï)ð[#¯,÷7OS:SÔÞ×Ut&•¯A¾åÕö¾ÇÄßÂWN\go…Eö&çµò•##k)o´Iº ú¡ú‰ Ÿ¬ôC?ôC?ô3£B?ôC?ôC?ôC?ô“A?ôC?ôC?ôC¿QÐýêWF?ô­¹Ü#ô›9è·ëÊè‡~£5—{„~3ýv]ýÐo´ær�ÐoæŒô+Ư�'Ñ_Y–Ÿ,!JQ?ßÑÅÉŽ6ðÓåçç+ÇüPËsÍÈot³÷ÜïB½P÷§\T^ÙW�ß,or•?êw¿¾¼¾ѯX`þÊè‡~›UW/TôC?ôkè‡~…­D?ô«×!ú¡úå+ûªó›…~è‡~è7Ú…z¡¢ú¡_C?ô+l%ú¡_½ÑýÐ/_ÙW�ß,ôC?ôC¿Ñ.Ô ýÐýú¡_a+Ñýêuˆ~è‡~ùʾêüf¡ú¡ú�v¡^¨è‡~è×Ðý [‰~_›~ñÓü®ù ÷•–¯ßÉž·Þ÷�O½+÷ê{Ö׿!eÄšYg9#Y®¾wŠëæ�Êiú§ù=*^ÙÇ/~qj~„þ¨¼¯Üè\Ø>~5dÿúÕ{§â—¨PÏè‡~è‡~è7uÐo5Bôkè‡~è7®ôC?ôE?ôC?ôC?ô›9è·!ú5ôC?ô×ú¡ú�Ž¢ú¡ú¡úÍô[�ýú¡ß×§ßƒŠ¯Òþ´¸ò¹ö¤`Ò¿Y~å‹Vìm9ß/X±–äZå6‘rÊÁW£_ù‡�<<¹ƒr³ŠVøbó«Q\çѪš«Éµ*n¥9Ô~Ôá!w?›&ǯì;å]ŠïßÔ¡è·ú¡ú¡ßlA¿QCµrÐO^ýúÍô5T+ýä•ѯ¡ßÜA¿QCµrÐO^ýúÍô5T+ýä•ѯ¡ßÜA¿QCµrÐO^ýúÍô5T+ýä•ѯ¡ßÜA¿QCµrÐO^ýúÍô5T+ýä•ѯ¡ßÜA¿QCµrÐO^ýú½LΗ»Ç\.騧C—3ú©j‘÷ò•æƒ~òÊè×ÐïEí{dNÙ—lêgQ”D®@qå‹çŽ®3ºZs|f. ß;ñÜü4ÿZ [ÆÏH®m¾o¬ðþ¡uŠ1=.»2ŽÊ V¬ y_“¾+óûúý ÄÞϧl¾Ôæëøûú1ËSòŒîmN6rGükßkéw¾[&*·pñnóË?ô•úÅ ß ýæÐ¯Ó{Iü]V‡Ðýö'_ ýNè7…~— Þ§ï�¯‡ÎŸ�Kßü¢ß¨ Ð/ýNè7…~Ëïi/¹ÅÿT¾øC¿Q _ ú�Ðoý>Mw^ƒw‘GÐýÊÉWC¿úM¨_üßËòú¡ßMÉWC¿úA¿ÏG.Û?öE¿Q _ ú�ÐorýVG.Û_ü¡ß¨ Ð/ýNè7�~Ÿ¿Â»Œõ+|ëõ“‹&ÿR›iÞø _[ßïOéYYÃæù›É÷õ_wCs]0¯½oŠÜ•þ¨¼¯ï,óüúkßf«VÊ ÆÔƒ\ÒYíù\YuyIoØ__ÏõQå]¨/‚éŽë]^þ7^®¨�£…è‡~è‡~[£:²~Ÿø;÷ÿ¼þÛÞEéççýÐýÌ’Æ _›@¿ÕÛÙú6ÒO9.R¿Ÿ…üýÐýÆKƒ~mýs°z›[R2�¼z—0ú¡ú�–4ýÚ ú >ãýÐýÐosT‡Öoô/è‡~è‡~›£:²~Ÿ>¿ üÔCÿ²óH¿Ÿ„|‹~è‡~ã%�A¿öúú ?å ýÔƒŸù¢ú-‚~ÇÖoõ{-áþÒÇÜþ^�<µø /Srqå}�ùæõõãÕ-š%ñ5à�æ7AÔ+­XØyj›-V”S^9ßWÎ]ž[j¾…|¹|PÙ‹d|fÞ¬‡­O˜ß‹³¬:¿Î²HrCÝСò^޷¨^\?ýÿè7®Rôó÷E¿†~óëw‡~è·8ýÐïkÒï²~@Âø„O9@?ôC?ô›R¿Á×~ê®üÅÐouTVf±wäQôC¿QÐï9õ[ýØã ŸnŠ~è‡~è7�~ãŸù®d,|¸)ú­�ÊÊ,öŽ<Š~è7 úíÎyøû~Ëÿ9ßmã‹~룲2‹½#�¢ú�‚~ûsIïõøláçC‹w� úéså|ÑýfÕoý6ßü}°8€~ègï‹~ ý ßðSÒ±MüÐo}TVf±wäQôC¿QÐï¦ÄO¸º,°>ýj?ô[••YìyýÐoô»Õ¿÷yïÛårVÇ>9W.õ“Ý!×$?Múæ×*ïTÜ,yµb Ÿl¼ò^fð²Òü¨6›1ËiÆ9U±ìß©ø1×{aµ•òÊ2ÅW ÿ Už\yЬv¿•ýÃ.üŒü"øšôŸCâ¼ßBöo±}âÑ/¤ß3ýFóõ}‡~è‡~ùÊè‡~èƒ~è7gÐo4_ßwè‡~è—¯Œ~è‡~1è‡~sýFóõ}‡~è‡~ùÊè‡~èƒ~è7gÐo4_ßwè‡~è—¯Œ~è‡~1è‡~sfô^STmËÆ¾Œ~õòæÆ-õW>•Ó�)ö»<êMÍCõÝ!«NÖ°oó|_9;¿’¾³Š‰£*^Þ7k°yJ¥µåÆù×z¹ƒ¦–F&Ÿæ;EN3¯³|,ïûôC¿b5ôC?ôC?ô³÷•Góf¡ú¡ú¡Ÿwý6c6ýú¡ßò(úÝÙÈ­–xôC?ôÛìYôC¿ú¡ß²¡Šû"×Öú5ôC?ôC?ô³÷•Góf¡ú¡ú¡Ÿwý6c6ýÚ›ÓONM•WÏïi\ùܪ²8%¡¾†M¹¶²Ø2ù^j9_?x?ßÜÚ«ßí7‹ïŸæ{§8*¿?„âÆ_|å}eÅ»Á7?£âɧ傉+ã»Ìï¾_«¶3©`Ðo¸/è‡~è‡~3ýÐýò`ÐýîÑýÐo¼¶è‡~è‡~è'�—H> ýÐýÐýÐýÐýÐýÐý¿X¾–äÔò•ýŒd»És}g ìd#Çéÿ\ç(^^0¾“ýÇkä•÷®Fqjñ”•|A¹€7æ{¡X±7ìQ±õ|åÔÛÖ¬³_¢x.úímäúóóƒè‡~ègRo[³Î~‰â¹è··‘ëÏÏ¢ú¡ŸI½mÍ:û%Šç¢ßÞF®???ˆ~è‡~&õ¶5ëì—(ž‹~{¹þüü ú¡ú™ÔÛÖ¬³_¢x.úímäúóóƒè‡~ègRo[³Î~‰â¹è··‘ëÏÏ¢ú¡ŸI½mÍ:û%Šç¢ßÞF®???ˆ~è‡~&õ¶5ëì—(ž‹~{¹þüü ú¡ú™ÔÛÖ¬³_¢xîqõó{ê×Vv´¼š)ŒÍø Ê®ùiš©Ý‡÷_È«õ�Ò£Q™ºÝì,AÓ€ñAù*Ó�æG¾Wo¤™™�_+¦÷Ü¿îxuûQùNynžï¿ƒò:rFfâñ™rÅ%’û‹~¾+ÑÏO­èú¡ß¨8ú¡ú�/Ø�¢Ÿ,N_uè‡~ ý>ýÐïýÐýÒÕúƒè‡~è7UЯ2_ôC¿†~ƒSÐýÐýÐý|W¢ŸŸZÑôC¿Qq6ôC?ô_°E?Yœ¾êÐïmëç ¬O¼7~Û¿õ~_|•Êú/¶ŒÜS¿ã=™…xAßøòÊ{×Jž+�ÊG}äZå‰K0ýÑÔ;º=ýå3ãòú+ûuö»Ÿ_Ýbü S±8}É[dœãÑbqúùöÞ�óE¿bŠ-÷TVxÞÜbu�ÐýÆëìwýЯíO±£åžÊ Ï›[¬®ú¡ßx�ýî£úµý)v´ÜSYáys‹ÕuB?ô¯³ß}ôC¿¶?ÅŽ–{*+ú¡_ÛŸbGË=•ž7·X]'ôC¿ñ:ûÝG?ôkûSìh¹§²Âóæ«ë„~è7^g¿ûè‡~mŠ-÷TVxÞÜbu�ÐýÆëìwýЯíO±£åžÊ Ï›[¬®ú¡ßx�ýî£úµý)v´ÜSYáys‹ÕuB?ô¯³ß}ô{úyg|GË–u˜Oñ…áM–§¼³‘õ`†çÛ$>˜[l½§,Q¾oÜ£ Ý÷*rÍEâÜ󆓧ÔWÒ,i\Uï¹Üß¼Î~0r;r Ý«¿a÷]HqTEÖê6ö¡¿>ñ¯~Ì©ÃÐý†WF?ôI"+§¸V²$ÐýЯ¡ú�ƒ~rõÐýÐý̘Ñýú¡ú¡úÙ+£ú�$‘•S\+Yè‡~è×ÐýÆA?¹zè‡~è‡~fÌè‡~ ýÐýÞ„~fÖ£Êøøs}¿Ë®Ìô&ûZ’çÊ1›ÈÈ®ô¥ë»²x_)˜•�.ýAåÁ¦¸ƒÅ‹È¹›{Åiúµ’®nV¾•¼oqåÑbmÈô¾ù¾Ë¬Éñ�_ ÐýÐÏíúɧÝPè‡~èwB¿å4ýZ¡ú¡ú¡úÉ+£ú�Ðý»†~è7ª+ôC¿ú¡ß¬A?ôC?ô“WF?ô;¡ú v ýÐoTWè‡~'ôC¿Y3úd{¿zÞ·~T®­/¹|‹x_³¹¾Mâ)òÊù"ñ\?*_á¾êü:û+{y2eÒ·¼¶ò‚ž&9*¯�DÀ›\\ç"/õ]0”5õÆ“¶?ïTú}ýû5dCå©yíå}å¨üJ¢ú¡úí ú¡_C?ôC?ôC?ôC?ôC?ôC?ueôC¿†~è7h ôC?ôË·ð@?_è‡~è·ªôC?ôkè‡~;ƒ~è×ÐýÐýÐýÐýÐýÐO]ýЯ¡ú šýÐýò-üЯØr;ú¹}×ü2Æ ÷rú /ŽªØOɨCÍð|äà½oŸøIÏ;£‘Àæ¨|¾÷<¿�Õ_)òƒ~�åuН_7T�Ÿ‘¿—|Eö=(o··¹²–§O`¢ú /¸C=;xôC¿†~èWàýЯ¡ú¡ßø”½,l 8¼šýЯ¡úxA?ôkè‡~è7>e/ ›(ï†&B?ôkè‡~^Ðýú¡ú�OÙËÂæŠÃ»¡‰Ðýú¡_�ôC¿†~è·G?¹¶Å]óèåGž˜<‰€ìŽåö WÃÏ×W‹ìñ\r²ïdöRvÃë—ï»â˜åQÙk6¦8ã~É‘KHÖ¤¯¿VÅQy…üÓü˜å8s|R¬v9_ô˵„~ ý–GÑý^=è‡~~7ÑýÐýÐoôC?ôkè7¾`>Š~è‡~3ýÐÏï&ú¡ú¡ú­‚~è‡~ ýÆÌGÑýÐo† úùÝD?ôûôó{*‰ðûâ[¦o�/H_-õJó5ìÛ¤X‡ò¨¿²ç(¯•7GýÊù¨¼ T(ÕINÞt‹øþ”<Ùø�–çõ‰Ï”ëœ UÞ·_¤>fS~£/rÞ©[æ��<ýòÚ¢Ÿ¼ú¡ú¡_.NôC¿†~è‡~è‡~è‡~è‡~è‡~è‡~è‡~ègƒ~è·ôC¿†~…sÑýü² ú­‚~ýV÷E?ôC?sî[ÕONüÁÌj·Á¯â˺2[ mÌçÆ£y¨ñ¨W(«+›1wS+ÿƾoŠâ}ý)±ß»`¿¼æ÷C~uͯ¯ùƒ�þ´~‘C²3Ñ·¼¼Òd—×oóU²’úEr]mŽÐ ÕóâòÕîkò†þE?ôC?ôC?ôC?ôC?ôC?ôC?ôC¿Éƒ~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~ó'ê'}+Fž+{6/šÜýza›—VÈË÷ò (Ÿ)ÇlÚ¤©·™ËY‡½œ.φtÊþèš?ù“k~sÍŸªü™Êo¯‘6ö1ÿ RDC.Qñ¨¥¸¡žýþæQIg| ùªóécþ.¤XϾ&ÑýNè‡~è‡~µjA?ôC?ô›!è‡~è‡~þÊè‡~è‡~è‡~èçë¿Þè‡~è‡~è‡~èçç{C‡¢ú�Ðý>ýÐýÐýLMÖëýÞ†~r×dßåx’/"±òŠúSäEîk‘ÏÏ3Úª©4ß;~âñ½¦=[ÐÏϨ+�ê¬ýÍkþ<¤ë÷»k~’ÁŒ$æ÷‰Äw‘t~ûð¤~¾ÝüZù«NÚ¸÷Å·)c§d||ÕùûÞàyqŽñ\ ú¡Ÿ¬ôC?ô›3è·²¢>TôC?Iú¡ú¡ú¡ßÌA¿•õ¡¢úI"ÐýÐýÐýfú­¬¨ýÐO�~è‡~è‡~è7sÐoeE}¨è‡~’ô;¢~¾ñ}=È*•;îW¯ß¢_-žâ+ͧ_Ð?-àaë}=½ヾÒòðä}{¢ùvQƒþ´®ÐïBþ·§éçwÍ•›%×§(§¬?*Ó&ñ¨´%ÕŸ[/×,˜ýÍýo±ýIHWè·*ÿØ5ÿø5ÿDÈ?yÍ?uM¼r¿H‡d°#ù‹�>xù®™¾,²r|Wú—-¿ƒ~_N6y¨®ºØEåÕò¹Q¿bw÷ÄzF?ôC?ôC¿—‰×oý/‚è‡~è‡~oI?ùSù WþZè‡~è‡~ÇÑoõ£Ý`ÜêÇ;?ÝýÐýÐonýÄç×ß…#ËßƒÞø›–è‡~è‡~ÒoÁÚâ÷âÿœï¶¿ñE?ôC?ô;’~çÏì·þ³½ë#{þ¢%ú¡ú¡ßäú}âïsΣ#[ø¡ú¡úJ¿rçÑ‘Mü†ŸlŸ�ñ»æ{6íçf$ß©·ZÈ=ÍÏ—§øúñÚÇ=}°1iä=‰ÙF)^žï�!p�ü„ùnc'È?òÏ\óÏ^óÏ…üó×ü ×ü‹!�ÁþnŽø™ùýÍ&�Áßé Ê•Ï�ü]H^+_WÒ/�DÒïo1²b‹rÊÚð$úÍGã:û•ü"ú}>ÛùrÙ¾ú¡ú¡ß±ôûæÃß4úø§Ã#çÊEÐýÐý§ß³ýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýæú¡ú¡ú�dÈõ�—â†ø:ô§È§í­´ØÅSd¥e¬d w—â¹ù¯˜Õ[&_$þ}´þ1]¼_…ôÏ"ø—®ù[!ÿò5ÿÊ5ÿjÈß¾æï\ó¯…üÝkúþ8¤ÿ-¹ŽdUÖÏOü)¯}Ò éŒ¯É¢3²r<Îù ï[½rü¹~ÝòÔÒŒÐýÐýÐoê ú¡ú�:ýòš ú¡ú¡ú¡ú¡ú¡_^+ôC?ô›3è‡~è‡~£NG¿¼&è‡~è‡~è‡~è‡~è÷–ôËõà�úz�»&¥õ¥+/hjX^Y:ã/â›1_Ù›g”ÿŠYý%£ß®£ß7ñG)¿éþë×ü!ÿæ5ÿÖ5ÿvÈ¿sM?úï†ü{×ô·uÄ1üyÊ„ôÕ�Vä�óµ!�Ê+Ë26;(«]íÊ×Ù¼é~Fuu}Cå zßâ× ¦‘¯P£ú¡ú¡ßÔA?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ô;®~~ã xIРɵÍGGNš+Ë —,Všœf¾oŸEª‡EÙ?”“—T>­7ÑoCú‡É÷÷\Dg~sÍ¿Müìúÿàšÿðšÿ(¤?ø_óŸ„ü§×üg×Ä÷ztû{=â¨òÛa¾)¾È—*³hñ™yÇåvø×>ù4Ay_]ü;Ó'õ•LýµÀk�~è‡~è‡~óýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýæú¡ú¡ú¡ú¡ú}�úÉ]Ë],×Ö“âãOñ£Ú¼`ñÊþ¨ARV»¬á\·2õÎê£òÓ­Ë?¡?Ø?”à?ù/®ù/¯ù¯BþëkúÑÿ&俽¦ûög!^?ÿ)Fž¶õYæE6r½žÍ ·„«^WÞ¨ü4¹÷6rðùA ¬œú¡ú¡úÍôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC?ôC¿ãêç7W–�Ñ£ ~Gݯ^³ñú�lò)7ØhLÍ÷TKo|Ùï6ýi¿ é¤tyâ;A~uÍß½æ¿ ù;×ü÷×üí�ÿášÿñšÿ)侦ÿ·? éèýîš8*¹yšý•B®³|Éçõó5Y·ÑÌ¢^íû“ L>MÞ7/oý•ý¼è‡~è‡~3ýÐýÐ/ý2Vè×ÐýÒ¹è‡~u ÐýÐýfú¡ú¡_ úe¬Ð¯¡ú¥sÑýê ú¡úÍôC?ôC¿˜¯S?™å¼ÖɶÜPK~ë‹O“·«_Ùs>úÎÆ÷�s±˜=¡ïóëk~¥ÒßÒ?âþ é þ¯×üo!ÿû5ëšÿ#¤¿‹¤›ö›�þ^�nc;¯•_�ø`~׌ló¾ƒß…äÛÉ‚‘è売}d)æS¤œ¦ïŸ0ŸïëÑ‹ëö”E?ôC?ôC¿ùƒ~£ºòGÑýÐýÐýÐýƒ~è‡~è‡~óýFuå�¢ú¡ú¡ú¡ú=ýÐýÐýæú�êÊE?ôC?ôC?ôC?ô{ ú¡ú¡úÍôÕ•?Š~è‡~oI?ßï~Çý²Äí§È½ÈGëVä§ÉŠ~¾òúHÍ|…û~�U*Ñëé¾­;ÿ¶Zÿ „ŽäŸ„üŸ×ü_×üß!ÿÏ5ÿï5ÒÑë�ü.¤3ØþEH±¥üý\¹þuÄï‘o _$¾SŠÓ”žûå«ÅSr—ÉWöûýA?ôC?ôC¿ùƒ~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~èw\ý¤or=óQ_rƒŠ¿ß^¯%ÿ ö†n?x9*ù/¶L>EîB�Øïý �Á¨_§©3øû!�¦þü_«ô7ŒÄsû)ºèj~³IôÜlt\ ¯A_庫~Tþ"¹`|=ÈR”£2üÞ‡w²Õ’¯ÙÃMÑýÐýÐoþ ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡úW?YKyMäQ_B¾ñå½Q¾ŠúÉ1ËZÊÊ+Ë É (ÏíGcÏúÅÿ>%ê÷‹”øÉöù£æÿ4$#Ñûƒ”èjFï�C:’ß«äÚ¨ÍèyÁ¼~¾êâƒy_äþÞ`cžH]ì</§_«Í~D?ôC?ôC?ôC?ôC?ô›<è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è7ÐýÐýÐÏ—Á»I„á¥.I®‡´¶ë·öb}ŠÜÓ|_9_Y‡{õ“�ÒŸæY�.ß×ÿCÊ/C:\™Á˜?¿Fþ]¶nÚoTº‡ñ3~¼FZá·£ÙäPYu~�ü¹>²}å˜:l[¯×Þd3¹h7ô¯ÜôC?ôC?ô›?è‡~è‡~è'÷ýÐý*A¿†~è‡~è‡~¯-ú¡ú¡ú¡ú¡_C¿Á¢¡ŸÜôC?ô«ýú¡ú¡ú½¶lOÑÏ`%ó]ˆ/’¾hOÙS)‰)9YØñ\ßwýh—ÄÏÈ+�JôÌðZx/@Ï�!ùh”0ÿù¶øŽŒŽ^fð}ú›8ú{Cþ(¤»Ú¥•3úÞF>-¯†œ�\+ór³zf>ê_Ýò)rFÞUy4¿ÌùÎ’3’úy‹÷}‚~è‡~è‡~SýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐýÐï¸úÅxýÌÓꋜ¯sÞæò ù:ôcöçzôüÅss¿Ë—€üüè†UZ7*¾#ã·×tÊ~¥Ò?ë>~~~~ãIU^Rÿ$#_ûüþæ£õQù,^9vÛúŠ"ïšlß>7ôB.0ߘèç­ðGÑýÐýÐýÐýÐý²þÊè‡~è‡~è‡~è‡~ ýÐýÐýÐýÐýÆFù£è‡~è‡~ègD?ôkè‡~èw úMôC?ôó·ð;ˆ~èç­ðGÑýÐýÐýÐýÐïËëç÷EVšÜÓb¼�¾ÒŠ’ÈÈ1ç+Ë·¨û +MÞW®³yƒÿféæfô®ÆÏ@è•ðK•S¼~r%å¨òÊ‹<ØÈ•ÌGã.ä£r­Lµlª[¬Xe_NþÙÉ}9…æB¿í _C?ôC?ô³à�ª_p¹Aè‡~-/ú¡_C?ôC?ôC?ôC?ôC?ôC?{AôC¿†~èWú5ôC?ôC¿1 Þ¨ú—„~è×ò¡ú5ôC?ôC?ôS³Þ{ô]ÈO¯‘ eInˆ¬�ü'çdßù:ôµ$WÃÜB>MölÖC>ó†I‰6fŽ~PÉhÄ1Èy+ò¨äJúW e¿Vò¯öuîGå½~²Súƒr¾¹Úß©ÈÝÌ»à+V®¤¬|ôC?ôC?ô›?è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~è‡~_ƒ~¦¨|{>¨÷/ÄÍêôÝ!«%³¬¥¢f±»sùÅgö²÷/â#�Ê*ÍGGníå˜óÑÈš™…\4Y~5ä,üQ3qÙѲ¶‹ýî}«ÇùS®ìwß?ß«ëƒ~¹ÔÑýVwñ«!g�~õ ú¡ú¡ú¡úÉùÊ1£Ÿ\IôC?ôC¿Ñ,ÐoWÐýÐýVב³@¿zÐýÐýÐýÐýä|å˜ÑO®$ú¡ú¡ßhè·+è‡~è‡~«ëÈY _=è‡~è‡~è‡~è‡~r¾rÌè'WýÞ°~1¾–rÙHgäÑ| ß²+}GK±;õ)]°< Ùr5|ïd¥eWÊ+ç~‘È•”=[l^�•œ¯œf¾‹·Q­÷ ¹¯Ü5¿¿¦ÀF›nnW|ڦɦÝFñ¥Ø“kCö¯üÌôC?ôC?ô›?è‡~ù\ôó{„~è‡~¹bÑýÐýÐýŒBè‡~¯ôC¿|.úù=B?ôC¿\±è‡~è‡~è‡~F!ôC¿Wú¡_>ýü¡ß ú�/r^=z¹û˜ôø–~õZÊ«'�ò’ÈŽ.®|šTטì¦|®´ÂûVo —‰¼²ükkEÁ|kGåWã†uöúù”KškXÍå'OñhÄ4/7q̲Sr‘Ë£róÑØ)¾êrÈ‘eÿ%õ{tî¢ìËÐýŠWF?ô›^¿‹@.à÷>›_þ¡ú�jýÐo^ýÎâK¼+ˆ—Oÿ�~;{Çw%úmýÐïKèwÉú�??p.}ó‹~è7ªyôC¿iõû€ße%Üâÿ+_ü¡ú�jýÐoVý>~mw^é·øßsá‹?ôC¿QÍ£úͪßÇ/ý–Ü}³úRýö÷ŽïJôÛ ú¡ß‹ëw~ü¶Vè·rãǾè‡~£šG?ô›T¿O°Ù/ö.Û_üEýdýË9öëG’ Ãë'#k)ÿz¹¼²ŒZ‘#å$É…íoñ`ý|eµÿx�¼r~Í’nl‚¹wðr­Ìð¾|t|.6¹ƒþÙ�Ê¿ô$ãíäÚúµÊµQì�@¼oîÊë[¥^\¿Ë'×ü·ºè·Ñ2FƒšQöú�ž�úVšá¡ßéMéw¾~‹‹~è‡~ú.è÷Fõëÿ ·ðí¢ôó¿ó‚~# nhFÙ;èwzBê[i†‡~§·¤ß¥+—õ;¯ž—õû‹�¿D¿�74£ìô;=!õ­4ÃC¿ÓÒïüµ¬ßZɬßÝ*è‡~è‡~Ñ/�‡~è‡~ú.è÷õ»Ü-ßͶW?¾ó­hpC3ÊÞA¿ÓRßJ3<ô;½ýÎñ_÷nÑ/†ŸzŒ4¸¡eï ßé ©o¥ú�ÞŒ~ ðüO=ø™ïFË nhFÙ;èwzBê[i†‡~§·¢_ü¾wëc ø}¿�–1ÜÐŒ²wÐïô„Ô·Ò ýNoI?©úm´ŒÑà†f”½ƒ~§'¤¾•fxèwB¿-ý¾S‘«íW/U|f/›ü4Y~~×¼$²¨Þ©´Zd1ûjÉ‹ ×ÊÏÎפwCîƒ�dÍ™c."GeÀ‘¼ÈùÊE˃‘ë\ÜßX9†ýÍŠõ}Tl�"\õW¨ü…Çè™9¯­ßú3^ÐýÐýÞ¾~ê®üÕÐýÐýŽ¢ß2˯îV?öØþÒýÐýÐïMè·üÖ·ðá¦è‡~è‡~oC¿ø/}çíßöC?ôC?ô{#ú=~ñw9_!Üõ-Ño3è‡~è7­~çåÏB¶ðC?ôC?ô{#ú-ùÛÄýÐýÐï­è÷Íù²¿á{=²Q~Á¥$òÜ\TéwŃ)UÛz7‡/*_ÿy"ò—ŸW÷†J+NÜ_$›ö>ý�ÄyýúÓ¼~õÁäÕó=+�W2žkÊþ´õê¶lüuy++p?x/O¾…<ê¯,g”Ó÷orà‹éwyŸs~ð½|—så|ôC?ôC¿cê÷Ô ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡ú¡úW?9GÙ¼yÑüfÉ£R æÞø‹dyä)Þ _BÒ¨§Ô¿ì÷½PKuåH:\ý?„H0ó)rü˜ó)ñÊyä)~ååàsûÚ¸A_òiÅýõçJ~MÈùúbKWF?ôC?ôC¿©ƒ~£SÐýFD?ôC?ôC?ôKWF?ôC?ôC¿©ƒ~£SÐýFD?ôC?ôC?ôKWF?ôC?ôC¿©ƒ~£SÐýFD?ôC?ôC?ôKWF?ôC?ôC¿©ƒ~£SÐýFD¿·§Ÿ\ä,^\gy4›æ7º^K™_KõÈ7tç öÉzðkUž|•‘6fŠåšç‹HäŒò½6ë?ß®Øbmë5(¯•´ñ§×È]ð£ò{$×*?M¶�W¨èy½SöV]±ìßm}† ú¡Ÿ¯+?ôC?ôC¿¼/蟉~èç+­^uŲG¿{ôC?ôC?ôC?ôC¿eÐýÐo4B?ôC?ôC¿¼/蟉~èç+­^uŲG¿{ôC?ôC?ôC?ôC¿eÐïˆúùÕ“6f=Þ'/‹¬�w*þ¨·Â OÖ¿/ƒQ=ûÞ¯”_ì_œþ^}â}åýŒ| ËûʉäÁËv—ä`²Z£‚)¦¸G²ß‹Pç÷†È¾ˆ£òkå÷wïÖ÷È5í&$Õ$ú¡ß:è‡~ ýf ú¡ú¡úùæE?ô«tVýÐoò ú¡ú¡Ÿo^ôC¿JgõÑý&ú¡ú¡úùæE?ô«tVýÐoò ú¡ú¡Ÿ¬%9G_r^ÿ®Š¼¹Ò:/˜?šo! k2—M½‰düZù™F�c�¯#¾å`2Å6‘,øÉ£^Î~eyÔµ)Re+å+²ì#ÿ"è·ÃïoŽ||׌ß8ÿ5 úùúG?¹’¾äÐïýÐýÐo<ôkè‡~è‡~¶;ü¢ú5ôC?ôKA?¿•è‡~÷è·§e|É¡ß=ú¡ú¡ßx"è×ÐýÐýlwøD?ôkè‡~è—‚~~+ÑýîÑoOËø’C¿{ôC?ôC¿ñDЯ¡ú¡úÙîð;ˆ~è×¾býÌæúR�éÐùí�û+wÍwV±å•÷Vš¿…'¥ØDñ‚*¾°oîb9;yey//Xn·8#e¿ƒy­|å82¶¯A½ž‹Ï÷:©7Úøµª÷‘)¿ú.È+û‰û‰ ú¡_C?ô›>è‡~èçëýä4åíÐýЯ¡úMôC?ôóõŒ~ršòvè‡~è×Ðý¦ú¡úùzF?9My;ôC?ôkè‡~ÓýÐý|=£Ÿœ¦¼ÝÓï�Ê^ãç„ç—Vy[N¯�š¯ßnñAÿéþµ (�œ¸¯ðü4¹¿^?*¿DþiòÊjy;y彫!kC¾m§8£êÙ÷BÑ(TÆo«Qúþͽ×ýÐýÐOöú™ ú¡úMôC¿ÕEЯ¡ú¡ú¡ú-ƒ~è‡~è7*TôC¿†~è‡~è7qÐýVA¿†~è‡~è‡~è· ú¡ú¡ß¨PÑýú¡ú¡ßÄA?ô[]ýú}}úÑ�%${§?Ë /Z}£M‘øž�GûŸëò݉bûjÙÛøqTùùJáëß÷»?÷†®4 n –Œ}”owC{›Wn�³¬ßl…_çúk®×ÏŸ›ïgT¬+¹×è‡~è‡~yvè7[ÐýÐýÐýÐosÁú¡ß¸ÈÑýÐýòìÐo¶ ú¡ú¡ú¡ßæ‚7ôC¿q‘£ú¡úåÙ¡ßlA?ôC?ôC?™êÐ[a�”÷•Ý!{Vne~¦ìho…ßq?€Bµl¯³<Úl¼çmgF½oö·Ø•÷û#Gh^2dÅn–q®À,�ÔL5¿ÜÈm•EâWCzî3„ÊEð+ù}ú»#?‚~ ý ‘#D?ôC?ô[Ý®?ýîÑýÐývýÐo´’è·¹V¹;ò#è×Я9BôC?ôC¿ÕíúóÑïýÐýÐogÐýF+‰~›k•»#?‚~ ý ‘#D?ôC?ô[Ý®?ýîÑý úùõ¹¡8ÍFßp®?š #–�7JVõ^uãJúõ1 Ç »#ßÎ׿Üßìð;õ/˜Ì û›¯ì»2ö].ÔøÌ‡_9rTF<µ‘Q†9_yÔï‘ü4þ¼r¨rF¹œR=£ßvm _C¿Á)ùôkè‡~è‡~× ßæ¡ú¡_C?ô¯ú¡_C?ôC?ôC¿õž¢Ÿ{д*ú¡ßœA?ôC?ôC?ôkè‡~è‡~è·ÞSôsšVE?ô›3è‡~è‡~è‡~ ýÐýÐýÖ{Š~îAÓªè‡~s&ê7ZçœÜwÞC©�ìʼàïld=È.ŽÐŸë¯&G˜§)O‘?®¸ >rÌþÕ-߯F}­üà½$¾8½EWýÔ|ä®e^Šƒ¯¯dq㞢n}Ðϳ�—ýÐotÁzßåsÑïýÐoì•™H½ýPó4å)èw�~7­$ú¡ú�â{ýÐïýÐýÐÏNÍGîúÍôM¤^‡~¨yšòô»G¿›VýÐýFñ½ƒ~èw�~è‡~èg§æ#w ýfú�&R¯C?Ô�\8zÙ§Ÿ¬+o…_½j)o®¼EÞ¹Yò¾òhn^¹Ñ2¹Je ËEË÷õ-滣ø´ûýŸ'àç[¿²_ Ã~”?wñæðüŒN6}ý¾þõË/é u%·ÒWûÞöÏ”K*_^_\¿%~ïsÝüòýVCE¿†~èWXÒIô‹Æ]¿ë½~kŒ~ãò“óE¿†~èWXÒWÔïÓ÷»kãΟ¿ã ÿ‰~ ýÐýÆ9�~áŸúVÆ]âÿU¾øC¿ÕPѯ¡ú–ôuô;ççëú-þç\øâýVCE¿†~èWXÒ×Ño•øÞ’ÂoÐÏ–Ÿœ/ú5ôC¿Â’N¡ße­ßê�ÿ±/ú­†Š~ ýЯ°¤Óé·úbï²ýÅú­†Š~ ýЯ°¤Sè·þ9Çepl[?ïÛ³”zS®Êss#ÈÈ“5_,N9*ß~T~üïlÌ¢ùçÇ,n�\+y—þ´þ†‚ö¨èj}%÷Ö¤<Åï~~ü+uź’Vô§eKeG£ü|åšùŒóõ÷ýÂú]ÐoôC¿ñPÑON ýF™V?ýÙ¥è‡~ã¡¢Ÿœú�2«~ƒn^ý;ßÎO9ˆG?ôkè7ú�^S¿�kê®ü…Ðo´§è×Ðo0*ô;½¢~£/êV?öØùé¦qâè‡~ ý£B¿Óëé7þŽvñ­oáÃMÑo¸§è×Ðo0*ô;½š~—»!kÑÅóÞ¿j'Ž~è×Ðo0*ô;½–~�ø]é��?? ýÐÏõ;ú¡ß(óê·L?x^>¾…ú ÷ýú F…~§)õ[ò·‰ú ÷ýú F…~§9õûæ|Ù�ú ÷ýú F…~§×Òï|ÉYùxùðï‚çÊÅFÓM¶ªÙ>yô�ÊÞ�çúÁø¦ÈSk6ñ¾™q¿^¿xÿA~„ÅùÊÂö+™wM®³\«½ƒ—µ!G"oד;Zž+›×Û’Ï})V¬'à?O@î Ü£<ªâvHÖü|S×|Á¿éö´ ß(è‡~è÷ôó-Vl"ôC¿Õ¨ÐýÐý̘}ÅúsÑýêÛ�~ ýÐýÐïôó-Vl"ôC¿Õ¨ÐýÐý̘}ÅúsÑýêÛ�~ ýÐýÐïôó-Vl"ôC¿Õ¨ÐýfÐ/Îñ¾¹§Å÷}'k)—_½JsÉy+ü˜å¨Š’Ô­(®¤�|Ç�Ÿf^«Q§ä‹ä£²£}9ýòšBòóåàem“{|s;ò)±ê ¿uWå•ýVæExP1»¶Ùey×®óE?ôC?ôC¿©ƒ~è—ƒ~õí@?ôC?ôE?ô›3è‡~9èWßôC?ôC¿ÑQôC¿9ƒ~è—ƒ~õí@?ôC?ôE?ô›3è‡~9èWßô{úÉ‚ôÝ‘[ì»ÁçØçx…ò"oÚ˜+Í_YŠ-G•ç++›É·óG3°òrFrÌ’J0}ÏúåÝ\|SluèLaKíã]¾·ÉümÈ/®éÃóž{åk�<Š~è‡~è‡~óýÐÏì ?Š~è×ÐoÐè‡~è‡~ÓýÐÏì ?Š~è×ÐoÐè‡~è‡~ÓýÐÏì ?Š~è×ÐoÐè‡~è‡~ÓýÐÏì ?Š~è×ÞŠ~qŒ›å—Ÿ&ß "ï•”çfµ$\q0¹U‹ÝÀ‚ïÙzòàåÓrUˉ�v³ÒÑò"~�å`*¶Ä†òµÁÃnMv´_gÿ:â=—çúÝ?Ùô¿y÷‡*R]ÁÜzrå˜Ñ/? ýú î"Ÿ†~÷è‡~è·¼ïJ9*ôkè‡~è‡~è— ýÐýFµWŸ¸¿ÎýþäÁû®”£B¿†~è‡~è‡~iÑÐýÐoT{õ‰ûëÜïO¼ïJ9*ôkè‡~è‡~è— ýÐýFµWŸ¸¿ÎýþäÁû®”£B¿†~è·¥_¬_Ãþh±†eý÷å®y|Ì>ÆûÊ ÷͘Ï�kÕ�öjIõ0´ÂÏÈOÜ×a¾È¦9Åx+ÌV>¨wøù}�‘P?yŠ/T¯½¯É|¹’ým¿ùñš›½íŸví8ôûô»G?ôC¿Yƒ~f0¦JÑýƒ~è'ƒ~÷è‡~è7kÐÏ ÆT)ú¡ßcÐýdÐïýÐýf ú™Á˜*E?ô{ ú¡Ÿ úÝ£ú¡ß¬A?3S¥è‡~�A?ô“A¿{ôC?ô›5ègcªýÐï1è‡~2èw�~è‡~³&êgdÐheýüž}ó‚ù»È=’[ï7ÚWK?*/’gäÛ­ØþÊò"¾µý²x+|%äáÕ÷×Ϩ¿ôÈð.™â|üyÁoTñË ß rFý¯¼ýâÕ͉-®Ÿ"?åÀ”úmV)ú¡ú�–ýÐýÐoo]¡ú¡ú¡ú¡úùâ”Gå•ÑO^GÞWúùeA?ôC?ôÛ[Wè‡~è‡~è‡~è‡~¾8åQyeô“בƒ÷•€~~YÐýÐýöÖú¡ßSôóÝáWRî¸ÜʾÑËÖF<êoá7:•õï‹3CStµ^árrüû¨ä:çí�-תø"8n“õàóÔ|ÕÝÛÈô3ò¯*~­üÑV‹tuoÕùûþâ‹Ü úùâD?ô;¡ß¸PÍ¢ú¡ú™•D¿ÍjÏ·@?ôC¿JÏ¢ú¡_ úÕëýNè7.T³€è‡~è‡~f%Ño³Úó-ÐýЯҳè‡~èƒ~õ:D¿ú� Õ, ú¡ú¡ŸYIôÛ¬ö| ô“u%ë!�›?àÝ𤋮ÒbaKä•ó¹ß…{§—·b�žŸ^#ïUïý¼Vþ"�8;®l¢¼ò"¾Åý& ú¡_ey}ÏÊsѯ¡ßÜA?ô«,¯ïYy.ú5ô›;è‡~•åõ=+ÏE¿†~sýЯ²¼¾gå¹è×Ðoî úU–×÷¬<ýúÍôC¿Êòúž•ç¢_C¿¹ƒ~èWY^ß³ò\ôkè7wÐý*Ëë{Vž‹~ ýæú¡_ey}ÏÊsѯ¡ßÜA?ô«,¯ïYy.ú5ô›;#ýò2Æ¿úÔ”z [ÖR«å†ÍzWK§\YK²ÒòŠ-Ö¶œ1KÏõG‹¯nÑ åâÆÉs3ß©È+gäk£X“ñh‘PyµÞPòå~´_äÁFžÛ#7ݬmœ8ú¡úíÚ8ôkè÷ÚA?ôC?ôC?_®èç{ ývmú5ô{í ú¡ú¡Ÿ/Wôó½†~»6ýú½vÐýÐýÐÏ—+úù^C¿]‡~ ý^;è‡~è‡~è ¬X)‰¯Ã|T®ü çöȆÊóõ-ãñÍÝ(äY“®ÊÒÍc–û’‡GØßc"_ÝÚb€•/uí÷ú&w¡¸û7Ì·À׳\™ûZâ}3z~÷ã¹�S�^Oê#ôÓåŠ~è· ú¡ú�¶ýÐo´,~÷cÐÏï~<ýÐÏ׿oÆÑÜÑ/�ýÐýFÛ�~è7Z¿û1èçw?ž‹~èçëß7ãhîè—lj~è‡~£í@?ô-‹ßýôó»ÏE?ôóõï›q4wôËãD?ôC¿Ñv ú�–Åï~ úùÝ�ç~�úÕ÷%7”dÐ×C±lü`üEâ`2tÞä8ß쌜¦œx®C9Â~åL–ç—(³&ŸVé‘kn´ïË"Ÿ™_³äVJ$f}6WÒŸ›—Å·@ÝÕ¾Dý±6úƒ«|µXŠ~š~T²°Ñýdµø¢ú­‚~è‡~è‡~è‡~{/‚~è‡~««¡ú¡ú¡ú¡ú¡Ÿ™/úÉQ¡ú¡ŸYsô3…�~¯ôC?ôóóE?9*ôC?ô3kŽ~¦°ÑïÕƒ~è‡~~¾è'G…~è‡~fÍÑÏ6ú½zÐýÐÏÏýä¨Þ˜~²†å®#kÀWiÞÙ;þvÞ¨,ÀwêÏ„ù*•Gýjȧùù6ñ{T<ší>,‡g^2FËÛÓ[¦÷ì ¯›ò¾ERnx¥(îþ¦“�‰nxgò)ñhð‡k~ )¾Tåíý�9³ i÷ÐO7`C?ôC?ô›#è‡~èW<×ï>ú¡ŸoÀ†~è‡~è7GÐýЯx®ß}ôC?߀ ýÐýÐoŽ ú¡_ñ\¿ûè‡~¾ú¡ú¡ßA?ôC¿â¹~÷ÑïÐúÉ^(Öƒ\Ÿ¾qÇóQ¿ãþ\Yi²Z »ö!½O}…Ë+Ë fRäÑþÈOCò¤QLß§ôû½Mn±‡ÁŸ3çÆTlÕ•¿ïS^ƒr‘Ÿl¤~²°sýÿ2$¿|Üp_ÿ5Ææur—¡_ÞJô»G?ôC¿É‚~è‡~è‡~è‡~èwó¨ÐýÐýVOC?ô›3è‡~è‡~è‡~è‡~7� ýÐýÐoõ4ôC¿9ƒ~è‡~è‡~rîkI+ [ûÝò=«.ŽéÏ÷%çkØß7Ƴ`úå>}½Ønqšy›­š¯ÓµŒçæâUûj×Þ©·‡Ä£ývrmû¨¼~7evÍþ^‰íWCž+û½8æä4O{Ÿ_\Óô·«Ï(Æ×UÚôC¿á¹è×Ðo¹è‡~è‡~•‰ËQ¡Ÿ¿]}Fè‡~¾°Ñýv�Ù—1ú¡ú¡_eârTèçoWŸú¡Ÿ/lôC¿]cöeŒ~è‡~èW™¸úùÛÕg„~èç ýÐoט}£ú¡úU&.G…~þvõ¡úùÂF?ôÛ5f_Æè‡~è‡~•‰ËQ¡Ÿ¿]}Fè‡~¾°Ñýv�Ù—1ú½º~ÒŠÜã±Àäúd…äÖû~ï‰mÊ›ìÕÍ]Y¯Ò¢Qþ\_ÿòirñ=¶Å÷¨¾q¦$ü:Ÿlä¹ò™¦„ä£$æjò‚õµÊ¥ëÇ,—¨ÿÕ¶_‡üá5ùï¸�› Íñ{dvüZ¨è‡~¥£è‡~›K„~è‡~è‡~è‡~è‡~è‡~è‡~7ýÐýÐýÐýÐýЯô«¯úÍôC?ôC?ôC?ô«ýêk…~3gô^_r¹|Wú£~ë}ýûÚóíæµ÷­í›±ŽF¾ \RyôÞ¦ÈK¿šßßxe3<¹€r÷¥º÷µø1ç—¹8†~´ÎQžfÛYuÅ×_Wr‰2ìõ¡ösåše¹?ò{=ü’¢_C?ôC?ôSË‚~è‡~£ ß„A?ôC?ô‹A?ôó‰~è·+è7aÐýÐýbÐý|A¢úí úMôC?ôC¿ôC?_�è‡~»‚~_(—»�¹œwê'ßÛW©¬Csß§#^rg=¥euÊÞÇZ»¯á¼DõΩC·÷ʲå˜s#Ëùʵ2KÚÂ;#äàý¨ò)£½®œ+Ÿ)+n¥,ìÜ2ñ:ù]~¨r�ýîË+çùÊ1ô£ñÝX¯­ß'û>ú‡~.è×Ðop.úµ#êð{ŸÍ/ÿÐýÐ/Ÿ‹~í€ú]¿ë½|úôýú ÎE¿v<ýΟ¿ã=—¾ùE?ôC¿|.úµãéw‰âU¾øC?ôC¿|.úµãé·ørï\øâýÐýò¹è×§ßeéúÙ _C¿Á¹è׎©ßêý�}ÑýÐ/Ÿ‹~ípú­¾Ø»lñ‡~è‡~ù\ôkGׯð­/ú¡úåsѯ¡ú¡ú�'Ž~óèwQúùßyýM7B¹)¯¨ßyý@/ä[ô#„{>ÌãEy#?«mÛôù‹»»¿xí1dýç®Ó¨ÿ™‚WÍç=Y øHó8›Âš}�Ut‘G†1û¤^.á� Ýhö—0ì¼¥Gœ”úûË—á §Ìðï iç˸°æžGè ;+ýÂ4é¤^4Ë¿ÿ{œÙß­²ù'¥^™—ÂOÏøðéHó8›Âš{qt~VG.²gÍõKü£}£¸Öo1òNê,¦qYÍãµÇ¸‘ËhC5�»Õ`�3§ß¸#&ŸÔX±óê?�ÇûøŸg]¥ÇšTîµOóº¬þsÚ|úÎýq­ß£ÕG{¨yÄøôÝâQæaôwÄì“zÑÄ¥ºiòâõëÈ“ú0ÌõPÿ?ýëòyý:ÓÿóPóXÎb1ôÙçq¹ûøãŽ‹Ù¸#fŸÔKæ’÷úµ‡Tùâÿ5{ÀI}¤ã¼Òoñ¿çÙ¿¬ð;è<–Ã;¯m8Àޱ污ßæ‘õ;R/‘õdû}ÿy½¡ÇšÔ§Û×áÉÿs<ºCÏ#üïQæ‘õ3q”I½DÖ uˆ#N$~1¨I]>•œÿ.dêÂüüò“Þhp¨y¨oµ~ÓÎc Ÿþÿ£Lê%r<(I?Ó:Ò¤Î×X¿O/?ê}n‡šÇò«Ÿs¬«£ÌýйSËrÀɧÍjRýëÖÅ@/ª0§UüQ?õž©cÍã:Þü«q‡™GÖo܇™ÔKd=÷óìPèœc•nR—>¼¬Æyõ¼i sõ>�õ—⇙GžŠþ=ª‰ç¡õÓq˜I½D’ “C1Èòì`“:®·¬_šåk�v”Ï_ó­ßMu¬y|ˆüö8óØÖ~G˜ÔKä`P ²õ;KsO*Œî èwþ<Ôøß‡™Ç7ÃÏx9Ì<Я˜ƒA¡³~+ï±&u¹[þ‹åñº­= /¼ kãÏx9Ì<Я˜cA¡“>ÇàP“:ÿÅòX…¹þ>ñ³…ÇšÇõ­þá§GûýŠY/Ëô?ÍÉâr¨I�ÿÅò¢æ5ma®>g"tѱæ1þ”ƒÃÌc[¿ÕÏ|�0©—ˆüE C½ÑE|‚Õ‘&u¹þ‹åy]³3#ž^bÎ}=�óÝñæQü}¿ó¡&õ9Ü/Ë ¬h;Ò¤Ä/ŠôѪ0å/ŽéqÏ?ýÿG™¿í\Ì‘ Ž_þ™‚ƒL ý&ËÝñ¡@¿bÖ5{°ïúõ7iR[ú­ÿ%mÞÂôúfipÇ›Ç@?ÝG™Ô‹Dü+Çqæ~–øjRN?õáC¯=Þq=Ô<Ì×~G™ÇàóýtGeR/’ålgþù@οãNʲ=3âߤe{p¨yýŽ2�Ñg;/ÿï|¬I½H6Þ#6s†øwRî[öé_ þÇ~¤yŒæ{˜yŒôÓq�I½L"!‡ú¦ÿ±.ÇŸ§yÄI­†‡¾hÃ)³èœá_š}«×Ô#ÎcøW�dGdR/“Ͽξúó}³çñÈ.1Ëš=à¤ÖP?N㼘Ѵ9¯G»ú{™Gü»œŸæÑëjöy|êƒÐ«Y}#:böI½ìŠÝ-r˜/{ïrÒŸ�>Ú¤ÖÅwÍoʬFVýHóXÏ"<ïT].ûè<:2ù<Î㪚}F?Ó“Oê…óy].‡qbK¿cN*—_ø´¥Ôåy¸ê‡šG„âPópú™Ž˜{R/žó‡+¸kæ—œu¡nRyü0�£þ8ÚËðÈ1æñø¯g9܉ç‘[bqØtÄÄ“"„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„�uþ½§‹Î³Mâ]%tEXtdate:create2019-08-02T18:30:07+00:00ÁZO}%tEXtdate:modify2019-08-02T18:30:07+00:00°÷Á'tEXtpdf:HiResBoundingBox306.59x297.184+0+0›%?&tEXtpdf:VersionPDF-1.4 G:xIEND®B`‚PynPoint-PynPoint-e5d0637/docs/_static/betapic_pca.png000066400000000000000000000706041512720201400227260ustar00rootroot00000000000000‰PNG  IHDRýÖi#ªgAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<ÅPLTEÿÿÿ‰‰‰¢¢¢ 1B#0»»»0h�#Lg/i�2c�2b�.kŽ.lŽ3a�0g�.mŽ,pŽ+tŽ1f�-nŽ/j�1d�1e�,rŽ,qŽ-oŽ3`�*uŽ4_�5\Œ*vŽ+sŽ4^�8V‹5]ŒªªªUUU6[Œ6ZŒwwwWWW*wŽ7YŒ7XŒ(zŽ)yŽ8WŒ)xŽ({Ž:S‹9T‹'|Ž%ƒ�9U‹:R‹;QŠ=L‰=J‰?E‡D9‚>HˆI‰?GˆG,{HoHl;PŠD7�E4AB†"Mg&�Ž"Ph#‡�•‹*°~'}Ž&Ž¢†%‚ŽB>…F1~B@…C<„B=„G+zF/|E2GbGe �Œ�ˆ!¦…>¼syÑQ†ÔI,±}G(xC;ƒE¿o×âýç$§Û3"§„G*yG&vÍàóåÅß!;ºuDDD222DWGf2µz�ÖCrÏU'­€GjDTH!r!ŽŒ™Š$…�'~ޝ¯¯$0"Oh8LÞÞÞîîîÝÝÝÌÌÌ p…ÞbKGDˆH pHYs,,sˆéRtIMEã& ·ø×nTIDATxÚíÝ÷ƒ$g~ßw`hùV»Ø`Ò!vwȸ;�¤˜$Êi‰”DŠ’lË9J’é$9Ò¶ÂßëÚÙ­÷~º¿ßî™ÝéPÕóþütu=õÄWÍìôSýÊ+ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1Ƙ—ΫgƳã¼zlÙÔÏs”¨Ÿ1æzfúýÜPÑå�cÌŽò¯¨üܱe»L¾7Tô�Ý0Ƙå�¨|ïØ²©Ÿ1æàQ?cÌõŒúc®gÔÏs=£~æP¹9&_¼5æµ1·#wÆÜóZäÞ˜û]¸o{=òFÉ›.ñ`Ì�ȱ;Òì(êgõ3ÓŠú™CEýÌ´¢~æPQ?3­¨Ÿ9TÔÏL+êgõ3ÓŠú™CEýÌ´¢~æPQ?3­¨Ÿ9TÔÏL+êgõ3ÓŠú™CEýÌ´¢~æPQ?3­¨Ÿ¹ràåá˜GHy<¦-µGjÉw#o�ámi…ÀàÛ^|Ü“qø­•¡�ŽBè±ÇÇlˆú™+GýÔo–Q?s娟úÍ2êg®õS¿YFýÌ•£~ê7˨Ÿ¹rÔOýfõ3WŽú©ß,£~æÊQ?õ›eÔÏ\9ê§~³Œú™KävIХ~ïŒáÜw"¼Èž‹w#`)Y«÷Æp4«û5¾yX’{= ´ÝŸòþ¾©H9iýsì±½ÆQ?s‰¨_^WýN$êg.õËëªß‰DýÌ%¢~y]õ;‘¨Ÿ¹DÔ/¯«~'õ3—ˆúåuÕïD¢~æQ¿¼®ú�HÔÏ\"ê—×U¿‰ú™KDýòºêw"Q?s¶ZÔ= CxµîuaEç¹øv· ,|0&ŸNOɼ?m¤2´"K†£VlºÖöÖÆÚiÙ^^¤ii#r²�¤íjrìyqÒQ?sõS¿kõ3çQ?õ»vQ?sõS¿kõ3çQ?õ»vQ?sõS¿kõ3çQ?õ»vQ?sõS¿kõ3çQ?õ»vQ?sõS¿kõ3çQ?õ»vQ?sõS¿kõ»~Á¨#Å–d�ÅË+¹Þ)� þßïÂ# Ò7Ö;È£(ôú˜üö4*ƒ$ùüJæa©Pí�÷#�A¡ü>¸ú•sº”z¦œ´¨ Áä¬UšcO¥™Gý®_ÔOýÌ“¨ßõ‹ú©Ÿyõ»~Q?õ3O¢~×/ê§~æIÔïúEýÔÏ<‰ú]¿¨Ÿú™'Q¿ëõS?ó$êwý¢~êgžDýN:¹Þë~�ÜÝ8¬»×#œËþ…Ü7A�øÐG�º°´³Îȉ´Ùºz4�¡iÔ9mä¼?¯K­¸VúÆ‹¨›÷‚Z�ôœ’ë÷Ç ¡ÀZ½!\‚·e­êþ”cϾÉGýN:ê§~fcÔ藍~êg6FýN:ê§~fcÔ藍~êg6FýN:ê§~fcÔ藍~êg6FýN:ê§~fcÔ藍~êg6FýN(uÚçÚa‰±s¯GYwi#oC’„‹ÈÛò\­ÀÉv� okK&­`J²7x‘KdORòÇcnEx[½¡ ¡ÀÚ?CØ'R¯•¡´¼.]Tq¾WáoÆ&^9öl�@Ôï„¢~ê§~/õ;¡¨Ÿú©ß DýN(ê§~ê÷Q¿Šú©Ÿú½@Ôï„¢~ê§~/õ;¡¨Ÿú©ß DýN(ê§~ê÷Q¿Šú©Ÿú½@Ôï„¢~ê§~/õ;¡¨Ÿú©ß DýN(ê§~ê÷Q¿yæNæ?ÛÿïF°…rÍÖäåX2­~ì¯çZùUn\—’sUV ò(�M@¿|ž§P“w#4�ê½Ý…ÒÞŠp ]šŠÒWXš×¥ò”œ}E{…´‘ëÖ{VrÚÞ)h/%§œt ¯ä½àØÓùHQ¿yFýÔOý®õ›gÔOýÔïªQ¿yFýÔOý®õ›gÔOýÔïªQ¿yFýÔOý®õ›gÔOýÔïªQ¿yFýÔOý®õ›gÔOýÔïªQ¿É'ç•$2óŸ‘VÔ� yÛ òº,(J{³Kkc59W%Ök AoÖ¹òI„Û½5)víü|'%·‚z#mD?®õ8Båa0÷ãÐ¥í¹4“·åÒ“íQ�Ê{Ö$»Òo±J�ëÖ8„B¨I­•OýêkMþtL®wáº)'Ò´Ï"T†Êg{ᨕ°v~ös=7âEš–»WØóþ˜#º”ÞÈëR …dÉô3ï/RïA¯G¨Lṉ̃×Ä®³OýÖM‹»?oÿáOýÔOýÔo§Ù»~ýÿ¯k§~%ê§~ê·ß¨ßT£~ê§~ûÍ>õ;ëô[®þ׆·ªŸú©Ÿúí9ûÖ/‰[¬ê·Øzò¹x‘WÕOýÔOýv™Cê÷ʪ~ÛOæ'ˆú©Ÿú©ß΢~S�ú©Ÿúí7ê7Õ¨Ÿú©ß~3iý~ù¡ú©Ÿú©ß.sHýÖþæ»X{«ó]‰ú©Ÿúí7ÿ¼ß¹yå±~Þ¯DýÔOýö?í<�äzg–2år÷=ËœUœóŸA#W%‹‚÷çó”¤ÔŠ%Ö…xY+ l¯ÅBfÝ嚥ò}?Âåà(wîs ÞŸ½Qij�áh"@�)-{²Ò”×eÐiÑG*@+ò>R1‘ýLõ¸VåºT/ëÌ-�’óÉ ”\K›wÔoQ?õS¿#dïú­=ãå¬üg÷NõS?õS¿}gïÏ÷{nZþc_÷„«íÅ«Ÿú©Ÿúí4û¶óêÿ-ª„¯\û§›ªŸú©ß²ýP­üÏÙÊû.øNKõS?õS¿�f¯ú=5®}˜}þßââOû©Ÿú©Ÿúí8»Ðoù4çž=ÍŠqçÈ-–kÄ==²Xy“ú©Ÿú©ßÁ² ýÎJV |žøív±zä"üÔOýÔOýv›=ë·ÊßÊ?í-^¿SÔ�ÉÜî¹`f¶ 1s½³ Z£XØ!–dɼ�:çN� HÖ™ñÊ瑺1¡]Ñ•ý!EÓÚïeãm) •©{R2Û·„<Ú*p7Rï#9 técÚ’©@ ÆQ¦Aöäc¸Vîš¹W’w(.Q Ÿwö­_ð·\û»Æbùø©Ÿú©Ÿúí6;üw¿ÈšrË'ÿ ØŸùäÈâ2Q?õS?õÛiv÷7ß=GýÔOýÔo§Q¿ãEýÔOýŽõ;^ÔOýÔï˜Q¿ãEýÔOýŽõ;^ÔOýÔï˜Q¿ãEýÔOýŽõ;HrAÕ‡ºç‚j7qÞ†QoG˜Ì\7Ïeu´âURr-p¬>ˆÔzææ¤ndBUÛ{ëÓZ ª–÷â)ñ´1û™é´O#T†Br4épÞ–•©Ãšú½V’Ïù§[Z“éɶ7*VY2§Ð?ÙÞº?%¯ËpÔW†Pà±Ù‹GýõS?õ›\Ôï Q?õS¿ÉEýõS?õ›\Ôï Q?õS¿ÉEýõS?õ›\Ôï Q?õS¿ÉEýõS?õ›\Ôï Q?õS¿ÉEýõS?õ›\Ôï Q?õS¿ÉEýõS?õ›\ÔoŸa¾áEôÈUÉbd2çJùr —ȧ T³d¶¨³"RN&3×Êo"cQp­lßéFÉù�`¼È2Éïe£™,Ïl/•g�ç¾~#È�ûœÂÒFòZJ¦áí3è´ôÚ�ß�ÔQÈ1z·$Û[§DºZosÙ"êLÓ²E|©oËZQÕú ŒÌ±WÛ‹GýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýv”\YL¾§,çp��­QÍsy‹÷A¦kÚÈQ.‘õg!³šZ+x¥õœ]y.—`gAÛW’êR+žÍd½×½!i×z#Bå·/dÞŽ—x+Romo|¿ w·*í½øžµv—´•BÞíÂÑÜÍQ{2]­_Æ×¶wŠQ¿EýÔOýÔOýÔOýÔOýÔOý¸„ú©ß´¢~;Šú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿúq õS¿iEývõS?õS?õS?õS?õ»>úÝŒÔgÈa¦ñ¶,‡…Àч k<Ÿ@Î)•²!ÌáÊoÊÀµîtaª§~¼ˆ~mÏд”4ÚUI§QH¶èã1”–»H@€¥~toËZQž÷‘*v­¤´ÀRÏì^:­•³öFîÖá”¶Eto½Ö*Óîôá”Ú?Ù:ºôØkñÒQ¿EýÔOýÔOýÔ¯íõS¿iEývõS?õS?õS¿¶gÔOý¦õÛQÔOýÔOýÔOýÚžQ?õ›VÔoGQ?õS?õS?õk{FýÔoZQ¿EýÔOýÔOýÔ¯íõS¿iEývõS?õS¿k¥_kÅög0I˜Ãí¢`V§«L¹OÆ<ŠPrÕrH=z;Bå±¥ÝÉŽ¹*ëbÌ]ÿ_�ám·»PZ¶ˆHM²7è„ÏÆäÐÐÞö B'´÷ œ¼q”ÞÈZñ¤J~Ô…kµbÓ-­ºôm¶—)ÄPæ¼¢j+†Ð�w»Ôé×Þ¡ÚçTðâ±Wjõ»ZÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOý޽R»¨ßÕ¢~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êwì•ÚEý.�\Ìafxî#àEæaNûưs†39™áí”ãù"Ö-!C*8DhfÝlò(ö›´{Lø²6ÖZ:S¿óëë`Ò¢t†X÷†ä·§ñ¶ü~4 ¤ionMÞ¡j·äèÓLZ”íåj• Õ)ôM¤~Ë[ö3/2[RÝÚ¢;]˜-Ù"ªJå³7ê ¢½ç"^¶— Ãû�ºˆW£~—Žú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúqŠú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúM%êw騟ú©Ÿú©Ÿú©Ÿú©ßÉëLjçL«{2í¢ ¬Yfi»v˜-9—Xø ‘’|;†yÛ*D!y”‡Ò·{L%§Q,(º%�²‘RÞ‹°*ÛMÃ[£Xwô|ëF»î¨^ÓÇñ,woÖŠBêÂ϶Sù¼ãÐWÔóËG·ßG£œT€êå�‚N£iy] äZÙ¢ª_îO©S(Ï¥zT ïõÇXÐõ»(ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êwœ¨ßEQ?õS?õS?õS?õS¿k¨_®hŽò…V9™ØœÛ>Ç ‚3„eÒ>©€È+¹W�YÚ®wJnŸ{@CnoÍçc²dÖ,¾åºc¹1ÿs�<-‚îvÝñþÜÅÿÅVqZQŸ½�w F¡îô¿ßgÇÛ²h8GsŒ¸.u~7ÂÜàKÖò¾ÉÛh㇚†ê§~ê§~ê÷òÉN'3Ž9‡éùíë�QΣu’äQ&$߯•«»ûzJÎUVgø§^äÜ´‚YÊu?�ÔÅ›uæ( Ïý •²û lo.Rë9äµ’<Êc°rOM=š=Y›–+ê™×­M˾¢‹xÎ:††keë(°n y[-¨Õ­¶Mã ë4x{‘hxNE^lÛËÒáY2}Åwºµó™ê©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ß±eS?õS?õS¿ýê—ë�¡oWk–£ùÉy l?Á~·äv®ÕîAh?uO˜!Ù¢íŸÉ¯ÛXrs ÕË£ôÆ[]ê "W}Åûs÷ ×eEäx}1¦mÒWi«’sÛö¶ý GÍ5[{#BåÑ ë\çd;F¼’ï¤2\÷ýú1'³äz3Ê{P­@Þ¿ÊÖÆz¹¬óG%9Fô3%çÌámÔDýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýŽ-›ú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúÍK¿Åòìi–ËrìÙ¡åâ`ú½aºÒÛ·"t8¯$\u™¿Ö¥ÝÂ_×{ÖŠKðÊ'¦P��CЀö>Šl?ލUúF­¨|ÖŠ97WV}t@ZñÕ˜ïÆ¤�7KîuaŒÚ~æm¹¢©õlï’mOR:ïtµ‹ræP õÌ’Z”ͼ_’bSú­þN»LäVþç2?ü©Ÿú©ŸúÍK¿ç?Ó-ÖÁ[¶GÔOýÔOýNL¿üßåê!õS?õS¿ë¤ßó#Ë‹ÿì«~ê§~êw ú­Y^üÃß•ô£÷ò³ñu/@ŽƒË.Œd�)ÄHµs˜�²ç˜r98Êo^«ë=kÅ%(íA¤N›ôpØï�×E!fæë]Ú9ÌQ*�ó™Nh­ ò¼’3¼.“ì™G%9¾Â%ò�t’äu©*¯d'T@²Ÿëv’ÜËC3Û»j¥,¢òuãF¶¨Þšïņ&®ûE„µ@ÃsùÐE¬£ì�ú5ˆ9FÌ:^I½)�žuæ(Þ�°~ÏÂ[nÖï¿úªŸú©ŸúÍG¿•éi¡ú©Ÿú©ß)ë÷Œ¿ÿ9þÛÞ²Óoûg^ÔOýÔOýf¤ßÚv6þ°QþʱlõûAä‡ê§~ê§~3Òoå1kÛÜŠ’åäµ]Âê§~ê§~³ÑoÃ3^ÔOýÔOýN[¿MÏxQ?õS?õ;iýž=¿ þêÑØy“~ß‹¼ª~ê§~ê7ý6>å üÕcÏó½½5 _;™T©§0.9ÿëãÐS0 ©¤dj!CØq¿ 3�µ–çRç:!sNNîÈ Îݾ×#Û[ÙÏfrJkíåýyá\Žf‹AÞ–#ÈÜh×;KŒ1jÏmW%pÝéRoöF½_çþº”Wò‰ñœKïµ·-ª—wЇ%Ù" ¡äv·N;'k_åø2XÍ;2í¥Ví:š¦~kŸk‰ü•Çìùó~ê§~ê§~ׯÿõS?õS¿ÖïLýÔOýÔïÚê·\¡…qßO9P?õS?õ;´~~öëžpµ½0õS?õS¿SÐoíÏû~º©ú©Ÿú©ß!õÛü7ß5/ñpSõS?õS¿ùè·Øøy¿ÕÿYœ]ü‹¯ú©Ÿú©ß|ô÷wÄ^�ç>?´² DýÔOýÔïô[ßæ[n¨Ÿú©ŸúÍ^¿�O9(Ç.ÄOýÔOýÔoNú­>Þt¹ú‡�xúÕÅø]M?fK®Yæ[¼sÖ·µ£V§ëãøF3v‘· ±"²V•✇ËÎ÷\YÂû¿©ÛðSìêùÍ.u�á(×;ârÌÛì�Zr^Žñþì ú¹öü�Ú¥�º¼Ù…•…©_=÷³“�UœªÐLî#9ë(�ʧ`4üË1Ù¦#þ Â%Z±ë %Ç·ÞȲVt8–fOÖ†·óŠy˜µª‹1{’†OW¿Á¿!ƒoËå¢;öäÈâ2ŨŸú©ŸúÍL¿EýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔïôõcºòyøì½ºs.1IxÎ%¦:%§`œBòsõŒ8ȹT�æ¹\Ž·¥$-k„¦ñþ”“sÙ^‘ë½vQÖ™BtámHÒjPÅ˵ϘæÕ»[¶ˆ’¹VöDÐϹî¨÷¯LÞŸ£P»4kE�©^Ž/`ò¶ì+jõõ˜·#[ ÏTK³þŒBŽ/ çýFÞÜ*_ï•7c?%g‹xÛ']f°×CýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔokjGå\âÃö•Á!,U^Éq©æ\b‰1«sÝQ«öèøV±zÏ9çZÙ"ÞÆ^€<—Ïä·3¼NÈœÃÕ·<—ná‰ú¹�ª¾Õ…µvE·÷ ^¤¯òº4œWrçK½!f{©3¯är”¡¼¹5í�‘ÒrJÝÝ�µâmô|Žoí�|î=ç2FÙ"ÔeÆfɵ{sÖÕÊå× ]DÉYg®Ë%ò(s’¦©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ß´£~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~§¯ßí.tTÅ@Êk]˜‡y†ƒ·µuàèëVX¨åÄFÎw#Ÿ•Ü�0¹VÖ™©^×Ë:ðƒ1©]úƘô�sé–,¹®¬¼p´>#"{†È£u ³Vô—ȹAe�'å ig]ûM‚t ãò^¤N°G‘: ÚK!Ù“œË�%«Êå¨UÈÛèÛœuÌ ÉQàauL³þí¢ ªUø ½—b«Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúM;ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êw}õËQ£èÏv†³"ò(0|¹»¡.Š»]ø}~F�BX¹² …ù“-¢z\"W4/Ö9Ÿ©{†P5ÉYúfIÖ¹v~®;êÌdÎ:3 \ëA—ÚiC(„;K®J:œBÞìRï�jE®,Υó¯hÐåQ†•WòºHßæÀÑÏŒZÞé(¤îÐRÛ›£_¡kg;ɤ‹jÓ†p‰JwŽ5H9ëdkz!9aÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOý¦õS?õS?õS?õS?õS?õS?õS?õS?õS?õ»¾ú½a ?óe„‰Áˆg9t8s �ÒÉ njÀÄ`ïCŽ)ÛñœÿÔêGcrÝQÕvwAÝ,¹.ŠÛÎýfLžËt¥&y9þNÞÆu¿�ГÌê<—…¯"”̘þ8BÒíÖNýz #þi¤j�³®6¼_š–’ГL’¼ ž3sò(—ƒÐöºŒxÞ‘é *Ÿ³Ž·Õ»CšL²VT†÷ç�¼XïïC(°�±u1æ(¨Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúM;ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~'¥=ö R7éç¨ÕÉ™û¾épBö-—có\ŒðݘœÌp^¹Ý…µ–ó�:3!s.qJ]bCê—Ê¥Øu~¥�º—ÈÓ·¯w©> ¡3Ìáì+^¤´šÉ™í­Àæ|®_Æ—srû¹L‰:Á>Ї0XÙ^ZôI ¡ä¬o£7ÔOýÔOýÔOýÔOýÔOýŽ-›ú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú]GýjÓnE˜ê¬Žœ¥¼­Î�!ôc‘}Ë4hcu0m¶ÏÃ<Š3uR=Šo£V9ÓXu�g87ûŠ:sô³ËœB²VT•Bòr·J²¯*ÔÙÏm×{]¼9¾Õ™¬ͤžíŽ›�iÏmK¦òŒoÞsa�x=Be($ÛËøÖžýF­Z£êv’!7K²VuóžË)´(K¦E,Õô­ŽQ»BÛ£µÎíÚçê§~ê§~ê§~ê§~ê§~Ç–MýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOý®£~,·ú9ü\ƒ }Î>+tÙ·u(s²µ„9ÐΖKŽx~Ú¿.Àö3ùT/wdp ÞŸíåöJäQ|£[Ò7JæZù©{:ºsò"’äQ ×z½ øQ„ª~>&×]ú¸ o£žùd{ØçmÙÌ«;]*\Fx‘Èû%3Xùx*_¡® ædÎ+NáhÖ™¦Q«<·Þ7óºÔŠáh÷ÅÔžÂè3ýîGê�,­c >êÂĦ4õS?õS?õS?õS?õS¿c˦~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êw²úÑ{¹7Ÿ™Æ -R§Ak×Ê£LÈú¸ƒñ�nœ›£Æu+ÎC¸¥åÑ ]¶¨Ö9�âEú*õ£äú¶!?ó͘4™KÔe2„Sêíf— R! aªçê F0Ï­Xå�‚d�ç·ÅÑáL°¤˜‡f|7&­à(4Èûf…+Ï¥iŒ~«.çæ¼¢˜`ÙÏ t{].Ar^ÕNHÏé«:±‡¼S’ó¹Ò”}Åèsn«=GÛµ ~ê§~ê§~ê§~ê§~ê§~ê§~ê§~“�ú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ß©é—+‹ÉLÏçê`òþVƒúYú!tc«ó�}/R7›¤3o–ä ¯[3ÞŠð¶{]êBÈÞ¨üæŽ z’:¡E¿0&?ÏÏ&ÞŸ}ÅQöhü4ò‹c8š£@Cè�÷"Üæ~iÌŸŒüòê™%SUžP2++¯Ëä•Ü‘Áâå¹öéÆ7GŸ)Ñî1a°Ú»j½]¶w™zÓ¿^0-‘àmè—óŠ¡©s†PZq» ­È» àZ©.ç2©_•!åT?õS?õS¿c˦~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êwÊú嚥9´1g �)i£V‡`/~Ø¥ÎÃ,™žgÊå<ä\fHŽ ;#hEŽ)/2!ÓÞö  “Š÷§ÉÔ —ruÐLÞöM„SêÝaûDè�_‰üêôË’#QŽíýµ1¿¡ú¶½Ñ¥¹Þy‘sÛ¹Qïwc³ ÏÛϬ·žl— ´t¦ž›3‡�'µ†P2¯ä:ªDä=—™CÃs¶3Ix%õ»Y’%3´±¬*�©3|·†#ïAê§~ê§~êwlÙÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔo®úeÏÓœV¿wK²o+>ùN^¤osLy×ÍôœÂ¨e­ê˜f(�G4dÉÂ<Ì5Ëãmí<䕜BwK²¯€«>âµxDõ?oLõ|BXñ]l¿áKÖÐ2ï#7õS?õS¿“ׯÛжìôÛþ™õS?õS¿yé·š×ßZ øAä‡ê§~ê§~sÑïùÏ|ìg[nÀ®×oÝNõS?õS¿Yé·ˆŸã¿ÕOýÔOýN[¿á–ê§~ê§~×D¿Õg/¢_Æ¿z¨Ÿú©ßlô[®ý%w¹ª_þÕÿùªŸú©ß©éŸkY@\Ùü"Ÿ÷û4Bà쟪Á;‘ÚÉ?�°m�Ò²Û\*�åÜ/É£7KòÓï\‚áË1]©@îȨÕËër”¾Ê:0™áy”ʰ´óº,F°ÊùOå¹î/Dþõ1¿8æÏGþ®ûãµ¢�¹fë=è›Èo�ay¾×…Þhgwáün5¾6ŽÞËUI?Ó9OªBÙ“\w}ÏÂy8…:çQZÔî×€ˆís’:ç¼¢À:â‰|{7¯•ϤVtiÞ™u,ŠlQ�M‹©ß+ÏÀS?õS?õS?õS?õS¿Óï•íú­ÿ‹ ú©Ÿú©ß)é×þÕ£}ÂÕö²ÔOýÔOýæ£ßÚŸvøµ?{¼ÐÓMÕOýÔOý¦®_óüú³8²ú9è ¾ÓRýÔOýÔoFú­°¶ò�{ù?‹³‹ñU?õS?õ›“~‹çö[ÿÚÞõ#—ÿFKõS?õS¿Éë÷Œ¿çYl:r~¡_®;&$ã’ýCß2÷r¦ÑQ¼-{�ë^‰! ýù¨ =Ÿç>,ɧ2·»0¦uçÈ�ú¸ûq¦\ëp;K®‚e3iZ»p~~ÌïDþ☿4æ/Gêår×L{cªám©ØR«¼3þÒ”Îë2Áx~ö]ÚÞ¡ê$ɧµÓÞvNRyÆ4g�Ï+9 uôß�pn+'×­¯¬­©§É¾âru[Ön—¼-[´…îü‘†s߈TNïF£ßr‹MG.ÄOýÔOýÔo^ú½Ïv^.6¹¸õS?õS¿yé÷Ê“ï4:ÀéÆ#‹Ë¢~ê§~ê7;ývõS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õ›~žë—ý\iÊÍ”»Ù…)ÄHµ#ÈÛÚ}î\"çRíð”„%ÆHýzfWë$£œó�ÙÂ+Y«º<Û’ioÎŽòð‡œá¿;¦5™NûÙ˜_‹üÞ˜¿2æ·#ô%ߨu¨I®³’y µbŒrø.9Þ–³®N¡<—N㕼÷�$Gó»ä¸Dí´!�öèÍ­©ïÏr4m¤2µCX>¼’6ÒS·�“Ìýúv9Û)„s³¯8¥Õ¾>i$K¦ÓêüÂÔ¥´ì+*O!©ý³ÖÎð;ÝÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýŽ-›ú©Ÿú©Ÿú]¬ß»‘úÁïìF�qIÁêæ‹\wèWçÀ*Pþƒøî-^ÉžgøÓO#Œi+ ½Ù 4žçÊ¢d消·ÝéÂÔe™ä<¬_@–ú}>~óèßó7ǤQ�JR³%1W4—àîöm„Ë1jy.ÍdÔ²† k�¥Cê°æ(ÔêåQJ®Õû0¾Ý�ùŸêr”žÏŸ"¸n½£ ¡@*�÷kæ�}Îånw©?¨d_q#®]:¤.�ôœB8ÚLõS?õS?õ›tÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýæª_úFÿ´ŸçEFùn„çm)§PZ®Y6Ôk aê¢Gvy]2í>‘viS2ó'Ÿ0ψ·3­nëÈöÞ*i¯Û*Ä”£½)‹ýþVäßóo�ÉçÞÓÏ¿9&¢ÓZßê‹Y+–ö/�Iíw }›ûbêæ ý�a¦¥`\¢½PUÌž#¹ë{ÎÃQjòY„yŠ]÷=½Õ…Þ˽-µi¹Ê¸\«=3§ÞY†<.Édb·û�¨ ¥åZP?õS?õS¿c˦~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~sÕ¯iÅ�ôök6bóþt†sÛ þL ÉÙR,�û¯+zí“Zi#GœȪÌçPU.‘ó�iðÁ˜\wTàË1¹Þi&çf_Ñ¥¼’µâûÑhÑ×¾íowùxÌßóû‘z§¸Ù…:çÞ|ÚËlɧ€OÕc�_ß69™-9‚ }›cDeÚ§vJÎ}ýT Þ oÅf®•µªX¥BõNz· „f­hf{k†Á¶Îõ9y¯¯‹+[TçFsgW?õS?õS¿IGýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔïô£iL›ü´?�eKH;ÿù({Êù°$šsYbÙ· #õ° ­ÈïϪË$?WOõ(¹]Y´1 [¸V믤�uyæ'ö)™žÌs©µÊ£l[àÜ,ù/Œù»c~/ò÷Æüö˜ß� ¦}¡E´÷G‘ßCi¿=î©Ú×Áò³1œ›#HÉHò~„KPrÞ7ÁªG*TosC(WÛŠ^®£ lÎ:šhЍ¥å̧²Ÿë$Ï’k‹r©Ÿú©Ÿú©ß±eS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õ».ú½7†hyáhöX¼í\ŠœÃ\‚BZýHîŒ`GS!4`ÔÒs惛&ÿüÖK¶ˆN`eå*Ãæ‹4ŠB¨UEÉ–uæ\:-kU;-LjêýÚ˜;ò§Æüʘ\ÑõrY2M«¶ á‰úè—ç"ïÏQ`UÂ`ÖŠÝ+¿<&K¦2íøÒæRŽ £@!­~œ›#X§}îõ¨ >ŽÔ½<ùSDmZÖ™Z!m®£:…²VõgŒvÖñJº¡~ê§~ê§~Ç–MýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýæª_“ïu¡èí÷#œKoçlaÊ�Uö<£V÷‰¿«‚·"T†V´ë�£9[(™:¿ oËM·TºsbóJÎN­ÒvšÝé×……ŸE ˜EÔ>€üÝÏ rùÚ8î_Ù"º…üÕÈ/Œùõ1)ØÒ9ë¨ü�Çä³¾(II˜TïQdËÍhM£&7#T•só{÷è¢vÖ1ú\+�ÒÞ7»°ŽxÎI&×MíJ®›u®$f{Y¡uU>_˜ê§~ê§~ê7騟ú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúÍU¿œÃ4§]•äQz�O¼gÏs ú3Ïe–ÖÏág 9š6‚”Ÿoç\fK»‹¤’2„ÙRÙ7>WÏÛrÍò"Mk÷° "ï#t‹(K¦z¼?û™M!٢ߒ9  ý“£À> \ý�G)-}ûÅ’¬ á•ìg¾޾ú&‹Àž÷¾*I{w»Û¥N¿œ9õ« s‡QE2g,‚Uþ$P)nNà™F1Á¸V¶¨ª›?E0%˜ºÙÞí�¦~ê§~ê§~Ç–MýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔï”õËÐitã둪AöÅ+9)™Bòº|X½N¹“¡Ï£Ÿ5¶0Ór‚<ó!÷kð6Jû4B¨U~r…¨U;ÿÛ¾"í]æµ­¡2”œ®R™º|;†QøI„s'w°-ñrŒ(�Z¥�uKHî^áEž]Ÿ×å\^ÉöÖ»yž‹`v©—-ª_C�{¦x‘iÐöF½EáE ùùÈѼSP+š–s£ÞšóÜÊxûÓ çæýKýÔOýÔOýŽ-›ú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ŸúÍU¿||�eÚ´}ËÑÜMßÞì)Xš×­ó!cø(-Ïe°‹Öd*�çBó6[ÜU“)�:ß�pJÝŸ>„äáy/`ɰ"Úžd¿yŽQíÞ#ZÔ–ÌQ\Ê̓ø^¶ö›õ�çÛ¤0.¹vþüJþw"ÿî˜oLjÀuÊ܉Ï%µÔ€Q ¯R?«Îÿ!KrëòÉž¬_Š—åp ê™-b>×1ÂÜ &í*kåä(ÈÙÎêfóYê§~ê§~êwlÙÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýNY¿L�6ígÅéùœt ¥¥` } D¯5&6¯ägãÊVNZĪ̧!L×{ÞJBÉКüF*Ïûu©ìߌïí"¹²è^æmžË‹ V¥Ÿ™ÕG¨*}›»*¨*žù³cþú˜ì (ãKåþýÈ0æ/ŽIý¨*36Ç—mÔ9�ÒÏtKö$í¥iw»Pr»_£½nÝu'Rçs®”úsEÎX ¡zé*okçFÊ,™Êsg¼Q£~ê§~ê§~“Žú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ß)èÇÝé“—:Iò\X`«B.d†€soG°¢^ëv|ΜkåQ>9Ϩe­˜iõÓòC(¹J;„·QZÞ êÊÙR¿ �¥’®R ¯ä÷ @ ê¶3œÊg_Ñù´"×;“G¼ŸïÂ2I颿6æ?ŒüGcþã1#òݘ_ó—"y {=~7Be¨@î}øñF?Ç·r”FÕÌ™/ôd^—gþçœd²ÕŸ:rçG[J¦�íã›G™NÍö¶7Ä:·ÛÝIê§~ê§~êwlÙÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔïô«íó(FÑ{9—*t¹ÞkÏç¸p +úvÐH…ª�¹¢ëtÍÐ"*�ÚS µú<¹4-wîSfi>—€¾bve?×ÄÛ‘ŸŒùjL;ÿßìÂu�ëÊÐà—#¿]’ë�™éÛ2æ?óŸEþó1¬¬_�üƒ1ÿpL~›‹·½#ÓÏ0˜Of`ЊԀyŽ †£9sê=7×3–Wò ½T—îmõã\èNÁ˜¨”œ½Qï›yÏ­sIýÔOýÔOýÔOýÔOýÔïØ²©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ßuÔ¯¶1�a½3²÷êʯ@rE3m‚¬C%ôa„…@=sÄ)¤Ò=„ à[Žxe?õãmœ›-¢z¼’çR2=™G9…£oE>Éüö´û%ÙŒ=ðe„¡�Á߉€Þ�»ü•1rÌw‘ÿbÌ9濊ü×cþјœ9f ;AÚ]ôUJ‚`L’l/•M.y]&‚=ìÂäÌ©XåO_Œùј£:èy.-Ú¾#£zx#¾9‘»L–Lo0srÖQ« ýË'Y¬½º<;Oy]ýÔOýÔïTô{êܲ³¯P?õS?õ;ý– r�ß� üS?õS?õ›Ÿ~‹æG¼Äå³ÿP?õS?õ;=ý–U¿Åó—úåWýÔOýÔovú=Áo¹&ÜÊÿ_æ‡?õS?õS¿¹éwþ³ÝbM¿•ÿ]\â‡?õS?õS¿¹éwþ£ß*w¯¬ý(¨~ê§~êwzú-žþZÛè· äöíõ#tE~‚�çÞÓwºPHÎ%zž‘j?ϬÎÊ0|F=mä¯u¡�:1róHý,}ÎØv¦Q+º%Íá,¢7»Ð“Yrí´ÜE„d¯Ç{L¾Õ¥îAÈër” äÝ�'Û³ûã÷"ÿxÌ3&‚µÿvÌùïÇücþv„.Åäö ótiîå¡ÎŒiÖŠBèçÜëQg{¥Ó¸ÖƒH­Ìgæ£�S¢î1i¯[•~߉@§å*ã®Êê¸×¥]¿7.“ýë÷ ¶­?ì-/þáOýÔOýÔo^ú-Ÿ¹¶ýW]õS?õS¿Óo1þŠ«~ê§~êw­ôãôV|[vúmÿÌ‹ú©Ÿú©ßœô[¢\Õo±ö¾ªßDþPýÔOýÔo>ú-ž£Võ[W²êw¶õS?õS¿™è䩟ú©Ÿú]ý–g«»Ù^T?óU?õS¿Yê·ÈÝ{ý2þÕCýÔOýf£ß xÛÿêáß|ÕOýÔïtôËß{/z¬�Ÿ÷S?õS¿ÓÒ¯I«Ý•õ«, ©‹7÷׳*™KÙ¥¬JÆ%Ç”N®O¸_©ÆÏ£•µ‡¦.ïÏús¹ú¼…!uGyžËÛ¸D>!�ÊPù<—ÅȼÍñ"­È’yÊÁß“OW¨ØsݱÜXYi£ðY—_ó÷ÆüãÈÿ8æó÷#Pö?�ù_"ÿë˜2æ7#tjå]†.å•ì &r¦ötGóÛâêór>s¹ö±ðBßf�™9<î Ï­“$WJ=š#XÆx© 9[D{om�ú©Ÿú©ŸúMH¿õg¼¨Ÿú©Ÿú�¾~Ý®¶—¦~ê§~ê7ýV³úÓÝÚŸ=.þÑOýÔOýÔï$ô[ýÕ÷7U?õS?õ; ýò_úÚOýÔOýÔïDô{úÃßr1Bø’ßh©~ê§~ê77ý« ¹?õS?õS¿Ño•¿ ñ»H¿‡]èFVb~Μ1e”ow©e‹\+?gNÏW‡P2Ø^ùœi4¤N›!À~§ }E{óܺ%§•aîåÑznʹåCXª”–‚q]¾Åìû‘º rŒ`áǤ~ÿtÌÿ6æ�°…çÿó�"sÌÿ9æÿŠPg Éѧ7˜‡9ÙVZÔêÇÛr(¤•„Ñçh{ÝÚ{CêègÉœÛÞ¯hÚ›®rJýaã~|›!GSNÎEÚì &É4õ{e±|üÔOýÔOýfªßrÈ¢¾8È·\\æ|õS?õS¿yêwÕ¨Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ßÉêGÒ·:ПFè(Þ–ø€}›³å^IÎR*õ²†ubä\¢@Þ–-º_’çR=fÈëŽòJ*Äl©¥e{«i¹*io>�½ò›{êÍ(ï#¼�Zå~9gRN^üñ˜¿ùÓcþÁ˜aŸMûs‘ßÃþŽ"ucBŽ7DØÏö2œ›÷/ƨ½»1’ß/@?Ó-Ù“uóEÞe8Z¿h`•ÿé˜\T�ü0Â4fIÎIz’i�í¥ª•ý¼¹¨Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ßä£~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ÓÏeõK ØZN7æªd€øF­Ü%M7òþ´‘á`èsÄëÎýœáL¿:Žã{åªCê×±eÉÌ%Ú›=Ãå˜]w#´—ÞËyH¨^ÚXWbžË)íª¬ìç(ÔÞø2ò+c8š×E0F0Ïe¹ýö˜_�€$»þÛ;ÝçcÒ7–6MËomC?޶÷Mly'BÉ Ç›†ƒ÷gÉLÆ(û™Nk×HÉyÝzgL)™ž3¶ÞrýRU.‘µºU’£ *ÔOýÔOýÔoâQ?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õ;Mý²p†WrÒáL›�ºp4û¹’’3�K0jé ƒÉœú!¯dë¨s;Êu eÉLN.‘+š¦1ճΕߜ¥K²©*ïOÏ©{4ÒUj…oyÝÏJrŒØÈp¯K­^¶÷WÇ´½Á@×í3¯Ç×í1 ²E¼­¬Þƒ²'9z·Kµ"ïõu�äŒ}\’çÒ½m‹˜u­~õǃíí͹AU뮓!ÈÑw#ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ÓŽú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú]_ýZ èí÷#¼XgÈf)#ž{êòÌ}Œo˙Ƌ»0”ov¡2\÷' ©°ßާÓÓ´¬×¥Ú;MËžäEz2�aºþlL^—¦1ÿóþE39šóŸª¶¾ñ6ôxáíŽzƒÒrUR6näÓÚ+tÙ^6¹pÉ5[çCö§Ðð�#EÃS0^¤zy]ªÊ-#ãrõ9„ämÛ]ÍuTÌï ó©g®ºˆqyAÔOýÔOýÔoâQ?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS¿éGýÔOýÔOýÔOýÔOýÔo[è=ú6Wó°µ±žÒö|�·âûàHÎ4Na>ü8Â#˜��»PòýHÕþ­SŽÕ”}õnI®ÁZr*TëÜ>Ç –6`ykc}e¬Ñ´V¿ÖÕº´³½õÁ9¾\ŽÞ‹Ôo^K«`­$µ�YŽf{i&¾µ†Êç(ÔJŽB]PŸFèRz/kÅå¾ß±u©? ÜìBÕOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔOýÔoÚQ?õS?õS?õS?õS?õS?õS?õS¿�aU¦ték[ÃtÝ®_*Ä)’ó�B˜¥¹«¢r”ÎpJåhÈcxÿ�‹—ݹ„·µž×é×®,æ|v~=7¯K_qßiwÍÜíR·-äº J~· �–íå tzÎ@sM9y‘K䊮½‘PgJË[7›¤ºu(³Ÿë­'Û[o¯yn])ir½™¶wÆ»0úíøÒ�Ô3kEÃ9ª~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ÓŽú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿúm �œ#Ψ}7æ�H�ö¯G7÷‰0ÁÚ9L­ê–�!õ‰ú7#[àZDÒdÞ"@õ¨sÎR*Cõ²{ëöŠ´‚·q‰¼.M㕼õÔBò(—cy¦œ·JòºŸ”䊦@š–cD—¶Ú³*+VCèRÞŸ×eE�BCê-#û™¡¡[òºX;-ï›íýšNãºyW¥Ê¬ÞL³ÎœBõÚŸ@v©…¨Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ß´£~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~×T?’Ô)t¿K]¼Ió?ç§PržËÛÚÝèÌá;]€ŽϪֵ“çÖ]ð9—8…Ê?ŠT¬Ú•Eod‹˜ö\÷^„Þ ´\;œKïe­@ƒ˜¾Õ{AÞ¡î–|¡V·ºT“3ý|LŽ~í„<—Nàhö3�ÏÔmëLRl*SõHŸ©IÎ *COæ™.¥ÛG¿½P“_ŽnŸW/Ï�ú©Ÿú©ŸúM:ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~×Q¿G]—œáu¶r2â90Š·}a )9ϭˤÕ�šä dkF:S·„d‹øZ1êÜÎÞ–µ¢ò¼’%?,É;Åví—äQj…`y]¬ZáEäü&Ro ­3¬Ä”„ÉFÓ>�04T/{¦~+_zŽŒ`ÎB·¼×¥îWº[–¸Dö9í¥V9¯¨kçÓH59K¦á¯w©;PÒsÎU?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õ›pÔOýÔOýÔOýÔOýÔOý^> e»:ÜöƒýŒéý._�aªçL«…䎃¯Æ0r§çR½œT¾î+É¥ŠÙ¢ÊBžËäZ¹î˜êÌÿ×"õqñy.s¸õ�S¨gžË‹T/W%íe庣™¼ÿ£.ÍÞàºÌ�_î2¼?kŹíâ¥ÓÞìRçpÊYG0�ÖûHŠM—¶G©<㛣PoUù£Å;%YÿúbÖÏ«–WBOýÔOýÔOýÔOýÔOýÔoºQ?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS¿éGýÔOýÔOýv6ì·ëŽ0â9ÃÄk×ì½.LlÌþÝÌ'$Pù¶ªL9QÊY§P>Õ�êaEÖª.Þ¼îÃ’ì«zwÈZ1 \ëÃH½Aäú­¾å38…BÚ§XPH¢ÇÑ:[†ÐÏíšýɘ¶Ÿ!¥½ñ¶v Ñ4ÞŸÎ0�˜`·»pn{´Õ�†PrjVoù”F�gDd�™äT Ÿr@eèùö>¢~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê7᨟ú©Ÿú©Ÿú©Ÿú©Ÿúí í˜2Y³9X¼ŒÅ£.ÌáüV/.ÁÑ\Ñ\‚/áj÷z0!ÓŠ:Ãsr &dvç"ç»]è�·#´ˆ£9Ki%§„µ_E8—.Ím¹³ ¡Îôs Gyå§‘�ùrLû=eu L†·e{Ù ÂoïAtKÞƒh=™êÖMIÙÿ÷˜]¸D{ï£@êœ{y ‰£yámômŽƒNOfopníõS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õS?õ›pÔOýÔOýÔoÇaþäˆ×!Hê‚jEÞƒÞs· •iïŒ\·N°!t5Éû@Ú~æ†ÈµnG¸E2¦©/ò¶Û[“CÃÔ¥iG¸¢oW{¿Þ²_i%ïgÝ«Ÿú©Ÿú©ßÄ£~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~Ó�ú©Ÿú©Ÿú©Ÿú©Ÿú©ß.óZ„‰ 97˜Ã­œõy´j�ë®n~Ï£L ÎÍ™Ãåx%×ìöIÅÛØŸžžo_„†çºãlðOu©ûë³½¼�å:b¹q­\;,OF0û @xŽA룖çr9ê™Ï| ôÕב:ëò\®�uuæd­(°öíšöÁ˜|z•¯ý3„öÖ[äÎÅ´œ9\‚:ßêR¿Mïñ†Çq¨Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©Ÿú©ßä£~ê§~ê§~‡H�á9K9ÚÎ4Æ”E”kߘÌy]^l‡¾’’k¶˜×e‚1oÛõι­«Lõì ÞÆüO (�¥ýF„£ôÞå�â\ö‰|aÚÞ ')í‹GÙ'’žSGS äýyïãE6n´÷Ü:â9±4�b^1'ßëÒrÊÞ&IÖùqI¶·\ž[o*ÀûÛ™³¯¨ß†¨Ÿú©ŸúM#ê·R+^Q¿ê§~/õÛõS?õS¿iDýVjÅ+êwCýÔïå¢~¢~ê§~ê7�¨ßJ­xEýn¨Ÿú½\ÔoCÔOýÔOý¦õ[©¯¨ß õS¿—‹úmH�ê9[˜`¼?�2‡4¦ýô{�! ³:�òØvæ^nµ¨Ó&M®++Ï¥ÀÖ(®ËÒNIjÃs¥Ô #¹fYø�›±w£í+êÌšMyím=¯÷¬!lµ€…zá�ê§~ê§~;ÍAõKãÆßzÇ_�ÕOýÔOýº¶÷®ß³ßw×�[<ÿ�7þSýÔOýÔïPÙ¯~ñO}kÆ-óÿ.óßú©Ÿú©ßN³×¿zÔçC¿•ÿY\â‡?õS?õS¿�怟÷ËŸðV)|EýÔOýÔï„õ[®ë·vhûŸ}ÕOýÔOývš#é·öÃÞòâþަÉΈ3ÚµÃZËOì×é÷8RmL…˜T»p.ÕËõΦí>Þ–søã1´7ë\_Ì]ÌpVô�}Eïµ+‹’ÓF^¤y.×åý©�@¾Œð"ç~¡‹¸Ýä÷ÁÕû×ÝBYžËuiEU¿P/õ£zŒÚ7®[7_ ¡2íœä\ædÖŠ�®,WJmøf]Eò9ðo¾Ëæ¿»ÿW?õS?õÛs§ßRýÔOýÔïúê·h$õÛþ™õS?õS¿�æ`ú­€Wþʱlõ[ß*¢~ê§~ê·³J¿ÅÊN·‚�ú©Ÿú©ßiê·ŠŸú©Ÿú©ß5Ño}+ï%õûAä‡ê§~ê§~»ÌAô+Ï1¸¤~ÿê¡~ê§~;Í!ô«q)õ˜Ûß|먽ar21ÞèòÁ˜,™…ü혔„·q­Ô`»�ð¼M+Øò°]NÎMÁ¨ ÕË’é@v�dÉB³d�N­Ò 2ž×¥7ê eR½Ôé–¬síÀï§~ê§~ê÷2øÍÿÓÎê§~ê§~/…Ÿú©Ÿú©ß©ë·áÁÍkÿÎ7»§¨Ÿú©Ÿú]� ®uO¸Ú^�ú©Ÿú©ßN³gý6ýP·ög�)?Ý”¨Ÿú©ßÓ¨ßð[ûÕ÷7U?õS?õÛmöªßòl#kéâbÒßjDÔOýÔïiÔï’ø-WÂÁ3¾îr9ío´$ê§~ê÷4êwIýVÃÁÅêëá§~ê§~ê·ÛM¿Uþ.ÄOýÔOýÔo·9ž~¯,–/€ßôË0!™®9K™ö@—ó�éÇÄȧ °:(ùóH�®9Ky‘ke­Ø´V9ÛiZ…ý^|-•ÏQ+Z‘ΰîhZ¶·ršu†—ö(•Ð…·Q�l/k–ZÝêBÓîGJþQ„£µosæp/hçïO“)�y•=Yo‘Û{ò£…ðJÖ™ÛÛ+•ÁÃw#Âu�½v#{ý6óeÍš�Ë'ÿ.¸¸Laê§~ê§~;Í¿ÓíjQ?õS?õÛiÔ異~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~êG!ê§~ê§~ê7•¨ßKEýÔOýÔïzêWÅÝÈ;%¹*™6ovyo ³å“s˜k½Þ…9Ÿ‚19q)?±Ï´gÞ¦ºÌpJÎs©|ýÖ³!x¯ËöóžÂÂÏï{XÒZA’µºA&[ÄW¿q4ëÌpðJÖŠžl¿‡®B×Þ¿ÚYǹŒ`¥'«ÒCêF¤v8hZnJª36ÛK�T Ï¥K©g^îÈËXý^*ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê÷4ê§~ê§~êwäe¬~/õS?õS?õS?õS?õ»>úÕ´+ºõ�©Ë´Ér˜®í,…жäºvr½W4îG¸.jµÎÔYýZ<Û¼n‘²]ûº×#ï\‚B>Œp ×Í]Û×;%sÉsŒÁÊž¬ì§ºõ>’ý̹Tï½ g8¾ŠÔQÈ[M£ž÷»PÏ;]¸DöÕ«H¡dÆ%ïGS�®�úí(ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~Šúí(ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~ê§~Šúí(ê§~ê§~ê§~ê§~ê§~ê§~ê§~'©ƒ›»é™öý(ÂÛ˜ê¹v˜ºÌð<ÊÚ¡�œ¥_ŽA­öÙ -Π·ý9Ìê;[“µ¢\"WpîÛ–}•çVº³¯j­²EõY2…PòÍH}ž@ÚȾ“½Q¹�GéçúÀŠ!õšO¢ dZq³ “³/Žæ|¦2ߎɞd-Ô»Ò�I}ƒ[�ú]-ê§~ê÷4ê§~ê§~ê§~ê§~ê§~Ó�ú]-ê§~ê÷4ê§~ê§~ê§~ê§~ê§~Ó�ú]-ê§~ê÷4ê§~ê§~ê§~×A¿íaÚä|`É0»ÞŠð"ïÏ©ËôkW%aiçþJ®oRëœ{.ê2É=T¦ú?äý1Õð!u{EÛW,ü\•«”óqIZA{¹VêG39÷ÓH…+[ò´(§bE;aê}'o=\÷õ a8Ú¶?èR§Dö3r‰¬�_çÒÔ£~ûŒú©ŸúM7ê·Ï¨Ÿú©ßt£~ûŒú©ŸúM7ê·Ï¨Ÿú©ßt£~ûŒú©ŸúM7ê·Ï¨Ÿú©ßt£~ûŒú©ŸúM7ê·Ï¨Ÿú©ßt£~û 3$gS—Iõm¤®èœÃh@!÷»ðþœ¥ì¹hwC0ÿ©|>q½®ÊÔ¯njHIð 4jvsPçV¿Vn”–k–£4üý…ÔN˵O�³ET¯õœ9šuþ´$' $Vpîľ æR:U¥YçÚÌì+6}=¦�9Œoî©9ö"»BÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýöõS?õ›nÔoŸQ?õS¿éFýŽöãçL« ?OA!æaÎa&$GS] © ¦±Tà^„£”öu¤V&wß³*‘'­ÀF^iÛK=SN*Ã%>Œ%§oõèÝÈ–.Âs x%�ªMK£ê3²7*\y.µ¢Þ‰|0†Nkg_9—£_oÍÙÏ 4¯do{]!êw¼¨Ÿú©ß1£~Ç‹ú©Ÿú3êw¼¨Ÿú©ß1£~Ç‹ú©Ÿú3êw¼¨Ÿú©ß1£~Ç‹ú©Ÿú3êw¼¨Ÿú©ß1£~Ç‹ú©Ÿú3êwðÜ+I£X€Ìÿ<—‰�–©_=šßqV§nÎÿªÁ½.Ô*K¦í÷²U~³ÎTµ~÷\òºuÓG^éÒïG¨ =�ý\·fä·ÔUíÓ(�o}«ßaw+Bè盆ó¶û»1 tv — í÷ß±»'{²ª{"Q¿ƒGýÔOý&õ;xÔOýÔoQ¿ƒGýÔOý&õ;xÔOýÔoQ¿ƒGýÔOý&õ;xÔOýÔoQ¿ƒGýÔOý&õ;xÔOýÔoQ¿ $ya–¾Ù…·qî‘-Û†° 8š+šë¢Á�.,í‡ÖûrÍrnë* ©,¤¼?w7T|r½ÃBÕr Ù.'Õ˽õö‘£Y÷kd{©<çf�Ù¯A�ÓF ¬. ¡zÔ$w¾ÐE_ŽÉsÛ[íéGý&õ»¡~êwø¨ß¢~7ÔOýõ›@Ôï†ú©ßá£~ˆúÝP?õ;|ÔoQ¿ê§~‡�úM êwCýÔïðQ¿ Dýn¨Ÿú>ê7�¨ß õS¿ÃGý&õ»¡~êwø¨ß¢~7ÔOýõ›@Ôï†ú©ßá£~ ˳Îê!¬ÞÖ®Y$ù&Ârc%¦œTà�.¥&oE¨j} Á«… aݵ®RU(û4ÂSXÚ¹ëŸ.âZy´.ü¼SÔ´OWødLö$…`xêWïPécD­²Ÿ9…/YËçcp º%ëÌ%꣮oÔobQ?õS¿Eý&õS?õ;PÔobQ?õS¿Eý&õS?õ;PÔobQ?õS¿Eý&õS?õ;PÔobQ?õS¿Eý&õS?õ;PÔoªa括Ž~a©V4r½³êg¦•ièw‰_}ÕoöQ?3­¨Ÿ™0íN䨕2“Ïqô[vúmÿÌ‹ú™�Q?ó29¢~‹õªx‘WÕÏlŠú™—ÉõÛúÂyÎÖ¢~¦�ú™—‰ú™ùGýÌËDýÌü£~æe2iýüw?s©¨Ÿy™Lä¯ÿÍ÷�†7ü?ܘ&uÌÿ9v¥Ìäóÿ ¨üÑÁõ+�5¸øó~¯žcÌŽóêÁõ{‰O;«Ÿ1fçQ?cÌõÌ‘ô[ÆËvýþÙ«¯¾úsOüá÷æŸ'íxõØ•ØA~8´ãØuØEþðìì�]‡]Äåñbù£•vxýº'\]â´'ÃûƒÃ×vçyÒŽY|‹òùÁå†mòùƒ³³?8vv—Ç,²ög�‹ô{õ›VÔoZqyÌ#+¿ú^âá¦O£~ÓŠúM+.�y$ÿ¥oqvÉ_|ÕobQ¿iÅå1“œ‹·|òßÓ/ô½Ì/¾ê7±¨ß´âò˜I«w¾~ê7±¨ß´âò˜K/ŽŸúM,ê7­¸Éú׌͸ü×5ÏÇd­ÂsjÇbËÄšz;žÎ¢e{d㊘z£ö—ø¤³µ~Õ®C:ÇFuß¿¼ÜØÂIfã÷ Ω‹åæ‰5ívÄšØÚªþÆ4ÑFí5«ßÿ;ŸÖŸ­e¥æslTwg^~òŒo¼!Í©‹-kÚíÈÚmoÕœ'ÙN3þˆ?·_×õ[©ù µhš±\kDZëxA–›dVí8[«ì|Ú±M¿Í+bâ�:@�-Öþsy:bçÿ¹ègé¼U×Ú³v-×þs²yö›ûÓ¾~Ñ¢¶³jGþûóßçÒŽ-úm^SoÔ^“]µœSã›ûלõ¤šëU]ùÿÉß—ë÷þsVíXmÅJÕ§ÞŽåÙùŸ;–MÍ6¯ˆ©7jŸYÖ±>v•.[ó•ÿ[™³3lÔ9‹5ýVþw1uÅ—køÍ´«Õ[¬Û0ƒv4~ËŠ˜I£ö’Uì7Oà©gËl�:ÿÑomå­ý(8ñ‰¹yáÌ«›ï@siG£ßæ1—Fí#[Ú˜S²!3lÔâéìlô{eõ§«øæêÍ«è7ƒvTýóoÔ>²ÞØÙþÞ¿XÐy5êY�·Þ‡'þ˜›k7ëvÄÿÎ¥U¿-+b.�ÚGÖ;jÿF¶±!ùÃü¬µ|6å¶ÿ2é‰ùüöS6̪Ý/‰½~“mÇýúÿŸK£ö‘ùA±!åoZsjÔb¬àŒõ{vûéö¹Íª«?ý,r^Í¥êwÉœuÝ2ÃÆ—?ÍͪQüܺRÑe71'«øSýº=SójÇXßúѸٴ£ê·yE̦QûÈzÛgúïÕ�šÍ­QKªWÕX¬½o²smŸÇú�â³iGmJÿ9ª ·£×¯_³iÔ>R\˜8²z›Y£Ïç[Õ¯´òصݔç?ó­ï¦šW;ž¤ýv>í¸X¿WVõ›C£ö‘™A±!}fiÚ�Úôïê󚘫?)-×ÿ{6íxeã3^fÓõ»dfEŸõ­¼ójÔòlõ_,ç·Ú¨zT/nHójÇæg¼Ì¦êwÉÌ Š>å9³jÔbã¿XÎkb®ÿžøÜÂyµcÜêõ˜ÛoðêwɬwËäÿm/Ož_£¶é×=|èØõÝœ³�µ�U;¶üì7—vlx¾_¿"æÒ¨½dµµSþû@Í&üæÛ¨­lOñWJ7Ç̪[ô›K;6=ÛyõÿójÔ^rÁ±)g#~ómÔ¶_Ù'�øJåËÃQæÒŽÍó�M;6éׯˆ™4j?IBfõKÿ¶/ šk£ÖªšU_Y†“ÌÊÊÙøm@SoÇÚ=uŽíØø­F튘I£ö“çg_ûú¾©çé?�-3«sv†�êþrðtÛÁÊ®ƒif±^ÛµïØ›I;ò{9Ÿµƒy5õv<[±0ÖZõJ³"¦Þ¨ýöØê¿·ÏæÇÞ³šòµÓskÔúä[ljß$³¾Av±éȤ۱ފú{ðtÛQ—‡6¯ˆ©7j¿Yn蕉g«~3mT™}‹YÍËÕu´ØtdâíXlžUSoÇý¶¬ˆ‰7jÏyÞ/ËÙ8q‘~ólT�~ñ´¥ÌËÅÆ^ŸU;ŠYµc›~[VÄ´µ÷,žü[Ár^-_Ö¬OÔÙ5ª6âüÅ'͘ áOk»Üxdíxú¯g˶ºnG]+‡·¬ˆ 7ÊcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcŒ1ÆcÖóÿ/_û§¿à%tEXtdate:create2019-08-02T18:38:12+00:00L °%tEXtdate:modify2019-08-02T18:38:12+00:00=B˜ 'tEXtpdf:HiResBoundingBox306.59x297.184+0+0›%?&tEXtpdf:VersionPDF-1.4 G:xIEND®B`‚PynPoint-PynPoint-e5d0637/docs/_static/betapic_snr.pdf000066400000000000000000000264731512720201400227570ustar00rootroot00000000000000%PDF-1.4 %¬Ü «º 1 0 obj << /Pages 2 0 R /Type /Catalog >> endobj 8 0 obj << /ExtGState 4 0 R /Font 3 0 R /Pattern 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /Shading 6 0 R /XObject 7 0 R >> endobj 10 0 obj << /Annots [ ] /Contents 9 0 R /Group << /CS /DeviceRGB /S /Transparency /Type /Group >> /MediaBox [ 0 0 416.410625 312.808875 ] /Parent 2 0 R /Resources 8 0 R /Type /Page >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream xœ•WÉnG ½÷WÔ19¸L²ŠKc$�Sl È!ÈÁ�eE†oˆ?�%�§{â!Öpªù¸<>Vsy¿p¹*TÞãóµüYþÂÿo —3|®·ۥ³ÕÎd¢øz³þÚXjP„+ì´ýú÷²¼[žÿ7ŸñØÙ²¨T��Ú‡å18§Q…Ì73iµ�ו“µùLÀ®<ª±J !ä/‹qµáÞÇ&Š•UëØ±¼@u¾./ÎËó_¹0•ów‹FU2|z+Òë ¤­åüíòýXÎß—_Η ½°xÚÜ·Xkó`,\uôNmDŒ4>„½ � ¸•ù)¸ðмšyÓ89€£ÚM£Ånm~N´W´¸[k|×àšXuá°-ÜÚü\ªÍ¤�F]ü® © DŒ¸•ù)¸° –¨ˆ‹2E¿Áíž‘ÙK§½Áé¨>OþþéúîâúÛ›rqûáþîòîËçm¨û‘PÏ¿z›sžZ�´aTމ©­ÊhªÃ˜c¢{Õc`�²ta°½õ"�av•˜™â‰©Ò1¨ƒ ¦àÔke> †d*‘žÑšOŒ“–F„�Û íͧÑ4iÖ„;ÉMOäš(›õ-Ø7ëi¬�ê<Ü´X'Z†xjxã!°•ù$šdn� �¢p<3Q®"Ã!“´½ù4Z÷Ú›`²{Û¡�èšD«&cKþ•ù4šcàbtƒvh«®m¦”°^ž1¶ a›üBFS ÎoçÒÌç___ݽ¹yöåþÙÝýõçËòéÍ—ëû�Ã�å{ë°aV�!fN˧ËòG¹+R~+ØXf„_Y ù¸.©³�ÿ¿8i„4•Q^�•ç?_þs}qùêìE¹ø¼,ˆ2+Es£Èƒš94©ÇP-†*SV)p»j”¬�‹$-"°Dˆ 5@¸6ÄzÎv ªçà)ó€‹á#ÍL\»2ÖÆ)…‚¤ÓÉ üÍ[ ¤ë霹#|øiðÞªc;¶é°ÛD•æì<íß6(w a‡Ä´·Ž€G �]13ØqH¨�ÐiÏ#S3‹–u›ñ(*K3ŒzWpÙB¡R�r‘~…`ùèiµÞeÆiŽø½e^ÎP Hh²›QàÖ�TîD²AY^\|Áž @òcƒå«Ñ…l޵XsšIí÷7£ Œ*̤Я<Áäf¢M3ÆÆÐ€œ×ÔsÜ�Ò ô±6&Kp�sô*íag4 w³óiwÄî!½äJwƒ·u°¨Cß v0¥n|iï„ð¡ ´ mžG�� AÄŒ¡ »Ú �ȼg€q,©Îißß°‹ ‚ò°bÅr�¡MˆK†½Ùäv+âwêˆ.MQ—Y”9£ìØôîÕ’-ÊŠaË‘GøHÏæé„q´M}2Tg®°†" : È¤@Ídjr#CôJŠl¡¸c<›v”¼;v�LUÌ‘d³òîAÃÄ‹›@À=OÿûëK6W….Xƒè½£uB91×7Æ×�FŽd)‹o^:¶ö¼Õ@ËÇ<ž ᜓÑÚuL3F¤‹d ò@‚³4 ²ÓµÉ(ð•"KÜPb*7¦­2™ÜR3¡U#ˆI’ÇÛPÞ| 5©¹ìš"ŽŠ¤}yJæ£ö¨]Êòëåeù8eŽþsoß Ù÷ß!޼Àïwß.n�¾]à‰ÿõ–²=¿÷táåò/[ºÍ endstream endobj 11 0 obj 1314 endobj 16 0 obj << /Filter /FlateDecode /Length 165 >> stream xœE�;! C{N¡#€Ày6“нË;I,Œ%?¢+:öÊÃccÊÆk´±'ÂîR: §©Œ¯Š§g] Ëa2So\Í\`ª°ÈnN±ú|¡òìæ„¯Už—L 0•;¹‡õúQœ–|$» ‚ê”�ÊÂ0GÒ÷@LM›§…�ü�/yjAP™æ�k–•GkS4g˜*[ ‚õ� :¥úÓÿL§½?Üô=€ endstream endobj 17 0 obj << /Filter /FlateDecode /Length 338 >> stream xœ5RK’[AÛ¿Sè®jþÍyœšÕäþÛ쬠��8ÈÄK Q�rÅy‚ñ:�¿ëåiü>Þ…Ô‚§"ýÀµiïÇš5Q°4b)Ì>öý(!ÇSãßè¹Hfd2r ˆûµ>Ö›L9ü¤isú(Iz-ØÝv|Y�AÒ“–ìä‡u-°ºM˜k"9µ25ÏÇžá2ž#AÕ, IŸ¸OŒH3;g-y�áxª/~½�×2�©—U2¶ æ`;™ §("¿ $¹‰»hê(ŽfÆšÛ…†’"÷Ý=œœôº¿œD^ á,ÖŽzÊ2'; J jå˜a½ö…qš6J¸SÓí]±¯ßg&aÜæ°³R2 Îqp/›Ì${Ý1a‡˜÷îŒÄ¼ù]l\¹Ì¹&Ó�ý(ÚH×ç\M™­(áªä‰×øÿüÞÏÏ?�7}± endstream endobj 18 0 obj << /Filter /FlateDecode /Length 304 >> stream xœ=’;’Ã0 C{�‚ÈŒø“äód'•÷þí>2ÉV€I‰(/u™²¦< i& ÿÑááb;åwØžÍÌÀµD/Ë)Ï¡+ÄÜEù²™�º²:ŪÃ0�[ô¨œ‹†M“šç*K· žÃµ‰ç–}Ä74¨u�K ÝÕhY Ípuÿ;½GÙw5<›TêÔ�Qæù!O¢‡éJâ|<(!\{0FäSÑ@޳\­ò^Bö·²ÂAjIç³'<ØuzO.nÍd¹TøNQìíÐ3¾ìJ =¶áXî};š±é›8ì~ïFÖÊŒ®h!~ÈW'ë%`ÜS&EdN¶Xn‹Õžî¡(¦s†s êÙŠ†RæbW« ;o,/Ù,È”Ì 2F§Mž8xÈ~�šôy•çxýlós; endstream endobj 19 0 obj << /Filter /FlateDecode /Length 230 >> stream xœ5QInÃ0 ¼ëó�â.¿ÇAOíÿ¯Ò ``hKœÍx‰ÁÏAäÆ[ÖŒšø›É]ñ»< ·‚'Q^†{Ù .o—ý8Ì|ð^Z9“O2 ÙôðDÈ`—@èÀìÔa›i'ú†„ÏŽH5YN_KÐK«(OÙ~§ J´­êÏí³.´íkO8Ùí©'•O ž[²—�WLcôDå.ÅôAª³…ž„ò¢|²ù¨!]¢œ�'£@;°ç¶Ÿ‰W… uuŸ¶�¡)Oÿ645I"ä%œÙÒ¸[øþž{ýü¦TSŸ endstream endobj 20 0 obj << /Filter /FlateDecode /Length 245 >> stream xœEP»�C1 ë=`ý,{žwH•Û¿=JFp…!Z?’Z˜ˆÀK ±”oâGFA= ¿…â3ÄÏ…A΄¤@œõ™x†ÚFnèvpμÃ3Œ9ÅZp¦Ó™ö\Øäœ'Îm”ñŒBºITqTŸqLñª²Ï�×�¥µl³Ó‘ì!�„KI%&—~S*ÿ´)[*èÚE°Hä�“M4á,?C’bÌ ”Q÷0µŽôq²‘GuÐÉÙœ9-™Ùî§íL|X&™Qå)�ç2>'©ó\N}î³Ñ䢥UûœýÞ‘–ò"µÛ¡ÕéW%Q™�Õ§�¸<ÿŒŸñþŽ Y> endstream endobj 21 0 obj << /Filter /FlateDecode /Length 247 >> stream xœMQ»mD1 ëß\àëkyž R]öoCÉ� ¡/)§%öÆK á[¾ä‘UC?1ì3,=ÉäÔ?æ¹ÉT¾ª›˜Pbáýh¼t/"+Êß�e sÎ÷ࣗ`&4`¬oI&Õ¼3d‰¡ŽÃA›TwM,®Í3ÈíV7²:³ lx%âÆDÙ�Í`£Œ±•�År¨ ’Z`×éQ‹‚+”Ö t¢ÖĺÌà«çöv7C/ò਺x} ëK°Âè¥{,|®BÌôL;wI#½ð¦fR™�‘•:=b}·@ÿŸe+øûÉÏóý (\* endstream endobj 22 0 obj << /Filter /FlateDecode /Length 90 >> stream xœM�AÀ ï¼"OPDÐÿtzÒÿ_«Ô½ÀN‰E‚ô5jK0î¸2kP)˜”—ÀU0\ Úî¢Êþ2IL�†Ó{·ƒ²ñqƒÒIûöqz«ýzÝÒ"X endstream endobj 23 0 obj << /Filter /FlateDecode /Length 338 >> stream xœERKrÅ0Ûç\ 3ægãó¼NWéý·•p:Ý<ô  2=eH¦Ü6–d„äò¥WdˆÕ�ŸFŽDÄß)¡À®ò¹¼–Ä\Èn‰Jñ¡?—¥72ͮЪ·G§6‹F5+#ç C„zVèÌîQÛdÃëv!é:S‡p,ÿC²”uÊ)”mAߨ#èo<ܳær³¹Lnõ¿î[ ž×:[±m@ –sÞ`Äà ÚÄÜÚÞ)(UºŠI­ò\';P¶Ðªt¼7€9`Ã’­×hoÃ>ô¢FÅö ¨, þÎÙfÁÞ1ÉH°'æN=q:Å‘pI8Æ@Š­âºñØâ/®ˆšð u:eM¼ÂžÚâÇB“ïRqð"n]EµlOç ´?*’3b¤ÄÑ ‘ԒΗõÐû¸‰¯�Äæž¾Ý?Â9Ÿëû¸ƒà endstream endobj 24 0 obj << /Filter /FlateDecode /Length 52 >> stream xœ363T0P0±T026Q064b…C. ˆ•Ë˳@ªr¸ Ês`ªr¸Òé À endstream endobj 25 0 obj << /Filter /FlateDecode /Length 68 >> stream xœ32·P0P°4†& æf )†\@¾©‰¹B.H Äʳ €´%œ‚ˆ[B4A”‚X¥f&fI8"—É´å endstream endobj 26 0 obj << /Filter /FlateDecode /Length 45 >> stream xœ32·P0P°4†& æf )†\–V.L,ÌÑ–p "žŸ} µ endstream endobj 27 0 obj << /Filter /FlateDecode /Length 255 >> stream xœE‘K’ D÷ž‚#€üä<™šUrÿí4˜L6v—¨ý„J#¦,çy0ýÈòÍd¶éµ«��Š"Ìä*ôX�Õ�)â̵Dµõ±2rÜéƒâ 箜T²¸QÆ:ª:8¦çÊúªîü¸,ʾ#‡2lÖ96på¶aŒ£ÍVw‡ûD�§“©£âv�løƒ“ƾÊÕ¸N6ûê%‚~¡ LòM u†@ãæ)zr öñq»ó@°·¼ ¤läÈŒA»N®qëüÞÿ:Ã�çÚ WŽwñ;Lk»ã/1ÉŠ¹€EîÔó=­ë ïÉ™crÓ}Cç\°ËÝA¦Ïüë÷PŠfˆ endstream endobj 28 0 obj << /Filter /FlateDecode /Length 161 >> stream xœE�Kà C÷œBGðG|žtºJï¿­!M³€§±@w'©µÑ/mKº >[ ÎÆxè6n5äu€V¤ãh”R}¹Åi•tñh6s+ ­fz”£ :Þí¢är¦îÎùGpõ_õG±îœÍÄÀdå„fõ)î|›Q]ÝdÒcnÖköª°´¬¥å„ÛÎ]3 î©íý©s:„ endstream endobj 29 0 obj << /Filter /FlateDecode /Length 214 >> stream xœ=P»C1ë= äÎ|í7ÏË¥Ëþm$œ¤B6B”šLÉ”‡:Ê’¬)O>Kb‡¼‡ånd6%*E/“°%÷Ð ñ}‰æ÷ÝÕ–³C4—h9~ 3*ªÓK6šp*º ÜÃ3ú mtV‡±[ Ф`×¶ rÇ Á™‹" JMÿ­r÷RÜï=o¢ˆ”tð®ùåôËÏ-¼N=ŽDº½ùŠkq¦: DpFjòŠtaŲÈC¤Õ5=kµ®Þzù7hGt€ì‰ã4¥CÿÖ¸Ç뇊Rô endstream endobj 30 0 obj << /Filter /FlateDecode /Length 80 >> stream xœEŒ» À0D{¦`~&fŸ(•³ JÜpOº{¸:2SÞa†‡ž ,†Sñ™£`5¸FR��죰n_u�æzS«õ÷*Ovvq=ÍËô endstream endobj 31 0 obj << /Filter /FlateDecode /Length 236 >> stream xœMPKnD! ÛsŠ\àI$!ÎCÕUçþÛ±ÃTí*†Hé²UëSbu‰ìò¥M—Ê2y¨åOÛÿQ nÿÐÈA´·Œ�bæÖÅÂ$<å4#'×,�;Of…ÆÄ�ã�.`Ÿ5[•Í�9m:7@º�·ª³ÞdP "êBÍ“.œ§Ñ—ˆ9ž¨`Jw&µ�\>Õ¡qZ½ ¹c‹“oÄY¡ÀaÐLqÖ_֨ɲYÈ I†!Î}£{” �¨Êy+“¡õ†Õ •¹…¢©å’üÚ0¶Œué J�ªÁß*§}¿$�]S endstream endobj 32 0 obj << /Filter /FlateDecode /Length 49 >> stream xœ36´P0P040’F†@–‘‰BŠ!HÄÌå‚ æ€Y@¢8®&‡+ Æè & endstream endobj 33 0 obj << /Filter /FlateDecode /Length 157 >> stream xœE�¹C1DsUA °ê±ÇÑwÿ©ùJ´o-‡¯%Sª'"¦Ü×hô0yŸM%V,Ø&¶“rAJ1˜xN1«£·¡™‡Ô븨ª¸uf•ÓiËÊÅóhW3“=Â5ê'ðMèøŸ<´©è[ ¯ ”}@µ8�IP1}¯b�£œv"œà>G™)#qbn ì÷f¾W¸ÆãÝ7y endstream endobj 34 0 obj << /Filter /FlateDecode /Length 332 >> stream xœ-R9Ž$1 Ëý ~`ëòñžLÔûÿtIUªlË<ärÃD%~,�ÛQkâ×Ff Âð¯«Ì…ïHÛÈ:ˆkÈE\É–ÂgD,ä1Ä<Ä‚ïÛøù¬ÌɳÆ/Ù÷Vì© ËBÆ‹3´Ã*ŽÃ*ȱ`ë"ª{xS¤Ã©$Øã‹ÿ>Ås‚¾#hÀ/ùç«#x¾¤EÚ=fÛ´[–iG•–ƒíiÎËäKë,WÇãù ÙÞ;BjW€¢0wÆy.Ž2meDkagƒ¤ÆÅöæØ�¥]èÆe¤8ù™*ÂJšÌl¢ þÈé!§2¦®éJ'·QwêÉ\³¹¼�I2[÷EŠÍÎë™õw˜2«Í;y–¥NEø{¶ �ãêÙk™…F·9Á+%|6vzrò½šYÉ©HHÓº N£ÈKØ–øšŠšÒÔËðß—÷ÿ3â| endstream endobj 35 0 obj << /Filter /FlateDecode /Length 68 >> stream xœ336S0P°0¦¦† æF– )†\@>ˆ•Ë˳Ì,Ì�,# �–.C c0mbl¤`fbdY 1 ºÒrø‘ endstream endobj 36 0 obj << /Filter /FlateDecode /Length 17 >> stream xœ36´P0€ÃC.”ì endstream endobj 37 0 obj << /Filter /FlateDecode /Length 131 >> stream xœE�Ë ! CïTáò>©‡ÕžØþ¯ë0šABøA";ñ0¬óò6ÐÅðÑ¢Ã7þ6Õ«c•,ºzRVé�‡¼òPi0QÄ…YLCaΘÊÈ–2·á¶Mƒ¬l•T­ƒv<¶§e«~©maê,ñ ÂU^¸Ç ?�K­w½U¾BS0— endstream endobj 38 0 obj << /Filter /FlateDecode /Length 338 >> stream xœ5R9®Ý@ ë} ] €vÍœç©~î߆”_ C´VŠšŽ•iùe!U-“.¿íIm‰ò÷É ‰’W%Ú¥ ‘Pù<…T¿g˼¾Öœ K• ““’Ç% þ —Þ.çck?�#w=z`UŒ„Ë£kY:»Ãš�ü<¦?âr®X·cH ºqÚïCóyÈ f˜–Š ]V‰~Añ G­}÷XTX ÑíIpŒ�P‘€v�ÚH 9³»¨/úY”˜ tí‹"î¢ÀIÌœb]:ú>t,¨¿6ã˯ŽJúH+kLÚw£IiÌ"“®—Eo7o}=¸@ó.Ê^Í ASÖ(i|Ъ�c(še…wš 4LJÌ<‡3”ô}(~_K&º(‘? ¡_£ŒœosÑŸ¶ŠñÙa¯`…ÒÅšä}@*z`úÿ×øyþü¶€T endstream endobj 39 0 obj << /Filter /FlateDecode /Length 248 >> stream xœ-Q9’AËçzBsÓï±Ë‘÷ÿé ʃ†C :-qPÆO–+ÞòÈU´áï™ÁwÁ¡ßÊu9HÒTM¨]¼½vfó¤5,ƒë?c 7zqxLÆÙíu5{×kOfP2+qÉÄSuØÈ™ÃO¦Œ í\Ï È¹Öe¤›•ÆŒ„#M!RH¡ê&©3A�£«Q£�Å~éË#�aU#j û\KÛ×sÎ4;«<9¥GWœËÉÅ +ý¼ÍÀET«<p¿ÛCýœìä7ÞÒ¹³Åôø^s²¼0Xñæµ�Mµø7/âø=ãëùü¨¥[ endstream endobj 40 0 obj << /Filter /FlateDecode /Length 210 >> stream xœ5PË C1»g ¨�džV½uÿkmÐ;aÿBXÈ”y©ÉÎ)éK>:L¶.¿±" ­u%ìÊš ž+ï¡™²±ÑØâ`p&^€7`èi�5tႦ.•B�Å%ð™|u{è¾OxjrvCÉ` jºMX´<Ÿ�Nâÿ~Ãî-ä¡’óÊžùœíð;³ná'jv"Ñr2Ô³4ÇE> /FirstChar 0 /FontBBox [ -1021 -463 1794 1233 ] /FontDescriptor 13 0 R /FontMatrix [ 0.001 0 0 0.001 0 0 ] /LastChar 255 /Name /DejaVuSans /Subtype /Type3 /Type /Font /Widths 12 0 R >> endobj 13 0 obj << /Ascent 929 /CapHeight 0 /Descent -236 /Flags 32 /FontBBox [ -1021 -463 1794 1233 ] /FontName /DejaVuSans /ItalicAngle 0 /MaxWidth 1342 /StemV 0 /Type /FontDescriptor /XHeight 0 >> endobj 12 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 352 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 318 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 15 0 obj << /P 16 0 R /S 17 0 R /a 18 0 R /c 19 0 R /e 20 0 R /five 21 0 R /four 22 0 R /g 23 0 R /hyphen 24 0 R /i 25 0 R /l 26 0 R /m 27 0 R /n 28 0 R /o 29 0 R /one 30 0 R /p 31 0 R /period 32 0 R /r 33 0 R /s 34 0 R /seven 35 0 R /space 36 0 R /t 37 0 R /three 38 0 R /two 39 0 R /zero 40 0 R >> endobj 3 0 obj << /F1 14 0 R >> endobj 4 0 obj << /A1 << /CA 0 /Type /ExtGState /ca 1 >> /A2 << /CA 1 /Type /ExtGState /ca 1 >> >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << >> endobj 2 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj 41 0 obj << /CreationDate (D:20190802204309+02'00') /Creator (matplotlib 3.1.0, http://matplotlib.org) /Producer (matplotlib pdf backend 3.1.0) >> endobj xref 0 42 0000000000 65535 f 0000000016 00000 n 0000010445 00000 n 0000010251 00000 n 0000010283 00000 n 0000010382 00000 n 0000010403 00000 n 0000010424 00000 n 0000000065 00000 n 0000000399 00000 n 0000000208 00000 n 0000001788 00000 n 0000008891 00000 n 0000008691 00000 n 0000008244 00000 n 0000009944 00000 n 0000001809 00000 n 0000002047 00000 n 0000002458 00000 n 0000002835 00000 n 0000003138 00000 n 0000003456 00000 n 0000003776 00000 n 0000003938 00000 n 0000004349 00000 n 0000004473 00000 n 0000004613 00000 n 0000004730 00000 n 0000005058 00000 n 0000005292 00000 n 0000005579 00000 n 0000005731 00000 n 0000006040 00000 n 0000006161 00000 n 0000006391 00000 n 0000006796 00000 n 0000006936 00000 n 0000007025 00000 n 0000007229 00000 n 0000007640 00000 n 0000007961 00000 n 0000010505 00000 n trailer << /Info 41 0 R /Root 1 0 R /Size 42 >> startxref 10659 %%EOF PynPoint-PynPoint-e5d0637/docs/_static/betapic_snr.png000066400000000000000000000711341512720201400227640ustar00rootroot00000000000000‰PNG  IHDRÇ2hN?gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<iPLTEÿÿÿ£££‰‰‰–––»»»))) wwwDDDðöúˆ¶×w´.€¹–ÀÜy­ÒL’ÃÒäð[›ÈÃÛë´Òæ=‰¾¥Éááíõj¤ÍUUUªªª™™™ÌÌÌ222666WWWÝÝÝîîî… õbKGDˆH pHYs,,sˆéRtIMEã- Lp‡IDATxÚíÝébã8’®aOO·NWIÖbm93g¹ÿ‹”UÎ9V;Æ]Bu%y|N¸ÇÚ�oû8K ŸoÂÊ9ÇÚaI×±Åê 2²ApÇu½0&�~®<«»)Ô„Óèó±ê=ž¨ü1íº�‚œ§Ü޵ƒ�Lô€ ôsíîé™BÊç”ÿ|�÷ +—²êÊÑÞŽ}9¾~RêÃèçzäèwSŽ9Vi_T9|UuåpÚ XqvŸÛŒI®ŸëwSŽ9¦ßÄU‹ÉÕ dÌå¼ÍŽ#ÈàOrý¼ž‘cug$Ña`1¹º�ŒÝ¦>ﺳ3üI®ŸÏÉ±î ØfüË’«È×Áuƒà— =àOrý¼2<«ªjükþ;‡º�l¹oü6ñy0 ¹~ÞŸ¯hÙ²JAŽ9Ÿ×¢à2ø“Z?ï<êÒAÛænlL96| —ZÝ@¶ÚÛ±IÛ×süI¬Ÿ×�EÌnÛyt?U_û_)þ�VÝ@¾ÜÏké=àAb9Ö½“Òw›²™q{áÞKÿî„bJuÙšp^‹â2ç‹£´r¬7 ø~*V leï–cr ðoÊy-oA’ʱÁ­kû ÷c@,_“6~ã2x“RŽ�ìð[[—…¹åØÿSñ? Õ dlÚÁݯÛÇþñQ‚„rlt£zë4ÄîÇ™¯¬dÚy- Ž ƒ7éôóñóV¬ñd\?6¼Áb:u9knÇ“¿�#ÈàM2ýÜ娰I9–IÝ@ήS7~á2x“J?w:ý’Ò2õ¼ÅÜ9 +‘~îcõ`ŽuW�eQ7�µÉçµ(8‚ ¾¤ÑÏk—˜¾¡¢Ÿæ‘HÝ@Þ&Ÿ×¢à2ø’D?w‹±û«ô�DÎÑV1gƒà—;SÁ“ú¹cŒUÚpaU)§¹ô‘¹K¢n oíŽTóæj\fì. ˜$ÐÏíuhçŽU›^XuÎw©•’c@hç%·cAoèçíñb %‘žYöÚ)Xÿª×½C®®fy$Q7�·v’Çܽ~9‚ ž$ÐÏ7}ÍÝTÕûDçD—÷›T¶×¥[7�µöرÙ[$>8‚ ~$ÐÏÝsL=H³{×UMбê²Öìsÿù5÷ v ¿h$ÐÏí9V[S̰F¬²¼0Ùº�œ]MòPÞ�#ȰTý¼ê{QýcÛÿD'ËÍÐ#µŸK­XnùÒ±ÆÄ#ÈÎìc#©ý\jÝÀr§ÅKÇÇI;S}³nfRû¹Ôº�ÅÚQÅëâwšvY³/17dè’ÚÏ¥Ö ,åeéØÓ´#ÈžžÊ¡8Rû¹Ôº�¥Ú¥c>–}M:‚¬ÉON,C—Ô~.µn`!?KÇ· Ó÷›Yú,~.‡ÂHíçRë–ÙÞ|>¥šrÙî•c,8ƒNj?—Z7°Œ¯¥cÚ»9½Ùé•cÎõ!„Ô~.µn`‘/¿·DŽ Û~¾-Ÿ)‰¢HíçRëñ¶tìéÛ=¯JŽqÔ 4Rû¹Ôº�%¾oˆÜ� ;*9ÆùÐHíçRëøö³?°Âý²›šcÌô€Jj?—Z7°ÀÃãÒ±§�ëLúfÒ˹g(�Ô~.µn`¾«ÿ»¡»ëÎT͈æýÌ&‹è‘ÚÏ¥Ö Ìæw験ódÍ­àöÊ&‹è‘ÚÏ¥Ö ÌÖ.óz~³ë^�¯øºø!�9©ý\jÝÀ\ž—Ž5Nn·W—×÷Þ±É"º¤ös©us…™báxÙîuÖ®‡fê=^¤ös©u3y_:¦½íØ0áéwG¦Þ£Cj?—Z70ÏÖûÒ±'·#Ⱦ•Çh_l²ˆ©ý\jÝÀ<þ—Ž=m�î®îjÚ�˜z�Ô~.µn`–K°�4œŽ ;ªÏÄîl²�Ô~.µn`ŽvéX€Á<§#ÈöÚwß3Ó©ý\jÝÀA–Žio=xÙ—>מ©÷ÐIíçRëfh—޼÷ÕaäAÍ7›,B#µŸK­˜!äî¼.G��;» ?œ&9B ©ý\jÝÀtíÒ±03Ç� Ûv÷T¼0õ*©ý\jÝÀd¡–Ž5Σ™tíݲ±É"TRû¹Ôº�ÉÚ“˜Í?‚¬?äຽ0D�ÚÏ¥Ö LnéØÓødÏ»/õÐè-Sï¡�ÚÏ¥Ö LnéØÓèd߆›/6Y„Bj?—Z70Q;ç>ܳ¨±9ýwÃ,“æ§Ú�½7D�ÚÏ¥Ö L~ÑñØdÓâ™Mñ"µŸK­˜è6: c©±#Èö¦qÍûçHúA©ý\jÝÀ4_ágî†./æGa7‡ý¬ „Ô~.µn`š]øñ»Ëð·Ø™7®b“E¼HíçRë¦ ?¬8vÙÉ<ìØÌb ú“!Rû¹Ôº�IVV9‚lkÛLäÈ&‹hHíçRë&YaXñ5÷ÐüÉ«-®Ød-©ý\jÝÀ$+ +¾ÂÒ¼9ÇÑ:Ÿãæpâ D�ÚÏ¥Ö L±Ê°âðd7ë]×�Mñ$µŸK­˜b•aÅÁ#È:GA«Ød ©ý\jÝÀ« +¶;S'z’”Mñ$µŸK­˜`�aÅÁ#ÈIÚÜÇ9¦9‘ÚÏ¥Ö L°Î°âàdƒÛO±É"~IíçRë&XiXqà²ëàÐá•MñCj?—Z7àî²Ö„À‹uIóÈÞ‹·ÐgÊ Rû¹ÔºwÇÕ†í¬“OÃÇŒ±É"~HíçRëÜíWÛÁÐvÙ÷Èá–Mñ!·ŸK­pv]o�ñò3Õ}lE¦ÞãCn?—Z7àì¸Þ¾O;ËæSDZ•Îl²ˆ¹ý\jÝ€³ýð³)ŸlG�íGSêÄÔ{ˆíçRë\]WœCa9‚ìküG¸ì)¤ös©u®VVlo¼ºi´søØdbû¹ÔºW++¶;sì�ž�ÈÔ{ˆíçRë­9¬h9‚lë2fÈ&‹ÛÏ¥Ö 8ZuXÑ|Ùð¦T­3=Ä“ÚÏ¥Ö 8ZuX±�Ñ¡G–m6¾îÊÔ{ñ¤ös©unÖVlw¦Ò×\ßÜ¢”MÅ“ÚÏ¥Ö ¸YwXÑxÙØ¦T­3=¤“ÚÏ¥Ö ¸YwXñ5†¨~èàøà«Ýd‘©÷bIíçR뜬=¬Ø†–:8øp�'6Y”Nj?—Z7àdíaEÓd{Ë&ø=Í$‘õn‘©ý\jÝ€“µ‡ G�]Üï²ÎL½—Mj?—Z7àbõaÅvÖ¡²æÙeSª›, 'µŸK­p±ú°¢á²“åL2“½ÓJ3”Jj?—Z7dº\v»óyÿ¹wœ ±þ°boÑóÖqÖ½úÅÇ5`¤Cj?—Z7Dù¾ÜÿäWsc3!" +öŽ = Z+ô“µÐ’IíçR놗Ëa÷8ï?M\Z}„aÅ6Š^7ŒÇIC…ͨä�Ý‚E’ÚÏ¥Ö�Âmï�óç —}# +öŽ »MÚ5±¹›c ™LRû¹ÔºQ´Ëqÿ9j¼ÕÇVìAæp´æÁÈ¢`Rû¹ÔºQ®ïÝm<Ä~’btƒŒÊÝ#È\7¥jm›wX6�âHíçRëF¡¶÷óh~µÌF—YEVl'v47T.GAk®Œ,Ê%µŸK­Eº�'žÎÇÝýòs›ÓÜãŒÝhÅVìA6ýîªYd™ch7cÖÆÁ©:Hj?—Z7Šr=önÃŽƒ·Q;‡±·HʯûÀß?ŸædéÙu" #µŸK­å0Œ'žï#ó"¶S=b +ªG�¹ݹ"®YP©ý\jÝ(ÄöÐOÜ9܇´³úæ³ÇVT� ›´)Õ[;²8õë�9©ý\jÝ(ÂåÑ ±ýÑñQR;öf½q‹6¬Øî üóxËù(èŽ#‹"IíçRëF zOÅFÇßÚuw#o¾þ°¢zÙóžÐuSª·v"ËÄIdNj?—Z7 pŸ1žø66Õ#Ú°¢rÙ×HÖŽW#†�Ô~.µn@}2¶?N]ö»½ .³Š7¬¨A6á(hËÅÙ³ñ½$Rû¹Ôº‘¿Ë;ÅÎÛ*Ú©æÉ‡ÛŸì:}SªþÕadQ©ý\jÝÈ_ûtìv˜9›á1tËqXñuÙvÉôyF’ÚÏ¥Ö�ìmGžo�k—Y Ÿ‹9¬ø:‚lƦTÊõiÆM9RS©ý\jÝÈÞnyÒ´·,†©ú1‡_G�M; º‹#5å‘ÚÏ¥Ö�ìݬ4ù= óÚc+¾ö¶ì‡ˆi”Ij?—Z7rwœmèÈzËwXQ_Q0û‡h7ßbdQ ©ý\jÝÈ]3•oÎDEE3r×Û÷"î°âkóÂ9RS©ý\jÝÈÜ·ŸåQí-KwvzÜaÅö²§{Kq¤¦0Rû¹Ôº‘¹�§;�vG]ýRäaEm‰÷’‘Ó-GjÊ"µŸK­ykôò}pO¦gH‘‡µ�#E5#‹²HíçRëFÞîKVkŒS=b+¾nÏ6<3²(‰Ô~.µnäíä��®ÜÚEVT÷ÜZöF®Gj~Ïç…sfŸÔ~.µndíb]÷5�áhèèʯÍJ–ï�èt¤æ×‘ÑÇ"HíçRëFÖŽ½ÙY»Xë[ч_+´=ÔØj”xigmHŒ„¤ÑÏ«jóTU�ÏÔïOÕƒo zmBuS|ûm»Ý£¡ã+¾Ÿk-°Õ^,ÛÈâ;ÅØù#{ ôóWR5yõþLÝùŒ=�6ÕØ7M n`"[+ªÚUÇí°ZüaÅ÷S;C§í[ïì®JŠùºÁE4 ôs{UÝOÕÓß#ݺ�‰nžçàuކN`Xñ5aþèá½ìGjÞoŸß %ÐÏ7Öêå˜5ÈÈ1”ïî{qo{ÄÉsè-…aÅ×h �›Âö~³;e¤›b^Ö1 ¦ú¹ò\¬ÖS¨RF›L|ž�¡d~¶VTµw?¿o™Â°âkg*/�MGjnû)¶x’?bK ŸkwO•šV•úH¬ºÑˆ¸dë&i·VôùžêT�†ÛŸÈÓ=ç­;²¸ÝíõÅ&¼—ï�‚˜èçz4©Ã‡µv[U‘c�ì¨ÏÊð¢Æ;&2¬ØÞCyºçl—U7#‹½ûn«Ž|Š…’ëçi50°HŽ¡tþ¶VT½ÏœLcXñ×GÿåýÉl=Åö»Ÿ+ùà—¬.¹~^Ûs¬"Ç ×ÝVU£óí“­äskEÍCëðч}ÓoÞ×ð¾í}>ö�‰E’ëçö‡`có¿{?@C¶RëçcÊ–…a®ûW%[70ÈóÖŠ*å̯ç;\‡RìuŸËÙ-YK¬Ÿ×ö];†nǺûy_ö_)þ�VÝÀ0Ï[+j”gG%;© œžûæÛb\’ÄrÌV ÷ÑäXõÞqØôwÂ.¥º�a¾·VT½¯ôÿô-¯‘ųqððùYv¦ÊZZ967Æ>ÞOÅ*ëFŒr ¹ò¾µ¢æ5¢Ì»’û@е‹Lp9’ʱÊVƒ›R9¾”û1dËÿÖŠª×K‰ÃŠ|=ÎGki»`±š”rÌKŒµ/Ψn`Dˆ­U‡‚‡ÇÜ™°˜¿„ú¹§™’^ÝÀ˜[+jÎ+ޏ„ZЀõ¤ÓϽÅ9†Â„ÙZQõµÿÝ›*v¡Q”ühPŠdú¹¿Üø#¹º�Q�¶VTmw»�ÐÃKnLXÌ^*ýÜgŒ‘c(K ­ñë‘ùÎT—‹ÌûhU"ýÜkŒÕäJlkEüØ…´ êûÏ/9{ñ»‘¤ÑÏçnãaÆó1%ØÖŠøqÍzÂâï �½ô;²$ú¹ßû°¿[bun­ˆ�Ì�ÒÜò;Î�úù¬«*Û)-ëÇP’�[+â#ï£4/9‡°? ôó�'`öÇfÚù.Z¢UNÔ¨prkE|´û$çy”f3(*}¶eý¼=6LÑ$WÕÿL¥}ÕÇûuÏO½v ΠnÀEØ­‘÷QšÍÍzž?¼? ôóM_“V•áSÚW}Ø^7zYu.Ân­ˆWdyÃÛnñœåïOýÜŽ9¤™@Ý€ƒÐ[+¢}Æ”åò¼æ·é¿æ$ÐÏ—çX=5Å’¨p|kE|g|‰Û“ ²üáýI ŸW}MÕ†Oi_õ~“úyˆ¦mcŠuãÂo­ˆœ�Ò|�tû‰Kj?—Z72sÈyRx.ò=Jó}”wìŸ$.©ý\jÝÈÌ-ëÍ&2±Ë6 ®¯“}Ã.µŸK­yakÅ5²ýea÷ʱ,g©x#µŸK­yy°íÐ ò=JóøÊ1Ù=¤ös©u#+í¤{¶V +Û(8¿rLöD©ý\jÝÈ [+®#Û£4÷¯“½ÀPj?—Z7²²g³†Uœs�‚Ï7Ñ=¤ös©u#'w†ŒÖ‘ëQš_JŽe8KÅ©ý\jÝÈ [+®äži\”Ëðéž?Rû¹Ôº‘‘/ž|¬$×£4wJމ^*/µŸK­akÅÕd:ŸFÍ1Ñk ¥ös©u#l­¸žL�Ò<+9ö)yq†Ô~.µn䃭×óÈsgª›šcÙ=ÝóHj?—Z7òÁÖŠëÉô(ÍçO}büYj?—Z7²ÁÖŠ+Êó(ÍvzJ¦O÷<’ÚÏ¥Ö�l°µâŠòIíçRëFîYî/‘³]~›í¾çXæ8*ê•Ô~.µnäáÈV÷+Ëð(MårŸß¨¨WRû¹Ôº‘‡SŽËr³vÉï–æô¯³ð‰Rû¹Ôº‘…m–Û$å-¿5XÊO¼~ÿ.µŸK­Y`ƒ†õ�ršSç]…?O•ÚÏ¥Ö�,ˆŸGAvk°Ô³f¤Ïo•ÚÏ¥Ö�,HÜCvCsÚVZÂ'zHíçRëFØ\q}Ù Íi[g¸üÍ'©ý\jÝÈÁ—ðQ¢(²š{¨Ñ•Ýݤ_Rû¹Ôº‘ƒCn·EÈmŽèY½iÏpù›ORû¹Ôº‘ƒc~{KàœÙ––ÚBá·ðRû¹Ôº‘ƒ[fµ Ǽ&×tö¢ÊínÒ/©ý\jÝÈÀw~KrK�ÙQš�½�Û³¡e’ÚÏ¥Ö� \¥ï^Gf›Æwžˆe¸Ï±GRû¹Ôº‘VAG‘Ù¦ñ�àÊìnÒ3©ý\jÝÈ€ðµ@Ñäµ–¸³ÿH†û{$µŸK­�ýÈ>ž¼~8wRWôCU©ý\jÝHŸì߬#ÊëS÷·�“äI®Rû¹Ôº‘¾�è'åµ–¸ûÛNfËü’ÚÏ¥Ö�ôe·ñz)²:-§7»RôD©ý\jÝHß^òQT9=bºvc+«öMj?—Z7’—Ý~µå¸e4a±÷0o›S û&µŸK­É»³Ip,9�èö‡å”¾IíçRëFò޲Oàˆ)§ÃOνÔÊ)…}“ÚÏ¥Ö�ä�Do”Uï™SÂú‹¶%o#µŸK­©Û² :šœ?éÿ¨™íé•Ô~.µn¤Nô´³ØòùÂðÏ$³ý!½’ÚÏ¥Ö�ÔIŠ.Ÿ£4MC yíé•Ô~.µn¤î,øi}tùì‰aš’"øŸŽÔ~.µn¤îSî/Õñås3lJÜœ¦[z&µŸK­‰ËiªAyò™*a½Ê]y(µŸK­‰;ÈíE Ègª„iѳà�`¤ös©u#qǬÎ)N6S%Œ‘•ÍOï�Ô~.µn$î–ÍŒ¹"�3Y„þe\�‘×A >IíçRëFÚ¾%oöš€\ŽÒ¼w‘;ÑCj?—Z7ÒvÍf¢A™r9ÄË<±2¯ƒ@}’ÚÏ¥Ö�´å3ñ»L¹ì¦²3.“;ÙUj?—Z7Ò&÷ G"2IËs¼|öÕòÌ{?¯ªjóTUuìêV¬ð@l#JE&‡xY~Ì\¦©x繟·ÖFY²IFŽ!A—Ln楃 ±åßI.ÓT¼óÚÏ;)–r’‘cHÐNìƒúTä1åÏö ,—i*Þùìç†û#Í #Ç� ÉGú¦!�£4/–ŸRìý¼Ç~nޱDƒŒC‚änÈ�Š<¦üY§µJ]~诟ׯ¡Äß±De¾GìÃÖ ø’G-[I°³Ý·Ÿ„n㯟WýçaUºAFŽ!=w6 Ž.‹$°ÎKÌç5¿¼õóÊYÏ�Æ.2dÝ€7Ç,&”-‹$8ÙÆŸ¥Nôð›c•ùà >!#Ç�ž“Ôå? ÉbGëøs.û‘øæ­Ÿ[n¼êDÉ1$g›Å³™Â]2Ûµ4&õŸ�×3åUEŽN®B™NJ‡Qdm&û‘øæ5ÇL¯Ò|@FŽ!9Y iï3ý$x &t"9$â,³%&ƒ= wö(B"Ç€Ddp' À.ýß&6�¼È<Àn•çcä0ŠUÐIÈ`êúÉþ ÏVæ?¢à9Æ|EÀI T‚ ¦®MJ”¹µ™ßõcµùÃä0ê!õÐ�´¤G38¥RæCVÏûytƒ¬f?ÀÍ-‡‘H~êúà·<žñÍ[?¯M{)V쯸ùº„59É¥y€¾f°ŽÛ?ßû+;×íGÜ–ŠCj®2gš¥'ùC•gTæ°ŽÛ¿@ç�iŽ]càºd%hðv'�ÁF‘=<öózc”dŒ‘cHÍ)õá,)’_ÿpLªä‡ECðÙÏë|bŒCjžÝóûÇ@òGiÿKy_ï·ŸW½KñÙ˜ÿº�¥.©ßÈqJ{dnda@òâ!øîçU•CŠ‘cHÍNbûISâ{íŽl=•ü°h!úyõ+Ý U70_âÍS’Ä÷Ú»á’8@-µŸK­©9Í,M‰¥9¶. ƒ û½“ÚÏ¥Ö�D‰ JÔâ%X_Aמ�ݹ'¿þ-©ý\jÝHÔ=í{Y–�ÌÝo¾6`ŠœGîÜ%Nô�ÚÏ¥Ö�D%ΖNÕ¢‘¹{èYûc)+qæ«Ô~.µn$ê$ð¡F²Ž æÜ4wC‡õFc*õõo,ìçÏ©‰?ª+»ØEú¯ðj+°÷¤kÁIpmŒ…;¿lüÄ瓼ƒöóÍë\–jc»Hÿu^]7?L16¯ #×Ñ�]r;™)r ˆ/ñ%KÂÌ>Jócáþ.ǧ# qS�ÒÔb¬}Üä÷—ãžg¯ÈVýüuú¦iNHÝ|rlºˆëëªøq÷ûsê&>aÒc¬�Á {N‹œÅMôðÖÏkËÑ™ãó+}Ê~Õ}ß÷§†Žæt}�ïº�Å\~ÇÆš¦ý�tc¬�·â†ÈaÚ½ÀåˆÞúye¹_?Ö]m¦½K=”q3^ç½n`±SÐG˜î:eê|/ÆÚ�½£N·Zâ~1J0Ç´·i‡ÇÆ ]_ç½n`± ó´1Ô}ý{�KXBæôƒMŠá$’cÍØcÝM¡ê=N¨üÑüÝ]^ç¿n`)�û%ÏýW SŒµAâÿW“/§+‹O´ÎM9Véo£|�ú_ÕÀ�–ëëü× ,µctrœCý0ÆØG°%d·+37ˆÌV9Võ^ÿº›ª´[«Êú^®¯ P7°”¼íÒçzö‰%Æ‚-!s\ât~¦óªdEoæa÷{Ù~‡×ù¬ðäæI À;ÅØköŽßÍÆœï³ÇK±FŽMy;íQׯ”O†Tt}�Ϻ?¶a °ÌèQš#1f ™ã´û™'�æk…sØíð¥Ö¾¢Gõ@Ž�¿î¯¿ÿ"Ç�†+« S46Sb,Æ^KȼÎ<¸Î6Ñ#xŽUSb¬s'Õ‹#Ç3¿îïÎOFŽ!ò=ÌÂà&+ß×ÇhŒYBæ8íþõãK™èxžÇ¤Gc½A¿9¶!Ç� iKVsa ‚Ëawþ|³ÇX�%dî�½dMô¾zê{¨ã�~sŒqE¤è“UÐIêÍpÿ¾ì·OÝièT€%dîç{>_)åˆñ”r¬·Ã¯ß S7°ˆ° |¨»…]î»óþ³o0ÆB,!sG×�µÊ�PŽõ7ªïÆQå˜cÌWD6²Èç£yuÝ=NŸÃ1` Ù„}ìeý»J§ŸÎ[1® «-_êðº$ë†lY¤óqýsûK󾄬¹ywÙúEÖa@ÉôsÓ±a¬ƒFñn¢žcdäk$ÅÎãñä} Ù”›,ÿƒš K¥ŸO¿$ÇP:q'æÃš`·óîê¶*¬™âm ™ó´ûa¿ %ÒÏ͇8wŸsm†rÌáuéÕ éžà‹é±Øùx¸Lö½„lÊ Q§Ú¥ÑÏë�ù‰–žOµuú†ëëR«âI;(*#ÇÞMØäôÍݶ·%dîÓî…­¯ÐÏëÉ‹¡m1Ö ´Oßp}]к�éN¾§´Á—ïý¼›0•ç%dSžy¹� VÏý¼2nS5òEÖÚ¦J È‘í¬Â× Ìã{&üù:žÏ»û¢ º{�mát(Ú¬çÎk?·íµ8üUÏ3�špý°Ú8¼n�º�™dÍŽ–Çï² Óî?œ�Ü,‚Ç~^[÷ þºf?aÕëÞ¬ze\]’ëcüuáëfÛ‰Z­*ÐÑçèÞ´MìÏ‚&zxìç›E9¦yßMunñô½•7¶¾.|ÝÀl’z�H^—�M™v/k¢‡¿~Þ&I»óý+“Ææy æ˜P�½7㯠^70ß^ÐØ�L>—�M›J�4 ™7ýü• ¯�«n& }¡-Ç”€ªº{/j7z–ׯ˜Íù˜zäÊç²ó¤ßz$-±÷ºOð;¾ªî‡¾²O¢úçæ®{Wg˜Ðo|]èº�ùî‚~eÊ粉c”‚nöýæXÝþ© —%Éy× Ì×Ì`“à‚�¼Må™:“^Ð ­Þúù;°´\¶:Ì»n`¾›ßí� KȦ®lÞÉù-)tŽ}¸M»È·n`¶­×U²HR»„lyœL›v?ýõóšcϼÒsÌÃ9ÑI× Ìve´Þ–�M½¿4‹ÈkŽ=ÿDŽN$­ð‘«ùmeù|‹É;ØË¹Ý_%Ç|@FŽ!>6 ÁײiÓî?¦m�Ÿ·@9ÖÙ1*v•ëæúöõ‹:’¶ó4¾·Ÿº5ˆœ#È<û Ç�‰àk Ùä8”3n`¾¢~nŠÓ®½× Ìuö¸eæg ÙôŦí�Ÿ3¯ë Õ]<¦¢’sÝÀLÍ„ìS쟡ùYBv�œ†rv¦ò»Ÿ‡òÇ÷Þä`pÓe¤ó³„l7ýöÝçfûIóÖÏëÎÀâϹ�Û’wÝÀL1O/Äó²„ì8}ÖÆYÊ~1~÷»WÍ»×§‚Cd[�È"m^–��§CO;°,cžÏSŸŠ¥cäbcXQKÈfl_�²3•¿~^«®T‰Ç9†ØVÄDzo1}Šc¦Bõóºr?Ó²¤º7 +Jâa ÙeÆjé'½d+d?¯Ó¼ ^70ŽaEQ΋Gø¦O»ÿ�s”¦Ô~.µn¤‚aEQ–/!›1í^ÎQš^÷óHq‚}èº�Yö +J²Ý/]B6kî¡”£4CìK•r Q]V”eñ²Óîå¥IŽÅl}‡_—¥KÈns¾þ"ä×%r ˆ`/äÁZK—�ÍK$/;;¦/Ĺ-9 ÇÓUHÁËÂ%d_ó¾\ÈQšÜ�ëcXQœ…KÈ.óžt 9J3Ĺ-9 ÇÊòœýî2óLL!Gi†8·%ä"bXQ eKÈfnù{•qãd¿û �cˆh'£»@Õ,!›¹˜kæ,BŽÒô¸OpVAFŽ!¢Ê-ZB6kÚý‡”£4=öó*å}�Ö Lô%£¹@×LÕ˜¸”¹1÷¾JÆQšþ΃®ªv‹ûžØE†¬˜l'å8 hš%dçkÈfN»ŸuŠt†üÎó°ˆ]dȺ�ÉnBv½ƒ®ùýåóó89ÉfN»—r”&9¬ŒaE¡¾??ç&ÙaîüyGi’cÀÊV”êü²ÏݤÛñ™ÓIŽ+cXQªvêýsºê”$›?]CÄQš^çyØÄ.2dÝÀD +Êõ}üœ—dó÷Iq”¦Ô~.µnÄw`XQ°ï‡šd7×ó÷q”¦Ô~.µnÄw’ÐX`u9OO²Ûrˆ8JSj?—Z7¢û^²%è$™CÄ\æodÖ|é!vÑAIíçRëFt }î75ÉΣI¶dØ¢í‰3!µŸK­Ñ5ʳÏFî“’lö´ûGiJíçRëFl +âIO²Çà/6K&J8JSj?—Z7bcX�íN]N6¸ÅÇyÁ=ÕÎçÀvwÞ%x(§Ô~.µnÄÆ°"^œ“lÉ3.ŸGi~ßÒ\2"µŸK­‘mV„b»Sƒìót0.ÇX´¹”Ï£4‰®}”ÚÏ¥Ö�Èî +B£oññùù¸÷£lÁ´ûŸGi^Ú2µ “ÚÏ¥Ö�Èš_h|Ä€XºIÖ�²ek™ÏÞ†NŸ‰™Ô~.µnÄÕ •~FÙ²½¥¼¥yW~À´6ЗÚÏ¥Ö�¸î)6Äwé%™e˦Îû:Js«OJ‰}ÉTRû¹Ôºʰ0$ÙþØ<[2íÞßQšú””¤‚Lj?—Z7¢bXv߇›%Ê–ÍÔðt”f{šõ>Á “ÚÏ¥Ö�¨VÄ ¯�1Êþ³ñs”fs¿xÛžÒ 2©ý\j݈ŠaEŒ1EÙÂ�A/Gi¶sî¯ï ;¥rø�Ô~.µnDµgXã,Q6{¢†—£4›éç®nûH-Ȥös©u#¦¥ãCÃe³3ÃÇQší$�çXÂ1± “ÚÏ¥Ö�˜ŽíÈ 0êrÜë96ÿ�ž_¿d™vÎ}û;XbA&µŸK­1í}<§€W5ÊÌÓX>-£É­ý¶ó�D‚Lj?—Z7"º¦6Ë é{GÙqþ›Ü–á×gï–îdû¦-…èçÕuìÂ"Ô bXs<£ì¸à¥ÙιW?¶K(È|÷óªÚüªÚÿü#v�kÔ ŒbX3]ï>&iÌ>ô®ÝXñbühAæ·Ÿ7!¦äØÏŸc×¾n`ʈdáQšÛfîdwC‘t‚Ìg?¯7›nŽÕÊŸ“BŽam;†Çײíbï~îd‹7!^Æc?Wcì•]äðtcX‘˜ï§}ï­1ø>É%î�dûù;ÔìJu`‘Ãʾõ`�EGi6³Dn¦ ö—}A毟¿'x¨9–ê 9†•íøßB-9Jó2T_I™·~^¿‡É1 çæc“;`Ž�º©ÔD§áeØI™·~^½K˱ŠVDL ŽÒ4ϹW¼ƒlÉÆWËøÍ±÷É1@Ű"â™”f»±âÑþ’¯ø'kzëç�EcZŽ%8уúVDD³�ÒÜu7V4øŽ~²¦×k6£"Ç€†Ó¹¿¯”“ïO—;¹÷Éš‘Î$òšcÍÉ1 ãÀ°""š{”¦icEƒw�ÅÙÙƒVpbXÍσõc@;¬ob2„›u·dÝXÑä<áµ¾…žw_‘c@;¬åÿq`ÞQšÛý”ô»Gü]-ô:èMš�ÇÈ1¬‰aED6ç(ͣÜû·ïˆSr=ïKõû„¬Ò‡S¼#ǰ&†ÙŒ£4ÛI®ÿl#Îeò½Opõ¡äX]m”éI!ǰ"†Ùuú„ÅóÄQ„ˆìy?·¥ªÚ«ß;েÃzVDlÓ�ÒÝXÑò-ŽªóØÏ+íÍ÷#TµjÝÀˆ˜OÀ�_SŸ^mo“Ÿw=§…Ä8)Ög?ׂì%ÅQEr kz0¬ˆØ¦¥9iÎýÓ1Ú–^ûy•MŒ‘cXÏ6æ Qà×Ä£4Ý6VÔ]£méṟw“,ÉgcêìîQ·P~LxÜ'øÇë¿^óïM2r ëøŠ·Y r½WjFf�Fz쯟k ÆêÔwô ǰŠÃÜ'æ€g®GiÞf͹Š´•hˆs4?:ûz¬\ÒºuvÛf„&Îa€êì6™°½›µšy» ð›cÚ$½­9†ð®û6ÆVDtŽGižÍÕˆs˜¦·~®V{YÅyÐkÛîÎíRàv”æ÷Ü9÷Oq¶ô’cõû¹X•è2r �}�^)¶çä1Äçv”ænÙÔù8‡izͱ6¯Ô»°DoÈÈ1„uxߌ�ØÊ)pÙ5ªÝÊcö^¿·é^s¬ý³zV‘c�ç=ÁƒÐHÅÉáX•ûÒ-£¬— ’cê,Er ò¼'x|ÞX�D¸¥y[ú@7Êþ5!r¬VÇ«4gÞ“cf{|ߌ=Vž¸X9¥yY¾è§Ã]Ÿo!r¬"Ç Ø×í=Ácý“˜›Ëø½Öqù:æÇÂls„˜çQõæ|¬YÑÊu:e¶ýiýƒq«ïÑ!¿v‡à%£‡[z„˜w¿éÍùX³¢•ëTßLð@²F�ÒÜy˜5ã0M¯9öÌ«þUÌó€êfÛ#1£GiÞ|lóaKÿû+Öú^T®ëÇêJß0_ù˜Æ¼¦ÚíU!ê^¶�÷ÍØ‘ HÍndÂâÝË�‡izëçu»Ugo`—«Þû1êßô™ßËíU!êZe‚Q!=cGiž½l*aK�ç�sÄe_*õ+íoIŽ!mÊ�3<� ‘£4Û­—~›õÓô×ÏÕ#ÇÞqä2̓Cþ¾ßÛ)2Á‰þ÷yôôÏ÷èã)Û$û¹dµö±ÑaE[Žuž�mì™Øn¯Ïó1Äqg‚Ò7x”æâ­[÷Õ�óÙÏë&�”q:·¥ÚüNð»uz¯É“ûÉ1x¤NðØ1Á©: .ÞZ±µ]}KÏý¼îÜ ¹Ý5¯uÉ1Ë{‘cˆh«Ñ¤kð(ÍÅ[+¾œ×žyŸP?ɱÁ!Jr ñ(Q�¹C†ŽÒô°µbkõÃ4êç#9V‘cHÒ×ûÑçe"iCGizØZ±µú– õó‘Û }šC,‰ÔÙ—vùØZñeíÃ4êçÃ96<óñù¥Ýý@ò¨Y»3Áù°¥éckÅî›­ö°8¡~>œcà ªÕ¥cNY–PÝÈÙû¨1ŽhAúìGizÙZ±µöaš õóáÛŒö•d$ª{Å'�!Ö£4ýl­ø²ò– ûù{s_Óž¾-Ç÷Jª‘…h.ÛyüÝy9†…Þóí÷<C¬GiúÙZñeåÃ4æØ{ÓRÆM:¬slä}šÅj(È6ä¼zÇ3<�ÛQš¾¶VlÝ×�À›IŽ�íoõ~*VuvÆÒJ%ÇàÏ{¾ý‰È„å(M_[+¶š\Jó6°_Õ<릙P?Ë1Çí†É1„Dz1äÉx”¦Ç­_VÝÒ#¡~nÏ1§SÌä‚zÓ²÷ø,ÞÖ4Šð0»pÕÃ4êçö›6¬HŽ!,–�!_†£4ÛI÷^'e¬º¥GBýÜžcSfy|¸Ý¾%T72óõ^6¶Ö2OÀÃQš^·Vl­z˜fBýÜšcS‡y>†€X6†œŽÒÜ\sK�„ú¹5ǦÍòhnßF^ŸPÝÈÊõcGb ù¹÷¦&ÞìY>¬¸¥‡÷~þÞªrÙ¯WýJ[އíú+Ö�!–�!oý£4=o­ØZó0MÏý¼³zR’ÙrÌîv׋ÿoìsŸ`KŠ‘cÈÇ{�Ø? PˆvV‰ºË~ižîÑüõóz³!Ç�9¶ñ¼;}¸�Ï»ÝÂI þÏÑ$Ç�/¶ñ¼;|ŽY8 Ä[?ooǘç�|]Yÿ x÷½˱…Ûî{ëçUʧ�¬%9c€÷± K%ÇòŠ1r ín=lãxµ½Üw»Çùl˱Tž�åcä Žá7dÛ^L‘FŽÅ­åhOÉ]apß—Ëa·;ŸOiåXÚGg†©å8²È‘׋]LŒºQŒví“<€¼0®<í¼Ìœ°6¯óîÉ1d¬¹[m×9~ø]?»šu£w/ X�¿~ž×À"9†ŽæôuÎÎrãyŸàlf,’cÐq;äÊç¹-¿wd™$9]³,“3 �ìøÛ'¸ª*ÛFÁ±‹ Y7õ}¹LÙ³¾Ù’jÏ>÷@v<ï̹-HÃÏ0ámÂJ°[R¹"ÇP¢ËÄ3�Ù’ È9†�'Þ^±%�/r jo¯öŽ÷W[nÇ€|y�ça»È�u#EÇö8ˆ‡ÛëÙ’ ȘÔ~.µnÚÛ1×#1·lIdLj?—Z· ÇwŽÝ\&Ò>'N �©ý\jÝ"(·cnS=Ø’ È™Ô~.µnvÚ‰éã‹ÈØ’ ÈšÔ~.µn Ú§]{×É'nÇ€œIíçRë– �|xýtË'¶¤ò¤Ÿgp„ 9V¬övìÞ.† ¨©k¦¤…CaÞO»ÚùÇÁ×·¯âv È9†Â(“Û ƒëÂØ’ È9†²h“oã ÃØ!È9†²hkÁ.ã‹Èv“6°�r E¹ës;cû³%�=r EéL>lcÊz»ÅÁ@öÈ1”¤·ì0²_ðmÂvÂ’DŽ¡$ýµ`§Áý‚Ù’ È9†‚´ó:”Ça_ƒS=Ø!È9†‚˜Ö‚µ‹ÈLû·Ã�±n �c(‡q-Øv`¿`¶¤ @Ž¡æ­9ìû±C0Pr ŰmÍaÝ/˜-©€HíçRë.ÚÑ2€hÛ/˜-©€"HíçRë.™}kË~ÁÜŽE�ÚÏ¥Ö]²�­9n¦ebmî}¹½=€DIíçRë.ØÐN‰Æý‚Ù’ (ƒÔ~.µî‚톶æ0íÌÁ@¼÷óªª6OUUÇ®nźÛàÖ†EdlIÂs?o3¬�²d“Œ+ÍH,õ÷ fK* ^ûy'ÅRN2r¬4c±ÔÝ/øÊíPŸýÜc¤däXaFG »û³%P �ýÜc‰9V˜Ûh,éû÷*�+ý¼~ %þŽ%*ó=b×¶n¤À!–ô©nÇ€RøëçUÿyX•n�‘ceq%l÷ >|°%Poý¼2FÖ󣱋 Y7Rà6J¨ìÌ–T@9üæXeþp‚OÈȱ¢œ�bé½_ð–Û1 Þú¹åÆ«Nt`‘+‰ë(ák¿`¶¤ â5ÇLyU‘cÍy”°Ý/˜-©€‚xÍ1ÓÇ«4�‘cqŸ´ÑîütŠýƒð�Cö&LÚx¨9Æ–T@È1䮽s%l‘±%PŽUž�‘chÒ¤�Ã;DZp^Ï1æ+"¬í´I§6ÆØ’ („ßõcµùÃä™8‡¾Ý/˜-©€RxÞÏ£d5ûy ¬övÌyÒF»ˆŒÛ1 ÞúymÚK±bE6ùXçí�-©€¢øÞ'XÙ)¸n?’à¶TäX1¦ëü»_ðž-©€R:Lûsì×�˜&ߎýñõ8?¾bÿà|ñØÏë�Q’1FŽ•búí€Âøìçu>1FŽâÎz@<¿ý¼ê¥XŠÏÆü×�X\ÎÏP6ßý¼ªrH1r¬nçg(Zˆ~^ýJ7ÃBÕ�Õq;@l?—ZwYÚcX˜CH&µŸV÷Ef'Ÿp` €bùÛÏÃ2”ø3Äèúõý—Võà;L˜!YTŽÝ÷2{¹ûù™ æuEÛ~÷£_ûžbøj·Yüµ2ÁÄáÑ\I9ö-ôÑuÏí€4rL�âhøj§«ãÎ{Ý Ø‰¼+ù>r;ࣜk>]¹Ž-–”c�·%Ûv×z÷[*ƒsy>¦œc>@&XÝ ¸É»/¹Þ^1æz~&€R¥‘c¿<*K޹|w5�Žî,)ÇÚ†~Œýƒ¬å=¤(ò¹ ] 9¦¼ÃÌÓ£®¾ª »| {P´Û¿Sìv�ýÓˆ-tŽÕ«ä˜>$Y9 ,”cwYOŠ.7õfŒ ©„Î1—{£�—º}uÝItX,(Çv’}?”; ¹0hq?¯Û)�¯®�cÝ0‡oZPŽ)‹Š¿!S‡÷œ9àÇâ~Þ;ªeödöÛ¤û•7�å˜ÒÚ ¿!»œRгFŽMy§ÎÇÔ4¬œ¿RTŽ}~ʸ!ÛÕB¿bÿ8R±B޹à2’cÇ™mL96| WNŽ]Ô+ø†ì )bÿ4Ò<ÇÜÏ!Í1k6u?UKʱfšÇ©ì2mHñÈ�"€·Àó<�ý²æXõÞGØü~½Ï_úw' ɱf´í»ä2eª?‰]d‰f ¾zÚ;ôrì•„ÕÀ ¥[ŽmÊ̱ç�ʩͳoÈî )°K<ǵý†Lôýس½?^ó=Š›�þ¥îBõ`H@G>9Ö™é3n9ö_)þ]JŽ}½vlnÈn±"ÏÔ!ÅCŠzšï0:[Þ: ±ûqIó¯¯ÇbÛ}‰7dweH‘-�$ÔÏçç˜qýØð<É„ê^F9DsWà Ù—2¤È.TLêçã«—'åX6u/ó|x´ÿùc�7dÛCŠFéçãÃz&äØ7e–by7d¯)ìBÀ&§«s¬³ß½”{Móø(ð†¬�ãqbH€UN9fŸ¾¡Âa;�br¬Ù•ªYTUØ Ùµ�àAŒ°Ë)Çì ¡õ/•tŽæAÛÅ£¬²ïvù3g>�QŽUÚp¡¶ë•~§æòíKɱæ¬óŸûž&mÛ-™n`HÒ9¦íÏXmzaõ~µz«æ°z¬œ;ë‰í Y ­¿=¦¥Ä�¶x”DŽ)‡GkÛ 7{ÿ¾¢ÙLJû�¡ÄßïYë·m«ÖA·Õй!k@ßò/@PIäXÿ´—çÇûgÂÔú׼ߢ²½nͺ×÷Ý»ýºrCöÕ>ãÀLòÊ1õ,³î]W5)ÆJɱKo"ĽŒ²×è2¦¬(é«­)fX#VY^¸bÝëÛõïYʸ!{´GfÆþA$/‰8~³~�ÐiúýCõÏK�î,$ÇútÅEÜ�½@g]€U$‘c¥Ô½¾“aF_7d—v4ÇŒ"Dzf|Ëÿ†ŒÐ& Çröe¼õÊþ†ŒÐ&(£ŸK­û®íJÕùèg®»¶ÇX ÀEý\jÝMÇïŽ ž³žëׯpÆ£ÖTF?—Z·rˆ¦ê’ó   LSF?—Z÷Þ2þ–ñ ÙkwàCìŸ@&ÊèçBëÞÚ¦Cd|CÖ.€~ÄþA䢈~.µî‹uë¦loÈ,€0ÑÂ~^9‰]¤ÿºÓ ¢©Êõ†ŒÐ&[ØÏ7Nbé¿î4û»Rµò¼!k�Ocw`îȱŒ=ÃêdúT{c“×}Í™Ð&#Ç264#âœáZâ×îÀ±9!Çòõ=tóÒlYezx–ª+  ÌÀ<�|]7Ó=ævCöZ�QôH@ ý\jÝ»Á9‰ßQRa{¿(¦<�c4€yJèçRë6¢©ŠpC¶mŸp™œÎçóîp± Y `–ú¹ÔºoÃ9µþ Ùõöéâ|Ü]{wjíèÇLSB?—Z÷ر•oȶ§kÝ´›³vZ  LUB?Z÷eìqÒº7d×ý¤ksëÏÍÙå‹Ðæ+ ŸK­û>šR+Þ�}O»ëÅÙçØÍ%XÐÏ¥Öm9DSÑÞ�…¿É9̺ëa4€é èçRë~îØqzIu7×·œéûüŽ¢½:ñþ¾SœÏç‘´Ûç¶­1€èç5ë Wa;DSÑ>v »$K½Û�L7üm�“-ÇX `Ïý¼ªØ—j%ÖC4UÍ YÈ�ž¾”Xº9&Ñ÷åÏíYïæŒÝ�ÌᵟW쯸û!šŠö†,ÜâbuåóØÍX¯íæ,Ÿ´$Åc?¯Ù'xMM€|9½*Ô��z3všù=ž7g'îÆÌ㱟³ßýªÑT5[lÙ&CÛ†Š‡¿~Þ*þ>"«ÞʘçÆÉm¦ú%\Ì\”m¨Nìà ýü™Zõ3Ð~£ëe)¦X 9æúà«Ý×wШ7c{nÆDã­ŸW5¾ªîS“}Ž}¹ÞfµS=<¯1V·¡:³î @<~s¬nÿÔfW�j�eŸcWçùí1Ë>‘©{ï90 @LÞúù;°Ô{Æ[ì"CÖËð!šš³÷Ý2¸�ŽÐ9öñºMKKö9vv›®øã{ïw�–¶ Õ5ö… �×{敞cUš‹ÙçØmB4µ32|dÎ÷AÝVêÁ©—bóšcÏ?‘ck˜4›þäi{*=Ä>oÜŒˆo•KðYî96zˆ¦¦=myÑôøNˆM߆ B”cïä"Ç‚8LÛnjñöT_»›bÎ{@Xž�}�cáíܧyüضÛSÍúfýãf @2ÌWüÐÖŒ%ºÁbî9æpˆ¦¦]D6}dÑbŸg¶¡� ¯ë Õ]<°8÷M�¶{È1vñ0›×y÷䨻×,ÄíðÀâìC4uï�¨ØÞ@Îü®‹]M„º½Q6M| ,î—OWüÑ."cñ3€¼ùëçy ,¦šc¿ñ48°¸àM]³-ÈiþzjH€ç}‚³™±˜^Ž=o�~7ÿX\rˆ¦îÁâgðynËïY&I–^Ž©·YC‹»±eÒî®ç�ÆäÎß>ÁUUÙ6 Ž]dȺ}ùRW… ,.:DŠãyŸ`Îm™ë½|ìcx`ñ<¶ý"ˆBŽ%â ­nXü|?G�c©ØiÃ…ö�Å/_Ó  äX"ôÇ^ö�Å«—]© ^çyØÄ.2dݾ(ËÇ~X¢ eI®ŸK­û¦ç˜u`ñÌtEP%×Ï¥ÖÝÉ-ëÀb'ï@ºäú¹Ðº{»MÙ™æšÔú¹Ôº_§h¶,‹^Ñ€‚¤ÖÏ¥ÖÝ›†hX™=¢ ÅXe?�ŸMðcªnOÎý|2 ,~3]t+åXrgE§–c†ÝM‹½i� Ýz9–Ö›i昖Y¦�E�‡h@V̱¤ö N,Ç´S4[†�EÑ€ÿçA·'B+ÿ‘àÐbb9¦�¢Ù2 ,ž8DtûyÝ=¬ŸùU¿ÿ¸Igd1±3.o6 ,>?rŒýã@:üõóÚpÇU½Â«Nì†,±ÛÇ {‹¢ ]þú¹1§ª×c±:­'d‰å˜ù¹Wo`ñήTÐáwžGÿÃït«È1;Ãò±ÃÀ"‡h@—׫†>\'5°˜XŽYNë,>ãnû§€„xëç¶»­÷ÇÉ1;Ës¯îÀ"‡h@׊9V‘c6[KŽu·Ló€.r,Öí¦ô�Åæe÷Ø?.$ÄkŽ–‡Õ䘃«m¢>°È!šÐã5Ç,ó<ȱ1ÖÓ1õ�Eó*3ÍkŽm?Ì<+{@i‹¢ =~×�UC%Ǭì¥ ,wé<ïw¯ŸÍÒl«è´º9OÚða—c_&A�bŽ™æÓ«‹¢ }þúyÕ�›vsàJù¼åK߇¾h¯õÃ`Òiò¡�iåØÀö¿ÊÀâ•C4 Çó>Áý³ZÚYŒÛy¨ieýÄð~ùEä˜ñFKXäMèó~nËÆ’=•�³YÞ9f˜®=>ûy=cC†s¬M¬ Ÿkïó|>føj­›W€d õs¹96|¬X;°È!š`�P?wW|>6a43¡º—�ýh÷LW€¾„úùXŽ�Íóx-ÂάîÑý¦š�Ev¥ƒ„ú¹SŽY†¹î_•bÝöS4[5Ç8D4 õó‘ºëîça|ÙÿSñ?éÔm?E³õPrŒéŠ Y˜cï)öueçø^ƒ9V�ïK¥ìêhú–wÂ.™�ˆ¨,Æþa 1 sì½YT/DËfSVƒ96cï§b•u;¬Mâ9f�2°xŠýÃ@b2ɱÁM©_šìýØø1Ïï�EÑ]96!ÆÚ÷?žìó±áeÐ?Þ‹LW]96)ÆFf„ø©Û«ñý·Ló‹æyLŒ±LslðNë5°øû‡€Ä$ÔÏm965Æ7þH°n—}ìïLW³„ú¹%ǦÇXž968bØ,²+t$ÔÏÍ96#Æê¼rlðÍÖƒi`”P?7ç˜Ëî_’PÝ£ËÇ~Ü9DŒêçÆ›cÃK¦“«ûá’c[—›6(¡~nÊ1{ŒÙ'BV.sýª{|ùØ�;Ê`’P?7äXe¿µRÃJK´Êé�ZBußÜfp\wöº€ž0ý¼Ý°×qéX³ÌìùÊ�VIƵhZŽ=_¥|ãÑ¥× å38`�ý¼V¶òp9 ¬·ƒoC¦=B´¯ù°½nô»¦“cc§h†,îçý'XõÄHñŸcá™NŽ��¢ ²¸Ÿ÷žFÕÝä•¥9VOM±”rlôMÀ?9¦~ ²F�•mGÆz`¯ÆÎ|Åúù|Íy3ÇtrlôMÀ€¥ý¼7¬ØÆX;qÃmd1»ºýqZ °ð’cjPic{nsàs¬ÛŸ3ç<ÀKûywà°Ö‡k·�ÅüêöÇm4ÀÌGŽõ‡+ý¿¼!K'ÇÆOÑØùαîÌrl ËÇ`‰…ý¼6æX÷ä˜]³|Œ-§`ž…ý¼2>ë[ì*½×í�Ë)š+Ï9Ößk¾"Ç9�¢ °ñ‘cuç¿É± X>‹øÈ1e±¿^Œö>ÄïçRëæMð!~?—Z7ËÇÀ‡øý\jÝœ¢ >ÄïçRëæMð!~?—Z7§h€ñû¹Ôº™v>ÄïçBëæMð"z?—Zw³|ìûB@æ¢÷s©u³ ¼ˆÞÏ¥ÖÍ)šàEô~.µn–A€Ñû¹ÔºŸËÇn±¯ä.z?—Z7§h€Ñû¹Ôº9E¼ˆÞÏ¥ÖÍò1ð"z?ZwsŠæ!öu€ÜÅîçRëfùø»ŸK­û@Ž€±û¹ÔºY>~ÄîçRëæMð#v?—Z7ËÇÀ�Øý\jÝ7r ¼ˆÝÏ¥ÖÍò1ð#v?Z7§h€'äXœ¢ ž�cQ\Y>~�cQpŠ&xBŽEÁ2hð„‹‚S4Àr, –A€'äXŸœ¢ ~�cQ°| ¾�c1pŠ&ø’LŽÕÕ/ãg6¿ªÑwp{]u³| |I"Ǫ&�þè}®~nS¼‡ëë’¨›S4À—rLI ^ŽÕ•ýVËõuiÔÍò1ð%õkr©3t}]usŠ&x“xŽUïqBå�¶·]usŠ&x“FŽýNð¨ 9¦f[5p£åúº4êæMð&…kr¬Òn­LA7íu‰ÔÝ,ÛFû iç˜>ÔXY ]_—HÝ,ƒo’αº3Bh0t}]*usŠ&x“tŽuo¬6–C××¥R7Ë À›ôslðÓ^—JÝÏaÅc´ï!Ç"`ùx“tŽmLùdxðåúºDêæMð'ùSã¨ȱñ×ý×_ŠG¬›S4ÀŸ¬rìÃ1Ç̯û{£‹VwsŠæW¬ï%”c›TrŒåcà� Kæ~ìAŽ€7‚r,™ºY>þd•c•cŽ%>_‘S4ÀŸ¤s̸.¬ÿJ‡ cÖÍò1ð'¿søÊ´×A“cà9¶:NÑ�ÒϱÎ>öÖsx]usŠ&x”tŽu&pÔÖ鮯K¢nNÑ�ÒÎ1ýCö鮯K¢n–A€GäØëÎJû�ê‡ÃëÒ«›S4À£´sìù¤«V>¯…ÕÆáuéÕÍ2hð(‰«ž~ó§êßhý†R]’ëcüuéÕÍ)šàQ9¶éy}ªÒ?^ë_ó1þºôêfùx”zŽéUw¾æcüuÉÕ}áMð(ùSªª»_ó1þºäêæMð)‰«z´O×?�Î:ûè¿ÌüºäêæMð)‰U7ËÇÀ'rlmœ¢ >‘ckcùøDŽ­�S4À'rlm,ŸÈ±•qŠ&xEŽ­ŒS4À+rle–A€OäØÊNœ¢ >‘cëj��b×¥ ÇÖu`—`ðŠ[W³›Çwìú äت¶ +€_äØªî¬¿È±U=8´ü"ÇÖÔ +ÞbWå ÇÖİ"øFŽ­éȰ"xFŽ­iϰ"xFŽ­èú¼;Æ. BŽ­èÈ^÷à9¶¢ç°â>víPrl= +€äØzv +€wäØzn +€wäØj¾ž·c�Ø¥@QȱÕ4Ê÷Ø¥@QȱÕ<‡?·±K€¢�ckaXB ÇÖ°"„@Ž­åİ"@Ž­äûcçØ…@aȱ•ž9vˆ]8†[I3¬ø»p( 9¶ŽfXñ»n( 9¶† rlg† rl[† rl÷gŽíb— Å!ÇVñxæØWì² 8äØšaÅ[쪠<äØV€Pȱ50¬¡�ckØ3¬��c+¸2¬¡�c+8>sì»h(9¶‚ç°â>vÍP"r,¼fXñ»f(9Ê9Þ�aE† î‹aE‡ n÷̱{ì’ HäXpÏaÅÏmì’ HäXhͰâ#vÅP&r,4† $r,´Ê9Ø7Ê9Øá™c‡Ø@¡È±ÀšaÅïØ@¡È±°šaÅSìz TäXX +@XäXXg† (r,¨-Ê9Ôý™c»Øå@±È± ÏûŠ].‹ ©V¼Å®ÊEޅİ"„Fޅİ"„FŽ…´gX#Ǻ2¬¡‘cŸ9v�],Œ è9¬¸�]+”Œ §V<Æ®JFŽ…Ã°"„GŽ…scX‚#Ç‚ùbXÂ#Ç‚Ù=sì»T(9ÌsXñs»T(9J3¬øˆ])”� …aEX9ʉaEX9È7ʰr,�Ã3DZ €Â‘c�4Ê߱ €Â‘ca4ʧØu@éȱ0V€u�caœV€U�cAlV€u�cAÜV€u�cA<ž9ö»L(9B3¬x‹]%”� ¡VÜÅ®ÊGŽ…À°"¬… à°"¬… àİ"¬…ó¯Ù˃aEX9æÝvÿŒ±cì@rÌ»æ è={RÀ È1ß¾>y:ë!Ç|;3YVDŽyÖ,�þ¼Ä®d ÇüÚÞž1vŽ] AŽùÕLòàà1XIÒ9VW}µñ•n¯ _÷7“<`]IçXµé«Œ¯t{Uøº›�oÛØ—¤ Ç|j6Vü¼Ç¾r 9æ“<`mIçXçùØoB™_ù›]ÑŸ�íØXÖ–tŽéª�-{­X7+ÀúrË1Ë�V9vl6Vd’¬'Ÿ«‡ž{¥�cí$�C”ËBå“cUê9ÖœžyŠru@ª|rl3”U ä+@ ÙäØà°b“cU5:ß>XÝí$�GÄke“cC³<´õcnYæ½nNÏ€(²É±ÁaE}ôøê1ÿu³±"Ä‘KŽUût¸lçñ×ߊy®›Ó3 Ž\rløvì£Q¬†‚ìïNØù¬ûÚÜŽ]c_'�&“žåñ'Ç^OÅš(3 -nÂå§g@,™äØÈ°¢ª¶Ý�¼ãôLˆ%“VÔÔ./öZ÷÷žII96ávÌñÕ^ë~°±"Ä’QŽ9̦W^½bŽqz&Ä“EŽ�ÍòèZ9ǘäñd‘cÓ†×α+@®I=»�k9ÖGŽõѳû¸&}ôì>®IxäX9ÖGÏîãšôѳû¸&á‘c}äX=»�kÒGÏîãš„GŽõ‘c}ôì>®I=»�k9ÖGŽõѳû¸&}ôì>®IxäX9ÖGÏîãšôѳû¸&á‘c}äX=»�kÒGÏîãš„GŽõ‘c}ôì>®I=»�k9ÖGŽõѳû¸&}ôì>®IxäX9ÖGÏîãšôѳû¸&áý÷?7ÿûgHÍŸkÂ?»ŽÿË5éášôý\“ÿû‡H ×€U›_Uû‰|~™.‚Ì TWMÝÆÂ¥_Såu{Qbÿ˜q<ÿê|Pä5©«®Ip¯ÿ1_Wµ=õ>)òÕJÕ†¦-òš||ltúEQ/™¬toê–Î5Ñþ÷è_¡×$°jàÿL1ÔŽÝýœÌ Ôû?±¶VÊ5éå˜ö�¥¶~FSËzM†rLê5 «½ÃmoHbÿH)œk¢S ¨§†¯¾] Þlº&Ö|MBê¶ ±-ÉV>¨_7×äIíIݲ|ÔÍ•0ä˜Ìkò̱º¿;�àk-ICŽYTäXOš‡òIYý©½ý ÇÔRZ– ¾&U¦–$ø²ö:2H¹ê­‡ôkÒ®bm{ÔÆÔŸ¤„ûkMïÊ‚¯IgS·÷'_“€z�è…ÿz`Î1.�ö›ôk¢v¨Zý`o“*!ý©~]‰~Ž ½&Ýý<øwT¯ kI]æü€µöÿ¢ôkbý=»ê¾JHzOjÎ1A×äù‹_õÞ}œk’ô–ÔEŽ™è1&þš}îOÊ_?9öº&í?�fs{Û³eA×$ é-©‹3èn¡(ýšTêïÙêó1™ý©V.9f í¢È5 AzKê"Çúz;sMÞUÛÖÔ êOêR)rÌD}ºÌ5 ¡÷È~#²%iåÏów�úÚsMÔ aéObæ¡iÑ5œcb®IG­üÂ5 ¡7[FøUí•/þÎeM´Â�O†mÖ5ðèTì5éâßIh´$M¿|áÈx¼˜ðkò¦4!±ë[7F†Õd‚®IW5’c±¾üÑ’4ä˜Î|J¦ìk¢PîLÅö'rl9ZçцôÑZsމ½@–ÞE_“Þ•Prl`߆b�å˜ÄkÒÕÍ1®‰o¦#8bÿLõ;²è dI(Ñפw!Œ=älÓ0�cb¯‰ñ)æšøÖ¹ŽÒ¯j¿~ÉÈv£%ùšhìÞ }|?øŽÐkbÚÕ�kâ›v_+þªZ&5ȼ@öñB¹×D£Î§î\-¡ÙnÊ1éפùßEË1®‰oêeÕþ¿ÉðÏJìÒ¶!àšüÒÎàÐ7úP/—ÔG†Ý!f©×Dýwòü¤s €ÀkØóÆŸëÚùÿR”ºzí6ôü“¾sžÀ Ô«y}Rè5ùx^‘ú÷ß˦ӟÞW¢ÙR/ö�q6žÀkÒÿwÒÙÖMà5 ¬6= §{΂r!„^ þQþ�zM “z÷g¦ÏˆaÞ²LÞ5±w¹×$´Úv½%È1¡h0Ç„^“~ÒšPeýŒý©«2¯ÉPŒI½&ÁÕ•åz 2”c2/ÐpŽÉ¼&•vUªnªìŸ’Á°Cä5±-øš¬áçÿÏJò5­«.ýbÈ»@ý+RugÀˆ»&íuÙü>þ°|öçs±ÈXêÊp]d^“ªýwbüDæ5âÛüHê�<¿ •®vør �ŒM×x”‘c¹ª]U€|l6““ŒËUõû×û§¯6&õø—xûÞ>ë}±rGŽ(Ðfz�‘c¹"Çh£µ¶ªZŒýcÍ«#ö‘:r @�º±U¹Œ,&ˆs@Ž(Pïö«Êó†Œs@Ž(P/µêM–‘�å½6r @�úw_j޽UÕûÿ ¦ÛW÷^aœµ_·ŸÕ>¯½Ñë¿^oT ½K÷“.9fÿò÷;w?ÓþTu¯÷úíÕê+LIVm4µú½õŸäÝÀ{oÔy“N‡ϱÚþåÚ[ëßµyߪû…µí'+cê·ë'™^HÝ}cÛ_Ʀ«2_WîjäÅ%Ç*½Çr¬“�;K5åØÀõúpÿ³uÖöv]%äó¥U÷Óõð ]�©ßnS}¸~ùà_†%Çz×…�—ëô8CŽ tfC£ȱ�ý� ŸíÖa/³¶q=þ]«î§»_S8—1ýÛi18øåƒæ¯ëÿí�còÒϵ™©N~Ÿ¯¼^оZ}Åû¹�úXæýéöÁÐHŽ=ŸðôzøóMêåñR7FÊl¿þù ¨÷©Óú9µ¼×ƒ¤ú]°ù)ÃéÛÕ†2õ/ï~~ð/Ãø|¬VÞ®}ÕÿÀ2½F©ÍW4Î60æX¥ýwÕ};uð«αÚòNÚ¤†ºÒ¿x$ÇzñZWó§ª�ñ§j3)I¡}A/§leLûvu÷Ý+íÓÝÉ¥cýùŠÝ÷W. ä¡SZï3ܘs¬Ûi;ÿYÛ¿·þ_ýwzýwmøâÊò^µ¡ ýÛö’Bÿt' úß¹wGe+câ·ë¼{¿Cn�üeôsŒ™ørÖ»[ÒÇ¢L`ȱþD‡æ?†ĘcÖÝUOȱ�0íÿüzïïÝÈmŒW¬›c¶2¦~»Î»W½2µ�ŒüeRkð²@´ÎW›¦LwBÖWTý7úÞúÙ{´ËW;©ë=zÍÉ �ÄM)cò·ÓßÝð×ÑÿµÃú—AŽ(ÑÆH|cú’õö¡ªÁ[*Ch/­»lÕÿê¡WZ>iøûÁR¾Ù”2&;íÝM…¨—{lÜ�qEŒ1s››œcõÐ÷ÖÿËp¯aÚñ¢zM¾3¿WçKšµá»ö ~³)eLþvýë^�å9æp8$Ë�bUgžÀ‚¾£2€þ‚+²7zÈÍÌ1ëÎÆö¾N9fûþÓ¿�!Ç:”Lϱ×r´Š›2™J±å9¶ð —i’ÃŒ3ÝÖiï©L ßð96üí>zŸ5rùË0þuj;`‘e2´ÑÅï|6¥3ÅX�ëGÇð%Y˜cÃß.tŽu÷Àb„@nzü‡§³¾û¤SöªŸú|ì÷sæ=Z`€›ôí‚çØÐþÉ��rÌËýØØº-9÷c•‰Ë_†õ× }ëa‚ @^rɱ±¥Qs,Ýyc933Ç~?õÎ2†d%lŽù›¯Øï°úO>ô�Úw=9XåØôo7:ï~øgsα÷Oºhy ›c“×A/¼øÉGslhtÎëdž¶I96vÿ )J,Ç7Â0­à5¿—¡Š¡½ŠÇ6ؾ$S˘ôí ûyØÇýçûTÈOà켆°nLØûäÆ=Ǧ= 3ìO?|I¦”1ùÛéþÛò�c@ ;�slðýMÐ;9¹2½¶¢Ì`Ž ¤éÈI(ssÌVÆäo7¶+óÈ_÷cŠ:ÇL'¿˜o>ž`;<«ó6í¢'·ëoRÿþ!Ì'UÖÖ÷uË1ë`S¿�ñÝ;AVÙŸŸ�ûÒù›!Çd't޵÷'ïã0+Ëà�Mµž€¦ÿrϱÞy ÕëÅõÐw�Ÿc¶7œúí:i ÑN¶Þ¸æØG÷‹?:ï5ü¯R<Ç”�h»›p˜ yeóçîÛ¨Ÿsϱ÷1ÎïŸÂø©ÞÝÔì3—1ùÛ}X¾\­Ä=Çz ž ïÅú1y žcÝ}�†s¬»‰bwNM‰)9fØ�qàSú ËœùŠö7œøíl�µ¸ÿR¡ç˜ñoò>Ç:Û çXçŽÁÞÿ« ûy¾|c¼Ç1|×y96PÆÄo×ÿHÿ÷÷Ӿ؜cĀ̬�cÝÖ[ ÌóкüÀÎìÕ”}© _¯ŸQRwÞÚV¬¹¼‰eLûvÖýALAèò—Qiß¹ UÈÁïƒkóªMŸÕxé¿Âø5uÓ0µÏè'żöóI�é0¬f,ñùú÷1�:c*öùÕð[ÛŠ5—7¹Œ)ßÎ\Øûrjïàø—Ñù[¯Þo6vvwT9(¤ ÀT…@!e¦*$ )0U!PH€© €BÊLUHR`ªB �2S…”˜ª�(¤ ÀTN;r¤¯�2Fÿp¡+¨0¨R�%tEXtdate:create2019-08-02T18:45:18+00:004ªáÍ%tEXtdate:modify2019-08-02T18:45:18+00:00E÷Yq(tEXtpdf:HiResBoundingBox416.411x312.809+0+0Vý(tEXtpdf:VersionPDF-1.4 G:xIEND®B`‚PynPoint-PynPoint-e5d0637/docs/_static/eso.jpg000066400000000000000000001673411512720201400212630ustar00rootroot00000000000000ÿØÿàJFIF––ÿâ@ICC_PROFILE0ADBEmntrRGB XYZ Ð 3;acspAPPLnoneöÖÓ-ADBE cprtü2desc0kwtptœbkpt°rTRCÄgTRCÔbTRCärXYZôgXYZbXYZtextCopyright 2000 Adobe Systems IncorporateddescAdobe RGB (1998)XYZ óQÌXYZ curv3curv3curv3XYZ œO¥üXYZ 4� ,•XYZ &1/¾œÿátExifMM*>F(‡iN–– u ZÿÛC      ÿÛC  ÿÀZu"ÿÄ ÿĵ}!1AQa"q2�‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2�B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ý5Õl7¹òÿZÁÖü4×víî9®Òh|Õ%:Ö&­ È3�Jðäô>ºSÅüihúAuÁ¼ëY¸w��Ò½¯Çü8qí^_­øl«’OJÁ»�+S”²imï)ï^�à�FI‘DŒzŸç\µ§‡$YÇœ„}k¨Óm›hÛÓ¸©¹¤|΋X•ŒK°f µ¶ûBŽ95›©›‡Ü sÔÖÖ—kJ­»¥&îS'Ѽ?œW¯µþ&S…·-.‚ÀP>•©ofo—�YKr¢®q–ºFås[Zg†„ØýØü¹®ŸMð�žaë]f‹àán€¸ëøUFF�¨žowá9a ¢c³¤Kg"½¶÷Ã*ê8=+›×|$®­…¡Á¢©ÕOsÆ%Ó $6N}©«¢—a…Ïá^�qàà&%€ùR� $}« 6ŽÕÊö8ÆðÂÜEò ¿AY÷^1© ¤þé‘hj£…ÏÖ£›AóÂö¬®�#n§›ZxX»a’¯'ƒ· ì?±|©: ·Š_ äÔ·ÐÝN(âG†D~T²xt7�Ê»Y<â?8’¹æ¶®“w’*‹ÀÊñZFZ˜UVØÈ’?'–ª—1ù§"µ®íŒ§'œU,˜?¢v8Þ»””`âšàâ¬Ï#qQ”'ƒZ&c8”®ãã-TgG+Ž1Z³[™V³îàd5\Æv3g_-I#9æ©É†RÁˆ>•¥:qƒÜV|ö¥eù³V¬Ì™^B.sš¯¼ƒÁ«“Aóü½ÅC<;:Òd4U›§ÉÆj¿9ÃV€ƒxÏ ÖØ< ÐfÑBh 7j¯*aN:Ö¡€…ɹªÒZ4™*§hïŽ=„ÕŒ™�ÔFàûVŒ–ä¶QÉdG\ý*‰h¦±á=iÏ̈«©n�“õ©Ð´g‹Ô�ôóWþǑϟg1ô  Æœà�§�?¼qNTi3‘R rzs@F¾SS¦U½i#¶9Àrp5-�-´a“îäŸÒ®YÄPർGzº‘ �H DA© ž”±.ÿ½Và‡8Çz›lã=M\†Àô¦ÅÏÇQÚ¯ÛÃÀ¤f6+P˵jhtâ¬3šµ°Ü â­þa¶�îVŽÏŽE9AÇèòÌsP”Ï Ô›Í5.æ|¥¸›¥L²ò7UHçÚ¥³rhæ)~�3$â´,ßÞ² ]‡ƒœÕûI2} 4î'Z«Ïèj„lG"¦Š\i5mC”Ò‚~†¬‰¸Ÿ ;wv©Rç´\,hÙ¸ÆA«ÖìF3ÎzV]ƒðqZ0–eRŸÃC‘<¦Õˆ•bÜS {Ö�œæ>¥f[jÒˇhÅ_²}®<îÕQ‘Š7t÷*„Æq[V´¨7’k Í·."­�0å@÷ªR9ªDܱù¿ Ö³cÒ²,Ì1ҵ웚Ò2¹Í4iZÕiÖã½S·S»#¨«±¾ãŠÖ29¤‹J›þbzÓÕvÔ 1ˆc­H³n­.e©2u¡Îj/0çƒKçg¯Z.Åf+1-ŒÓXêj6ŸÓ“Q´››š@¢HîpqUšS“º’i˜qúÔ/&ÔÉ<šMš(ʺš†æM‡ž3L’]§5Zêç,0y<Æ‘€çœƒž*¼ŒÍÉ<Š[ަ£7—å¡»š%aò6zš‰À€)v—äš‚((€®ãŠ‚xp*ÉS’GÖšß7Þ¨’õ3e„“ÍBðsÒ´æ„ÅC%·R³—47j‰­I5¡<{qžjµ^]8‚³h¥PædÓ@ VuìÖ˦óÇåŠÏ»°Ë>µ.%ª‡y§H¬J‘YWÚa›™ø5Ü^é­ÿ,ÀëX÷ÚK31�f©)èp÷ºFÒw�ZʼÓ”ìü+¹¹ÓÊçŠË¼Ó '"š+ÚMÖ—¸/çX÷¶D’Ws¨éÞ^k"ïLu)Ø\ç¨XR¬9¬¹4’à�Œý+°ÔtÍǧ5›6Ÿ±«Nk5Î3P°Úä78ö¬é¬ñœ-vwº_šç"³/´’�¶Šq•Âç+%’nû¤Ñ[qÚÇŒH9а±ú{¥x‘'Æ­M{}ƒ¸æ¼âÃ^kuêEZ_3Ê>aï_[Í¡òÊ:›½„7¥²Ï5Íj~�[fHÈ­×DŒrj†©«ª1äzÖw¹¼S¹ƒ}¡î›1¥PÕ4Ö�Á•¥â- …úÖaՄ'5 ›7q …Ã†ŠØÓ®Ìô¦Ø¼wH�õõ­�EYg�½¹©F¶‹fó¨'±ÍwÞÐÌÊ Š+ÚrFÀ]î‡h‘ªà�nj¢®M- M;DŠ8�Ö´å´�š­f@ pTõ«ø>NµÓ£Š¥I1#±_'çÕ-K@©Úµ§fv¿ï:V¢$R ,*䑊¬àÏ>¸ðq.NÜæ³®¼2#|2~•ê2Ù¤£�Y— <Ùq\ò¡ÌuSÆØó¿øFŽï•OZ¹iá3*üÈ3ï^�‡íÙz½cáØ20OÕn9f<¢ãÀÏÉj{¾rÊMzëxU&¯ZÂ*Œ6¯è(ú­µ9å™·Ôó_øB|è²ÃŸ¥eê06 öCáÑáF+Zðø�·ò¨�µÅÍٳů´L9Êôªéåîé:Ÿ‡�°8ëÒ¹­oE{f9ž+ФR=ª›ÛSˆÕ-CdM¦üÜŠêîtòòr9¨N•óa€5Ã4�R�m79ÈtÓÚ´,ôFl{úVý®€eûªÐVÞ›á ÁK)çÚ²TÚ2©Œ±ËC dci­ ü&Jìm<&£\šÚ°ðª!ÉP~µJ ©çÕÆØãí<>r¹C÷‡A[g ꮪ1�Jê­|8 �”}+wCÑ|ëvIRäRzu|{ZÜóÅð±à•5*øcÂ+ÐÇ…±Ã®$ô§¯…Çz§KC—ëúîyâø_+¸¦G­dk:ÖáqøW­�„`V‰cxêr—Vì²0ÅW1‘ÔVýå©ÝÈäqTd¶ÃqHÒæy¸$q9j™­6ÝÎ M;× s@ÂÕDÕ«eØÆp@íU­¡Þ¸=ªõª`b¥²Ò/Ûü `ãð­];;O9¬Ûx÷Z–*@¬¤Ç¹«g#"÷üêäneA¸b©Úr9«‘ ŒTs1Y(fL RÈJcu>4ع4ª¿icŒqDdØY!ÖìÈ7g5~ÆÖ7c$�†ô¬ø—|›9«H�'Ê«Nâhµîrs’*h¤(ÜÓ-›q!ƒß5+®ÆÆ*£+Ø· ÆGZ’9I<“U"rƒ8ÍX…÷·ùƒ”¹ÌW x«0Hb幪JÊò¤ñWaaÆis‡(ü[pèy¤’O9è¾XûKØì5{˜‹�AÍ.qò”¥ùWš�09`j·sHÌÈ0 È•Y¡ ÊŸh‹åÎ…MlŒAt§ÙØ™Xœâ•WÈbɽ r–-#!ªôK�¬F*г È~Ó{cËRÄú nW%ÄÔÓ�›“[ ;Ô Ç·DàÊ„~«c fâ’‘ zjàü¸­» kLê1[¶)œc’j”µ9j#SO^Ÿ•kÚGŽ•™bœþU­h¥N}ëhHäš¹z”U˜9É]èêſʸ­”Œ$®N„wö§yªËÍBíéL­9Ìœ'i2~ZcJGz�4ÇûÔ¹ƒ�W›ÜTm8ìi©¨�—9jš×[ºæ¡’BÇ­#60ž¦“™j(dìpqTå|jÄÍ»& ’=ô)1¥b¬„¹ëÅ"ÆsÉëOuÚÔÒ7œç¢Ôc˜9¦/­+FXqHˆFAª‰2¼šk&zSÂi ôªjâæ±9¥h·jO,ûR„ã­�ÎRšÐ1éQµ˜ô­‹"¢’:|‚ç)ýœ 9=*wŒæ•"lÓäBçd 8ÅJ¶Äž•b;lýáV"·ÈQbÊéež¸§ }�ÂÕ¨ c!©Å¡=E¦ne$“ÐPm¸éÅi-¡ô¥k>9—(¹Œ¶¶ùV¢k#d Ù[Q·Š>Æ3Í>Pæ1$Óxèj,8WBö`ŒuªòÚŒ T8\Ù†úËÒ«ËiÉâ·d·ÂôªòZ gÔºmji´`KiÁVuÝ�ßÀ®–{p â©][ú ÉÀÑT9;ëMƲn,ðNá]�Í®àrfÜØ‡$t"§�µTãîl˜äVmö���¸ï]v£`°È¬{ëa#aøªPF¾Ðä/¬1Ȭ[ËNÔ5ÚjZzDA‹•#šÇ¿°*9§k Lã/t’Œ~µ‘¦óÇ$×i6ŽÓ!bp=+÷N"M dРŠR9+Ý4©â²µ2…×a{i¸œŽk+PÓ¶±$tªåE¦qòØ�Ü/éEnÍi–áIJ*nË>ª‹PÊsRC©n�ë.ûJ· Ùæ¾©Hð’]Kšž¶aæ6Á¬××^HÉ‘‰?Z©«]y­û±Y²Hd#-ŒPÙL¿uxfèjšÊæS´óš#'ÔÐ[üùêMg°�­õ£Q¼ó].‹­`Ÿjä­a8È5¹£Ù4®:š9�"ìz‡õ62I®ûA¼i#BIé^qáØŒs‘]†�«¬ iÆZ•~cº²¹ºÖÔiòu¸í?[ŒÊ9{×Kaª¤©÷³šèŒìq֦*sÚ¦[ác5CíË´í5ZK’ÍÖ´sLåömüHÙ‹(ÈïJ—†Vùòk)o°˜SÍY³•¥•ì¥�¹„„ž™¬Ï ˆài%*Äv¼j´½›±ìáq¼«Vxý׆¼…fu÷æ¹]sKo3�q^«¯é¬ìØ\Jåµ]Í2“\ü�žþÍfy­ö™¸¬‰ô|“¸W Þèœ��YwÚ&AùqC¦{TñI4š@U¦Òʱàâºéôr½¿­.�¼a‡J|ŒëŽ&Ç5�¨ª—+žùúWc6˜—ŠË½ÓU‰Ø:qC§dv*÷G#w`N*‹Ûdôë]=ÅŽä*G"³îtý£$SZò©#�¿µÚ¿*ƒYWvìË“Šê.­C-e]YETu9eŽjêÝðrxªsAžµ¿sbI8ª’é„õFn&!¶ùª ˆ}«piÁ ÏJ†]J×µc´ ÝHážš–íÇj°Ÿ/^Õ^Æj|{šÕ3'¨¤óÏz)6g֚ĭUÈJãÁ¡¦IÁæ�|ƒ>µÎHâ‹�”I%�Z‰¥ÉéB|Àæ�¦:sJ贈ܒyâ¢wõ©œñP:çð£˜,1Ô†ÿgÒ£™þ^•+¾© Å³ž9«‰%UC¼æšcù�XÙóRˆŽóÇ­bH¬W•<‘šjA’rzVŠ,NHjŽ9 ¦M?Ëæž±úŽ*Ò±›w!Ø(òýª$¿ÝÍH¶¤ôçD7b°ˆ·jkÂpx5yaqÖšÑëM+’åbŒvù'p©#µç‘V’̳ô« h@òiò�ê4Q0|ç†Ô‘Í\[=½³SGoÇL¤‰½ÊÑÁµ±�R˜Hè*ÔVÙöÀ¡'בY·v%ÆMu:•© FÒ¬«Ëb”‹GDe¡ËÏiµúQZóZ£7 çéEgb‘êÖz¨n§$Õ†»AÉÙê™ÃkRÛPW_�±ø×Óž2Õ7 ûÕ-Yß+š˜]¬™àt4[]‘7=(ë;g©o§áA"Ÿ§m›‘ÅiŜЎÒÔ`zÖö�‡’:ÖlQ„QŠÒÒßqÁì+3T�ÛkÍ� ZµÕÈ›�Ʋâ?-ÎT1ü¨½µ5‚³:kmpÇ/ ÚÑüLÁðcßÅhçç9®GVî×:#AÇ¡>µsä&k.DÈÀg­iIw¢1ÆMBºoÌXÖ3½î�’Q[ §ž¹kGMÒŸå ÍYÑôpHó:W_¤i1Q“éP©ûFrWÄ{%dPÓ´Fx”í­«+1Æ*ÒŵqŠxC·Þº!‚¾ç“R³ž£<õ©<ºP˜<Ó•s^…$cÐærcv�OTR픵éS¢¡ÐW`:QEµ�„a•æ£h…KÖ�ŒŠ™ÁMX/b«À ž*¥í¹xŠ�ÔV“'ÔrGžµóøÌÝÍ¡Pâu –g!zÖ÷…‹¥zl¶¢@7Š¡y¡¬§å Íxu0î £Ó£�pÐòMOÃ*Œw/"°5=!W#kÖµ¿ fÂæ¹}WÃÞa8SÁô¬�&zÔq÷êy�ÖŠ¹>¾õWû‚µÚÝh-æF0i§E FWó¢¦zK±À_hAÊñX—zFÂÄ õ;íl%W¥sú�‡ÁS�MÒº;¨ã4ºÒO8\ÖMöœàÊô{½ü£“}¡§+\î��N–6úy>œpw.*…î•…Î+·¾Ò3ÅeÝi{�85±ßŠ¡ÅÜiÛGÍÒ©Íh6šêõ 3 pµ‘saŽH¡«ƒ‡cÚ¬¿ÙÍm‹ Ç�5­?-RV0‘…öíÀ¦Ke³ ­ï°dt?•V¹±ÛZAéc)¯¦˜w!ÅhÏi· Ôf˜ ÆFp„(éÍ5¢É«ÒG†â¡xê­s6ìA$%”m¨¾ÊÙéÅZXÈj™#â¤Ë˜Ïk_› ©ÛhÏJ¶WÔR,LÇ*)Šã"ˆç ©–?ïŒúT‘DsS$]ÚªÂ"ò7ýÑ@€Ž Zò°£Šk!'�@›±XÛ’y§¬jt‹�œSÑv6ŸËŠ/ryˆV<ô¥>8r~QW#¶Ý÷W§ŽØg¥¹¬8ÅhZDWœu¤‚Àã5i"$ŒŠ Û,Ù £žõ£o&¢±·È§ok‘ó 73lŽ(¼Êµ°žÕ-½°Ï¥\ŽÜb—)7Exmƒ�–�ö|t«vÖÜñSOœ*Iæ3šÏµBö>[ädãÖµÞ«À¨ZÜ·QÖ�s3- ÏcŸj…¬Ë»ZïiÏÏ OBÔŒImJuÍW’Óyç­oÉlôª—Vd1Š–�0®tÎB{ ¤ŒWJöç69ª—??ÖmXÒ,å/4àXñÖ³o4€«]}Õ�oáÅg]釜œÖ3‰¬YÂêv'‘�XŽŽYɽRÒKV=æ“Éâ±nÈÞ2<úóIùˆaYòé Åw:ž“€p+.ãKã ük'w©´YÆ\häªWo?v»+½7x�k>ãKÛÔëSvj¤sØìr@?ˆ YæLƒŠÞm?ž¡—O`ü º)H«gã†*жØÃŽõ5µžÀ7u«il`Ô6Ze_!sNŽšœ[ªõ§P@ y¥Ì#Þž#ù†jP ´¯”¾´›Ànãšž%b%¶̓Žôò?x ðüênЏã‚j¹e ö©_×&™¸°É<Ñ{ìZ‰bH÷«�Ÿ-9ëT b5$sÜæ¥‡%ÍkiÀRjý´¢@6šÈµ9#w­hZÈ|½i\N5íÙR>¼ÐÒ„$­Ý2@XÅh¤»˜JõŠn\Žæ·ôÎØ×3e!’A�”fº]6PUíN2¹ÇUu6­¾ðÏzÕ·?7“jŘgµk[tZè‹ìyÕQvÉ«!I¨#ÆÞ:Ôª¿.kxês·aÁhù€ûu¤Ý´Š•ÁUo(dõªjÄ'b´ÊLy)‹�â©¥>d ·‚>õBPФ“,c€½*7$t©väüÔÖ\uïTã¨\‚N[š…Ðæ¬ºŒô¦ìr…Ê¢2zSÖ/Z›ÊÁ§Û×�µŠ3!òé>8«‚Ô‘“GÙ½kh¢[èQò u¥ŽÜ®sÐÕѵ8[稧ÌIEmÁnjCoœZ°lÉ?/œ°àr3N:»v+¥¿•É¥nöæ¬}œ¿_¤K"Ê0+NS&ÈVÓ<�R n*ÚÄõ !݃š¨ÄŽk²ª@Ý^~•"Ûî*ì0íS-¹þî? »"B8{±¸#.*ÊÚ�rG'‘ÏZi_byŠâÄaqSGµ=aÅM}*¹L܈Â~4»=¿J˜ÇÍ!MQ�•­#@ “Å=—)1»½Thpÿ皊X¶¶E^xÁ99¦ô¬Æ�Š-+Ïz¯,�Ü+E—’O¥WtÜäv ¥#&îÛ©M”Žà{V¼ðòsTî¡ÙŒô"¢HÕ3*æ=Çäç5›<''>µµ$YlÔ[žMecDìs×1míÖ³ïbôÅo^Ú€xæ³/`ÛÒ‚îs÷Ö œãÞ°µ61À®¦î.Nk.òÄHOJ™ÊÜÛ‰·ÇáYW»2k¥½±1¹ u¬Û›0Àæš6ƒ9û«u›!ëöÓË—âºYí‚g›}¸9RM&�S±ÎËj»Î¢®½¡,w`QXòšÜȰÕÎÒÖ¥•þü|ß­r6wåßµm®¶(ÁÅ}Ï)eµþ1’MiZʬ7W#k¨€>cœUû]d«ýã�­5¨Î×L»ÞB¥nØ\mS“×Ò¸m;V˃Ç5Ði:¦NzeÆ7:X¾u«ÖChã úÖM…ê°Á9­[IC)ç“Sdj‹ñOÛš™#Ï8ëTàb­Í_ŽBéÈ4¥k$ 6Œ/Z’Ü�M$P36jÔv›Ï<â±{CræŸ!'æ&´â›Ëä΂.?—ƒSG! Pn•ÍH¯÷c=iãPpÙª+€)îß-¸špë-šÒ´ñD‘cçã©æ¹o4¯Ó’ãËÎjãS•J‚šÔïí�…Èvä? Õ–È�È¥ŠÛüÃ5 ÜÑ3'ì˜Qž´Ï î­I-brGµFÖù?.>¦¢ìР±Ý)|³ç3мmAo“ñ©îþ4®xàÊÔRóš¼0‰óf¡›k¯õ©nÅĢ͓NUãëR< : @™SKšúºÐ `qK9¤Ýå©Ó³H¨¢äs{Õ»iqŒVZKŽõrÀN})\r�Í‹YÉcÅL× ëXðjddsR ¯sPÛ‚4Ì£h$â YÖfÛ3mM®ÛiòN ¨~у—lš›š(v.^E >Ìû³×ž”@yõœncž�ª{kŽzÑÌW!¯‘Œt­+)_š±m®KŒ)ÍjYJýfNk'"œt6l_qRMmØÉµ—o­`Zà¯é[Zd™?9èj”ÎyFæí„„Éóu®—N!@#­röo˜WI§à:íéÿÖ­!-N ѱ¿fÝ=ëVÕ²5‹büŠÖÓñç'Jê‹ižUTiDù\ÕˆNTPZ[›�þHàTñ¿Lñ´`×L$r5Ðx\¶z·§]¥¥Ã5ÌfQÓ]>nEäÖ×¹ƒ �²Ü³Ä»U¿‡=)’ Å>޵Q.,¯³-óR<>‚¬Á¥ò÷v­’¹ReXâäñM–\dUÑÓ’(hw°ÛUËr.Tû>ÈëR¥ªã$f­ bøâž–DÕ¨™¶®TòÏaÅ!„±à}kImp¸"—ì¹ì?*Ñ4…ÌŠ jIàR›&q€+Qm²½qE‰æ¹–mÊ`Í'‘ŽHëZfÜúS~͹¾aÅ4¬(¤íSÃÓÏJ´¶»>è¥0ª3nä¶qR%¸~@«Á‘ó ™-öŽ*š¶ÄKÝZ£³ç© Õ•�‡JCÍÖš¹�Û )¸`w¥Kr<Ô¡1É©Tî h•ˆ‘[ÊÃsN©dLŠ�†ÚdˆïëQ³c“K÷�Zd”×Û4Õlýi’HAÇÏ0ƒÁ vdç‘QÒ xäšO2³$~|UiЇÊ÷«Jp9¦H™ÐtÀ³r*) Ý÷±W$‡<÷¨&ç â„®h™›<Î*•Ê�Ù9æµçŒ‘Àª—á“$RqÐÒæ<ð‚ lÖ]ü'Ž+ræ,+6îTçšžR‘�yn[¦k:xS[×±ü¼ ͹�ƒÁ©’*ç9{X�Zɹ€’q]-ݯ^+"ö݃Š”ms·¶Ä•—<{ãŸJè¯á;Ö²gƒ$ƒTíceÜÙ<é ÀZ*ܶà7N´Ty-¼ÛNEhGxBpkÚøŒuïZPÜ©AŠö’8¡ùR2kBÖð10üëŸüTñO·”84�ŠQgaa|O5­§k%eä×g«Ø5§eªo�ÜR5HôÍ'W\Ç­oé·áÈ9â¼ÆÇV ®º­'YósJèÒ(îm®÷µkÚ8’>kŽÓu•p9ÎkfËZ…>Ô^æ©Mž««"¹û S‘ÍiG©äŒ0¬äiÔ¿5ÆÒ¡e…Ti•ûŒÓ„ã=zTÓ�wŽ <¸ª)އÙn½NsAIóŽãškO°õ5 ¸ 9"¡y€êxœ‘q‡1§m¨„qÎ{Uå¾PµÌ Ю =ëFÓR >cSt7I/5z¦Ú¡¯5ôÀœ©¬ù®Èn g9ÙXP¥ÔÙML³€Ç5¹£jæ;ý+‰†ÿç5µ§jŽkQ‘ZŠ}DÓµæ¶Z†Üs‘[ZN³±†ŸzÓÚ·»<ŒFšúÍ ø±ÎÑ¿·zê¬uÁ2|ì+Çt=uX®5×i:«8n Z«(ìÏ�Äà•ôG¡Ex%)æ¤Á®nËU1&õ«öºÀs‡aÍuCï«<¹aÜMq'ãO`UD¸ 2©øç¥wPÌ’¹ƒƒE˜Ûpç­+tâ Yñþ4ás×&½Xãé>¦N yëNÝŽµ]îðN(ó÷ŠÎ¦aMlÊäe��éC ŠƒÏ Ô‰.áÅ]<\*«18´#&hUÇZy`E6ªT õBNÛˆÀ~5©““ÍX¦ºf¸1WCDÈ˃Àª@€v§*憥å0SÔœPew›ïYÞ†åºÑýªÆ?,çÞ¬œ�£_í9{**k{€{Ö43Ó²*í¬Ý O9\¦Õœ»¥í[¶R`�Üâ¹».Êè4ÖÝ�õŒ§¨¥nÆ]Ø­�0î~N+Àö­{ÈÇJg4雺iÌ€Šè´ÇßçžµÍØ|¬1[ºoÊ+hLóë­‹NbHï[v¬£­aØ?Ê+^Í÷/ášë„ϲ6ôÛÆ²å†©7o�œjœf1šœ6T鄚8™f7òð1šr¸+Àæ¢Þ Ò!ÏNµÕsM¸žŸ¥ S»šT;zÔ±¨lZÚ6 „Tȧ¬|Ô¨™"Ä@é['s9È„@[¨©b·ö©âƒstâ¬Çl3Íi7"²[ñÒ¥K}ÝV­¬ H�‘ÐU$fæS6ÍŸ” ›‘Íh,,ÀÅ<Û·|Ó±>Ñ–Äàfœ,€ëWc¶ù9çñ ÛØÕ¨‘í5)½¸ÇJ…­ÀÈA“Z†%Ç?Ê›öP�NÅûM ô±lüÕ*ÚÅ^ò=©ÞIÇJÖ13s(‹_jo”SŒUó{RrzÖ�hK“eTÏj�¢Ì‡<ÕÓ;Ry< °®AxíHPÕƒZk&{SYãÈ5­[vÀèj" sրܪëŽEFã5;Ä?�8¨¤O)Ç2m´ •2FìÓvJ�м§�” Ôe>|ƒ�Sv#)ÏQåúÔ…3È=hòýh“"e9ãšz©ÛÈ5 àQ@Yµ±cØ l°LVUwSa²h½‰3.mvzÕ9â`ŒÖä° ³î-pM;ܨ½L+›RXVuÊEtSÅò‘ŠÊº±ÚxÍ'©Ð™…uäȼˆï8ÒÏf6Z̼²Îp?ÆE-Nvî—wöÊŠéîlò¼ŒVmÅ©Rp¿�I¤t9]B܆ÁʸµÚçŠêµ+"Íó(•sd¶¨éZ£›žÔoäQZ76¥dû¢Š’Ò¹ó-½þÓŽjüWxN½k/9cÍ<_–<+Ú8V†êÜîBçµ=oLddýk_Ã&¤K­í÷ªZE&tÖÚ‚¸ûzUë;ñùOZäá¹Úôln±Žh+™�]–°ÑL3ŒWI¥kAØa«‚‚ðcæ5¡¦jÞL£ž*\KR=?LÕpÙÝùVÕ–´S^u¦ëÙ#i­Û=SqëRj¤wöZÀýkbÃVܹ&¼ò×W%€V�méºÎч<šÌÕ>§m¥‘Ë ”êFI®b×TÈÉ9«?ÚAù9úæƒXµ¹¸š�Ý÷¿:™uWæ5ÎG}—ùÏÒ¤ˆÎhfÉ›rêKëȨ$½2.TšÈ’ÿyàãñ¦®¦…'5sHÊÆ„÷$c$“V,ï 0$ñÖ±Åáy3R-É× VSfËS kÅ‘H ’j�ÉÚIäûwª‘]�ÍN³™—'5‹w7„l$Yf‚+cL“=r+6×™0qƒW­ÛËèk$õ&ªV7íîcêsWlî¶°$Ö sü¹&­Úß�ÅS–§ मv:ªc�ž¸Åw¬&œW•Ø_la]g‡µ‘u;ž>&ާ§Áx^1Ïj³ezDƒžõËXk;⵫c|8-Í=;ž$èï¡ÖÛj&02sÅ^¶Õú“\sjd 3U›=[a={Ôó[cŽxk�ƒê©’Ãó§¥Ò”ÝžµÉͬnÀÝV_YÙb»[hUš9Þ›7šªGÞ¡ƒ\W?)ÈÎ+š¹Õ|Ãó±¨WTX�sQÏ)u6XMÝoƒ¯ÊF~µ5½ÈÀçšãìu²í€ZÙÓï·‘¼óü릅w¦pü¦ÿ�޼æ”˃Ua”mäÒ=ØCÍzðÆÉ-Î7NåÁ&h-ž‚©�ME#jª½zVñÅsîÉô/-#š 5•w YŽäH¹5ªªžÂtÚZ’¯Þ§1ÀªßoÚÇyÒ™5ÿhöˆJ ™æ;±Li ïTZü—ã8© ºó[(¹£¦ìh®„c·¥U’59Î=©­q´ãÒ£ó|ÃóU§qF61õØw© Þ¹GK1Ž+¶Ôð[Žõ“<œùƒ"³œSG¥‡¨ã±ÅË¡�~µRçÃHá¶­v²ÇÀ¬ËÅRüq\S �J™]&£á ÌHZ‚çW5Ý¥ˆ�ðzUë}5&¡aîmõ×Ï.<.Q0PgéX¿‡³‘´ñí^Çy¡.ß•Gå\Þ³áâÀ_Μð·FÔ±×g‹k>+œÈô®n÷B$ž9¯_×|Õf°Ëòþµ• •Á«0Í�òŠì‹8åEeÏZ•dç9ª1K‘É«1IžµÓc(—#mÇšž7ÀÅU…�Z€n8Ín™Œ•‹аÊ*´g?wš³Ì=khHæ™bÆ=êÜpäÔêN«±ò}+h»˜MØtQ|¬$Ò–wßš´�y^kXÉ-Îg"¶Ü~J�Z¼U¸­²xa-=+HêC¨g%‘â�°ô­AkíN[Ojµu!Ô2¾ÅìiVÈžÕ«ö`§¥(ƒ=´QBö¬ÌK<ö¦ÏoQZ‘Ú’OãfIéT•…íY�m3Ú˜ö§¸­£f3Ó4Ù,ƒ)`Óµ¹Œ-¶ö¤0nè+U´üTcÙïšj ªOjsÒ›ö“¥j½¯°¨f¶ÀéÍ h_´2¦†¢’ß#šÕ6�Àü* -÷gƒR5PÈ–ÏjjÛ™¤ Xf´d³9´ôu­„|°úTf Ý iÝ|½Æ”CëVE¶JC:ñH.WÆÞ”†0Õ?—Í_­*²�8¨&L©ãš¼ñæ¢xÈ 44eÏm»FÅ+شΜߌg'ŠOíp£,E`.¦O³LŸQÈÛÓéQ)§cyõc'*i#Ô l±� Š�jA†3Päi\èâÕ<·a5Ç ×0š‡£ T‰ªa1»õ¬å©¼%cªŠü`ÕÈ5< dW#oªgø³Wmµ}âk&­±Ñ÷:«k±�ÛŽáWíïw.Ió\•¶§ÎI­ =`ëX´jìΡoCG×4õºŒÖ%®¤$\gz9ÁSšIÛs'NÈß±»Ë©&·ôÍOË#ÈÚIÀ Ö̈́܌žµ¢•Ï?Nç¢ke¢]Ç¡®‚-dyca¯?Ó®¼¼ õ­»K8Ó´0¹ÅæE_-Žpi¢"OÍVÌXSLy=*\A;•v•~=£Ü=*c Š<£BˆÈ>Z‰¢+ŸJºÊóÖ¡“棕 2“›$UK¨ø-Þ®¹^é��«"Ñ‘t:Õ ¨ÀL�Z7KúÕ‘˜ë)#TÌ‹•Ê6k"î Ù5³qÀ9¬{ÖÆvô®Y02.£Ä†²¯Wkþ5«vIbA¬ËÓ‘ÓšÉ�pØÎ»N*�àùjýÑ,¿J£r2§šÅ›À¦W+ÅA·i«°aUîqÁçëXHÞ(­/ß9íU®[¬(ÎrMR¹ûÜÖOc¢‘HÜñPH…ºw©ˆÍ4¡›Øè†åIãªW L�[�ëZ’G�xÉ=*´Ñ´`,èAn™,Ùó&ê…¢Íhµ™j),²:ÖNGD¹•=±T%¹—y“8ô®ŠXI�kŠ¥uk”ØŠÇœñYóY�Üç~ÌS "›«Jǵ·5�|¼Ò %£Q¸c4¥+�1HÎ]7=MOžÖÇ¥iæµYƒOÚ@ šÊR6Dp½+bÒß8㨦ÛXnê kÙYƒ‘À®yLwHKKbqZÖÐ…Á‹KaºÖ��ŽÓœÁÌÎsDúhÚá[ˆ¹j�¥·ÌïZÖ6§píMMžmi\еPª6ò{Öµ’’ª–ǦJÔ²‡aµ¼%ty•šeës¶¯ÛÉ•éíTáJ¿lŸ(=+ºCͨZˆíêä,qU#ù”b­AÍvAœ’,BÙâ®Û9ª1¤æ­ÛŸ–·LÂZ—bn9«PÀûU= ]´%_>¢µ„Œ&ËŒ»n™?Z¯ |ç­^‚.ÓjrJDöÉóŠÐŽâªÚÅÞ´í ÜEtEœµ$K xu«¶ðdóÍ-µ¨8$ ¹ $žJÞ ûs�…ŽF=jhí2y©`‡ÔU•‡8®¨FÇ<ªÖÏžp1GÙ€««n J�è3[*wÔÅÕekìiÂÐÿv´ÝéNíé[Æ�ÈuJ)iÓŸö\õ«ÂÔ÷§-™'€j•7TËk\!³ÝZßa#ïL{`­Þµú»ìÚÆKXdt¨ÛO=Emlö4ƶSÁaùTý^ktZ®b>�œæ¢šÀ…ãšÛû#3p8ö¦›BI ¸Ⳝ¶-VG<öØ^•Ù ^EoÍdð*´–Û嬕7ÔÚ5S0ÞÐgš�­xè+téà †k ¤ÔI.Œ¸Õ×C ­÷€i³X¢¯ÊsZ¯j3Ò –ÔœÒQ5R¹ŒöÁ†dTmlîÖ´–žÕ¶™ëC‰|Ö2;zdÓØ¼kBKb‹òw¨ü’>ýC�a©ín=):U÷@OÊ 5¢v¤•Šæ3š,kC“Í]’N4Ã+BÔŒö„À¨^!éZMnNõA…â³Ì·‹ î�yoóp;Öä±Ò©Ü[îÉQIšE˜WVû—¬ë»"$g5¿shrIÅR»±Þ9¬ÜMã#›»²È9™ugÁ®šâÐ*Ötö[Ç¢Æñ‘ÍI§|ÜŽ´VÛéäž”Tr£E6~m*îê N€qŽ”ÈãÍN‘ó^ÙÍÌÇĹ©ãL;ÓaLŽ•:ü¼SZ…Øèð§Ò®Á6ÄÕD�wZ�X• \¦-g{zŠ™¯sÐ×-©åx&§ƒXg8Éæ³hÛ±¾×g³Œß˜Îw~µŽu<ÿ4_o<’j¬MèµmÇ˜^íêÙ®uoŠš-@ÿÅKØÑ3¨µ¼�Õ~Ö÷2aÉÅrú}þO&´b¿ Ù¨±¼}㣋PÙ Áâ¯A¨�œç ¼ó5bCk ‡ã.VuÖZ†áϯiÀÉã¥qö†ôkcN¼ÏSXÉXéOœëìîàæµ-n¶cšæ4ë�£Ö”Wc nE$cZ�ÑÔÚêvàÖ͆¢Œž q�^ecsZVz§”'5ižt蹪ê{P`Ñöï7©$×(5àWïT‰¯qÉýh¹Ìðí]­î×$Ÿ»úUiµ"שùGzõְŽAjdZ®å`Hçô¨²‡±ÐǪgø½ªÈ½Ÿ1â¹AªõéÞ­Á©îN9¡Y39P¹Ñ[_„q“‘ZVzÖÀqŠã“RÃu�\‡TëÅo~¦S »µ¾¸H˜Zš~´c9® ×WO§e«å¸­a=N)áüŽá50çšy¹Uù‰ë\¼ZÆÜr*q¬yƒ©#ë] œÎ‹6./”œTOvTpk{àWƒUî5R«‚[¥Z�ŠXvͧÕHnH5VãVÈäæ°'ÔË7—¹M/jm1²Ú¾Ö$}§ˆdc\Ä÷§'&ˆµ §ï~´*¶4x]úËÄ»ˆÖŶ²>µæ¶ú¨%¸¦®Ùx�†Ú¦¶Ž"Ç,ð—èzÖ6¯ ãK¬þðñ\�ž¸FóV%ÖÑFA�l«£�á­5 r¶E?ûlmàu®1|E”m­ÐúÔ_ð“*¡ºU¬E‰ú›gy°ƒ« ýjC­rDd×�I⣿åsW,|M–›­\qiŠXРÕî«ñêaÀ$“\<!ÌyÍ>/l'œWDqHäž í®55 €F1YwÚ‚J0H5ÏÍâC"}ìVmο¿£àÔÏ™Tð�jkê%d_““\Þ§mæ¹ßÏ5nßWó¸fcô¥¹ÅÂáx÷®j’ö‡e88éJÀñšÎ¸Ð”“´ ×E%³À¤ŠÇæËŠå”ØU²9¿1RpªŸØE�k½xP�²(9㚆M!fåT ¨T[.8�gMцñ¸VêèŠÑ‚@­m;@Æ7 Ð`)µ�aPú•èP¦ë|'Z‰u?½Z0CÀÀ«v>’f_” ·+Xè�þ”à°õÍzÔrê«Y-6­u-!«+[Z¼Ç©'ØUÈ´y\ýÒ£ÜVe÷Ä›K2~Ë�øÿõëŸÕ¾/'kÏÿ­]j(|dÆ�z›#ºþÉXqö‰•Aÿ>´I%�¨;ä⼓Uø°òó•ÿ?Zç5O‹ny7 }²iK†�ÀŽªYEz¯V{œ¾'°„‘�qïU[Çö±ÉN=Áþµóž£ñrA’€8àÕqñ9¥MÏ!?V¬%œÓŽÈï‡Î[ŸH7Äë$ÿW°íÿÖ¨¥ø·gÀuÈ÷ýjùŠû⪻íŽP1מµ�?ÄöpBÌzsÉ®oíýl‘ѾçÕð¸lð Úsßü枟ìäy??úÕñî£ñ¦;(Ó–pOLfª'Ç Û˜šPO›Ö®YÌä� Ç©ö¬_´ùÈ_óùÕÔñv™2üÒ '¾ì:ø‰>4Ý0üŽ9#ü+JÏã­Õ¨RÓgFOøÓ†g'ñO†&¶>Ñ‹S±»‚u˜9þU+Ú‰Çî¥Vö¯�l~?M1Ƀõÿ]W‡ÿhÙ, ´—§@ßàk¶µ-š8§ÃÕâïéÓ ¶*)-3Њò­ö�Œ¨72.ßSÿ×Úh_4�u@šXËc?xëZûjUŽ Ù~*†²‰¯4Î1Ï¥DÖÃ8É­+[›N ö²¯<Š&²hùPãš¿«Sµã¹Ìª´ìÑ�5¢Ž‚«Ëd@=+ZâÕe�¤• ÛíàŒã½e*<¦ñªc½‘ç9¨d¶Àã5°Ñ`ò*´–¸ÖR�´gs&H=ª&ƒÚ´žy¦K¬\ ®fIz㊎H‡J¾ðç­BöÙ¥ÊZ•Ê?-1£Ú>jÐh¯CPIl$=qY´R“)<9^*"«ïÃ�Î*XÔY£E#>hv­U’ ž;Ö¤¶Ü|ÕZKp3š,Rf\ö„©ª7§‘Çó­©a5Vâ,¡ÍKFñ™ÏÍf9ëU%²Ú…@®…­W’Ã5ZK0Ìx¬Ú6S9×´Áè(­‰¬ˆ~s#òÖ8ù©£Š¦ŽÛ‘S¥¿^¦½‚H"Cš˜Gó –;BÀ©Z×f8¨�ÉSI«šFWÐÝ‹QósŠ‘¯L\ƒšÁK¢àm852\>sYµsDͨ¯ÚT$Gj³ktdC¸äƒXv×EdÛš¹kqµˆõ¡¤j™ÑXÜà jý½élàçë\ìgU«;ÖÞFp1ëPiXé­nK.3V­®pØnµ�k||ïÖ´m®”Ž'µCÜÕH謯BgŠØ±¼éó~µÈÚÜ•q¸æµ­n¨bÝ{VSFÔåfu¶WÛXsÒ´áÔF85ÈÚ_€2¦®Zê%~ûf²z1j{�¥ž¦6jìZ�Û’:ä¬5=äa¸«Ë¨ç¿ëR9PFûj€¶“ûSoñ`Öß²ýÒi†ù‹�ÇŸJFWLêcÔY à‘Í:-L¡ë\ð½fAå’)WPeûßþº –飿.Çâ¬Á¨0ã ×+ªAÍZ³Õ÷I×4®c,=Žž=C-É5hj#g\âß…©RÀ5jG<¨#¦�R` i隆Oï¦IÇõ¥]@íÁ&›¨5JÆ¡˜å¿*kÞƒÀjËkÞ¹Ï5º'�zÔsš(î.·‚yªþ{/RqUŒå�&‰$-N÷�dß�xïÁ©íuʲðÄñNÆyãs �߇\!y&¤:ÉÙ’Çñ5Î’Íò“‘C^�MR©bÝÍæñ�H'­T“]ë†cŸzøºÃñÞ™öœž¦�li<{ZÙfË19«öúàsÍrÐ>àJ�.J÷¡U"T—c³·ñÅå¿Z‘|DYø'šã† S¡Á§Å©�à–æµöÇ;ÂÜìSÄ.ÙÒ\j[À*Ü÷æ¹ÕØ·Ÿ­7ûyã?9ÈÅ °G uöÚÏ–øÝÿ×­+]`Êy5ÁZëŠÌ œV½Ž°®ß!\Ö|£“ŠÆF±eK†ààçJåò‚­\䊣8,§9¬dZ(\ÈNsëY—R�ŸZѺÜ=+2á7±ÍNæÈ̽&F'­dÞ“„‘Åm\Ç·“YZŽ1=GJåq:Q�{!9Ç­fÜ’ß…jJ»‰Èª ó"°’±Ñ2qƒU2Oaš½u qÒ©Ï’qX½Î¨•œ‘ÅS¹@NG^õqùéPL·m”Íã¹Fäî]«š„ÅŽµbHعã$Y85Œ�‘[Í=a%¸ëSˆ¶Žié5”ÍâÈ–6'æ©V×bÞÛ/ótÅY[aŠÅ›FW)Ågæ žÕ*Z0ûµv+|ý*ݽ° qšÉ”c¶œ²ã9«úv{Ù‹Ks€*õ®�³� úV2–†‘š[˜–º^FÍ]ƒJÚAÏZÛ·Ò½�ÞÝ*Ü:)êT×3H—^ÝLì¢P°P3I�å‰Û]`ÑÎÜô¤]ç>�«7¼Q�e¤åÅh[i¼ð­xt­§‘ÿ׫1i¼Ö/Sâ.P³ÓF9­;M,2ƒñ«v¶@tzÞÃs Q‰Ç:ÌÎKsV¡ÓÁ­(´ü¿"¬�;æàÚ19å^ærÙ ‡ƒÒ‘,ÇF¬¶A~øéNû�k¢2ö·3E¯©á·%FE[[\‘S¥·¨®¸#T+Ck“óU˜­G§5fPHÈíVa³Ý÷El�´1•BŸØ÷†¦¶²ö­ì |¼}jx´Ò‡æé[Bìç•R ;l cÚ´lírNiÖ–EðcàZÖÓô¹.\%¢ú]4èNoÝGjÊ ä6¶›×åâ¶4][Æ |ǦEtøzv¤º›lP3·ÿÖ+Zó^°Ðbe¶UP1ý+é0YJkš®Ç�S)û°W)é^ Kdó5ã?ýz±wâM?Aˆ¬A2?×!â_‰&V"F>¿àk€ñ'�þW2JXŽÄší©‰ÂáU©CWÿxz&½ñhía£¿ùÀxƒâ™šVÛ0o©çô5ç ø‚ï+fë\½ßŒ$ÞÅÁlúšó«f•m{è}&#„,ÏKÔütÓFIb®kžÔ¾ ;FV9q��”Šá¯¯+ºIäy§fKˆÃnÚ1ë\JßHYŽòwš™n¦ò×妺“ÑÊ’‰ÛE«¶Íþa÷¨n|x4ÿõÌN;æ¸{ÏÉØ@C½ªŽ¡ª½ófCÅh“kS_c½±ñýÖ§)0JW0qü«rÓÅr�>Ñ;=I¯%·ÕÒÇa³fÞG<÷«cÄòÈÄO6Ü�\UªhÊÈöo‰FÑÞ[ÇOç]�ñ‚kTV‚v_ƾz]~[5U¸•±õ­ãˆb�T±ÁëN1”6fs¡ Ÿõo„ÿhûÍ<§úIã®ú{W¯x'ö§ge[ùC#c‚IÏó¯„tÏAö`c�’=êí‡ÅÁgrŽv¯NkhâªÒ|Éžf#'ÃW‹\ºŸ¦šÅ+ÅEøÑ�ûcšè $ɾѕÔôÅ~sx_öˆŸNx䲸p<1þ†½ËágíhñûTÀäŽÞ£Ú½\>e«TÜùlgÔ¥wKcê ¨0¿7 z ªðÆ9kÁ¿4�Ášd°�os]\‘¤‹º.sé^‚öuu‹>~¤*Q|³V2ní¿»U^!žk^X·Ž‡�j¨ö¡Ûlc$Ör¤�!UlÌï)KóÞ™,r{S‘ŽzñÚ«º•kLÝIt)ËÞµ‘b¯HsÆ* ”õ¬Ü ´Ô¢é’r*"«Î€†Hùà ÎQ±i•L[‡5Vkj¹rœT. JÎÎæ‰Ü£$^ÕÁ¸üÝ*ë'µÇšn%­ Ô"[©§>LŠtk•ÁÍIqÚ;�ǽB_qëR´[Wš…úPh†°$äsÅDí¹zT±ËåîÜ)0$Éï@È­†9ý*ÈñšŽ(ÀÎ1Ö¦BfÕ‹Lr6×ÈëSÇq†œÔkåÈÅ^4¦iÛ\–LU„”ªçñ¬ëk€£šž+‚ÍŒÖf©šp_n#‘š¿§‰+6(ü°nÞ`ŽY˜:TÈÑ;e½ðÚ9«p_ü½k˜·ÔÁ<*ä‘f V}Ma%s«´¼½hGzc=+–³¾çƒÍiÛ\–` 9¬§›Á³£²Ô|¶ëZ1j<õÇÏZÊQx«V×DŒÉFÇTj›gPÞ2>”±]ùÒmÏ5”—;OZ’Úäy€®F(5Œîm5é‰Ïzu½ÆG'šÉ¸½'îœÒÛ]Ÿâ4‡) w�zqREq´ç<Ö:]ÇÜÕ˜§$Ð5fkÅ~Àõ'ëV£Ôw¿¥c½‘Œu§[]–?6~´ÌeLèm¯„½M^‹PòÈ®b;Í�ò·5r C³š.sÎ�ήËTÎ9«cQÎ9®ZÛPÛÀGZ¢3CÍ8È84Ç•ˆùȪŸjëHnø4Ù“<Û9ªÓ^ö¨'»ÉëUeºÈëE‹PHº×º‘ϵM¹nMe¬ÅÇZµ¼dÓµƒN†œn6ã5"ÉTá“åê:Ô« ?w½+u3qL˜Ëów¥qUZ_Ÿô$ÐdôINy'š‚YþlO™I&ªLØ“·Ó!2ÔW?7ËZvÅωnYžµ!` nÕÆ]Œä®l­é jE¸ßÏ’—[G'õ©"¼ÉàšÑJÆnÓ–|tn´Äº*ÝzU¸,ÜœÓщl曕Äà‘½a~«ÜÖö›©… \u«œƒšÕ±¸95¤%cެÜî´ýWpZßÓµ˜|ç5ÀY^r¼÷¹§j>AÉ9¯B…Gk3Ï­Et;U 0xãóª—êcÎ"³­õÍ m9ÍLÚ –3¸�κý¢±Â©É3?P�±;»ŠÌs—­K¹EËqÛŠ¦mŽìšãž¬ìŽˆ£p˜3RÛŸœf¬Íh£§ZjÚà�R©—Α±¦¨=5 °ïa�Ut›s`ã¨î+NÙ>|½zŠHà©;BzRu{ÌóúÐ\àbµŠV1U•5®Ðr1Y²ÂUŽàq]ЧoSYó[‡<óXÎ75ŒÌ¯#qÂsM{$øÖ·ØŽ5 ÖçÖ™²¨eMl{Õ »vQÀÍoIo”çš©=¦r c(ÆFD_;¸ÀÍU–:VÌÖÅIâ©K­sÊ&é™S@ žfßAŒ‘[·|‡�{ôó¤[�L€ƒ�:vú×§�ÁÏ.X£ÏÄâcKVÌ_x.]\Œ¡HóÍv6º]‡„í²›Lz Ôº¿ˆ­´kc¡TÚ;þ½y—�>#ívÉÁîkê©áéà¥fÏ:«Ž—dtþ+ñæÔ!X¨é^wâŸ2£y 0F ®Xñ—ڃ퓽ëŒñ¼Y ™ækÌÆf2�ã¢Àåjº5uŸ² kšÕœ×+o© �›;|°HµrÎ÷Í��$ !3þ}+·‘\â”îiÝërZÍþŽr=¨Ÿ\1'˜]û¨&¹Õ¹‘'´±9=sIi¨�2à¸!‡½j¢Œ\ε¯&¿…Y𠌚¦ug2f&5õj©mâxî#-ß<âªj7¯px¤–ºŠS5§×ZÊM‘JI©£ñQBŲJâbÕ$y Ê~j³¨Ò¾e< Þ4SWg,ª´ô=+Oñ‰·�ÛïWQΈíg8 #.Þr_Ö¼NÛ_e�üÃ�îÖ¦ƒâ1ø�5”ªc‘›ié“þ{QNUpîèæÅet1°Øýmðç�l_ëUe�É%ÊuEÜ¢@'­FñžÀœU‰Ô¨&¢’~=ë«êZvЫ*g¯Z‚Hò8ëV$$¶MFýk'DÊŽ˜àŽE0Ó֬H™ri»+6_1Ê2„TË'š•mò8¬Å=…{ìøö¢�‡Ì�É;%L‰Á÷ÍM¸�B¹ÎÂj¼ä!>ò`�Üæµ4Æsª?–J�¹$×Yè+|™T• !*r)ðªJ„£>”ÕÚ�›ñ à¿ZCzsNVÝRÄw&§•�;䀑œSa·ÜjÔÒdáM4^œš|Œµ4ÚñÍ;ÉÁö§Á¸žjiÏZ‡å§r)Øé…o–¬ ¾^­GŒÝØâ ¤ìT½ù U†l³9ÞÄ7¥ThðÜž´�bÅ•À(͸\“Í2NNZknœÐ “Û¾Éqž NØ$Š£nH“-V_ßrx4¬R'·r±ícÐÔàšªÐŽµ,Rþu-™(<Ô¢M óUÝÉéŠasžµ“F©–ÕËšž q&Öt3�¹ã5$W!ßç=鸚¦k#�à5rÉÙ˜çëXö³l“¯¡mpD‡9“F‘ÜÚ³œ‰5³îÐ|W5e9ÝZ¶÷ãqæ“W6R±ÑZê�Ÿ¥\†ç ‘Þ¹û{œ ¸ºŽÕ JÏ–å*¦ÂHXŸ˜~fÞm �Ö±mîK““ŠÑ³˜y_9¨q6�Bð�sùSÖä!§ãTâ—h!Ï�¹Hâ¢Ö/�²êÝnj�'ã®k3Ìç�çR ÉéBe)÷4’ç+×?JzÞèH¬¥¸#Ž”õ¸*zæ�Êö–5|â_v{Õ˜õ «óšÆûQ>n*hî6Žy¥d.tn%öÜZž S 2O¥` ¼¦¦†ñH¡\RšgC%ðf §5:j<�‘Ò¹øoñÖ¥7™ÆjŒ�ô¾ÁË7^•jÚóx;�JæVøœUØ/ÎÑšwbi3¢Žì/SúTŸjÏFÅaC|[ï—í¤°ÉÀïO™�à�g¸ÏñÐú–¨˜nÆÌ’r*¼—e›¯îȲ4äÔw7ÍÞ˜÷!š³Œ¹5*Ýn(»"Z 8úTë>@¬åŸ)�_Ö¦I>PR1“5c›åàÔ‹)=ë69[oÊEMퟚ™‹Ü¼¯ž§š±ûÕ(ääU¨Î@"‚f>VÈæªÉó?­Zd,œÓE±ëÁ Í» kçV•i�¦Jß*â´H�’p8¦-ÃDrM+½Wž\ô¥p-­æóÁ©âº!†95”ŽAâ­C6Þ´&Dö6¬æÏ&®G9R ȵ› c5rº[AÜÂJæþ�vX ì+nÎëæ&¹;yÊb´-5"$ÖÊN&…β;° ô©øìzúW8š™8…XKÞy5·µÐÆT‘Ð[ÝncV "N¦¹Ø5�ÆyëVàÕq�¦´§4ÑÏ*m=ŽŽ(‘T䊚(ân¤W?¬\žp=ªÄ:žK]1©béÈé­v"àœU˜f@yjå?µÎ~V55¾¬Í÷Û5^Ý6a: �H]ÝÁÍ0¿…cAªð5z-D:žµ¼gsMÄ’V¨�ÞÀ£ïiñÞ})§Ì;X�Fæç¥>[O58§ˆòÿZ³ xóO”‡.S,é岨®,ÄKÒ·|°£5Fø‰›‚e«¹ƒwndšÍšÈfº m„€àtªóXn^‡5ÏR™×‡;uc�{š¡se�Òºyôì sUî4Ÿ”íÍ*W7…[Œ¶Å âª^Zexk¦ºÒÀlÍE&Š]NAÇÒ±•�•dq:{œVeõ– ãšîu jò;õ¬-CHÊæ�&o‰œ�í‰ê+:æÄ²ôæºË½1ˆÉ¬»ë=„äW4¡Üé…S™–ßo=qUeµð:ÖüöŠÄôÍPšÅ‹|‚°•3¥Jæ öAÁê¤ú_PÊé¥ÓKÀÍ6M,íéÍsÉBv99´Ì®ÅU},©'&ºë�,¨éúU)tÞzsí\òG\'sšû n£–Ä€r¿˜®�û0äõÉ4ß•²+oœØ³ôP)ëšÕk¤åM6-7ÌŠ†ŽˆÉb€‚µ-¬FFµ-®—´|Ãÿ¯ZvvÆqYµpsK© ­†Ò +N PPqV­ì?vj ``â—-·0•dö"´µ ­KK#Àš–ÊÀ(ŸzÔ³±ÛŽ·8ªV!µ°Î íW£Óøùjͽ§U¸-8£‘R«vQŽÈ÷÷§ý�œVšYîcÓŠkDAéÖ±•¶Q™¢vï'û­‹qžriDç+'Mö+œ¬�àr8§¢§§Ø /‘‘òŠ s!Š NMN ÜÃÒ� 75v;|v­)ÓMêfça¶vÞµ?’ã-´{zŠ·¯šØA–ïíZZ)ÙÎI¢!8àUë G’@ª…˜ûT¶š{;„…rþ•ÞøcÂép ‹ð „Lž+×ÀåÕ1sI-:žv'*ÝH¼)á8¬àK­@`�Ðÿúþ”Ïx²+Ùhû@�ÿ^«x¯ÆÂ é p£ŠòßøÆK™�ÍÅ}{«GKÙÑ9p˜ ¸©óËbß‹¼fÌí±Ïç5æ"ñÞÊäô¦ñ'ˆxmîlW+ªê@Æ|³Ö¾k�rnÇÙ`p*ŸA.õ‡ó€bp}ë#Äš€Œ©$sÍ:[–Žœe»W7¯jžx++Wô¯&UÛzžæ�½k¼×¢BùÉÅsÚ–£ö·,�©ë?3¤ ±ëûNg,¬pkžRlöa…Fëj‹"l,AÁª®¸–6î×GpQ�3Ö²®®Z1»<޵�âë×’Ö7\íÏ8ô¬Ôî§BÄÏ«,óùЧ¥Aªj&LaÏ«·ßµ~î餈‹®éX×vð¼Ng#�»ƒN=EX¯ªNDˆöí�zÓ罆Q´Ÿ›Ö,—»öd¨Î5FËPydbøb:×DèDàžÆž¡,–ÿ6ï–±µ›ï´Ç¶ã½YÕ5Fš0ˆWýk•­÷�„äšì¡%{3ÍÄÒilMkvlš2Œ2 .³¨»r>d?ÔTðCv.~•KTÔÚÎÍÞ6vkº“æz& Îë5vaÇ$T¶÷Þj!r‡=k”¶¼�ÈÍ,‡cy«pêKÒÔ’ÇÚ½[#Ê“{›w^*û„«"’ì*« eµ�ç•q)ÁéõªªË$oöþr>_­c&¢^ÑÒÄ,?�\f•ŽÒÂåb!`lßÖ£¼¿¼y·) £ÜÖo‡ïêÛvFþ•~š$8©²S KCâêO0–~w�+F `[&'<0Åsº¥Ô{Ú+WPy¦´s¸ŒÝ¶Ö–‡–·:‡Ô"ž blŸ­9uO!<ÖmнNq\Ŷ¦–³€œ§ví[\%Ôxl߯4­pæÔè´ßö„˜°²k¨Ó¼FÒ¢†`˜sÖ¼Ù£[wÝn@ {ÔÓõI[hev@3×Ï4ÓÐê…]MѼkqŠVàúׯ|.øÝ>…<>lØl |ÃÓüM|Ǧ`Rñ³©=ºãñ­] ÅÛÍ�)`N~ñȬ]y�d£Z<¬ýNýŸÿjÔÔ¡ŽÏY‘J°ÆrO¯¹ô¯¡´ëëmbÀM¦ÈxÝÁ¯Èÿ‡?N�,^l§�ÜûõëëŸÙ»öªóEk>ôo©è ua3 òOcæ3\†Ñö´·>µ‘<Ø�`ƒUnmð>Z<=â Ø­ÕŒŠXŽ«W1“Ö½¨¸ÕW>F\Ô¥fcMÍȪSCóü¢¶n`Çj£4X=+ž¤lìuÅó#2QŽ;Š‚s‚*ÝÂm’«ÎœVº–·#\0%‡4» û´‘¡$бc¬Üu)»¬F¤ ELT§,@õâ—(¹ˆOÖŠ{�¬h£”|Çä=¥ÆÉ×0MYO ÀpÚ°Ö÷Çò©`½mß#1†¶=E±¶n¾ÒÅÔl,rE2Y7œ ¦.K ÛÁïV–]Ð`h)“?v• Ü éPÄÛrd"Ÿ‚CËqW̼ùjO(…¦$ƒoüªD}Ë…4¹€šb ¿$T-¹2wŸÎ€V2¬I#½g-Ë‹)?ys�QÌÙ)¥‚ õ(ä:rîEäÔfpé†á—å?ZÉîjC,�ª´ …ÜsÖŸ6Pæ¡{ c*Ýi ;�l‹‘÷ýi’ÈzɤXŒûýj¼„“¸še�8¤{N9ö¤–]‘uäÔ+ppü}i�!’qýÑH´\îEÛÚ¥ŠB:š§æå%OÊGëH×D·Ê=iŒÒvëG›ôªFà îjwÚ2�pjZ4Šl�.~cš’+•â³™›×šXŸÞ¥èh™³¹š¹e1<úV5´ØažkNÚç�šÍ³XÈׂô É?�_µºÜ2NA¬0¿"¯i×$pý1YšÞçCmyžµr;…À'­aAu¸ñWáºÚê–‡¶¦Ä7Vœ�[ÔV­Ï¥hC9–0 "£#V¯4sÍKf¨D C ÔñJO­;&_1eŸƒMóª&f#�j&—oPj%Ô‹‰.ç4é¦*xªI1ÎTâ‰.2@5—)NEØçé¸ÔßiÀÆ Îó@ÆÓõ¨å¾ اÊ.cTÞ`ric¾Àê+'í[¹\ÓÖ÷`¢Âç6á¿ÀëS-öâ9¬(õ ŽjÅ­øÏ\Q`ç6ÅîÜ=jÔ7à $þ…çïn¦§KŒ/ÌjnO9¸·äýÑÅJ.ŽìçšÆ‚çiäÕ£w•àÓŽâs4Eë‘Öž—Y\õ>µ–·g&¦Š|¯ãZ\ÉÈÑ\rqJ·XnZ¤·aF3MûA.q‚ "/sQn›#'¥X‚ä±ëXÆè“бæ[­R!ÞÆÜsÎjݼá‡&²-§/Š»jÙ4Ì�$bOz¹i.qº¨+�*Ô$¶6çð Rw4÷|£4±žx¦Z)aósVBsÍièb؈ŒúÓ&p;õ©&\GŠ­?+Ïj±ÏpÖ Iƒ6OJ’Uó9¨lâ±1æd¦P[ŒTÈsÍTE9æ¬DHNhA{î_¶“å㊱¬ßxš§À«Ù ŒÖ±Í8‘É5nÞ\Ч ½j[w9É5ªØÊnåø%ÃÔñÜ�ÜÕ8˜îÍM¾ƒ2êÝœu¥]D†äŠ¥¼S7î<ÓRåU#rÒôŽIÏj;°ý:Ö°`3Á«pÊ{Vª3)SHØIð:Ôð\Ÿ­dÀäsíZ6òe=�k˜ÊÕ¶�™7Ö®Gw±9$šÇ·ŸË\g½YŽë WDjXÉÓLÙ�S'jܹëÒ°–ëæ­ÛÜñךÞ5µ9§HÝ‚pçƒV™¶GòšÃ†ãiëøÕ”¾f\dà×J¨sJ™ry‰O¼j‹¹þ"M<¾V ›=Fjyî8ÀT~zÓËÜóš®­ÚF¯’ÝA£r¥¢ÖÁûPÚhuäu«ß*/JcÞ¬kÚ—"!JFLÚ8ßœS†‹½OËW$»Õ%½Ø<ôùb[”‘�áÝÈxǽsú¿‡½Fs^Šñ … ÍfêZ@93XT ™¤+³Ìo4, X¾ŒF@5ꆂNÁÍsZƈwüÃ"¼ÚÔ-±èÑ­u©ç§Gfn�é'2Ê»ìcØP<=æu8ü+�Ùk©Ú«i¡Ædå¾å>] „û¿˜®Ìø|F c4’è_»Î+)áû"½¹À^hä}Å5Ÿ.�UX•éÍzLJ÷)þU�¨i21×ô®YÐh駈¹Å¶�¸ ŽM#ÌŽõÕ‘MþÄ8Ê©â¹%M�jª]NFMƒÀ¢-+î×U&�Ç¢ÓF`y\æ¹Ý&j«¤`Å¥ŒýÃW,´Ì¸ã�[Qi,q…«1i%['4*L!4T·Òƒ(È«±iXÁ­]4°Uøt¢&«Ù9nrNº3­¬6Ž\·³ÀäU´²*FXŽÇ¦iû+“­r¼vøéÞ¬Å: ˜[…^M>(‰ëMS1s8ÎiþY©*p‹Úš¦dæVh µÛ÷«þYþ‰â'ð¤é�œ¨`ð),ö«~OÓLY÷¬%H®vU ÏÊ”FXàæ­ˆ4õ¶éŠ…Mô%Ì­™'5~ q·æëR[EƒŒrju¶Þy£Ù¶fåq‘CÇ®ÛZ¾@·ÌcƒKomåcpä×]á/ oo>ìtûµèe™{ÄÔ8±8•MY²Ç„|(ºt?i¼ùœóÏj«âÿ‹hÚ0Ø Æ=«CÄzêé±”�ÇäŸüT2͸äöÍ}mZ�ÀCÙÓZõ8°xwŠ©Í2—Œ|TAmŒrxë^y¯kþc¬}i|Kâ¸\©ù¿ýuÅø‡ÄFɱݫæñUÜŸ»¡÷8 ŠV&ÕoÞk�’v÷¬‹�Kͼ Œ^*´ºÓ,E¥ã"²dÔD³à­yk4{ôpìÙÔµ)Î:û×â�_s³:í\ÖΫ¨‘PrGµq¾$¸{�Ê;×jóKÞ=L>ÞìÏoZÚ»;›ßÌk>#i¯ µá~”ºž‡ GrØÇ­sÑ]rÊíÓÞº”“Øõ©RÔÔ½×ex[t® uÆiçSkͱåÇo^•�©~õÁ�ºuç­[´»Y#ùù{g­sUv=Ò9Û�4-"ãÈÞ¢´Àã¥IòOcç3Ì™U½Z;Ÿ[^.åÊœsŒ Qž>tß,·1.£Ë¼U9”ûàúֽ;s‘Tn!À>•ŒàÑ×&Š1æÔÊüóQ2ín)CãïçšÔ—¹6þiûþ^µU¤m ÔžhŠÍèφ=h¨Œž”Tó”ükK�Ã>µ<3b²a¸Âç9«–ó«ÄyäVÇ«±¥ ÷ÌjäzŽ0+)>aÏZ¸®7P3K%ÎòN 9ß)û²}ê¤w @PN>µ0�¢ä¹ äŒ1çùÕ„�¢Ù•›ÑcœäŽ)í~Î2hF{ÄüùÍ1.|ذ˜÷Ȫ)~ Îiáü´ëÅSŠ`Z¶Ö&Óƒ¥›ìWëÀ5_íäÊ ‡'¹õªÍr7zÔE·1'µK‚¶…ÆZ—î.Õ‡­Q‘”JYzv¦€ÀæªË)y0dÑ¡1ºiäÚãÒ›,Ø“ÅWY¾ÌÙ^M29ÌÒ¾}sJ%\ºÒùŸ…ˆn¼UQp±7ϟ”N] ð©.2-±ã5$A‡ÿõVa¼-ò·ïS-À€qAi–~щ3Ö�$žib«Es' G­F&1¸æƒH²à¸ëžÕ,R‡šÏû³ƒÔÓ ‘‘Éš/s …v[<йê«É¬x.ÞdU'8«k0ÛŒóY8”��‹yBŒ9üªô©\/SÞ°#��SW­g'½E‘¢™µløoZÒµrz㎕‰g.Z´­îðØëšM+Ìl[ÈëPÎUÆXàÖ]½ßÔÕ„”�0I©v7¢º_5ÓŸZ¤.[œU‘³P÷=�î¯ÌpiÑ]᳚ %-ÔÔÐÆ�¹Íd»lV “sdza|�*ìg‹viDá‡ËSÅ.žj…¯lýjÊHU$û ¹ö€È;í^ÕPÍøSÞaâ¬\ÅÖ»ü)Ñ^a¹ª·wÍ>.i ÷/-Éójä2r1Îk6Ün9Z¿l70É"ª$6jZMÀÉ­K_�óY6q�ø9­kL¹ËsRdÍmør´ÍR·Rjý’äsT‘œ™zÕ¶¯«Îy¨mWž}*p£ëZ­px°zÔ0’:Ô¥rß)¤vãY"ÀÎj`É$犴F)¬™¥Êcb­H†—ìÿ7$pcï9BĶÃ֮‡<UíãÁâ®Â˜ëDQ Øš%Ú¼Tðr*4©a­jŒÞ¥¨FG5)M“SÊÓ&Ì�Ÿ)ù¸ïD‚„Z‘bÜá�=ªÜG-TâÈ"®¡ùÇÖ™,Æà`Wmåùx¬äáù«0Ü«NÆ ÒB[üjÌ2T’Mý*ÄOèkDÙ›EÈæ-�J·¤µFéV#|V‘}LÚè^ŽëŸ˜Õ˜o‘·+š§>ž#ÎÕ®¦p²¥-’ÈÆ±�Í!]œúYoa‘Í\MòàcÒ´Æœ¨A"¢¾�Ç ÔVnšKSGPʺÑPƒ€¸ö¬MCÃøs´VþöÛÉ&˜Êeê3\s‚:)Ôg+'‡ ž”ƒÃûAÀ®œ¢ÇÃTRª·AXJ„Y¯·‘˾„:§G¡z]ZÅMi§îƞՋâ½»±Ì¶ˆATæ–=‹|Ëšì#Ñ‹SŽˆz°çÒ�ªÜ_Yés™‡KòÇ5n-8²+`i$>ÿëÔ‹¥y©úµ™·[˜¢ÏoÞå²Çoζ%°t¨^ËŽ”éYØŸis5¡zsH‘Õæ´çëH,ñøTJ�†¤žä ¹§òj#=E=` Ò—³°œ‘TFTóJñðx«L™Çy9^Ôœ4- "/j_+š²aÅ ?á\ó…¶b$²Ï8ëS%®®*ÌPƒsOŽÐó’*b¬š=È­áä`U¨­·ãñ©m­÷uÎjîŸff¸T N?T(ʤŽz•¹kÃz ÔnTÎ>U®£Rº�KµT )öV±i6Cø[s\O�ÿçÒ¼Ï^Ô³!u?{5ó¸Œ[›w>ç+Á(E6µ(kú£ #ë\µö¶u9›Ì9Øjo_=Älžk�g¶%Ï5åU«sê°ÔÚÑ"ί«²!çå‰w¬ÄüÕkýXNs\æ­ªýžLÄEpMóžµ:XüJå„N,q“K{H™à“\T¾ y˜€Wž*í—Œ#P©;rkЬ\u=áä•ɘë¼g½ZX•ÐùlV\Ó ±€É*íŒí‡¨?ÅVWPµÓµ%ýÛ£°ÔY-Ô×Ûc\#wíÒµäð¿As4ÃÌ�xQžÕnºg°Ž+C+]Õž2²Ç¼`‚Fq‘Q¼iul—6`ïa–H≄R4�]+™#lƒÏ4û[YŸ³œî“]ç´<êÔ§Ë« I ˆ�ßxã¥O|ßÙf#2aõû¸ëTlÁ*Ú^>WïnúÖ…ãéñJ‰Ü¨ê–NèV m<�Y14:\®^?5ºäŒÕÆ$¶–†ž¿`&‘HP뎽}k>õ^Ô¸WÍLusirMÜrQ¹ÿõTº“A|‚]B!:³FÙcùñU±©•§‘æP“ØêëRÛê�9ã õ«vÞµ–ÈÏms)\d9¬8®;ÓÊŽ¦¶Z�Εy.“ª,nUc”`ñÚº›•¶Aæm‘�¸•ÅAf|¸¦¼w‘qò�ÞÕ±¥Û9y$@C²»�rÖ�ÎÚº±ÚÛk+g:,`‚ ×§|4ø’l&WIÞ9=zW†øt¤K#ߦ\ ·¥x²8®r¸lž1ÛÁ^“ž‘ÜïÂMs8ÏcôÿöHý¥¢½±‚ËV™px˜ ýãߢ¾¢—mÄ{à!¢�å=E~BüøœÞ¿¶’)™UXƒ× ì~µú=û.üqƒâ'†íío¤r®W'’O§Ò½,¿àý„÷>7ˆ²¯e/¬SZ™woûÀ¹ä÷¬û‹|/á[F¨$YÌ~éªS&W­{wçGÍAØÄž=¤Œwª³&>¦µ§‡®k>á3œW,–¶:“¹U[—Ìçšl«°;S“\Õ6*Ìœœõ¢¡wÆ0sEaf#ñbÒqä°cVìfàŒô¬U›¦xÍ\¶ºòºw®Æ¬z*W6!¸ €«~nwÍb%À' pOqV »eldšEšñJO*zTævolz΂ñ}²}{TðÞ ÄK ÀéÅ_VDÛ�^œÕ€c:imë»°Ï5’× WqmÜ⤒àSJúf€-}¡ƒOkÝÄf©Iy¸åzPÓ=j®U‰ÙŒ“:S'¸òÛ½E$§$Æjçļ<Š.;I)ÆV«}³’š× ¯þ÷˜ùõ©hi–ZL9¦nÄ[ûæ« ûožIùÊ:Š‹X»—O1y<Ò¬ûåàVzÈL�p*Äæ3.zt¨hi“Nþi *ͅ櫤¾D§4æ•gƒt|›\°ŽåO4K.þ2*©pð/˜^5�åçÞ�‹L¾·{TÍI¼æ¨[]‚zU„¸'îÒ+˜Ð·º Œp*ÌW~[‘Y)pÈ~~ý*Õ¼áÇ$sJ×4R6a�È28vÄq:Ö5½Ù¥]´œ4'yɨq°îoYM…ùúÖ…¤›œ¸¬/2£¯Ù^í#'ÿ¯PÑi›–³j·Éß“Y1Þ�™8j;¯185’‹‘³ÆqVVl:ò:VE´Û±Í^ãu8èj™xËÈ©P9n•I.U‡ÏÅ?Î޼TØw-Ir­Þ™ö¶�œUlªšWpëÖŽPL™®ËŒã8ç^kÃ'T"Fˆw¦b»¨å Ž3ß0©rÃÕ@å�-OGÚxéIÄWE´rªKœš³g6àMPY¸ÅMÛ  £VÞL°ÅhA&õù�5�˜úÕûY³ÒŽR[4#¹ �*uŸ¾yªi:àg­;ÍS°\µö¬œdÓ—æÜÒ‘œ´5 ñWí9ngZÍÍZ‚ã É«R2lÓY•S ôö¡gßÈÍU‚@êßJ–»Vµ%²o8Ž™§,¤Ô,p8¡[4Årõœš_/ëQ¬»~ñ§¬´‘!­<ÛsIœÔàäPCo [Hø¨ñS$˜<Ö†DÑ¥L‘äÓ#nEX„@ Á§ÇZCòtïM-šq]ü朣Í5dÚ8Í86îMO«1eÛŠ­ëƒV- ÝÍ4c6XòÎsÚœ¿{Šr}Ú–(Ct«1½ÉaÆzš#Q*í"¥‹­Z"[“¤›MXŒîT.ãValu5i�×ReZ‘NV¢Y>cOIT¯­Ålœç¥0ƒ»9⇔玔Çn)Ì8ÉHnMFÒg­A$Üõ©r¬°÷D¡{²½ÿ*‚I½MVž~jFŠ�2Ô·¥»ÓèŸZ¦n=M#\`|¦§žìÓ’Êæ¤7Øàœ~u/–TØ®qnNy5¹«²CòtÍ@ÑñòÖ�‹RЮb-Ò•móÔUˆcb~j•`Ï­a8 ç"‚ߎjd„ƒRÅ\ÕˆmËœV ›Ž¬ÍË[‘[ÂCï#�]7†4°Ÿéc²zV~“¦©ü²8[Ú‰f›².6Š÷rÌ"~ûØóqyß*3<[¬y6呸¯$ñοó3Èïšéüi¯æªÃ5ä^8×wüö¬óÎr÷]‘îeXTš¹ÌøŸ[ûeÓØÅë×Û³å¶E^ÕïÜ9cÅrº•ñ/!Î8Å|íyól}Þ�-™CU¾1‚K ÃÕµ.ÜäçpíÚ�¬_6ÆÞk—Ôu‘Hb}+Ï”®{¸h]•u Cnò9é\åþ§ó1‰§&§Öo±•…vû£Ï¥(žå(2غb¤Ä ¥±‹í³ %Ü¡{f›¤ëA“VnuX$�’,'bzW i]�ôé\/õåµýÜ|vë\‡Œ£e$Œ6’+nx¶©h°ùî9Åeëöìö¿’7z»‹û-AS–=+¢œ“V9êFÌK‹Øl¯“‚zœq\Ÿ‹üs]ˆmWsçµiêwKä;]©.G¹�EYµÖº¹�|K“‚?Ï­wÑå<,rgGá�Ûê‘dDt0=jÖ§©›F†]*RŠÅ•¹Ïó¬sá‹;¸§–Üôë�JÉ‚ñã´– vó^~œWE£-�Iõ;ß³DlŒïI3¡ä€Nk�¾ñŸYy5(Èò U\8í�j½§K©Yé¢òà“€@â´íVÓÄPJÂÒIÉ$°èjàù]®pâ)ókb�Õ…¦·n·6ìÈÙÈö9¬ù ¹<Ä‘S×�çN›J¹ð¦¡™eBÄá@àuªÚ��JvFPqŸ,篽zçµ<:Ô%{ØÑ´Õ\¹K„B `œf¤–Ùàa.—pXž»�ó¬F„ïÚù|~5r+£g [ÇÝ "µåæØâ“å,kî+–7n8b½?Jd¬ÈáÈ.AëC^Çsb©¥`[æAwÀíRiö÷:�’êÙF2ÝëE¢³9åIÜšÆiF%�£uÀ<�Çß5æ /¡12<%¸‡ô¬{Û†Ðá"è3²Ÿ—žH©áÖ›\²SbÞTŠ>éêz{T´5¦åè57Ó´Ùb¾Vd_”`uàU; &Þþ×Ήž žF_jA¯rš²îí�ÿ]>êÍä·2鲪nþ)jZ𑽿O ¬^LÞ`^Ÿ.«zn¹z$c¨Íà|«œþ9®ZÎçRŠÃl›D`òFsY÷²Ô#{g‘¤=Ïz‡\j8½DMA¥•¼Ýɾs“Rèº�¬7'í>bÉ!ùO#·ãXjO€�o óO=;Ô©pé ½Ú‚!.:žk'NÚõ:ãSSÔtoÉ¥ÙÂ.rïÈ ûÿõëê¿Ù—ãƒøkW±–ÊgU  ŒãŒ�޾Õðχ5«�~öKmŒ�?•{GÃÝvãBž)'8T=�_óÅyõé»ó-ÎÞUˆƒ„µ?f>x²?xJÛP´ » .=ÈãZpa¾N•ò—ì?ñÜ#ŧ^N¬�Ž }Ñëìkë‹�—ÄðŒ,ê ûäWÐ娥‰…ú£ó\Ç ,¾»ƒÙ˜óG’k6潫]£Ë¿·5Fö,1þu¤ã¼û™BVv2&sš„¶ƒV®â?ãYó±�öŽ•É3¥j+JUŽh¡WxæŠæ¹<§âj ÃÍXKÅÏ"±!R£­Y·ŸûŠÐ·bOµ&K‘p7枬qPo FzÓ¼áŽi ˜±¼ÔñÌ23ëT£}Ƭǎæ­"“4#aü57úÎ*Œs(äžµj)WøzÓ –âX®Àû±U#mñóSÛ¾ÜR¹5íåçéWm¤ÁëYpI‘W­äÊÒdÈÕ¶¸ Þõv ƒwëXðÉ�Ö­CpGZæR5£�ÆÕn)1Ò²à¹Ü>cVຩ­"IqܑŸÏ5œ Ò#çüj€±!ÊŠt5 �Ý*XA W-[¾Õ„lÕXÎÀwt©•ð¹ —¹:œ• W’3Þ¤Š«™2äRdõ«1K‚3Tcn‡Ö¦Y8ëG0›�.ÌsëUc“ž¹«ù5B$EÝ×ý›GqÒœ¯ž ¶O Ý‘ëV"o.L¬�±…M îlš å«/Dù8«Q‚NóUèäÊŒV‹ÌÆZl8zÔŠ¤i‚L A&óÍ]È'IrÀTѶj²¤R¤ƒ=j®KEŒü§4øÏ¡)c“W$�½1ž‘œ7CQ<˜éIÈ,¸ÏµBòÒI'Ìrj&ÄÖmö4PyŽ9ªrÊI9©$oz«<Ÿ)ÇZɳT‡±ëL{žp½j»JHô¤'b–'&³r¶¦Š:ý¥�õ4}°†Ü9öªÍq׊�ç8ô¨ö£äò/%ó;r1Wmç$ õ�o3äÖ…µÆ'šù…*z–×8>µ¥osòäV-¼¡š´-²GëTe(XÕ¶ºÈÅHdü*Œ ³­N$Èæ›“0”I‹íäÓLã¶)�&äÅ5Wq©õ"ͤäž*Ü8^jŒ)ÀïRo pj£$‡¾†”3Œ‚jÔR2+&2¹cš³¹Àí[Ʋ] § ¶²‰Hæ¯Ä¼Œgž+ÎáBŒžkJÚR@ÁÏzï£;œcby§Ä¥yò¡ºÐÍ´s];jsÜŽàeFMSšßs�X–L“š‚I/5ÏRÒÜÒ%I`*ß7ZAm¹ #š°Ä·ß¥Rc…®mÌÊogžÕ^KB¹ÏJÕÀjŠH·1V3§rã>æD‘cŠ‚K~zV¼¶û{Uy-¹â¹'NÆŠE(âØr*hSwARNzUˆãÚ>Q\³�\Ä>IÇNµbÒp1OHÙˆ«VÖÅæP¼ �dêZ(Îu,�_Ù…C!Æs�óùÖwŒuO*ÉÇùÿëÖë±Ù 8¯<ñæª\HÇ€¿çúWÔTp¡…Q[œXh:Õnyß�5"æF%ˆ,Oó¯,ñn°.çnzWaã�dÎÒz×—ë×›¥cžµñ˜š­Ÿ¡å¸T¬`kWÎ k—Ôuù×®{Ö®±¨}à9 ‘\¶¡y’đҼ‰É¶}…*I$SÕ®”£+¶µsw’ˆÃdñž3ZµÆNXã5Í볆Úxõ¬.zØzZ™ä…¤;Z°õ£ ½¹ç­_¼q–.zW=sv%¸`N6úÔ½t=Ú4Õ‹¶·ž]Â1íW¼A©Eql±Ä�Ë}k äÄYR1ëUšïÎLgæÎ sÎ}:fÝž¡ö{p²¾XúÕ™m…õ«K3¡R1Œó\­ÔÉ·Q>±" DvÚy95“Žš^̳«hŸd22$†©\˜-•·Å·#Ž•¿¤êu¸\îö5¥£.¨­�<Ѽ�ST¬pV’¼7®1 I5*] K¢ò)ÖÖ©¤Ëgó)Ü`}+üµý¹xT ‡'Šê„•MQ3}†Ý®ÖóNᜒ1D®³[±ìaŽrNkPÕæ¼S °]ƒ‘ëRi÷& ª0 Ç­oÉdfª[scD™e·ew€´‘ºHÍTvš†ÖY-Ѽæ†nÇ#Ö²üM®¾†�ì[=sÏ¥e9KR¥5thj±! È Ü2=«–Ôào¶e›~zJtôm½P t§E5¾¨Þ`•GãšëPqØås¾æfºŠÖ„HŠvúVV‡8»“ÉÊ«p1Šê­-`¿G[†äƒ\õï†ÚËUó¬X…BOë]TäŽJô”µ-ý�ü½—lØ=*m?ÂÐÃ2Éiä˜ú梳×�Ù1ñ½;Õß^é³°e*½‰Ï5Nûœ-(î�oÜlj$+7AÓµs: Ôút¦2¤ ƒzƒšÛ½ â]>+ˆXHéËŸjÏÖu©m"HÌ ŠFEuQÕjrV¥ +—5›ËmvÍbôëõÿ¡mm}i+¥Ä(bû¹8ɽQkV&9íNOSƒ[ðêË~™•v•9ëœ×Z’†Ç�^““²1Ù,5t ¤„õô?�Rºðã|E)u�;Ä%„Ã;N Vã ›}¹çò­)ÎïSž­ˆ¼÷�¸P]ÛŽø¨.õ�iv±¯œÇÕÛùÓìÑí –P?t@ÛþÖjµæ�}(X¢‡å”îß‘‘]JÇ:§%©¢/ØÄ¾^ò$9;¤û¿…h‰!{aåÇ1|s°kœ¸Ñ¦ÑæI�Âù8|Ÿ›Œv­q¨­ÁE´]ü|ÃvÑ\îÌÞšî,/+ºàH?ÄÕ¨.®.ÂElÀÁ沤šu½ vâ4úÿZ±fŠ·;Òኞ¸$ÓmXÕ&Ž¿G*·QGå„d‘ù×{àûò9Ž]Þg;³ë^e¥Ë ÌÀ‰ny®»D½ƒN„Ãc&#*ÇžzãŠá­®Ç¥BJ'Ô_³‡Ä™¼;¯(ó—‚9<�›ÿ­_¥ÿ~#/�<j¥”ÜÛ gž2:~ø×ð¯Å­ÿ ²–qp˜ÝÇàuükï¿ØÇã1°ñHíåÌ0À“Œ…oCï\ØJ¯UslÏ?>À,mR+T}‹xŒ!mÀÇŸx�GjÚº‘'ˆ2¶Uþ`@ëYsǼÀðkéë®d¹v?<¡'+Üǹ^MgÜG–­kÈðÜ+>hþcÆ+‚jÚž…=Š¥‚qE:Hòy¢¸Üµ~Áyæu§Á ‡ž++‚:”^nÕìµsdmÛ]nùœüµiµ'b=+;œC€iÿh;WžÔ¬Šº7"»!zŒš”_18Ȇ·x\–54s�ß3T´…sj+Ì6I©ä0flŠÇûP�I-ȧ�DºrF)XÑ;›7ÂH¿ríã½DnóÉæ²äÔ‚F6rOZIo÷Ãò7>Õ/r¯sOÎ!²:RµÀ#æ¬ËkÇž2Œ¯'&¢¬_XóŠ@\–b„àÔmpdÈÏZ‚ýÚ7ÚÀ�^j©‘£•‡5W-3NÑÌOó¶¶·áßbõ#­b}³iÎãV!ºÜÀçš/pfªHÈI,1MŽåcrd9ôª›ÌK�ųÛ4ßµowÒ•î$îhŸ0þæ•§(£Ì?�g%ÏÙÇÊzÔ†qtÃscŽ•<†—4çr… ’h]Èç&©YK‰˜�:T­9'æ94í`R.ÀI-šš9Ê–³’bqɧÅs–äÔµ~…óNëS<çÖ²Rä©©þÒdÇ5›H¥#Kí Py|š·Éx€›ƒïY¶",3ãiïâQ·…©hµ#N ú}ªôS qX«7Ì6ž*íµÇ˃Hµ#bÝüüW uùO5�ÞNcÞ¬G9ÝÁÏÖ²z;1µ ÙCW!½<`Ž{V4ˆÞzU„Ÿk{Ó(ÜŽèíÎißj%«1.³ÔÔÂ|Ÿ–œ· šIs¸ š™&÷¬Ø®0yô«1\gÒ²–ö4L¿¹¥’oΫ$àH¢IÇz�ÌXŽLçy©A Êõª��ý*_0ƒŽÕ-¤ –�î,«ÂÙjx[‘J÷aÎh[>Æ5n ¯CT"`jÄ5/¸œ®Ëâ]ä{TÛ†ÜUX˜U„|ƒšBNâ© ñSÇ!Ç5@µ2/ªL‰co˜dñWaq�j‚ã8«P°N¤ÐƵ´˜NÝ*t“#=+*ÞnE^ŽlŽiØM;i²@¯ÛÉòœž•�˜9«�Ýíã=iX†jÃ>óÇnµ5½ÎFÖdWOêÄ2�ÊOz[õ5mæÃsVà¹Úß1â²’äVRX qd›1Íæ´ÔâQŸzËŠãÊŒ± ùç­QŒOó Ôé%P†PGZ�&Ïôªˆ‹ªùR*Xß÷UQ$ÁçÓzzHÕ) «—•·c4ìúÕdŸ(3ëUΑ›‰`¾* $棒B~ï—#f:š—;•„Ò�Ç¡/–Í4ŸSHXzÔ=M°Ù_ê´¬yÇjž^[" ~õœ†Š®Ç<š0]zÓÝwtã¾&²‘¥Æ\f%àçëQ;€Ô“¶åâ¢Aº/›µbk}É­WæªüKÉöªVÇi«Ðr¼SZõE‹bCqZ¶¯ò Vd ƒÅ^¶“{V‰œòEðx©C`Õt~9©N+DÌ8ù�H«Ž• g SŽ¢¦L™ ·n=sNíGZ¾|‹êGQѱ‘SZ¾zÔ)ÔÔ¶êW­j¤*…ûgÚFMlÙKòV,+žµ©fß/×J©ÇU\Õ†ãpæ’i=jªIÍ=Ÿ"ºý£8Aß5dsO*GZc7j›š+!¤ç­99Í6•Hi^Å&Àuî7{ÔeÆhiAn3ÅK‘HI—9¨Hç>wñÒœTmgÞ¹ªIlÇÍb±ñJ‘þµ+DzM˜é\ÓJ#¸ûtÃ|Ý+OF·I’3ŽjŒKÆ@­Í2°+t®¼º�ês½Žjõ4±__»òmˆ8äÞ=ÕKÃ"¡â½Æ7¤Bàc§?•xÏ�µ#™TçùÖyµG—[–QFòLó¿M¾VØÝkÍ|A|Írñ®ÛÅe$“ŠóßL~Ö\qº¾r½NfÏÑrølsšÅÎËdœæ¹ÛËä‰'>•½ª$Mœf¹Kô ­ƒô¯.£Ôú|4[v3u»ÀÒœô®röè»0â¯ê·`¹ŒqX77AŒ€œdÄö¨S×c3V�•Ê‚5�un…Y˜‚Ø«·n–|‘Ywm¼œqš…¹íQ¦RšwÚUO£û_’ª�÷›¥V–ðÂÇ‚F}*;«Á(�‚�Tžj¤®w™|Áòî”õNêáX¡íUouWxöŒUËðI#­f£c¡BÆÏ‡uW$qšê®Daœíf¼þ×T—`Ä>Uê+¡µñ\7ѪÎÜcô¬§K[‰Äй‰o¡eŒä7zç-mcÒÌñÉóäãùÖõ­Ý¤Ä¤9$�z§>•†F µ¹8ÍgâìJHâ5�Ê�¦Ed\Ik�Õn~Þñ W*AÇ»mrçOêX kšÑ´”IÛ|dc‘‘^¥9i©ÇZ6ØŠ×Xº´�C|»£8ðÎÚOZ[\en[sŸº¼€ÔºõÐi\m•ÂÓoéU¼[n^êk“å¯wÎ j­¹Ì¤ÞŒÈ¾ÐmÊyvéåJ~÷<¡ëU&ðÍÆœ› WxÏV/ý+|Ú´ÚGÚ'#½sŒÖn³uqe§«} $mü$ã5½7Ì)+jXÐ¦ß �qïRÍrVXÅÄm‰ÏAXúf¤Òö‡F2à÷5§.¥}qžÈª wî)J<º”¤¦¬VÖôái+¾žr5•ªÞIs³íiòàäâºû«X® o³©Ž¦²õ ÚÑV.íÓ }MT*®§=ZËB¾�¬ÚÙil¨À>:gük;VÔd×JFávÇœ[:o€’vàíÝz 5ϥݴp)u#ïzVÿX§.yòÂO¢@ðâ l“À>”Ù<6Þb�—�†ýßZÒðì-i‰v(x$uäֽđ]iª‡“ÎOáR«9=¦ êq7Ï—Ô×=¯i-«Á1„í’"pzWOâ=õdGê õ¬kùÆç‚ýkÑ¡ˆHùünô9�Z»·ßm9,Pà“ÐõïRÚxÖM*ïþ&0À߈ù'�jê0)�Õ1–æ²µX­¦ŒMt ²¹ô¯F•U3çëa¥KÞ6.|Wý¬FÙÍ¿Ìx=§­-¥Ö•sjZýÒI.Ió: ã8®"-èë22 û¶ã9»£j-ÄÿgXƒ�¸`�¥W%µG4*JzIoIÑ®¼¦s$EËGÉÁRx¥XÓ¼K¨YÁ ·ï’ä²ÛŸ/þµbÖ}r†œzt1%ÇÛËdå€nq\ÊiÖÊ%}6Gó�Æ^µ§yâÉ[ˆwDÙÕQí„÷©>ˆWò@Æ:Ñ ÃV:Ê-®RˆšîXƒ\o •¯¢ëð,^Tƒ.;Q5ëÚ*î=Ò“´…WÑ´¸®/štå–Çoóƒ[s+%wdhXÏÆ«,¶mäFßÂ8ÿ=+~ÇU¼hŒK…�zŸZÆ‹LŠwY,›?1 Úº[kUžáËÔ/cÁ®y+›Ò…·:ßø�lõ¥|w¯§þxöMTµ�ßq�®@¯•d]º’D”Æï¿aëëU§œ”%[‘T¤ÔX.Ô ƒÔÔ7ùdyDŒõïš9C˜¿Î-Þ­C¨õ�²9"¥Žà7z9KR5£¾;÷3dzT¿iû@ùzö¬U¹ ÜÕ”»ýßÊy©fŠÏƒûâjH$oÚqéYÐH_—5a%àS¹e¿1“Ÿ¥XŽðìúÖw˜CpsR ±þ³ƒÚ„Âæ’]du§C7ÏŠÏKÕjkK¡$„Õ$6ÍAuœU�ßݬ¤¹v YûW—ŽzÔJ#L¹ç¾à1V`˜‚9â¨Ç9l•fç½fÕŠæ5a¹Úy9«¶÷>„V$R`ƒžµj+‚ƒƒI¢¹‘»à^ æ¦[Í®+í;¶œÕˆ¤%²MfâZ‘µÞæz¶—'hɬ[{�®­$üÄ ‡ÎhÅrsšµ ÆïºsXñ]þô¯lÕØ&zðjír“4â—æ&¬E>-“T'…æŸl|ÙAn*\“4ļuëS›Õ ÚjÅ©(™nõ¨«—¢/¥J³ñÇ?Z¦’g¥K¸Í)-™¡näž?š#�ô5FÖb8rÝש©°6^¶8ÎjÔG8ªQsV£n~´8Ü.\ˆç¯zš7ªÈÜg5,rjÓPe¸ÎA©Q¸ªñ6𰤑Y‰êH½r*X¥ÞpjlNVÍ4µcË]³�KTVPê¥XŽM¼­4;èh£í8õ©•È\ŠÏ†S¿$ÕÅ“1ž´ÈnÅÈî NqV º%05–³eFÚ±›F}hьԊRñŽNêµ §Œ“À¬ëy1 ½[GùŽÇjä³J)·-X‚RXè+:Ú\c5e&çå¦f÷4UÊô5r9+&ÎîNkB7bª"/G'J™$Áæª)ÛÒ¤ŽLõªè“Òž¯éU¼Ï–�¿Ïj{–’R H­º«ù™AOI(a[ “�0j_'œT›Àz9˜œš&EÆjÂMéUD¹O5aVª©ÇZšÞBF+ ê {'¥* Ž•&Ü­:%Ù’iTI¤O6ƒícË(Æ~•¸ÀAAÅeéq&ÍhjO¶µë੪tÜŽ:žô’8ßMäÆíž+Ä|mwåÈû‰9ô5ì><¹?gpOÿñ/þòf9Èæ¼ }eU³êòjiZç âiD�6þUÂøˆ¤‘ É_Zëõë�,²à× ¯L"ŽO,û×ÌÔ�™ú)³šžà\Êá¾ïzäõÉÚÖF¥n\Ýý™›f2ÇšÂñ8i­Ë dòk–lú|5=QÌêW_,„�““\åìÿ)'¹­-bïÈFWüë âä\FÀqSt{”i™÷îí'Š¥{’É™æŽjÍü±˜HÝóÍg‰@C¾*ìѧ¦¦ þ¢ñŒ¼Óa¹i-À~€æ¥×m^0Z ®OzφᢶmýAéZ]3¶0°ûévò*(îöÄöÅ#Î’®5!A� V¡¢å¢'�C!ÏÍO·U Ÿ­E>nƒµ6–K¯- (kB9SGMá[Ûk]QU�<†´üI§åüË}Á¦Ò¸½:ùôm`QŒõÍwÖ>'ƒQ±¹BçŒdW$ãÊîsÍêswÁ ·òî {ñÀ<Ö§üÒF¡T@^µÔëúE*O Ê7#ô¨î£Ž{�`÷­!U­R Äòûøæº´‘Bï œr>•wM¸ŠkXÅØ.°§ýî�È®§_Ò"µµßhb9À®6æÎK;ÈÖÝ7«ç#­wÓš”O?’Ì‚Âî}@Éå3y¹R¼g¥Uñ%‘º„ÀýzŽ:V¾Ÿ –wn—q¢‘ð1Îjž¿$q긌eÎkjzÕÕl;ô©T\É…f+Ž3ÅtšEÄ­¨e—| ÷œåxü*µÝŒR‰/"uØû€#Œÿ“Pê×#O’)´ðûÀù@RzõÍo/}ô×+¹ÐC,–W ä)tcÈô­=y�C.0ϵqËâk¯µB‘˜Ë»ëß“[’Þ¥…Ò‹Æh²¡‰ê+ŸÙ^Ñ3¢Ó„pHÍ3€Z·×­·°}²ƒ85ÈZxÊÎóT6Ò0Šùè~ž¹­Í:ÎÞY<ëG s»?z°tÝÉçMk²FöÞl*7~5ƒr$ ®B¨ÜFk_ÄÚí­…ªGï'³Ÿ­cÜÏŹe‘KíÎÐ; 饷8ê=nV¸UžèNÏ�:ƒXzøK‰üÅ qÍZ·Õ|ˇYT�§Ò³§¿}Jý–Å8ˆr0OùÍvSv<êÉÍì7J¶Žô¼’Ÿ— 5…â;c;HLIl£æ*¼ú÷®–+¸Ñ\(Ý�Î U_Å¥Hâus¸dWm:–81nxjŽ*-NÞêæ;m5XÈelgüõ«:†š†Ú6FÁŒœŸZšîå5‹‰­­��WüW‹Wˈ™IsØõß ¶GÎ<-¦ÑcÃRKnäÄìª3‘ŸóëT/|@³êeo[>PÁc#®+GJÑ®Åà–_–%ùˆ=Åsž(»¶½ñB�?¹ØÀc¯?ýj9¹ß29±0öiDìôÉ­õß ´H0¤ü£Ðq\þ�ªÜxvâAu�±œ!=ë¦ÑìMÐßÊ1 ñ¬MgO—Ä�ˆ`ûÉÈ`xoÆœk)»"‹TÔÑ·e,Zô+"‘θjŽÛÂR¥Ëm”¤gÛƒú×1¡jrXêOovJ•?0ôÆk­> i]#Ïî‡OSVâöFTç jÙ£o;h¶L°®÷<(oÜéêŸÚQ­ÜSRüÉîÐ9õÍh°þճР¤F$õ=GùÍfôÜÞÎ_ ±¢xŽ9mR]Ø9ãŸzô\Çr¢á�‹ Èäמxá|òÛ¹.UÉQžß¥z‚^+/ôK²‚2O^µÇZPrhô0ô§k3ìØ�â#éšõ¬‹#O<û1þ¢¿Hôû…Ô4[i×øÖ¿$?gmdéZ¥±´lǧP+õàŠWÅŸ 휜¼jwô?Ö–OS’s�ò\a†º…h� ò~U—qÍmÜAóæ³oaÆ{W£^GÈÑ—Cæ"e8¢¬KÎx¢¸\Žåcù»·¹dûÀµX7ùm*~µ�o¨2ñVC–ù‰¯§²Í{[ÎÄŠº.ÎÜf¹Øemùz+©h.ͨz�ï�Æ…ŸdÅU²=k%¯6ýâjxn�Á'�z‹™¨· �ɧC0g=k3Î7Š�\yn09Gti™þv Ø$T±jYH¾Ipçò¬Ÿ8±ÎM8Ê_”œQI£EæÈçwÍéP­óª*¬R˜÷ŒŸÒ›Á“Ö•�î]I�_sI¸³~µMî6“³­^±¥+ 嘮r0݉©MÈN†¨4�OÎzÐ&©'4r•ÍbÿŸ¸ÙÉ«0LAç9¬¶¹;WiÀ4nw ïÚ¥Ài›H G»w•$wG?ZÉíÅYKÏ•xϽfÕ�5cŒŠ|³–EÞsý*—Ú¾Jtw>`æ�Ó-ÀÍ:Þä—ã¥@'ÈÅ,†èyéV™Fй'г Ìü0?-gÚJÏ6=M\iöŸÝœQp¹zÚvçíV­å\ry¬¥”̸݃ëV-Øó7#Þ‹\.j¨•4ýçÖ¨Z_€p[5`]n8¨q*åèæ!úÑ�·F cÇ&FJ¹o}°cš†‹‹5¡˜#‚M[K€ý cGq»¿Z¹m9¬å»ªÊB•<÷«‰'î€ëYK™jå¤�fËr)›±§hì_/ÏÖ´wéY\‘)ÛÒ¯ÅpÌh ²ìrcªÌN\pI™ª¿˜Ü³ÏÙØùVܺ�·ƒRŽ#½UŠçÍíSG¹�l –ퟚ¹ dñTà�‡'nØdõéNãæ/Fä ±¼ ¦¬wp J«á»Ræ¶ãLº³`{T�Ë“ÅT�²:Õ€ÀýÞ*—@æ.Ç/ËÇj± µA%Ø•$7�g æ4QÎM>7¸ª¨ãmI äÔ‡1v' >µbÜ8ªKóc'¥Z�öôªåÈÛjÄr|¼¢²~jž98§º ˱&À*hÛ.WŠ]Ü5J§kf„f‹ð¶É©ã�çšÏŠmÇ“VÄ€ô4 hËö³dU¨åÉëT,¡’T-’­[µO1NþíL™-K±ÉÍ^µ›�Y{öŸ–­XÍœg±§MˆßŒÔ€ã¥RIŽáÏ*ËÏ&¬ k!©7*ªÍÆjO4·z ‘m_äž’`ŒÕHä95 ›ð ’âIƒšqrÍÅTIN)ân8¨{�÷/$‡g8ÍH%÷ª0ÊH©D§=èØE¥*½€©<Ü­SdŒu§võ'šw¸ROJ{Kµxª«/5'š6úИÑb;“ŽMXŠç9æ¨,�½ªXOÌjù™W4c—Ž´¦pš§Ç<ž)ÒÈ:Ð�ÉJåäº ÔÔ‘Ýg½g Iõ§y¸^i¹X—E®ÁaƒJ×ÀƒƒYÉ8'ši›'“Pçqr—^ç'­'Ú3ÐÕ;´ �SÅMØì^Y7]ÃäÕ8ç%°*EbO5l´‘<„òÔ1¶OÌiYñ×4„àT”+/–äúÓ‘·þ b[š±G=*€\ŠLjÜ2ŒŒÕœ1šš2r ôôëUaœ÷5aò«nèÎH°‡Š°½*±m¤{T«/ÍI™`>¦”j&zÓѹã½ffË‹÷…Z‹…ÁïTãlâ­)æ´‰™b�úæ­Ã!½U!!œU¨¸qô®ˆò-ÆsÍ=[j<ÔŠùë]00{’™1ÅsQî´…ý+VîJZŽ=j:w™Å1›”˜ÁŽFÇ4¤äóHFîµ›wWÝÒ§&ïP…çŠ�y”“Äg TÛjÎWš²�@õ¦õI™ÈÐÓ ÂäišÔ†([&¬iÿ*sÞ©ø�³½{ òa´9#ïTÔó�_|¬{׎x®|ÜHôë^=�;�|çùדøšÄ´îOB+ã1ÏÙÉØûl­%cÌüQ:›�ÜõÁ渟‚™(3žµØø¶Aß·×ÄëwžL8nMxÓ–§Þ`Ö‰œ~¬n néX:›1bœõÍ^ñSÎ\S¼ä ’ÆHÞàÉßQ�XZ�ÛÅn|®£KñdV–Å$qV¡¡Í:–:;øÖåÇÚK+‘Æ*}"[£�—Cž½y¬Ó«¥âFàu©~Ðlœ$)ç°•>c;óÆŸ¬6µi¥ÙÙ7W#éS¾‰ ïIüÅ8#=뉟Å"ÂöÞêïC�=zWR-5´2F¹fíYóXý•˜"”yzâ´ &ÖKb_i늣¨ÜÊó£Œî)©´k:qkC ûN{ ØÂY�îÜŸzÇ}~{¦�Q�Y$èŒx'>¿Òº½BèÄØ× â¸c–SÆ ò§¥váä¯ïf.Œ[EûKa#*hÑ–ÇÝÇùÍZ_Ʋ‹Èˆ*ç"¹=÷VŒy– òƒ�§#ù×J¾6·1¬ ä:ŽPñ�^þ¹­§7tô8)Æj:šÿhó"žØ>õÂøŸL¶mN;½>-®�’×Úº¹÷]Ú’K 8^œz× wwu ©òwcŒ µ§ø¯QŠTŽyÛ“»ƒ¸×U* â<ÎuND7³7>"WÒÏΊ˜=òÄT0jÓ ÅŽpr*ÿ‡Ð%Üó_1ä#æÎ@ç½q¤ÀoTB3ËæºÕT£cÂxG9ޞĺ=È”Ä~µÕè–K7”ïÔe—ƒ ‘#{2q´6N;Šéô}m­×p ƒùW�Z­ö=|>Ks¤·bð"}ÅŒõïPÜY}§R‰£b¡"’�:!ûÀ@àóÍ[I½ÊH€¶; óäß9êBŸ+Ôõƒ·¡\Ç$Í•êIüúÕúCûøÈk~–ÖfÃJ�ïp£ÿe¯Íê¿n´Q·oqô¯¶ÿàŸ*ò¼C ø,Æzü®³ÁTtñ6î|ÿaÕL šè}©:yïYš„[ÓÐæ¶.£ùõ™x™Sõ¯ ¬~WJZ˜s.d9íEMrƒ}Çc½KCù޵¸ù7gŸCS‹âäõíYO3C&ÌüÄÔ¢C2uí_PG36¡º%rNOêÿx“X©xΣw4s…NN=è-HÚŽ`NIÍJ—C8¬?¶´|dà÷©á™——=i=‹¹³ö�ƒŽ´%îN ¬•Ô?yŒ59ïpäô#µM˜Ï~cÝúTñj€ÇÍbÅ?Úb%¸=€¥‚bÈ<Ž´œF��¶—o”ÒÜ¿ÖS^˜ãÜT­wæ ±¥`ÔÐy6,Më`%˜ƲÚí³€r½éæø2ãr�]?mózý*T¹ÚìgëY‰|#^Eó´òd“G(s3an È9úÔ…Ã.TÖk\ü„§ÛÝ�¼Òh¸³Q'$ ÇŠ¹îP==«/w( Ú­A&@9íRãrÔ�XnEçb'eRTŒŠÈ‰€s±orBdÔX¤Í�÷üÇŠ°·PœóÚ²EÖæÃ�% Ži4h¤�T›t�ὪÄ2æ?œóÖ±ÒççN§­Y¶Ÿí Ä1Û52f­³ìäÔ©0ó$æ³ç–B¹b+À@ p;SŒ¬ÇækC" Î*xîr+9/U8&¤¶—#=sNè6¢¸ ƒ‘œU˜c.k&ÚáAóWã“+Ç52-2ìRóÇ"®ZËÈÎk>•â­[0¨¬ŸašqIÏn ^MgÂÀ�”ÕËwÍfÁI­~ÝÊtµ�ÆÌ{U˜ï>R*v!—c8>õ0—®*œsǽXI€#räwªI�2ýŒ² À”Ö�¼‚&+’IÍf4°›pÑ™5:^1E`ž”ír[LÑY2Ýjx$ØÂ³ã�í5n7ù>´¯bM›‘ƒÒ§Y‡¨¬¸äØy©ÒãpÏj|Àh­ÀQ×5 » sžk4OØT‘ÎIù‰4¹˜š¹ ³îäT±ÍÍPtÚjÂÉÍdµbâ¾îµ"ÉøUT“§5 |R3e¤ríH’bªÆÞôðüðhe$Ä™#Ë»©ªªIÓƒã¯4ÆXI*Q&G5U[¸§y¤Ô„YI9æ¦IOcT’CR¬‡=i�~ÝÍ+Kƒó~Z00M9›$mê*“°ËI(SÍ,“ ry5Y™œiC�š“ �iö·,¥Ï¡ÎhŠÍÈi9'“@e'ÓQ÷¯½A‚æ•Çd\VÁùO5 ”÷&©¤¥pJ™_¾sHdáóÞž­º GîiâAž  “ëR#Ô¼ÃÇZ–1P÷ìZ‰ø«1÷ª1ÈGnȤC-A'jÚNƳc�«V —êÐXÐCósÞ¬E&*’MSÅ/?Z®c6‹¢@ØÁ©L�@÷ª(ø9öœœzÑÌ`âÑudÏJš6Ý€j”2Õˆ¤ÝÅ%võ!¢ü<�ô«ÉÎ5J *Ì ’ ­"Ìd�ø е‚Fx"¨+gš± '­¢Ì$‹ Ó•³Ö£IC/©§[FF$”S|Ìu¤2ý+U-Ê?8¦1ɦ<…�ËÍ'ÌzÖrcåE"Œ ZKR ëRµ}ê™z –®ÀµsS¡ãµW‡ «Ÿ˜USWŸ)”‘­dI‹šËñ2œ3Òµ,F!¬¿¶b'ÐW­ˆ\”¬sSþ"<ÏÆŒÆ'ÝÔWœëŒÒDÅûp>•èÞ0q8q^oâ7û:2×Ãæ.ò>Ó/vHòßZí¹C^oâ+lýWÄiç$ë^_ã�¼ìTåG'â¼ ²Ðû̾WŠ8¯ôÂŒ‘ÇJãu+_³înæ»nåÁ�Œé\gˆf–x‰R£oZäç»>×/³F ÄÍ$¬Œ8í\߈b06Ö�®–ò’؆–š>¥r–ñ�¢¹«•껳€y®ÏVÒæGP¼çéX×þ–Ú3u³§Ps]0š=vŒ#r#š@¤�zTw„¨=jÒiŸÚy*cØTséSY¦.>éèkU+š'dP™Ï”D#“éY~t¡‹¿ÝZ“TŠkKÄìC?#>´ë™ì’%¸¸5¬Le¨«pº€Ùµ·w㊂/ %Ü�å)Fõ`9©n˦• �`9ÆH«uãå6¿Õ&ÎiÅ=ÊúµÇöM´qÌW¸çÒ¶R/íM22¤éúÕâ"…šåV=8Î?:­ayqe™âèülô;˜ór›ï¢–XÉ!ÖÿmØÍd"»UÚ£ U¯9°ø�Ú™Šå€cÆ+jiQXK ‚íÐgŒW=J.Nå)¦Ž£@º�feÎô=š‹VðÜɨ ¢q’==«ˆñOˆ.ôˆ–{U\ú7½*Ï�¾2NãËÖ¤ þÑÎçPéJ+B,“»4|A¥G> C°“ɪž ðí�¶ŽƒQTÆ2@Ïëô­©–/\Çu¥Ëî8*­–Î=+Æ-5œ�oTmÌlã±7ˆ-ä[”;�P .‹(–f™ç�coùúU­djsKlÑì`dõâªøQ}+W‘o”ì úæµ²±ËÎùìnø{__ø’V¼UÉ ;u­Y~ Ûù/ nh܃�?ưµ|Ü1¹’åùÎ~••%ÄÓsm1Û�"“:}£Š;}7]†æÙÅ›©y3…ÈÝPɨC§¸K×>kO»\¦Å·ÜBLN§‘Ÿ¥T»½¸›Xi\ù‘ºãýÞi:ðÄ>®RáÕÒe÷ÿ ¦úU½Ë”¹!ÚN;ŠæõIòUX¤`µY‡UX™~bXw­# •x·g±©>‚-'ØmÀàŠÈÕôî¥+zœ°á»“[1ø�~Î~Y¸º?øGãñˆ%µ*ÜdŒòÙø?çµS ÿhZ¢Êv2úWaý§ÝŸ“ª:Fã¡9ÍVº´‚Î0hØAÈ­«¨$>[ÛÂr ®F×Ac lµ r¼“ëRÖ—lžÈ·¤iÄÞÈV °#Šè,••¡GúVNŽßeb÷ 0ÓÏ¥ZÕ5O.ȶœ2ÙíX·ï&hák6ÎÓÃ"û Њ,6^™ï_[~Ã~&’ßÇ:kÃ2*‘ë’«ýM|MàØŸZòe—*êp/þ½}Sû(x“ìÞ0ÓÄ9${¥cQ{:é£ÏÌ©û\$Ó?Tüá6Ÿ ãï ?˜¬ÝA°¹Z—I¸3ø~ÕùÎÀ?¥VÔ_÷|×ÐU—º™ø´#iµæeÜrüÑQÎÙnh®#½ËÆæGÉ8÷©¡¸2b=k>ÚIX~ÿîõÍNóFF-O>µõ†eñu妤šè |§Š£¦Hñ/n>´á eùÏC‘H4EÆø—<⤖ñ¦S嬱râN:Tâl¯S4R.¡läµH.5$�Ùý*„z‘‰J"©¹ëJf2½Z€ç/ À©ÁÆiÑÝmaåÍQI8ÄÜŠy}«”£q©]šOt»pÃæÅ7ÌÜ:�Ta³.~j#�Ÿ‡n(*æ”wO˜RŒIÛZ¢×!Á¦ùã®ê™•�8å//Í÷jxe6A”“’:ñJ.È8&¤|Æ·Ú�cÓ'Þ¤Kß(a¹Íe‹ÅDË¥ê?7ÀÈã5m/‚(\Ö]r[< ‘nă;…&t_k.€Fr{ÔñË·O5ÎÛjF"9cÚ®CûÀ€cßµK@��—Ÿ‘Å;íD‘ÍfMpPƒ�Àú�n2 Žµ-&iÁ>G&¦�‰?#Þ³ç°=jheaóu1IÆå)VÌ%ç9"§K•brpWŠÈ·�Td¹ö¬[1/ów¬œl_1µkv�Ãæ¯C8 x¬xÛÇ5j+�¼út©);š±Œ6I«°ß rséY0Îdj9ü·PyÏ_j¥©HÜ·•d�9ÆEOàš¢6$!£ašµnyÔÊ&ˆÓ³s¿ƒZɳÖdn:ÕÈœàf±–ä³Fß÷Ëž˜«vËȳíç8ÍhC" Æy¬ÚÔi–âm’b­‰w޵�‡½Y·}ÿ… ¶Ñ©Û×ß4/µx¬è&Øx9«QLO#Ї¨”‘~)ˆ<Õ¨gÜÀYñIžjå©VB×½¹¡r)0jÊÈ[ëTc¹úÔèäýÓDµÐf„oÇ5,, ù�J+�£'­KÆW㊎D"â¸�²zTÑɸñÒª$ š±„EÍW-¶v.%ÁQ€ ÔÐÎGJ­o qÍLŒ#4X®bÜ'$1« '­Scéë'"³’°¹‹É'­OÁxõ¬ñ.ß½š™& :æ’1|KƒS#â©G.윚™%Ú¸ëMú— ›çÆjêÎ1Íf#c­Ã8dÀÑYå#WL¤ªzu¬¸&8Á&¬‰r˜Ï”È4ÅÈÛòŠÛ ç5Ÿø9ÅJ·_�ΦÀiÁrù�57›èk)d*~SV­'$‘!í‘I / 0*x_zŽÕF)wuéVb“Ž) NÅè±Üš•gÁëÒªÅ.Z99 KRìWàõ5e f³â^ \WzÐLÑ6jDz�_ŠzIÍ;bÊ6?_2¡I{SÕ²y¤"A'áR+nëPw¥óïN×®N5*¶Ö Ž-ñ†,y÷©#M½è´®vg4žfåéøœ)9ïJ0:RB›ˆÍJ°<š™ ;î$zTЏëJŽ”äLžjGÌ:%çð©£ïMT É&ž˜ƒ9nI äš±À¨!8ÎjU|PI(9«!¡vç"’9ö®cE±¤³ñ–8©RèºsYÑ]ï8jŸÍ >Z9ŒÚ4 ¹g50”n¬¸åÉüjÔN3ɪ'”¿€÷«ÉÏBŒšµ œàPg(—â�žõnÎL?5Ÿ úÕ»vÛŒúÓæ9ä®i†ÜœUˆ‰Û×U8 aV¢sŽzV°wØç’¹,ïëSà±ëUCÙ4øÉÝóÙ\É¢ÃÆUs“L [¦MIŸÝuíQyûGZÕ;"Q"~ïïRsëQ¼ÛûÒ+`óPÕÂÅ„}ÚvsUZ\c$zÒnĸ¨9ÍL¿v¢GSÕñ֥͑ÊXŽM¢¬@ýëT•¹­@ÙQZSw´ú™MhnYŸÜÖOˆ[ÇcZöu¨ö¬6Ø™�zøÍh¦rPøÏ7ñLa¤l{×�ø¾ ©¸ פxŸç•�Jã5»4$@¯ƒÆ¿xû, ­yž¡açÇ.áÔW›x’Ç "Ê™M{.¥h‘JÛËŽkÍüqe ¼o(�|ÝyûÖ>»/¯cÈüc¥dŒc* p:²ªÍ*ó‚ò¯]×,…͹CÃÆkÎumþE“ž”´¹Ïê Î�‡Ÿ¥dx�Y¸6>H?»“ƒZ×ùәݔÈ3ßœT7f 5÷GÔ�Ú¶�êÓzÝ�Ú-¿‘Ã)ÎO~;U{Ýgíó}šï*ÉÓŠ–îkc¨4¶/Ý•=Ä1»,è£qÖº"hÞ—9ë˜|ùS»’j�õ©·ÔX)c zs[wr•˜™¨_Æ«’3—í]1l­o*±Ù"ãoÍ%坨¥i9~0 @‹ *óeIëZ2žÜ4�Ó¥7¡Ï&Q±�¼ÒB>G\œÔŸÚ¼…Šìn„zŠ|z™’5àn'š¯«mûT2Æ€F£2`{RŽŒæzœ×Œ¬·Ü ,U\àšƒCñOö4à\“€¥O¸5¯b·,dIT'Ý7QYW~]·˜›Áùz×LRkS)^/BØñÂëô6vè<ŒþxþµsÄ-„êÜH¸b +Œ[è¼1âv–ä¤`l žŸã]„w â ,9Œ<(Ãyè玙ô¡ÅôCRæÑ—ÓTû¿m˜Ò[#ŽvŸ”ŽÂ»=SA[­ <ùÄņü�|âíç·�cЍ�Ën|‘ù×aᩤ’ÝY6Iª@NëŽ?•ÇQJ.ö6QM¶±áv¾Œý„í+Æ}+’dÔ<=©2’$Ç?½2ã`–{bÆ $c´õÇ5Ïø‹Áo䩆mû~ûçæ?•k‡¯}$sW¡}b`ZjfòMÚ„D�Ôt§ßé*n¤eÏ®h:,Ö­0ˆ?È»wœÕk©ng„ï–E1ûã½vÆJ[UGìß¼fø·Á|éR0~²Øb²µxžÆt&=ä89ÏQÞ¶|9âlꊺ†æPFâIÃF¾´˜2ëŽzœþUJqFnnÆ=Ø•ƒIr¡röª(×hd5Ðêó%Än,Ã9tæ°n$alË#QÞ·Œ®“3qq"›km¹–a²œ¨aÉÍT…cžÉ¤y°ªç×$}j8`Ä,Û¦N ãÓ•Éé¯w ?™k3!oRH5»£ø„ÜܬZÜ{šU?¼è:S�4g Í„ßN‚pÑ„�ÌQËkš·3[Kæ2†WmÅ€ç?ZÖÒ5†´…æu`¥öîÎãùP¡`•U2×ö\ã"Þå�Ñc­ 3w I`ã ìqIm­Eoo»zI¿øOÞü©-ßJ³§Œ»gÖ“W±ov¬ 9ãµ[³»ç$b°Qˆ^¦¯iÇ¥CF‹±ª&ÜܳÃ(u5�ùªÝ¡ËŒúŠ–¬$Í(äPŸ?-VídÚ »`zVròæ®D~jÉê�¦´,Š6šœpÊ29ª6½*¬ YVÎO$tÝWR7 ÊQ‚1àã�γàè+{?ñL/³æ*[kcDGn<µÝ�þÙ«öW8#Ì�fé§÷…hF>EúT]”™© ÊÌ«Öó`â²mzþ5~ß­6´4ÜÓI¾ï8歬ؑk4cëVƒëÉíYÈF¬�I âCНi÷ÿ š?øø¬žä½É¢�«rzUØ' Ö¨¯­N�é04,æÜÇ5r);/~+2>cÒ¬Ûœ¯5MS¹£*~cúÔÆMË×ñªHÇwSS!ù*“Тì/±zæ¬[ÊXœœUDæ*ž×­O0ã˜ã©c}ãTá'oZœ}ñM=@»ycäÎ}jćÍɪ*NÞµ=¯$Sl ±Iž�jd�÷¨ ÿYSVrw°)$óRÄØªèr*XzÔj' ŒU�À “Öª¯Þ©¤ÿV´ÐÖ¥Ë|:óÛŠµªš¥ YNµ[¢·-‡Ç Ô°ÈIæ¨)ýý[C‡õ¡èKE¨ßždɵ *ô4'q¥rý»ÇÎÀý ZIPzæ²â9r!òÐÉ.Çw�Ž•fÚlƒšÍNµr#�JÂ–ÅøäÏz±à|ÜÕ(:š±8ª »‚*e|š©j™s@’Jz¶O]O0ê(d“¤VÝP¯Zrž¿Z—¹ •¥#­*0óšI‡Ê¿Jkö¦¶)lYU!qž)èXt¨mÕŸ­YŽ¥îK%ŒäsS©ä�:Ô©Ò�‰vî&šPÞœ½$ŸpÓ2•"ɱ>\ßšH>å,#/Ï¥g-Í@%'pÙ+ü�éÓö¦'ßZ@O áF{S–XžÄÒ§J‘~íL„Ý…A·­I°v<Ò(â�ÔÔ“ÌÇ,\Œš•PzÓiGQ@|"ü�ééO=j:,²ðzÓR\š’NÔÆéRåcDô,,Àr¤T‘Üg©ª>µ$Gåü)s15sAn¾j³ÉÏ8¬àx:œ¯4ù�Å$kG!aòš½o(=}k+N9N}jä}êîa#QpïW!~™¬èOz?¸)õ9Ù£o?ŠœJY¸³ýÓ’ÖìUsŽkŽ×t¤»f8 W{xÝ‘\n©ÖJùé®[Ibg|’yÿ 릓W)½lI«]µÆ¸õ§Of“Ù#/•&¥þ¡~µZ68&­èL˜ËëD™sŽ˜â°î.^ ^ÝÉQÚº)ÆLyõ¬_(YWOVpÎNåxå\n¾¾µ¤oa6_g•r’ ­ŸzÆ'ˆ¿Ýäbf“ŠÖIÉ‘M¦Ç¦êkÒ Ñ‚}I¤¼P oàÈj·ÄCþ™§žù_ækFen›À8EÆ{S“»%I˜ÚÖ›¦ÛÞÇÜaå“6?JÆÖ,ïü/peÓ��»�ÛëýkWÅê0“×#ùֻē蟾U~ÞíZÅ»U½ŽzÆy}YÉÃÇ–&6©¯Gm)U±$ŒŽ‚µ¢Ô^(¢›kùr N+’ñ 2ÀRWzÖŧï4+?3æÎ3žsÒºUŽUÈŸ_Ó6¿ÚmÈ*Ç%Tç®k þH/¦ò�vté[²\¢Ž‘ÇjÌñZ½M Aõ«‹èEHÚæaÑÒФŠ¨îZŸ-¼7DX�õÆk*òâ@àzn5ŸŒòᘑîk²ž¨äš±¥­Ù´@:vP‹¨v±Û"޽«v‡uÖ‘Ž• C«ÓÿÕT¤Û±Í8¤ˆî¯®,m~ÏrKÅ'ãþzÖ–žÆ+XíÖLFNzþÊ+Ê�À#ЊdãlªŠÒ÷GI4ì� YgûiT`aNõ§olfO4Iû®§Ðz© ¨m:ãpƒK£¹H€�»¾îxêk6‹„Ù§£„¶¸2�ߎÄ7´®5{U!õšBü) ÷k*Ϩ«úªmÀÉŸJÇ­‹ç44ûLâS–F÷éZéáË-FEó7˜ÆAØ2z{Ö ‰ GƒßúÖ²ÈÐi²X¡ÜySЍèîe7ϹBmKuíÄN–äå\þk Óc’‡ýªµ¡¹“Fc!,HêyõªA@1àWÎäÒ{û5 Ž·Ãƒí“FÖíó)äÇZú�öL±’M^ß~7ôÿf¯–¾ó¨>yä3_]þÊ>'·§˜¿ÍkÉÅ¿ß$^"W§/Cô—@”'„ì¼®� }yþµ^öaëK£ñák,Ï%ÿÐET¼<šî”ß³?å÷ß«)]\bLÔUYÎ\çÔÑ\W:ÏÿÙPynPoint-PynPoint-e5d0637/docs/_static/favicon.png000066400000000000000000000207501512720201400221160ustar00rootroot00000000000000‰PNG  IHDRÄÄÀ¦ŽkdiTXtXML:com.adobe.xmp pynpoint_favicon ŒÉ{�iCCPsRGB IEC61966-2.1(‘u‘ß+ƒQÇ?6šlLqáÂÅÒ¸B3µ¸Q&�ZZ3e¸ÙÞýRûñö¾“äV¹U”¸ñë‚¿€[åZ)"%wÊ5qƒ^Ï»­¶dÏé<çs¾çyžÎyX"Y%§7z —/já€ß5]pÙ^hÀŽƒ6¼1EWÇC¡ uíó^¢ÅnÌZõãþ5{"©+ÐÐ,<¦¨ZQxJ8¸ZTMÞîT2±„ð™p¿&¾3õx™_MN—ùÛd-žK»°+]ÃñV2ZNX^Ž;—]Q*÷1_âHæçfeí‘Ù�N˜~\L3É>†ïc/ƒ²£N¾§”?CArñ*kh,“&C‘~QW¤zRÖ”èIYÖÌþÿí«žö–«;üÐôlï½`Û†Ÿ-Ãø:2ŒŸc°>Áe¾š_8„‘Ñ·ªšûœp~UÕâ»p± ]�jL‹•$«LK*o§Ð…ŽhY,÷¬rÎÉDÖå«®aoú$Þ¹ô 6ÙgÐ[Khú pHYsÄÄ•+�IDATxœí�y˜U¹‡ßîY2[€ ²D”" ŠÂ *BD@Á ¢.B¯âUTÔGDŒ;*ˆ .,¢‚àQT�M½à¨l²0Ó³$“9÷�_5]©ª^§ºút÷÷>O?ÉôtW�é®_�s¾5çœÃ0 ‘oõ Ã'L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!L†Âa!z[=€ðt`àYÀ&À\`^è±QðÈ£À“À`5°*øy5ðwà¯À¿�™,ÿˆNÀ‘-9`;`Oà9ÀîÁ¿ ÑÅ»èú�¾àõµ2¼]ðÞ>à$Ž;€»�›�{«=T†œÕej:Ûû/ÐÅ;BvKVŒÿŸA¸ø%ŠÍ$&ˆô™…0 ¬Gð‰ 4®<ðà"àGh9Öµ˜ Ò!ì Ž.´¹-QýŒ¢¥ÚuÀ×€ŸS-Q 0AÌŽEÀRàÍèb¦ñeÐ$º{�A´Ì™BwòBð ž Î5¼v xß$ÚGô¿k”'ƒãý8ø_ºdßa‚¨ŸZ � ìŠ.œþ:Þ¿]äC”¬Bww÷�‡¨ÿîÜl <;xì<ØØí!êëLð¾�W ýOÇb‚¨�ˆ,\^a‚¨ÌAÀWѹš¦Ñ2g5Ú”~ -‡Ú�§#a¼ùIrÈZV‰ ´_9ø:ÕM·÷[Qò}€×?mhÄM‘ÌöH{ Ík%ÆÐÝõb´”¸•öÞ€n‡fÄwSýF0†¼âoAæÛ$¶B7‡¨È&€S€/7:Ø´1AlÈ0ðaà´¾®äÉV§— 5y'‘Gþ”3Ñ&}ˆòžs‡–‰W'G~?ÍIK²qtó9„Î9{èq¨sîqç\Á•gÆ97朻Ë9wˆs.ïÁ¸³xìåœû™snÜ9·®Âç3|~Ëœs¹È1N >»$ ιkœsƒ­þ[m†Ð]ëKÀ먼©, ˜ ÷¿¢½—E�òà àÕÈ:UnÆ(¿Gû’Ás9d¡[Fòç<�–Uû�¦7äúèvAìŠÌ ó‘ƒ+‰Ú(¿ ¸6£qùÎ ÐFz[Êï1Ö¢åÐë�Ÿ…ž?ø"É¢˜ž^ŽLÔ™Ó­ù9àÝȺ�d1¬C_èÙh£ib(q+2Õ�BÏ“ÌÉýÈLýà³”‚ß@{“¢×=L/°¸Í™Ó�3ĦÀw�½(oAGæÀ�ר(Ï\à#À»(¿Œþ…ÌØESôŽÀ ÀfÈ€eņ}'ÝáV¦Û± yžO²�}-²‰œiFí쌖Ÿ›“<ãΠYá4€ÄpJŒJzÏpHðšLè¦%Óhªß‚d1ÐfyGL �p7ŠŸº˜RîE˜<òküxMðÜãÀ‹Ñ_HxÏ Zr-J{°åèA ܈ִ=‘ß“gÞ¼}IFcL ãÃQ(b6É73„ü6'?O¢±/’,¤a´´Ú"íÁ&Ñ K¦ã�OQ~J^�¦å;³T°p9ZJ%íÕ È!w*%‡ÜñÀ9Ä-PÓhïñB´¤m�>C|}ÀIbG¡‹014ƒQ ߥ$/‡†Ñ¦ùû”fí/ŸOx}/° ÊÏhj€NÄéhZN²w€ï¡ ^Ò—e¤ÃztÑŒòË¡ý‘c´h�: ù-¢¦Ü´ß8ŸúŠ/ÔE§ âDôÁ–‹�ù FëèdOpHǑ쯎ÞzýQÀŸ�Õ/Ìrô½·)#¥3÷Ç¢;N¹=Cæ¶mã)#Óìñ»ü8Ú�_ü<…ÊlEüÆ=�¾çËÒ`§ â0t±—Û3‚ß&ÕdM ?¶Dþ“…ÁÏÕ[ú'J@zE—�PJPò‘EÀ¯‘ 6Êð*ä+…†ü±ÌkÇ�W hƒÔè$A¼Ý}ÊÍ ¯E•$|b(w4° o˜DK¹¥Ïja×Å¥E±@Á$pºÛ^…Äã/Bþˆ¤%í(Ú+c™öDfפ×>†rWžLk`�"ˆmÑôšTúeUÅø^–*Cú‚Ž@™j3Ì®BF% Á9FÿPjëó4[_Jüæ�/h7Ja3åfþIà‡hÏ‘ � ˆAàvT5êtGì ²Tˆa` Ú . ž 9~§™¬£´”º äZZ›çý_À'Iö;Ü�¢j‹wÿSQ^{ôµã(ÃŒ¨Õ ³|äœs;%®$%�œÜ± ç_.½Ïí:çÜúÐ9&�s?p¥õÿ~.ùûwÎí:ÎÙ^·M£ãk·¢9›¢¦·)4õžŸÁr(îæj4Íwr¦>”CòSàdÒ ª‹†bÌAy(KƒŸ¯CKµè 0€fââçýQ´DJóç\» ât”ve Å(5{C4ü8G¹*�È *Xv9³w"^Füb>‡²AyÑÀ¾Z¢. ~žBþ‡è~¡Uíø�F×N›ê�€?¿ ¨&éÅM>ÿŽèN¹ a ÝÂZÿ@ãuk7E‘y‘ç]pÌ]�q)ªÖÝ—�Ï¥ŽòM”M ¾x~½ƒk—"‡Z>Eÿèi$’K¢oH™Cƒó”+YÓ- ÏDÛ� c% ¥‰ÞÙsh9õÉàç‹P<ÓúÈëúQ)›âòíD’½íÏö©wpí"ˆ—£":Þ)àM4w©t,Z»fÙ$ÑgzÐgq�Çý˜ä} ß}§o$¾têE#^ü¼Õ“�¦–£ŒÉºh‡%S¥xîy~ õXÞÄsWŠ 5tg^‚ZnÕË<´DJZ:ݎ☪ùôâyÙ«‘©{5ÚH¯@€0ãh/qk­ƒj‡;Þ�hšŽ²šYXj`0fb(Ï /nï]…fߤ¥ÓhæeÛ%UBÉ£vÇ ÀÅåÄ—Nƒ¨òbÍø.ˆêTÝX�¡òéÑõeZl…bð«õ†0ôÝ܈BÙëåJd¾N²:-G³ÇZà’­IóC?�¸Y8‡fˆ�j�ï‚8Œäz<�"`3Ø}ÁÓÄdö"‡.ÜFo ï 9Ôb€ÒÝý&Ô•)\bvz UåˆæzôSªUŸ÷9àÄ—KcÈueÎÙ‡¾ØR½¥”±!“( cê/Þp0ª·õqL¢¦‡Ñõp2±?‚ ËÝyýn(=5zœ1俪ÚÙÕçb_’�pÿDëÖ´É!sÞó114ÂJ ý*õϬW�è›§›äPåNÈêh3~OÂó™Î«â³ N&>�Ï7cZ{JklV:g70„Rc—V{a§ÿ^ûQ¹ Íë8ÎÙ(/;Ì\JîŠøºdš�ʧGïÔ S[ÚƒÞ U°è„hUxPX]çû®@…�Ã7ê)£vR�ÇèEK¬èêbu@z Ò›}�!–· P_äf(øSdŸãÜÉôS ×®‡³‰—ЙƒâÔžVã1¦Ñu’dÎ=®Ú›}DŽä>d=¨÷sÚìNr,ŒÑ8È�u¦VãVàÞ„ç%¿D-œOüÚžƒºUÄGA,&žü3ƒ¬J«R>WõJHù¸†.À ©ƒ}ñ0Œ!´dªõX+Q‚Sx“™£�‚8�øfz9èÒæ�¨|�ùÒ'�,BGÖù¾+I.@½ªiU+Ë�0F‘sï!jðZû¶©îA©„QAÜ�ìÑiv#´ÁŠÆ¿é²å9×Se}já¾fPTó1ugc”ï¾)J¨Ú Ç·bOâžÆ)dÛN[¹gaû†,>\ç{. >kç�ÃIÎ¥/ÇàÛ(¯¢¦ÎP¾ ââëùiÒ÷J/D Ük>Ch\O¬Ó(E4ô{2�6 ßqqóç$jè—&õL»Æì)ö|¨‡s‰¯ú¨ß·Q> bK`ëÈsÍi.—rÀ;1ËR– ¡Ï¼@¾ŒâÞc h4uµ&|ª±M‰áuý(éGµîJíN#=¢¼‰¿Ôñž3QU÷ƒP:érš\¹Ü'+ÓUè3…, WbK`9ªêàÓÍ X‹Lç§µz •ðIßxÝ‚šg¤E²6˜©µ5Ø´¹m潚&;ü-MÛß–L ‘>LÜk]̳Nûnó"à¬uÚL @·µz õàã 1ƒri£Ñ¯ÓÀ1M8ß-À�‰�3‰bÆÚJ àç J½Ÿxa€ ˜ø´½9šÞ³¬ÔÉŒ¡Ðý¶«”èã òjÞ@üÂß…ý¦Í£ÀéÔ_o$SN£ ÅþÎû#G]´™Êí(A$í�çQ<ÕþØ~¢QÆ�«€ÿ¤M > "�Ò£m¾ (¾é§M8çàר‘·Õqª�)±¼˜6Þ�ùºdm®ÿ›¸ vø Í©T>…fˆdÛð¯Ý™FÉ8Khc1€ß‚Y*Ox~kô× V¢»Üh“Žßi8d _Låžqm�N!>KŒçѼñß¼«ÒQ ãÀË�µz ià» @Ié%#À¡¨´ÌÞhÆè!¹J)Hq¤"¥öHµJ IÍKËIÛJGJJ·IK”aÊ8ÈÄËì—é”y"‹’Õ–õ’Í’="{MvVŽ.g)Ç—+”;-÷H–×–÷–ß,L¾_~NAQÁI!E¡RáªÂ¬"CÑV1N±Lñ¢âŒMÉZ)V©Lé’Ò ¦$“ÃL`V0{™"eyegå å:åå–ŠŸJžJ›ÊU‚*[5JµLµGU¤¦¤æ®–«Ö¢öH¯ÎV�Q?¤Þ§>¯ÁÒÐØ­Ñ©1Í’fñX9¬Ö˜&YÓF3U³^ó¾F‹­¯uXë®6¬m¢£]­}GÖ1Õ‰Õ9¬3¸ ½Ê|UÒªúU£º$]Žn¦n‹î¸CÏM/O¯Sš~°þ~ý>ýÏ&  � ©†.†y†Ý†iñ�ª�î¯&¯v\½mu×êׯ:Æ‘ÆGŒ˜ÐLÜMv›ô˜|253˜¶šÎ˜©™…šÕ˜�²élOv1û†9ÚÜÎ|›ùyó¦é§-þ²ÔµŒ·l¶œ^ÃZ¹¦aÍ„•ŠU˜U�•Кij}ÔZh£lfSoóÌVÕ6¶ÑvУʼnãœä¼²3°صÛÍs-¸[¸—í{'ûBûªƒŸC•ÃSGÇhÇG‘“‰Óf§ËÎhgWçýΣ<Ÿ×Ĺ˜¹lqéu%¹ú¸V¹>sÓv¸u»Ãî.îÜÇÖª¯MZÛé—ˆÿ2Â6¢,b&Ò*²4r*Ê*ª4j:Ú*ú@ôLŒMLyÌl,7¶*öuœs\mÜ|¼Güñø¥„€„¶D\bhâ¹$jR|Ro²brvò`ŠNJAŠ0Õ"õ`ªHà*hLƒÒÖ§u¥Ó—?Åþ ÍŒ]ã™Ö™Õ™ï³ü³ÎdKd'e÷oÒÞ´gÓTŽcÎO›Q›ù›{r•swäŽoál©Û m ßÚ³Mu[þ¶ÉíNÛOì ìˆßñ[žA^iÞÛ�;»óò·çOìrÚÕR V (Ým¹»öÔ±? ìY½§rÏçˆÂ[EEåE‹Åüâ[?þXñãÒÞ¨½%¦%Göaö%íÙo³ÿD©DiNéÄ÷e̲²·7¼Yn\^{ˆp(ã�°Â­¢«R­r_åbULÕpµ]u[�|ÍžšùÇ‡ŽØi­U¨-ªýx4öèƒ:§ºŽz�úòc˜c™Çž7ø7ôýÄþ©©Q¶±¨ñÓñ¤ãÂÞ'z›Ìšššå›KZà–Œ–™“!'ïþlÿsW«nk]£­è8•qêÅ/¡¿Œœv=Ýs†}¦õ¬úÙšvZ{aÔ±©CÔÓ)ì ê<çr®§Û²»ýW½_�ŸW>_}AòBÉEÂÅü‹K—r.Í]N¹<{%úÊDÏÆžÇW¯Þïõê¸æzíÆuÇëWû8}—nXÝ8Óâæ¹[ì[�·Mowô›ô·ÿfò[û€é@dz;]wÍïv®¼8d3tåžý½ë÷y÷o¯ñy02*|ñ`úaÂÃ×�2-<Þ>†+|"þ¤ü©üÓúßµ~oš /ŒÛ�÷?óyöx‚?ñò�´?'󟓟—O)M5MMŸŸqœ¹ûb݋ɗ)/f þ”ø³æ•櫳ÙþÕ/ M¾¼^ú»ø�Ì›ão�ßöÌyÎ=}—øna¾ð½ÌûØú>|œZÈZÄ.V|ÒúÔýÙõóØRâÒÒ?B,�¾”Í pHYsœœ&Í:4tEXtSoftwareGPL Ghostscript 9.54.0óÚë IDATxœíÝ1xãÈ•'ðšY׊€MFÁMG… v:ÛÁ´R¸'ÕÁy6¥Äã„”ƒó8 ™ìî}߉¼Àž H(𮃹÷ûZ`4¸Áh:1uG`Ô} µ¢¾Y–°@…*àÿ îf›Y–HàááÕ{}øð�@1~Rö |®ë^ÿ¿„Ï÷ãÿð}?Š"–WÐ4MÓ´ëÿV…¢ëº¢(Íf3ÿÈã#d¸kÅó¼Õjåy^U/—Koª( ¥4Ä…@Ý à®8×u=ß�ãlÿÇÔµâ(\§T×uÃ0TU-{E…@À]5a.—Ë8‡Í'�� JiÓ0|@õ à®7¶\ •ÆÞN|7 Ã0�FÙËØ n‰…a¸X,^ºîr¹dÜÝ(MÓÚ­V»ÝÖu½ìµl·|®ãìÅbQöZø‰#ïN§ƒœ7È·4Dˆ³)¥Š¢Äÿ½Z­V«U)kh·ÛíV ‘7H·\×½tœÅbQP݈ö£¸öuÏ>MÓƒÚ ®ƒïÕj!ÄóýBƒrJéa§Ójµ°ÉD†€[\aÚó¹mÛùƬ†aè”*ªªSÊRïÂó¼(ŠâÁ:EDá­Vë°ÓAoonA0³¬ù|žË«Å¶®ë”RqÊ0®„çµã3Nxw:�Ý_ G¸Åâºî̲v쟭(Šaº®ë”J‘÷õ<Ï].s ¾5M;îvQgâ@À- Û¶g–µK­EܾºÙlJd'É%øV¥cšÝna7”wùv µ[­ÖÃf³’9Ýw‹*ŠÒjµŽ»ÝÝ i‚ p‹( !hM™ à.Ó.¡v…ãì»ljû!ny›¦¹KØmÛöp4ºù/½^ï¸ÛÝîÕ np—ÃuÝñd²ÅvMÓ:�NÇ4ëgß²c'òîÑÑE&A<~|÷ß[­Öp0¨á_²BÀÍ[ÃÑ(ë¶È¸:â°ÓÁ„sòcä}é8[üû½^¦N&3ËšL&k¢”üE n~Â0´,Ë:?ÏôSh»‘".òÎÚ?Ñ0Œãn—qkéxg¿ªQ¥ÛínŒ•Û7¶2†ïPC¸ ´E ÉŽý4 C{>·m›½÷‹¦iÃÁ ¥ÂÄó¼þé鯤”ΦSÄÜp ®;�ØÃ>„ÚùÊÚr1½‡I†ãÉdcÕ ¶QÀ]¸ ‘¾ÓîÃ0ú½¢´"ŒÇcö"“�áò݆Üw)Š2›Nñ×€k¸sæyÞp4bl°½±˜vǘœŽm¬êv]·zšÁ#怛pç)¥gó-[4„†]¸®;³,Æ­«éCm‚ 蟞¦_S¡] \CÀ��0 ‡£ãDt´(‹mÛãÉ„¥ÂDQ”ñÙYÒÍÆ?÷p0À5üÃp8,{ Òó<ïøääÕ«WŸI)ý×ù—¯¾újoo�ÃÂà]×;¦IÙøÇºººúÓóçWï߯�¹÷ööÚí«÷ïÓ_ç»ï¿ßÿäÔ–Ô2Ü»b,#Q¥cšý~ŸÃ’`£ †£K… ¥t|v–Ô=†ee÷èw€:C†{{aþë¿ýÛ9C7’8hûòË/9¬ X¨ªúå“'SÔ„�wïÞýéùó†¦Ý¿ÿ®ïòÉwߟò ¯^½ºwïÞƒvZ1H ÷–Â0<>9ù>5ÒŠu�Ž&ãñþþ>‡UA&ÍfóÑ_¸ËezU÷ÕÕ•³X$‡èºþàóÏ¿ûþû«««¤WX.—¨-¨-ÜÛð<ïðéÓ�CU4M›M§Hl‹lÿÉÏFÑÆNŽß}ÿýÛ·oöèÑ݇�FÓ0œÅ"%æF=7@m!àÎÌqœ��˜ !¦i¦Ôþ‚8ööö~öèÑþ'ŸüðêUJÄLñ}ÿõë×Mø»çu17¬…M“Ù0ÎD�m±4Ø&©Û(7¾fâÔî X¶o‚cù+§ÄÍqq?bn¸†€›Õ`8Ü8<}B!È‚q~{Ò} ÄÜpn&,Ñ6Ú-W‰çyÃÑhcyIÒ,É Ÿ>M ÙE¹xö %þu€€{ƒ0 û§§'¤`ˆwõìø§�ç�¦ÄÜ”ÒÙtŠû!•‡.%iâÚ€ôÙ(Š¢üÇþðh]·8�ÚÞÞÞ—Ož¼}û6=Ï�Ô{dcß’wïÞ…Q´¶Ï T î4_ÿêWéÑvœ¤\;ƒªág�Ý»w/=ϽuÌíûþÕû÷Íf3·å€xp' ‡‹Å"å q´�’•÷àÁƒ�]ºSbnUQRf¿¿zõ ͹ª 5ÜëmÜ%‰ܺÙX“M¹xölmèœÞ¾MKªíã² ¢�ѶišöÅ¢íZÑu}6�*Š’òœã“Ïóîþ{§Óé%ýTEÇ''aæ°JîÛX¢íêpjiċÎkcî~¿ošfÊöOOóY%%%gãäöjGÛaú¾¿Z­‚ £hµZÅÿîû~R)…añ(ŠÒÐ4EUuJ5M«p‡é�µ%I%"â •;@%!àþ›ºEÛAø¾ïy^°ZEQ´±átV”REQtJ�¥´J5Ê[÷ØÃðàñ㔜M§hZP1¸ÿÊuÝã““”'T#Ú‚`¹\þÅó–Ëåu›EQ ÃÐu½i¾7ÆÜ­Vk2gýAEQ._¼Àö€*AÀMà n©£í0 ‹E)Av’ëà»ÝjÉ[|²1æN*I¿—’©€¤po®¬•· ã8—Ž“ÞM¼t†a´Û­VKÒßpúfǤÁïãñØ:?Oú©^¯wÜíæ°>nÒë÷SBR£mÇq^ºîb±Ho-šV«õ°Ù\žŠlëÛ�ÃäË8p]·ìµ$RU5}ðûp4 ‚àîO ƒ¤Y.—¶mç¶D(Ck¸ÓK·)¥ßþþ÷<×Ã. Ão¿ýöô׿–z[äÖ¢(úÓóç?üðÃÏ?s'ëþþ~JYöÕÕÕë7o¾|òäÖ¿ß¿ÿõë×oÞ¼YûS¯ß¼yòóŸïííå¼Và¥vwÜuûêêjí£qÕ¬˜ÁÍ̲NOOë“ÕN²Z­þó�¼zÿžRZÖ_Êó¼8>¾÷ëº~õþý«W¯Öþ`ÒVȦaØóùÚ�e\m"þvHR»îô®Ûbn”t]w<™¤ÔÀÔ“¢(ý^�saw†ý—=†a ƒ»û»ÇÇI—FI[!Ógè\¾x�Ý“’ªW ·“:v±{t$`´=�ÓaÖVEÃѨ{|†!·7½U:¿\.û§§w0>;K*探hm`Ýn·[­VÒûŽ'“­Ö å«QIIz1 ¥t2s^R:Ïó¾þÕ¯Ì^ºÕjeÏç M»ÿ~ÑïåºîÝ1IïÞ½ V«ƒ¿o?²··×Ð4'áo÷îÝ»«÷ïï^Ýé”þéùóµÑ7oÞ<øüs$¹dT£€û7ß|“2I[´ùí3Ë�FõÜ™ÕÕÕ•³Xp¨ê¶m{mqö›7oîÐ÷ïßO)æ~õêÕ£/¾Øßß¿ù�ªªFa˜ô#¯ß¼¹;=ÄW—’’ôb’µe¸e ð×ïO&ÙgFrf�ŸŸœxžWÜ[¤|H¬óó»oÝív5MKú‘µÓàûý~Ò�ø¾?³,¶•€@jp‡a˜RÛjµîŽ#)‹ëº�£Œd;¾ï>}Z\TjFÊ£ÃÑèV1wz�mß÷Ç몘ú½^Ò�X–ų`rQ‹’’o¿ýöû„ÖÈq1‰ }mÛ&U™£årùöíÛŸ=z”û+«ªzïÞ½¤ö#ïÞ½#wú÷5�”Â’7oÞ´[­[µL÷ïßÿá‡ÖVÅŸ ·ö@ŠêÜžç}óÛß&=ú¯ÿò/º®ó\O’ñx<ù¿ÿ·ìUT„ïû¯_¿nFî—RŸ—½� ò}ÿàñãÜ·Q¦Wf¯m³­ëzJ½v÷d·ÛMêä½X,PÉ ‘Š—”ôOO“z}üò—¿,¢Ì7Ïóú§§jSœ¸c Ni¾]hö÷÷SйW«ÕÝÂJ©³X¬ý4FQtwÞ{\ ³ö-PÉ —*g¸ÇãqRkJéq·Ëy=·xž‡’DQt|râ8N¾/{Üí¦4-O&w;–dm?rœÜUОϑä�Eeî0 íä:�Ò‹IÂ0LɾCQîµ%)óÛW«•u§ÒºÝn'ÅèQÝ}>!¤“0é&éù  ÊÜ–e%…³¦i–{;>2�)’<Åyî|cîôbnëü<‚[ÿ˜õùÓLŠé‘ä�E5k¸ƒ 8ýõ¯“Ÿ�•ØU-޶QIÂ_\ÏÝ4Œ[Õwqÿþýׯ_¿yófí£¯ß¼ùòÉ“›ÿ¢ªjJ[î{÷îݺÜÛÛKö~uu¥¨ê­ÊoP53Ü)}ÓºGG%NqG´]®"òÜý^/) ½\.mÛ¾õ�)íG‚u7=Rž÷Å@@ ¸Ã0Lê²§(J·¼½’ˆ¶E�{ÌÝh4:¦™ôèÚÝ“IÂÆº-’ªª&½þjµBÌ ¾ Ü)›É:¦Yb1Éx2A´-‚(ŠÖާÙZ¿ßOj'²vwcRû‘¤­)In Á_Õî”æ$š¦õû}Îë¹6³,L·‡ïûkÇÍl-ënÈ»NRöò¦'¹ïª¡TmÓä·ß~›4ޤßëéºÎy=1×u¿ùæ›RÞ’¼yóæêýû¼úÕ4�·oß&ÝÁxuukÊÒþþ~Ç4UÕ4­i_ÿò—_}õUÊëkšöŸüãÚ‡þÛ½{¥�p€}øð¡ì5ä& ÃÇ�×vTÅ}ù’ÿ’!A>}Š–ÛbŸ�µÛí\^*åãG¹|ñbÇݺƒá0é&ÉË?ÿ¹ÄZ)HW©’’EÂèlBHʶ¶BaÀ�àrˆ“²’äQl}˜0‡’2ã JW©€;)¦)±9 6J .ß ”)ÃØçóùÝJîLt]§”®}½JDV�€ÛuݤÙ�e5'ÁFI)ø¾Ÿã˜ôã2’ÜØ: ²êÜÉI¾RÒÛaæÆA¡¬óó¼ÖN§S\’»Õj%õ¼tœ]^ŠS‘€;‚Åb±ö!³¤ôöp4Bé¶Dò-,IzhÇ$·ªª­VkíCóù<ÇÎâ�£ŠÜ)5¬)[ÍŠã8NÒˆiµZåuG¢Ð$wÊç91U$àvB Ã0ø÷ÞÃ0ß¡*ÀGŽ…%)Iî78¦l�|‰2n!U!àv'i»äAN-–3A1‰¼ò*,IIr']²KjŽO€˜ªp'%öEép¯'A1‰Ôr,,IJr¯V«“Üí„2n“ôw†In)ÃnÆ“ ÿ7…Yçç;–YÇ:�NAE�†awÿ]O(5€rIp§L—Ìkd7;Û¶“Š[@"»7ÌŽ%]ò-—Ëcú~¯w+š§”–5Ý ÒIp'Õ“PJ9o— Ãéíj˜Ïç¹Ì{O©hÚ}ëäųg¦i†aF÷èh6�–Òþ6úèÇe¯a{a>üéO×>ÔëõR:Ea<[çç<ߊc†5›íþ:Ýããåry÷ßEq_¾Üýõ@|rg¸S¶'r.àÃÐÆ÷ Y.—¹´Lj›E‘ƒÙ�õ wÀý—„ûþ”Rη×-ËBS¶ŠÉ¥’»Ýn'õÄ0v€š�;àNÊpsÞ.ÒÛÕ“W’;©‹ÚGÔ„Ä·ã8‰ýIø6*¶méíJÊ%É�²u2—­™ 8‰î¤`…RÚh4x®éíªZ.—»ÇÄ�F#i{Rµ T‰ÄwÒˆlÎõ$HoWÛÅnýûbk·N†�F~u kÀAÒˆ™æº!|ÅÉ% a-‹0 w|‘N§cþ}ÛEQ†ƒÁŽ/ RøIÙ ØÒÚÞÆ„EQxλñ<Ï÷}noüEQdÏç»÷t ‡Ÿéú_o„“°uÿp4*÷öÈx2Aa ’ÜI fMÓø,;&a»»3Ë*ýöHE(,àO²€;)AØh48¯jk»,µ åóù<÷a™�N¦€;én8·z‚n „d¯,²m[œé¤³òêÈêI¦€;)ØåÙ„-J€dÿãÎçsTrð$SÀ]:옄X¦R~ÇqÄIoÇì2ZÔî �Þ†X”%CüÒu‹[ÉvÇ){ 5"SÀ�Ø„›WI‰hyJ�B.Óàóåû>ªJ¸‘)àΔV,Ú;@Œ½¤Äó<1¯Ó¼ ¨ª�>|øÿ—뺗â›Þ´Z­Ö.OÓ4>}¸ÿñÿñü÷ÿÎá�@poß¾}÷ÿþË3£(³³M÷è¨ßï—½ €ZøIüÿ�Çcëü¼Ü¥l-)Ï�agÿçÿpx#Üx<~þ_ÿUö*v|i P1Bf–%o´ [À`n>&„X"5 D.¨˜�=ÏC®  nppó1¢m€â|Ìs.:BQ”²—P«ªÚjµÊ^p…+mn>&„ ¤»Šð1!DUÕË/�ç¨n^þ:øFUÕÉx†¡˜Sñb—Ž3ŸÏïþ»iší6‡<þ\ÿì3o‚û§ú§ÙtÊòLÏ÷'“IÑëÙ‚¢ªe/ .~róÿPUµÙl–µ”�\×]û甆ðY¶'ðÕð´¿¿Ïø‘£”Šp7 £ì%ÔÅÇe/ ƒ¤‡Û�jÜ…‡XCÓŸ)æ¦dMÓt]/{u!SÀ�­ÄAdÅ»kd ½ ÀQnnÐI b™J˜Œn;�²—P#2ÜZÂ}|nCªUì3ƒì�†iše¯âoPOÀ™Lw£ÑXûïÜn‚$7B² Ž»Ý‚V²¡P2Ü"À„ Ùïuˆ“äÖ4­ƒz¾$ ¸“2‹Is‡F%°Ý]AòÊÃÁ ì%ÔŽdwé fŒ �¤½é�F¯×Ë}1™´Z-‘íT•dwR¬Ãm$ 2ÜÀÞ„û–ãn·ÄŽ%š¦!½ P Én5!Ã…!Ÿ A»|ÆggeÝ¥Ÿ�¡Í@)$ ¸“*:¸ ›$hTR{»ܪªÎ¦Sþ1÷p0@+@€²Hp'Ut 3 ð±û__×uÎ1÷p0@g€Ip'Õpû¼j¸ !Ÿ!SXc¹\nñŒ¹m”N²€;iö !Äó<>k@†»ÎR>�™Ä1w¡Ÿ%EQfÓ)¢m€ÒIpB’úlGÊ€;©òÝR×B!«J)·j%¨)î”]b®ër[ªJêkØš”7I.ãæp뺎1ïu (JÇ4Ë^ÈJÖ€;©ªÄå[UrØéð|;(çþ$P1²ÜI·ø}ßÃ�Û2:�Ž’0ù*—U° Yn]ד"ÝÅbÁs%(6¨6J)À.d ¸Ir÷_8vã&„t�þ¬4¤·`GÜIyGÎîF£ÑBÀŠRT°#‰î¤6ØQ9ŽÃs%H‚Vê…`wÜ�F#©+ßKŽÍ !Íf3©¾ä¥(J·Û-{ =‰n’Ü«„sU !ä‘YåtLÝ`wrÜÍ„¼2ÿª$¹+émȋܷ®ëš¦­}ˆsU !d8p~G(N¿×Czr!wÀM’·NÎçsžp!�FÃÄ»JÐ4 ÍI /ÒÜ)�*¹a;ø;@ޤ¸Sz•\Ø6ÿÅt�Ž8¿)ä‹RŠô6äHú€›$÷*ñ}?΋év»Ieå Ôâ@¾ªp§L'™YÏ•BTUEÄ&¯îÑQÒS€íT!àVU5i¸úb±à¼u’Òl61ì]F”Ò~¿_ö* jªpBªJ¢(²çs΋!„ EQø¿/ì¢ß땽¨ ŠÜív;©rÚæ¾u’ °DBÝ££f³Yö* ‚*p“äþ€«ÕŠóÔÉX»ÝFa‰,4MÃ\I(H…nÓLªâàß0†ÂY Ì•€‚T'àNÙ:¹\.]î“Þ !ªªÎ¦Sþï ™ôz=“@qªp“Ô�üûÆt]G1·ÈLÓÄ\I(T¥îF£aÆÚ‡ÊJrB:�Ž™Ü)JD)Eg(Z¥n"d’›2“æÏCYEŸ�¡tŠVµ€»Ùl ˜ä&„̦Sl Êøì¬Ñh”½ ¨¾ªÜDÔ$76P e8`£$ðQÁ€;=É]JOî6P Â4ͤ®í¹«`ÀMR“ÜãÉ„çJnét:ˆ¹Ëešæh8,{P#Õ ¸S’ܫժ”aï×s—Ñ6ð÷чÊ^C!\×=>9Yû�¢(—/^”۞¶íáhTâjÑ6pày^E„J)zaA¬²7!¤×ï/*‡©. IDAT‹µu�Žúý>çõÜ‚˜›'DÛP¨ f–µX,âh;fšf¿×CØ U¸ƒ 8xü8éÑ‹gÏt]繞»só�h eÛöx2¹j_£”ΦSÄÜ5÷Ãê"ªª¾}ûÖ÷ýµ�«Õ—Ožp^Ò-º®ïòÉwß_î2ª Ñ6j0~ûïÿ~uuµöÑwïÞBЋ¶æª¹iòZ¿×K7³\.KlË}­Óé\<{†™8ˆ¶ 8¶mÏçóôç8 ®P¸UUí&·´,+ CžëYK×õÙtªiZÙ ©”xr;úm@q‚ `i7¼Z­8,DVñ€›rÜíRJ×>EQÿô”ózÖÒuýâÙ³¤uBVŠ¢Ì¦Óv»]öB Êf–µ¶nà–*oš¼æyÞáÓ§I�öz½”A9<…a8žL6Þ™‚t”ÒñÙY£Ñ({!\÷�*‹¦iâÿ–ø Ã0ió7¨÷•EóáC–#9¥Ô¾¸à°ØE«Õª oßOŠxQÑèºÞ=:²ÎÏ×>jYV»Õ!òPUu46 WÌÛêu»]7ƒA°\.ƒ ð|¹\–½œ¿C)Õ4­¡iÍf]c¡†\×u]7X­|ßêÖ¿¢(ñ×ó3]§”–ÞY n Ã�ñdÝLÆ"p]÷Òqnös¤”ö{½|#ïZd¸ !a<~œôÅ0 ÚÍ8/)…çyÃѨô‹\E¢•‘A`Û¶³XuOgÆA»�òw¨<Û¶_ºnÒ¸)ŠÒ1ÍN§#B†Ȧûç×D·k…ahYVRBv8äx*¬KÀMq'¥b[œÂ’kãñ8éC·†1>;êpfÛö¥ãˆ–Ìf§iZ§Ó阦P¿U€ÝÅ—Áö|.ï�DÓ4Úm”�ˆ@ÿì³�ÏÉ7nƒ¼„ax|r’’ÜÌ÷J©Ê}¸o¹ÿþëׯ߼y³öÑårùè‹/ö÷÷9¯*E³Ù|ðùçß}ÿ}RkOˆõz½Ñp¸··WöBþÊqœ¯õ«?=.QVû®(Š–Ë¥³XÜÿôS¤Ó Â0üöÛoOýëW¯^I}\õ}ÿOÏŸ_½O)çÐWO?üðCú¡Þ4ͯ¿þšÛz€ÝWÿüÏé¥WWW�F#¯R®Ü„�¦a8?sõ&w¹|òóŸ uðj4Ó V«¤ë„š£”þþw¿;¦ŒÄó¼ß|ó�u~.oæì–(Šp^‡j�Çßüö·òÞtºëÕ«W¸$.ÝýO?u‹¤ë·îÑÑ7¿ù ç%‹ñxÌRNöé§Ÿæu+©F%%1×u�ON’mµZ“ñ˜çz¹®;žLPÕ}MQ”~¯'ÔMºj—a41È«ò»bP±P.Ïóú§§·òÜ­Vë¸ÛÅ>W1¥ï뻩{tÔï÷syÓze¸ !�FãêýûW¯^­}ôÍ›7÷îÝ{ðàçUmÔh4;�{÷îù¾/õ�Ð\t�ŽÆggâü™Â0üÍ7ßT»Ÿã»wïÜå²Ýj!Ï r‰wïH]ßµÑwß¿ÿÉ'ˆíʲ¿¿ÿÕ/~ñè‹/´FC§ô«_üb0|ùå—B©ÂMÏŸ?gœýùèÑ£¼‚�Úe¸ C™ül:v3JúŽÚÊ3 c8uÿ´òɳ›„½°Vµï;Ý"ò™ @(ƒá�1GvùâE^!GnBˆçyÇ''Iwâ9…"g <ÏO&U*FÜHÓ´~¯'Z׿ôR% ØÏ`-ösj5hšvñìê¾6j°ÜõÒ4͹¼ÌëM«?Ú}-]×û½^Ò£Q G£0 y.)]×­ÙìâÙ3Ó4Ë^Ká ØM§Îå%¢mX–A٫ؠnÑ6!dµZY–Uö*Dçyc�™‘븢šÜ„�N§“­ú¾ŸÒ´[º®�†ÃË/LÓT¥ìåä¯ÕjͦSk6ð>i\˜T·h›EÑ 'u›mÛu‹¶cÖù¹ëºe¯@h.suÀÃ\c�úÜ„�~¯G)Mzt¹\dØQÚh4â°»{tT™°Û4ÍË/&ã±€¡6©q´›ÏçHrƒ°lÛŽFe¯¢4—ŽSö„æyã3óÍp×´†ûZ‡OŸ¦DN9v„áCºaÅ7QJÛí¶øÓ {ý¾¤¿á¼H÷½€š`µ]m/ÿügÁ¡%j>|È’/£”Ú9¾oÝn²©37‘³Åi†‹ÅB–Ñâññv«%Tû‘$q—±-~PÓ4JiCÓr_»0ŠV?ÚåuEq_¾ÌkUyén×2È0 =ù†'žïBv?hcg3@’�!ßµÜóJ?Éñµ$Õl6‡ƒAÊ-Èø!¹bnUU;�N§Ó ‚ÀY,ǰo�¦iíV«Ýn‹Üæ–0 Ç“I¦Q¥cšÝnW¨œÓŽŒ(Š\׳àjË¶í¬ŸgÃ0Ž»]Ñ>ÉŽãxžgÏçÛÕ­¹®‹€`-öM¹�áþ«ô-íâ7 Ü( Ãåréyž»\–|kšfÆgºn†ùì[2µõ3Ô¾Åqœáh´ÅyU% öÑq11Cí›Â0ŽFÛU¯¡ª`­îñ1ËM¤"îâ"àþ›ô?Cbîk×Á·çûjN(¥”Ryƒìk+þo’è³qÔZ†aX³YAKÈ*ÓŰiš#öÄ“m÷€ŽÏÎDë£ Pº0 þô§,Ï,bÊJJþf|v–vDQt|r"K•NUÕv»}}8ÃÐ÷ýÕjA°Z­V«­Sà”REQtJUÕ)Õ4MêûÛ¶«mBTU�M§Ycn)¶@M„aXÉh›üX͘5æö<7À-ì§­"N߸ÿfcØQ¥˜û&UU×ÞW�Ãïø¿=ß�îLºþ)EQ*ö;YËfîì; äú…¨ª:>;cÏßÇ‚ ¨ÒÈ‹½î‚Rš2õLLñnœL3êú6-HÁÞ°Ýjåþî¸ÿŽªªÃÁ ¥Års�ÏÎD®üËK£Ñ¸§êð¿7�ã8ŒÁ¨a2æ–�FÇ43�ÔW«nÁ…m3>³ßëÉXÜÜív3í¡Ü± @%1޼)èæ|­߬¥ëúl:M™ ÇÜ6óñª�}œ„tù³k]t6 yžÇX e†¤¹UU;É£‘`£ E¤· îµ6ÆÜ„�áh„˜»>‚ `¼gM)•«˜ä&UU[ÅhŠã0_ JÕÝõ–L÷Í�ḥÜn‚€; cÌ=³,nK‚±Qe,&¹IÞ«¨-‡íbXQ©¿žº®§Ÿ’nBÀ pË_Jšè~ w"–˜{2™ äÙí[cÿ¢t+Š›LÃö<ñ¦)AÝÜÜÛ�® “(O´ìY˜òb¼MmFAÛ<°i2Ms§ì¡$„ÌçóÕj5>;“q#0bÌpW  b¦3úŽ£°mÛ~éºËå2þ~QJ;‰Fº:Žsé87×ßn·sŸð†¡=Ÿ»®+BFEQ(¥š¦©ŠÒl6E¨‡fÿµ<`µ;Ò)eüßËáê"®ˆ�ç9¬V+Î9uMÓ4MÓ)Õu½¸)I†–eÝœ"×jµŽ»ÝÜïA0³¬år)Â-‹ëßy£Ñ(¥x2>=9{¹\êŸ}–׫Ý<¯aðÍfžç¥ÇÜ„J©t�à€‘çy‡OŸ²W€)ßÊá`�×w! ÃñdÂøÙ.…¦iíV«Ýnó‰|?¦‹Ïk¸™°ÄÜŠ¢Ô¤]`ÝÌ,k2™°<³ÓÝØîíÆGÛ¶=žLR¾M½^/÷­Ò¥¶Ø�ÚÕ(ßgïô·E¡$c­{³aÎøÄÁìS–„EÑp4âP^Â>ŽªwŸóÍ“ÝÛ%Å;ÅEhŒ³µþéiŽ©VÏóù MÛå�r)H+—ïûyå=Ï“úCx“¦i¸·1‡ƒÁƧM&“îñqŽ—zÀ{Z�€›ýãš5eÈ>VWÌdd†ŒëßqZgeN0Öùy¡1w†‹a!?QYqë8î8ÎñɉŒ‰í›¢(b)`cľ±dÇAAeМÅ[Úv�¹e,­Ibî-u:��cqÈ�{8�ê–T†ât½åƒ1e¸E Mö~ÉÜÆ³OYŸu~^ÜغÝ}bÿ~¶Ã'жm–� RX.—yÕvr˼Tæz;¾àÙñ&C•ò•ÇÝ.îí5›Í‹gÏ6–ÊEQ4™Lzý~•>:5Á~ì«Æ½Ðž¥�ÕÝÑK^ùÔÊœqcÃѨ CûŽÉj|=Ù?�[ƒlÛŽFÛý¬˜r¹Þã–y©FzûÚjµÚñ&ƒR‰J0BH÷è¨Ñh àÞI£Ñ˜M§KºÉ�;)‘ê– {v¤÷¬ íùP\îœÆ3®¢(»d¸ÙKEeEãn³L¸Uøˆƒ½]Òvß êEÛ„�Ý›ôŽ™—ŠÜ„�år¹Ë5O5ºyRJãÉ ¸w¥ªj\Ò½±¼$Nuóì™;bÜ¿²cŒ%ŽâzÊÞ/™½|vÇðŽýšG"‹Å"÷H‚[…� Ø¿AÛ]üÇ ò·øAÁEQ´ûg�[楒_ÿ]®·�F÷è(ÇÅðgšæl:�ÿw>â’n–NLñÞÜ{UBî¸mQGqÇeï—̧|–d9µË%÷›{Ü*|Áž&ÜâOZÉúS²`/¿NÂ'óÂ~ÓF.«Õj—&¡ÝnWÒ›Tš¦ ƒÑpx}ºüI¹ ª’x2ŽeY,M4­ós{>ïv»"�°®9Ù7ùeUhÏÙû%s(Ÿ�±®ìÞh™‘ïû»o¡‹·¯åx5ŧÂGìQãŸÀ�Ã_“ðüFQ´]<ºã$n™öf ”Rn½SsÙRraÛ[~WUu2{žç8Î×N쇯O=:¥º®·Ûí[ÿŽ€;Oªªöûýf³É2, ®0±m{8TcOOÅȾÉ/«B{>ÈÞ/¹èòÙû”¥ŽiÆE�|„a¸X,þây‹ÅbëàÛ].óŠ}¹Uøˆƒ}Ó^æ»O®›µï»¦iÇÝ®aœ·[„ahÏç–eeúî˜áæ–ya?ã ž—‘žç¹Ë¥ëº[ß¾ïA°Ë§E×õíþ'7>dyšaÖl¶Åëg‚’’üÅÝKôñ4ÔîñqõvKÈNöM~Y1V}(Š’5à–½_rÑå³×„ís§ªj§Ó ‡—/^l]R™ã!Ž[…� 2Ô“dÏ7g*±�ï’;——�N‡ÿqOUÕãn÷òÅ niuÂ1óÂø©Ö4�óM]×�»]k6c¬›]«”æKì‡î-¾8[@À]ˆø&ÈøìŒñ¾Ïr¹DØ-Ù7ùeåy^q=„�#q+@ÊR|�eÁ]9žq¹Uøâ’9à¾{;Ý̲Øë4(¥Ïžm]�UU·ûn‡OæEŠ-CÍfÓ¾¸èõz[ü,ûw6Gì‡î¬_œí à.P»ÝΔBØ-Ù7ùeÅžBÛâ¶©ìý’‹kÞr“DS–šÍævO^[BùTø"®çay&¥4ëáˆ}7¥t6� Rñ¥ª*Ÿb!n™ö‰î¥ß´9îvY&mßRÊ(%ÆC7·�¸‹u�b¿sv7¡ 6’}“_&qe$Ë3EÉš ¨@¿äâÊg·x"ÆeI¼G<ëOårÒåVá#‹¹ÁKÖï¦ã8ŒYUEQĉ¶c|âN~w·˜Ï8"';�NÖÒ²RJJDKa àæáúF {Nh¹\öOOÛ3ËBAþdßä— û&¤-Žõ²÷KfOoïX(Ý”%]׳žts¹wW«»OìÃ${’•ý.¿×í@§iZîϼ‹[楸–¬é÷û»üb9`?Úpë3†€›Ÿx·G¦SÔjµšL&��Çãª6è�ì›ü2ÉtF?È^è&{¿dnè2NYêv»ÜJi¯ñ©ðûÅpÖž!aÎÙ¾øš¦•^·½ u‡�(ŸÌ‹¤7m²65æ\++à�7Wq…Éå‹™þÀQYçç‡OŸvmÛF»h²oòË$Ó}‹ÿ½Ân½gT\󖛤Ø2u·RÚ›øTøˆ ö‹á¬ÑOq¯Ì‡£4·ÌK¡S�ŠÓétø_o³cv™Ž{è²OY2 ƒñµcŧ˜·‰s7Ù£mBÈM‘eߦ¡S ·Ì ûMÑêI2á\í-f×܉£ä f–µX,¶ëYëû¾ïûqa®a:¥º® Ј=t�4Ã�5Ú&„ô·-&éÖûkÜ ÐëÓçnÇ>bÜ*|J”5ÚnµZ[ÄIJoSáЭ�[æ…ý¦�¼Ÿj·€›ýÐÍùÔƒ€[8�Fc4ö{=˲œÅ‚ñs³Ör¹¼5MÓ4-Ž¿…íQ:ö/ª¤Üãñ˜}—d¬{t´Ý§EÒ­÷׸Í~“}Êû5ꎗýÜ*|JáyÞp4b¯Û&„(вEz»ÛT8 óɼA õMöÄ ÏâöC7çëIÜ‚Šöû}Çq^ºn¦œÇZ«ÕjµZÝ<ˆPJE‰¿ñÇNÌdO²oòKáºn¦mX1Ã0úýþvïȾõ^Ìs ·6Ro_cïRº{ŠKö“IÂ0´,+ë•0!d8lñ‘�}› ‡tn™ÙÿìIž÷Ø…ÝZ�€[tív»Ýn÷{={>w'kÀ”"~©ø ëX­½*m4UÝ�)æN‹m½7`»üÙ5Ùû%ó©”}ûûiÇ—ì-&׊Cm{>ߢ€Ð4ÍíŠz¥›iz ‡tn™ö›6bîß`oÎȳ¤DØ®/¸å ªêq·{Üízžç8ÎŽ¥&]^“>¸Š¢ô{=©Ç�­%û&¿›‚ X.—¶½õEÚøìlëÿ™Ro½' Ðeß¾Æ~cÇ· >\×½tœ­÷êPJGÃávo-ãLÓ›8 s˼°ß´óŒÃxráùYÊpèæ~DEÀ-]×u]ï÷ûqäí.—9æ¼ÙEQ4�¶˜¶ 2Ù7ùÅ¥™žïGa¸û%Ùp0Øå�+ûÖ{nè²o_ã6?YöA-žç­V+Ïó‚ÕŠ}÷çZš¦Í¦Óí~Vöm*| Ðùd^â�Ë3żi#f:û¡›ÿy·¬âÈ›ü˜È|éºËår»dÉÖ.GÌ `;E4‹ÃОÏ]×Ý¢¢£D¦iîxûBö­÷|f¿U`û·ö[E¼Q¶m—•¶ØŽ¢(ã³³¢÷Qop(zæwwKÈá,ìÄ.Ãxè.åÔƒ€[z�F£ÑhÄá‘뺮ëz¾Ï'¼+´¬…¿Üsu¶m�'ÎWA»3Msë»Õ± ôKæS(û–)nè¹Ç@[ïS,—¢(³ét—ƒìÛT8tëãÖ¦SööDbÎÇe<¨–’Â@À])7çÝxžç.—žçù¾_Pd¼cW]Ñä»É/k?]At�޶nKrMö~ÉÜ ÐeŸ²Ä­=ß Ÿ0 �ON$ÊjÇ4MŸ�íø•‘½h˜C·>nURbga'à|\öE)7ppWÖuÍ ¹QÝðå•ÿ>²úv;ùÆXãñXÆh{8ä²Vö­÷Ü ÐeŸ²Ä­=Ç I£mJél:Ý1ð’}¦)Ÿt>í•\וzËûg‰gz[ð›¸kAUÕ[ÃÞã#×jµ ‚ Œ¢Õ�Ø_sÇMu¢É±s§ëº2Þªyí_”=‹Æ§Ûרß(Ç Ÿ-ºÑ—®Õjm×rûÙgšr(@çvwKööDb 3®ª¬S+¿×>tó<·¼¸ûœN§#f¨´5ökãáofYy¬ˆJé.o‘½_2·­÷ؾ–ÿmŠ�<Ï“îÖS¯×;îvsy)nÅáP€ÎmfŠìí‰Ì%³ºËº�ƒ€n‹Óáñ‹ùUÏ]Ž›ü<Ï“«!I.EÛ7ÉÞ/™[‰íkŒr¬ðaÏ_Š€R: rL6×gÕÖéƒ3/)*°­\Àù¸âïAGÀ �ç&?ö¼Eé ùߩ�½_2·tÙ§,q´žc…{-x¹Eé˜f¾WÂìéíÚ΢ⳇ†bæ¼Äœ�+f×”›päc­­À�¦iÅ•à˾õžOºìS–¸‰9\£P¦iö{½Ü¿õ)Þú�[{¥ …+B~ªÅü,‰êAÀ �gŒ¥D^Ó4í¸ÛÍ¥ÉZØzÏ'ŽÄöµÜßHÌá ™˜¦yÜítCƒO±Dq8 ³g^v<|1ÖÝ¡=;öCw‰§ÜPwùæêš†1ÙyIE0 ã°Ó)zŽº˜™vܶÞcû£|oë” ¸Å". )t'zІ9 ó©’’}[¹˜óq¼¸ 7Ô]¾›üt]7 Cœ“:¥´Ýn·[->uÀì5²bÜܶÞcûZ¾oÄXáÓétìù\�ᯊ¢´Z­‡ÍfÑ—ÁD†ýdé8 s‹ƒe¿i#æg‰ñÐ]î ÜPw¹oòŸ�õOOKŒ¹5M3 ã3]7 ƒç~»|û%—‚O ¶¯±¿Q¾1P£Ñ˜M§Ç''%ÆÜ†aè”ÞŒP4Ì4݈[{%ñ÷ö¥³]ŠSn¨»Üc,UU­ÙÌqÏóøtEˆÃ²F£¡iZ‰;B*°õžO){áM¾ƒÖSQá£ëúå‹ö|Î~Ù° MÓTE!„4›ÍKr¹µ”)‡tn한ˆž‰€èì‡îr?Þ¸¡ÖŠËÕµÛmwŠ…"f惷8Xöíkì—‘;æ“ ªðQU5¯Q2R¨OÑð.ëçsw‹ýŒSÖp–tbÎdzMá]—øÞ¥ã–««>Ñ‹ÃgÛ ¶¯åþF¶˜„쳨8tëãV%…öDE`L”~7Ô{®®néê¬ØãH*d]2·8˜[¯ß‚d¸FÝí-{‹IqAÀøLMÓ ]Év.l›ñ™[g…¹e^œˆž‰€óq%Ú;„€êK¢/ªø£Uaq+@çÖë·  ‡xuN³ðFìßM“®ë²_ o]ôÌ-ó"àDôLøÌËD̤ûZ¸¡¾2´72)+Y†f'áV€Žíkù¾2L {ÀŠ7(wfYŒÏ<Ø6æ–y‘¥Ô8‰˜èÝ4@À õÅþEE=ÉFì3íé‚| Ÿ­÷ؾ&ÚU^¦Z´ûTãñ˜1Ž›šo÷.Ü2/²ß´³�ñнûL€Ý!à†ú’}“ŸPBæ0Ú÷}ö¢R>¸m½Çö5F²·˜G¦š="äÀ¶mëüœñÉÓܺƒ[æEö›6ÎÇe?t‹pÓ7ÔTH¡åˆñ¨O&Å­d ܪ¹õú-{úŽ÷”eo1)©¸=yÙ« „Û¶Ù�Š¢twèóÈ'ó"æDôLì$æØË$¸¡¦dŸ¯+µÅb1Å)å6ûMÀ3V&ܶL 8\£¢(:>9)7ÏA¯ßŽFìµg»¤·¹e^ä ï³]®±�|5%×U|YŠÍçóÅbÑ1MÎ3®×â3û�[¯ß‚p+@s¸†¤EÉôü8æ6Mó3]oµZÜ.ü<Ïs—K×uÙÓ˜¦i»¤·¹ÅÁì õÄüú‹Y€ÎxèÞ}&@.pCM ØÞHjjÆó:!$Š"ëüœ½L“‘aív§Óa|>·­÷²OYâV€.QŸ/ñm÷YšÏçóù|8從Ü�ÏÎv‰¥¸UI±ïþóë/à|\öC· }ÆPRuäyžÔÍ"$ÎIb¹\G£ÁpÈø|n[ïeŸàS�ÑÏIDAT²Ä­wŸò%æ8›\t�ŽølÏÝ1ó"ûM1»±º9¢"à†:bÿ¢¢€›‘h¡Ï|>�Ç,ÏäÖÆUö)KÜ ÐùTøÔϦÈ<†Ñï÷wyn™ÙoÚˆY€Îxèç¦n¨#‰ZåËBUUÓ4Ë^Åß±çs–§ñ™ýÆm"zA¸ ‹9\CjìåU¡”ŽÏÎv|n™'¢g"f:{wÑ+a„€êHöfb:ì¼Î·qÛz/û”%nèb×�Z£Ñ'æÈ…¢(;–nÇD»»%Âp–µœ�Ë~èç@�€jÇu]©›EK×uÑÎëO¨Ü¶ÞË>e‰[º€Ã5*@´‹á]PJ/_¼È%0åsw 퉊À~ çVn¨1ÛUC¿×ËÚ†¬PƒW>³ßdŸ²†!·tÜ}*B»Ýs7^V”ÒÙtšËŸžÛÝ-ÙoÚˆY€Îø[j§n¨Æ\ÏöF•¡ëú.=qóµ1øã6û�} §˜›tÙǃïz€=k%dá�Ȇƒ�ìíJºGGöÅE^ZÜ>lŒo¤(Š˜×Ûbv b<(Q‘¶Ä àX¯#Ø@Yw»‚ìžÜx�ÛÖ{ök<©Ó�Š¢ðùÖ(Š"ÎmbY¨ª:>;ê;MÓÆgg;ö$Ù¥”Oây—y™…b̘¦)N.ùšP) Ük(Š"N¦V:£á°{tTî ÃØ˜—â¶õž±ñÈp0óŒËˆ[ÄÐòÔ.>]×gÓ©\ynEQºGGÏž•uO£ßëqx‘Ï8,iŠ¢‹·~Ã0„jу€jgcô£(J^e‚µÕï÷gÓiYé´¸‰ÁƧqÛzÏ☦)l™ËmYÓ4wO@6›Í�Ÿ™\Þ¨¶t]¿xöLŠ)q¨}ùâE¿ß/âhÌòuëõz»²¼B.MW Ârôœ¯�7”â»"|Ãè†Ìóت�RjÏçWWWk�£m17¯È¥ÑhÄoÞ¼Iúm!ÞVµ¿¿Ÿþ´ ¾ý÷gyÁV«õ³G�vY’ªªWïß¿zõ*é ¦iŽ>ïííq[†òþ¢¤°··wÐn?øüó·ïÞ±ï.àIÓ´ŽiŽÏÎ~ö³Ÿíííô.ûûû?üðCÊo`8|õ‹_ìþFŸ~úiúç?þð‡ìþFùôÓO�Å"©ÑŠ¢(¿ù_ÿëÉ“'œW¥*Š“Üd†Rúøƒh×0¸¡vöööš†á,w�€¦iþþw¿ÃÝê¼ìíí5›ÍŽi*ªJ²ìÜŽ¢(¿øŸÿs8lŒ¶ !‹Åâ»ï¿gyÙÃNg÷ 0J©çûwš¦ ƒããã_¿hÍfóíÛ·w7*å¾~noTs�FãË'O|þù»w/Š"ƾu…jµZOž<_ýu³Ù,.Ô¾öè‹/ÜåòÝ»w·þÝ0ŒñÙÙ£Ý.³¯¥œqZ­Öøììþýû¹¼QAööö|þ¹»\Þý�†ñûßý®”î÷ïßßÿä“»Çðø,0�9|~²úèÇe¯ aÚóyÜ!5n~ÜnµjÍu]Ï÷£0dïëÌBQ”‡Íf«ÕbOiôú}Æ.¼y5ý%„¸®{é8qØ­Sªëº°e$k¹®ÿIÁëçöF‹;WzžFÏ´·N©¢ª:¥%öy´mû/?Îx×)m·ÛEÜá¼yÆ!„4›M¹Î8a.‹—?N±(î•I¶mÇ MÓ>ÓõLgÎp@5>dÉêQJí‹ ë€ æI¨Ùg¿€\p@íÈ>û ä‚€j‡}Èœ˜³ß@.¸ v;p†!ìþ�n¨ÇqŸÉ8! n¨ÏóŸYJY¨ÜP/Œ;&EAÀ ¹@À 5†áÝ)†ka»$ä7ÔãtI‚†€�ÜP#¶ÍøÌv«UèJ >p@]¸®ËXOÒjµ�FÑ뀚@À u1³,Æg>ÄvIÈn¨…ñxÌ8ïFQ”êI ?¸ úlÛ¶ÎÏŸÜ1M ˜€!à€Š³m{<™0>YQ”n·[èz n~RöŠÁx2aoH�Þ€ à€ª ‚`¹\¾tÝL¡6!DÓ4¤· w¸ L¶m¿t]ß÷W«UÙk!ÃÁémÈn(‡ã8ãÉD„8;Ö=:j¢ à£>”½¨�ñxÌÞ6„Ã0¬Ù¬ìU@5¡K ð6³,¡¢mJéøì¬ìU@e!à \¹®{|rRö*þFQ”ÙtªëzÙ €ÊB†¸bŸ¯Î¥ôâÙ3DÛP(lš~<Ïcœ¯Î¥t6�¢-  nàÇ&ÚîÙˆ¶€d¸€Ÿ( Ë^Qe8´Ûí²u�€øQJÍ(+ŠÒ1Ín·‹Ä6ð„€øiƤŒ÷E¨ %BÀ üèºnÏ}“š¦µ[-„ÚP"ôá®Â0<>9ñ}¿Ðw¡”¶Ûí¦a å”7”À¶í—®EQޝ©SJi6›”Rä³@¸ ôÿÝwmå®C´IEND®B`‚PynPoint-PynPoint-e5d0637/docs/_static/near_limits.png000066400000000000000000000346321512720201400230030ustar00rootroot00000000000000‰PNG  IHDRDf˜Ýö-gAMA±� üa cHRMz&€„ú€èu0ê`:˜pœºQ<½PLTEÿÿÿªªªUUUÝÝÝ»»»xxxddd€€€‰‰‰¯¯¯ÀÀÀwww888(((gggDDD222Òäð[›ÈÃÛëw´—ÀÜ5„»�³Õ–ÀÜ.€¹ðöúˆ¶×áíõy­Ò*~¸l¦Î=‰¾¥ÉáL’Ãj¤ÍK’øÔçaŸÊV˜Æ´Òæ­Îä;ˆ½I�Âz®Ò&{¶e¡ËP”Ä'|·�²Õg£Ì+~¸@‹¿(|·W™Ç))) WWWYYYÌÌÌîîîçü_bKGDˆH pHYs,,sˆéRtIMEãø3¯Ë7qIDATxÚí݉zãJb˜Qœ›x’qœ‘Ôžhë¸ÕÇ�8vö=yÿÇŠ."@l,‚U(àœï‹Ó-Rˆ©«¿Q ww W”›J™zC /‡„n©72RlN9€Ñöñ,MéÀeʯiÜÒŒ.\  çþÏ¥CQ­¬|–'EzmjÙ,ÍçÀHEc×|.ŒÔ<ôܘÏ€qš‚úPFQ´i‹¨E`X³™EkD÷Û‰òOÿâ÷¿ÀtþÙïÿûž:‰×G´ýôÜ?là¶þ2uoÑÔ;€å[lD‰pk‹�hí3ÑñùŒÔ“ç,Êï?ÓòW©“x}DGœ�û/7.‚`R¿}¦å©7âr­×‰ö/ž+¢LlIíÿ`b" �rŽhã.." @\™F´qfQ1|^‘ˆ0µ\#ZŸ¿sSn`bYGôxì9â@TD˜Z®Ý} º?øu`bÙF´ gU΢qZ‘ˆ0¹l#º¯èQ1ô|`bùF´^ÑÁ†Š(SË8¢'-‡*¢L-çˆn?ý i9tJÑŽˆ0±¼#z `b" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �ò�hQ–›�²ó|`b™F´8t|FE€‰eÑzC? WTD˜ØR":\Q`b9Gt?‰»êзˆ(Ë6¢'ƒ»ÏE¾ED˜X¦-j³·¥ˆ�@¦m3Ÿ+¢Ll-E€øD-#¢> �EDÔ‰E¤°œˆ­¶ ¢Ll í<ýÝo'þ(¢Lk-:—ýûCsmÀÔ› À¢äÑî†ÞmD€Ê>¢}kþ9à–r�hﺹ>à–2�踵ç+ÎÎ`byGô‚†Š(SË:¢—4TD˜Zν¨¡" ÀÔ2Žèe Q¦–qDG­˜ûED˜X¾½°¡" ÀÔ²�è¥ Q¦–kDËîÅþ:ˆ(Ë4¢»†–5ß"¢L,ëˆ^´0®ˆ01€@" �2�hQžø`b™F4€ˆ01€@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰hFîîSoG"š‘Çoß¾=¦ÞŽD4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^D4#" 0/"š˜͈ˆÌ‹ˆfDDæ%ÿˆåV1ø<`bùG´Ül•ƒÏQ&–}Dw QâË=¢ÅFDH$÷ˆ–" @*™GtÛÐRDH"ïˆV“¹…ˆ�DÞ­DïD€$²Žhu z'¢¤‘uD«É\ ‘œ#Zîã)¢$‘qD÷“¹" @"Gt?™+¢$’oDËc:;#ú»ßNüQD˜V¶=NæöDô›†Ô}%˜—[G´ÜŒðÂ'åìŒè?gND`^r�hyNG¢$‘iD‹ãYEw> ‘L#Z릳sH"FDË~q+kÙQ’ˆÑ�§FôÒZ`b"š˜͈ˆÌ‹ˆfDDæ%Û‹N8±€$D4#" 0/"š˜͈ˆÌ‹ˆfDDæED3"¢ó"¢Q€y‰ÑŽK;Ër8ƒ/ý©|–ˆ0±Ôw9`bsˆè×�AoID˜Ø<"£¢" ÀÄ¢E´(ËÇ û›ˆîþz[(‹wvnQUóxÈYn¬]ý!Âç¢" ÀÄâE´lÎÚ*úõ‡›Q&õ3ÑòüKÛÿ¿ˆ2Ÿ+¢L,jD_*NEo¾" ÀÄ¢E´mÊVD/#¢ó2‹ˆ":ŠˆÌË,"z'¢£ˆ(À¼ÄŒh³a¥éÜˈ(À¼D�hËÙ¹"z˜—¸Ëþí_ÑqD`^â-û·©¯X´_ð¯ú³ëDÇQ€y‰»bÑ×â¹§ Ï[±h$˜—xm»�Ky|@DGQ€y‰ÑóŠÆ¹÷žˆ0±˜Ýß°%IBE€ÉE�hí®¢±ß©ˆ0±ÈMHD˜˜ˆfDDæED3"¢ó"¢Q€yÑŒˆ(À¼ˆhFD`^âF´(Ëdë-ˆ(‹Ñ¢sá¿D€‰¥];WD/"¢ó/¢ÅFD¯$¢ó/¢‡åþöwCÛÿµ¸þ…GQ&-¢åá¸ópøYï'‡ˆ0±¨­âyœÃ­æwã½S`bÑ"z<îüú ´ð™èeD`^¢Fôð‡C9˘][@DÿZDf%fDËúDôRúŒè¿J½‰hF¶G¢J½‰hF¶Ÿ‰~{J½$ùLô3½LÑç—Ô›À^ÔK\ŽØ_õ—¥DôÛkêÍ`/jD‹ÃNþí�.&¢Ïo©·€�h-N?=¬ÿç:Ñ‹ì"úí{êí`'îÚ¹Õjws±vî¶ýןÿï>õ†P‰Ñ㺧�w º”ˆþÍçÿ{H½!TbÞ”û¨Œº˜ˆ>þ°lÀ\$‰è]±ýD´Œxz·œˆ¾þŸŸ©·€­4Ma)½ûùùßSo w"š•]D·ÿ÷GêMàND³²¿ÚƒæAD3²�è½Åÿæ!zD‹²Æu¢8Ü”û»CQ€YˆѲ¶Î‚‹.uˆèÛóç,þ�\Ĉ›"z�CDï^-þ0ñ"ÚÚP½Ä1¢/ÏV\˜�¨ Ћèu޽{²øÀ D½Úg3c.ôW·¨ˆÞYü`¢E4òróç–Ñw+.¤5¢éCï–ÑjÅ‹ÿ¤5¢IßéÂ"úhÅ€äbF4élîÒ"z÷aÅ€Ô‰f¤Ñ7‡¢©E=;7é;]ZD«Åÿ¬¸�RÔëD��{�zD_,þ�X¼‹Êħç..¢Õâ©· `Í"®�[ZláJ�ˆZü ±˜wqÙ/ZäVh¡­V\°ø@:1#Z”ÖνJ3¢ÕâV\H&òt®ˆ^ã,¢�ÿH)î‰E"z•³ˆV‹ÿ=¥Þ,€ÕŠ+4Ÿ‰;�¨ÅÿRŠ{+´„'ç.3¢ÕŠ ÿHÄ­Ð2ÒÑjñ?+.¤aíÜŒ´D´ZqÁâi¸‹KFÚ"Z­¸pŸzËÖID3ÒÑêPÔŠ I˜ÎÍHkD«,þ�BÔ³sS®?¿Üˆnÿû™zÓVÉ­Ð2ÒÑ»ŸÿH#îŠE)+ºÜˆZü ‘Økç¦Ëèr#Z-þgÅ€øâ®XdíÜ«tEôÞâIˆhFº"jñ?€4D4#�}Û®¸`ñ?€ØD4#�µø@ñ.q)[¹Úº#Z-þgÅ€È"ž�›Ø¢#z÷dñ?€øD4#=µø@"š‘¾ˆ¾[q :ÍH_D«,þ•ˆf¤7¢�V\ˆMD3ÒÑ»+.D&¢é�è›CQ€ÈnÑíå¡O~Æ$Ñjñ?+.Dt눖Ã銷åGôÅâq‰hF"Z-þ÷‘z#VDD32Q‹ÿÄ%¢Šhµâ‚Åÿ¢‰ѲŸˆŽ5Ñjñ?+.Ä#¢Ãb¼ÓUDôÑâ‰hF†#Z-þ÷”z;ÖBD32"¢ÿˆ(Æb Ãb¼ÓuD´ZqÁâqXö/#c"Z-þgÅ€(D4#c"Z­¸`ñ?€(D4#£"Z­¸pŸzSVAD32*¢Õ¡¨bÑŒŒ‹hµâ‚Åÿ"ÑŒŒŒèvñ¿Ÿ©·` D4###z÷ÓâQˆhFÆFÔâqˆhFÆF´ZüÏŠ 7'¢Ñ{‹ÿÄ ¢Q‹ÿD!¢Ñ·íŠ ÿ¸1ÍÈøˆZü ÍÈ­ÿ³âÀm‰hF.ˆèÝ“ÅÿnND3rID-þp{Ñ"Z´Ü{ûóKE´wº¶ˆ¾[qàÖ¢E´liØç—Ê�× ²¶ˆV+.Xüà–D4#—EôÑŠ 7&¢¹,¢wV\¸­Ì#Zn_vkøÃÕõEôÍ¡(ÀmeÑbsbè•ÖÑjñ?+.ÜNʈWF´Üˆh¯‹ÿÜTˆåu­7TD[lÿûH½ÕË!¢›>á׉–§Ÿ…~YDÏYüà¦RG4øU‹fƒ‡r¼ÆˆV+.XüàVGôÊÑK¾}•­ÿ³âÀ�$�è‹þc>­YgD-þp;"Zîl£WsÕ²¹ˆ®4¢ÕâO©7`¡’^'z…‹D×Q‹ÿÜN¦ýšÍm¹9L»•F´ZqÁâ7‘éýD÷³¹ãý[oD«Åÿ¬¸p YGôd¹…áÃѵF´ZqÁâ·�sD¯8ýÝo'þ¸ÖˆV+.ܧÞv€%JÑíɺלŸûuZnäÒr,ú‡É–v˜‡°ˆV‡¢V\¸�x-¿N:ÎÃW´¾XCÙñbÓ­�4�­V\°øÀô¢E´8‰Ø×dlhEQ,ÛE‰îlÿû™zã(ê%.åׯÌZs·ýµ|&º÷Óâ·�â:ÑÇ™—/˜P{µÓ&–ÃA^íÙ¹w_‹ÿÝ?<<8Å`2Ñ"úU¹â0�{ÅM¹›.D´×ÃnÅ…ð ËžÎæ¾ص³£Xíu¿[üOD&• ¢_ý _ PD/´[üOD&•(¢_—¦„¿œˆ^àm»â›ˆL*~DO>¾¼2¢N,º@µøŸˆL*~D׺¾\ã~¢":¤Züï¯E`JñÏÎÝLÑú|n1âšÓ•Gôîéó›ÿ,¢SоØBqryMDk‡¢c.9]{D«ÅÿD`JQ#zXaátÕ…Ð×+¾n$ZŽZühõ}Q€‰Å[€þd­¿ý1ãˆz�uq‡–á]}D«D`Bñ"Z4zzŠÑu/8j){}Q€iE¼Ÿè!zÇÛˆ^±vîî¿V²qkR½ûQ€IE½)÷îVÜÇ¿Mps²Ý+Ž*±ˆÞ½m#ú·©ßÀrD�hR"zw÷o>_àïR¿ €åÑŒ\ѿ݊þJý6CD3ruD«S‹ž_R¿€¥ÑŒLÑo©ßÀRˆhF&Ѝ ]€‰Ä�hQ–›ºk.q¹Œˆ#jB`1#Z4 *¢—™$¢6¡ 0•ˆmI¨ˆ^d’ˆþÛgºI±ìŸˆš$¢�Û{¢ý0¡ 0�È ÐïnãRîæRŽX®o*"zx�íBô¯©ß ÀD¾Ú]Óòø…x Ñã ¼YB`ÑoÊ}Œè•wB»Ø"úòøøxÕ<ì¾Â¯&t&-¢ÇãÎúýD}&ÓáPö§ ]€)D�èá§7�öNEô+¢÷&t¦3¢eý"ÝýÃÃÃýö&t¦ ¢ëdB`"ºN&t&�ä3ÑCÌD4�jB7õFd.ê%.Ç?ì/�z�‹ˆÖ™Ð¸ZÔˆ‡?œü)Ú;ѺjB÷>õVd-ZD‹ÓEëÿ¹N4¡í„îÏÔ�µ¸kçV¨ÝÍÅڹɼü0¡ p�x=®ûwZÑx¢"zæÑ„.ÀubÞ”û¨Œ*¢-Lè\'IDïŠí'¢eÄÃÐ;maBà:i"š‚ˆž3¡ p]5º×ˆw�觘�žÑÕ„îSê­ÈUüû‰¦"¢m¶ºÏo©· Sñ×ÎMED[}ÿ¬èCê�ÈTü»¸¤"¢­^žMè„ѵûeB ”éÜÕû0¡ (þ]\RÑ&tBE½‹KÒˆ‰hº�R,@Ÿ†ˆv2¡ &âŠEeäÛ¶4ˆh§jB÷=õVä'êg¢-Ü”{ª Ý—Ô[�åÓÃgE?Ro@vD”OoÛ Ý_©· 7"ÊÖ“ ]€ËŻĥlïÒQíeBàrî'JÅ„.ÀåD”ºQöLè\*êg¢Í¯ùLtNLè\*êٹͯ9;wV¶º?LèŒ'¢m't_So@FD”£ûψ~{L½ùQ¾¼šÐ¸„ˆrâ§ ]€ ¤Œh!¢scBà #ZĽM·ˆŽaBà"ºéã:Ñ™1¡ 0^êˆÆ{§":Š ]€ñG4Þ�¨ˆŽTMè¦Þ€<$�hÄEÿDt4ºcEˆèþΡÛh&º•hEDGzÜNèÞ§Þ €$½N4*k;¡û3õFä@DizùaB`÷åŒ ]€qD”s&tFQΙÐEDiaB`Œ¨­VËm\*í�Šè%LèŒ1¢- ѹª&tŸRoÀÌÅ‹hѾhQ´w*¢ÙNè>¿¥Þ €y‹Ñ�ˆfåã³¢©7`Þ¢.¶°]ø/òb_Dô2/Ï&tÄ�h²‚Þ‰èÅ~™Ð-¢©*¢3¡ 0 jD“¾S½” ]€1#ï$¢6"z1ºýD”n&tz¹ݪ Ý÷Ô[0[Q#êÄ¢ÜTº/©·`®¢.¶�t>WDC<|Vô#õFÌUÜeÿRVTDC¼m't¥Þ €™Š¸}w™¿& òdB Säeÿ¬�›º�D”~&t:‰(LètQ†˜Ðè-¢EÙ*Þ¥£"Ê„.@‡ˆgç&&¢Á¶º?LèœQ†m't_SoÀüˆ(Ãî?#úí1õVÌŽˆ2« ]€"Ê?M蜋Ñ"îI¹G"z º-"GôäjÑØ!Ñ«˜Ð85¢�âfTD¯cBàLĈçëŬ¨ˆ^Ç„.À™ˆm[õ/bEEôJÛ Ý÷øøø–zCf#^D÷s¹eµZnQ–‘—ÎÑ«m'tÍ霈·vn3šEä›t‹èµE .î]\г/‰hF^E &ZD[>­N£½S½ÚË85¢Í¯•"š—jB÷ï]- °3¢gS·"š›¿ÙVôùUF*"Êþvwn‘ŒTLçr�ý ºß¾ýxO½)3�4¢N,ÊÍ6¢/£{Q/q)Ï¿$¢YÙFôõíû.£?-¬]ÜëDk-,¶��ÇÝ%.o»Œ>È(°nñ–ýÛÔ—,Ú-Xqñ\�ÀÛëëkÎLJ}F­¤ ¬Y¼ˆoâ²½)÷á¦hÖÎÍÖ!£ßeX¯ˆwqiÜM4rCEtrï?öu½ °V1oÊ]¦l¨ˆÞÀ>£.Ö*fD-cÞ’[DoãõYF�‹ÑÏŒn$9¡"z#/‡ŒºlX¡ØMGDoäåÕê ÀZ‰(W;¬¾ðÃe£Àʈ(°ú°N"Ê$Ž«/ܧÞ€x"D´}y¿È‹þ‰èÍY}XŸ-Û—÷ëøò͈èÍýú!£ÀºÜ>¢�‡œeÜCQ�Àê ÀºÜ>¢�­,,@¿@OV_VäöíNeÜCQ�Ãê ÀŠÄ‰hû#…›r/’Õ€Õ¸yDû7Et¡Ž«/üJ½%7•4¢åu]+Žw%3),¢12jõ`Ñ’G4øÌ¢¢~K˜Áç‹h\ÇÕdX®(í{,ôÌ¢âÒ{“ŠhlV_/JD‹žÇB#zœÉ-GVTDã³ú°pɧs#Zž„s?±;ð"šÂ~õ…o.)׳s7µËOˇ¢"šÆ»Õ€åºyD{.½æ:Ñz5Ǭf/¢‰W_xJ½%SËuÅ¢F5Etεú°4qÖÎm­×U7CѬX}X¦Hm ܘ³�:‰hf¬¾,Q¬û‰–c¿�-!fD§T?™Èu¢ùùÊhê-–iDÕs¡¨ˆÎÍËa-@Kê¹Ê9¢=o!¢óó&£@Þ2�èFD—AF�¬eѲù…þoÑyzûØgÔ’º@~2ލ#Ñ…x´2=�+%=2•iD��»02 d)Óˆ®]7x2”iD«�¬X4t/4�;+ÓÙÉ5¢Í¥s‡ï #¢ó'£@fr�è¨èkD4ÇŒZRÈA¶­ß¤tÄ’ö"š+Ó9É8¢ÕmaÊíMaFÝFD3!£@>²ŽèED42 äBD™¡—×ÝG£Ï–ÔfMD™%+Ó9QfJF�ùQfKF�¹QfìÞ’ºÀ¬‰(³fez`ÎD”™“Q`¾D”Ù;fÔ’ºÀ̈(°2=0O"Jd˜#%2 Ì�ˆ’�§Ý’ºßþ½%u�™Qòaez`fD”œ¼¼þƒEŒ€ùQòr¸Á‹Œ3 ¢äæ°¤®Õ€äD”ü¼V_¸O½%Àʉ(9:,bäz )%O�‡ËF�¨ ¤#¢äj¿ú‚ë]€tD”|.}J½!ÀZ‰(;¬¾àz %k/ûë]~¸ÞH@DÉœËF�tD”ì®wùp½ ™ˆ².ÒQáp·Q×»1‰( á²Q >e)\ïD'¢,‡Û¤‘‰(Kâz *eYÞ>Ü& ˆFDY׻ш(Ëã6i@$"ʹވBDY¦'·InODY(—�·'¢,–Û¤·&¢,˜ËF�ÛQíÞmÒ€QÎe£Àíˆ(‹ç6iÀ­ˆ(+à²Qà6D”5p½ p"Ê:¸Mp"ÊZ¸Þ˜œˆ²‡Û¤Y˜˜ˆˆ²&ûë]~ºÙ(0 e]v·I{öÉ(0em^MéSQVçñÙ”.0 e}^Üj˜„ˆ²FO»UéMé×QVé¾:¿è‡)]à*"Ê:½ì®5¥ \CDY+SºÀÕD”Õ2¥ \KDY¯ý”îkêí²%¢¬Ù{uÉèƒ)] Œˆ²j÷?«KFÝØ"¢¬ÛËwSº@0eíLéÁD”Õ3¥ „Q0¥ Q¸»ûµ»±Ë[êí2#¢ðém7¥û+õvyQ¨¼šÒ.&¢°cJ¸˜ˆÂž)]àR" G¦t�ˈ(|ÙO麱 0ŽˆÂ‰—‡jJ÷=õvyQ¨ÙMé~· 0‚ˆBÝ£)]`,…SºÀX" gLéãˆ(œ{ü±­èSº@?…/ÕÁèSêíæMD¡ÕSUÑSº@…v÷¦t�!" LéCD:™Òú‰(tÛOé>¦Þ`¦Dz¼|wc ›ˆB¯÷jÀSº@ …~÷»{u›ÒΉ( 0¥ tQdJh'¢0Ì”.ÐJDa„—WSºÀ9…Q~íîÕý–z;€9Qçm7¥û+õv3"¢0–)] ADa4Sº@�ˆÂxû)ÝW*" —ØMé~ûðÑ(p'¢p¡Çç]F8D.ôò}ŸQ‡£€ˆÂ¥^ž~ì3úãÉR€°j" ¿ï3úí»µaÅD‚8DÂ9…ÕQ÷òz<}w8 +$¢p•_ûŒ>¿O½-@l" Wz;Žþt8 +#¢p=‡£°R" Sx{}>ަÞ …‰¼?G­k!¢0™¯Ãч£° " Sr8 «"¢0-‡£°"" S{yÿyX‚Áá(,›ˆÂ Ü»_¬‚ˆÂM8…5Q¸‡£°x" ·ã~i°p" 7å~i°d" 7æp–KDáöŽÂB‰(ÄàöݰH" ‘ï—öÿ�: Ë�AD‹²ÒúȦRŽy%¹¯ÛwûùúKH!3�h¹Ïd[‹¯Ç6ÅðK‰(sp<݆ôû»e oóŽèI&ÏXlN ŒŠ(óðöþãWG¿ýRÈY¾Ýdz;¥+¢ÌÆýÓdz�ÂdÑòk·5£+¢ÌÊýû÷B ¹›{D«3ŠÊ–ˆž†µs(*¢ÌΛ�Bææѽ–ˆ–µƒÏ¶Ê6‰(³tÒ§ûÔ›Œ–kDë3¼åˆù\e¶!}þRÈD¦-¸#æsE”YRÈQ¦mzn†çsE”Ùû éO!…œäÑÞgœQ²ðòëUH!" ³#¤�‹L#ºi‹hÿ‡¢"JV!ýöðê&j0?Gô´™EkD÷Û‰?Š(¹R˜»…D´ýôÜ?lR¿¸˜�œ-:¢e_!u5˜…EGÔ‘( Òé�%…ôQŸ‰²0��*)¤¶�ˆ:;—Õh†TI!¡L#Úz�hÿâ¹"ÊrÜ?Õ–6úôóû“Ž ¾%E´ÿED”…yTRHND!gJ IåÑÆ]\D”µú,é�FI_ß­dÑÆ™EÅðyE"ʲ½<¾~4Jú ¤pk¹F´þ%7å†-%…ȲŽèñØsÄ�¨ˆ²í%}K½Y°H¹Ft÷)hqò¸ˆÂ—·_¯ϵ�>?¼þRR˜ØÌ#ZîT‘Ü9>²9”³(G-é'¢¬Ž’­Í<¢gKÈ7>ýR ½”ˆ²Ní%µÄL"߈Ö+:ØPeͶ%m,øýÉ GpµŒ#zRÑr¸¡"ÊêÝ¿7Kêžjp¥™G´|j\:´ÿgÈÃ÷××ÇG'l­“ˆÂ:½4?&ýébÒ]2ŸÉ|þv¡‡Ïœ>=š"_…{|úh’®iõú·m2·Åü”Ìϯrº" +wvæîÒ¯%}yüõ™Í?…”qÛÆªŽ»ÙÛj¢·c’wïÇö[ÞWôO“µQà3¯µ#±E^sÿøô™¼‘ÉüQKæÀ!åK5 Ü4ûãáÙH $¢ÀÎÛûkýÞ¤‹Y”¡:ôü1*™ïÛd^ñ£^¶ÄßûSýs›Sg"-„ˆ_^ëýÈù ˜·ÏãÃ�öž=oO©ý<0¼.™ý?ü3§?{jºÝ‡¦™Q áþýûÏúïúÜeø<ô|í8ôüùyÀ9\÷ÛÍéÉéç&½g¶ƒ9Q EžWÀ z&OUß™HKó$¢@—|e˜×¡ç˜ ®ÎDznÝÜä­ç" ô™÷0oŸ‡vs>ôôYÿÖíÿÜø_³+?-DÔ\”áO÷7ÿéWÊ‹ï·gít|ÊXzÎôˆ¹Ãvºí@úÇÃëÓìÿ°r" ŒrvÌîpo{–ë{”µyÞªO¿w^�ùüðñ¹%ó9J¾œÃÒ ‰(0^sQ†Ú¯úíå�÷ôåqûiçëCï•"zöqXš.sÿþŸÿË·~UOŸBר­hû0¸¸Pþ‡ž½{a»BDÇaérþÅ�?BT‡ˆß‡×mÿ¹_‹} u÷Õéª#Ö�>:õˆâ¥ï°t©ÿ~ÈŠˆW:L¹þÈ_ÕÓ×Çãõ&Ç9‡¾q{h[ÿÖµ¹ï:,M½aˆ(0�ûýLìPÿôçÿ:Î]s“ž<3燥ÿ-õ&!¢ÀMÜïïlòs°–µYÊxgûf«:,}v$:" ÜÚ[oOŸ¯; i­^ß_žRo" D´ûôÿüç?ÿ÷UÈÉRˆ(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@ €@" �D‰(Q$¢HD �ˆ@  "Z”•–/ovÊbÌˈ(›yD�¡l°øúúØŒŠ(›wDOSÙù@e¸¢" ÀÄ–Ñኊ(Ë9¢ûIܲåñ" ÀÄæÑꌢ²%¢'ƒ»ÏE^KD˜Ø¼#ºwÑ¢8{\Dˆ,Óˆ6Œ™ÏQ&¶Œˆ>áND˜œˆ@ eDÔg¢$°ˆˆ:±€–Ñ¡ÕD€‰-!¢�¢¿ûíÄ¿ø|Òo0�ߦå¯RGòºˆ�Ëþýa·õ—©#yUD»z—zǰ|ÿ#u$¯‰hßšŽD¸µÿ™:’WD´wÝÜÚg¢ÿkã3Ñ«mgÿÿwê�ÈÞ ÅëŠS0'ðŸ;ñÿ¤ŽdxDÇ­=_qvî²8mö Å ŠS0'�ÅPìŒè 5\¦�Åp™=Cq†â Å d1»"zIC —)d1\fÏPœ€¡8CqY ÅŽˆ^ÔPÃe Y —Ù3'`(NÁPœ@C±=¢—5Ôp™BÃeö Å ŠS0'�ÅPl�è¨s¿.Èb¸Ìž¡8Cq †â²Š­½°¡†Ë².³g(NÀPœ‚¡8�,†b[D/m¨á2…,†ËìŠ0§`(N ‹¡ØÑêKƒwn9e¸L ‹á2{†â Å)Š˜ùP,wª£Î�ý×7'_ª=ÒÉp™À̇K& Å ŠS0'0ó¡x¾Fáîëå¦ë‘N†Ëf>\2a(NÀPœ‚¡8�™E�—™—LŠ0§`(N`æCqˆþßß6¿¥~;ÙûŸ;ñw©7"{†â Å)Š˜ùP,Ïì¾^”]�¬Þþ>ieÑÿ´âð¼ÔÛ;K#öNá>:CŠq;ÆPìµÛ‰}ÿ9ŠýÊý#÷Œ¡Ø©øÚ‹Eï³òŠ'·íÛþâäyÅè_‹Q{ç즮©·z^ÊräŽ1»}ýâêý¯ÙPìQÖ÷ŽßŠ!ŠÆN\ôoÅú{è|«ÅèqµFãöΆËí”cwŒ¡Øcäž1{4÷M÷Ž4;��°bìSoyð{-ÿúêz^ãiÆK͸½³€árC£#j(öÑë5÷M÷ž4;��°®Šæ?‹¯ÿñ‹¾qP~톲o�¬ÓȽSîþƒË{öÿfÆFÔPìsAD Å»0V,Fü«ØPlSí�ý$nïZþC±<"=ïôô¡rè¿ÐÕ¹wJû­G¹©þ*‡"j(öÙþB*öèy𡨣¶kJ¿ƒ”§ƒö å?kï­è|§eíŸYƒ¿çVfìÞɸD04¸ Å1Dô õÓ}ˆi(ö(j»lɇ�÷ÖùNëÿ+;‡Õ:�Ý;Ù—†~ŠcˆèdJ¿'°Yî¡Eã7V×8h¢š¹85zïd?\bˆ¨¡8ŠˆN¦s~ÎP¼À‚ççÿ»wý£«×GFÔPl5*¢†â°ž_ÿ†âXE_sŠ":… "z˜óßßâÀ.<'¢Sލ¡8Fß!”¡8RoC³Š":… ½c­Í":…KöŠ¡Ø©w׊ããÓ˜ãPÑ)„í§!´Ñ)\´W Åý¿Ó ÅQ.hh–CñìĢ͸ˆføVo(lï9ÎÿGpaD ÅVíC±ÝÀq‘¡8ÆE Íq(ž�OÖñ~[¯ˆÊë û†÷N~Ã%Š�ëD ŦË~ŸŠm†æ Å.kh–Cñšˆ¦ÞöÙÜ;vb+‹-Lá²ß\vb‹ÁÏç Åa—64Ç�(¢Ñ)‰èDôZÃ縊ƒ.nhŽ;±qðÜ»}Ùùm+¸w2.1Œ‰¨¡8DD¯4âßmM;ÿrÑöÔ¢ãû»6´óîD 3eOoö�}Dõ§ú°�Š,Ϫ>ê;nÝ�}ÏáÃÃÝ_î/±Ÿ]-O>d<�hqܦ²k›ŠÃmë›j‹hËfÜ}E´ÚŽÍ×W¾žüõƒ÷ºýêÐÎk•¯‡D ÅæäwzÃù‘^}ÊõôoÇ–�Ì[–-‘;ù1'.j›Í”œ>xúšÅfÜ6�½©úú6£6[Öÿ¾ßeõŸT”§ßݹóÎ^¥ý�0o§Ÿ"ÖOm\ýR~ý‚oZu^&sV¿Î‰ãâô{"Z{^ío�×ÑÞÍ8}�¢õ5;–SêÜyE_(E 3çVîµ5äô[:ŸYàôÇt¢Ñ–6íÿÚxýÎ¥{#z¶›æùÅEÛw´¼`çÎë½0WDrÓ8¦6yú¼¯ßðÍ öëôH±ûòÍÓ—è‰h÷Áa[^»6i`_tlF{áŠqi=~so'E ?e­£§Õ«=«¬Æ#µ¿õ¼\¥8}æåm†½ñHÛ{ ߌŽÂ5?9Øy" °<§K|U©¬é Ví—ã*Ⱦˆ6¯iyµú·žÎ6jlRÛ ÞŒ®Cé“Ó‚ë_lÝyýä" �¯Ú½Ð6m†#Z¶KKD‹³W?µÆ·¶à]ѱ›Ñ;A|x¯Åù×ê{¢ÿ¶n" �³“ónÃ"Ú¹ÃÙ÷œÇ+IDGoFwþZVýQ€5ú:g((¢eý€¬ï3ÑÝ3kK4_í®ù­7ˆèèÍèÉ_mûëUE`mŽ!èù…Þîé¦8ÿzÇÉH헤ČèøÍ:½¸B:B ,1»»»&€Ž&�Ž#ˆ�%ƒ��ˆ"‹�&Ž({Ž(zŽ$…�%‚Ž'}Ž^h“‹*vŽ*uŽ+tŽ.lŽ,qŽ"§„ ¥…)yŽ6[Œ&¬�”‹2b�'~Ž$†�1f�)xŽ˜Š-oŽC<„:S‹ ‘Œ)¯0g�!�Œ3a�"Š�+sŽ/j�4^�,rŽ'|Ž1e�™Š!ŽŒ#‡�$„�8V‹-nŽžˆ!Œ�3`�"‰�*wŽ•‹œ‰›‰ �Œ%«�1d�7XŒ’Œ#¨ƒ–‹,pŽ9U‹;PŠ#‰�/i� �Œ8WŒš‰…6ZŒ5\Œ>I‰=K‰2c�B=„,±}4_�—ŠªªªUUU0h�!¦…9T‹>Hˆ5]ŒC:ƒwwwWWW8¹v?Gˆ;QŠ:R‹AA†?E‡¢†¼sGeF1~G%uF ^DTYÇdGfH"sGgHo¯¯¯ ,0dgjgbgdgfg_hhgcgELÞÞÞîîîÝÝÝÌÌÌ ãÙbKGDˆH pHYs,,sˆéRtIMEã3!·V~¾IDATxÚí½÷£l×u߇èŽG�wíÆþ´íòa¶l0æá&7é„—Ì”†¤ûYíÂC¯.X2ҙ܄*îðTÀ»ô+/jv˜8ÖšûFŒb |p@g™ß\)ð' Ь|çQ†}E*Ce¸¥(þ¢ •¡2ÜR”a_Q†ÊPn)ʰ¯(Ce¨ ·eØW”¡2T†[Š2ì+ÊP*Ã-Eöe¨ •á–¢ ûŠ2T†ÊpKQ†ý€4G)m%ÈìÁ³Êàs¦z¹Èmä©¡²ƒ*’ÏØã•"™œ"ß_-Î p Á‡wT �È�P’MjQäÝN-ÚÉ æzÎ x4`yb”('¹-@ÔÌ×Aºc¾0êµA[ÛB´œð–§\)èu=„Mg@.b¡Š2ìÊP*ÃmFöe¨ •á6£ û�2T†Êp›Q†ý@*Ce¸Í(Ã~  •¡2Üf”a?P†ÊPn3ʰ(Ce¨ ·eØ”¡2T†ÛŒ2ìÊP*ÃmFöe¨ •á6£ û�2T†Êp›Q†sa–§È±-—‘\y{ �ø¹�^Bžè.÷= ¹Hö=Hi�øè+ ÿQ+› ÀÜÏáµópÙ½·E#yö=àšt~@âs“vÊ,ô4ŸuBÁõt!OÇcU:¥áÆ€´}A˜u| ×=EN+ËV¹�ïrša“~&8ÛWú/Fe8w(Ce¨ g�2œ;”¡2T†³@ÎÊP*ÃY  çe¨ •á,P†s‡2T†Êp(ùC*Ce8 ”áÜ¡ •¡2œÊpîP†ÊP΂m’á`¸Bç;»Vèx¯Þ,· qÑëñFš¿QÜÜ] Ç—›*ª-Ò\�ÚÝYÆÙyog´„š”Kê#¨§ /©V$@QFJ¹ávž•ùŽp°"ƒ™’G ”–dKÐÕ18g_(è �ÌCôp&'fKh9OgMÌ£)Ká]¦òº€–`¸ú�†<`‘gq· Ýž[qd8Ü+µSU¸‡ çÅ;Ãõ¢ •¡2T†[ÊÖË0¤ÖÈp°+NûX#Je¨ •¡2ÜfvV†{%8lÿ¨<œxK*Ce¨ w”•ápß·¾áøÀÁ>9Úo�ÊP*Ce¸Ýl‡ WþådØ!ÃäpLycÿ·‘¯†ÊP*Ce¸¥lÛOk:d8û28vÁ˜øj¨ •¡2T†[ÊNÊpü_ªqükâÊP*Ce¸Óì  '¿ðÅÿN\<þR*Ce¨ ·Ÿ”á¤ââ‹b×?-×¼}¯eHÌäžü$™È­+(|¸? D¹>3”;¿UHUÂ=FånY‚�IHúò`€ ÈYö®¿( �)RPœ€QPvŸë9€€ÕᦠÝN?‡âV Ì“«Ò…‚™)(ÚöóEö‹^ðŒzN@)3Â+9#Ü�Uì­ =€€‰Ë&Ñ/Æpƒ¡>ëœÛ;-ÃîÿŸ”Ÿ2T†ÊPî0Êp(Ce¨ ç�”á®.ÇÿkÊ¥ÊP*Ce¸Íì° ÓxƒqÖüð~!øEe¨ •¡2ÜJf(ÃÆe¸ö‡ëúD*Ce¨ · e8”¡2T†ó‡2œÊP*Ãù£O2üDp 2T†ÊPn%3”áÄ¿&&.õ_“•¡2T†;Ȭg¸¢Àfgg¨ •¡2Üaf-Ãnù)Ce¨ •ᣠwB-ä»�$ã±GrŸ™ï$JùbÀ»ì“@¦à1v @ ¹É�FÒí|äÐâù"/#Y¸Û—Š<×ï邞ž�ݘŠáº& �h�µ ÝÂm¹,•°£fóµ€åéä‚1Ïí¸;!ä íy…È6�½£_¼BËóT>Ö¦õí€Ë.)¸mnÔ@ƒYòèÀ3ïÃY'â4vZ†¡¸‰�vM¿R*Ce¨ ·›�ÞÏpŸâò/ »¶ðZûöÊP*Ce¸¥ìøN×ãÿ7hÅxÀÂnîª •¡2T†û^BrÍÿì»n�ÃB•¡2T†ÊpKÙI®*¯s§ÿü¿Áú¿2T†ÊP*Ã-fd8\eEo«Œ)oÅyƒá„ñVߌ]¤ •¡2T†;Æ6ÈpWÃ>OŽ¿Œ¿³ž •¡2T†ÊpkÙYŽÛp쯛r¡2T†ÊPn-;,ðápâŸHÃM¸pÞexxÄ é™éCˆ’G) \A¬^U¼p*. °1-i�Éñå¢Í²‹ƒ¶@#�ŠgxÖ½E¦×SZ‚вû|–𔬻¡ð„%€E$Ý‚QqÝ?2 ìärÃlòtÚ–³ÉŠFÍ]ÎgµU?Yí´2û,…y ÓJOo.²ˆ‰Ád6siãHDb˜Õá+Ë‚7uÑʰ3;f�¦lßßÒîùËÄîOîyg°�g(Ce¨ •áÖ²mÿš¼Í(Ce¨ •á–¢ ·e¨ •¡2T†»•¡2T†ÊP®  •¡2T†Êp·2T†ÊP*Ô¡2T†ÊPîV†ÊP*Ãå–!³|t@ñÃO¤K`6öç&Y @ê‘�YÃgÉ…7ºó邂އ²ŒMæS†ä;] å¶àò‚ëÑÝåª<½È=öù,Ò8·Èî“¿˜‡y* ƒ–<0†È�ÉB ¯ŒÒ;¥ƒ¹Ü´ëÝE®4éĆ,Táz™-áÌ5+E–Q±BÁÔ#ÁkqDpP1ë|�@n%ÊP*Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+(Ce¨ •¡2Ü­ •¡2T†Êpe¨ •¡2T†»•¡2T†Êp¹eÈŒcªƒò—˜áƒ¹)=9Å+?ðcéöN@4b€l ©‡Ç(Èýái0’!eÞ pÅ/׸R“’ÇÆ_+xhnÅO¸ÿaj¥>‚ú—t e<ýÆ"g“*TyTp[·Í&a’¶'%�R¡¬'bö¹-‡œPPC¿˜ý¼ŒîðnN:Ò¦mØû¢ =jáÖ€Ü^àó\•Ú ”ù¨IQ†[€2T†ÊP*ÃÝÊP*Ce¨ WP†ÊP*Ce¸[*Ce¨ •á ÊP*Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+(Ce¨ •¡2Ü­ •¡2T†Êpe¨ •¡2T†»»ÎÂ{#h êÙÍàꀰ$òsßƒ× l€42¹öÈ-#h0û�n™Ü�G�žÜ–ªü¯d(ÝÁTy`º£_¤LçààóÔ+—q·Ü��^3#l��‡è¡/–ŒÎ (h9¯dHp7ìÑêq7y¨83@)ï H{Ç\~ þ%Ót}‘ÁZÌpÑ…ãÞåY98ÄAäd”ÒkÆaíÓôv e¸(Ce¨ •¡2Ü­ •¡2T†Êpe¨ •¡2T†»•¡2T†ÊP®  •¡2T†Êp·2T†ÊP*Ô¡2T†ÊPîV†ÊP*Ãå“a¯}‘ó×2ÌÈÁ6góð2â�»eR–òR�[²@€»¡”L‚�W¨;¸>à³$òƒEÖ˜à"n›ÇÃñˆo ι%¯ôëô€dÁ‹,î*¾VФ£ºÃ»©/:HyƒŸ“N©.º£¸8øBÁr“ó…îž/ÈcºO½Ò=d"¸,Ï) ³4˜¸ÍÄi³O*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊpžeØZ1Jã ºS 64È}Èh2… ¿7 z�Ì£èx—%=ó2â�<"h·J /™Èìr€ŠIƼ›HðÐÜZ�óï:俀–Ó*›£ÁèëÚ€vò.Û#<Õ.¢Ë™ÑÈ�¬<² 6 `sóštcÁ*–—Ý_°ïÁ•#Ì+<=÷©`èXX‰r�æ„_±Ç�é5ra@üÙ`Æ„›Ð…À‚Næ‹;nEe¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ceø± }^˜,2š³Æ2U¹þø‚»e¹«xyŠ?ÚG­´-¥Ñæo6˜Ÿ.x+Oâ#V+0ÛÙ-§;O’„ΣI�¶x'Íó(»H-3°múdƒ±1Zx®øz@-F»ê�À<0&¬b�ùÎ\óÁ¬»¡Á¼BÛò&ˆ—¡8è�€€DYY�D!mÚ–Á4ñà°€;s=ñÉ ]øÃL:&�iU†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†3‘aûãö£¦ïäâÑ€j ¦ž`x8 }pK#Z \ð"!�'Oxñ‘"7´'’©”án÷ÔGÐA<™…*¤Þ›EÚû˜‚–Óë‹$À(ñÖ#6¦¶â�ག˘Ág´L¯©¬É|'é`®YÌ7aTÓ-˜„Á$ò$Ö6ç¿5à],è\ã8€€Áç²»ºC xOGP9éÌëiÝ— ºßÖ+�àz†‹Ng�:  •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce8[Q‘çßQO4f$ÞÌé7‹kîÆ!bLwl‡H)™?8`·>KXæñd¨•wó,6A:f«ï­Çò2L…ZsËÀ…ö`ÀÝ0*dªâ ð|ÀC1ã–'ñávnÂ^y®{M°)DÆ nW¥óÜÎ8p‡SÆ�m:ë‚¶á–<؎β:àÿ\ ™}âá€g1étÿØ€¦åê$ D{Û '|ûA‘P0b�ÏR†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†;/CB%ë>¾X©/¦þ΂‡Ì b•›ÛWT p°Z–± J!( È3éøÑ>]¸7 �ÚS̸3'£3/h'Å ™zÔ0\\örÀ(¡ž•ëíäþ�ÇÞ1›í‘|#vlÀÈä‚´r®\®�4€Ë�RÅ0é¹pÜ�Ò`¸:LdAA†) ‡¡¦µ¹x± $Ù`ˆ¶þ%oŠFÛ²î†ØàzY€«™_&.cƒ%›Å.뤔¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áŽÉ0à¯Ô8|X|* ’É)ÌöQÀ>ö‡7äeؘØÎ¼ }Èb&·ýç2B:Ç#Zª3ryªx—;¼0†$TVÐ’vkýô^¢dã[ÅÙM‰ÍÅZ ×­dF°=þ/¼•õ,L"w;>h{Í[Ù}$ÀrÆTæwbÁ) 'HžÁä&yÙÓD3r)ÄxCð•‚Œ¾(À�¼Òé1*¨ ¡㊀Øþ•"{MHSc³²�2¼ô«EnûOECg-L[ZÂüf ©÷íÜ0t¸å­ñ2&4#×�öÀ$“£Ž?änÿÜ™Ï2 �meS[¯4‚Š’{Ч ÉÝX‰ÒcÌ>m»*à³4˜U,k²ˆ4*kðù£5AhùÒ �ke¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*ÓáÓÅ}!� ¿|§8©@¹?ÂCÉÁ[òèŒ"³† $¢°w ŠSáˆäÔ¯Ôì«yavVÃ))@O3¹è+Å—‹£ò‚‚Èσy—θÉw ’÷ÜW°-Ì (¸Œ%#�¢cUbbÉH{sˆ3ˆNsÒß/*,¸žÕ9ŸõfÁà ‘ã–Q3�å‰WhFJQs&`ž�Ç\3JÄÆµA{7ÄžMâzÖ�T%áÊ8(Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ã�/ÜS²Œ0#~=À{©ø�€Ê œÉÝð,ŠéËX=§ CŸ ®k¸±8#ø¹â«Eöúƒ�qÖØÑÀløüœ+Ò¤;œF ½þ øË øáÙàè†-aE£I¹*aH–6Òíá€ñg Èç¶õ&XñKA«tŽ�Ë3é^(œìu˜ ±_á”ØðJ®§,£”På|ÿ¬�ª¼PÄÚ‘§ãÿt�–ç’ͰãÛ6äFÐ¤çŠ ?&‚iR†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†›•!±Ú)ÃvÖˆ�ܞ鸫Èä"2ÙXf�×ã=¦ï�€É價e=½ I·×v�'fnè,ý¢ è æþ) b›LÉ]Ù?[üfÁÓ¿ð"cÂ=¼[08� ´�QÍŠ!È€\Xäá1€Š_ / ò<$ †Ëé‚^·'2Œà³ô”Å+O=`®%îGÀ\sÿ\²Ñ2µK¬˜Ÿ ?6ö?!@n¬;gÙ`Féæ‚T:2`vXãòÐ ¦˜XR†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸H2 w­26ïí}k8P†ÊP*ÃÅ–!*\µ^— Û7”¡2T†ÊpÁd8Ø5NJoL“»Öýr¸5ä©d!¡B}/`œ±AÊ�;“\G¼K ð¬³ZÂYc·è‹PA;�!µí9|–µÙæ‰uŒ‘O_ÒÏÔà“ªW4�Sáè`.l€ÀCé~žXGºÑ’«F‰½ ØÅ"OýcEã]F57…À-äÝÙæç&4)W%´Àh<÷X`ösø0à&¯Ü-ƒ™€Ä-Œa.hü™â…€ieâèiJžã™‘´7ÁÌÚÁš˜Ñ3«^§3ÙÖ#OÇc²‰U¯2äOÈÃÆ†Ã‰·”¡2T†Êpae8 îýs½5Ø÷Ö`CRV†ÊP*ÃÞÊpüÀÃtÞØÿlä«¡2T†ÊPöZ†Cþo0é¿aç;ÊP*Ce¸Ø2ÌÿŽ¿¥ •¡2T†K,Ã}ï ×ÿee¨ •¡2\@N¼3\ÿ«¡2T†ÊPöZ†û¾ÿ §ËpNV†ÊP*ÃÞÊpì ß Õ¸ 2¤*�Ï3ÎZŠMÚŸý�8­àgùtÁïç9Ù-¶ã³¨òùâ½àû1�ª¤å莇æ¹c$òIÅÝyAšx—„(©JñÂ-á¡�mDž HêñPZžõ,$>}ÉäbŠÿJÁi†¿0‡œ —k œç3~f¸hÛûïrrã–‚Â�ô+m€¾¨!J;'‚XBû‡¬Ü?OÇc=e�ãþYBZý H-³îp=S“µ0ôš� d& ¦ð3©” ÆŠ¤F{\àËp¯ ügý½à°K†kÿ¸F*Ce¨ û+Éš;þ�¤ù“a§ ?¨ •¡2T†ý•áØN Ãéîë–áDe³2T†ÊPöU†Sv­Q†ÊP*Ã¥’á´]k”¡2T†Êp™d¸w †ø”á¦dø Á/*Ce¨ •a_e8u£†æPü×de¨ •áâÊpâ4ñCÃfgg¨ •¡2\pvÿÿvÊ�_㟠;†”_Åg(TLÜ0YÄÛýE�p7Ê–éY‹�* ¤4*VD•ß)¾œW ù<ì·�¡ H6˜jlCôfAí¤ƒYÆB*18Œpnû�3™VŠg‚o¤j•JFø‡Å¿0b”x�Œ9ÔÜöÛÅoO'h$Ã�Áät^ù(`6Ìûƒ5öÿÿd€*¹÷ÃÉàÌ\÷™teæë†à¦†<&ƒÓNF8�J8ágÞÊ»!y˜ï²RÐY¾¤3çT†»”¡2T†ÊPvn\†“»Ö(Ce¨ •áâÊpÊ7î-¼Ö¾™2T†ÊP.  'þe+6wU†ÊP*Ã9•áôMžåövU†ÊP*ÃÞÊp0õw†ãÿ3صþŸ’•¡2T†Ê°·2¬ª“¨@Ù§Æ}o�Õ¦(Ce¨ •áâÉp²4¹ýCsÇÊP*Ce¸h2œºQCóÞº.Üf Z •Ìè÷ž B#‘Ó¨ì¬ÞžÜà)³†T¢_mËGÀˆúË&!T., è>E.ùu‘Ì–øYÆòtA†²eýÁU¤• OH›�í È F‰Œþ«Á¿X0Ô4#«Bî)œ¬¬y³hµ|AÀ´Rÿ‚ísQ¦,1)(tǘ0JÌ̇»¸ŒEá·îL,ѯ¯ _ rA§Á¤MÊE™%›rÎ5Ȳ#|˳NØäŸGÕY�4·2ßÝu8þo$±½×ú.T†ÊP*Ã^Ëp¤Ã#Ý ‡ƒ®÷ö¼3ØÈm”¡2T†Ê°ß2Ü"”¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊPî‡ [„¿0}œÅ†â2U©(ç ´ÁõÌK!6h êÚÙÞAH“(KO{c"ê²€ôa+€v‹ƒ´“~Ûm£§¹{3B“H®<õ�ùâ¨Atúù€ËÚáAî3›¿[üKÁ¿\à=ºðPÀÚñ׊ÇޱcÌYwr¥ Ùÿ�£s¡d; ´pWÀ¦'ÎÌãüˆ †Ž%&J¦µs׉�–0¿tÿü€¨#Àxú÷úÅÓ™‘T+kÝÏ(}¥àŒ[î¢ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ ×�!ÃK¹Çã?V¶ £/ ¸ ×gR\Àd)C~oO:¼Q<„äQö‹Gp>oÂï¹[þŸ¬Ah,Ù/JKˆ|r!›Ä(Q rf@rð\Ÿ%4˜F¾pLM}ÑAê>8’ïÆ.hGÝ}!x»øt‘cÈòÑÌ)‘Æeˆ7O¤°åÈ.¸a)ÌCôZU"¥<õ1ƒYÆ‚ŸÛc"sÝ!$ø`–81û¬¿¬¯,„cøb@Ɉ u>ËSêõ¯›±¢2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •á"Êð§÷z/ûƒ"Ib —NtÛÌò¹Ÿe�ñ¼ Aø½‚„cº È”,7 åwÑþòŸëSAØ›ÝÑ3¹%ŠAØÙ>«Bh' òo 0©wBÀªÄFý,(9&ä BÔ© v x  áý‚‰Èf”X¹zŒã ˜ôg>BêÑà&î‡3ÒyÖe9_xŒÛ2_Çè‹e—Qº3`ÌY‹³Ä õµ’ÉB.ce¹2à]Š\˜ý¬Nb6)'¡t(s™ñ'—_ ~¯`ᣈéS±¡ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸¤2ìª@!àñy‘aÆeÌ#1�¿rçÿ„e¾{u–Ë‚bõ‰‚�ðó³„ô�DíÉ••54˜€Ü‹þW ®§%)(^D�¤vVÖ¢?(ò‚· r–»�ÉX”._0_gYÆB*1˜Äõ-Ç¢ÌÈïP�Œ9M,”ô‹|/ ?,Þ † Ùfƒ™t„†<ßèu:m@v’±ÁáÌ7Éš,†î¥â´ß Npf›h·|Ç`ò ‹vq!n°ŽÃÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\rž0 -‡m=„$T›G#Z‘f^!Lß9õÌEñè.ŸE`0}Ÿ 8žŒ¼8¤È3ÎÈ2š¥út‡4gS‚ëƒïÈ“óòò 4LE|à ‡gå¾X±=Øî¸€T¥ð?O»cû‚Ïw¹xa­¿^ �ûòˆœM�ð.{tžÝƘÐ/æ(uÇ� Rò¬§œâGääá€,L:=ÍcLÔ‰áÊ|±ÕFÚ›í,‰k2‚ì#Yòë×Ó’Î`æn\ŸgòYrŸ¾d“ˆ4FU*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP¶•$ÔùCÄ»è.�]{·à²Êx—éÎY# fíð€ÐéÃ&£oyæ•_ïç±wô/µ‡Ù� _|�ÔÎBò—‡f1YC;QVV0Ô·6|� ~çM¾]0Ô¹PZB�ŠË“ø¾T0›©JŒÆ!Ï‘y—êÕàâ‚áÊ…2ênÞ\( LµÅ±Ô¦Æ@j0­9†¸�&}:à;cÂ’‘‡è±(3ƒ´-‹˜)(lIs® rGÀPPD¦ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸\2üé½£qPÐ:íåâø€‘$}(m¸7¸¡ Þòò\öÅ‚ÉÍ9eêyúëEæ ¦Ü ƒ�&VérÖ´ï^�ª¬/d�±GtQ‹‘ƒC) £”Ï¢%ôñæ–õ ;nùQ‘ýâÅÖØ#H¥ç‹¿Qä³ø,AB ËK &¡²Ú²@0G·$2EOÄÆ—† ·¤x‰4Ö‘ö䈌!¡Î€œð"¢Îs%X PÖ;ÅEA{LÆëA«; vò&|Oø+E{\¾07¼Ø~OÈ’zA÷•¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸ä2<%@A”êS©�—‘7Fà½ùœ—‡—!̃Ò-˜‡§“)©VvE ´ˆrЧçM 2%ÏbãD3‡¶åpÈ�è"¶sCäÖ¹Ù-go$ÿ¥€QbQàúÌ2¶; ßsSˆçŠ“ D�»=  ^a. È2.KÉsþg·1J¹aF}»øA‘éÇ(}µÈ#`Ú éœâ�˜O{Ð6¶ÚÈ%›u‡æ˜Ât&Ï¢ t‡'Ñž p&=eËÕ–Åîê.8Ñ˘šaöÇ qh¤2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áRÈð''«NF0õ¤]=4 ¤‚½7T%„)(Žåân¤L1?†çü>ÅË#Hü<ØSÒüŒ?»OÖ´Ç4a€×‚î/Þ 0?Ïʲ>‹4¨ÉàÎi ûÏ8–1|8@˜‡ª�LUqcè¸,;Ëq~�uŠËhð› æéß/ò`»ÇŠû»ÀZ/ÄCtp7„F<ä(Q¨Âàg€Ñ"­Ó™„ß". Úï<ýkA[/ÆàwžÄÇ€äM¾[ð*wNHIð+Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP.… jïe Æe xÎéq�A¼¥[Øn�|9à]~OXfH3ÖÄ”í¤mmiCîdN–a¶ã–rö©“ðÔ¤d•Ÿ¥R-¤ H®S‹OlÔÏÊÂJtZÀˆÑ¢ý¼€U‰ùÊRF˜›´{×�àdºÀÓ³ç[ÅCENw# ÚŒÁÞõÑP:qi@H¼PäºÀ³aŠ}2˜);bpxë¡€Š&"ûE/ pr ¹¾µÍš‡—èu:“)fýe½Î\¦rŠ–ßÐ$bž.<$´œØS†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\ þÌ^ä83õ<¡’±ÊìØ M·üÞåÙ¤¶¡ ž R•&¥Çh0³||@¥3Ž(RwD1�YC÷é|1 Š‡}Ô)OÈ•âs±úp<‚΀§¶…ƒ:7{gÉx¢È#˜¤M#ó²3Š¿XPre@÷;WX–&Žùºµ‹vÙMÝýAAƒo¸3ƒI“žx—q /)yìMÌg¹w¦Ö†/Y¨ÂŒ s®¯l ä^ p&ïr}V µÕ1 ±AeÙ—g"0›ŒjçéÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\|þôÞ‘Ì|§ïm{vÉ`Q&èæ€Mx%ãçî‚4ÿ È#öÈ÷Û ¶D¸;`úÈ””Æ# ¢/)(ZBw®0 ¢f7‰öà¶_(. ð-Uö¹*ÑY^¡ þëóE÷‘gF>¢Æ-¹©Ä¢Ð&þA–±tfþNAiÞ„)þÝâ•"oBƒ±"£tPÀ»ô:÷=à#¼ËÞé拘ow‡Á(15i9¢ŽIçþ',£Üä¤à™£2Go¬YtçSÅ£ �üdÀe?,~¯È£Ùn‚‡²'Fn¢ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸°2ü©½?é?1àgóä;¿r?= ÛßÀ?�zdeSà[Âøºñ¢L}ú™Dæƒ/"mÏ)ÑÚþÚàݢͲ´÷¿Z`;Ë»ñ,z�¥D##Ì"’ÉÅIm÷¼•ÏÂç´ü¥€d!U™£,;B ôQgJ¡$+ˆ´ b#W Æ�óÁôå!­íG Îõ£r*ã�“àÚàOSa¹÷ŠÌâêè†ôå)(NñÃ�ŒR®xŒGÐÓú À¾ptG“~%à4=Æ�áÊ*e¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ã…•á¿¶wúŽ ˜SvÑ'ó÷öÄB#ArWvnËä¦åH%ŸY{. È‚hd‚¾�GÄÌëÍÃRÖ} ´¯Y=Aw(|x²È_ã“ ´�œÍŠ®g-È2†‚gñÁG¶âÇŠ42�Nx°`~œ_p“þ•àÓwÓÏ˜ŠØÈDf ú× *qξY9¬·ä;óûù€)FÅ´-W@„Ã�Ÿ?X§àC» øù`JžÃ æ3„óý‚%&‡¡ Yõrñ"ÛF�Â?ãð½€;clGȉP†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\XþÌÞ±ÍÀ`É#¶"Ï‘ÄÄ6CzM@b2õÇü¾}7ùÏ÷¼KÞÝ\^ »<€à±‚cxèy2$¤S•h™�Fã«Üùˆ†CAwòJê#(<¡iTjFÐ#£ô�€–3æ/˜J št~€©.æ(eˆÆ±Ü¼{GÁª—ûùS(Áì3gÌWK#CŸá:> $xÉdaësÔYåÁ`v–ÑA–éÔ5;\†Ï3ÀH:ôÕy$Ä×òÐ BˆØ åóY4é× \‘8ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\|þ{³æo'Gª’Ùwâ�`:2U™S¤ñJ€*™ ªÝóp1v9 P\æ ©Mª¦½¹ ‘L�y^F“çô‹€'ß±‡ jÛ.ÀŠÄjæ’gW n{Y@–±µs4‚SáØY⮀Ùa?š‘ƒÃݘ8ºŸéÃéiLk®�Šî³í@Š—ib"0U'‡‚ZÝ� œ¸-7¹'¸¸à¶,(Ùr‚™Ën xÛD0Jy:×£”œ&Æ„.0‡܄i"öòn¬�Ìo¦0³Ïõôôýà‹Že³IÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ó&Ãáp×*ÃÁÄÝ/O•á_˜Ì£ŒU´Àï¯ ˆUN=c^î ˜SÎÿÊsÄI¼G1ÂEAØ–ld ÉxXdç¾QÅðZÀ©dtçö"Cš”AÈ»D#±šÑˆyx÷‚€îó gœåikÌÎQYv\€3©Yx3h'‘áJ?3‰<‹.çɉ¸½óÈ6ÌóDA�ä!zÜ�“ø¾]äZ@¤qr_‰È´rÄy–aTJQXwžp ‘–7aáã–Î<¦°Uk†z«tB(k—ø,ÁL#s"Š}r"ˆCr„Ë2æšQ¢ì(¿öÌF†ƒ]Á°UáäËÊP*Ce¸ˆ2îš"Ãñ7Öýr¨ •¡2T†}–á¸òB†õGäú3´2T†ÊP.° ‡ù—‚ƒá>ö¹q°¡?)+Ce¨ •ae8˜ü#0ÿ9Lnä«¡2T†ÊPöW†Ã©8öep°�¯†ÊP*CeØ[N—Üpüe¨ •¡2\dNÿb8ÿƒñô •¡2T†Ê°ÿ2œþ…¯ëg6Ã5ïµG†ÿæÞOiP5pA^œ´ÉÂHæ¬a9êS (. ¢„Û>0à˜É©oúbÀ†öä/âMÝ«ÌxVjP˜À¢@-ÀA+yÚö;‘üûE¦‘LÈw‹Ü¼�2‡ë 3S•z2:̓åoØÝEûnV…�øô%÷ØçP†« ñ³ì¹1ªô+/c~‰–ÜåÛc`t—_è>K Å)gvAåD0Ô¨’¥3 Uh0½Î�ÅŠíPŸ�†§e¿p «í-âe}dp²° 3P“B“þí`§e¸ïOÉÃá„é&å§ •¡2T†‹+ýøí*ÆS†ÊP*Ãe“aWÕݰK†kÿ¸F*Ce¨ {-ÃqÆ49yisƒÉO+Ce¨ •ae¸ï!EwÃ)îS†ÊP*ÃÅ—á`Ÿïò¿•¡2T†Êp©d8&¼áfdøÇÁŸ(Ce¨ •a¯e8ÿÿÍÈ0ñP”¡2T†}•ápâ߈‡ã2Ì@ñ_“•¡2T† .Ãá¾ÿ`¼¦hÙß*Ce¨ —G†ìûú§ •¡2T†Êp¿eøï\²Êw‚� ΰ#�|^!ß/ ¨ò&î8Pì¥âªâ”€½H7Â2«ò)ÆG�Ù`.c–)öÿ0@ÄÛáÍmip­·güýr‘‰ü�‚’ù² ж_Þ.È_V±¼.¢åÙ’Ý£u ¯ä ˆ+X;Þ þjAl0ÔyúúQAO� ÈwVØ|÷KÅ7 –Œ”#FƒY»O xÄéÊeŒCˆ‚Ø#ßm¿pY®¶lOAHpÛûÞåƒW,…¬ûäWnÔÀeÌ/‹ò£ÁNËð€µe8ù·‰ÊP*Ce¸À2ìü”Î-¼Ö¾—2T†ÊPöV†ÿhÊ›ø” nîª •¡2T†½”aÇæþ»â�ñŸc¯sX¨2T†ÊPöW†c–û‹ÁüŸÁ®õÿ”¬ •¡2T†=–á`ßF†“Ç#O¾3\çVÊP*CeØ_îµá>ÓÞYÏ…ÊP*CeØgN8o0í�u]¨ •¡2T†½–á±Óõp0õ�õï³G†ÿîÞ_žÿõà�‚ÑàíÏ„ ?­'ARPL.¹pOÀàSª@ॠ*¦ƒ_ÅçÉb…Æä�z~΢.#�òLÀ«¥Lddȳ(OÈbH¯³f„~á .{2`„y—ÄÿAÀMX)6(ÀÍz2a³z³±t^üAÁìcÅ/�ûŸÐÔújq[À˜ð•à·‹›–QJlr¹ÁЍQ䤺T…Ü�&Ä<± NB$•ÒÏ ×çךnj¼QdZÌ<‚ïK'3‘á{ŽƒZÙßuê;ƒ�ÜD*Ce¨ û.Ã-A*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce8U†U�’Ûþ3VÌ)2ü(À™üB¼\^�Œù,²†:‚ÎâîÌNø1phÀ»T[Ü]´œÍá/ h?é?2à×øm:dEæédežDÀmɲ܊ŸG�æŒR.(<â¡‚§çÝÐ2bïTÙÖrù`((;"ÀrU¢T„FûY²A"SÍ”Ë(¥è‰ØÈÁA•g,(y7æ—Ë X‹ÙEŸ‰ÈÃ)è U!×¼ÈBITg”þVA*ñV6˜€aÒ;—“¯:y_h9óun°’Rÿž2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áÊð'÷Æj!|{A3ù9:–#,s:ø,Å#GØAqÛŒÕÖŠLn^†½ Œ¬ ¡å§üÆ>w/'àÛžŽh Op櫉ÉÓI™Ü¼ý3±ýå€nBFg¡ Å>L©š ]Ø&ïeá /¶Ê�´Ÿ(˜Á¬­ _Ø�åÞõŸ-¨Éðc•aí` s"ÈnÌ`ælìDè‹2–3Š<‰€£mÙæš“&0êeáÊ\óõ#'‚/*Ì~fħ r“^g)P{†¯Ü¬ˆóßW†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áBËp¯S)ŒêÁ 9õÄ�G¬¦î¸Œ)¸0à2†”ÒòÜ÷€<â2,š—1õT¯gKÞ,Ø÷€&åg«Àl™¡H˜èm·˜ñvA¬RöþB€4òÜãñ²é–5û­Rxëð.¶Ö�kÃð§ûT_ä|0ˆ—ú‹ÏLúçº`Ť§„AæJá•Ff“iB€ÿa€ú¨„âúj†®5ð¾“Þ„ë/ÔÑ`ôôì`¥úç?R†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\`æ2nüòœ9=#x« CÅ•µ”]¼°ï:O'Až Zbæ½âÞंË^Úü¥§éLÄÔ? yRõÞ.¨øjÁó·ý¼Èê�K›±Z ñ´åÔÂÜUt®JÜm¹m¶b%OŽ åÔݼ0×L+9˜Ýª _�‹Å#Ü6狚‘Ü6‹Gè¦z£Èb¤[¡— Æüò€ÏÒ’üŽA s7R)'ýW TÉW—ãR˜3²‰Ô@h,7Ykö£‚£X°ÞVnùw”¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ X†™ÈŒ$ÑEâç¦ô� —e˜ñãvæ4'‹ð&¤™ÜߘSôÈ�ç¯ ð³–U‡ë‘Ò]P ’(„7aCDåvëtçÆ?äJAy‘œ(­Ž(”x?xºàYä]û´ÉxHÀekפ´M¢/ÇŒ9}NÀÁ Œ*ãp^ÀBÉÝø`.Ê\Ïà¿üÇk7wKË¡ IËSò”µ'GŒàÔ žÎJôõ€ï dÜ-]ð „WN >*˜Ò0‡šy´¸-`ÄX€óot‡â,&ýÝ`a¶ýW†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áº2Ì9%ã˜üÑ€thO¸»3 � Ê� ¸ÛiŽÍêuîÌ$~¦íıÔà“ƒôôº€Àà²Ó6 ¤éË…F%Ì”!w£‘ð¿_ÐkÄ›uôd7ÝaÜ2òiç‡E¦2¤×­÷®oç÷œ8 ñ[Å ó… ÚõtÄŸlvÁ(el°QstbÀ1yÌ>‘sYÀ³~¾`…ì½fÌs£–xÁðæºC¤±ÞpglO~ýnÀõ ]›˜#H^9&` â;3’ K÷HñL°rÉ¢ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸À2LiðÃxjpQV 0Dm‰Çý'»µ.Y�B¼äO$•´6³ŒwùiýMç�á"Ä{k@ˆ²:\Ü^]œ;–y�Rˆ|Jr ¸ ƒ“¥ œYÆñd NÇPó.ÎzÙf¯%ª-Z=Ž >…Igjž 8»íkEŽ!½&i[V…ðP† OfUE0ÏDGèÆåI| &òdÝÉ/\ÏÓsph' ’ÉËèÁG€ÒXÆ<—6FŒ‰ ÒÎ ¸žÁÉwI²‰›dI†¼§á›ÁJ×ÿSe¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*ÖáÑõÄ*órh@˜áÌÎàÙÆŸ < .’ž‡Ü„©oÃ2#ó�"s��ð ü¶ˆc¶§mYžÃM0âÍ€gù ¢8k଀Rž�e,¸å‡Fͺ$OÛÐéÁ›—ÓÔª´•8#ìAlœ0&Ìæ¯ ;E. N.(ø™‰ø\‘ ÊU?#xñÅ+¾°:S÷AœŸ qÖ�Cç±Ý=üaÁ(= Ãï¤a®€ ;×ÓåŒ ÚFöe¦cHŽ�hñ¨�iâþ+ÿÏ”¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸ 2L^äÆtäH2Vl§@àåY`$&’Ég¡Æyü°­½Ð$ÞEhWï<ô¨gç4ãö-tžÓ‡3ÉлŠ`àß-ÚrƒäA¨fª"7ž•7aîHU?g“^0æt?ïÆZ@†v–8Ñ™[ñ3_Ô³ÐÈ,bb~™¸/cÂRÈšÛãÓ`JQN¨ö"$ž/ò :Kd2Ñ©VŠ˜ê[»`ö¥[‚öŒ>xY°ò® •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¸À2Lø±:ÓÁ6õOD&Ûž¿S¤Ç¨Ô ~. ø½}»µ{zŒxcÖÈÊ{êðXt„Š„Êøaóüvïôô¢ýIÿIAHÑAž2¤Æ„’�—ƒÏZ°›}6˜œÂ¢�na0ó¬­`„‰sŠS^0 ÝÇ¿P4ÁP)` F) U("$Úù�©èÂMA[ Åâ•+;ý:«!Kœ˜Â5+Pø!Çj›5;´“§?ÐNZÎݲNŠàDYœ[ñFÀìÓÈ\o.˜š‘|0Լ³²_+ïü]e¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP.› @ºÉ e˜qtÓlj]WœÀÅ1m©J’‘#Ão±Ç6D>©�ùNK¸nžÀ#¸ìÊ.hnI£bEÔÊ–y±Gñ>c˜Û>p=Í£ÍÈAÞeW„CLÂà �Ì žõEVÙ3­¤*ùžGÑaà�Š‹º`c ¶Ý¸& ‘Y;œÎ C rö©€¶±_0­„kwnž@€‘øÿE‘ºÃ™ÄR¶^_´Y0‚µ-çj˲Èr}ÊðW &ý{E ŠP'»saw¶§`prÉæ>ÚFìå®´â¿T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\ðd ¯0Ñ\FX¦[ø,uù{{Ò¹ñÁüå?sD`¼Yd¡ �Aà� tG�çÔ“˜Äv–{�¿t–b“t BüüåâÓA{ÆÜcöÀ-dÙÅiþ~�‚r¾x—‡æÒÆÀÒAòèìU-¸+Ÿ…4¨YÈ•‚T%y%í�oi ·})à¡ô%ŸE <² ºCø`¹(BÜí•€»Q;ÃÄ]`ž/ïŒ 1O¿ÒrX°óJ.^Ì5wËå†ï3 Wû½biÈàç+ý{ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†Ë&CvG ð(…±zºÈ0c‡&(oÏtœQà±CtG ·Íɽ« zsÿv*;øý<;üçe<�]Ðkvìç•<΀~Ñ6üŸ©JìÑëLs´@Û¸Ž0Ïfìx/W úÅ1™ÈL+cÈÊrZ@K¨bx±Èôù|Aúü�àù‚%ƒ‡þRðÝ‚Ú3W[;ä�€aG_5Ç˵gCd‰9Bƒ³ƒ¹-M£²@0æù.Í#GèÂû%3Lú‡E:“~1q¹Ÿ?Á"‹Š�èƒC.geÍÊÀü¬2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •á’ÈÚMÑs:¶XGž·Ì …9õ$2ÉEä§[È2E¨¼ð[} ðN@Pµ?ËÏ�¦,…Gt®Ü„W²Ê€)l9· jL¨ìÈ–Ð ŠMèà G2rÛ¿É_+¾°?‡Pe�;Û“zÌ5ƒŸn¡åô+�ÊD´G!dqÅÝxè›ËèŠÏ¬,$>}9 ê¸}é,§ÂÀ� %Ïê´7« ‘vG@À´‡8di %¯0qO\Ƙgí  þJ‘K³O¾ Ö£‚Êp0ÜÃ`âÕᮚו¡2T†ÊpAe¸ª½a— Û7”¡2T†ÊpAe8ìp^¸pĺ_•¡2T†Ê°÷2t|,?÷þ‡2T†ÊP.¼ ‡­ û^lèOÊÊP*CeØwîqápBxcÿ¿‘¯†ÊP*CeØs®|óLÈpìøj¨ •¡2T†=—áÊÃqû0ñEQ*Ce¨ ^†ƒÕ?wÈpÜ—ëüƒ²2T†ÊPö[†{=·æWÁáú_ •¡2T†Ê°×2îÕÜÚ.V†ÊP*ÃÅ–á þ<¼í2Ä<$o希a¹$ ’ÑÝIÁ�Û0õø�Œæ¡yþ]kãœS€‰�ƒ‚·‹ó‹Ü‚€²tb›²÷ôاŠ_/Ø: W nËJñl@^ðl1‘û9´—ѶÓ:È6ßؽ�Ôf%º< Á¼ûÍ"Ç�»ý¨¸3 Np ž Øvžd¤( ó‚,à¶ÏGÔÒ'„4S‰vr|‚�ÈÏwy�óЗê>ˆ(Bú›F¥þåÅ€Èdè~¾Èga¹ï ×׃» >˜ëÉÈšÅfÙÑ?(·=;À<Ìþ™…*¬€OYÆroA“˜Áヶî#L1ÔW‘ë³ÃMüÎã Xms+~ÞýtÁBœ(m6u~Ç`$À2­(¡_ ïkÁG×g¯ùöB>®9†dP»f)Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ö ‚¯JðóøËb•qÎC(÷àgüÌZZŽYà¶ü¢þþ€4G­¯Äåno0uYƒ@²ðÙ‡ŠŸE_­HÑAòèò€hD¼|Öà1ZBôæÝ˜&î–’o=ä¿0°<ôÜâêà[—ÝÐÚF-FVñ�¡ˆ‚“Ül¿�š7N¬@_OÏ´„‰à²<Í�¡%¹ÚV`"�ÎD­dA–{ÐÆŸ.dKXŒ�'+à“3BÛÒr|„`f0Ó™h™&)Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊPn„Ü<’ÚdʃATy ù‰BºsSŠÐº- h�hÌÐZ£’P9õTÙÿ·Åùïâ=â's�îðŒÒÁmãô½³zM4’2‡GKˆöÜkÑ’<�ã2*ÅË$ÒÈÏw¿V°MD;00¼’ZÀ�L+󕇲2˜¹�íÄrx츀¡æn¼•qHp2æç4€Ï²aÂ!ÙÄ|eF�/ôšž^Ó‰pr‘«-5ðåáýà„‚ÃÛ…xKð ¡ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ Çà@±öä¸×b›ˆÊí3|–Wé‚„â²Ô³F4ÞðrŠÒ†¿ü¸ø­â³Á7 Ò‡~å1jœvÇ8 ¥wÚI•Á ïr<ýp@ñJ¦üz€oY;n ("xú}Á§ ‘¸CV  ºÿl€ZY)8VïÖ€üe„1Û±�¨;—oÜB�Þ(è5 ÊcŤØ'ÍCKZçy‘t�õô©�ßS°°Þ<Ü@Ø|PcÂ|e”28íÊ~mЦ2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2Ü Ì;Ûcª»øBñí€Ø{¢@Pð ‹RGpUÀ‘Ägþ¶ŸG`*âç¿ þûâ�ŠLÒ‡°D­«$ �`ÉÈ$�R¾08ÔœVä¾÷ôúé‚|OA1&TLd^ /®?©ÈÝæQ: ö,‹ :¡eÉÌ x2Ë=8¯�Iç²\2¨'b¸ x‘Ë༌ýù%&:, QP––`�Dzì£RÖ”5Œ 2Ä“™}íPç¹í?!Á»�“þ|Á³Î 6ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ãu`QÁÎÛb'„ËŒJ¼±B:“à]ÄK„¼´ËòÈoû Úÿ± U2à¹3ñFòæasä5�i É w�­gH� xâ¼­»Ér’åËÅyšeZ9/ï˜.8N=~�ï´--Ç(ñ.ýz!¸«@#,vé1â ïÝ´µK|0»Ï#Ãv™A0#yü"7aíæXà FŒ§§ iïv.£Œc—ö¦Æ„I?=`=åúý 2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2\ð«ÅÅiN„\ /B…ꉌ� žÅ¯÷sCuŠM¸þŠ?]ˆ÷‰€²®¿#8²�È}ì ¢‘­þ3‘É#âóိÝÛ¢‰4*×ÓSvéÏÍöL®?+ C‘'MJÉÿn�+ÈÙܱŸØ I'ÌC‡Ç¨­h»“G P©Áº“‚¢³< -dÙëc[â‘ut– ”»ƒw ^aA9" Â/îß"·ÿ©8>`áYÔår=S“‡8Û@~åyÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†Êp A#Ÿ ~TŸ7|–ýᙂÜÏŸ_Èù„VÆ*5DþuÕ+´íO‹/aC|¦úh9~ hóY4éö‚Jœ«ºCÓÞHãS gt–‡ÒÚß pOÏ ÔŠö1OÖ, CzJ÷³Ú…¥ŠWî ˜tÊXxzÆ f"Tj¡­»I·°ðñPÚ–…EŒwcÝÉZñ~.`Šy·Ý`ßxD¹à%šô™â쀡hóµ€Î2é‡í²ÈÝ”¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ ·[†Ç? ˜\ÌöH@�ÑÌK*ˆjtA¹ýc·=µÈ](®§RžøSƒÿ¹ Šÿè€Ò{Ú†\Z–$ד-a¹aF^xñÜ"³†ag¾EvŸc Ù'�í¾�˜¨5÷`p8ÃŽùÍ“i0AÒ¹y!Á‘|yŠÒæ2n’ÁPÿQ�ÙÒc'|0£”Cw¯—,ÊÄ|Z®Ýƒ¦–¹ Éõ'—¥åè×Ö9@*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊPn”œµPü¸èÔ!JD¥ 8¨ŽâÂ,ëˆ(JQÒ-^F¹�Ìš² �œ|²�]ÒÑØ. YYCƒóÜ7ê>H’ñþ€^ÓHn{HÔüýà�‚Ô&¹Î hç¢ÓW‚§ J62ï*�áGEº¥m£zPÀIp„Pê‹ñ§ì‚%#Ç„8ä€E¢åÁ€š V¢\)˜V´üb‘'ñ¡J'»CËy—ë³ìˆð#68êññ€cïv¿ŠM”¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •áÇ"7ÀgSô?+¾ >f™­øß Ø�ÍùÛ ÛG�¡ ¤QÛþ—"c•°¡P"Ë"h )€”ò—ÿtŸ›ðÁ‚–‡¾ qTIÊœð,yu¼ËMrÍÂ3´„r’ƒ¼D¿Øöÿ…à¦ä)K¡ dô±×cožž%´üíâî€Ø 8©Hº( �”{$¹PRÇA÷³²ñ ‹xf‹ìÅqÍ£åÙ/– ‡–?DZÚ{R_*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊPî¤U'·”T0õüðþø€èâ§òüÚÿà.ˆ·ü½=Ï"�®(2ÌiR5£ëîÚ–¥%\O¥ÏrƒÖ·W¼HAÉxc€¾Ø ž,Ë|ÇŠÜ$õ…yèן°Ék¹,|8¯!eˆÛ‘9{JÀ‹´„²£ëF -çsé,’a?ÿ‡>KÈa§¬6b„ ¡ÏD—rYÄDlÐÓ L‘/í—ÖМ/J­M–)Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊPΛ [òð8”™tT §4˜zæ”d|6`–ï)ò´56%@•äÑë±Jª¦ ÉPºÀ±k7¤O'3¹HjçoP%‚"‘� °"éÀ€äeÏ4ò½€&}˜¯Ã‚ß.ãvAÀA¯ó˜B>ÛnÓq{À(q=ã�±ÁšÅüæRÅá}ì¡�=. ˆ+ôÅ€¤©Xæ+ WsÖQšƒÃõ|=HU²�q¶ø¸< %Œ*¾%àÅûP†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP*Ce¨ •¡2T†ÊP.¦ Ãá®U†ƒöÝáô·”¡2T†Êpad8(v;/Þί ó@1Ž®{ª¡ó#Ì2?Ë?! gè‚ø!¶IäP¨ÂÓyèkÁ÷ ôEÉFÖ˜0#„—­¢m�F.”qÈ+0 ‹PëÔ—†î3ƒ_%\„'Ï h0 VÊ�Ë8i‘`¾,@nÉÍ-!ä¸ìKö‡Üÿº€Û©äÞAŽ»pÄ`ú»ë~9T†ÊP*Ã…‘a*¯þˆ\ŽV†ÊP*Ã…–áÞ?O*o°ï�ÇñŸÊP*Ce¸�2Œ¿&œPÞ0ÿo#_ •¡2T†Ê°¯2´Gˆ Çþg°�¯†ÊP*CeØWN�ßÿÆÍx€2T†ÊP.� ‡“2œxkíPV†ÊP*ÔáÄWÁáú_ •¡2T†ÊpAd8ùO&Ã)ïÍ… •C6o° *!«'ðÉE4fA[’â%ù>‰œ±Jòôüå?Íã³ÄÛÁ[÷SªpñH@š“eÙkÊ#ž,Ž Ú=ÞqlÅP¨ÂD¤RÈè/Ü$mð\Aš£Ó“‚� .{'`¾¨ñ¡‘yê#ÖVýd¿°]È¢$– F˜._0qm%ÔËâ¥_y†ŸýjÁDgÝKaç¤38ÇÄüƒÁo,èdúyçÒ{Ö2*Ce¨ •¡2ÿ{Áa— ×þq�2T†ÊP.„ Çü×üƒÉ°S†¿ü¢2T†ÊP.€ cåx�ûºe8Yͧ •¡2T†=—Ḡ•¡2T†Êp9e8Y~¬ •¡2T†Ë(Ãf+† ÊðÁ�ÊP*CeØs¶ÛÒ4ÿ€â¿&+Ce¨ ^†[t5;3ø;Ce¨ •á¢Ë°s»Be¨ •¡2\2voݪ •¡2T†Ë%Ã)ÛXOüáoÔ�`EªòO.î È‚S ¢÷ý€ƒÕž)R•d45ûÄ[^F2R ŸÉEK8�.ä¦oÈ�žðPráþ€Mx(ÏÊ#öØᔆÌ÷W 97 ÀrŒÒëÒÕjEÚìö�ö¦ø™§§xaR›»e¾·Þð"mcFr"XGÚ�8=`~¹>#‡P¼ÜÅ‹»”äຄ{8|&`³ÚF3¿¶ó ¼y�áÍumáµö�”¡2T†Ê°Ç2œö•oâ_PæxsWe¨ •¡2Ü>Nü9y{»*Ce¨ •ae8Ü5Õr©ÉÁ<¥ •¡2T†[ãÂá¼¹‹sD‡s}T¨2T†ÊPn� ÇáÍÁøëë¹P*Ce¨ S†ã6\×…ÊP*Ce¸ 2<`0Ü„ •¡2T†Ê°·2 [&t9ÜówŠƒ�ÜldG6äikd ¯<Ä9·=+ Zˆ¢÷ª€_òSP�§­qY+�¼ 2´³eraHü�–kÏÚ#ò²¶ƒCß2Ué5¥%Yîñ^qZ��o °#Œ=R¼Tý ö¼ ëÝ£ÅA)CŒÛ™‘¼ŒÁÁØ9„CG—_ ƒÃòô\@¯¹I>‹îð,'�Ddv('ÉD zåsýÊÓñ>_óÎàUfw:ÞÇC*Ce¨ ·e¸(Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+(Ce¨ •¡2Ü­ •¡2T†Êpe¨ •¡2T†»•áË�€gg{¶s¿% DÉPÞÊ0£ž‚ꉌd U¸ÉEEº…*J® ÈAžE˳ƒ»Q A5MÖGÐN$“0JÜ�¼HÉÓ`òÜ�542Ï)`÷~¶âGhY�Òf7U?9ÔHž-ñ³²†&}ªÀ9›T p7š‘±�‚8k kpˆºÏA—ÔݼY´NÑ®§� ±qIL+—åªDÊ� šôXÀj;ãžDî?ÊP*Ce8s”¡2T†ÊpKQ†û�2T†ÊPÎe¨ •¡2ÜR”áþ£ •¡2T†3G*Ce¨ ·e¸ÿ(Ce¨ •áÌQ†ÊP*Ã-En%$rþ¶Ÿ,#É‹sJðÃA›ÝxìÔ€þ‰íl Ïâ2œ-i…CÕI†4½&¶ß �òd›ú,c¡ð„J^ùNpOñz‘K+ö ItÁ:‚Ù ¸Œ°²P‹Aƒï î*^+hmV»0›´äЀ1$$X€òY¬ —åŽýx�x8'x©x¤8¨‹vö>ûí‚ê§\m9bÖù:�2ÜJ”¡2T†ÊPîV†ÊP*Ce¸‚2T†ÊP*ÃÝÊP*Ce¨ WP†ÊP*Ce¸[*Ce¨ •á ÊP*Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+(Ce¨ •¡2L2~x‘ئÜþá€òy®?, ¤ÉÒóÄ€›�³y$€+Î((óg“’÷Å€GP§^@gÛ³óN ØÙ€L!=S íFÇ<‚v2Jß ¸ Ï╯È�»]0ttŸM'2~µ`‰á¶¹ ðYž•Ò`pØ&‚å�€#¹ÞËÙ¢`Üre1â ;¦57ÖàEV½·ƒÏt�(ÍŒhC}>P†Û‚2T†ÊP*ÃÝÊP*Ce¨ WP†ÊP*Ce¸[*Ce¨ •á ÊP*Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+ä©dh�W¨È��nDþu5˜ŠLÉÃË(x°ÈÓÖ*¾P� 7T(p[ŠònñF}Ä#ùH‘Ë;ŵÝ'õ¨XÉÁaL8E.e£—Xô¾€ŒF•œ—jå³Ì×gáýRÁ‘ˆy`d¾PVê¹ÑÓÔïr=>9!x«`@XŽ8°�9z( $(E¹¼ÈC‰+,wwÀ�YÑXg�‘An ÊP*Ce¨ w+Ce¨ •¡2\A*Ce¨ •áne¨ •¡2T†+(Ce¨ •¡2Ü­ •¡2T†Êpe¨ •¡2T†»•¡2T†ÊPN‡¼@q¹w=‰ŒmR¨�›à‡[ÌC%HÞßR>@Ц ø,§�ƒgdûäÐkÚIýKJžÎ¶¥+ßh'|- ƒ<‚êŒ,÷À�¿_´U'#°=M£2Ô,¼uo@ƒYné‚Ò!xtðAÑ]Š—�1ÄNï„AnûÏþüœ°€ÿ_>]\_仌ØÜ›t¢ we¨ •ᣠwe¨ •ᣠwe¨ •ᣠwe¨ •ᣠwe¨ •ᣠwe¨ •ᣠwe¨ •ᣠwe¨ •ᣠg@g„`Ejî ÚßöS’F%C‘LîÙN­ÑKeÁM/â%ò.|zñFqCpcA-ÍÈ]ô9õÛ“ø) ,…ËR_<‹Z d˜Ýg yh{6ÁÊR0[NÄÓ—áäktGA“~cÀe,()CŽ] %*=ÆŽýt¡ý(@_måζî'ü¸ÛåÏb‰É‚šµ#|îQ†3@*Ce8(à •¡2œ?”á P†ÊPÎÊp(Ce¨ çe8”¡2T†ó‡2œÊP*ÃùCÎe¨ •áü¡ g€2T†ÊpþP†3@*Ce8(à •¡2œ?”á P†ÊPÎÊp^ È�­Ð×Ëa|T@ÖpxYnã€?Ñ›3äMHžÅÝr£v9ÀǘŠ4ïÜž‚� x:[|=`Ðò]�•œÄ—â¥%'xò¥€cïø`G“^)XwÞ ¸s+ùÓF‰½&òÔ?^dAáú<¦ðü‚�@Yy^![1üf‘áGƒ¹-a“''ž]`샂YçÐÇDÎ ÊP*Ù¢ çe¨ •áLQ†ó‚2T†Êp¦(ÃyA*Ce8S”á¼  •¡2œ)Êp^P†ÊPÎe8/(Ce¨ gŠ2œ”¡2T†3EÎ üh-pÈ—Q€pAÀݨ¹4 9öŽºƒgN[ã=¢=/ãn<4ÍCî·G¼å)rdÜãÆÎîSB¹Gf(7ÁÉ—MB¼ˆ=ëYÞiÈ%โGàç´ídcâ²(†&qÿã<Æ Ò¤œtÅ;¨á˜€� %ONÄ·è®­+ÁŠ6ëtٔἠ •¡2œ)Êp^P†ÊPÎe8/(Ce¨ gŠ2œ”¡2T†3EÎ ÊP*Ù¢ çe¨ •áLQ†ó‚2T†Êp¦(ÃyA*Ce8S”a_9£È$A°Mn2OÝÕlçþ¹€úJQHä×Êe™†hœ*’óL…‘ü¡GЯãZBjwÖG°'?.bgû¬váƒç‹ûúõdA3Ò¨ï4‰QMÝá@çå€åƒiåY'¨’Û>Ür£I·”½ »E©+Ù8ʰ¯(Ce¨ ·eØW”¡2T†[Š2ì+ÊP*Ã-Eöe¨ •á–¢ ûŠ2T†ÊpKQ†}E*Ce¸¥(þ¢ •¡2ÜR”a_Q†ÊPn)ʰ¯(Ce¨ ·eØW”¡2T†[Š2ì+ÊP*Ã-EötDˆÙºc÷€sNjC_lðsÁCYvO‘ûà1Jû3õ8ä¥Ü^tîÀÀ~x/Ϥ㶘꒹ñäÔ€&!Oº�ýú~A“® p; p6_Þ¤=â�Y8: åH)U‰¾8ö'* ;Ä{}\°QËè‰ÇÎ:Ògˆ2ìÊP*ÃmFöe¨ •á6£ û�2T†Êp›Q†ý@*Ce¸Í(Ã~  •¡2Üf”a?P†ÊPn3ʰ(Ce¨ ·eØ”¡2T†ÛŒ2ìÈ�W(^È" R+> Ò�ƒÒ¾|¡øv�v¾PÁ³>x® å§·7ðÁ¬ÅàÝ“Š�Jl/¢è<äî· îöµà•‚ë¯ a|˹r'\�å¾[Ü×� âe„9Cð¢€gág,zF€ŸgËs‹2ìÊP*ÃmFöe¨ •á6£ û�2T†Êp›Q†ý@*Ce¸Í(Ã~  •¡2Üf”a?P†ÊPn3ʰ(Ce¨ ·eØ”¡2T†ÛŒ2ì=‡wA‚d½Å>Pl’2|«`‡*V (Ê µóˆŠ;¨†À-Y£š‡0ì<�‡Þp žuHöeØ{”¡2T†[�2ì=ÊP*í@öe¨ •áV  {�2T†Êp+P†½G*Ce¸(ÃÞ£ •¡2Ü ”aïQ†ÊPnʰ÷(Ce¨ ·e¸P´§dõ/>ÐðzÀ‘¤6›óç!x�š”;l@Y¯¤3ï+(açüÓzw‘ålÔOM U!ox麂žÞÐNZòdÀPSÐ�Ðn ¸3kg(œð.zÌ•‚Rªr:Ïz`Zg}}G.ÊPÊþ¢  e¨ eQ† …2T†²¿(Ã…B*CÙ_”áB¡ •¡ì/Êp¡P†ÊPöe¸P(Ce(û‹2\(”¡2”ýE.ÊPÊþ¢  e¨ eQ† …2T†²¿(ÃÅ+®ý"9˜ÉÕº‚3ïònxì׊ºàýY™!§Øìe7ø{—ýo]üïÅÿQüŸÁÿUüßÅÿSü¿Á¬Læœÿo$•?Ÿµ ›�Öÿ�á�»DD¶˜g-ÃýøÑµ2‘-GŠˆìšNîZ³¶ ÿá�ø{Œxà'zΨ¿8ë6|\þd×®?™u>.{þInÖmø¸˜‹?IåÎÚ…�[xmàc{Æmæÿúóqõáf݆�ËïÚõdznÃÇå6ss� ±Lü Êú_ Wqîçe8˜‹ÀØŸ“7°·ë*Îýœ  çbÈ¿%ìÚàŸ’�ûyAÎ&ÄB°"Àឯƒ«'7ò!ç~NP†ó� ± Æÿ�{¸¡9÷s‚2œLˆÅ`°y:÷ó‚2œLˆa0ܬ �ûyAÎ&Äâ0ùp8ÜÈ¿#¯âÜÏ Êp>0!–ç~NP†ó� ±¼8÷s‚2œLˆåŹŸ”á|`B,/ÿè»>ñ�f݈�˨ÿxÖmø¸ü“Oìú'³nÃÇå¢ÿ1!DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDd=êДᬲ©6×I/]§ §¿5‡Œú2lièQ˜Œápò�žDÖÑ4×}X �aç;S›Ý£ÈÚiâü¨]=žlrǬƻÃY7u#tpݧ>ŒÍF¼Þ›ÈN�¦9îÃpØ5èë6»O‘µÓŒŸ³<œus6ĸ 'ç{8ý­¹¤k ïQ&êžöÆp¿°í §GÓ÷aØ9èë6»G‘µóÔŸoæù“42ÌINôhÖ�]�AGÈö¨.Œ¦ö%²ø2_µzчµd8½Ù=Ь�g¸Ï%ñŸsÎJK÷þqfrRû¦0w<µ3ãíìQc�¥ØD¿zY»šhö¡kÈpz³{Y3 ÇrØ—Ñæ_ì :â·þ¯‹ßžöN|�ú0Ùô}óÒ—ÈoÛ`l¸ç¹Ã]+ÿr2ì�éÍîQdí<ñucؓѴG8¬ÿkC{¸áû΢++0ÑÌþôazãzYã kiúÐѪ5šÝŸÈšã‹ÃD\ô…5Ö﹟î•/†ÍìQ¦ ¢7‘5Ñ…üß>ô¡c¦7»G‘µó¬ù•¤74‹ùÄ[sÀû¬6·C†½èÃôïý‰¬é2ìEZ®ÑìþDÖ ˜�~þ-Âäb>œxk¸÷Ü!öŽÿš ö÷az6õ'²¦Ë°}he¸F³ûY3`r$çó¯EÖcò/…‡SÞ›?†{[·öŸ^æ·ÄË`²‚¦?‘ÕñGÇ”á¼÷aŠ »ÿ¿?‘5z1ÝêÄpïÿôjº5Ü}•ájË(wˆFö(²vå7© ¦^ôAn»ºÆm8ëVm’5þ†xNÿh“�ÔÐêõa¥¥ùƒ·A¾Ó“ÈÊßÞ­ùÓ“¹ìC+ÃéÍîQdÍ€ÉÁéã?¶�Ípó—Xs¹šGÛVÞʰ}˜¬>Yãú÷}X_†ŒËpí/1½˜î5wa¯¦;ƺç2\múX)P¯"kÊ®5½èƒ2Ü*z1Ýk1Y`Ù£éîšò‡Êþôar€X˜úYÓv­éE”áVÑ‹é^ƒ¦Ø¼?Ó=˜švýéÃä&ñ×·=Š¬Øœa0¶¯A/ú  ·ŠÉq›Ë/›N»ñFóWÄ»æuº§ÿU}úÐŒÿ¾–ö'²¦oÔЋ>¬/ÉMîEd͂Ο$õ¦>§c¢æüæ1~÷0kW›�}èÃÄOôÈxêOdM!Œ×‹>lðw†+ÍîQdÍ€^ü¬t�²õeº'ÿžjWü‰³/}˜ò“¶‰ÿ: óÿç†5Ñ‹>ø£ë­¢Ó=…îÍ)û2ÝÊpNXã‡È½èƒ2Ü*&#¡G‡0e£Þéè™/Ö“aú°VÞõ&²ÖøG…^ôaŠ »›ÝŸÈšã£1—?±ŸÂ”‰vL÷¬›:µùSdØ—>¬›wûÞ™ßÈZ«D­}舎éÍîOdÍ‚ñá˜Ë¿!^£áÃöõ‰ˆ�ËømY3ëæ¹Í»ù�¬Nö¨B›ÞìEÖ …þ Îô/øc˜ÏøínôpüûчñÆ 'EBŸæ8²v��oßú0M†ÝÍîOdÍ‚ …þüÂp×ÔyÌNŒŸî3ÇL |ú0öU£ã0¥DV×ï óÇðsÞ‡®?êNov"kìûùýÞ_ßϺA›hô¼¹úÞ`¬oóNçß[õ¢Mü §¾3ë¦N!;íUö~¤ÂšÝŸÈš“ß‹oÍ]ÿúÀ›Ó6dšg&Ú£> §6µ/‘ÕFÓ`Ú{sÕ‡5r`z³{Y³`8·³½±&OÂø|gÝÖ Ñ´´G}Nmj_"kÚF sÞ‡µr`z³{Y³`ßÀ çk¶7Ðâ®@Ø· }of»mj�ú0œš\}‰¬ÁpWû°fLov�"k& öüÍCüµÛ¼3¶Œ_±Ú¡9‹ÞéìéÀ }±/}XɯΦö%²ÃÕ†v5u^û°N¬ÑìþD–ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆÈvñÿ|I5mÁùò%tEXtdate:create2019-05-17T06:06:16+00:00’F9½%tEXtdate:modify2019-05-17T06:06:16+00:00ã�'tEXtpdf:HiResBoundingBox310.09x300.684+0+0WìT¡tEXtpdf:VersionPDF-1.4 G:xIEND®B`‚PynPoint-PynPoint-e5d0637/docs/_static/uml.png000066400000000000000000015132631512720201400212750ustar00rootroot00000000000000‰PNG  IHDR ´´ÛK¿ø IDATx^ìÝ ”ÜE�?ðJ!@Ë0<² "0¼‰ ‚!!B8‚܇ܧ‚(Gx€Ê-—A®pß Wäd] g�5𔕀�@ áoõ2³ÓÓÝ™î™îêšéÏï=ËLýêøüj|ÛUß®_¿à"@€ @€ @€ @€ @€ @€Mèׄ65I€ @€ @€ @€ @€ @€‚@»I@€ @€ @€ @€ @€ @€Mho »F  @€ @€ @€ @€ @€ @@ Ý @€ @€ @€ @€ @€ @€�¦´7…]£ @€ @€ @€ @€ @€ Ðn @€ @€ @€ @€ @€ @€@Sڛ®Q @€ @€ @€ @€ @€h7 @€ @€ @€ @€ @€ @ )íMa×( @€ @€ @€ @€ @€´› @€ @€ @€ @€ @€ Ð�ö¦°k” @€ @€ @€ @€ @€ÚÍ @€ @€ @€ @€ @€hŠ€@{SØ5J€ @€ @€ @€ @€ @€íæ @€ @€ @€ @€ @€4E@ ½)ì%@€ @€ @€ @€ @€ @€�vs€ @€ @€ @€ @€ @€š" ÐÞv� @€ @€ @€ @€ @€ @€€@»9@€ @€ @€ @€ @€ @€Mho »F  @€ @€ @€ @€ @€ @@ Ý @€ @€ @€ @€ @€ @€�¦´7…]£ @€ @€ @€ @€ @€ Ðn @€ @€ @€ @€ @€ @€@Sڛ®Q @€ @€ @€ @€ @€h7 @€ @€ @€ @€ @€ @ )íMa×( @€ @€ @€ @€ @€´› @€ @€ @€ @€ @€ Ð�ö¦°k” @€ @€ @€ @€ @€ÚÍ @€ @€ @€ @€ @€hŠ€@{SØ5J€ @€ @€ @€ @€ @€íæ @€ @€ @€ @€ @€4E@ ½)ì%@€ @€ @€ @€ @€ @€�vs€ @€ @€ @€ @€ @€š" ÐÞv� @€ @€ @€ @€ @€ @€€@»9@€ @€ @€ @€ @€ @€Mho »F  @€ @€ @€ @€ @€ @@ Ý @€ @€ @€ @€ @€ @€�¦´7…]£ @€ @€ @€ @€ @€ Ðn @€ @€ @€ @€ @€ @€@Sڛ®Q @€ @€ @€ @€ @€h7 @€ @€ @€ @€ @€ @ )½%ÐþiSt4J€ @€ZS Yë>ÿ·æ|3j @€ @€ @€* 4kÿ6Ù3é-´¡�lJhˆ @€„f­øüoò @€ @€ @€ @ X Yû·ÉžCo`aC{ÆŒÉ`4D€ @€ZMàßÿýßۆܬõŸÿ[mÒ/ @€ @€ @€e2Ø¿MödšµA]ëmh×*¦< @€¨Q ƒŸÿk|fŠ @€ @€ @€ Ð72Ø¿M+ОŒZC @€ @ o� DÚóž"zG€ @€ @€ @€@"� öo�4�ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷I¢ @€ @€ @€$È`ÿ6É8c#NhOF­! @€ �·@ "íyO½#@€ @€ @€ @ ‘@û·‰F*О ZC @€ @ w� DÚsŸ$úG€ @€ @€ @€@� öo“Œ36â„ödÔ"@€ @€y d° "О÷Ñ; @€ @€ @€ d°›h¤íÉ 5D€ @€rÈ`AD =÷IÒ¢ý{ë­·ÂË/¿Ü>ú•W^9,µÔR-ªaØ @€ @€ @€)2Ø¿M1ÌBNhOF�¾¡×^{-Ì�;7ôë×/|òÉ'aÉ%— C‡Mß‘Ä-v÷â‹/>÷¹Ï•ô¢U}?Í @€ Ћ2XhïE󥕺z饗†I“&µùÆo _ýêWk"ˆk4ÿüç?Ãûï¿>þøãÿ]˜ûך͢‹.† Zh¡šêS˜ @€ @€ @ o d°› X =uÚ†>øàƒ0vìØ0cÆŒö†�9昰Ï>û¤íHâÖfÏž6Úh£¢q�=:\ýõaÀ€í½iUŸÄ�Cs @€ô2� Dze ýÑG ^xaXb‰%ªzâ‹,²HátïáLJ/|á a…Vh‰/ W…Ó€B}ôQ8âˆ#ÚkþË_þN;í´°ÒJ+UÕÚ¼yóÂŽ;îüñBù¥—^:<ðÀ…ƒººþö·¿…'žx"Üÿýá–[n™oñu×]7lºé¦a½õÖ þ»jÂï  @€ @€ @€ú¨@û·ÉdÚH=kÖ¬pÏ=÷©«mnΜ9…¢q3üÿý¿ÿWø'žØUí7k·Øb‹ðÒK/µßrÒI'…]vÙ¥Ú*ze¹rãŽçž{nèß¿û˜ZÕ§W>T�&@€ @ ™@ "½2Ð~×]w…ýöÛ¯GÏ)†¤?üðB 9†Ý]õˆk›m¶Yxùå—Û+½óÎ;èQ£ªjä­·Þ k®¹f{Ù1cƄɓ'Ïw½'¾.†æï¾ûîªÚè\(†Û9ä�°ÖZkNqw @€ @€ @€´ž@û·ÉÐ{ËŽX¯ÜÐ~õÕWÃlP·‡¹ûî»é+®¸b—u¶j`»Ü¸ãFó%—\"ÐÞå¬Q€ @€�VÈ`A¤W~þ�¡å}÷Ý·nÓçøã�/|þ_pÁëVgO+ºà‚ ©§žZ¨æ¬³Î Ûl³MO«Lv¹µ‚Zíñþ‰'¶÷7:tüïιöÚkÃQGU—ñ­¿þú…/éW{ú]U  @€ @€ @€@ìß&sho õÿ÷^]ïk·Ýv ‡vØ|73ãfí·¾õ­0cÆŒöæ�9昰Ï>ûÔ»;YÕWK ½}²zX:C€ @€@v,ˆ´6+† n¾ùæ°üòË7}žÌ›7¯°žpß}÷úÒÛÞ×Ó@û)§œ.¼ðÂöçpï½÷†‘#G–<—O>ù$Ä/#L™2¥â3[mµÕB ©>¼Pæí·ß¿ÿýïÃh_zé¥Ãý÷ß–Zj©’Ç?ŸüñÇá�wÞ ¯½öZxä‘GÂE]TqZÄÓßcº™×¬Y³Â¸qãÚ¿4ßJ�öÎë ñoäž{î)¬Ët¼>ýôÓpÄG„›nº©äQÅùpâ‰'†¯ýëaÉ%—,û(?üðÃðì³Ï†3Î8#<þøã%eFŒQhwРAÍœ Ú&@€ @€ @€H(�Áþm²Ñ ´7�º\ =nÇ×rÇÍëJW|¥xÜà~ùå—Ãe—]fΜY¶h<¡+žŽ¶øâ‹7p½«êZí½kdzK€ @€�Æ d° Ògí>ø`ÕŸ×ß{ï½põÕW‡“O>¹ä!Ç0t 2/³Ì2�ŸZ˜>}z;vlûo[)ÐÞymg÷Ýw“&M ýú/©ÅC=ôÐÁx²ý‘GXõó‹'óÇ7óu¾öÛo¿pôÑGW]�‚ @€ @€ @€½[ ƒýÛd€í ¤.h?í´ÓÂŽ;îXS«O?ýtaÃò¥—^*¹ïûßÿ~8è ƒjª¯/hïËOר @€h´@ "-ho{®Ï?ÿ|Øl³ÍJóöÛoâzBÿþýkš³gÏï¾ûnˆ§‡Ç+~y>žðOŽïÈž_Å·ß~{ÑÚCwÖ6býõêOMÿ*\n­àÎ;ï £F�겪Ûn»-|ðÁíå.¾øâ°é¦›ÝO°ß`ƒ J$8î¸ãÂ^{íÕeå üæ7¿ ;í´Sɯn½õÖ°æškv«N7 @€ @€ @€ô.� öo“� ´7�º\ ½»§˜ÅÍçSN9¥ä5äó{�y‡–mÕíÙ>#@€ @ d° ÒÒ�ö8EÞxã�°Å[”„£« `¿óÎ;aêÔ©áÚk¯ O>ùdÙY7xðàB@ýÛßþvXvÙeË–ùÏÿüÏpå•W†Å[¬ðv¸Îo�Ûa‡ û?ü0¬¼òÊaß}÷-’¯WzòçÓÝ@{\‹9âˆ#ÂM7ÝTh>ºÝ{ï½aøðáEÝ™2eJøÑ�~Tô³J'¹×2ŽÎaúxïW¾ò•pà 7„X –ª”%@€ @€ @€è…ìß&Sho u=í±›|ðA?~|xùå—‹z7N×_ýŽ$„øúó·ß~;üóŸÿ,´7Nÿíßþ-Ä@}=6Qÿñ�ê�§µµ]q£8ž7dÈ�ªÇÖ¬@ûœ9s›o¾Y8ù.^qC?žz·ÜrË…%—\²êþW*X/ŸwD @€ôi� DZ>Ð'دýë°Ûn»͵­¶Ú*œ}öÙOi�áëŸÿüçáä“O®iŽüñ…¶ Ptß]wÝöÛo¿ªê3fL˜ûìÞzë­ÂcØú˜cŽ)œ¾Öñ*WnÒ¤I…Óàâõûßÿ>ÄÀ|Ûid�¹bèü»ßýnˆ'³U:Õ­qì[Ü„�¯ënÛ.WvµÕV ñd³±cdžÅ_|¾O¬Ú@{µ>ñuì‡~xøÃþPؘ�Ž1ÐözñxrÞUW].¸à‚ŠýÚ|óÍᇾøÅ/Ö4ÛáSS&@€ @ å2Xhÿ׬+dŽŸ¿c˜:~qºóÃãñ³üe—]Ö­9?ÓŸzê©Eaù[o½5rÈ!UÕ7zôèpýõ×·ÚÑŸª:R¡PwíÓ¦M+,ÐvvØa!þÓñzê©§ÂÖ[o]ô³råºÛÿG}4Lœ8±èöc�=6ì½÷ÞÝ­Ò} @€ @€ @€½D ƒýÛdRí ¤nD ýÕW_ l°AQ¯cX:†®;^ñ¤ó¤îxš{¹à{¥r;ï¼sˆåã kÕ^1ؽÞzëUUü–[n)„¼k½.¿üò°á†V¼­Ú@{O|N<ñİ뮻†øº÷ý÷ß¿ê!Ä ç=÷ܳâiz+j”OÕ�U� @€�–È`AD ý³™÷àƒ>{v¼*½¡íÞ{ï-pŽ'¬ÇƇ V8U<¾-~Þ¼ð Kæ÷u×]Ö^{íöŸÇ5�gŸ}6 80üä'?)Z_ˆó$è?þøã¿¿àCímW#úÓ“?ÈîÚñ‹_„N8¡½éÎFññKñ”ûŽ×Ô©SÃJ+­Ô“.·ß[îø¯|å+á†n¨ËóêÒI• @€ @€ @€4D ƒýÛ†Œ«\¥í ¤nD ½óé`±ûåíå6k+�äOc饗Ú%Î<óÌðþûï‡À®õ*·¹Û±ŽxJÛ)§œ.ºè¢Z«n/Úi§…wܱìýÕÚ«õ)|�>ñD¼x‚]­W¼7¾.¼ÒÕhŸZû«< @€­%�Á‚ˆ@ûgSî�wÞ cÆŒ 3gÎlŸ„åNþާ¹ÇÏÈ�?þx{¹xšûÍ7ßFŽYv?ÿüóa³Í6+ú] ¤ÇÏô1øÞùŠ�펧¿Ï�0 IDATïsyŠþÔúWÙ�@{çq,½ôÒáþûïK-µT{óñ3||&ñKm׈# o£4hP­Ý¬XþÇ?þqÑ:JìKüÂCWo±«[TD€ @€ @€ Ð� öo“�[ ½�Ô�´—«s÷Ýw/œŒÖ¯ßÿ=ÎjÛåÊu&‰átPXsÍ5 ¯ÿóŸÿâiìO>ùd‰^ÜT�·Ë,³LYÙ³Î:+Ä:_ñ¾xòù¨Q£Â /fÍšž~úépÄG”­çâ‹/›nºiÉïêh�'Íí´ÓN%Á€÷Þ{¯½íèóÃþ0¬ºêª…Ó×ß|óÍO£»é¦›JúÿÇ%¾"¾ÒÆv£}8ÝUM€ @€@È`AD ý³y?�n·ÝvEŸ½ãËâ©áñ³yÛõ—¿ü%|ík_+š}§žzj˜8qâ|gd<íýG?úQ{™ù…¤;ÚË}a>ejýSëN ý­·Þ*¬ƒ´]åìË} ~«­¶ gŸ}vUog«vwÝuWˆ§íw¼êy |µýPŽ @€ @€ @ ­@û·É,ÐÞ@êFÚ{ì±’ÓÉ«=y½ÚrIâ©k&L({B[Ü<�aúÎW<Ù}ï½÷.ùù /¼P6„~ÔQG…}÷Ý·hC¾íæ¸9|Æg„ŸÿüçEõÅ×¥ßwß}%§‘Õ;Ðí¼qß±#ñ$¶8 ð öÎW¹ïb™K/½´pÊ^ç+…O§»ª  @€è,ˆ´w˜G�?�Æ7¬�{î¹Eaé¼¾ñÆC<Ñ=~ÁúŽ;î(|ɺÒéìmÕ¿úê«aƒ 6ho-~Y;~;¾‘¬óUK =EjýSëN =ž€¾ë®»¶7UíšJ5_&¨µÿÓ§OcÇŽ-ºíÎ;ï, à"@€ @€ @€è»ìß&Ãho u#í�_3»_î´òzœÐOLï¸y[Ž*¾VûÐC-úUüz衇ŠBðñ5Üñ´õΧ–Ïï5å+=óÌ3 'œu¼Î;ï¼°å–[ý,e ýä“O;ï¼ó|gPçïbár›à©|8ÝUM€ @€@È`AD ý³y?'yä‘áúë¯oŸYåíÝ�vo¿ývá3õŒ3Ú«¨’®%О¢?µ¶Ñ�@û)§œ.¼ðÂö¦â)é_þò—»\ƒ˜ßéõµö»­|¹õ%�öîjº� @€ @€ Ð{2Ø¿M†%ÐÞ@êzÚ+�àOm>|x·6UËmêÆŠâA<ÑmÑE�¯P|úÖ[o¦M›VT®óÆê믿ÖYg�¢2£G�×]w]ÙÓß;7:kÖ¬ÂÉq3gÎlÿU¹ûSÚ«í{ì÷¸qãŠñ5áG}tÑSù4pº«š @€�> �Á‚ˆ@ûgó(Ú·Ùf›ðä“O¶Ï¬í·ß>Ä/†÷ï߿dzmÞ¼y…·¨½ôÒKíu53Ð^Kj|­�öÎåã[ââ[êâ)ö¯j¨µ¿�ËÇ5—ñãÇý8ž ¿âŠ+ö´j÷ @€ @€ @€d,�Áþm2�öR×3Ðþûßÿ>ì¹çžE�îØõ­¶ÚªpryçÍìj7U+ÚkyEv¹SÈ;Ÿî^î$÷xßúë¯_õ¸à‚ BìWÇëÑG-„ïÛ®T�öjû^©?“'O hïw*Ÿª±$@€ @ %2Xhï0ó:¿¥m̘1¡óçÉr5~¹ú�7Þ(üObï½÷B ŒÇ/¥ÇÏž=;Ì™3§PWÇ«Q�öz÷§Ö?ÎZí¯¾újáKõm×;ìPø"A¿~ÅËhÕ®½ÔÚßÎå�ÐÞSA÷ @€ @€ @€z§@û·ÉàÚHÝÓ@{Ü`þãÿ®¸âŠpÓM7•íéÃ?VXa…’ßU»©Z)Ð^©Þr�(wRØQG8à€öâÇsL¸òÊ+Ûÿ{饗ñ4±Å_¼ê'P®�xÂûÚk¯Ý^GŠ@{-}�ÏpóÍ7/¿ür{cá’K.)úB*Ÿª±$@€ @ %2Xhï0óŽ?þøpÙe—}ž¬h�'ºÇSÄã—Á;žê^ËD®g ½‘ý©eL±l­�öÛn»-|ðÁíÍüìg? [l±E·×^jíoçòÓ§OcÇŽ-úq¥gÕÓ¶ÜO€ @€ @€ ��@û·É0ÚH].Ð^Ms#GŽ o½õVÉiì�ï=óÌ3öÛn[¶ÊžÚ+½J»Rßßyç�CÚ3gÎl/²á††_üâ…SÈËõ¥ãï«1‰e¢Éšk®YTü¤“N »ì²KûÏRÚG�b�~�è²ëqÿ°Ã ñö¶«s =¥O—V€ @€�–È`AD ý³OSßzë­CürwÛUé-mñóòw¾ó�ðÒK/õhþÖ+ÐÞèþÔ:ÈZíñsüGQt°@¥/ý—û{ ¾Ÿ{î¹%oÒ«µÏËßu×]a¿ýökÿÑàÁƒÃ½÷Þ†Þ“jÝK€ @€ @€ �¹@û·É„ÚHÝÝ@{5]Š'²m¼ñÆ‹ö$Ð^í+ÌÛ/×VÇ ÜøûÍ6Û¬è”òxo|]÷‚ .XÍp ¯õŽÁùO<±¨||í÷©§žÚ¾Qœ"Ð^ëæt¹õ:žÐžÒ§*l… @€hY� DÚ?›}åÖâç߉'ÍϪŽA÷Jaöí·ß>¬¸âŠa©¥–*|¶8p`á³ø|PøvÇ«�öý©õ´–@û¬Y³Â¸qãÂŒ3 ÍŒ1"Üÿýe×/Ê…ßãßÐ< Tk7+–?å”SÂ…^ØþûZÞW·N¨ˆ @€ @€ @ ¹@û·ÉÆ,ÐÞ@êFÚwÛm·pÀ„e—]v¾=ïi ½càº+¢J!ò¶× —û}WuVûûÎáû�öÎ'¬wÕ×jí1$ßÓ“ôÊõ£Ö/'t5¿'@€ @ o d° "ÐþÙ»á†Â÷¾÷½¢ W.pߎv '•‹�çË.»,¬ºêªß.6oÞ¼°é¦›}­G =Ejý+¬%ÐþÌ3Ï„ø½íŠ'£}ôÑ›¼êª«ÂøÃ¢ßÇÕ¿üå/×ÚͲå+½Õ-®ÛTóæ¸ºtB% @€ @€ @€MÈ`ÿ6Ù¸ÚH]¯@ûj«­V8�mà 7 Ë-·\U=Nhï½÷Âzë­fΜÙÞ·Ž¯Aýõ×Ã:ë¬SU¿k-Ôù´ôÞhOéS«¯ò @€´–@ "íÿšrñôôñãǽé,>›{î¹' <¸}R–{ã×°aÃB ¦:t¾“7~–ßrË-‹Úèi =Ujý«¬%ÐÞ9�?eÊ”°þúëWlrúôéaìØ±E¿ß}÷ÝK¾dPkŸÛÊ?õÔSaë­·.º=¾ñnÇwìn•î#@€ @€ @€è%ìß&“ho u¹@û1ÇöØc�0gΜ.[ž;wnáUà -´P—e;hv ½cÐü­·Þ o¼qQà½æU¸aôèÑ!žZ׿ÿB‰ÞhOéS/wõ @€ Ð72Xhÿ×Ô*wÊù¡‡?üð¢‰Wî Ò—^ziˆoëêêzõÕWÃlPT¬§�öTýéjlÕ¬‘”k<µ~Ûm· O>ùd¡ŠxÒ}üÁ2Ë,S±ÉrëñK÷Þ{o>|x­]-*ÿé§Ÿ†xBüÝwß]ôó|0¬¸âŠ=ªÛÍ @€ @€ @€ù d°› I ½�Ôåí'�tRØe—]ØêÿV�2ÐÞUˆ¼ÜïãÙ/ùË;ÄÀÿ¢‹.Ú^OW}i+X­O,üñÇ^ÕÞvÅ`@|µw[ˆ¾«AtuJŸ®úê÷ @€´¶@ "-hæ™gBü’xçëá‡+¬°BÑ�;¯;Äöý÷ß–Zj©.'rüœ?¯v¼zhOÕŸ.ש@µ'´Ç/œ¯¹æšíwÇ/Ñ_wÝua�˜o“W^yeˆt¼â3<çœs€jín{ùG}´ðƾŽW\“˜»êPx5ýˆezc =¥OµŽÊ @€ К,ˆ´t ý•W^ n¸aÉä;öØcÃÞ{ï]òóÎëÆ S§N ñ„ðù]•ÞVï@{£úSë_gµ�öxòù®»îÚ^}%÷Îí¿÷Þ{a“M6 3fÌ(úÕ‘G8à€Ð¯_íËoÓ§OcÇŽ-j<ù}äÈ‘µ(O€ @€ @€ Ð 2Ø¿M¦VûŽZ²®5Ô+7´{k =nžÇ×›W{ŠØ¬Y³Â¸qãŠ6n;ž7Žã+»§M›ÖþP«=å¬ÖéÖí)}jõTž @€�ÖÈ`A¤W~þ¿ûî»Ã¾ûîÛ>YâIé1½øâ‹W5�>ùä“pß}÷ÕÑvãˆ#B 1/´ÐB%u½úê«aƒ 6(úù=÷ÜVYe•ŠíΛ7/ì³Ï>…ö:_•îíüæ±ÓN;-ì¸ãŽMëOU¨ Uh?å”SÂ…^Ø~g¥€¹öË�¦ËÅyƒí]�òÞ±Î'Ÿ|2|ûÛß.i¦Ú€}­>Ê @€ @€ @€ä)�Áþm2�öR÷Ö@{-¯(�|/¼ðBØtÓM‹$O<ñÄ¢SÍ:o~ÇÓâxà�°ÜrËÕõ ôÆ@{HåSWl• @€ Ðç2Xé3�öGyd¾'¥Ï�;7¼ùæ›á·¿ýmˆAê™3g–̧øù<†ª+}v~ûí·ÃFmTtïöÛobà¼ÿþ%õÅ7„�p ášk®);w/¾øâ’Ï÷å>³î¾ûî…z:_©úSë^5�öÎeâéò1ô_í—bŸÎ:ë¬Â?�¯øÏ;ï¼°æšk†AƒUì~<áýꫯ. Õ·nÔÁµZ*O€ @€ @€ �N ƒýÛdƒho uo ´G’)S¦„õ×_¿*�n¸!|ï{ß+*{ÒI'…]vÙ¥ýg·Ýv[8øàƒ‹ÊÄMÞm¶Ù¦ª6Ú Å×xÏïÕé½5Оʧ&l…  @€h9� DúD =Nœ‘#G†•VZ©ìŠo:{øá‡ç;¿âgß;î¸#|á _¨XîÓO? ûí·_ˆ'Äw¼&L˜9ä�°ì²Ë~Ãò=ôP8úè£Û‹ÅÏì|ðA¸é¦›Ú¶Æk„É“'‡¡C‡¶ÿ,¶O¿þúë‹ÚøÁ~Æ�Þ}÷ÝðÆo„ø¶·x y£ûÓ�?Êjí�O»ßj«­ÂÙgŸ]ö‹•úOÚ?üðÃCüŒ_éŠ_XmµÕÂç>÷¹Â›ñÞyç�ð‡?ü¡d/÷¥†XOü»¼ë®»j ×wÇÉ= @€ @€ @€y d°› D ½�Ô½9ÐOt;ýôÓC¿~óŸ"ñUåñ5ã�?þx‘dܘ_a…Úöú믇uÖY§¨L|mz<¥½Ú×nO›6­°Yƒö[l±EXk­µÂç?ÿù¢:{k =•O§»ª  @€è,ˆô™@{O¦CüÜûÓŸþ´=�>¿ºÚ>+×Ò^|ÎñóøW\N>ùä²·v<é½Ü—°;ÞO ðÁ �ëý©e¬±l5�ö[n¹%zè¡íUwçKøñæj�'îÇ/ÔãÚ`ƒ §»/¶Øbõ¨N @€ @€ @€½H ƒýÛdZí ¤îÍ�öÈrÝu×…µ×^{¾B1È7¹;^å^ƒ]éÔ¸Ã;¬°aÜUp>n>o»í¶…�ñ¶«ã†yÛÏzk =•O§»ª  @€è,ˆ´t }‡v»í¶[Xe•UjšMñó{ãx*{=Œ;¶äWgžyf!|ÞùªtJ[|Ývܤ]d‘Eʶó—¿ü¥ðZô¸™Üñ:öØcÃÞ{ï]ô³Þh�ƒHáÓÀé®j @€ú€@ "½2Ð~ï½÷–|>­f:¬»îº…·�ÅÏÝ1ÀO8ïîõÒK/…I“&•¼A­­¾ÕV[-xà�…Ïñß”ö /„ƒ>¸(ìïéxB{üï·Þz+|ï{ß+ùlçM nÇu„¶«Ñý©Å©ÜZAìoÛfÍš¾üå/·WÙv‚ý Aƒji¦lÙø%€ûï¿?\vÙe¿Ðvcôûîw¿6Úh£š¿ÔÐ㎪€ @€ @€ @ ;� öo“™´7�º·ÚÛhâ q[mµUXa…‚ .þçþ'Üwß}!¾~»ó5lذ0uêÔ¢MìŽe~ü㇋.º¨ä¾xšÛqÇÖ\sͰð ‡?ü0üùÏ¿úկ•W^YR¾Üfy,Ô›í±ÿ�öiàtW5 @€}@ ƒ‘^hÏéÑÇ/…ÿõ¯-|i|Þ¼y…ÓÇ—[n¹°ä’Kη›o¿ýv¡l »ÇÏå -´PÙò±þwß}·°>Ë,¶Øb…*]�îO=ì?ùä“ðâ‹/†¹s窋Aö‘#GÖ£ê¢:bp~æÌ™…ïã³i»† Rx>]=£ºwH… @€ @€ @€Y d°›ÌG ½�Ô½5Ð~ê©§NfûÅ/~Q³NÇWv—»9nŽsÌ1aÊ”)5×Ýñ†Ž'©uüyo´7Ú§Gèn&@€ @ Ï d° "ÐÞçg™ @€ @€ @€ P�@û·Õt³.eÚëÂX¾’fÚgÏž6ß|ó¢W†ŸtÒIa—]v)êl¹øi§�&L˜¶Ûn»ðä“OV-tÝu×…µ×^»Ëò1´O]?öØc»,Û¹@<É=Þ_•^î*7î1cƄɓ'‡´ßR­O¼!ð;ž_®¾ù ¤ÖûéS3¸ @€h)� DÚ[jÆ, @€ @€ @€•2Ø¿MöpÚHýú믇uÖY§¨…ÓO?=ì°Ã lõ«ŽAõm·Ý6L›6­½­TßqÇ‹Ú.hßb‹-¹çžúõëyä‘pÎ9çÌ7ؾß~û…=öØ#,»ì²5�+¾vüî»ï\pAá•Ûó»vÛm·°å–[†5ÖX£Ð¯JW¹qï¾ûîaÒ¤IE÷UëÛùñ�.ºè¢ö&ãó‹§Ø÷ïß¿ªñv¾ûí·ñYtu#|ªê°B @€´¬@ "í-;û œ @€ @€ @€�Žìß&{ íɨól¨\ =ž@~É%—®ß~ûíðæ›o†Y³f2pàÀ°ÔRK…aÆ…AƒõxpmõÇ“ÓçÌ™S¨oá…ñDö¡C‡Ö¥�w²‰ði"¾¦  @€´�@ "í-4ß • @€ @€ @€�Êìß&{<íɨól¨Ú@{ž½×+ @€¨§@ "íõ| ê"@€ @€ @€ @ × d°›ÌN =už ´çù\ôŠ @€ÍÈ`AD ½^› @€ @€ @€d'�Áþm2�ödÔy6$ОçsÑ+ @€4C ƒ�öf½"@€ @€@32Xhoƃ×& @€ @€ @€Ù d°›ÌD =už ´çù\ôŠ @€ÍÈ`AD ½^› @€ @€ @€d'�Áþm2�ödÔy6$ОçsÑ+ @€4C ƒ�öf� öo“¡õKÖRÏjé m�öžMw @€ @ ÑíunéÏÿuST @€@U6«*¯ @€�(0cÆ  @€�$íI˜kj¤¥7´Úkš+  @€ @ ¹€@{ÝÈ[úóÝUD€ P•€@{UL  @€th7% @€�Tí©¤«o§¥7´Ú«Ÿ(J @€@ßxã�7£�>Ø>Ø9sæ„ÿø�ÿ+¯¼r @­åû¾  @€@#Úë¦ÚÒŸÿ릨" @€�ªì¿TŤ @€ÀgmëÀí¦ �J@ =•tõí´ô†¶Õê'Š’H-ðþûï‡TÝìܹs eZh¡°À T}Ÿ‚ÿ'põÕW‡ï|ç;%$?ýéOÃGQòóZ˳&@€ÝhïŽZÙ{ZúóÝUD€ P•€ý—ª˜"@€øL@ ÝT @€R ´§ﺽ–Þж ÚõQ‚͸ñÆÃvÛm×í¦7Þxã°óÎ;‡±cdž¡C‡v»žV»ñ¦›n &L(öùçŸößÿ’Ÿ×Z¾Õ<�—ê# Ð^ÇÕÒÒŸÿ릨" @€�ªì¿TŤ @€Àgí¦ �Z@ =µx×íµô†¶Õ®'ˆh†@¥ twú2iÒ¤pØa‡…Å[¬;··Ô=µÔk-ßR˜K€uh¯eKþ¯›¢Š @€ª°ÿR“B @€Ÿ ´›  @€@j�öÔâ]·×ÒÚT»ž J @ õ ´Çþ2$<úè£aÔ¨QÍN¯i³Ö€úÝw߯�W2¾É“'‡½öګ׌[G  @ o�öº=Ÿ–þü_7E @€T%`ÿ¥*&… @€>h7 @€�Ôí©Å»n¯¥7´-¨v=A” @€@3êh�cXf™eÂóÏ?†ÚŒ!õŠ6k ´ÇAÍ™3'ôïß¿}|óæÍ ìãÕIèíu{N-ýù¿nŠ*"@€¨JÀþKUL  @€|& Ðn* @€©ÚS‹wÝ^Koh[Píz‚(A€�fÌ/оÆk”íÒÓO?ÝeW¿ÿýï‡ÓO?½Ër­Z ;�öVµ2nH' Ð^7ë–þü_7E @€T%`ÿ¥*&… @€>h7 @€�Ôí©Å»n¯¥7´-¨v=A” @€@3º¬ž1cF¸è¢‹Â)§œR¶ÛC† Ó¦M +¬°B3†U¶Í>ø |üñÇ…ß-´ÐBaРA=îÛ‡~æÎ� PS}Ýuïq‡ËTðÉ'Ÿ„�>ú¨ð›¸€µÈ"‹ô¸™îºô¸a @€@�Ú{Ä×ñæ–þü_7E @€T%`ÿ¥*&… @€>h7 @€�Ôí©Å»n¯¥7´-¨v=A” @€@3z¬¾ñÆÃvÛmW¶ëO<ñD˜>}zxíµ× Aé¶+†§Ç�F�=ß!ÇPt<å½ÿþEåâý‡rHXzé¥ÃsÏ=®»îº°ð ·—y÷ÝwÃÞ{ïVZi¥0{öìpë­·† .¸ <öØcEõl¼ñÆ…zâ¿;·Q©cÿûßÃ#�<n¾ùæ0eÊ”¢b1Ä¿ãŽ;† &„õ×_?,¸à‚ÇW«ûK/½®ºêª¢qF‡M6Ù$¬³Î:%í”s™3gN8òÈ#ÃàÁƒ å÷»ß¾”pùå—ݿᆆ£�>:Œ3¦ê)Y/—ªT�" Ð^7Ö–þü_7E @€T%`ÿ¥*&… @€>h7 @€�Ôí©Å»n¯¥7´-¨v=A” @€@3j Vwîã¼yó·¾õ­ðÐC•t?†¾ÿøÇ?ÂÑ�¯‰'Úƒî�ËÄ0üÈ‘#Kî]f™e Aù%–X"\sÍ5a§�v*)óüóÏNM/wç›nºi¸á†ÚƒÞ•žÃW\vÛm·ªSìã¯~õ«°Æk”-_«{¥qþìg? p@I•Ê?óÌ3a•UV)Œ#–™ßËÄú]tÑù–«§KU¸  @€@ÃÚëFÛÒŸÿ릨" @€�ªì¿TŤ @€Àgí¦ �Z@ =µx×íµô†¶Õ®'ˆh†@­ÁêÎ}üôÓO '’ÇðzçëÚk¯ _ýêWÈ#J~ß1t>tèЊÎ§‡ï·ß~%¿?þøãäI“ ?¯ÔÿØö‰'ž^|ñŪXÇ�n»í¶°À ””�cŒ¡ñ /¼°ªº:úÍo~¾ñ�o”ÜW«{½ÊÇSócxÿ'?ùIUc‰.·ß~{áËåž}½]ªê”B Ð0�öºÑ¶ôçÿº)ªˆ @ *û/U1)D€ ð™€@»©@€¤hO-Þu{-½¡mAµë ¢š!PkPºsçh¿ä’KÂ{ìQ1ð~Çw„ñãÇ—öüN~�§Œ¯¾úê…û*õ¿;–“'O{íµWÉ­'œpB{€¾Öz‡ ^xá…0|øð¢[ku¯WùQ£F…gŸ}¶¦a<òÈ#aÝu×MâRSÇ&@€�º ´×�´¥?ÿ×MQE @€U Ø©ŠI! @€�ÏÚM @ µ€@{jñ®Ûké m ª]O% Ð �ZƒÒ�ûƒçßþö· §xw¾.¿üò°ë®»†©S§†1cÆ”ü~Ï=÷ 1DÞ¶hÒ±Àk¯½Vöd÷uÖY'<ôÐCí'©wh��òXfôèÑaÖ¬Y!žú~ê©§–¥Ž'ÉÇSã^xáößOŸ>=Œ9²lùŸýìgaï½÷ |ðA¸à‚ Â÷¿ÿý’²tP8÷Üs‹~^«{½ÊwìÄ6Ûl&Nœþú׿†<°âô+÷œåÒŒ¿m @€Àÿ ´×m6´ôçÿº)ªˆ @ *û/U1)D€ ð™€@»©@€¤hO-Þu{-½¡mAµë ¢š!PkPºsõ«_…Í6Û¬l×ÛNR�aïµÖZ+¼øâ‹Eå–Yf™B€|èС%÷_sÍ5a§�v*ùy[H¾íó ´Çú÷»ß…ÏþóEõœuÖYáðÃ/ÛçÇ<|ík_kÿÝ~ðƒpÚi§•”�aö8 äçgŸ}v8ì°ÃJÆ-† ÖþóZÝëU¾­W^yeØyç�ÛûóÆo„�7Þ¸äÅÑ1Ø—Xb‰†»4ão@› ðíu› -ýù¿nŠ*"@€¨JÀþKUL  @€|&ÐWÖ�=P @ ÷´ç÷¬ZzCÛ‚j~R� *¥ãÉé{íµWE¤O>ù$ÜvÛma„ eËÄÓΧM›\ø}<¡ü�C)){Çw„ñãÇýüÓO?-Ô{óÍ7ý<ž¶CñË/¿|ûÏçh¿çž{ !íÎWå¼Y�öx~ô­tUh¯çœéìÒ[÷ @€@ýúÊFF "íõ›–j"@€èBÀþ‹)B€@nsçÎ ñ� mo,íª±|\“Xd‘Eº*ê÷ P�¾²\ U @€‰2Ø¿M4ÒÚ“Qw¿! ªÝ·s')PÏpòÆo®¸âаì²Ë–íò|ÖZk­B0½ã5qâÄ0eʔ©èÿûß '�ÿéO**süñLJI“&•Ô[k0¼­‚×^{-Œ1¢¤¾ø³xº| Î×Ó&—@ûùçŸößÿŠSJ ½‘mê&@€@þ}e##ƒ�öü§» @€úŒ€ý—>ó( „@Ÿ¨t LWŒëòqýz«­¶ £G�.ì¸ @ þ}e¸þ2j$@€%�Áþm£†VR¯@{2êî7dAµûvî$@€@#zÚŽ Ìp@Øa‡ÂꫯÞeWÏ=÷ÜpÈ!‡•‹aïçž{.Äÿ×ýWøêW¿ZRÏ´iÓ¨Q£J~Þˆ@{lkðàÁí´Ÿxâ‰Â âÕÓ9Óñ!:¡½Ë? �T ¯ldd° "ОtæjŒ ÐÚö_Zûù=�â[Y·ÜrËu-®_ýõá›ßüf�êÉáæxØÏÅ_\pÁðÑG…å–[®°§Ò¶“Cõ��Öè+ëÀ­õÔŒ–ôn� öo“ ´'£î~CT»oçN4R R8ùØc� ûì³Oáµ å®X pŠùâ‹/âÿ]íUédô¸À=~üøpú駇£Ž:ª¨ºuÖY'<ôÐCeÛén ½Ò 1Ë/¿|xöÙgÃK,Q1¸O¢?æ˜cÂÇ\í° ÓqqÁ:^µö»ÑåÛÒ“ÚëáR5¨‚ Ð�¾²‘‘Á‚ˆ@{Cf¨J  @€Ê Ø1/ÈMàÔSO G}t]ºuà�†³Î:«¦}ˆº4\ÇJ.½ôÒ°çž{¶×¸îºë†|0 0 Ž­¨ŠÕ ô•uàêG¬$ Ðl� öo“´'£î~CT»oçN4R RPzòäÉa¯½öª{ÓŸ~úi˜0aB¸ù曋êÞ{ï½ÃE]Ö[o½ðØc�ýîòË/»îºkÙ¾Ôôn«dúôéaäÈ‘%uÆSQ®¾úê«L¯¹æš°ÓN;•”9ÿüó ¯=íÉUk¿]¾m,ÕÚéÒS÷ @€@ÏúÊFF "í=ŸŽj @€¨RÀþK•PŠ �D Ò@O?ãŒ3Âá‡Þ“*švïÌ™3ê«®þö·¿µ÷¡7�§i�&@ ®}e¸®(*#@€*�ÁþmCÇ×±r�ödÔÝoÈ‚j÷íÜI€�F Ô”®G_¦N�ÆŒSTÕˆ# §–o²É&E »ñø>�¯à,wUê<áä׿þuÅS[*…²ã)ñ·Þzkád”'žx"Œ=º¤ÙqãÆ…Ûo¿½G§§ÔêÞèòmƒ¬&ÐÞH—zÌ/u @€@÷úÊFF "íÝŸ†î$@€¨QÀþK�`Š ÐP�÷ß¿°®þâ‹/–´3jÔ¨²köO?ýt—}zá…Â*«¬Òe¹Ü ”;­þž{î ñ�§.4K ¯¬7ËO» @€@íìßÖÞénÞ!ÐÞM¸”·YPM©­-T/PkPºúš+—üàƒÂZk­U² £;/rï±Ç!žOL/wUê,ƒð+¯¼rÉmñ„˜­·ÞºJï|tÐAáœsÎ q!ç•W^ +­´RI™e–Y&<óÌ3aذaeûÇ0pàÀÃøƒ -´PÉ"}­î�.ß6�jí�t©ÇüRt_ ¯ldd° "ÐÞýièN @€�ì¿Ô¦8 xõÕWþð…²ëêqÍ~‰%–(ùÝìÙ³Ãã�?â›\ÿô§?•íßñÇ&MšTuß?úè£÷æÍ›WØ_4hPÕ÷ÖR0¶3wîÜžÀ ,Ptëo¼†^R]4Їü¸ Ð,�¾²Ü,?í @€µ d°[{§»y‡@{7áRÞfA5¥¶¶ P½@­AéêkžÉsÏ=7rÈ!í…bH<¾r³s¨ý�ßúÖ·*V6¿@û¦›nî¸ãŽ’Eäûï¿?Œ;¶l�¿ýíoÃ׿þõÂïæ÷jÔãŽ;.œp %uTZ ž8qb˜2eJ{0¿V÷F—oH5�öFºÔk~©‡º'ÐW622XhïÞt @€@7ì¿tÍ-4L ®éo´ÑF%õwõVÕxÃßÿþ÷ð�o|£ìéîß®Z®óÿüç?ã�>bûW]uUÑ›`cùxÍvØa‡°úê«w9þ‡~8Üwß}aá…n/ƒñ‡~x!ÿÔSO…“O>¹ðÆ×¶+ί¿þõ¯…ƒnb~ùË_–´µóÎ;‡/}éK…°ý²Ë.öÚk¯Â!;.¤è+ëÀ©¼´C€ô\ ƒýÛž¢Êz˧»–Þж ZålVŒ‰j J׫{¯½öZÉ $k¬±FèøjÑå—_¾°pš+]ó ´Ç{¶Ûn»pÞyç…˜�W\<Þb‹-ÊVOD™6mZáTõ¶+.Zó›ß,[>†Ú�:ꨰÈ"‹~Oމ‹áO<ñDIù©S§† 7ܰý絺7º|[Ǫ ´Ç²�r©×üRtO ¯ldd° ÒÒŸÿ»7ûÜE€ Ð]û/Ý•s�ˆëñq�¹óUé�˜Î容暰ÓN;•Ü_雂¿ä÷{Ç IDAT’K‘GYõp6ÞxãpöÙgBå•®rkåqŸá�üãÿgïLÀ¯šÖ?þþ›GiBÔC n¡D*/Ñ HW…K¤n†ÌsIÕ¥”±›!³4��"•‘\%I*¹ÿó]×:wŸuö>{8ûìß>û÷]Ïsžú�½ÆÏZ眽ß÷»Þ%3fÌ� .¸ «èÃ?,·ß~»­ ß©ýçÎ�+eË–õÜf$ �| $Åœ/–'  ˆŽ@ ü·‘ –‚öÈPoˆÕàìX’H€ IÀ¯P:¬¾äŠò­Ûðr„¨]ÿu´÷-ZȲeËTuˆ±:¢À;¥±cÇJÿþý3.#B "ÄÏ›7ϱDókÖ¬‘wÞyÇ6Dîˆc5Hûå^èüºã^í…âÖúb=$@$@Á$Å‘ƒíÁ– K‘ � � �  @ÿKh,B$P°û÷éÓG¦L™’Uÿc�=¦¸¥U«VIãÆ�³²ÁîÿñÇKÍš5Ó×r^qk×]ý´ÓNËÊêä¿8ï¼óä„NpÇ /¼ 'Ÿ|²—¦Óyî¹çõ�‰H€¢$�;p”ÌØ � � �@~bà¿Ío>JSÐîVIe¥Aµ¤È³] ÈMÀ¯P:LžNG�ê6 @‡(=Wr‹ÐíÚµSG‘â¨P3­[·NpØý&Ù?øà©W¯^FQ¿Ü �_wΫ ù ÁÅ/_æ' —@R10ˆPÐîÒdm$@$@$@$@9ÐÿÂåA$¿þú«´jÕÊ6B9ìä-[¶tíê[o½¥ìñfÚÿý•­½råÊê’›Áµ¡?2Àîß°aÃŒì¿üò‹z衾"­Ã0fÌuj¬Ÿ„hï]»võS„yI€H oI±ç ‚� � � DF þÛÈÆJA{d¨ƒ7Dƒjpv,I$@…$àW(f_r…a°FTôråÊålÒ©ÿˆS¡BéÑ£‡k—÷Ýw_y÷Ýw¥víÚŽy¿ÿþ{éÙ³§Ìš5˵>�ï'Ÿ|RöÙgŸ¬2Ó§O·5lÛE‰GáBç×0`€Œ7.«¿‹-’Ö­[g½6Ïp™‘H€H  ’âȈ�A„‚ö‚¬PVJ$@$@$@$`G€þ®  �¸X½zµÀÞn&ˆ½W¬X!uêÔqíêý÷ß/_|qV¾SN9Ežyæ)S¦Œ8Eq×…þñ�ÈÑG-»ì²‹ T3lØ0GÛþ\ &LPõêd7»“a­�„àþ•W^‘7ÞxCªV­*gœq†íXGŒ!{ï½·lß¾]½pBì^{íåÊ…H€H LI±‡É„u‘ � � –@ ü·… ¥v Ú#C¼!Tƒ³cI ($�™3gJ—.]²š€·_¿~…lZÕ=dȹãŽ;²Ú™ÿüsסÀ |À¸æÓ(h÷ŒŠI€H€HÀ•@R10ˆPÐîºÚ˜�H€H€H€H ,ô¿„E’õ� äCàÝwßUÚÃJ„_yå•RµjÕt• �qãÆe51vìX[¡»5#"¯·jÕJV®\™UAxZ·n­Þ_°`�´oß>+�Ñ< A8ßµkWyöÙg3êð#ˆ‹!ë! 'I±s†I€H€H€Ї@ ü·‘Á¢ =2ÔÁ¢A58;–$ �b'àõ¨Ñ &H¿~ý| —‚v_¸˜™H€H€rHŠ## ÚùY#   ˆŒý/‘¡fC$@9L›6ÍñdV¯à ú6l˜tëÖMêÕ«—UÌIÐþòË/ËñÇŸ³™ï¿ÿ^ îÍÀ;Õ«WDNßwß}UùÉ“'KŸ>}²êzì±Ç¤{÷î®Cùé§ŸäàƒÎj§C‡2gÎ)W®œkÌ@$@…&�;p¡9±~   �ðÄÀÞ`\j¢ =2ÔÁ¢A58;–$ �b'àEÐ~Ë-·ÈСCEP¼Ž™‚v¯¤˜�H€H€Ü $Å‘ƒíîË�9H€H€H€H€B"@ÿKH Y �@^.½ôR=ztV×_½ôíÛWà'°K;weËJÍš5¥víÚŽ}ÈågX¼x±Š¾ž+átئM›fe��ýÃ?”Ê•+«kN¢yˆÞ[¶léÊhÕªUÒ¸qã¬|ƒ–;î¸Ãµ<3� �@’bŽ‚Û   ‡@ ü·á ÄC-´{€TÒYhP-é`û$@$Pr`hnÞ¼yVDôQSn¾ùæôqž~{‰¨(={öÌ*v÷ÝwË AƒüVÇü$@$@$Pª $Å‘ƒí¥ú“ÄÁ“ � � �@´è‰–7[#È&°cÇi×®�,Z´(ëâ[o½%mÛ¶ÍÛ/¿ü"‡z¨¬\¹2«.´ÛºuëœmŒ?^ú÷BüQ£F©÷�DóˆAú¨ôêÕ+«={ö”©S§fœ°šO_�¢Ÿ×¯__–-[&5kÖ̪~õêÕrøá‡gE�GFktvüí4¼ß£G×®ÿç?ÿ‘>}úÈ”)S2ò¢+W®ä©²®™�H *I±GÅ‹í� � � äO þÛüá± Ú=‚*Él4¨–$}¶M$@$@$@$@$@î’âȈ�A„‚v÷åÆ$@$@$@$@! ÿ%$�¬†H 0D0¿ë®»²Ê�;Vú÷ï¸^³àòåË¥E‹¶õ!ü /¼ Mš4Q×·lÙ"/½ô’mdv\¿ä’KdÔ¨Q¢m!xÏ)ü| -[¶tÚDÿ>ÿüó¬¼O=õ”œrÊ)²yóf)_¾¼T«V͵>f  �BHЏP|X/ � � �@øbà¿ P5RÐêà Ñ œK’ � � � � �@’âȈ�A„‚ö(,Û    PèáB (I¿ýö›tìØQ,X�Õ�7ÞxC:tèj÷  ãÆ�s¬‚òråÊÉûï¿ï˜gÿý÷ˆÔ+W®œÎ³cÇi×®�,Z´(£Ün»í&+V¬�:u긎Ú{÷î­¢ÒçJˆ\?mÚ´ 1½kåÌ@$@!HŠ8D$¬ŠH€H€H Àbà¿-ðÿW=í‘¡Þ ªÁÙ±$ � � � � � DA )ŽŒD(h�bÁ²     E€þ. �’$°qãFiݺuVTòêÕ«ËÒ¥K¥aÆ¡víµoß^V®\¨^Dr÷Ýw¥víÚåׯ_/x |÷ÝwïCä>oÞ<%’÷’† "wÜqGάcÆŒó™H€H ¤$Å\RüØ. � � �€1ðßúïtÀ´e1T£¤Í¶H€H€H€H€H€HÀ?�¤82b`¡ Ýÿòc     �€è ŽÅH€B!°|ùrATt3A8A{µjÕBiÇZÉæÍ›åÊ+¯” &øª{Ò¤IrÎ9çH… ²Ê!bûÁœõþUW]%wÞy§çv¾úê«ôF#§B³gÏ–N�:y®“I€H lI±‡Í…õ‘ � � Ž@ ü·…œQ3í‘¡Þ ªÁÙ±$ � � � � � DA )ŽŒD(h�bÁ²     E€þ. �’$€èåG}tVzöì)S§N•2eʬ{Ì�9R¦L™âØÆñÇ/ýúõü›K\ÿÚk¯IçÎ�³ê™lݺUªW¯.5jÔPýc" �’$@A{IÒgÛ$@$@$P: ÄÀx Ú#C¼!:´ƒ³cI     ˆ’íyÓ¦ =o„¬€H€H€H É(hwv)h—'k#   �¤  =é3Ìñ‘ � �@üPп9¡C;5'ÿù�ÂÀD$@$@$@$@$@$3IqdÄÀ Âç>ÿÇìÓÍî� � �@<èûM ÚÃ� ÚÃåÉÚH€H€H€H é’bNú]Î<óL_]Á‰k'Ÿ|²´k×N?üpiÞ¼¹T®\ÙW¥-óš5käñÇ—{î¹G¾ûî;×áƒñ°aÃäôÓO—ý÷ßß5?3� �€•@Rî7ã6«ô¿ÄmFØ   ˆ7Þ—Ç{~Ø;  H"�øo#ÃJA{d¨ƒ77ƒê¯¿þ*ˆ<…(_•*Ur˜Î�ŒˆNň_ÿEöÛo¿©¢v�%Ø„•¬L™2YóG´æ€5…‡¿*Uª„…¡¨êÙ¶m[Z´âås…Áå3ŸX#V‘ Úô¹/XŒs‰ºÃø¼£.Ô‰ö礨;K$@$@${IqdÄÀ BA{jµSÐû�|ì;h>/íð 7Ü ýë_e·Ýv ZE(å6oÞ,ãÇ�W6<#"Òy÷îÝK,Úù÷ß/`3f̘Àã;ï¼ódôèÑR­Z5Ç:â6îÀƒeA �P$å~3!V7ÿKˆCcU$@$@$@$@ Àûò@e•$@$@$@9 ÄÀÙ QÐêà EiP]¾|¹Š,…\?ýô“ôë×O7n,pÔ=òÈ#rë­·fDœjÑ¢… 0@zôè!ˆ2¥Ê>õÔSòÀÈ;31x8ü(µjÕ ÅGIõ|ï½÷ª#ž‘pÄ3œ†¹ÒOúè£Ò«W¯tg?üðCA"Ò\ Ü¥K—JÆ "f/ùá”kÓ¦Mp0KB4ܤI“´€wß}÷ŒËéØlŸ;vì˜!0Æ1Ð+V¬pŒ.¿cÇu$÷¢E‹T¯¹ìã�?–š5kª¿ "íûî»ÏS¯Á‚^Ôi—ÌyZ¶l™ân�;”;å”Sä™gž $h‡€›Íš/^,­ZµRo™ýÀø[·n�În^‡¾^½zrÄG¸:~1O¯¿þºÚ€à”žþy5F·ôä“OÊi§�¦„Ú_#áu°½ˆæÝêw»nrÀ\a€—õ�#â§L™b{‚WAûG}$gœqFzìnýÅæ lAÄ»\ ®¾újOëŸ ¬il4pJXsØt€Í ^Ò¡‡*O?ý´ìµ×^^²3 � � � ”@R10ˆPОZ©´ôãZ**[ÐhW]u•ÜvÛm‘˜~Û´wD5©è 6óã9=¬„à°™)Nãk¬¬‡H ?I¹ßÌ�Bø¥£ô¿„ß{ÖH$@$@$@$5Þ—GMœí‘ � � ÄÀÙ$PÐêà EiPµ:އD«Gtj-v÷3FDG¤r3y�'8x!øÕ¢m¯h´‡HúÇÏhÑ·…L'³>SÐnöÂîo¼Ñ3‹\ fÍš%Çwœgœ�=ö˜Œ1"Ýv”Îo;îàk%Ýn@ßccB¹rå2.{™ÏU«V©Sü&sS†Y›4ÀßYÞKóçÏ—öíÛgeÅF’óÏ?_�(à'¡Ÿo¾ùf 1úi‡yI€H€H€HÀ�@R10ˆPОZl´»}âxÝ�@.A»u£qíÚµÕ&øW^yÅ­Ju¢ö;î¸#ÒӲݴ5™öO�!lCM›6µ­ ¶#lGôx<«â~Ë–-‚Mæ£F�Rö»„¼¢P§N�ŒËqwèX �@’r¿ŠP«ˆÒÿjÇY � � � �@‰à}y‰`g£$@$@$Pª ÄÀ Ú#C¼¡( ª¹ž“&M’SO=UªT©¢œqm#2¸Sºå–[äœsΑZµjÉ·ß~+øÛM å^~ùe9þøãƒÃñXÒW®£²Eú¤“Nʪå�9æÛgΜ)]ºtI_Óõ;Eî:ï¼óäšk®‘½÷Þ[•Aù·ß~[m 0EÎÿú׿²Äº¹æ ŽÐ+¯¼R~üñG©X±¢\{íµiG³4úƒ¨ìýû÷Ïëœ9säè£�ÎxÏ� ÝñòW\¡"°oÛ¶M ¼†€Bukzë­·¤mÛ¶ï™Q÷õűcÇ "šW«VM¾þúkùÇ?þ¡¢ÇÙ¥’´[ûsÉ%—ȹ瞫6D`ã6 ôîÝ;k<þøãrÖYgåä�ñ[çÍî´T€(èø,ï¾ûîjm`Êš'\ýõY›t.½ôR=ztF¬¢æa nÚ´IfÏž­DêÖä䨷Û@�HñèC£F�”`Æ JØÑEeÔévê‚ǯ f#   �¼$Å‘ƒí©•HA{^GNp²˜Ï�ÖÎ�;›ÿ±Ïg¹žõ:uêä™3N÷BýøžÄÚ†­Âܰ�«2œ®†S׬©gÏž2mÚ´@Âú|ú3}útew0žKß{ï=Ùu×]‡òÄOH÷îÝm¯ÛÙÇÂ7†�/Ìl•*Uò<�~2b¼ß¹Îg^œú{Æ‹äwÝù¯î{!™úéó&—@Rî7ã6CQú_â6vö‡H€H€H€HÀ?Þ—ûgÆ$@$@$@ùˆ�ÿ6¿ø(MA»X%•5Jƒª�æ¹E‹à¬9òÈ#m£m#²—=ެ«¯¾Zîºë®t=p@""3¢�2­^½:#";ĸˆŽe—† ¢¢�Ax qy³fÍT$ùë®»ÎÑ©«Ë >ðZºt©(/Y²D¬‘ÏpÝÉyŒkëׯ—Ž;ªötBô³;ï¼3£«NŽiÂ!”×ÙÍñy´Û9gŸ{î9ùóŸÿœ…+¨ Ý)òüŽ;¤]»vkÊNT {¯^½2úãùÄí"‚ÇAÐn·IÀi€Ë¼yó2œþnói9ÎŒ²o…16¡èä¥Ý®^§9]·n�Úb]ÓÂ80ÝŽùùÄ…1cÆ(q¾6Xûùé§Ÿª9µnþ@~'a@!¿[X7 � � � hIqdÄÀ BA{jQQÐÎï–| ø´[ÛÃ3äNA pšÚ‡~(åË—wì&ì"ˆHŽ óv§“]xá…Ò§O9üðÃmë@Y´�ïÖ‡z(ëä@Ø^ ”¶ìµ×^‚àvÏ�h ßþèNb#·Õ®¥ß÷"òÿý÷ßÕó1žíÍ„gà(faŽí`¤Äa;1ž¥aó9ꨣTÐ�\iùòå‚ ÷•+WNgÛ¾}»€ ‚ À~™õ47°7mŠºpXóbí3NJDà‰ûï¿?ëT9çÀF|½î`Áz·®ˆü/¿ürGÛÚB …7ÞxCÐÂyä9ûì³Óù1fpsZv ìÏ7Ýt“Úà¡S®Ï<ò˜'ೄãÔuD>p:thº>¬å‰'æüœ@,pì±Ç¦ËàHwlð寤¾ÓÙ. � � �@2 $Å‘ƒí©�íÉüžˆrTùÚÑODµÆ‰[¦XW�ÁéÄ=ˆ§±Q:׉~V8µÂã}öÙ'ž ÍSþrñ3m:oXýÑõAt>nܸ¬®Ø�.g×_½!úFðˆðqr¢ºW­ZUekܨ §+B¬îu.P›Ïo¾ùf©Q£†-n;; n §ªÁ&f&ØyZ·n�ñvØó¢+ÿý÷¥U«V®5DÙ‡ÍöKóô9§ÍýºÒ)S¦¨Í^êÂI�f° /e™‡ì$å~3n³¥ÿ%ncgH€H€H€H€üà}¹f,A$@$@$��øoó€�Ò´û€URY£4¨šO'á·f±jÕ*iܸq�5:¹/9|衇¦£5G)*6E³8Jã³&ëx• â_DTúüóÏUäuˆp4hàX´¸ÑÖ<ðÀŒHd‹/vu¬Ù‰ŠMQ¹�cú“O>…s¥\h;§ßÃ?œ±Û¬Û¯ ÝÍ)ˆúÍ5ç+œŒ:Š¿ÍÛi^¬}Et®ƒ>8#¢[”kÏn¾ìÖŸµÏv‘ÛÌèþn‚ö­[·ªõ·yófùᇤnݺÊiž+a‡Õ™k:žÁ²mÛ¶×ÝÆòý÷ßKóæÍýA´*UªÈäÉ“•ÓÞ®>D£Cþ\Éîsâ¥\I}�³]   �äHŠ## ÚS Ú“ÿ�Qèæ+hGÿô³œ]„u»÷œNHó2VØ[¬Ï«æ³©[vÏøaöG·ï$h‡ ò=�0Œq£¯/¼ð‚íI{nq¶›… fm2À5§u…“qb lf2í …˜´ ‘|Ë–-½ Qå¹öÚkeÓ¦Mé@º i‡Òïã{ó�  ~“Ó©‚~ëa~HÊýfÜf2JÿKÜÆÎþ� � � � ø'ÀûrÿÌX‚H€H€H ?1ðßæ7¥)h÷«¤²FiP5SˆÂ|à 78Ý;EÄÒ˜ÑÌ£#Z–5R”]äqëøÑ·§žzJE,×ÇAÛE su›¶Ý6X!›â{7!³ñÚiœÐæ<¢¼[$uäñ+h÷y{ݺuÊ©ææ1ûê52÷�wÞ)ˆŠ®S”kÏääµÏ÷Þ{¯:æZ'3½› =Èw–¹öLdz¹®½5�~ ºŸ]ôt³>¯lP§É'×iAX° � � �€3�O?ýT‰µråÊò§?ý)çÉ*¥…cR10ˆPОúÐPÐ^Z¾9 7Î0ívÏ]ºÇ°råJµùÉnS¿ŸÑ™Ï‚ˆ€�HØ^ÊÏ�;7}º[ØýÑý˜6mšãÆôùÆoÌ „àu È—ï¸Q‡Ýé{~ú€¼µc“A�:u2Š:­«\õ[í …šÐÀæ{?Ñè�úìd/‚}tøðá~Qªü^‚1ª˜…J�¤ÜoÆmâ¢ô¿Ämìì � � � �€¼/÷ÏŒ%H€H€H€ò#ÿm~ðQš‚v°J*k”UÓ1e hMnÑ´Íü%)h߸q£4iÒ$-”¾êª«"g� @D-}t´Ž´~ÓM7 „ýHDCðkMÖè\pèÂá·Ë.»ˆ)ÔuŠðd·®Ìy0Ååæu=lØ0×%j–{衇¤K—.êègD¡×éÖ[o•¡C‡ú®Ï?›íMœ8Qúö훳^·5²dÉ’Œ£š/¹äM?8:Uþúë¯KÇŽÓ—KRÐî¶QDwÒmƒIA;"¢Ã� ZÙ²eUSˆvÁ9þ~õÕWåúë¯Os2çÑØ÷Ûo¿Ð8šõA ¿téRA„ú\‘í*V¬(<ð€\|ñÅ龘?\03� � � &pÖYgÉ“O>©o¸ï­Y³fຒR0)ŽŒD(hO}((hOÊ7CÉ�#,A»¹éÜ:"ë)Y÷ÝwŸ\vÙeYÆï~/`ß“8 ï/ù‹-kD‡�bË–-Ò¯_?ÛzGŽ™~n¬U«–wÜqé|a÷GWlwº�Ù¹N�:I¯^½Ô e{ï½·T­ZÕóBÈwܰ}™6kã·Ýv›êú´fÍ’r¿™ƒB”�ÒÿRˆþ³Nˆ+œ¶‹{üFnß¾]Ž<òHÙsÏ=ãÚ]ö‹H€H€<à}¹gTÌH$@$@$�øoC‰{5´»3*ñ~ ªp®AèY»vmǾC4|ÀÈ…^˜!ö+�5ín"a7±r!aCЭ[7Ag$3b:œ¤pniç"º#º“5²•�Ž*8åtkr“åùçŸ/t{9~úÅ_D»× \Ÿ~úé´ÙïÆ]�Y~8 uzäñáˆ8æ&G^¿Ú½ŽÝÖÈ£�>ªœ¯:yÉ#oØBl?k5è|™QÕ̼~^!Vã�7TDóçž{ÎO×Õqáp†ëd®M8¿±ÉÃËz±kجÏWçŒÌ^ÖW>õ³, � � �Àÿ\zé¥2zôèÐíLBX‡{t3l±p×÷CxÞÀ3œ‘?üqI10ˆPОZ ´ÇåQ¼ýKÐn>£[‰èçE;]µö¹¢-ÖI$@$@Qà}y”´Ù � � �ÄÀÙDPÐêà ù5¨š‚_§–Mq,òyÈê:‹IÐŽ>[�††cŽ$ˆf�–/_.p2"ÁÉ¡wݺueíÚµiÇÊ ztÆ U>3B“ÕQ$:¹W8j§N�šÃû�']¯µÆøÝw߉A ïcŒ{챇ë¢- A{б›så¶ùÂuð>2í³ùù27Ex©‘Ðzôè‘�ßG׳íÓ§O—3Ï<3]…“óÖknÎp¯õ Ÿ>UÁOæ%  F lA;6Èá„ »ï¾[ ¬S%\Ê*ÀX°ÙÕk¢ =ƒí)´{ýô0Ÿ�°í¹DÈZÐÑ;N´ƒ€yóæÍòÅ_¨gPümµ¡8Ù<ôûv'°9 ês=Ó²?èë7ß|#tPú@¿«¢ï‹.ºH:wî,UªT±-t܇vX–ø Lž/Yk}Å&h7EÍ8’�àÜëÓ§�ú¿Uìo:o­e¬›`,„^�ä#h7ûi:uýΓž3³Ü!‡"ˆèo¦:Èœ9s��Nõ™Ñ¼ƒôÓ-B{�:Ñß%K–Æ«S1ÚÍu`=ÀËçÕºIÜc8š<ð@Ù}÷ÝUÔ–òåËK¥J•äᇖ—^z)�ÝmNó‘‡)h�rN ÷sÇšI€H€H 8„)h·ŠÊ¼ž¾GJVAû­·Þš± Э¿´gâó ‡×ç·µÅ륗@X‚ö\ѯqì~“5p€µ¬��*ˆ°»�ýÑuãdœz§OôÛ&òÃŽôøã�ËI'�”U<ȸM�®Ô´Á©ŸV›˜5�[€kÞO>ù$Tñœ×u’kܰ«8EÄw:1δ-8ÙV`£™4i’ãÔë{%k»à"AÖË”^Îfî)h—+î.\(;wî”ZµjIÓ¦MÃm ”Ô†{»víx´Ø(Ž�ãNÍ¢˜'Fh<},H$@$s¼/�ù±{$@$@$�@´ÇoRéÐN͉W‡ö¼yóäÔSO•Æ�çœI3ùä“rÖYgeqÅÚ‡½¿ÓvIÐ0ë†@±lÙ²A— Ë•rÎfPÐ.Wë†$ü>Ì�;—ß{>ã¾+׉_°û×®][=¾òÊ+޵›'¾Z3F1OèìøÝÓÏøæ³´O4ÌN$@$@± ÀûòXL;A$@$@¥Šíñ›n:´Ss…CÛ¯ð¶ØíXÚC† QÂr$ˆQUý—_~QÑ»!¢A‚p§mÛ¶éOÂk¯½¦ÄÁº ¢3Á Û¦MY¹r¥zßÁ›œü¨Íy0ëö;Oz vŽGí85ø(3{öl%~vJ%!h7•^„÷è?D?˜/�üÌG¾_‰&'¯"|Ói:xðàôÚEŸr­»eóçÏWb·\ÉŒ f Úý~æÝØ…]Ÿ[{¼N$@$@q$ OXBUœ”ò׿þUÝ»˜ Be˱‘Ò_¡,¢àí³Ï>¡ ñÛo¿•ãŽ;N t6îMᔾï¾ûíZ£ŸÂa}å•WÚ Q„ù8k$ÄpÏc&8¹Ñ†Žªê·Þ|!`Óêyç�çX 6»â(w->wGÕªUŒkÔ¨¡êÂ3Æ‹²v©N�:J‰�:½ð rñÅ ®á>Ò¥ïšk®‘ /¼P �¿ùæ›å‰'ž°­õaÓ 6 X„ïØØ ±¢MúKê½v©×©Ô~˜´�–|P÷=î øÌÔë‹sÁçÿ?ÖJv,Bia Úa/Áw·¶}X›Ï‹¸†|ˆö}×]wùž�BÚÃî�Ó ô&¨7ß|S`CÂw·ŸdÚ¡‚Ú�æÜ‹Ý›× ^7“ù;ïÔ†ßÓÛœ§>™6sl^û á6ç&O·ü¼N& g ³&(h—ë‚ Ò6èîÝ» ¢t—)S&ÜF\›ÓÉ £F�R§ 7hÐ c4„â§ãÞcÀ€Yd{ì1Á<˜‰ó”àEÄ¡‘ � œïË Ž˜ � � � (h�ß’ C;5'´‡³0­âteuBŽƒ:H5çÞƒ`D'ó˜cD/D”1ëq™nÂ_-ž/_A s§ûï¿_‰Vt;v¬ôïß?ýwX‚v«SëËq^³@äHk´IkÏKBÐn  ½DCŸM¦%)h¿îºëT/·4mÚ4AtO�&Nœ(ˆŒ§S®u`5H#®h,º>Dƒ…@ QÚu2×µµ ëkÄ)â»®Q׉Ō8jÖçu>ÝØñ: � � ;¡ăÇsŒ:"Ü.aÓ!î_±9Q§ž={ ”‹Ä‹ØÜ©Åì¸?þûßÿ®¢­C4­"®ã=-h·;!§¹@Ì !¶á[Å\·Ýv›ªÛš06ôaÖ¬Y*"l�zó™½Á@×qüñǫͯ¸çÁ†� Ø„Én`�#üUªTQÏsÂCСÊ#‚:Ĉt¬Óã�?.Gq„úóÅ_T›ìÒM7Ý$½zõR/kyÜ×ãY›Vu”|<ß :¢׃1î‰?ýôÓtÕxæ7nœÙvðâáÈ*h¿>õ÷pKF„&^“z¡£VEäþ©¿ÿ×€·‰áóŠSÏÿÞ¦ƒ¹Š•@X‚v'Ñ/¸XŸ7oÞ¬¢ŒÿãÿŒ,LA{¡úãupxþúë¯å½÷Þ“çž{.}" SylVÃw´¶y[[ësšs/›êÑ^«V­²6.Ô¯__Ýè fù®«BÌ‹SŸLŠÉ›ÁvÝu׬)1×!í^W=óEA€Â™ÂP¦ =\®ãÇ�Oû2ÜNË·ådÔQºõ™ ¿Å¸çÂIin ÁÐÆºÖÔÉ´ßsžÜhò: � � 8à}9W � � �@Ô(h�š¸{{th§EáÐö+”ö]ÙtÈE)*ÖËÌúè£e̘1éç’íÛ·+±½˜â¤*ý\�£éûõë§ÊA°�1תUK,k÷­Úÿ”º C÷ãý£R¯�-™±sâ?šú;4÷1|þOÁŠâùßÇœ0kÈWx¬‡lžv¦ß‡ý}ð}…õŠß1ëæè È´²?AÆ…2°·à œªa—ð›c (¦ Ýî·Õì‚6´hÑ"}r¡¾ŽùE¿´½+ŸuU¨y *hw3íAW9ËEA€Â™ÂPŽ› ¿ˆº�à •*Ur´Î�Œ+V´µã»VR|×ãyÏ6HùlºÆ\ë=1X€I’æ¾!ë‰mظ|øá‡{ö»ï¾+‡vXF~¼‡Mó:…9O¨s…(ü^Ö«ç�ü‘ÜñÂgß�xf"  �’&Àûò’ž¶O$@$@¥�íñ›s:´Ss…CÛ¯Pºí¦ Ѳ!� »£!JADE¤k¯½VÉé¾ûîSCpQŽUÄbÃè�”—cžíÛˆ Q·nÝô'Óï<é‚^Ê-Y²D Z¬Ééèè’´ÃáØ¦M› Q×ìÙ³‘<�’�s³$íèço¼!:tpì3¢©4iÒ$Cè�“ ÕË|B eQ¹9°a†Xκ‘혂v»u�H­Ã† s‹yt¹á ­Ñ£G§Ë_}õÕŽÑh­� bíÈ‘#eРA*‚-Öm¾iã÷óÇ‘ � ”Ö{j»#Ù!4Æý&"žë{PÍæÎ;ïü~†!h77ÕÙm4³Š­m¾þúëêT8ËQ‘Í­ÉI¸î&hZo�µG<ý¬C|a�¦®ëÃýîÏ6l¨îŸ FGr´CLŽg$ˆý­‚Ôö¸¯Á½+í8 spÜqÇ©÷`¬ÀûV¡À;ï¼£"óCpxË-·¨ Ö„ME á¾íÜsÏUmáD(DGÂý4"Åë6þ(�E§C-[íÖèì¨x¾ÁúÿROI½ dßœzµL½VyrýÉçÿ�(žÿ}Ì ³!�|„Çz¸X‡´;¹Amx¦½ñƺ1ËŠ ¢¶‹.ºHöÞ{o%ŠÆw¥~Þ5±†%h77Š…ÙŸ|—˜aó‘]zë­·T�¤¨í_}õ•í†&óÞ"ŸuU¨yqêî™F�åh'ðÊØ©~Üç\sÍ5‚ ^Ö?>;^NoôZ'ó•.Îf¾£´# 6(#Z66;aƒ)îÃôÏðè“­0Zl6‚¯›R­ÁtPö©§ž’x@ð<`Mðcà÷›U£JxÔÛ0³?;#a£ž³œN6A=—•ƒÎÿ·¿ýM�’¥OÛõ2>°EÀðÂfm�`KÆF3l:†ïöqØš± 6l´eÚ›±á‚}ñŒp¿F²>�ê>À'°Ë.»x®Áe`+G@&lF‡�áÏþ³ZAçÉiݾúê«ê¹ì‘0·S§NUâv$øšð; V8½ '‰í·ß~é±8Õ‹9@Ýx~ÆI7Ö„y:t¨ÚLéeƒCØóïy"˜‘H€H Ñx_žèéåàH€H€H –(h�ߴС�š“(Ú^ÏÖåQŒ‚vôBÚì’)àÕy^zé¥täC³ ˜:Ú¢õÄ7¹XÓüùóÕ±�v δc�=6CTl'Zò;Oº-¯å¬¢)]ÖN€]‚vôǺ¹[#~›\?úè£ ¸¾^Ò‚vôyáÂ…R§NÛµ€Í§N3A8euzæšOscÚ -¸²6Šï–+¯¼RFŒ‘Õ»Èîvë>µÐʬ†²u27€ØEüƒƒÁI�zÌ2aøl'‚o’ � �@¬÷Ô/¿ür–Üú›o»é®i�X¿‡V:î? ’6�&· Àü´i-gÝlç&hw›§zÝÊÙ]7ýˆl~æ™gJµjÕ\«Ë%h‡C¾K—.‚±6hÐ@ðla¥ë{´7ß|3-ðÀ½Ùgœ!VA:6Öê¨ê®ú#Ú>õÔSÕ½ ´CLQçŸ~*­[·V›{õ‰LƒH�TKp»�ziAûÖÔÿ¦^-R/)Õ1õúͦ/ǤÞ{í�÷ p±×þ¦òñùR÷èL$��|„Ǻ]»S¼ô5ëÆw<ãÁŽ`&<×"ˆ€)3mIº\X‚öBöÑAþùgùæ›o”xë“O>QßË^~'0N»ÓØôø­›Ø½Š­­ÌÍ“Úô5»Íræ\9Í Ä|V!]>ëªPó‚ÍYì™ÉîFkü¾bsš™Ìuh ÐùÝäóùeYp"@áLaÖF”‚vó;ö^�!\Ï• f_ºt©úÞÊõû¬ë@~l„E`˜(’Ów¥×ïf<«àÙÇzrl®~C@ŽÓ¿öÜsÏœÃ{þùçßën ¿¡8¥ Byݧßs¬Z3wî\Oâj·~˜¿ÿ~ž·­uëÿf{AçÉ,‡ÓǰÍSÉÜ~CMß›Y/|õêÕ“#Ž8"ëÔs,ðwÀO�{§Tˆùw›C^' (x_^:晣$  �8  =N³ñ߾С 8´½ žõ)VA»“Ñ7—�Ð)ZŒÃ0<[¾8ÒŸ §¨×ÀC¬kuê~öÙg*â&Ä-ÖdÓï<éú¼–3�œDyóøk¼WR‚v»ùƒáÇw·lÙRªV­ª"� ò ¢~Ø¥’´k¦­|ðÁé.B\tÛm·ÉM7Ý”Ñm;ay®ù´[¯X_É׬YSÕ�õ¹xñb-Fh»„h>pX£�8­kôFlˆÞ`4‡cQsfÍš•Qµ]Ôw»ãéÍí[£a^1nôÙšèHŽß7{D$@$ð_¸¯‚H¿ñfÂÆ6D³ÞSC@Žûk²þæÛm¾Ô׃:»­mYèft5k>}Š]›I#òˆª‡ûˆíÛ·«âˆf‡DÐî·Þ kÑÎÏ9ÁÅÿ—Î?ÿ|µñQÕ­å¬yÜíùË_T´uÉ!V×QëP‡´ã‘[‘´øÜ*hÇæó4%ÝœŠ3gÎÁÚ~ø!ƒ;žW¬uZEî»#ê¿î�幦|ªÌ3©T‚VAû{©¿õ±AØ�i Ú1ÙǦ^�ÿè[¿Ô¿“|ÌŸÿS°¢xþ÷1'ÌZ„òc¸ëׯWô�„eZ|í$¼Fv¿ixßnS3ÞCÐ^¨þ ^ØU×L°=xËéræ)eú}<Ÿ·jÕJýDÐîd낽óh}¶6ÇàU t]j^0'1>îQÌ­ãvõ™ëÐi½º æ‹ðkƒ].Îf’¢´[¿Gñ½ûl×êìÄßNßãn•¹=ÿÂ&m€Å©^œ¢…ç4|ï;Ý—à}ó·0L?ƒ¡íMž�AU»ví,¡9ïýû÷—¦M›fŒù›5k&çž{®tîü_]R.Gòk±š‰ N j¦uëÖ©Ï—�óæ¼óÎSL!†0�ÐE=vÑë}L³’ � �@A à¾Ñöì~ã´3÷‹8f)—`Ýíº›CßË@­÷b¹œÃv‚v”½ì²Ë”(Ã-ù´­×­¹®C°Ø£GÛ,àüÀ¨~ÖäUÐŽ{?œe'h‡3÷ÜHcÆŒQ‘Õ­‚vD˜3£7Bœ~ýõ×+ç¿[Ò"y« BýI“&Ù ÚqNüôÔë/©—]„v·æôõA©ÿdä\šÏÿ)6Q<ÿ{�@æ+NA…Ç-6<ãÙÏÎN�ëÖS·ð ¶)|·Û�²XÛ �ñãÇg�µ³É8 ¡�ì7…êúm·ƒ€ÍÏ«¹ãz°v¶}ÍzàwܨcãÆ�êÔ »{ŽÙ³g ~ì"sÌ12oÞ¼¬Ë¦'èº*Ô¼ ÃN‘Öq âD¬3¡?Gy¤­`δCåÌcÞIÖ.á3� kXˆà_±bEGÛaq~˰×%A€Â™ÂP/)A»9Ü“ãþ¿J•*êt%üÞ:!AÙ[n¹EùjÕª%ß~û­úÛ<1Åî°BP„m÷ßÿþw:xŠn¢zø>�ð{ƒ¾êg_¼ç8eäÈ‘ŠÅî»ï®‚­]»Vù°ךð„aÌ„ßÄ&MšdÝË }××_-8‘ Áeì’“_búôéª�Lw>|í ¡>·ÓL½¶tžrù7pχÓ_áÛÀoNÓßU~ýGÖqÀwvÅW¨ìx~Åï1æÚ žcwŠ]!çß+kæ# H6Þ—'{~9:  ˆ# Úã7+th§æ$ ‡¶iŒs ÛEh·F`0—’a"Ìè~—­]D,ˆoéÄ) 2Dî¸ãŽŒËÖc¶�ÊA¤¡ˆ—H+ºp4E2úšßyÒåÜÐfÿß}÷]%j·&ëxÝ ’Aúi:9� Â0vcíœàæ8!Îxë‘¡Q®=“ªˆÈÍ5׸.[láÔÞc�=²òºÍ' ·íÛ·÷tT+ŒðS¦LQÎUˆë,X�ÕDUè„(©¨ßIä`VGæÌ*Þ²æA@|Nr9jÌ:?üpA¤R87˜H€H€H Žœ\諎€Ž{Ä8 Ús�n'hÇ©.—_~¹šˆ·ð7"šC´#?¢ÔãèpÜ;ø´­7ßõ€g—·ß~[^yåÛ�”æs€WA{®í¸G<úè£U×o¿ývµFÜíaà$æ ê@”ÿ]wÝUqÇ„"Ê‚vDV??õr´ãáiGÖˆòއK}ÌŸÿS°¢xþ÷1'ÌZ„œ„G£G�V'aá™Z'<B(a6Öëï»aC´´téÒô3ª]$Q]ÎNDmÚ ¬mØ=ÿ; »�¢Î²?¹Ä\ǼàwQs�~ï!®Öí­ù0ljÚe—]ÔÛ~Ç­ë²³[áD÷ .œc&ˆ!š4~Ë1× 6L_ *h/ä¼8‰ÿt§±1ÞjÛr³'™ö¢\õ#p‚ÝçÂË d1ÍuúM~ͰË%@€Â™Â@/iA;¾oqª—¹q5׿<£˜ÑÇñ}uõÕWg<»Dý½cn2Bû8Ëú¬d�Eœ`eW1¿·­ùÍÓ´œ6uÛ�Â�“¬`Ë6ìàvï;ù ¨†€š6mÚ$'œp‚ ¤ã4F¿«Ìô3¡µ,ž­1¿Ø�U·n]¿Õfä÷;ON¿ÿ˜ÃÇa=ÝÕÚ�›ÿÈ©^'¿5è”nÇnSC!ç?/ð,L$@$�¼/OÌTr $@$@$P4(h�ßTÑ¡�š“(Ú¯½öZ:3–�Ûq†¦á NQDÉp2Þ™Fب ªÖ¥ýâ‹/ ޶&§ã°u»2vQ4í>BpÃ@Q°]”i”�ñGŒ"²Êž{îéøIô;ONýwŠTem‚qDíÖ }Ä‘Íø¢D´ö.]º¤¯Y�ÇÆ›Aúi:mÝÖ Ñp¾b­Zè7„Ú5kÖÌŠ(fä·¯L'á9"ûßtÓM¶QÐP'¢™C@®Úf;æz´›O¬;¬9Ùí"´á ïús‹H-ø,Ϙ1#£¢™Nn8¥áH@ä•\Ñû®ºê*9à€ÜP©ï9D�8q¢<÷ÜsŽùѼ hËYàÚ9f   �€ð{lÞËïܹ3©ÓºI4NÚ�¢ß"²¢åaÓ T®\9¡ïA�a�¼t¸ÇÐQL½ Ú­üÖpºl‹a¾ NÃ@8Ì‘p_ ñ¡¬å´CÌQ9æ÷_¸Ç¶:ýÁÉ*hÇ}"Ž–Ï%hÇÚ:餓 !RÄý›Uxˆ:±iÑ“‘ìí˜K<ÃéM‡ƒHåT‘…©W‹ÔK Ú·¥þ�z³Ô !á{…Éù�ºøüŸÅóæŽUƈ€ÛIaA»jŠÔñ›ÐµkWyöÙg³ª„H§¥Aä�ï"<·Cˆå”¬ö�'—â1Ô‡ïSÊð;QÈþüôÓOrðÁç V€> š(N9Ã&#ôÿË/¿T§hØm×ã4…Ñ~Ç­ëq:%×õ|xà�*;˜áûmÛ%<Çßyç�—‚ Ú 9/è �½ÎÚql6@z¬ÃAƒåÜHo'`|ýõ×Õæ»¤×!"*#åÚ¸�+R~ÐÏ$Ë•.Îf¾KZÐ;¾{ì’�07W€�åË—g㣴�£ÿv�ŒÄ)ÀÉ#�<"gŸ}vzèè/î)œì½v‚fl¾²n°;UÅ-�‘éA‡¢ ˆc�{ô¶ùqãÆ9.xŒ~œBŒ vÁpr}ZüΓÝï?lóàê4·h?ˆ Ý ªcŽ6Œ['øø°fôóu±Ïa¾åX+ � �@Øx_6QÖG$@$@$àF€‚v7BÑ_§C;Åœíè^¡Zܰaƒr¢ÁA‰y…‘+e:8qˆŒ¶lÙ¢x–/_^ÍjM¦‘Ö‹Á5xo2KºERÇzÀ‘¬Û·oWÒ!`‡£ÿ+AD§*8U­ZUEH…¡Û.:›nýOôÇ…VªT)gw°¦øáµ®‘°®!ð‚?H‚X@N଀q›�c`"  �¤ˆ“ Ý* 3£ÅjÞVaºUÐŽè§8iÅ) Ÿu³#6¯õíÛWUi¿yB”u³£ßzóY8é÷véþûï—‹/¾X]ÂFO�7— Ñ`±Ñ|j×®­ž¸ÿ×I ÚŸxâ �€ ›Ûµkç*h‡ ý£�>R§;�5*Kü�Í`‡„Ó¦zôè¡î)Bx@07ú^ÏbAHÞÕôQ Ú7¥þž�zuL½ðT‘ëXãæñ×óÀçÿ4>ÿX9,’A ‚vDoÇ|3!B5"t†‘ð»Ò«W/µ!ú¢‹.;áš];Ö­…ìÏ[o½¥¾›ÃLè;6HYŸÏƒŒ[÷Éz²‰]?!‚Ã3D÷uJè~ã̰Aíh§�óâÑÞi|Ø<±yófucµnз0B�A<¢æ:%üίY³Æ1ˆ6 " €SÛ0×ëJ. g 3·%)hÇ ø¾…Õ.™6m»Ó3¬åJú„\¿Bi<—à;ßɰ+ãw›Âr%óä]sS¸¹¹Ë܈lW·Ý¦µ’´£x@°/'¼"?ƈà>wã”Zü�+ù�'»ßÿO>ùDößÿœíø´;EÜ·6böÝ܈Ÿ„ù/Ì7k% “ïËäɺH€H€H€¼  Ý ¥hóСý‡+Zìl�’CÀt:[ET…¥› ½Ðí³~   �xˆ“ ¤¬�—_~YŽ?þø €MÿíoSïi‡36¿áô•+W*':¢ Z#µAß²eË´` › q?ã¿uüæ=šU�á·Þ ³¡9"ž#9�Äd�’h=áÉ*h‡øÂ6kB9¢>6u‚ cEwˆÓ!j{ðÍ¡Ý*LGäwœ\dåŽÍ�˜¿ï¿ÿ^5g?Díè+"Í?ôÐCêý©S§ÊQG¥þo1ˆLMý©•«Zо1õ^ŸÔëÁ?ú>(õïÖX #S¯‰©×©×Ï>æƒÏÿ)X´ûX1ÌjK lA;NåÐß�fƒæé}nSñÓSO=¥NûÀÆk»d�¶‰(á¹"»£¼UUèþ€6…•œN ô;nÝ|à;'}I` á6"¾š)A{¡çŸ¹�Žq2œ)Rw0â^›œÖl®öÀó¬Oªq믓€ g ³6JRÐnžÐaŽÐün3£Q›ùÍ >Q‹²ý ¥ƒÌèí·ß.C‡M5ŸÛÌ>tèÐApRª[ðów7jvv,°!zĈê¤0? ÷xæÖ§‰¹­+·±š¿ÿ¸—Ãf<7¦~íV;�ÓxMÛ‚Ù÷$Í¿Ÿ9g^  �h ð¾;ݺu“3f¤ó˜Ñ?½¶$íA¨± � � ”q´¿þúëÒ±cÇô ‚+ÄÒ\ãHqAì"´ã}D‡c÷ao¿ý¶Šn�~ŠH„ï¾û®ŠVnÿ‰'ž(ˆ€ŽSe!ÿêÈï~ë ²‚¾úê«´ c{ä‘G¤uëÖêÄœ¦c‹Q×*h‡8BBDtÅ©8pôC\Þ¶mÛt·n»í6%`Ç58å  K—.U×ûôé£"Ù"å´oÛ¶M BqŒ„cé�=öXÅýý÷ß—K.¹$-fÇuˆu ÞCôùwÞy'Ct�¹=òÈ#¥I“&&º•©7ê¤^¸A{ÝÔ w�þžúÏØÔë',‰Ô«{ê5ê�‹ÿJýÛ2õò©�Ïÿ)`|þò f+�0íˆî‰çmlÀq‹ôùôÓOK×®]]'ß�ï½÷žìµ×^YÏéÖÂø A�ˆfm —ì1#z²?h߻Æ S} š Ò¿âŠ+Ôo©] 2nk=A„÷”›o¾YjÔ¨aÛ§éÓ§gmØBF¯6–BÏ ~ÇO8áµÁ.Wš4i’œ{î¹ê7bAsí=óÌ3ÄôulÃ=ͬY³ûx.ÃŒ$àD€Â™Â¬�’´ã¼_¿~Žs‹FmL‚ ]ŸÚ‰ÓCõ©8¹§^áïW_}UmÜÒÉ´/Y²D9ä�ôu<Ù�de²3ŸƒÝDÞ…Y�öµâY{àßÀfd¯ ›·°ݺéeýn<0ï+q¯€û ·äWÐî%‘ÛOâü»qæu  �è ð¾X½õæ›oÊyç�—³×Mžÿ§ñùß}¡0Gn8)£K—.¾0áY‹…º}ûöÒ¢E ×È›Ö>ýôSIÝNä­ÅñW©REÛ¼y³ú.ÂËLÇWßQú»uíÚµJ�eݨn-ƒÓ(ð{ˆ H:²?º�Õ«W«5~øaµQÈ-aƒ6-¡¿uëbPîdÜÖ! Ä÷?„Ö�ef«ø=†˜ý ƒÊÙ¡×^{M:w¿[¹ DµNÐ�Ï>û¬:…ö -jÄØ°¾�9æµ±ËÄé>z0"Òï˜1crnhÀI-—]v™º—qºGp›^'“…3…Y~íØ8úÀ¨Í¹N ß1p€\xá…ß~íÄ~…ÇnbßÂü_­~û«Kâ»û�7Þ�{ï½WmÀõ“Ìgdë‰Z¨Ç‹Hù>ûì3Ùo¿ýÒM{ù=ðÓϰòbSó×_-~ø¡z¾Dôö\¿ñƒ<Z‹üΓ¹nÝ6bè±ú´{Ù ç¶Æ“>ÿa­#ÖC$@$�Þ—çÇ�¥I€H€H€ü  Ý?³B— C;E8éí­[·ª¨‡…HˆzXµjÕBTÍ:K€"’˜ÌtM8Þ+W®¬z…|ˆð '¸5]zé¥** Rëί£¢�²I   �$`u&#B7„Ö„“ip´<œÖÍ›7·½ŽM~.C¤•o‚ bk4vÔ‰(£ˆj Ñ@ß¾}G�ðÁéû¯Ç\zôè‘Ñ<îÓ ø8í´ÓdùòåêßÏ?ÿ\åA”SªçÏŸŸ×PoË–ÐC‹­7ˆ Ï]ˆú§ÿ¼yó²Pj�ÚG‘qÍI¬AFÓ¦MÓy!PGÄc´aMaBpxÆg¤#âºUÐÑd³fͲúá*„ˆÖ„Íž‰B4ùñÇ«ùZ³f�Ê‚9*tžß IDATÀü¢Ï‡"B°Mª”zaâ¯N½0aP9n¶ä«ÿÇõómÊÞ›zoLêõ™]Å.ïñù?(éÏÿÖ‹<‹Cˆ ;DGø~Ãol3v ¢ëŸþY]¯P¡‚Êï”÷›o¾‘üQ‰âñ<�Í?µjÕ’J•ð•eŸ Ùk‹›6mR§bà_�Q¶|ùòªŸ8±Ã*¸÷3¥AÆm­'ž@üöí·ßfD»E¿4h¸_~Æ`æ�j^œúˆu‡ gúžDçÃ=ñ:*p®1âôŒcãÆ�²ë®» ¢ ç;×ù0eÙä p¦0sìWÐn f�ze÷}â×NìWxì&ö- ÁÿÕê·¿(‰“»ð i~{í«)h÷ËX·ƒg&ës[\ív\þýïË‚ dÈ�!¶õfq]Öï<eZ‚ö }-æù÷úYa>  �ðð¾<<–¬‰H€H€HÀ Ú½qŠ2Ú)ÚIwh#Ôƒ>X�uÕ¡C™;w®'gTA:ÀJC'àtÌ5ÒQä-ÇLTA¦#¡E±î�®[·né®x‰4:0VH$@$@$@@ì‘Äx+VÌ…PW�MªôaC)„ñfäBˆê ´€XuZË!Z/*šâÄ õvºÆ�>!¡_©9‰µ#ã£�>’-[¶¨2Øhi£µ/d"Ÿp‚““€ÓË š„�ìÀÝÜØîˆæ§Û³Ö©¯A¼×¸qc}©Lê?SR¯sR¯ÇS¯^©×ï6}©‘z¯�©×o©”œ¿zé³C>ÿ§À$ýù?�õÁ¢$@$àH¿™ãÞÑj5j¤6<Ø%ˆûÁL8e›Ú˜H Ž(œ)̬ø´ç²K[{A4ìÂÖ 2~Ŷ~…ÇÅ&hdžgsC·fˆ�Äx ì¾ûîêY ϤxFÄ©(/½ôRuX‚vœ«Oõ@åÅ$h×0ð � ãæ©)ØH>mÚ´t”v¿ëÊïºÕý)&A{æ¿0ß�¬•H€HÀŽï˹.H€H€H€¢&@A{ÔÄÝÛ£C;Å(éíȸqãÜWC€Åh| 0ÌRWÆk¯GZŽ–Æ‘Ó0‚ëź{ê©§(h/u«“&  (mŠÍ‘�Óñýí·ß–¿ÿýïÒ¦M5eƒÈ~©?Wý1�Ï¥þ==õ‚h½Ð‰Ïÿ¥àù¿Ð‹ˆõ“ ”.8u>3°N>>|x ØXûôé#S¦`ßVf3fŒÀVÄDq$Pl÷›qdh×'¿‚vœâtê©§Z7‚Úµ}ûö2räH±žåWìWx\L‚vlBêܹsÖ©XÏ?ÿ¼têÔ)} ˜ §hàÄ)�Üí&L�~ýú¹.ÇÏ>ûLöÛ�@ÿMÅìS‚xýœs°/ù¿iß}÷U' áô$¿ëÊïºÕíÆAÐ^çßu±3 � �@Þx_ž7BV@$@$@$à“í>�E��íä¤ Úá`1b„«!8Èz;è ƒdâĉÆã õ°Lü¬[·Nžxâ ¹õÖ[Uô-»!û5×\#GqDÖå(ÖÝc�=&gŸ}vºm:Gã·ŽØ#  HD3· '¼ŽÆxkA¯åâ–¯$Æ_lŽ  ÄAªW¯ž<úè£*’­Å ²2u©Ùs‹ŒµG‘øü_ žÿ£XHlƒH ô0Å�Ö‘CyÒI'¥£Òâ´’[n¹Enºé¦,@úaÃWÆ K<Ž´¨Ûýf±Àõ+hÏg\~…Á~…ÇÅ$h7Ç®óçÏlÈ•.½ôR=zt:‹)hŸ9s¦téÒ%}~ƒ¡C‡ºNêÑ›|‘¹˜ík×®• ¤Ç\¿~}Y¶lYú/¿ëÊïºÕ —„ �óïºÔ™�H€H ¼/"«   ðE€‚v_¸"ÉL‡v sÒ푬$6’h6lPÇJW«VM~þùg5Ö:uêH�5JtÜ8îÂ{ˆÊ~ÿýwÙm·ÝT™H€H€H€H L§±Ÿ6àè^¹re:r›Ÿ²qÉ[Rã/6Gž//¾øbyñÅÓSg½�¶ÌçüÔÿ;¥^¿E4Ç|þçóDK�Í� $…lAMš4q t;ÌÀ�•]œ’SD÷¤pâ8ŠŸ@±Ýo q ÚÛ)?Bé dˆ×Ï?ÿ|× D°­wíÚU°‘I'SÐnöáä“OVùÝ6nßÿýêùH§¨íØ”ýé§ŸÊŠ+dñâÅòã�? úd£ú–-[”8ÏöH¥)B{±ÎxŸBÖD$@$Þ—GA™m� � � X Pпõ@‡vjN(h�ßÂd�H€H€H€H€H ny䑌Óaüô‚¯�?þ8¹ÍOÙ¸ä-©ñ£#›@§N�*·Ýv›Óôý-ua|êµ=Âùåó?Ÿÿ#\nlŠH )fÍš%Çw\àá@ì‡èì @! F@ ï7#À’w´ç�0]�Aû“O>)g�uVº,NØíÛ·oÎÎ,_¾\Z´h‘‘Ç´ãy¶iÓ¦é<^žqÍÈö(µ ý£�>’fÍôY¢6™=5Äœ“�Ÿy‹bŠÐ^¬óÞ§�5‘ � DA€÷åQPf$@$@$@V´Ço=С�š Úã·0Ù#    ˆDvÛºuk nÁ¿Ë.»*—B%5þbvd@Àñõ×_«gÎN�Œ]¥ê©×�=Š6ñùŸÏÿÑ®8¶F$�8u‘xý¦Áƒ˵×^K1»_pÌ9�b¾ßŒ–�)h÷Ë%«)&îÞ½»`ñ]”ñG}Tzõê•®qìØ±Ò¿Ç~ûí7騱£ ²»5™‚v3:9òΞ=Ûúœ“Õ†Ý)_Q Úíú}â‰'ÊÌ™3¥\¹rž' ÏtW^y¥Œ1"]ÆŒRïgžPI1 Ú‹uþ=O03’ � Ä‚ïËc1 ì � � ”*´ÇoºéÐNÍ íñ[˜ì � � � � � €@R10ˆðùŸÏÿüR! �À6nÜ(Ï<óŒ 2D¾ûõ\rÉ%2`À9à€·Ç‚$%�¤ÜoFÉÌK[´{¡ä-Ϻuë¤eË–éïßý÷ß_V¬Xa+È^²d‰rÈ!éŠqR¢”W¨P!«1;‘¶ÎdÙý†n�áÇgÔ½xñbÛ“ÈÌÈèº�“ ùçÌ™“îçÞ{ï�× !ÖÁÞ{ï½rùå—gŒ¿[·n‚÷÷ÜsO×Iøå—_äž{î‘ë®».#ïŒ3¤k×®é÷üÌ “ ý-äü»N3� � ” ¼//ÓÌA’ � �@¬ÄÀ�ÿ‹¬¥ü¢C;Å�‚öüK“ � � � � �@¡$Å‘ƒŸÿùü_¨�)ë%(e¾ùæY»v­lÚ´IêÖ­+ëׯWöÕ=öØC6lȈì¥l=$a¸I¹ßŒÛ\PÐÞŒàä§Ã;L–-[–®tÒ¤IrÎ9ç®ýüóÏiaöW_}%–g•� F®Y³¦úQÙ!D¿è¢‹2ê´öxàÀ�Jð]¶lÙôÛË—/—-Zd ¬~ýúòÄO(Á}ÕªUeÆ *úyŸ>}l8 Úqþì³ÏN—éСƒÌ�;7£ý D!HoÛ¶­íX/¸àéÙ³§4kÖL�ª¦£Þ£Ì—_~)¯¼òŠ :4«iô|k”w?ó„ ‹MÐ^Èù:·,G$@$�,¼/OÖ|r4$@$@$P bà¿� í‘¡ÞP”Õà½dI     (½’âȈ�A„‚öÔLjÚKïw GN$@$@N’r¿·ŽÒÿâW¼jÕ*iܸq™“ÈZg0…ÊnùÞ‹;vÈÑG- ,°­z·Ýv“�?þ8-XòCè}î¹çJçÎ�Õ5§º�ÆüÒK/©Èæ‹-RYœØ™sؽ{w�È] ÌóeŠèéíÚµ“Ï?ÿ<ߪ"~lØ}÷Ý3êò;O~×­nÌ,¶­[·N÷%H½^×x¡æ?ïIa$@$@‰ ÀûòDL#A$@$@EE þÛÈxQÐêà EiP ÞËâ(¹zõjY¸p¡:rûöírä‘Gz:ª±8FÇ^z%`®ƒC=T5jäµ8ó‘ � � � �@�¤82b`¡ =µº(hç— � � �€I )÷›q›Ù(ý/Ó§O—3Ï<3�`ìØ±J í”ìíO?ý´c4pD놭{åÊ•ªÊ¨íhÓ)[Çf Ú7nÜ(íÛ·O÷7×Ú·)S¦¨Hã;v´Í�3F  ªAt÷K/½TÆ�ïºä^xá%„?è ƒ\Ù™ãã\sâÚ¸M†Í›7Ëm·Ý¦^A¢Ö÷íÛWE£·K~æÉïºÕí¹ ÚƒÔ A{«V­2æÉŽ¡æ?è|° � �@²ð¾øà©^½zúÒ¶mÛBô+¯¼Ò¶úN�:©k'œpBz܈b3fÌÈ(3iÒ$¹à‚ 2Þ›?¾<øàƒŠµ5µhÑBm&€P¾fÍšâuóÀÌ™3¥K—.éª5~Ô¨QŽsâ•™]>þ±fÀó¹çžs­êøã�—~ýúÉqÇ—ÁØ© ×yò»nu{&+D‹‡]§ õšÚÝÖxØóï: Ì@$@$P*ð¾¼TL3I$@$@±"ÿmd<(h� uð†¢4¨ïeq”t‹Q£`/ó%ä(Ë|Ûdy    �dHŠ## ÚS Ú“ý}ÁÑ‘ � �@I¹ß 2öB–¡ÿ¥0teüÛo¿U•—/_^jÔ¨!»îº«cc[·n•Ï?ÿ\vîÜ©¢Šãï=öØCêÔ©ãXfÆ ê¾QÛ+V¬(•*UrÌ‹z·lÙ¢ò£?UªTÉÈk Ú!ŒŸ0aB䛜€èûëׯŒyÇŽjÌþ… ¿nݺŽÑØsͰßy*Ìj)|­Å>ÿ…'ÄH€H€üà}¹ZÌK$@$@$�øoƧ:(h÷„©d3Ñ Fh÷Ά݅ *£h­Zµ¤iÓ¦Þ Ç<'í1Ÿ v�H€H€H€Š�@R10ˆPОZÿ´á—»L$@$@&�”ûÍcò]=ý/¾‘%²Nóm×®]zl'N”¾}û&r¬T6Î?W � �€¼/÷C‹yI€H€H€ ÿmÃðTíž0•l&TÃã�#¿üòK)[¶¬j7jÔÈÓÑ‹áõ xjZ»v­4hÐ@u¸C‡2wî\Å- ‰‚ö$Ì"Ç@$@$@$@ñ"�GF "y Ú?üðCùé§Ÿ¤B… rðÁÕs Ÿÿãõ¹foH€H€H N’r¿'¦è ï¿â6#ù÷ç£�>’_|Quý©§ž’;ï¼Sz÷îíX16“vëÖMf̘‘Î3vìXéß¿þ�a ‘àüGŽœ ’ �@©#ÀûòR7å0 � � ”8�øo#c@A{d¨ƒ7Dƒjpv,œÀ‚ ¤}ûöª‚îÝ» ¢Û—)S&x…1*IA{Œ&ƒ]!   �„HŠ##‘À‚ö­[·Ê]wÝ%“&MR«ªN�:jcn�5Šf•ñù¿h¦Š%  �È $å~3rp. òþ+n3’¦OŸ.gžyfº¢úõëË’%KÔó�]zá…äÏþsúRõêÕeÅŠé€?ù÷ˆ5DI€ó%m¶E$@¥“ïËKç¼sÔ$@$@$P’bà¿�lø´G†:xC4¨gÇ’Á Œ?>�ä”SN‘§Ÿ~º¨"æ9íÁ×K’ � � � ؈ґQÈä10ˆ´C rÁÈ÷ߟž b´ósF$@$@$@NuÚoÂÉ5LöJ‹ÿ›?·oß^�eP®\9©ZµjAêRéúõëe·ÝvË(Š¿´§]»vR¹reu ùî¿ÿ~¹îºë2ò^zé¥2jÔ¨ M³L pþc0 ì � $œ@”và„£äðH€H€H€<ˆ�ÿÖcOóÏFA{þ ^CÜ ª¿þú«ìܹS‰›+Uªä:~�+V¬(0nsúí·ß/0ÀÃJ•*UBζmÛG["yáJ£•˜GlöìÙS¦M›¦Æ·„[>‚ö_~ù%=Oz®ðYÈ7aXS`Æçu¡N$ôu2‘ � � � Ž@ŽŒ("�ÇÀ â[Ð>uêT6lXÆä6nÜX6nÜX´Ú ·RY3 � � �@±  =ÜŒ›ÿ%ÜÑý¯6lþüöÞl§jÿÿÿü R+ÓÕi@è¨dÌ2ù"2 9Žñ!We8JœŽŒQø:dê›ËØ`Hƒ¤(™Iù¡ï•J%…Bÿç½¾gï³ïõìûÞÃsïýìû~Þëº\¸ï5|Ök­ýpÂÉ®’ � � ¸ ÿ­ +““%zêTû~yvh'O4j sCuïÞ½òòË/«gΜ‘>}ú„ˆr‡èãÇ�—'N˜`°ñ7`ÀéÔ©“àF#¡ìŠ+äÅ_”­[·Æ€;v¬ 8PŠ-:`D탘ýƒ¤gÏžrË-·˜vÄë?„Á6l�9sæÈ«¯¾cwÕªUeäȑҦM›¸¶Ø]¾|y&®X¬^½ZæÎ�+[¶l1ëKØÖ±cG©U«V\!ùG}¤ÊýÁxýáHÈc9}útÉ›7¯ÊwÓM7 ëHF}ø7ÆÚš0n¹råÊT&ÈA Š›WAû'Ÿ|¢8c>[ç¿Ñ÷‡~Xzôè!wÜq‡'�5kÖ¨ºõçcß½{wiܸ±9VN•óÍ7²víZõìášTk³zß}÷IëÖ­¥fÍš‘<œàÔ?~O$@$@$@Q&´##¬äØñüþÿÄOÈK/½$x7ÃMSx§�|øp¦èÚÖz/_¾¬"t#*»‘táuN´ÁMçzîÜ9Åü§Ÿ~’ï¿ÿ^J”(!•*UJø<«ÈŸ(Ù‰ïÝ”óÿŒ%I€H€H€H gÓ‘AA{â9$Ÿ gs˜ïÿA÷…õ“ � � $—@˜ëÍäZíÚ¸þŠöøÐ:   ˆ®Ë£6"´‡H€H€ÒŸíÑã,Gh‹^—Ü[憪.�F4é±cÇÆ5VsÛE'·>þ¼Ô¬YSöìÙ£>Žº ÝMÄëãÇ�Ëí·ß®Ðñú¤su{Ê”)òÈ#�˜gÏž-}úô1ÿŸÓíAqÓíîŸÎÿäÔ£¥ë‚v=â>¢­CHž7oÞ„Í]¼xÑ6zº^Ÿ[6hLŸWË—/—¶mÛúé6Ë� � � � ü›@˜ŽŒ ÛØÉòû�|‚žða¾ÿÝÖO$@$@$�\a®7“ky´kãú+ÚãCëH€H€H€H j¸.�ڈР Hð߆™ÚCCí¿¡07Tu�´. Ö{á&:¹µLª Úç̙ݦn IDAT#=ôPÂÁsÓ'¯Œ�Êå4A»Ó ¿ÜÜÚý»ï¾“S§NIîܹUSˆvÁ9þ¿aÃ�pÆHº ÑØo¹åóû¬æÐëƒ@·# ²¿õv}òæË—O^|ñEéׯŸù•›þûÿ Æ’$@$@$@$�3„éÈR°�  Ú3™ßW˜H€H€H€HÀ$Äzsÿþý2oÞ|¸ ª»õùóÒ€^Ÿ—²z^§ç<+u³, � � � äa:2(hO<«‚äô|öúþ´=¬ŸH€H€H :’µÞDàì»Î�;WŽ;¦:Ø´iSõÿœ˜¸þʉ£Î>“ � � �€ÉZ—û·€%I€H€H€r Ú£7âYŽÐ½.¹·Èë†ê’%K¤K—.Ž èâhð*èö*|v#þv4< œà^ûSÜôÉO½¨[çÛ«W/AÔx#·SìP¹37y²0 ®‹†ÅÍNÐýÑGI§N�äÈ‘#®íµfÔíË–-“:˜Yf̘! ðU·Ý³ê»¢Œ‚“&M’!C†d¥ –%   �O LGF�‚ílˆdùý?H>AOt¯ïÿAÛÃúI€H€H€¢C «ëÍõë׫[&±Oi¤«®ºJÚ·o¯n)-S¦Lt:¢%–õoˆ­²)    �T'M…×tÅWx-Âü$@$@$@$ ð߆6 ŒÐjÿ yuhëÂÙx-#šúÊ•+%wîÜf¯b¯Âg7âoÿ¤œK: À½ö-ºé“ŸzQ÷Áƒ¥bÅŠfÇz÷î­í†ó©?vDÜŒ™›<δ³ž#,nº }ïÞ½R¥JÛ@”~Ûm·Éu×]§Æ!oÞ¼’?~y饗díÚµf]Ю÷%«"r½¾¬ÐvºY!+u³, � � � äYyá¤`;"Y´?óÌ32kÖ,)Y²¤¼ûî»R @/x³5¯×÷ÿl5–�“ � � „JÀÏzØW¬X¡þÑØa4"²CÈÞ¬Y³PûÅÆ(h�â¨Ð&   ˆ> Ú£?F´�H€H€Ò…@ü·¡¡¤ =4ÔþòêІþU«VR®\¹„�Ö­[W&OžlFûFf¯b¯Âg7âoÿ¤œK: À½ö-ºé“ŸzQ÷Î�;¥jÕªfÇô¨úNý±#âfÌÜäq¦�õaq³ Ú/^¼(Mš4QÂkzíµ×¤Q£FqÅ0«W¯–ûï¿ß,â$h4h�L�:Õ<œà•Öš5k¤uëÖf1DïŸ2eŠ\¾|ÙkU’/_>%Êg"   ðOÀ�ÀÈhÍkd Úÿ3NçÎ�“Í›7›µ1�>ú¨œ‘³ � � � �€OA92ÂŽ@� O‚öcÇŽÉÝwßízÔ^ýuùãÿè:ØÃ|ÿ»oa·ççý>lS©=§ý”Tê m% �T%`·Þ„p‘Ø‘‚v#aòÁTbö«¯¾ZÝXÂdO€ë/Î     /‚Úöbó’ � � ä,ð߆œ‚öÐPûo(Ì U¯_¯Âg7âoÿ¤œK:9`½ö-ºé“^ïM7Ý$ûöíK(fþý÷ߥgÏž²`Á³cVá5>Ôë�1c† 0 !ˆqãÆ õFJ%A{Pܬ\õ¨øú!;¸{÷î•*UªÄ|¥ Ú�?.%K–ŒÉQz… ⎄ò¸Æ"{$ôûöíR¤H9yò¤T®\YNœ8a–wªÏù a    ¿‚rd„�<"ží~Ç+ªåÂ|ÿ�*ƒdÙåçý>Ym§J=Øwضm›\ºtIŠ-s[ï#´§Ê¨ÒN �t&`]oâç·!d7úŒhìþóŸÕk` |OA{ü™ÁõW:?5ì � � � $Ÿ@PûÀÉ·”5’ � � ¤ �øoCCIA{h¨ý7憪W‡/íþí˜ NÑÔOŸ>-·ÞzkŒ`yÿþýR©R%s2-[¶L:tè`þ_¼ë³Î®N;A»ý»cÇŽçu¢(åþgxü’ú|L·Dó|É’%Ò¥K×L/^¼( 40EçFA]С¢ëÏ›7Ϭû©§ž’Ç<.€£G�*»‘Ê”)#{öìQ‚v¤ÁƒËôéÓÍïGŒ!&Lp ä™*Ï=÷\¦ÞÙEvß´i“¿[Óúõë¥iÓ¦™Ê£î=zÄŒWïÞ½õ/ÉÍרQ#¦,"3µiÓ&.m½L¢9ãcÈX„H€H€H€r,�tqdD`C„‚öŒ§ˆ‚ö¬ÿ(ñº¿‘õS¯ÜV·n]exvdO=j´˜H€²�€±Þ,Uª”|ýõ×ÙgˆË–!hϪh>«å݈îÃô¿¸DÇl$@$@$@$@&�.ûÀFLÓH€H€H€4ð߆6&´‡†ÚCan¨zuøRÐî_ÐŽ1ñºuëäÎ;ï4'ȹsçä™gž‘qãÆÅL;�ôÉ“'¥råÊ1QÜÛ·o/Ï>û¬Üpà fyˆÑ;w{ëÉNÐ~þüy©Y³fLþ¹sçJ·nÝß�={Và8 :Ù Ú“Á-Ñ<ÿòË/ÅòK@uqРAêp�qP‡¶oß.}ûöµeŠ2”)S¦ÄD·‹Íc‹¨ð P‘î>ûì3yøá‡åÍ7ߌAlõâõÕ«WÇä?~¼jß-ïÔ©S‚~ÃfkrŠêô³~   HéâȈÀ†ííYÿÉàu#ë-¦^ ³fÍ’þýû+ñ7°råʸÚS¯w´˜H€Ò�€u½‰ ÿú׿Ô¥‘þô§?©Û+V¬(¿þú«üöÛoêþ�µ…ñYXcŸ3ê 7dÞxã�ÊL®¿¢>Z´�H€H€H€¢A ]ö�£A“V� � � ¸!ÿ­3“’‡‚ö¤` ¶ Ú“ÇWwhëa?o]üm'�'Ì6zÖ¨Q#¹û‡~°�ö�é»víR‚g= ³³ŽŽ†  ¢µÙ¥°¢zÅÍiœ‡®è ‘Û‹+–IhÁ;DpYòWªTIºwïnFÒ×�èùwìØaË"õ‘#GfúÑôëÔ©#GŽÉô݃>(ùóçWsgëÖ­™¾·‹úŸ¼§�5‘ � � �@Î"�.ŽŒlˆPОñèDEP…÷]ˆÑaïNÉÈ�|ùòå ý–/«}Nï}‰ú‚ÃÈøsùòeÕwô%éÂ… Š'Æ7|~ùå—˜¹‚1‚½nlÀ�øåË—«ì8¿hÑ"óF07u¸ÍO£}£~°ÅÏâ‚ º5�ùH€H ¥Ø­7q³$„í¸µÑHTòç?ÿYÚ¶mk~fwûdÐ�ÇÏeCPïïd‹ëõ!‰úˆß™´‡é š9ë'   ž@ºìOŠ-� � � $‹@ü·ÉêŠc=´;"Êþ an¨.[¶L:tè`vÚ)z³]„öD½àl­^½zB1u�Ä�í^û[á°¿ë®»bú¤3ÐÛ]ºt©>|Xžxâ Çî–)SF ¼¿þúëmó~÷ÝwJ oÔU8lØ03fŒÔ®]ÛŒ,/ ["1yv Ú“ÅÍIØpúôiuíº¦x^,X „»C3fÌ�˜CsèÐ!U¿õ A¢q›LÿKt)Ð2    ·ÒeØm™�H€H€H û DÀ ÚCCí¿¡07Tá¸mÒ¤‰iìüùó•#4^úâ‹/Ì+9‘ŽêiÓ¦Å�èñwýúõ•@ ÎÑ… Æêú§f_NäÖ­[›_âJVˆÑ�äµÿ(§Gh·ëSùd\a;ÊàB… 1CðòÚk¯Ù:ó­…Q'þ@ÐnØíX93� � � � 8HGF6D(hϘmaÚ—,Y"]ºt1çö¾}ûTDr×%¼îÞ½[í@lì&?ÞµkÔ¨áø%3ƒ[A;ÞÍ ~Æ^†›„þ¯]»VÝ”•(a¯`Ĉ®êÅ¡qÔ‰w÷x xÛµkçêà5ê@ôõ)S¦H©R¥bªÔÇ=^{xg†pÞ„ëåôïŒz’ÍSoÂú’%Kªéìn*³ö{›6mR‚N& HnÖ›8h†(åø½öÕW_©nco¿°'ÈŸ‰™gB˜þ—t˜‡ì � � � ätnÖåñeÇÍI9}¼Ø  Hð߆†‘‚öÐPûoˆªþÙE¥¤“#ýÔ©Sòõ×_ ®XE¤o8à\À¿½$ÛáÐýùçŸUYDxƒc<+Q¸ÑçÛo¿Uf ¾k®¹F .ìÅ,ßyÃâ–È@ƒ)®È½òÊ+U$:DËOÕã  Æ×ÈãÚ÷D Qö¿ÿþ{�ã ïÊ–-+… òÅŽ+Ô‰úp�/^ª‹)"%J”P}`"   H>�tqdD`C„‚öŒé† Ýú¾‘öìÙ³åñÇw ãéA4wÀ®\¹²«›§ð~‹HåAж>ÙNï“È‹w'DG7Ý{ùÉ€(¸ˆn—ð.Ö´iSÛ¼µ±yófu›—žôÛñÜÚiw»¢ïwíÚÕ±ŠzõêÉ;ï¼c Ú�n¼ Ч>O!pÿûßÿîzÌÊ—//û÷ï÷¼¿âˆH€H x]oBØŽˆí¸�ÃH¶ã€nðdú?ô¿p&� � � � x!àu]n­›‚v/¤™—H€H€HÀ ÿmhƒAA{h¨ý7Ä Uÿì¢RÒ�#=*¶FÉr‹ÒhÐ    �DÒÅ‘�  Ú3&ZØ‚v}nã–©V­ZIÁ‚‘ÁÑÕ¸åËî9xúé§¥[·nR´hQu ÿŸ7o^LÖuëÖI³fÍBûAâô> Æ={ö” ÄØôàƒÊO=æóáÇËC=¤�ÿøã�òÖ[oI¯^½bòà;ˆ¯­¨/^¼( 4È$ŽŸk~ܹsgY¸p¡äÊ•+èi¬êwzŸÜ¹s§T­Z5ÆÂû÷ïoknÁ‚°‘æ�4lØ0ùÇ?þ“ÿàÁƒR±bŘÏâEs‡¸üÞ{ï�©Bø�šåíêÛºu«Ô¬YÓÖÎE‹©ÃF²‹ÒŽï¾øâ ¹ñÆcÆe�ŸezåN‚ö xÆ´ÇcŠ[ÊêÔ©3WuQ(��� �@²ºÞÄá§eË– ®áVN$¬/qà QÛqkhNL9ÁÿrôèQÙ¶m›Pá–ØêÕ«ËÍ7ßœ‡›}Ž8Ü„²˜«¸õè¾ûîNļEÂZ¯yóæ9öçUć�æ‘ �@Ž!�ÕuyŽÅŽ’ � � $�@ü·Ië‹SE´;ŠÀ÷9aC5˜5ÁÉ‘hã)\9¹¥ðàÑt   Èa’éÈ0ÄFÿýßÿ­÷#U®\Y ‡ñÿ"E`C„‚öŒ�Í.Aûš5k¤eË–¶SkÉ’%Ò¥K—˜ï&Mš¤"dÛ¥½{÷ÆãQïÊ•+%wîÜALÝLu:½O>öØc2qâD³Ü AƒdêÔ©qÝÈøñÇK�5Ì2vbñqãÆÉ¨Q£Ì<‰!˜·nÝÚÌ_¾|y¥=Ož<ê³Å‹K×®]ÍïÁqõêÕqí´tïÞ½;Ó!D3·F`G½«V­Š{àÀIÐO;AûŒ3dÀ€qç‘>N-Z´PÌš{¡Lp6B$�# $k½‰C“8�öÜsÏÉW_}¥XΙ3'Ô›T¢4€9Áÿ¢¯'þùÏÊСC£4 ´…Ô�›:˜$êÕ«'6lP븎;šŸãðâÆ�%þü¤F$@$@ÙB Yëòl1ž�’ � � ¤$�øoCãFA{h¨ý7”6TAçܹsf”ÿ´ìKÂ|å•W&»Z×õ99Ò]W¡ŒaŒW:r‹ÐÒ    �$H†#BöÉ“'+G¾SBÍfÍšIíÚµ•À=Y5#°!’r‚ö‹/ª(§gÏžU¬ÿÇ¿!Â�—�ã0ßÿõ÷­›nºIöíÛ' °5S@#š;„ÒÖ(ßÖ‚ˆ¨ˆèŸFDs'Á´Óœ÷ú}¢÷ID[¿í¶ÛäĉfµÛ·o—»îº+a3vbñåË—KÛ¶mU9ñLZ£¸>|XÀ6^:yò¤: ‚wlÜÄP°`A™?¾.\XÁç°óêûï¿—%JH¥J•Ú9xð`A¤w#Aà]­Zµ˜2É´Åëã/â¼µszß €‚�/¬Û¼ÎUæ' ·’±ÞÔÛúðÃÕÚâöœšÂ\ecî³gy¶ë–€~ÛÖÏx×(T¨�ªbÖ¬Y17)á¦$N�w»�Ûv™�H€H€üb]îÇ–!  È9"à¿ 6í¡¡ößPNØP�Þ½{˼yóüƒJP‘pUavEãJÇ ã0Æ ÑûÚ·ooŽl¢ëß™8¬”H€H€H€H€\ÈŠ#bU·Böxæ@D �{Íš5³½="¡ Ú]¢;‰Ò‘ÿý÷ßw9+¼eƒ÷–[nQ…²#B;¢Š�;6®ÑºHØ)ê5nÀ|ܳg�ª3J‚öƒJÅŠ;:‰ù­P&L˜ #GŽ´}OÕëE´uÈ›7oÂÉ€CFDvo³Æ>·ncЂö x¢wú~ ö"E8‘xéøñãrûí·›ž{ÉCÖA$@v²²ÞÄá9&{9Áÿ’Žþ ÎçhÀû˶mÛäÒ¥KR´hјµ¶“¥?ÿü³ºÉz(7Yq^¾|YZµj%¯¿þºYݺuërìÍNLù= � �@°¸.–/k'  ÈL þÛІ…‚öÐPûo('l¨‚®‹ž9s¦P Jf·ó27ŒÃ¯+VPÐÈÁJI€H€H€H€’MÀ�#ÙEž“™Ê”)½ÝKÝØq%hGôïDÑÐ�¾ûæ›o¼`ñ�·k×®*ª ¢™¬ÿ·þ;_¾|Ù¡}öìÙÒ§OŸ¸}õõ:•í^"xëï÷ˆ¼�÷c$Dc7%àÿAíE <¢¢Ÿ:uÊ<¸�èñÆã ÿ† dôèÑæX†-hOOt@ç 1ûC=”ð™Ìî¹çû ’ �€?ëM£J ÚãÃÍ þ—tôOðF´àݶlÙ²Ê(¯¦ôØ“&M’!C†dê ¾ÖÆ{/Ï)R$Z0h � �@Úàº<퇘$  �Ȉ€ÿ64&´‡†ÚC9aCtÆŒ£®6-W®œXqJÞqÇ*‚Wv]/�«­!d0’Õá�ôΆTaãµtéÒ´ãÒð°    �� xqd rÝüùóåÍ7ß ÅJDnÇŸZµj œþ‰R"ù3Ú¿&ãÏÕÿþ£ÿ{ìÃû“!J·‹šŽÈÉLˆ¤Oh!z¢ï ,˜4S¼¾ÿïØ±C^|ñE)V¬X\ î­P¡‚üå/‰‰jíUؤ Ú�ÄÚÙ-*NÔ?ý»^½z¹Þ3xã�7Ñé�¸] Brý»áÇ :‰¢‰»�<«¿÷Þ{2eÊyõÕWÝSù‚´Å¶{�§(“ÝsÏÓà03 � x àe½©WKA{|Ð^×_†,2Yýü>�Œñ4$%lÙ²EêÖ­«lõr¸Q©ãæ¤]»v©ÃÀv káGyÄü*žø=% ÑH  �”%ÀuyÊ '  �”%‚ÿ62l(h�ÌPäì Õ†,™ˆè�¸ò‚z\�xíµ×*ASbäÆB$@$@$@$�*Ü82 dŸ6mšºŠ=»RåÊ••°½]»v‚ë)Á†HÞŒ¼ñDè^>Ï—Œ¾(P@‰ÜF?�'JO†Ð8ý1êð*¨Z²d‰téÒÅѰW¯^mFóF¯Â¦t´»‰úm@—C ß IDATÖ9tîÜY.\¨Þ÷—-[&:t0Ç#Y‡Ù?úè#éÔ©“9rÄq¬í2„-hOO?óe(h÷5MXˆH ¸YoÆëíñØëúËiªüþûï2wî\õû3»wï.¥K—v*è÷^×}�ÃÊÓ’À¬Y³¤ÿþªoÖŸ‰:‹geàÀ�177ãö²¶mÛÆ-vúôi©V­š¹.Æ{Þ�n§%tvŠH€H Ûp]žmèÙ0 � � äX´Goè]]9=³“cQ²7T“ck!     0$rdDAÈn7R…ㆬ믿^º½pá‚äþ;}œñ·U¨ž¬0ä2ê=óï??Úü{Úúé§ ÖóäÉ“–“Ïëû¿. ŽÅNTâUØDAûÿÑ=xð T¬XÑD=hÐ ™:uªŠÂ®3MFÄȽ{÷J•*Ul‡âùÛn»M®»î:Õ~Þ¼y%þüòÒK/ÉÚµkÍ2Q´'â‰x�§(CA{Zþxd§H€2P8Ì4ðºþr²AZþøÇ?š‚[L«^½ºS1×ßcÍŽÛŠ Η/Ÿ¸Yûù}j„[bð�zp# ÚÌjB}Öä¦Nm‚ Ø %ËN§6“ñ=lÆX"a—ÕÄx6anã§n'ìoß¾½@ŒŽ„Ÿ‹-r¼¥H_":û¾}ûï©‰Ò /¼ ýúõ3³Œ=ZÝÍD$@$@aàº<,Òl‡H€H€HÀ @A{ôæícbljEoxh � � � � � älvŽŒ+V(§~vFdOâ¨\ʨ Bt;º—ÏÏ;ØÄ÷ïÿï¾û®´jÕJLH”êÖ­+“'OV#y6¥“ ]�l?lØ0™8q¢£èì6mÚ$ 409Z£ßëL­bw?Ï"IMš4Œ³5½öÚkÒ¨Q£¸bDã¿ÿþûÍ"A Úƒâ‰x�§(CA»ŸÙÆ2$@©@€Â™`F)Ù‚vý÷�Ýïa¯=Aê5kÖ¨w¶nÝS¼cÇŽ* |ãÆ�Õá6»äõ÷é¡C‡äõ×_—�7ª¿­ ±{öì)Í›7—{î¹ÇQ|l”ݳg�ª ýÐû€Ã{þóŸå¾ûU«Æ¬Y±úæ›oÔ!>¼wév¢NÔ׺uk©Y³¦«už×qqÊ¿cÇõ>�ö™3g¤OŸ>jÝŽ(ãKDòß²e‹Y�ÁcŠ[µÜÞ&•¬ñÂAÊ—_~9“½6lPr㶠ㆢíÛ·«~ �?>ÇØ±cÍq„Peô4xð`™>}ºù1Ú~à�œ°*~·Þz«œ8qBåÅáì]»vIÉ’%Ë2 � � $ƒ×åÉ È:H€H€H€¼  Ý ­pòÒ¡�Á™‚öp&[!     ¯¬ŽŒW^yE9æ!°ˆz*V¬˜º²‚‰B… „½ÿNµ2þ¶Š×φÔ¾ÿ‡ôþïUØ”N‚v=ê9"دZµÊ•x*Qv¯Œœž)½>äß¼y³à€B¢¤‹ƒ‚´Å}ô:OQ†‚v§™ÅïI€R•…3ÁŒ\”íø�6bÄ™6mšcç!ê…¸‚p=¹ý}ŠÜ#GŽtÕÚ€mÖ©S'®}Ÿ~ú©ôïß?Ó½xÐÞKÕ¾2Ö:t¨#d@]+W®”Ò¥K»ÊŸ¬Lú¡?¬™Î�;'5jÔpl7ñ,X° aÔöd�—n/!Àæ.]ºÄØk¬�!@׿³ë˜õ¨ñý±cǤlÙ²fö2eÊn`N¹I&LPsÕHÏ?ÿ¼šgL$@$@$®ËàÌ6H€H€H€¬(h�Þ| C;cL(h�ÞĤE$@$@$@$@$@$†#"‰¯¾ú*òPÚµk'½{÷–Ê•+ÇØ� ¾ÿ‡ôþïVØdL¯bíì'êŸÞ—òåË˾}ûâF5µ>$/¼ð‚ŠPi$«xæàÁƒR±bEó;Ô‹h‘ˆÊ™(]¸pArçÎ-yòä‰É†ˆ�Vñz¯^½dΜ9 …÷—/_–¶mÛšÑ2QaЂö xÂv¯óe²{îEþ $HYÎ3tQ´C¬Ü´iÓ˜ÞnØ~sóûÄåë¯ ÒÛ´i“©Ø_|!7Þx£×êT~¬¡n¿ýöLeqƒ ÖD .ôT/„òï¿ÿ¾ãÍFž*uÈlÇ‘Ë�ÈâNmAŽC—úå‚/«½–Ïž=[üq32»a/í8 A{×®]�º!õêÕ“wÞyG­w�¤³=z´Œ3Ʊ.#ƒ¾îFo¿ý¶-+ו2# � � ¸$Àu¹KPÌF$@$@$�4ðß&­/NýN"ò=ÚAA{Df#Í      �@*Ú!¶G¤?Ù¯¾újÛ1ŒÀ†ßÿCzÿw#l²N’t´ë!ÑOˆb*T¨�ðgÄâõë×�–!’e§N�T¹ãÇ�KÉ’%cêpªW¯ó¦›n’íÛ·K‘"E·=<ðÀf}³?ôÐC mÔ£¥#sЂö xÂv¯óe(hç¯h �t%@áL0#UA»~ã z?|øpµ€8ûÇ”·ÞzK‰»­ ßíß¿_Š/n~ìôû¾§ž={ªˆàÖôàƒÊO}2‰²?ÿüó±8ê„È}õêÕf•I?ûì³Ò¨Q#)Q¢„`ôÝwß©èíýë_cÚÆÁ@ôCsC® ŸÛ·o/Dß|óÍ*ÿ©S§”¼oß¾1ub�…CŒN‡ “5ÓtîÖz $Ý»wW‚½·nÝ*=zôÈÄ¢qëzu1^vë.}^=úè£òÃ?H¾|ùäÉ'Ÿ”o¿ýV¾þúkµvµ²F´ur@Â-Z4ÓÜ8p Ìœ9Ólâƒ>�Úµk»F�õÖåÆ Ì-ÌkÔwו1# � � x$Àu¹G`ÌN$@$@$�eðßf¹n+  Ý-©lÌ—ì Õlì ›&�EàèÑ£²mÛ6¹âŠ+ä×_UW›bS�‰H€H€H€H ýXŸ~ú©üë_ÿ’7ß|S~úé§lïl­Zµ”ˆ½Y³f޶D`C„‚öŒQ ã@»“°IŸ,é$h_ˆÁæÍ›gvsüøñ2räȄψ ‚1ˆ¢ ÈB²«÷©§žR‘-ã%¼7B\e$€öìÙ£íËwéÒÅüÎ Þ®>D,mРA¦H®v‚v½/;v”Å‹Ç�þ®Ïk�AñD½ÎS”¡ ÝñG=3� ¤( g‚¸dû_ôßCv¿‡�z¢ÿžFþxQÐq îÞ{ÕNŸ>] 6’ÓïÓ�;wJÕªUcÌJ´î€kk›Ã† “üãf?ÿü³Ô¨QÃ̃uÓ'Ÿ|"XëØ%»ˆã»wï–*Uª˜Ùõu¾˜1c†ôïßß¼1ËZ÷¡C‡Ôm7ÖˆèK—.¬{ÂHñíˆ$Þ°aÃL&Øq­S§Žü[…ýAŒ—ݺË0p°ÁaÝj¼‰¿sçβhÑ"Û1A9Ü>_‰1ô5µÛ±yì±ÇdâĉfvðÀa &  š×åAfý$@$@$@:�øoC ÚCCí¿¡do¨ú·„%I€¼€Àzåæ?ÿùO:t¨—*˜—H€H€H€H EÄsd@Ônü SÜŽuˆTÑn<шÚlˆPО10´gýÁwnmÚ´I ±¬ióæÍJôd— NkܸqŒX‘5§M›“Ý®Þõë×KÓ¦M3U‹qF$Î… šßáð "±ãgŠ.V‚ðfphZO¨ Q3Ÿ{î¹LßÙEv‡øíöÛo7^ñ¢ •%´#OP<�Æ1ÞXÕ¬YS @jÙ²¥ŠÒš+W®¬O,Ö@$@ÙH€Â™`à'ÛÿòÛo¿IµjÕÌßC»víR¿s½¤qãÆÉ¨Q£Ì"“&M’!C†Ä­bÍš5Òºukó{ý÷ºÓïS]Œ5ÎÔ©Sã ’Ñ„ú¬ ¢dñq(I? ‰êzdu½CÆz"vDÚF4÷Š+šÙ&L˜sѺnŠ"g¬áŒT¯^=� \�üîe|Üæµ´�1BÐ�xÉî0ƒ~ãOã{ììãÙ³g'\Gy=øªç÷;&º½NIÝŽó‘ � �€®Ë�ñ{   �dˆ€ÿ6Ù]Š[í¡¡ößP²7Tý[Â’$@^89 ¼Ôż$@$@$@$@Ñ&àÆ‘±|ùr%n߸qc`�)]º´» ûÕW_í¹�lˆPО1j´{žº™ 8½�Å‹fŽç´M›61®Ã‡ „]k×®�iGáËxõBTŽhë �K—.ÉgŸ}&?ü°ú™`MÖ(®_~ù¥XžI• vŒ;Ö‹¡½íÛ·Kß¾}MáœZ§L™US�‹2sçΕnݺ©çgÏž•R¥J™U9 Úƒâé4Žv3…Ú³þü° �hp³ÞŒg¹Ýa¨hö2|«¼ú_ð;ïÿ÷m×kIÿøã�ê ›Ák×®-ºÛ¥bÅŠ™¿×ñ="•#¿5ú9Ö"Ö]ôzNž<)•+W–sçÎ ng*X° ÌŸ?_ .¬²&ú}Ѝà·Ýv[Ls¬-îºë®„ƒ�þ z8Ö.FÂ:ªmÛ¶ê¿^oƒ1ê¸pá‚äË—/SÛv\pSú�(ÙÙé¦\2f¢�@Üi,/_¾¬"î#*»‘¬Ñòƒ/»y‚ϰfʼnDÉ« ]çâæ°ƒ]ûúkÑ¢…¬^½:n$ùdŒ)ë   ຜó€H€H€H lð߆Öe ÚCCí¿!¯ªþ[bÉ(€�aÛ¶mÊá]´hјˆ$Q´×«MéÜ??Žw¯ü˜ŸH€H€H€H ¼82 Æ0¢¶'KÜÞ®];‘â—¬¤lˆPО1€´geÿ_Y7ïcG�µ‡!Âh¯^½ä‡~�+Vļ Ë^zé%%þ¶Kº¨Çš§jÕª²cÇÛrãÇ��‰:ŠLÇWÑIõ„z €Óñ¼÷ïß?ÓÞòWªTIºwï.Mš4Q¢º† ÆDœ·¶¡GYu´£l<ÝŒ£Î†‚ö¬??¬�H š¼¬7õPÐL½ú_ì"hgeÆè·zêõCL vÞ¼y6¡}¼¨ã‰~ŸêíA8�öpÏ)éÓ­ÂëcÇŽ©(ëÖ4cÆ ùë_ÿêKp¬Ûé%ª7ö=òÈ#¦)Vá½S³ò½Îݭͺ½ˆ�Þ§OeJPã…ºu{�nð1ØdUÐn�7^xë‡:vì(¸5—·òx¡È¼$@$@~p]î‡Ë� � � d…@ü·Y1ßSY Ú=áÊžÌ^7T³ÇJ¶ëÆ/6<ßyç_¾AÙ—ÕzÓ¹~ïYåÉò$@$@$@$@ÙCÀ¯#NxD‘[¶lYLF7½¸êª«ä¡‡RBö2eʸ)â˜'"´gŒR‚v̹:˜sÂIHb'TY¹reÜ÷Ó_~ùEªW¯nÎë–-[ʪU«B˜¸}ƒ»qãÆräÈÇçÃÈ€~ßÿý ó:tHêÖ­k+†·+8yòd§OŸVõX£´Ækã¹`Á%fkР�­XB²¨*좆uû´£l²yz�§°‚vD•5˜aî%š«®žI€H › ø]oÂl ÚãžWÿK¢ƒk~¦ˆ¾Cï[n¹Å¬*k(/‚v/¢`½^ë:ëY¼§@ú¨mžF�©ïþô§?™ãqüøqÁé"²¹sç Ú4ÆQÛõ�Ù®]»ýFZ³f�´nÝÚ̶}ûv%š§×y {tqSØsÏÕä`& ðA ëMͦ}¯þ—d ÚuQ±¾†Ám-ˆ„nŒ¿ŸI´O­‡[jæÌ™ãj۪ͦ"ûòË/Åò~ak:Ögÿõ_ÿ%wß}wÌZSϬ·å‡ƒQÆé gVê¶–ÕÙZ#­'jC_ÿ`M‹CйsçÎt 1™ãå×^¯‚v=²?ÞªU«æ»¾æÓdz®�H€H€HÀ%®Ë]‚b6   �¤ˆ€ÿ6i}qªˆ‚v'BøÞë†jL¦ I$0kÖ,u]8R:FKçþQОÄ�U‘ � � �@Ä $Û‘±ÿ~%l‡Àý«¯¾R½o×®�¢V®\90Ø¡ =ctô6‰R¼âS§NÉwß}'£c ( E‹•ë®».K=C�ßÿ½ª õ–-[V *äºÞsçΩHò—.]’+¯¼Rðÿ믿^Š/·ôý@ÔvD(µ‹‚úÓO?É·ß~«êÈ›7¯\sÍ5R¸pa×v%ÊϤÇJH€H Å${½™bÝÌ\?þc� …1:{ö¬Ô®]Û¼ýå­·Þ’:uêÈÅ‹mû€ßÏø=m$ýv]ðî„A;Äì¸ÊMÒ…Ìv‡Èp(‡â9Û)U­ZUF�­îzÔõD7Ë8Õ«?iÒ$2dˆ×bžóûõè\­¢u½ÎdŽW²ìuºU·Íœ9ÓäIA»ç©Å$@$@ÙL€ëòl6O$@$@9�@ü·¡Q§ =4Ôþò³¡ê¿5ç’ˆzç)¢A¸¹ÒÈ�šõÍYçÖÂË� eüÁ•èlÍî¤_ˉ aDwËJ4Dv3ÄnÆ/HAô/({ýpó»Œ>èNŒæeVú�çì“ñ<¢.Ô‰•ç&«ŒXžH€H€H€üÒ‘q;„¯W_}µÓ<•‰À†í#FA»§iËÌ$@$@$�#¹ÞÌãt2Ùþ=j´W±®¾§œ ñuP‚öƒJÅŠM²ƒ ’©S§Úú/p»Ë‚ Ô­3N ‘¼X±bfÖd Ú�×Nö¹ýÞ¯@çŠCÍ®ãg@VíNãå×^¯Ú,Ó§O71z}FŒ‚?þøcÌ!LFhw;3™�H€H «¸.Ï*A–'  ðJ þ[¯&ûÎOA»otáLö†j"Ë÷îÝ+/¿ü²ŠvæÌéÓ§�”+WNNž<)‹/–ñãÇÇ\“]¥JA4…N�:™×P£~”]±b…¼øâ‹™®ë;v¬ºöβ;:tH^ýuÁŽøÛšp­vÏž=¥yóærÏ=÷(&ñÒG}¤6X‘ÒÀíøCÂî�)6íý ×yC°ŽdÔ‡ƒ¹5�Ÿ�ÄZyvìØ¡®×ÇïôéÓÊ>\-¾e˳:£�¸Ö³V­Zq…òQé_Pó%(n^7€?ùä5NxnNœ8‘©»?ü°ôèÑCî¸ãO(8 ®‡GÝ[·n�)‹±Çõò�76ç¢Såß|ó�¬]»V=ãúsƒŸ ÷ÝwŸŠºS³fÍ,¾p²ƒß“ � � �@”¤‹##"´gLl Ú£ôtÓ  ˆ�tYoFƒæ¬H¶ÿ%Ù‚öD"q·,íS/Y²DºtébV5lØ0™8q¢«}ÝM›6Iƒ ̲-Z´P{Љ³ÀòÙgŸÉ| hÛ꯰ö§|ùò‚ƒ½FôzìocÏÙHˆZ>eÊ ÈkŠwk�×zœò{õõíܹS­ÞHV®AŽ—_{½ÚñžƒæÍ›göϯ ]Ö(hwš‘üžH€H Y¸.OIÖC$@$@$à–@ü·nMÍr> Ú³Œ0ø ’½¡šÈb}3lß¾}jC"ÕD ÂèÝ»wË�7Þ(Å»Éy�5‚hÓ¢_�9R¦M›æª}ô^D±K:77pz4ŒzõêÉ;ï¼£6{õúâ©oëå0Ø vùC‡*BŠ]Ôö¨ôÏÕ`ùÈ7·ÀŸ~ú©´k×N Ùª‰L¶nXAÄ� ¶Ç<ÓF¢]؈C4ñÊ•+ÛF—ÖË”)SF xÑN˜ ¤ˆF ѹׄˆÔmÚ´ÉTLßès#hO´É‡hø]»vu4Ï*‚Gf» GDt·‹ömW9ò«V­2£žy¢Ò?G >3„Åíù矗þýûÇX©Ï·]pŠ6‚CM›6�á&^;›7o–ºuëfúúâÅ‹‚¨7 .tk¢Ê;ßÿ}uÓ � � � ¤3�tqdD`C„‚öŒ%Ýí8xý믿ò#Q<¯¼òÊ@êf¥$@$@$��Òe½™� íÚN¶ÿ%«‚v="•ïÚµ+á ²è×… T°#¢¹µ¯‰„Êúþ4ÚC #ãfÙDãõ /H¿~ýÌ,vûßnÇö(O<ñ„Üpà êãÓ§Oˇ~¨¢eèbðÏ?ÿ<“87Ù‚ïãÇ�Ë×_-ˆLÑ·o_ÓT€¨ ‘óÁÖ*N4~¸š´{÷î*Š>6¶·nÝ*=zôÈÔ¿—_~9&úÚŠJÿ<·ëìaqÓ7ô!Ç•¬úµªˆ‚Žgíºë®SW»;vLPV¿Q`ôèÑ2fÌÛ~bóúôé1ß!’ 6¾!2ÿñÇÅ©[¾ÃU®Å‹�ùíz[ˆn¾ùfå(9uê”:a�³¨„É®§"3’ � � ¤0�tqdD`C„‚öŒç ÝíØOÐ÷’õøë¿“U/ë!  �ì&�.ëÍìæ¨·ŸlÿKVíð ”,Y2ÆLˆ¹+T¨|õë×7÷™± ßB‘"ET™DBeì=—-[ÖS{Ȭ·‰Ïpj§N�bê‚Pk[»›aõé¶`Í17æþÉ“'3Srâ’ÝsMçîfŸÜ·eõ+9^a Ú—-[&¸1ØH~B|÷ÝwrÛm·™>®Ž; ‚EåÊ•+»‡ží“ � ¤9®ËÓ|€Ù=  ˆ �øoC£BA{h¨ý7”ì ÕD–Ø {A–«T©S³Ø$µ‹t¾~ýzÚš°7bÄyöÙgÍ�;wî¬">‡µÁ´sçN©ZµjŒ]‰6˰!Á1"ÉÉÄø,Ù‚o£Þ/¾øB Ð�^ˆÎa¼$écO˜ýöÛoKÆ 3 ½]ÿêÔ©#ï¾ûnL$—¨ôÏÿS”¸dXÜô¹¦G•�•8hP³fM[ƒõÈ,ñ¢´ÛÕïv8Hî½÷Þ˜9!<®x5ÒÑ£G•(Ýšf̘¡¢ÍÛÍÅC‡©(ïÖà K—.l(3‘ � � �@ºHGF6D(hÏxHÒ]Ð>`À™9sf ?Z¶l©Ú†µÏH'X) � � ØH—õfÔ7Ùþ—¬ Ú±DPëá¿§žzJݤ/éû·Œƒ`Dnívíá&Þ‘#G&*}{ÕˆŽ]¢D A —Y³f©½nˆÜ8Èy=^Ÿ]÷Î;ï4o ÖýGzø›&L˜à8¥�Ad†ªöÂá#Šçcq¬ÌC;¿Ã{ï½'8€/!ØÒ­·Þ³·Ž4•*URE‚/ÿ‚v}.8 Ë7nÜ(Mš41 ÓÔ©S=�‰î á´U IDATïsj×ÃÐ1+ � � $$Àu9' � � �@Ø"à¿ ­Ë´‡†ÚCÉÞPMd‰ÝÛš5ka»„ÍÈ.]ºÄ|5iÒ$2dˆmþ½{÷ÆãQïÊ•+UÄð0Òc�=&'Nô´QÁ~�5Ì2v"â ßúõ‘NŽy»ñsÚÔµ?ë‘M¢Ò¿ æHXÜtA;¢…tíÚÕìÆwõêÕq7nûí7Á�ë!’Ý»wg:l2nÜ8Áõ¥FJôL"žñÖ­[›ùq­,6É�ëi±áou`X#ãÄDoܸ±ù56éq°ÂîÊÛ Æ•õ’ � � �@˜ÒÅ‘�  Ú3&nº ÚqûÓsÏ=—éö³d<³wÜq‡ŠäIA{2h²  �(H—õf”˜Â–dû_~þùgµ_{äÈÕÕ>ø@j×®í©Û›6mR�v¬É.ˆ¾Çº7±"p�‘ôý['¡²]{›7oVAKìDûØûµÞ< Q²qÃ(ö±«U«sÃ/ö´ñY¢¤GîÖ÷µu_ êŠÈÅhÇ�ÇÓàxÈlçw@à˜mÛ¶eº!Õ¨ëdÜ–j$ìÕã @Þ¼yÍÏ’=^FÅNó$^×4çöÛo7EøºA/§ßB`×G7˜õ@~#½»i‹yH€H€HÀJ€ërÎ   �° DÀZ—)h µÿ†’½¡šÈ¯W ê‚kDs‡ÀÖUÜÚÞ/¿ü"Õ«W7£A; ´ýSË\R¿~9píæ]wÝ•°;ñòåË¥mÛ¶f¹¨¾í6H>œ)º¶µÃ¸QI•ÝHúÆ_Tú—Ìù`­+,n:×sçΩMÞŸ~úI¾ÿþ{½Æˆ´¯¯zÝ€(6p�XopšÆu­°§V­ZR°`A™?¾.\XìêÃzåÊ•=?7nÊ5Ƭ—H€H€H€‚&�.ŽŒlˆPО1YÓ]ÐôóÈúI€H€H  ¤Ëz3jc„ÿåÃ?ˆ¾©"n#RºÛ¾£íVÁ8ÊâÐ (P@.]º$Ÿ}ö™<üðÃòæ›oÆT­ï; •ãµ?H›6mb°`¯âõµkׯ´©Éyá…¤_¿~føŽ¦L™"Í›7—’%KÆ”Å9nØéÛ·oÌ篽öš´hÑÂü kd؃ 0Ö„ˆò¸qméÔ©S‚~ëu†)zŽw3,‚&­[·NE£7öæŸyæA°k˜#b¿51^¨ßižÄ›¿ú­È7wî\éÖ­›zΞ=+¥J•2‹ë>7Œì”-[Öí#b©ÞÍ¡ × 0# � � $ Àu9§ � � �@Ø"à¿ ­Ë´‡†ÚCAl¨Æ³Fß°B”gk4½œ.hÇæ"6ãE\×7¶Â´ë‘È [lþ:%=BuTßúø¹�Š��äGyÄÄ€ë?q ¨‘rš =(nÉØ,×碾I«Ïs·ÑM° n=]¯Ï-Ì}^éAœž;~O$@$@$@©D ]Ø¡ =câSОJO?m%  �p¤Ëz3Zî[ ÓÿâÞ*Ý÷b-[µjUÙ±c‡muw[oÛD&7Bå£G�ÚÆ�øºW¯^òÃ?¨hèŸë饗^RâekBp#^Ù³gO¦ü0CØŽu>ÿüsÛ¾tîÜYE�×oÝAtoÜbjDÀ·VþàƒJþüùe×®]²uëÖLíºÝ+÷2N‰òÆ´e5j¤.€-n/ÒìE_ì|XÉ/·óÄ®¿¨7lØ0Ó #¯Ý­ÇúmÊø7kÖÌ5zÜ„€›•�À>h¢øâÅ‹»®ƒI€H€HÀ/®Ëý’c9   ¿"à¿õkºçr´{F~�07Tõ 6]ج÷^ßTíØ±£,^¼8îÕÞQ´;Ùjí«ÎeÆŒ2`À3KTß^$p*•þõô9õ?^»NåÜ8 ôº· ‚Œq0œãÿ6l�Ñ£G›ÅtA;"äÜrË-æ÷Y=4¢×‡MtÜ€Èþº3ÁÚ—|ùòÉ‹/¾…'‚þ æë%   �¬HGF6D(hϘŒ´gõ‰dy  H?é²ÞŒÚÈ„éñÚ÷C‡IݺumEävuMž7ê³µ'baúôéÓ¥P¡BI«7“" üu×]çu|ç×¹/]ºT°ßþÄO8ÖY¦LÁÞÿõ×_7o²ÇkÙ²eÒ¡C³=/ûù‰Äûv‚ö·ÞzKÍ #!Àü ÆÏ9'@}ô‘Ô¬YÓÌæÅßçT7¿'  '\—;â÷$@$@$@É&ÿm²»·> ÚCCí¿!¯ªˆÌ��ŸbÅŠÅmÂò *È_þò—˜ "·›Fź ÝI<›�‚v½oˆ*‚ë‰r�~¾ñÆ1W[¢ŸØ¨5ÇQ|{=�`ôoãÆ�Ò¤Is¾è‘ö£Ò?ÿOQâ’aq‹· ±ú{ï½§"š¿ú꫞º© Úõ¹:|øpATw·Ázãz}žŒÓ2{ÙÏJ;,K$@$@$@ÙA ]Ø¡ =cSОO1Û$  �hH—õfÔ({õ¿„m?"�/Z´Hž|òɸÂöÞ½{˰aÔÏÇ.é{¼VCl—.\¸ "º#¨�]”s”A„õÇ\Ee/UªTB$ØûþðÕ/×¥‡~Xzè!©\¹²#f¬—_{í5Uo¢=u°ÁÚýî‘;'C<ÛÛo¿-ãÆ�“wß}×¶$¢ì# ÒÕW_íØt2ÇK÷ÍŸ?_zôèáhƒ‘ãÛ½{÷Lùq[2"ÍcÞ �}î¼óNóð¾C´uù�Æ~àÀ�2sæL3ëš5k~;&  ƒ×åaPf$@$@$@Vð߆6 ´‡†ÚC^7T—,Y"]ºtqlP-£@N´c£›£n’.Üׯ»ŒŠàÛëø}×û§‹ý£Ò?7cå'OXÜì݈$Ò©S'OQo¬}Ôízý6¯|œ®EõRߤI“dÈ�!^Š0/ � � � ¤ �tqdD`C„‚öŒYOA{Ê<ú4”H€H€B#�.ëÍЀ¹lÈ«ÿÅeµ�dCÄóï¿ÿ^ rG*P €”-[6n$ó¬�[DÑ&ÚÃúí-ZÔw”ósçÎÉ7ß|#?üðƒiæu‘"ET$òüùóû2Ù¸õvâ¶S£Î%JÈ•W^é«Îdrò;€ï×_-¿þú«äÉ“G Ø!èÆ¿ý¤d�—~úé'ùöÛoUѼyóÊ5×\#… ¶­jÚ´iò·¿ýÍüBþ‘#G:6«ß* ÁIFgOþ˜°F  g\—;3b   �䈀ÿ6¹JPí¡¡ößP˜ª^7ØRIЮGÃö-Ú)êuTí³gÏ–>}ú8N¾W^yExà3ßðáÃc"\D¥Žñ™Aï_Pܬ‚výÙ�é›7o4I”,Ó§O7³è‚v¯Ï¤²d×çÔ¿'   �T%�.ŽŒlˆPОñPОª? h7 � � G ]Ö›ÁòWs˜þ²TªðêoKÕ~fÅîÓ§OKµjÕäÈ‘#ªš«®ºJEi·¼—šÕã©gÏž‚ˆìFB@øú˜H€H€H L\—‡I›m‘ � � €@ü·¡ í¡¡ößP˜ª^7ؼŠ]ÏŸ?/5kÖ”={ö( ^Dåþ þ_IÝÖòåË«�±¼yó:Vý /H¿~ýÌ|Va2>Ô¹½÷Þ{R¯^½„õ~ðÁR§N3� ¯|u;F�%cÇŽuìߢE‹¤[·nf¾9sæÈC=dþ?*ýsìˆÏ aq³Î›-[¶Äˆ×ùÜ­7&èÝATô¶mÛ ¢´I´~Aq³rÕ£âë‡ì,Öo@]Ð~üøq)Y²¤§y®ÏqôûöíR¤H9yò¤T®\YNœ8aÖéæ¹ j¬X/ � � � D•@º82"°!BA{T'9í"  ˆ Ú“; aú_’k9kË �Þ½{˼yó²REܲvôÎ;ïÈÊ•+¥}ûöf> Úí‘ÁGNË—/73àßîc$=’;>ÿüóÏ¥\¹r�Œ!+%  �DÒe˜£L$@$@$�:"à¿ í¡¡ößP˜ªé,hǦ¢Ž[7)Ç�/#GŽL88z„êk¯½V‰ÄK”(a–[¶l™tèÐÁõÆ$6ßn½õÖ‘°� ]o»cÇŽ²xñâ¸Q¼õñƒANÑâílÙ¿¿TªT)rýóÿ%.·DÏEtéÒÅõœ¹xñ¢4hÐ æ` ë‚v»yþÔSOÉã�?ÂÑ£G"v#•)SFÝ¢A;ÒàÁƒeúôéæ÷#FŒ� &8òLžùD ÖñnÔ£GµÝHK—.øË˜H€H€H ;p]žÔÙ& � � älð߆6´‡†ÚCan¨¦³ # _IˆÏ6oÞ,uëÖµ óçÏKãÆ�cDă ’iÓ¦Åä߸q£4iÒÄü ¢wˆÑ A°53„ɽzõŠÙ|Ã÷v‚vDÛ¾ýöÛMá{ùòåbóxW(Ú ³!TÞ¶m›/^ܶ�cÆŒ‘±cÇšß¡ öóæÍk~•þùŠ— Š[¢çiçÎ�Jàm$ŒÓ§Ÿ~*v×ÿbÃöÑG•çž{.SGì"»ÛÍóõë×KÓ¦M3•·Û FtÔk¼ŒC4_£F�˜²ØŒ·^ªW¬—IôL5®¬—H€H€H€Â$�.ŽŒlˆPО1q)hóée[$@$@$�Òe½5Úaú_¢Ö÷œlü"Øo"Â÷wÜ¡ö×!ºîÚµ«‰ÙÍÍ·9yLà±Z‚¨ý³Ï>“gžy&&àŽ›@U9™#ûN$@$<®ËƒgÌH€H€H€b DÀÚ�PÐjÿ …¹¡šî‚öxQ®q}!ĹÖèчˆ××®]3xªW¨P!æ3=z¾Ä‰Ï>û¬Üpà f^ˆÑ;w_ëÉNÐA}Íš5còÏ�;Wºuë&øîìÙ³RªT)³*;a6¾„˜xݺurç�wšyÏ�;§6Ç�cŠ�@:*ýóÿ%.·DÏÓ—_~)–_6Ê@Ì7.0B`¾nß¾]úöík;gPfàÀ�2eÊÉ�;·ÙÉxóc‹¨ð �K—.©Íà‡~XÞ|óÍ@vQßñ|¬^½:&6ŽÑ>6–�têÔ)A¿a³5ñ:Õ f/ë%  ˆ �tqdD`C„‚öŒIMA{TžlÚA$@$@Ñ!�.ëÍèý?KÂô¿D­ï´'X¿üò‹ hQ®\¹äòåËÊOS¨P¡`MñÚá‹‚ß ?óJ—.-ß}÷�\¸pÁüÌêsKñîÒ|  �%ÀuyŠÍ&  �&ÿmhô(h µÿ†ÂÜPMwA;FáèÑ£‚hØzÂf""§ÿðÂèÓ'NœÈ”祗^Rbr»4xðà˜(Fˆ}ï¾ûnADnk�£F�RÂߨ¬v‚öß~ûM6l!ÞÚ¶õ:ž0Û(Ó¨Q#iР�ê£]´oDgßµk—<ë) ýóÿ%.7§çiøðáêЃž¹½X±b™„æ¼÷ïß_*V¬Sù¹¤{÷îæMÿïÿý¿¸Ñe�ÇŽ¶PâE7ÁÆ{�:uäÈ‘#™Ê=øàƒ’?~5w¶nÝšé{»¨ÿA�%ë%   �ì"�.ŽŒlˆPО1‰)hÏ®'™í’ � �@t ¤Ëz3j„Ãô¿D­ï´‡H€H€H€HÀ;®Ë½3c    �¬ˆ€ÿ6kðPš‚v°²+k˜ªË–-“:˜]uŠª¬‹f!Ê^¹reL´h+7D¤¨^½zBwœ!joܸ±­87^ûè×ý÷ß×3茓ÅÍIÐ~úôi©[·®«9ƒçrÁ‚’'O5϶lÙ’©·ú•¥‡RõÛΰC5yòdÁÁDŒ±K˜xnìnˆ‡¾V­Z²jÕ*¹îºë|Ž‹‘ � � �@jHGF6D(hϘò´§ÆsO+I€H€H Lé²Þ “™›¶Âô¿¸±‡yH€H€H€H€¢M€ëòh�­#  �t$ÿmhX)h µÿ†ÂÜPݸq£áÏŸ?_zôè×ø/¾øBn¼ñFóû�Ê´iÓÔU€véüùóR¿~}%œFêܹ³,\¸0®€Ö?5ç’¸¢‚cˆ€í¢J£DWüñÇUTöR¥J9VzîÜ9™3gŽüío³Í‹ÈØO=õ”üéORW$Ö¬YÓ#úöÔ©SmÙ�¢oë ‘æv"ÅP¿ýöÛ2nÜ8y÷ÝwmíBTîÈÕW_�°�ÙÝ?Çð™!(no¼ñ†´hÑ´ BùŽ;ÆX‰y€9øè£�ÚZ�¨úøsÆx®-ÏÚòåËcÊÌ�;Wz÷îó‘,Z´Hž|òɸÂv”Á!‹ *8„°æµ×^SóüÕW_�›uâíñ~86Æ $@$@$@$�BÒÅ‘�  Ú3æ=í)ôðÓT  ‰@º¬7CÂ庙0ý/®�bF    �Èàº<²CCÃH€H€H m DÀ[ ÚCCí¿!n¨úgç¶ä©S§ѧ!þ…p @�R´hQßQ¥!ü>räˆüüóÏ*¢vÞ¼yÑÔ³¥ú§Ÿ~’o¿ýVu õ]sÍ5R¸pá˜.:EG?¿þúkùõ×_•]°#2;þí%eWÿ¼Øè%oXÜÙd0½té’\yå•‚ÿ#Z~ñâÅãÃxb¾büòåË'ùóçOØmÌñï¿ÿ^Ís$Ìó²eËJ¡B…¼à2óž9sÆ|n~ûí7%\/R¤ˆ”(QBõ�‰H€H€H€r�tqdD`C„‚öŒ‡‚öœôÓƒ}%  wÒe½é®·áå¢ÿ%<Öl‰H€H€H€Ò�×åé0Šì � � ¤�øoCFA{h¨ý7Ä UÿìrZI'avNãá¶¿äæ–ó‘ � � � Ä#�.ŽŒlˆPО1É(hçÏ   �@º¬7£6²ô¿DmDh � � � D›×åÑZG$@$@éH þÛаRÐjÿ qCÕ?»œV’Âl#Nnþ¸± � � � �À¤‹##"´gL+ ÚùÓ…H€H€H@'�.ëͨ�,ý/QÚ““¼óÎ;êfâ+®¸BÝ,{ß}÷É| nFÂͰ͛7W· 3‘ � �@Tp]•‘ $@$@$�sDÀl ÚCCí¿!n¨úg—ÓJR˜íoÄÉÍ7–"   ø�tqdD`C„‚öŒiEA;º� � � èÒe½µ‘¥ÿ%j#B{r �eË–I‡ÌîÖ«WO6lØ «W¯–Ž;šŸ×©SG6nÜ(ùóçÏ)hØO  �ˆàº<âDóH€H€H DÀU ÚCCí¿¡œ²¡zîÜ93ê‚Zö%óäÉ#W^ye²«�\}é(Ìc^¤#·ÈMND$@$@$@iN ]Ø¡ =ãY¡ =Í`°{$@$@$àƒ@º¬7}t=Ð"9Åÿ(DVN ìܹSªV­j–ºé¦›d÷îÝR¨P!õÙ¬Y³¤ÿþæ÷Æ “‰'ŠñsÐcsÌN$@$@I%ÀuyRq²2   "à¿uaer²PОŽ�Ö’S6T{÷î-óæÍ „%";àêÂܹsRT*MGavóbåʕҾ}{sŸþù˜ÍÒ¨Œ/í    ˆ.�tqdD`C„‚öŒiNA{tŸuZF$@$@ÙE ]Ö›ÙÅ/^»9Åÿ5î´'µ à}eÛ¶mréÒ%)Z´¨T¬XÑu‡~þùg©Q£†8pÀ,³ÿ~©T©’ùÿË—/K«V­äõ×_7?[·n�4kÖÌu;ÌH$@$@Aàº<(²¬—H€H€HÀiÿ*ãûTÑ{ûÌTé ÚCœîíÈÌ™3}OæD[¶l)«V­’\¹rRT*MGA{óbÅŠ´GeÓ   HQéâÈ  ={' UÙËŸ­“ � �@” ¤Ëz3jŒ¹þŠÚˆÐžT pìØ1)[¶¬2Õk@© &ÈÈ‘#ÍnNš4I† ’©Û‡–[n¹Åü¼L™2²gÏ)R¤H* ¢�$@$@iL€ëò4\v�H€H€"J þÛÐÈPÐjÿ å” Õ1cÆÈsÏ='åÊ•ó+NÉ;î¸CæÌ™“ö‚öÅ‹K×®]M 3fÌÂS9…1/–.]švÜRyÌi; � � � ¤"�tqdD`C„Ú3€t?ОŠÏ8m&  �ì&�.ëÍìæ¨·ŸSü/QãN{R›À–-[¤nݺª;vø¦Ü”ÒEê7Ýt“ìÚµK®ºê*[ S¦L‘GyÄü.žø=µiÒz  �T#Àuyª�í%  �Ô'ÿmh)h µÿ†¸¡êŸ]N+ùË/¿ÈñãÇÕÆ!®d¼öÚk¥P¡B9 ƒçþ’›gd,@$@$@$@$ HGF6D(hϘ[´óG � � �€N ]Ö›QÙdû_°Ž›;w®œ?^Ξ=+Ý»w—Ò¥KG­Û´‡²D`Ö¬YÒ¿UnH^¹r¥äÎ�;a�x6sSóòåË¥mÛ¶qË�>}ZªU«&GŽQy |?pà€ Z; � � d®Ë³‹<Û%  �œK þÛÐàSÐjÿ %{CÕ¿%,I$@$@$@$@$@$`G ]Ø¡ =c‚QÐΟ3$@$@$@:�tYoFmd“íAð”?þñ�¦÷£�>’êÕ«'­Û/^üA@ˆóåË—´ºQÑ… ̵hþüù³\÷¥K—T�HAØkð@;xF ,˜e›ƒ¬ Ù|ƒœ˜Ëž¤Ï¼«´oß^ FGêܹ³,Oœ IDATZ´HñO”<(+V4³ :û¾}û¤@� ˽ð ү_?3ÏèÑ£7û2‘ � �@vàº<»È³]  ȹ"à¿ >í¡¡ößP²7Tý[Â’$@$@$@$@$@$@vÒÅ‘�  Ú3&íü9C$@$@$ H—õfÔF6ÙþDf¯Y³¦ìÙ³Guõã�?V¦³’:$¯¿þºlܸQýmMˆXݳgOiÞ¼¹ÜsÏ= ÅÁ;vìP"dˆÏœ9#}úô‘råÊ ¢`¯^½ZE–ß²e‹Y½QwÇŽ¥V­ZŽ‚e£à7ß|#k×®•+Vd²·J•*rß}÷IëÖ­§D"è½{÷ÊË/¿œÉ^ˆÂ7lØ sæÌ‘W_}5†GÕªUeäȑҦMLjáY»²añMÖ|ˆÇl! 7¢¢C°¾páBÙ¾}»š'HãÇ��A0vìX%~G‚Peô4xð`™>}ºù1Æö�pÌÏ[o½UNœ8¡òâfâ]»vIÉ’%Ë2 � � A€ëò ¨²N   �D"à¿ m€(h µÿ†’½¡êß–$     °#�.ŽŒlˆPО1Á(hçÏ   �@º¬7£6²Éö¿$SÐŽÙgO›6Í6Ð!$¯S§Žmþ%K–H—.]Ìï h>wîœÔ¨Qñþ:È‚ 2Eë¶ÄvòäÉ2tèPÇú�‘ëW®\)¥K—veïÎ�;•ˆùî»ï6ÅÖñ‚¨zÓ¦MR¦LW¶$#SÐ|ƒž8„�9a�#àÒ²eKYµj•:\ gÇ­E‹JøŽˆüF:v옔-[Öü?ÆåÀ�‚9ë&M˜0A= Fzþùç¥ÿþnŠ2 � � $�×åIGÊ I€H€H€DÀûÿ³w`VTçÿÿ°+¬‚�ö(m©YêfY¤$*ð“Òã�šR6 ‚…A…5D¤%¤I‰REŠˆYºò�€ %HûóŽ¿gÎνSîÌܹs¿çy|îœú9ï93{Î÷¼Ç³>‚ Ý3Ôö rzAÕ~M�@@@@@@@�@P62|° Aû]ƒ ó €€€€H (ß›~ëY§÷_œ´³õ&MšHÞ­öŒÎÊÅð�üƒÚ¶m«üÌ¢`ö¬-{¾6*‡…Ê,lNHHÈõæÍ›Ô³gOÉ“·•ÀÞ¶?ùäÉS|¸ú²ð™ã/½ô’i&ÉÉÉtðàAÝúZ©£Ù¸nòuÛ˜ïœ9shÔ¨Q9 ° �° ½K—.†8ÒÓÓiÓ¦MA»ÈæÅ_¤±cÇæ%G8|ø0U¬XQ‰Ïelܸѳ¾5]QDˆ ø.�‹nF#A@@ÀW|°ëÚ=Cm¿ §Tí×)A@@@@@ôe#à "´ß50Ú1Ï€€€ˆ‚ò½é·žuzÿÅ A; öèÑCòˆ®Ý»w§Ñ£GÓ#�<"ý|áÂÚ±cõéÓ'‡(ýÈ‘#9D⢨X�÷À�éÙgŸ¥R¥JIBä�;wR·nÝräË¢æöíÛçèFÆ‹eϳp¹L™2’ðøüùó’ þ¹çžÓ¤goê ¤¤$ÍïáêËâù!C†Hد_¿N_}õ•TþG}¤Écûö픚šê‰Ù¹Å7öÀ žþyúá‡(Ož<ô /Ð÷ßOß~û-íÞ½[Ó‡Ü|ˆ‚ÃíÛ·©`Á‚Ûãú0€fÍš¥ôƒÕ~áqU¿~}å0 ðù°‚Úë»'�ŒB@@@à.|—à @@@¼&àƒý[Ïš A»g¨íäô‚ªýš %€€€€€€€� ldø`A‚ö»A;æ‘@P¾7ýÖ³Nï¿8!hÏÎΦ”” *ö¦Þ¯_?]|gÏž¥ СC‡”çÆ £É“'kâ‡\³§ë† æÈ[/ß´´4Ú¼y³Æ3ö‰'ˆEéê0sæL©¾²ÝªŸ=z”êÕ«§Ë/^¼˜:tè`ª¾¡<Ð߸qƒ¸~j¯öV=�GbŸnñõÚø`œàƒ zá믿–>È¡S§N´`Áݾæ8W¯^¥Zµj)öÉby>ÀP¨P!K¸GŽI“&MRÒlذ�5jd)D'à»Ü ŠÈ@@@À ìßZ©nDq!h�Ÿ7‰�^Põ¦Ö(@@@@@â‡@P62|° AûÝaA{üÌh)€€˜%”ïM³íõ*žÓû/,ª®Y³&íÛ·Ojž={¨Zµj–š# wÙ{zfffHÁ0gÎ"îÚµk+å°høðáÃT @å7=Áõˆ#hâĉ!ëÇyT¬XQóœ+_¾¼ò§ÏÈÈPþÝ«W/ÊÊÊ [_#7iÒDI“žžN,¬gOîrЫ/ åû÷ï²¾"‡–-[ÒŠ+Bг-uŒAd·øziÜwsæÌ¡\¹r…l-{Ã/W®œòüÉ'Ÿ”<ï‡J#Æ×ëk3ý ò}å•WhÔ¨Qf’"€€8JßåŽâDf   &ø`ÿÖD-�‰A»3]ÍEe�®–ƒÌA@@@@@"#À›õVÃ=÷Üc5‰kñ}° AûÝÞ… Ý5GÆ   ³ œq§ë¬ ÚoÞ¼I'OžÔý^c1öÅ‹©Y³fŠ÷qö<žššJ,t× >ø FtÎ^Ñ«T©¢ñ^¾{÷nªQ£FXzÞÉ—.]JmÚ´QÒé ®�;–ûºº Û·oSãÆ�%¯ìrP{‹¿té’Ô>µwxö¾]¹reËõÓ‰õÕé‹…ˆâiöú¾pá°m§,Ë ¾^Û×_~IÉÉÉa‘X´‹\ìzÍWxyXÁ)A>  Á €ïò`ô#Z  ±DÀû·žá‚ Ý3Ôö ‚ Ý>;¤/ @Ð1mÚï"„ =b;B   8θӥVízË#©Ùk¯½FC‡U²ó/]º4±Ð;))ɰÑSºZxΉEa±YOÙÓ¦M£Áƒ+å³ï>}úHÿëk6ON+æk$À7ãùýôéÓ’Gü3gÎHõ3ònÕB/øºi,d?xð ÆK¾^ó#´‹vi±xxÂËà fëˆx  ñAßåñÑÏh%€€ø‰í~ê�Ÿë‚ m†pG€      à3AÙÈðÁ‚þþÇßÿ>ݨ€€øƒ@P¾7ýAó—ZX´‹bÞHÛ#Š{E�¸Ñ®(¨ž9s&õïß_©¢ø|̘14nÜ8Ã&„KÇÞË–-«äÁ¢è½{÷{vÏ•+WȼóäÉC³gϦ¾}û*qŒøYYYÔ»wï°õýïÿKuêÔ¡}ûöIñ¢)hw‚¯—öðÊ+¯Ð¨Q£ íÁª }ùòåÔºuk%ß;vPݺu Ë#ˆ}[¢D ©Ÿ (`9/$�Hà»<zH    `‡€öoíTÛVxh·…ÍÛDVT½­JÊF†D h¿;œp s €€€€H (ß›~ëY«û/N Ú�Dç={ö$r‡‡ËL׬YC-[¶T³˜{Ù²e”;wné7Q˜®ö´®_Ö¯_OM›6U¢p+V¬�òËŒ¤�íf<{ûIÐî_±Ïü`VíâÍŸ}öÕ¬YÓ²©ˆ}[¸paé†Ú-£D� à»X� ý®ñAÐnu">€€Ÿ@P¾7ýÖSN￈‚j«âÝHíì­ºbÅŠ â�Rff¦røÖ® ]Ì·W¯^’×x¶I'í¢0ÚN}cQÐn…¯A»[ö`ÕCû AƒhÆŒŠ]Zr‹/Ò< äí~›MQˆø.�Ÿ¾FKA@@À/|°ë Ú=Cm¿ §Tí×)A@@@@@ôe#à "´ß50Ú1Ï€€€ˆ‚ò½é·žuzÿ%RAû¢E‹¨sçÎ ¦aÆѤI“Qz8~[¶l¡ (QZ¶lI+V¬ Ü¹sK¿ÙˆsºììlJIIÑÍwåÊ•ôôÓO+ÏØ{û´iÓèöíÛ–»š½ÕçÍ›WIg§¾±(hÇ×�ö`EÐÎ×ôîÝ›æÎ�«ô«]A»Ø·´[bH  à|—;Ù€€€˜&àƒý[Óu�4"í‘ô ½Ó ªTE€€€€€€@\ÊF†D h¿;r h�«é�S‚ò½iª±Frzÿ%RAûþýû©jÕª Ñky84¢|Ê”)4dÈ%‰ø|Μ9Ô§OCÚï½÷µoß^‰7|øpIdÏÁŠ¸Ù° !B¬ Ú�àë¥=¼ñÆÔ¯_?Ãn²Úç,fg¯þrؽ{7Õ¨Qð1í–‘!€€€Kð]îXd    ’€öo=ëÚ=Cm¿ §Tí×)A@@@@@ôe#à "´ß50Ú1Ï€€€ˆ‚ò½é·žuzÿ%RA»(NNN¦Pbb¢!º·Þz‹úöí«ÄÊ¢@|̘14nÜ8Ã|,X@]»vUâeeeI^·9>|˜*V¬¨<½Âf&B¬ Ú�àë¥=¸%hûqÆ Ô¨Q#˦!Ž­Ò¥KKc#))Ér^H  ‘Àwy$ô�@@@ÀìßÚ©¶­4´ÛÂæm"§T½­=J�àÊF†D h¿;\ hþœ�‚€€€UAùÞ´Ún·ã;½ÿ© ýÔ©ST²dIM³Y4^¾|ù°(nß¾Mõë×§mÛ¶)ñ-ZD;vTþ- ‹Í‚ù»´G�4oÞ<%µðùܹsT¹re:sæŒòÜL}Íôk¬ Ú�àë¥=¸%h_²d µk×N×~ÌØ�çìÙ³T¥JÅÖ:tè@ .¤\¹rYÉqA@@ bø.�!2°HÀû·kl?:íöÙy–ÒéUÏ*Ž‚@@@@@@ Ne#à "´ß3´ÇÉÄ�f‚€€€AùÞ´ÐdO¢:½ÿ© �¿Ùûùܹs•ö�?ž222Âò=¥.\Xò^]¨P!%�(çü1¥§§‡ÌûÂ… T¡B�`ýàÁƒT©R%%Í AƒhƌʿGŒA'N4ì?Žóúë¯ÓСC©qãÆ”’’B²�sâX´;Á×K{0+hmÍHX¾~ýzjÚ´©b¤ÌÌLM_ËÝÙÙÙ’�ÈÁ¨\3y"€€Ø!€ïr;Ô�@@@ >Ø¿�¤ú–ÒBÐn Wt";½ �V T.� ldø`A‚ö»Ã‚öàÎh€€Ø%”ïM»íw+�Óû/W®\¡ªU«ÒñãÇ¥*oß¾�RSS-UË–-Ô AMš­[·R½zõtóa}“&M4ÞÙY4<}útM|=A;{ÿôÓO顇ÒÍ{ìØ±4nÜ8åYrr²$”OLLT~ûì³Ï¨víÚšôï¿ÿ>µnÝ:d»Å4,Àg¡t�”4A´;Á×+{0+h?}ú4U«VM9äÀ6Á‡tû›ã-Z4¬ ™ , ®]»*QÍÖ×LÞˆ  Và»Ü -Äp‚€öo�h†©< h7…)º‘œ^P�nkP:€€€€€�@P62|° A{ð†Z   à �¯¾úÊrn÷Üs�å4ñ’À�ý—;v‹ÌoÞ¼I5kÖÔˆ´Ípåt,hß¶m›&úÒ¥K%‘¸Ú‹ù±cLjÅëk×®ÕÄeqxùòå5¿é Ú9‹É×­[GÕ«WWâ_»v�&L˜@/¿ü²&�¬¬,Ƀ¼:ðaL®×Š+4¿³gùPþüù•ßÏŸ?/y^î¹ç4qõÄÉA´;Á×+{0+o!à6¾ýöÛ’ØœŸ]¾|™Š+¦ôï�7(--�ø¶À—,YÒÌp�âèyªçüx|!€€€€×‚²ì57”   `Ÿ€öoíWÞbJÚ-‹Ft7T£Ñ”      A%”� ,ˆ@ÐÔA‚v�€€8B‚vG0*™øuÿåĉÄÞ½ÅÀâóž={Ò?ü@ìýÌ™39â¼ûî»OÖr„P‚vùy£F�$!=ç=uêÔù²'î={öPRRRŽgì…›E˲gzu„îÝ»SÞ¼y¥´;wîÔÍWôúΑ‚"hw‚¯ö`VÐÎõ† æ8p!·SÏÛþÈ‘#iÒ¤IJßóŠæÍ››Ì|óßpèÐ!) —Á¢øP7 ˜ÎA@@À� ¬Ûh:’€€€D‰€öo=k9íž¡¶_�_Tí·ÈÙ”W¯^¥Õ«WSîܹ‰Òx!‹Ó" nåi½â5}4ûƒ‹ùÚUödôÓO?Q­Zµ¨L™2QíŠM›6Ñ÷ß/Õ‰Ù<ñÄT°`AWë$r¨_¿¾ÆÓŠ«…»�¹Q¿­½. D–!`¾‚iÄ*Ì‹±ÚsÎÕ;žæ¯>ø@ú®ãÀC´hÑ‚î¿ÿþˆ`e#à "q-h�Äe›Ž$¤&xhݯ~Þá¿Ó›4i¢+Õ¢eË–Q«V­t‹ñÅ‹{x=z´¡á—(QBò°ýðÇŒ{öìY©¾ûöí3ÌOŽP·n]Z¾|9)R$Gš%K–P»ví”ßÍ®ÙCx�5áó“O>IÌ„÷�ÜnóuÚìð•†;¡'hß°aƒdrèÓ§Íž=[sÛ@¸þÙµkÕ©SG‰Ò¡CZ¸p!åÊ•ËínEþ   �ƒ@PÖ�ѵ   ±CÀû·žÁ‚ Ý3Ôö òó‚ªýV9—’¯Î¬X±¢’azz:±Ø7ÒJ·òu®åñ•S4ûƒF»té¢íµ×hèСQëq¡™mþÃ?”¼Ü¸DÛ·o§ÔÔT7‹t5o£~ Z{]…‰Ì50_¹cׯ_§[·n™Þ¨â¸¼¨xï½÷ºS¡æ�ó¢hWl7yòä¡„„„ˆz˜7ÐÕáöíÛ1a‹ñ4ýýï'Þ—{\¿~}DßSAÙÈðÁ‚í6g Úm‚C2ˆ´‡îd¿ï¿ðß­,ž9s¦®wsnYþüùiÔ¨Q’WöbÅŠ…ll(�ç7n¤—_~™6oÞ¬›vüøñÔ¿S‡€ïܹC|€8++‹V­Z².½zõ"þ�íòßRbdþ­iÓ¦ÊÏï¼óuëÖ-ìˆå¿ÇYø,‹ê;uêDóçÏ7½žÉtà_'íÁ_5æúì³Ïæ@Æ7 °7~¶K9\ºt‰ªW¯®Îàgìm�J¶©ЬY³”¨+W®$>¬€  Ñ ”uàh°C™   öø`ÿÖ^Åm¤‚ Ý4¯“ø}AÕkby|Åj¹r唟�òÌàV¾Ñæ«åG³?ì\mêçììlJIIQ²çÅá½{÷R¾|ùÜ*RÉWäÀyjÖ¬éz¹n`Ô¯Ak¯[‘oN˜¯œ· q3Òj ¿ÿýï‰70Y°©PÙjÙ±?ÞæÅPvõ /ÐK/½d»ëøšõ¢E‹æHÏ›¹ÕªU³�¯ ãmþzóÍ7©_¿~ ÚaÆIW ‡SõAP62|° A»‘±…xA»MpH  q@‚öÐ�Kû/çÏŸ'ö‚ηk±È7))IºµSÏ»¹^‹�þîçü¿ýö[é6+^?á[¬Xplw-…EÌr}ùf,þ›©@�T¨P!ºï¾û7ò¼æ©=8Ñ?þø£t“,‡ÄÄDúÕ¯~E<ð€nÖÓ§O§?üáÊ3>(‘‘‘aX ¾E lÙ²Jà¾ÇÎ�;G•+W¦3gÎHy�îÀ�Ò‡paäÈ‘Òás9Ì›7O×3|¬Ù ê   » h�ݾCÍA@@ V ø`ÿÖ3t´{†Ú~Añ´ j‡’[·òµÓF¤�@”m`âĉ�%S¦L¡!C†xf.\ “'ORîܹéÖ­[T¦L͵¡žUÄ¡‚Œ6‚Ö^‡°!¢ùþ0²kÕ÷e”H=´‹�âkŠ333#>üæKXTÊÈ~‚6/ŠvÅ7 üë_ÿ’Ú5³@º~ýú´mÛ6éÉžÊäA{xã4²¿L;lRÑÓhà«éÙk Õ”� ,ˆ@ÐnÕøþ/>í6Á!€€ÄÚCwr<í¿Dëï®8bRÁ׸§§M›FƒV"yi×Yá�ݘ1b€€¸O (ëÀî“B   NðÁþ­SM1Ì‚vCDÑ�O ªvh»%t+_;mDÚõ®ÕܳgOL Ê£mר`ˆv·üh¾?‚j×z‚öW_}•4h@ׯ_×/$òÇ|•5Ï“&LPÚMvòòåË©uëÖJìN�:Ñüùós8زe‹äÌB-Z´ ´3oˆ&� ¬G“!ʰFÀû·Ö*AlÚ#€çUR§T¯^½J¿þõ¯»ví¢Zµj9ÖΟ¸rÈ›7/åÎ�;¢üYptëÖ-)ÎO"º•oD�5™8\Ýõ²�ãó³L+VTðòµšìµ$))É6r®“âÁsŽwäÈ*W®\Œ¶Õ� ¬©OÐ:ìßz†‚vÏPÛ/ÈéUQ¼cU¦×’Ï?ÿœV¬XA³gϦ3gÎäˆòÇ?þ‘ºuëF�=ö˜iÿþ÷¿%�ìéR�'{Ììß¿?uíÚU Y´»•¯é†™ˆ¸ÿ~úÛßþ& –/]ºD}úô‘êÎ�;G .¤ñãÇk˜T­ZUbÒ±cGéŠF9pÚ÷ß_ê—�;wjJ7nœäõ»`Á‚&jôs”£G�ÒêÕ«iýúõÒÿª—Ë×?²—ŒÇÜ´ØÚÉþàÃl‡Ì�=®0·G}4lû˜éŒ3(11QŠÇbqö¢Vú¿ûî;Z»v­Ä^äÄ}õÄOÐÓO?MuêÔQ„Oá*9hÐ ©Žr`Ûhß¾}È$ÿú׿¤Å`Ñ~x˜ùð Û¶mSÒË}סCª[·nÈ:±”YÈ|¹¿Ë–-«äãV¹bC�âkÔ¯Fí 5NYœúᇠeW­Z¥©>‹i322$4fú8Õ^ÓÝbDÌW?_�kõ½ÄùJ=°•/_^³‰gÑ´4Ñ�´s†,P}饗”¼ynýßÿý_zýõוÃG<†ùýX¤HSUæƒ|þóŸ•1Í ™øÃ(_¾|„yñçï¿Î‹¢]±Wu®«üŽ´z€lÆ Ô¤I“�vcö›ß[?#4ÚøwúýÈßI*TP¾±ù¾ ¢hÑ¢¦æŽ”� ,ˆ@ÐnÚê´!h· É@@@ @к“�Þñ³9­‡ú¹î‘Ö�×îÔ7 FšŸ:=ß ÈÎ,x?‚vsdy_¦råÊÊß༾ÊûŒ¼ŽÄ‚wÞOd¯írX¼x1ñþ€€ø�@PÖ�ýÀu0GÀû·æ*ê@,Ú€èvN/¨:)hÿâ‹/è™gž‘ùä“� ébÙì]˜ßýú²G~½Ûôò‘ž šÇF|Ý*×-¾Fýj¥½œ&Mš$Ý<¢þöàñɇÉäÛ;ÂÕ—ó`írà[0øk˜ý=/ŠvÅý6zôhé` hê@ßéÓ§5ž¼§L™BlG<äî»ß[úã…¨ƒ[ß[rz‡ø°‹Þ»Zo^ÊF†D h·ú¢ü¿ø´Û‡d    hÝÉNï¿øÙœø 5ÿýÊ΀Ø�¯Mó sÎ_ëÙSûåË—¥„ü75;ÿ9{ö¬r“"ÿöÈ#�XÏ)@@@ÀEAYv²p˜€öonQèì h÷ µý‚œ^PuBÐÎb’ жmÛ4 cïÔO=õ)RDZ|bïÒìÙPôìJ”{áªP¡Báí{ï½G¿ûÝï())Iò4ÍÞ2;uê¤+ÐÕй•¯ý^ ŸRÀ©c³PŽß{ï½Ä,X|µoß¾�¾úê«ÔµkW*X° }ÿý÷Äÿž;w®&þºuë$ï´b½�ÊÏ»wï. ¾ä…Dæ»cÇIL/Ц�9’C¬íVDS Ê‚qQhÎ7°­—)SFB�?^òüÜsÏiP«½ùª0ö쪾 ÕŒ§ØpöügŸ}Vë±ú�;wJ×wŠýö·¿ýMºý@ŒøºU.×Á ¾N ÚűÂÙ!C†Hد_¿.œ`ûøè£�4QCõ§íÅ|õóÆæ+wƳ]²d‰t˃ø}Ì·P¨o¼°k‡N ÚyV´’ëÈùó-êrfE]¼x‘RRR”g|¸…±òM4˜éq?΋¢]ñ·å»ï¾KÍš5S<öóæ>n(T¨PXó]°`�ô½%þF;zô¨ôÍ&‡P6…ï­Ÿ ½—ÝúPw,Û߸ Nã1͇ÏÔ·å„3„ ldø`A‚v›/LÚm‚C2ˆ´‡îd§÷_âÀœÐDˆkAYŽëNDãA@@ Æø`ÿÖ3b´{†Ú~AN/¨:!h½rëX[§N݆Š"ŸP^ÚY¸.Šh?ÿüsI,&%²¨^së ÚÝÊ×~¯†O©'€cA {ò®Zµª&±(¼Q?üç?ÿ)‰²Ô�ES#FŒÐx“e×üùó%Ï,ê����ƒ=P½eÊiØs÷‰Z8lØ0š $ߎàf{�g{10_i¹™y/q £ñ¤×~ž¯ìY�ùTz‚v+òøfŒV­Ziæj3®Ôï/±¶|SL… ”÷¤Þ»óbaâÃQâw¤_æÅP‚vž“‡®¹Í&Ü»}ܸq4vìX©YlWl›|S�™qŽï­_n 'h÷òý(òÓ;|jö ÊF†D h7ÿŠDL� X݉°8$ˆqAYŽñn@õA@@ ®ø`ÿÖ3Þ´{†Ú~AVTõ<-Û/�èµ×^£¡C‡F’…”Vôª( ÚE¡^ZZmÚ´‰Ö½fÍb¡‰Dq¶[ùF $L¢ g̘1Äb©PAl£‘ð&œ€K.C´##Q½ºnb_«…Ânö‡á˜È0R�¨ÈÉÈ‹«ºüiÓ¦ÑàÁƒ•ŸDᤑ§ÒPö ¦3['±>sæÌ¡>}ú(Åñu£ÜhòµÚ^3žùOŸ>-݈!(ç+7ÛëÖœ…ù*'Y£÷§0²/½þòó|å–}Éùê ÚyÎ|üñljò©Ã­[·¤[øù€ßbÂ^ÂÅÀ¿wìØQóóÚµké‰'žP~ ÷>ûYïfÌ‹½(+++ìÍ.ÑœÃ}‰7j„:TÆBëêÕ«ÓñãÇ%»Q¿ ÌŒs|o™´{ù~Åóâá³pó]P62|° A»Û/Vä     °ºÿt    ñM (ëÀñÝ‹h=€€Äìßz ‚vÏPÛ/Èꂪ(x³_òÏ)E¯Å¡òcñ{™<þ¼$$ãÀ^¨YhÆÿþðÉÅ@ríÙÙÙ”’’¢<×ó®W6{ž.[¶lÈtnå)×péE”(,ÓŠ}n$¼±#h7ÊS]'±þ,$ìß¿¿ÅÍþ0#3b§gwá„å¢ý%''KÞñÙã¹ìu[¯¯óäÉC³gϦ¾}û*�ű¶|ùrjݺµò|ÇŽT·n]CÓ³*0–34JgÄ×(}¨Š‡Kç&_£VÛË¢ÍÞ½{‡í£±çf{ ÇfÌW9Á½—8…‘}éuG¤‚öX´/™ƒž Ý¦ÉJÉ @™™™9æi=/ÐGŽ¡råÊiŠÓóÄÏ‚ÛòåËkâa^ô÷¼nNægõë×'þ^åP¢D ÉÃ?ß‚£ëׯ§¦M›*?mذ�5j$ýÛÌ8…ÚøÞj«û]äåü%Ú÷ý¾}ûÂÞ4 W:(>X� =’—Ò‚€€€X"`uÿÅRæˆ     8AY\Ç A   `>Ø¿õŒ.íž¡¶_�ÕU§íj1²Ø «üñÇÄž�W­Ze©‘¢ �=¥vîÜYÉcøðá4iÒ$Ã<�~nåkX±" mŬ�ˆñ�Dµ_¬CÏž=%/«áDÚr9zÞ‰—-[&lp³?ÌÇì° ×b[#èö‡GŒn5U–U�±œ�(Ê=ñu£\7ù�3«í5søÇhì¹ÙÞHl3\Z#ŽvÆœ:�3ÌWË5ór´æ+·ìKÎ×IAû{ï½GmÛþ"šë>yòd1b„ò³žçu>ÀW¥Jå¶…PžÜ1/¾Aýúõ kFcÜÍyѨì·ÞzKsðlݺuÔ¼ys¥=âÁ¾ÍfÏž=ŠèÝè=‚ùËŸó—h… &>xP @é.(>X� ÝÐÚ@@@@À)V÷_œ*ù€€€€Ä&� ¬Ç&}Ô@@â“€öo=A»g¨ídgAõêÕ«Ä"1ðÇõåË—)55•Ž?.=fO’iii’'u½Àž¤r<ÚµkuìØQÉÇj EA{8¯Þáò6s»•¯ÕöZ‰ïG�¨ïÓrÅ>éÔ©ÍŸ?_]ºÙf„cb?ÙÇ×â3+ý,ÆE“ìÕ~Ö¬YJ4qÌ„*˪ý„ê7ñƒ_7Êu“¯Q}Ýh¯‘xÒÍöFb›áÒq´3æÔiŒ˜é�QÌWúŽcѾd[ˆTÐÎ‚Ô &HBvÑö‘�²HùÀ�”””¤DoÐXºt)µiÓ&ÇP±:>ÌÌÇnö£Q}ƒ6/Í/§N�¢’%K*ýªþžáÅçãÇ�§ŒŒ %¾/Ì_æínÚ½8p!h'òÁ‚ín}¸!_�ìì¿#€€€€@ü€ =~û-�hðÁþ­gM‡ Ý3Ôö rzAUi˜ɪ[°ÿ~ªZµªn£Úµk'y--R¤ñÇ|bb"åÍ›—Þ}÷]Z»v­’ÆHÐþꫯҟþô'CpF‚dQãT¾†‹ ‚‘ LÌÚˆ�ßHÀÅñÅ:Xˆ²ËŠ+*Å8�233%{p³?ÌÇì°óJÐþä“O‹$e/øƒ ¢3f„3¡L̪ýÈùˆýÖ«W/É+¿üG¹_7ÊuRÀ&ò5ª¯í5{n¶7‚))lR#ŽvÆœ:�3ÌWÑ„ŠãÉ-û’óÕ´/^¼˜6lH×®]ËQ<Ï[òÁ>°?øàƒ¦«ÈéZ·nM+V¬PÒðm4éééÒ¿ù9 ãYÄÎ!œ÷f«ãÃÌ|ìæø€5j¤ñ`ªŽÀ°V­Z)? ÚEoÕ¡ÌÒHÌ-ЉœÊ×Íab$(Ë6b Æ7pqüE‹QçÎ�•¤Ã† £I“&)âæpíß²e 5hÐ@‰Ò²eKI˜;wî‚v'ûÃŒpÌ»pý±råJzúé§•lYØ6mÚ4º}û¶eáÛøðÑõîÝ›æÎ�rÌ„*ÀªýÈùdggSJJŠn¿ñ�F|Ý(×-¾zíyã�7¨_¿~JûÝh¯ÑØs³½– Òd«ýŽùêg°Fö¥‡ß »hÌW&M%¢hz‚ö={öPµjÕ"Ê7Tb>€÷ÄO(�Õ³D¯Üü~œ|¸tˆAF|Ý(×M¾FãÌj{EA¼p£±çf{ Àf#ŽvÆœ:�3Ž‹ùÊœ 4íK¶=A»�fÌšù¥K—¨zõêtüøq)‰ÚC³8W†››1/j ùm^43¿\¹r…j×®M‡’ªŸœœLì‰�o5ùõ¯­4‰o èСƒ¦‰FïÌ_æ=´{9AÐA»Ùwâ�€€€ƒíÁèG´@@@¼"A»W¤Q€€€€L‚vÿÙíwûÄ/‚ömÛ¶iÄëì‘:++‹råúEP'š{«nÓ¦�ä¥[¢Mª°¸}ùòåO—z¦ÉqZ·n­<…×nåëæ0ñƒ@T䯮Pbb¢aÓßzë-êÛ·¯O-ôu³?Dnü±ä15\ؾ}»äaUzÂýpýÁ‚¶Š+*éÕÞè ADËݰaƒt ‚QÓ�3†Æ�g”Œ,X@]»vUâñ¸f/ñr0æ¹Q®—|ýà¡ÝÍö€Í˜¯r‚3z/qŠ ÍW6ÍÇt2¯í\±‰'RFF†RÇuëÖQ³fͤyX¾¥Æè݈y1rA»›ó¢A»ž-°—~>tÉžû9ð�þF*T¨�Ʀ�Þ›ß[æä¸iâD$ÚEéÒ¥¥þMJJ2œ³‚²‘ბ¸þûßÐÐ@@@@ÀQ´;Š™�€€€@à e8ð…‚€€@€ø`ÿÖ3šÿϳ’"+(®7´�^P�ÔC»è™T½êuµè=—㈂vQ¨J¤ÎŸEþ=zô yóæ)?‹‚d·ò�̤çöƒ@”ÅZ%K–ÔT”Y–/_>låùðBýúõ‰>ÈaÑ¢EÔ±cGéŸnö‡ÈmæÌ™Ô¿ÿ°õ}ùå—‰ßr°*h?wîU®\™Îœ9£äa†“ûY²d µk×N‰jÆ8G9˜bé�¥HÞN”ë&_£qf$D4J¯×ÇFâI7ÛkÆæìıÊÁª—]#f\gÌW桱h_²MFCÐ.Ú*‹—‡ B¥J•R†ÊÛo¿­¹ICCnÌÇnö£ÑxÚ¼hf~Ñûváï‹Õ«WÓÚµk¥.gÛÈÌÌ$y[¶#^˜¿Ì{hwÓîÅq{öìYªR¥ŠòmÇž÷.\öð®œGP62|° ×ÿÛùCûœÞ±_¤ˆAYŽÖ¨#€€€ÀÏ|°ëYW@Ðîjû9½ © �ÅÉ�;wVd$²½yó&5hÐ@#pæÄ¢ ýÊ•+T»vm:tè�’÷Ê•+‰Æ¡‚žˆQ$»•¯ý5Ni$(spC ÊgöÎ=wî\¥¸ñãÇk¼ÕêµÄH°îfX€_¸p�*T¨ £[´3ƒAƒÑŒ3#FŒ�<ûŽóúë¯ÓСC©qãÆ”’’¢í_¿žš6mªdJ0'–#Ú?7òV¯ÇâàÁƒT©R%%{#až[åºÅ×hœYm¯Ñ\È Íˆ'Ýj¯‘=Ú}nÄóIï1öÚ®¾É$hó•]û1›.‚v~ò 0òí2ùóç§-ZìãÀÿæo–%J„læÅÈ=´»ùž53'sù|X�ßÓ²g~±Ãù¶—ÔÔÔv`ôáøÞ2w ÇM;;.;;[ú&“ífgjGãAÐî(Nd    Ž€Óû/      l´»Ñ:ð#Úý×+q½¡íô‚* Š«V­JÇ�—z:”'”ˆ"öÂüÅ_Ð=÷Ü“# ‹tžþyš:ujŽgzžÝEoÙ,cáûÃ?œ#= åŸyæEh&Gк•¯[CÅQnÛ–-[¤Ãê°uëVªW¯žnÓYÖ¤IÍáaOŸ>]ß­þàìåŸö еŸž={Òüùó5ÏìÚÙFù0†:¼ÿþû’2TÓèÕõôéÓT´hQ%‹ääd:pà%&&†5==%�ÓO?ý”zè!Ý´cÇŽ¥qãÆ…-ËH˜çV¹nñ5gVÛë” Ý­öb¾ÒÀ|õ 'æ+Î�¿6nܨ|<òÈ#Ô¬Y3GL/‚v®øš5k¨eË–ºmèÕ«ñ·Œè•[ó¢3‚v·æE³‚vîS½¾äßÃÝFbô‘mß[?“0z/»eâ_°`uíÚUùÙÌû]Ž”� ,ˆÄõßÿ޼8‘ €€€€€iN￘.A@@@b’@PÖ�c>*   qJÀû·ž‘‡‡vÏPÛ/È�Õ;vHž‚Y^³fM]Áo¨ÿûßÿV_c EcÑ2‹aeá0ç»{÷nzî¹çhß¾}ºY 0€¦M›F¹sçVžïß¿_Û« }×­[GÕ«W—~¾uë}ùå—’P~íÚµ9òÖ$»•¯ý^ ŸÒHÐ#¦vÃC;—Ê»þÒ¥K%±¶ZÄwìØ1ÉÄ>aAyùòå5Uv«?Î�;G•+WÖx\oÛ¶-ýùÏ&VÊ�=�wêÔI×6í]ˆ' IDATÚE/¾r9ìў휽øÊáüùó’`‹Ç†:艥nܸAiiiÒ¡œ×½dÉ’a (”èNKœÉµk×h„ ć ÔAïÀ‰‘0Ï­rÝâk4ά¶×ŒàÍŒxÒ­öb¾úå“óÕ/^Ç�š¯8Ÿ… R—.]SKOO§M›6iÞóví0Z‚öK—.Ißò!@uý7lØ@�5ŠÊ|ìÖ<oó¢™9Yî`½o ~6eÊ2dˆ®½GäDøÞú™„‘ý¹e÷êÎÓó˜/Þ*nÐe#à "´Û}a"€€€€€enì¿X®€€€€Ä � ¬Ç pT@@@@­Õ�½·í^‹•Æõ†¶T‡.‰„Å�’’B>ø }ôÑGšG,tîׯU¬XQó;ǯT©=ûì³Ô´iSbÉþðš1cF޼YèÎBÞÕ«WkžñïjѼž Ù­|m�<ƒ„F‚1¹[‚v.çĉ’÷Q1°8š=œÿðÃÄÞ}Ïœ9“#λᆱñp)Gp³? ¤k?,ÿÍo~C|Àº®cÆŒ‘T‡’ªgGÐÎ騛:‹ÏõD�Ý»w§¼yóÒž={hçÎ�98…ó¼>räHš4i’’†w4oÞ<¬…–ˉX€Éž÷¹ïônOàúp]“’’4å óÜ*×-¾FãÌj{�´»Õ^ÌW˜¯dpk¾ÇL‡$‘{®\¹"6¿h Ú¹â'N¤ŒŒ Mø½¸wï^Ê—/_Tæc·æ‰x›­Ú™¹Þ÷ï‘#G¨\¹rºv`ôQ'Â÷–± Ý-»W÷ßdÅ·îÈß…ü½Ë‡ CÝp#v|P62 h�øµ… @@@@bˆ€÷_bª    qG (ëÀq×qh0€€Ä0ìßzF‚vÏPÛ/È� ª.\ zõê)b�p­k׮͛7�$í¶mÛrDŸ9s&õïß_ú�ÅEÏ<óLáº^,¼Ý²e =õÔSŠk$/[¶,‡GX·òµß³¡S.Y²„˜›Œ„²z‚v=r~W¯^¥Zµj…q«kÇ"«&MšèеCµ‚ËoÕªUÈFºÕgÏž•ìL"…ëŸaÃ†ÑØ±c)55U9¡g?F?¹ .›9…º•@¯.uëÖ¥åË—S‘"Et«Ê€9O9ôéÓ‡fÏž­ñŽ/&ë»xñbb�Ô£G�64×%JHcéá‡Î×H˜çV¹nñ5êW£öZ§òüV£F ÍØ 5Vݰ'C°Á*ÌW¿@Ú|%Ž™PïcfFâ{‹ó°â5ÙN™r¾iD<�Î+·º,Ì‹oHÃþˆÖ¼¨÷=îû‰mŽÅÎrhÙ²%­X±"ä-Fï‘ ¾·þA|³ŽÑ÷¯›ïÇ]»vQ�:u”:X=˜”� ,ˆÄõ�öHÞYH     `�€÷_¬·)@@@@¼"”u`¯x¡�È ø`ÿ6òF˜Ì‚v“ ¢ͯ ª×¯_'¢?ÿüóºxØ 4?ûío«ˆoÙ+ì€héÒ¥š4o¿ý6õêÕKùíæÍ›´`Á1b„®çoö¶=yòdêÑ£‡äùµfÍšŠˆ˜½†geeéz„u+_§ícýúõ’Çz9¼óÎ;Ô­[·�Å|ýõ×TªT)å93ž>}zHÑ3‹Çêׯ¯èÔ©ÍŸ??¬]îofqŸëyç¹_F�%ye/V¬˜!·úãÚµk’ °·½À7¼òÊ+’mr»X¸$‹Ðù6�ÌÌL »5kÖ‹ÖäÀq8éö>ÿÁHå¯Zµ*$¶wþ�íò½z‘/]ºDÕ«WW0cë³ðþ¼$a¯íyò䡼yó†ŒûÝwßÑþóúé§Ÿ¤ø ¤âÅ‹‡›¡íV¾fÊŽõ8Üì“=›r?&%%IýÊ˸™öºÑ²m^¹rE²�ÄÄD*\¸pDõ4Ó9‹ÑeN,šã?n (@… ’ÆŠÙÀ‡Ôâ|žgdd„LnäyœûïÛo¿UÆ ØY ÏŒ" ^—ëßHÚìeÚxk¯Sl1_™# û ÍéÔ©ST²dI%‚‘WnuN˜ÍÙŸÝXA·[Ì_æ,Ã);àÛlÊ–-«ZºtiâCùòå3W‘»±‚²‘áƒÚM["‚€€€DJÀïû/‘¶éA@@@œ%”u`g© 77 ø`ÿÖÍæiò† Ý3Ôö ‚ª}vH A pîÜ9É ñ™3g¤æ°(Ÿ=³0^/ (Ýb­rÝjò&0qâDÍ!"+›1/†@ vŒ9’&Mš¤TxÞ¼yôì³ÏZj@P62|° A»%ËCd�H`ÿ%zH    ñG (ëÀñ×sh1€€Ä.ìßz‚vÏPÛ/ ªöÙ!%…À´iÓhðàÁJsÂyi�–€2Zå¥�ÑÿÐóØÌŠøf3ó¢JˆÑ'ðÕW_Q¹r唊ØñÎΉƒ²‘áƒÚ£?,PˆØ‰›®FCA@@@ÀAYv2ð„€öo=i§´çîYI‘×ÚXP�Ìx�‚@àÂ… T³fM:~ü¸ÔœüùóK^ÚU/,¥™ÑPF«Ü ô/Ú à?—.]¢ôôtÚ·oŸR¹Å‹S‡LWó¢iTˆQ#pçÎêÑ£±Gv9X¹‰A]ñ ldø`A$®ÿþ�Ú`@Á    §°ÿ§�fƒ€€€€MAY¶Ù|$ˆìßzÖjÚ=Cm¿ ,¨ÚgçFÊk×®ÑO?ýäFÖ”��@÷ÝwŸ+y#ÓØ'°|ùrjݺµÒ�N�:Ñüùó)W®\šÆEK@­rc¿gÝkæ+÷Ø"çà¸uëݼySjØ®]»¨{÷îÊ!"þ-99Y:H”˜˜hºñ˜M£BDˆ�-[¶Pƒ ”ò[´hA,hçïr«!(>X� Ýªñ!>€€€€€mر� A@@@ . e8.;��%àƒý[ÏÈAÐîjûaAÕ>;7RöêÕ‹æÎ�ëFÖ’'ØM›6Qîܹ]É™Æ6ö Ú¶m[Zºt©ÒþÿmÚ´Ñ4,ZÊh•Û½êní1_¹Ë¹‹À AƒhÆŒ!uðàAªT©’¥Fc^´„ ‘AÀsâ 8\�#GŽP¹rålÕ%(>X� Ý–"€€€€€Ø±C i@@@@ ~ e8~{-�Ø#àƒý[Ï AÐîjûaAÕ>;7RöïߟfÍšåFÖôä“O{á=n»R2�IçÎ�£Ê•+Ó™3g¤úçÏŸŸ>ÿüs�ð*ZÊh•“éQ¥1_yÅ‚@8AûÖ­[©^½z–Û‰yÑ22$ÏðAÁnݺI·ÝÈañâÅÔ¡CÛuÊF†D h·m…H    `•ö_¬C|ˆoAYŽï^DëA@@ ¶ø`ÿÖ3`´{†Ú~AXPµÏÎ�”cÇŽ¥©S§ÚöÜ®N�=öeeeAÐîFÇ(Ï/¾øBã%˜Eí_~ù%-ZTjåÂ… ©K—.J‹gΜI,lv;D«\·ÛËùc¾ŠåÞCݽ& w£ÿö /Ð#�;¤� `Oí—/_–šÇ4/^œ¤_½z•NŸ>-Œ¸}û6.\˜òåËç:Šh•ëzÃP€@\¸té]¸p�òæÍKׯ_§‚ FØ¿µZeÛñ!h·�λ„XPõŽ5J;‚²‘áƒÚí Ò€€€€Ø"€ý[Ø�@@@â–@PÖ�ã¶ÑpˆA>Ø¿õŒíž¡¶_Tí³CJð‚@P62|° A»‹2@@@@$Ø�!€€€€X!”u`+mF\�èðÁþ­g h÷ µý‚° jŸR‚€€€€€€‚²‘áƒÚ½0X”    Àþ @@@@À � ¬[i3â‚€€D—€öo=A»g¨í„Uûì�@@@@@¼ ”� ,ˆ@Ðî…Á¢ ‰ö_`    VeØJ›@@@ º|°ëÚ=Cm¿ ,¨Úg‡”      à� ldø`A‚v/ e€€€€H°ÿC°B (ëÀVÚŒ¸   Ñ%àƒý[Ï@ÐîjûaAÕ>;¤ŒW¯^¥Õ«WSîܹéÆ�T¸pajذ¡' 8qâ}úé§tÏ=÷ÐO?ýDµjÕ¢2eÊxRv¨B6mÚDßÿ½T'fóÄOPÁ‚£Z'NÇ[ýúõ©X±bNdíi|ð�4_pà9«E‹tÿý÷{Z  à/AÙÈðÁ‚íþ2mÔ@@@Mû/�î^4@@@'”u`ÇÁ C×ø`ÿÖµ¶‰CÐîjûaAÕ>;¤Œ‡¦Š+*NOO'u³ÀÝí°páBêÒ¥‹RÌk¯½FC‡u»Ø�ù/Y²„Úµk§añá‡RÞ¼y£V'ìO.\ “'OÒ7ß|#‰«ï»ï>éy!塇¢%JHbqyaÅ­ÇÛöíÛ)55ÕU³T‡¿ÿýïÔ¡C%MZZ­_¿ãÔED�`ÊF†D hÖÐ@k@@@@À×°ÿâëîAå@@@@Àw‚²ì;°¨€€€@H>Ø¿õ¬w h÷ µý‚° jŸRÆ�¯¾úŠÊ•+§T˜…¢,|Í•+—ë�øÇ?þAmÛ¶UÊyã�7¨_¿~®—«W@vv6¥¤¤(�J—.M{÷î¥|ùòE¥>(Ô®]»F}ôMž<™¶mÛfXÁüùóÓ¨Q£¨uëÖ”œœlßíâxûì³Ï¨fÍšnëJþo¾ù¦f®6lMš4ÉW\i82Ð%”� ,ˆ@ÐŽ1    àì¿x†�€€€@ e8��F€€€@œðÁþ­g¤!h÷ µý‚° jŸRÆQÐþä“OÒòåËãJÐ~åʪ]»6:tH鸃R¥J•b§#QSW ,[¶ŒÚ´ic» ¾y`âĉ”�� ›Ç�;wèÓO?¥[·nQÁ‚5·&Ø.THíܬ۷oÓSO=E«W¯VZ¹nÝ:jÞ¼¹S¸�€€@ ÊF†D h�!»GUA@@@ Ö `ÿ%Ö{õo eØ[j( @@@ >Ø¿�¤ú–ÒBÐn Wt"cA5:ÜQª· h'Ihœ‘‘¡€Ÿ2e 2ÄÛŽ@i¾%0wî\êÕ«WÈú5mÚ”|ðAºpáýóŸÿ ¯gÏž”••¥{XäÔ©ST²dI)mzz:mÚ´‰rçÎí(®ßÉ“'¥|Y8_¦Lb/ò±Ž;FeË–Uª_¢D Ú·o(P V›„zƒ€Ø$”� ,ˆ@ÐnÓ‘ @@@@À:ì¿Xg†    Ï‚²Ï}ˆ¶ƒ€€@¬ðÁþ­gÈ h÷ µý‚° jŸRÆ�x´‹¢ØÒ¥KÓž={bZè;Öçÿšîß¿ŸªV­š£¢™™™Ä·°]íu�…â,NgÏáýû÷Ï‘nñâÅÔ¡C‡¿oÛ¶�êÕ«'ýÎÏÙ›z®\¹ü(Ê5œ6m ¤ðš€öo=k2íž¡¶_�Ó ªwîÜ¡·ß~›þûßÿÒåË—éÙgŸ¥âŋۯ R‚€âYÐÎcrÀ€4kÖ,…äÒ¥K©M›6�EA À¢tµ}°ðÏ>ûŒ~øaÃæ±Gt©:tH‰Ë&8@IIIšôo¾ù&õë×Oú�…òË–-sÜC»a…c03®Y³&?~\ª={œgÞÜO  ñC (>X� =~† Z    Q'àôþKÔ„ €€€€¸J (ëÀ®BBæ   ŽðÁþ­£í —íž¡¶_�Ó ªW¯^¥_ÿú׊ðn×®]T«V-ûÔIÉe°Wß¼yó:šo$™Ý¼y“ø?ö\ÌdÜ{qdçzZ>pÀuÍ�;·)Žr|®Xžd¿yeïŸþ¹4^Ù¾Ôö(Wì�ü#uëÖ�{ì±°6æW{w²ÿõ¯IBpÙãyùòå56ezêDda8:’=¬³×ïƒÒý÷ßo:[>˜Ò¸qc:þ<µlÙ’*T¨@¿ûÝï¤ùAWœÏ=êÀóˆ<‹s¯Ý÷@vv¶4'ÈïžWÊ–-«ë…}ó\»råÊc˜ß ¿ÿýï¥Û *$‚zýõוC+Ìžm^o\³—væ*�•Â… Óž={¨hÑ¢¦û A@b›@P62|° ×ÿÇö(@íA@@@ ö8½ÿ{Pc+‚²l¥Íˆ    ]>Ø¿õ íž¡¶_�Ó ªNÚ-ZD�;wVÅâx!ªã‡O>ù$-_¾\DŠé̈éEïÕééé´iÓ&�]Ì—“,"üÍo~£ˆëCõ‹4·lÙB,Tô*ˆõ=pà±à”ÅÐá‹À÷îÝK¥J•’x›‰ÏâôÚµk‡Í—³,îŸ>}º)\t§¥¥…�¿lÙ2IjØ‹1 ¸Õ^ÊEÛá<ܲ³‚vö`ÍâÖ¡C‡5IzÎ"dfP¼xñ�ñO�:E%K–Tž³²pY}pALì¶½ñÅôÌ3Ï(j£Æòa€iÓ¦Q±bÅt£úÍÞÝèG±�zó”ÇPÏÅ9›…Ò�ÞMFá½÷Þ£V­ZI6äÃ"1>¸Ãs©ŒÇÕÏA@b‹@P62|° A{l™>j    1MÀéý—˜†�ʃ€€€Ê:°aC@@@À7|°ë Ú=Cm¿ §T�´«À,¼�3g�5*‡xœEƒ,è•�¢pØŒ ÝŒçn±>,ˆ|饗$ïófBrr²äñ8!!ÁLôˆã˜å§W{µg/Ê•+WÖõš-¦1H³—÷&Mš˜f¥ÎŸ=”³‡{½ÀBú¦M›šfÅ^ÚeÑ('Ò´{a?\¶ž•=5÷ìÙSºqÀJ`ññ'Ÿ|"Ý` Ä6½øâ‹ÄÿpÁM{Ǜٶ†YûÉÞ½êG=û5ËRŒ'zhççï¼óŽä)܉À·BtéÒÅ0+Q¤o¶_­¾Ü´ï�>úˆš5kfØV9ÂâÅ‹iêÔ©ÊühÔ¯z°6nÜèÙ»ÅtÃ@@ÀAÙÈðÁ‚í®X(2Ð#àôþ (ƒ€€€›@PÖ�ƒÝKh€€‹€öo= A»g¨íäô‚ªÓ‚v±e,l}þùçé‡~ X� =¶Lµ�˜&àôþKLÃ@åA@@@ eذ¡ˆ   à>Ø¿õŒíž¡¶_�Ó ªn ÚY(ÎBhÙ#»Øj¯í¡Ÿ7nÜ ´´4�Wr3ž±í÷¢6¥ž`“…�ìÉ»jÕªšÈ¢@RýðŸÿügoÃ,’1býùÏV¢²hœ=‹çÊ•K“wvv6¥¤¤h~ÓóN.G8{ö¬$xV{R6lMžB…“'OÒ£�>ªyì'Aûĉ)##C©·%+++,'±6iÒDI“žžN7n”<¹Ë�o P{¼Ö‹£ÇLÏ~œ°÷… R—.]”"¹xL„4ë Ù»woŽC~±w·úÑɹ!T^zÞüŸ|†¯ð¼V±bEzøá‡-UM´G#ñ¶[ï7ì›ùµnÝZ²g9ðûŠo�Þ|óMéЊ:1á¸bý_yå5j”¥¾@d�Ø$”� ,ˆ@ЛCµ�˜$àôþKLB@¥A@@@LÊ:°é#"€€€@Ô ø`ÿÖ3´{†Ú~AVToÞ¼I,fŸXLËÞƒ›5k¦x�fÏÑ©©©ÄâT½ðàƒR�4�ô‡_~ù%%''‡m¨W‚öPž¢Õ•Å›ì=›½¢sû=:¥È�½³g$ÝDb]Ù›; ‡ÕÞ”Õ EÏßz"Lö¶^¥J�ñÝ»wS�5Â6YOļtéRjÓ¦�”NôòÌuå¶©&Öù³­öèуæÍ›§<ó‹ ýÒ¥KÒøP{¥çöT®\Ù2'1�hfo Ó9eï×®]“ìáǤÿüç?’ÇïJ•*…mç AƒhÆŒJ>ðÀfÔÁöîf?º1GèåÉãdüøñÒ-f�=dóa�Ç{Œøßá‚‚v'ÞnØ·è�ŸYX� =f¬�Ø'îfÑØoZ    àÞ[µî¹ç«I@@@Èû·žõíž¡¶_P´íj±²Ü «‚ìPéôį")3"K;ž®Íˆ¾í÷Zø”Vëk†�ºD3mëгgObat®\¹ ›­çM}Ù²e’ÀyÑ¢EÔ¹sg%�áÇӤI“ ó4ÓÆhÚŶ6$LQÐnÆ»¹^vVí‡ó0crY,Vÿøã�%ó«V­²Ôd3‚v#OôflÁª½»Ù�–¹ùúõëôÍ7ßÐ�è“O>‘¼·Ë·OèÉã’íO-è¶ÊÝ­÷€ö-ÎKf�073mD¾âX+\¸0ñ"Ú]2~d  >"A»c�A»c(‘€€€€€Ú�á9€€€€€Úa   ^€ Ý+ÒæË‰ë m«‚vÆzõêUºsG¦ ,²¸|ù2¥¦¦ÒñãÇ¥g6l ´´4Éã²^`OÓ šGv‡œA4É¡ÌÌŠÀ×¼©š‹i•ŸU¡©™¶Ùñ‚,·N¬O§N�$Ñ,†ç½=3mŒ†ýˆešëaýXì{È�!ÊCöj?kÖ,åßfxè�##�8§1co×®]Ô±cGe~°Ú^¿ ÚÝìG«Œ¼Šÿí·ßÒ¶mÛhäÈ‘ºýÉÞÝù]j\Ý®`u“Ë1Çvò5²o;yr}Å›,Œ˜è�5Ú½²x” Ñ'A»c}×ÿ;F�€€€¸Nà§Ÿ~r½ `�ß<Êk{;wî¤õë×ÓÑ£G¥›G $ý‡  `�ÀG}$ÝL}ÿý÷K7�³ƒ3þÿ æ ÀC»yVˆ    ð Úýg q½¡mGЮ EáŸY­:O»â@#!£^½í��øº1¬ò3Ã@]O#q'Ç�DÐ.Š<H™™™’·g1ßW_}•þô§?b4ÓÆhØ�“BhQ Ë ç3fÌPؘ‹Ví‡ 0cû÷離U«êöU»ví¨J•*T¤H©Ÿ)oÞ¼ôî»ïÒÚµköÁj}ÍØB¤önh�a"˜5G’¿Óiùp8éÖ­›&k>ˆ²`ÁÅK»UîVûU.ÜhÛÉ×ȾíäÉõÍÎΦ””…›™¾¿xñ"=ðÀJÚ�¶hä þ%A»c}×ÿ;F�€€€¸N‚v×›.€µ°xý‹/¾�þ;uêTŽ´ÅŠ“n&E°O zõêôã�?JäÏŸŸš5kF¼O\¢D û™"%ÄÚ㨳ÑTp�íÂt(«¸ÞІ ý+*W®œbJz‚B;bE#¤C¶«›�ÕúZššiÛ¢E‹¤“ór6lMš4I·†kÿ–-[¨AƒJ”–-[ÒŠ+(wîÜ9í¢WòPùši£‘V/o;ùªD¬\¹’ž~úi%ëž={Ò´iÓèöíÛ–M„o;`8÷îÝ›æÎ�«äMA;ßÐдiSÚ¼y³¦]|ð5jÔˆ’’’tÛËýÞªU«°mðƒ½»Õ�–� Š X¼ÞµkW¥¥K—¦={öH ŽÌŒuõ­ö«œÖhÛÉ×hÎóœ3gŽä=Ä(;vŒÊ–-öý#æ!Ö‚v#Êx Á!A»c}×ÿ;F�€€€¸N‚v×ëÀâõC‡IÂuùõ">þøã’ã ^Ãæ°jÕ*ªX±bt*�RA@ &L˜@ùË_r´¦N�:Ô£GjÒ¤I@ZŠf€€; hw‡+r� € Ý=×Ú´CÐnUhj$îä!.zã6ãyXžDq¨Z´¾fÍb�»8ßeË–Ib÷pÁL�„°zùÛÉW-h7“Þî”Ébö^½z)ÉwïÞM5jÔ0ÌÎ Á¯ØN®ÄÖ­[©^½zaëcÆË¼ÕúZenÆÞ­æiØ 1�½•,YR©9{ËØ·o(P@úÍ*#«ýjþrØÉ×ÈÄ ãÇ�§ŒŒ Ã^äºÕ®][3Ÿ-_¾œråÊ2-í†X@K‚vǺ6®ÿþwŒ"2p�í®#V<®³x]öÀ®W*‹×{ì1Í�= ³S�îÝ»Óèѣݯ0J�€¸té’r³í¦M›è�wÞ!Þ7áÛk9ðÞϹmÚ´¡ûï¿?à4Ð<°N‚vëÌ�@@@€‚vÿYA\ohYÐÎ×;¦§§‡µ¸íÛ·SZZšÚ‰ŒÄçFâN†)ŠW“““éÀ�”˜˜h8¼õÖ[Ô·o_%^88‹Ûy!#!!!l¾§uëÖ–úÙ û9|ø°Æk‹Ú½!(ƒ¢pwÆ ’7t£à†àwÛ¶mñ:{¢ÏÊÊ +Üe/õ¼ %{¸ázëy™·Z_«Âj3öîf?õW$ÏySìèÑ£tðàAâ?üðñø '¨UÞ•+W$q6{-âOÚE›2;ŽÅ¹Îhîe®¢=2gž[CÝr‰} -€€€¿@ÐîXÄõßÿŽQDF    ®€ ÝYÄì�ƒ×q?ýôSi “ÿW/T«VM#\Wß&«Ž?vìX7n/^\òÎa¥³ý…Ü@â—ÀðáÃéý÷ß§_|‘x®å½+¶ó ×'Ož”ÀðœË7C³¸·cį­ å9 @Ы°C‚v;ÔÜM×ÚA´Ïœ9“ú÷ïÖz^~ùe3fŒ‚vgí¢·fÌ‹ÅåË—Û,b®_¿>±Z‹-¢Ž;Jÿ…Ã… –Äœ…  ™ï�;w¤kèæÍ›g©Ÿ½°ŸsçÎQåÊ•éÌ™3JÝÌp23%.Y²„Úµk§DU —Þª@œó2}¿÷Þ{Ô¾}{¥X³÷îÝ;l3D/ÿÙ¯‚v7ûÑL_Û�Þ‡*Uª¤$ÏŸ??íÝ»—J•*e9KQÔÍc“m9<´ëÍKê¶ëÁÇ Ç1#h?{ö,U©RE™3:tè@ .´uÁr'#€€@T @Ðîþ¸þûß1ŠÈ@@@@Àu´ÛGÌ~wíÚ¥×y”Ã#�<¢ˆ×Y¸J¼.¦c¯ìì��¯ÍÕ©SÇ~e‘@@@C€uîÜ™}ôQ:qâ„æ;0cq»ÚÏÁÿó?ÿ#9ÉB�x'A{¼[Ú  ö@Ðn�››©âzC;H‚v«Þ .P… 4bbÚ�E•Fâe¬,"gÁòܹs•±;~üxÊÈÈ;–�ë¢hÎlåÊ•’4TÐ×ëõs´ìgРA4cÆ ¥ú#FŒ ‰'Îyçõ×_§¡C‡RãÆ�¥+ød¡'^¿~½ä�@¤ÌÌLM½BÜ´ó¡^|’ƒ‘¸þæÍ›Òæ�ú`§õ« �ëæV?BôÆS‹-¤1etë�ºXïÏ?ÿGÒëçhÙ×½víÚšºóUz­[·9nÄ4z¶~úôi*Z´¨’Grr²äÍ>111ìx´3þŒlBá–.]ZÚXÐû£ZO-WXϳ»ÕúŠžÄ�DÄFmûÿìÝ üŒeþÿñÏ¢ƒBlh‹ÑRËWZR|Ias,+6‡ÇW¶œv‹Ò²$¶µ’rÈ¡œmJ�|W':;�<4¼\×ÿÖ�~Ý8ô3@ý,°B… òüóÏšt•2@��™3gÊC=$mÚ´Ý•=ÖqðàAóºþnþâ‹/‚§éŽíl¯V­¶øJ€@»¯º›Æ"€ àš�v×(]+È× ÚnÚµWòóóEC &®S§NÔ@_a½—h�qÿþý’““¶ãº3}ôQÑhàÐe^^žlÙ²¥@5´»h�µË¶]5¬º›øgŸ}&^ñÅÃúDwV¹üòËÃ~öᇚ/M„è~饗‚�ùüé§Ÿä“O>1»FG–©×Eëçt� p«Gèãñ´Žº£ý]wÝ%%K– 6õÛo¿5;Pÿõ¯ k´Pë‰'¤Aƒ&쯇–£cÿâ‹/.ô—g"ï¿x¡ïÿüç?ò—Ÿ¹¿ö·~ð_¦Lóÿu¼hXÛí½©ç¨‡­‹-lƒÝú&+О¬~Ô†êcko½õÖ`›6l(ëÖ­ s(´S yQéú¥£hæÝ»w7¿+«W¯nfŠ)bJÒkôƒÁÕ«WG}ê‚ÖOø¡»¼GŽ-gúôé&œ­¯9r$,@o·_MLW =Úï%ý²Í‚ L�ÿÜsÏ}ÿêî÷]»v�Ú#ñíÑž|í©‰Ž®Cð¶�v×úÇ×ó×)@@ éÚÙè#ð'°{4xÝÉWCë¡’ÑAº#°îήJý̶nݺɸ e"€¾Чoè“©õصk—¥/%­_¿Þ¬!†®·êïi ·ëÎíøA€@»z™6"€ à¾�v÷M�–èëídÚ�vH¢AF½oß¾}e„ ª aÞúõë‹îØ!XO>|¸ ëNzhw/Ðø�Aw*Ž<4€®;ä뿺yhŸÎ}æ™gÂv"ü\C�ýúõ‹ÚÏt×Àö /¼vKýyh`7Vp4]ãGw¯ÖðyàÉ¡•×Î>ûlóSýÐ>ò(lçõÁƒ˘1c‚—hè¿yóæ…¾EyÿÅ ´ë ï½÷^óå’ÈC?�:ÿüóeÍš5a/ià½W¯^vOÐó5`ýç?ÿYš6mjÞ¿úÅ•ÀoÇêdÚõþÉêÇÈ6vêÔÉ,˜æNçVo»ekˆ[¿˜pÁ„]ª_°hܸqØBOˆ|Ê€Ý~ ”•®@{ac<–¡~ÙFÿ |é$^ ]Ÿ€¢Osü}¥fú%•²eËÚí&ÎGÈ@í®uš¯çÿ®)R € €@Òüh×Mqþö·¿E5ÖÍ#ÁõF�™ÿNÕ¡Ÿoj`R?³6lXªnË}@_ 躢®!ßÿýaOÈ�‡¡_>Ò`û¬Y³ä»ï¾3§ëÚ•†Úõ÷·nàÄ�@¶ hÏÖž¥] €$W€@{r})Ý× Ú^ ´/Z´H:tèìËxÕÐNÿúë¯E?Ä þ ƒ 2àÐ݉5P¸lÙ²°��©�|¯ºêª° |d¹‰ V+ר­o´€oauÕÝ™¯¾úêB¿ZOýæü 7Ü5¬«=zÿ›o¾9fsÕ÷–[n)\�v�¾_{í5ùãÿ�ÖÆdŒ«Á\½·:ÅÚ�¹ýņ¸•æ@�´ hw�ß×ó×)@@ é~´ëμúèÞ½{ÍÓwï¾ûnó9š~Vœ®C?ûìß¿¿yª®7ˆLWOp_ð‹ÀÆ�¥sçÎfwv]k¶{è¦jº†ªY}êpàÐÛ5Ø^­Z5»Er>ž Ðîù.¢‚ €xR€@»÷ºÅ× Ú^ ´¯]»Öý‡~ƒú¶Ûn³¦A÷îÝEÿh =nŠv²~ð¯�@ ìü®&žÕ�bvÇ�–cu¼ë8Ð1pÏ=÷D½}“&MÌkúÞ ´Kw×1©»ò„Ó§O7v뛊ñîv?®\¹RÚ´il~´ßSqß0OÐ/¨©þ¾*l ŠÓÿõ‹Íš53ïáx‡–«»ëGú$} A  »ý(/ÒJw‹×/ŽDʵ:¾÷xã�7dÆŒf÷�ÐCŸ¡OÐ/l”)SFì|¹BÇ”¾&Ož,RÛª { €þ ÐîZ?ûzþïš"!€ €$]ÀÏ�vÅ õ¿õÉ»©Ü‰=²sõ3eýœ]Ѻ™EèúUÒ7@|,pýõ×›/7ÅÛ-‘>]C¿˜´bŊਗ਼Ðp»îÜÎ�@¶hÏ–ž¤ €¤V€@{j½­ÜÍ× Ú^ ´[é4+çh0Yƒ¼G�5;=ŸqÆ¢»ÇÚÍÚJ™œã\àÛo¿ÝÅZwz×�fñâÅå׿þµ£~ùòË/å›o¾ý�_ûZË«P¡B¡aïx-I÷øÑ0zÀéĉ¦-‚-W®\̰£µI¿œúåŽQ£FÉ�!Câ5?©¯lúé'Óýÿ¿ùÍo¤lÙ²1ï«ãFÇ‹öïYg�%gŸ}vRëèVánõ£[õ±S޾Gu ª½ŽAµ×>ÓÿMd,î­»Áÿ÷¿ÿ5ÿW/ŸwÞyRºti;UˈsÕJÿþÑq«í<çœsÂêh×/hL�:5êm>ûì3©R¥JðzýÀæÍ›¥D‰aA%@œ hwnøÿKðõüß5E B@Hº€ßí ¬é¦f×P{ºŽÆ�‹†!õi¨S¦LIW5¸/ à;�™3gÊC=d6}ÒÝÖ�ú% ¶ëß-ß}÷�)N7ÓP»îÚÎÓ7œ s}º´§»¸? €™)@ Ý{ýæëíl´{o¨Q#R/°ÿ~ÉÉÉ îb¯_êØºu« Æs €@ú6lØ 4V@ŸÌУG�¨UWÃí € €d»�vïõ°¯´ ´{o@R#Ü8pà€Ô©SGvîÜiŠÖÝdt—ö�¿�ܾ%å!à+�E‹I‡‚mÖGTêc�Ë–-Õá…^�›nº)øš¾'uqîâ‹/;_ƒï]»v û uåʕҺuk_ùÒX@í®�_Ïÿ]S¤ @@R(0nÜ8Ñ?º3¯~æVºté”ܽmÛ¶fßfÍšÉôéÓSrOn‚ . OÇèØ±£ù;@wiOÖ¡_bÒ`»nöÝwߙ۔*UÊÛuƒ"]÷á@@@ ´{¯W}½ M =µòرcòã�?&å¦ÅŠ“sÏ=7)eShæ èïú|=9¼ó IDATàÈËË“9sæH‘"E2¿q´�4 |ýõ×R¾|ù°ZèÿŸ7ož4hÐ@Š/n^Óó¦L™"Ç;·oß¾2~üø­xíµ×Âv�Òݨ4Ю¿ï9@ü%@ Ýµþöõüß5E B@@ Å-Z´�íÛ·K¿~ýLØ0ÙGàót݈BÔjä@H�@nn®ìÝ»W–-[&7ß|sÒ+¶oÞ¼9x/ µëŽíZ@@²I€@»÷zÓ× ÚÚS; õ[Ü3fÌHÊM6lh¹Y´hѤ”O¡™- ;=ë‡-K–, 6Dÿ»]»v™Ý0j�€G"wi­VÍš5Mýý÷ß/P[ ¾ëÊ•+öZä“ôÅO?ýTªV­ê‘S @�T hwMÛ×ó×)@@� èÓ u³=t¤Q£FI«ÁÁƒ¥råÊ¢ÿ;vìXó¹: €@úž~úi9r¤´iÓÆ<9#UÇúõëÍ®í³gÏÞRCí�=öXªªÀ}@@Hº�ö¤Û¾�¯´ ´Û/Ž.èÝ»·Lž<ÙQ±.nݺµ™Ä³ãvRx³¢Ðýû÷KNNŽìÛ·Ï´Gw—yï½÷ÈfEïÒ/<óÌ3rÛm·Y®Š.ÂÍœ9S.¸à‚°kô (ZŽ>E!p<ûì³Ò©S'Ëes" €@v hw­?}=ÿwM‘‚@@HƒÀ¸qãDÿTªTI6mÚ$¥K—NJ-ôI§ºÖR¯^=Y¸paRîA¡ €Ö:$5jÔ0ìÚµËü=�ÊãóÏ?—#F˜`{«V­Ì“x9@@ÈíÞëI_/hhOí€ÔÉ®~k;;ìþþ÷¿—iÓ¦hOm—fÜÝ>úè#©^½z°Þjÿä“Oä /̸¶Pa¼(ðÕW_É‚ dÔ¨QÁ/�DÖSƒìÆ “úõëGmBß¾}e„ Á×´¬!C†x±¹Ô @ EÚ]ƒöõüß5E B@@ M-Z´�íÛ·Ëí·ßn6ŠpûÐ »ÚõsóÕ«WKÅŠݾå!€$ 0`ÀY¼x±Üÿý&\žÊãƒ>�Æ�›'w°C{*å¹ € €@*´§BÙÞ=|½ M ÝÞ`ál²A@wj?räˆiІ£*T¨ ÅŠˆ¦Ñ<%ðí·ßÊ�¤D‰Á÷\Ù²eå¼óÎ+´ž_~ù¥?~<ø½ä’K<Õ.*ƒ �zí®™ûzþïš"!€ € �&�={öHóæÍåðáòlÙ2¹ùæ›]«‰+W®l‹˜ìÞ½»keS €€3�üü|騱£Ù�]wiOÕ±~ýzóE'Âì©ç> € €© Оjñø÷óõ‚6�öø„3@@@�t hwMß×ó×)@@�4 <ýôÓ2räH)]º´ 5êÿºqh`Qwh¯W¯ž,\¸Ð�")@ÀE�ÜÜ\Ù»w¯ë_hŠUÅY³fI×®]ÍËìÌîbGR € €€§´{ª;Le|½ M Ý{’!€ € €¡Ú]¾žÿ»¦HA € €¤Y C‡òïÿÛìЮ;µ;=tÞÆ�KÉ’%eõêÕR±bE§Er= €€Ë�/4µiÓÆ|)™aödêR6 € €€—´{©7~©‹¯´ ´{o@R#@@@ T€@»kãÁ×ó×)@@�4 ¼ÿþûf·Ü“'O:Þ©÷àÁƒR«V-ùüóÏåþûï—îÝ»§¹uÜ@ šÀ¡C‡¤F�æ%}BG¥J•’E˜=)¬Š € àQíÞë_/hh÷Þ€¤F € € €@¨�vׯƒ¯çÿ®)R € €iX´h‘Œ9R4بGéÒ¥M°Qÿ7‘ãî»ï–ñãÇK½zõdáÂ…‰Á5 €)0`€,^¼Ø|iĈ®ß•0»ë¤ˆ € àqíÞë _/hh÷Þ€¤F € € €@¨�vׯƒ¯çÿ®)R € €i8pà€ ²/]ºÔܽU«VräÈyýõ×åæ›o6;µÛ=Ö¯_/�76—½øâ‹’““c·ÎGH¡@~~¾tìØÑìή_fró Ìî¦&e!€ €dŠ�vïõ”¯´ ´{o@j�Ö­['ÿýïåÌ3Ï”ï¿ÿÞ|0»aÃùñÇM…Oœ8!-[¶”R¥Jy³Ô @@\ Ðçÿ®)R € €)xþùçM˜}ß¾}fÝDwæíÒ¥‹Ù¥=77W>,ãÆ�ÝmÝêqðàA©U«–|þùçÒ¿ó‡@Àûú{ïÞ½æ‹Lú…&7ŽÐ0ûرc¥}ûönK € €x^€@»÷ºÈ× ÚÚ½7 õq™:tV¬aÆòòË/ËŠ+¤S§NÁŸ7hÐ@Ö®]+gŸ}¶÷A�@@@À5í®Qúzþïš"!€ € �"�cÇŽ™ ûüùóÍu7õ#FHåÊ•ƒ5X½zµôìÙSJ—.-›6m2»öZ94ü>~üx©^½º¼ôÒKV.á@<ýôÓæï†6mÚÈòåË׈0»cB @@È`íÞë<_/hh÷Ö€Ô[k×®¬Ô¥—^*›7o–%J˜Ÿ=ùä“Ò«W¯àëƒ ’1cÆH àâ­ÖP@@pC€@»Ц _Ïÿ]S¤ @@R °fÍXܽ{·¹Û}÷Ýg‚ëÑŽ=z˜��5jdž€ïX¿~½ Çëñâ‹/JNNN¼Kx@À#útŽ5j˜ÚìÚµËò™¢UŸ0»G:•j € €¤M€@{ÚècÞØ× ÚÚ½3 �=*×\s�lß¾=X©mÛ¶™ÝAÇÏ?ÿ,üãå…^þLwiÞ¼¹wBM@@@ÀUí®qúzþïš"!€ € �D�S§N™ ûŒ3Ì]rssÍ®ìÕªU‹yW 7êy‡–qãÆ‰î¾ë8xð ÔªUK>ÿüséß¿¿ùÃ� �Y �Å‹Ëý÷ßoþŽHä Ìžˆ× € €d›�vïõ¨¯´ ´{g@Ž=Z† ¬ÐرcE'ã‘ÇgŸ}&UªT þ¸bÅŠ²eË)S¦ŒwCM@@@À5í®Qúzþïš"!€ € �$�7ß|Ó?ýôSs}JmŸ>},ÝmõêÕf÷Ò¥K‹> ·R¥JQ¯Óò50¯› é†A €™'�ŸŸ/;v4¿ëu—v»Gh˜]¯�™cïÞ½²lÙ2¹ùæ›-W,4ÌÞ¸qcY·n�¹ö©§ž’–-[Z.‡@@ÈíÞëE_/h»h×GANŸ>]~øá9räˆüùÏ– *x¯×=T#5»ë®»‚ÔjÕ–,Y"íÚµ‹YËH�:udçÎ�æ ¾oß¾]t·v@@È.í®õ§¯çÿ®)R € €. ¼ûî»fWv}­º#»îÌžÈqèÐ!iÞ¼¹ 8j ^Ë =4Ì®¡önݺx-‘ûq  €@úž~úióÄ�F�Céñj£çþnÐ ãÚ·o/>ø L�:Õ\ªe6mÚ4^1¼Ž € €@Öh÷^WúzAÛí@û÷ß/W\qE0hýöÛoËÕW_íj¯ë=Š)"gŸ}¶«å:)ìäÉ“¢~úé'ѰÉ9çœc¹¸�?þXªU«<_wgߺu«/^¼Ð2¦L™"wÞygðœhÎZ®'"€ € €€g´»Ö5¾žÿ»¦HA € €¸$ O£}ì±ÇLiºN¢ëõë×wTz~~¾tìØÑ”¡»´ënízh€QƒŒº ÓêÕ«¥T©RŽîÃÅ €éÐ/1Õ¨QÃTb×®]R©R¥B+ÔµkWÑÝÙõ„Ùèß3fÌ0ÿWÏiÒ¤IzÇÝ@@H‘�öAÛ¸�¯´Ý´ëÎìuëÖ î¤ñÎ;ï˜�Äí~ø¡<÷Üs&ЭÑž={JÕªUåå—_6îÀ®äyyy2gÎn×CÃó+V¬0×;vÌ\oâºÿ~™0a‚œqƦ “k¹¡G¬ú?~ÜÔiÚ´iòüóχ]£�Á2dˆ´mÛVŠ-Zhóûöíkê8´í�[ »PwiÿÝï~'ûöí3§•/_Þì,rá…Úáæ\@@ð¸�v×:È×ó×)@@‡º©�†Ë7nÜhJÒõœûî»Ïa©ÿ»<LÔ0»†ÚuíDwg×cÁ‚’››ëÚ½(@ } �Å‹K¿~ýD¿$ë(,̸Fÿš={¶É<óÌ3rÝu×¥¯aÜ@@� hO´�ÛøzAÛ‹�öùóçKçÎ�ƒ]¨�™ÔPyèÏôÅÖ­[Ëòå˃�öÈ묄é#wGoذ¡y$Yh=²\ýðSCãºKH \k¼i@þµ×^“Š+F=eÏž=rñÅ_Óó¶oß.%K–´4„G�m‚ó�cÒ¤IÒ«W/K×r € €d†�v×úÉ×ó×)@@úôÙQ£F™*W®lvew{'\Ý,©yóæ²wï^S¾n†¤¡önݺ™�Ú9@²C`Û¶mÒ²eK)]º´èfpÑ+aöÀuºî>oÞ<9óÌ3M¸½AƒÙE+@@ˆ!@ Ý{CÃ× Ú^ ´ë·¨Û·ooFŠ»§N�*C‡-×@û²eË‚áóÐëôZ+�ö;v˜ÝßGdH^Y ¸ÿãÿ0å[9.»ì2ÑÉt±bÅ œYgý`Õ·©Ñù¯¾újÔ{Y©+ç € € €€÷´»Ö'¾žÿ»¦HA € €$  ë1º+»n¤Ç­·ÞjÖC44˜Œ#???ìi¸*T�Õ«WK©R¥’q;ÊDH“@‹-̆q3gΔÛo¿=¬vÂì� ï½÷ÞàÓäu§v}:= € €Ù*@ Ý{=ëëm¯Ú#‡Kùòååž{ÊYg�eAw¤"ÐYÝÍCe¦;«?~\ôYývÍš5a§nذ¡À#,O�:%wÝu—Lž<9xn´ó {ËüðÃæqg�p½~@Ãó¡»¾{ï-G�@@@ÀŽ�v;Z…žëëù¿kŠ„ € €€M�Y³f™0ûO?ýdž€«›û´jÕÊf)öO7nœ<ýôÓ¢;¶/X° À:�ý¹@Àk‹-’�J£F�Ì“ØG Ì®ëç�=ö˜yr‡ÕCËÓrK”(!j¯S§ŽÕK9@@�Œ Ðî½îòõ‚v&Ú5(>lذàŽì‘C)Õ�ö¥K—JÛ¶m Œè'N˜Ç�…îàmçõï¿ÿ^®¾újó�q=4¬¿uëV)W®œ­wÉàÁƒe̘1Ák^yå×Ïi«BœŒ € €® hw�Ó×ó×)@@‹{öì1AvÝ]�[n¹Ål ”Ê]ÒµzèÆD €Ù' _ZÊÍ͕ÇË®]»¤R¥Jf_¸p¡äääØnx¿~ýÌÓâK—.-³gÏ–ZµjÙ.ƒ @@ðº�vïõ�¯´Ý´k˜[¿¡¼eËÓÓ|ð�\yå•¶z=2˜®wïÞ]¦N�*EЉYV*í'N”Þ½{Ǭ‹†Ù¯¹æšàë7Þx£¬X±",Œ¯»¹W­Z5xNÆ åÕW_•bÅŠ9òzðÁeèС¶Êàd@@ð®�v×úÆ×ó×)@@ º#º†×�=*eÊ”1»²·k×Εœ‚ €€=}¢ºf4„þÝw߉>DwfO4̸»>m}åÊ•rþùç›�Ú¯¸â {ãl@@<.@ Ý{äëm»�ö“'OÊ_|!§N¶°CƒØ:AlÖ¬™ì۷ϼöì³ÏšoDkÐ=Ú¡“?ý 3ôˆhÿä“Oä²Ë.+tô¤*Ю;©üñÇêZ¹È°z§N�dÞ¼ya�üÈúFÛÅÝÊÛEëR­Zµà©ÑÂóVÊá@@ð¦�v×úÅ×ó×)@@�Böïßove×M~ô¸é¦›L˜ý‚ .À @�¤lÛ¶MZ¶l,Û�0{ °;î¸CV­ZeþÓ�Ú«W¯ž”6P( € €é ОõÂïéëm»�öÈð´Óîüç?ÿ) +&2è­Av�„ÆÛ¹x‰Õ�Ë#âåË—7“Æ2eʸýÉ“'¥[·n2gΜ°×Rhÿꫯä / Þÿ²Ë.“­[·Êgœa‡KæÎ�+]ºt±íeë&œŒ € €i Ðçÿ®)R € €@V |òÉ'fWö7ß|Ó´Szf/V¬XV·›Æ!€ àD`Ïž=f§ö;vHÆ ÍNíüÝéD”k@@R!@ =Êöîáëm·íJŸŸŸ/?üðƒhh¼N�:Qå…uQ¢�öýû÷KNNNØŽëŒôÑGå’K. ÞR÷•——'[¶l)P�TÚOœ8! 4�wÞyÇÔ£dÉ’æQd_|±å|êÔ)éÑ£‡Ì˜1#x�–§î € € �Ú]ëG_Ïÿ]S¤ @@�¬е†#F˜öU¬XÑÙ›7ožµí¥a €¸)ðÅ_˜Pû®]»¤Q£Ff§v@@¼,@ Ý{½ãëídÚ�vq¢�v½oß¾}e„ ª añúõë˦M›ÂïǽßöíÛÍ5© ´ëý,cÆŒ Ö÷¥—^²õá°îˆÍ5×믻Òk(¾lÙ²N»�ë@@@À#Ú]ë_Ïÿ]S¤ @@�¬ОîÊ®OÂÕ£cÇŽ&Ì^¢D‰¬k+ B@ ™Ÿ}ö™yºÉîÝ»åøƒÌœ93™·£l@@ hwÄ—”‹}½ íÅ@û¢E‹¤C‡ÁΞ4i’ôêÕËRçýõ׿ÛÎ�€za 4Èì4’››Ü­]íË–-“¢E‹/M¤>ºCýUW]”�,WoðÊ+¯È 7ܼWÏž=å©§ž’@`%^£ß~ûm©[·nð´N�:ɼyó¤H‘"ñ.åu@@Èí®u”¯çÿ®)R € €@V ÌŸ?ß„Ù�;&矾Y7iÓ¦MVµ‘Æ € �J�O?ýÔìÔþÿ÷f3»iÓ¦¥òöÜ @@ËÚ-S¥ìD_/h{1Ю;€4mÚ48fÍše¾ÅlõÐ]uRد_¿¨—Ô®][|ðAiÑ¢…?~Ü·lÙbÎíÓ§�Œ?>,PžH}4ÐZn^^žÌ™3§@ÐüСCR«V-Ù¹s§¹¿î$¯a|}”g¼ãÔ©é3‰ý IDATSr×]wÉäÉ“ƒ§®\¹Òì2Ï� € €Ù#@ Ýµ¾ôõüß5E B@È �ÿþ÷¿&Èþ¯ýË´GCìº+;O€ÍŠî¥ €iÐ5Í8èß·­Zµ’)S¦¤¹FÜ@@�‚Ú½7*|½ íÅ@»[CDƒí?zô¨+VLÎ8ã )_¾¼\pÁnÝ•ržx≰ðý¨Q£dÈ�!qËÖÇ•U©R%xÞ¥—^*›7oæ qå8@@�Ì ÐîZùzþïš"!€ €d¼ÀÒ¥KM˜ýÀ�rî¹çš]Ù;vì˜ñí¢ €xIàÃ?4;µóÍ7òÇ?þQ&Nœè¥êQ@@„@»÷�¯´³9Ð½Fû÷ï—œœÙ·oŸ9AC÷[·n•råÊÚ„Áƒ˘1c‚çÌž=ÛLˆ9@@@ »´»ÖŸ¾žÿ»¦HA € €+ O�ÕðúâÅ‹Mš7onve·òÔØŒm4G@ �›6m2;µkIÆ� € €Ù+@ Ýµ¾õõüß5E B@ð´À† Ì®ì}ô‘©ç€äî»ïöt�© €~Ð'¨Ü~ûí¦Éݺu“#Fø©ù´@@ÀCÚ=Ôÿ¿*¾^Ð&Ð2W¯^=X1 µòÉ'æ‘c&Lþ|Ô¨Q2dÈï5€!€ € €€«Ú]ãôõüß5E B@ð¬À£�>\G¨Y³¦ ÈÕ©Sdzõ¥b €øU`Íš5Ò½{wÓü¿üå/2lØ0¿RÐn@@�4 hO#~Œ[ûzA›@»÷¤ÖHwj?r䈩œ†W*T¨ _ýµ?~<ø³K.¹Ä›•§V € € àª�v×8}=ÿwM‘‚@@<'ðþûï›]Ù7mÚdêÖ«W/8@@H«�v×ø}=ÿwM‘‚@@<% OvÕ�Ùõ¸ì²ËÌ®ì×^{­§êHe@@ ºÀ¿þõ/óE4=úôé#ƒ ‚ @@�” hOµåùzA›@»åq‰ € € €@Z´»Æîëù¿kŠ„ € à ��>úÈ„×óóóM}ºuë&÷ß¿yê+ €dŽÀòåË¥oß¾¦Âwß}· 0 s*OM@@2Z€@»÷ºÏ× ÚÚ½7 © € € *@ Ýµñàëù¿kŠ„ € �v�iÓ¦É<`ê¡ë<doÚ´iÚëE@@ 1�%K–HÿþýÍÅ÷ÜsO0àžXi\… € €€5íÖœRy–¯´ ´§r¨q/@@@À¾�vûf1®ðõüß5E B@H›À®]»Ì®ìëÖ­3uÈËË3aöâÅ‹§­NÜ@ÜX¸p¡ ³ëñ·¿ýMz÷îíNÁ”‚ € €@ íÞ¾^Ð&Ðî½I�@@@�Pí®�_Ïÿ]S¤ @@�´Ì™3G†jî]¾|ydoݺuZêÂM@@ 9óçÏ—Áƒ›Âÿþ÷¿ËwÜ‘œQ* € €§´{oøzA›@»÷$5B@@B´»6|=ÿwM‘‚@@R*ðå—_ÊÈ‘#eÕªUæ¾íÚµ3aö2eʤ´Ü @R#ú%¶áÇK�=Rscî‚ € à;íÞër_/hh÷Þ€¤F € € €@¨�vׯƒ¯çÿ®)R € €@Ê-Z$ ÞoìØ±Ò¾}û”ÝŸ!€ €@zfΜi¾À¦‡~±­k×®é©wE@ÈjíÞë^_/hh÷Þ€¤F € € €@¨�vׯƒ¯çÿ®)R € €@Ò8`ÂkK—.5÷jÕª• µ]xá…I¿77@@oLŸ>]þñ�˜Ê<ôÐCÒ¥KoTŒZ € €d��vïu¥¯´ ´{o@R#@@@ T€@»kãÁ×ó×)@@ ©Ï?ÿ¼ôîÝ;xlIå¦p@<-0eÊ5j”©ãèÑ£%//ÏÓõ¥r € €™%@ Ý{ýåëmíÞ�Ô@@ ÐîÚxðõüß5E B@HŠÀ±cÇÌ®ìóçÏ7å7nÜXFŒ!•+WNÊý(@2C`Ò¤I2fÌSÙþóŸÒ¡C‡Ì¨8µD@ð¼�vïu‘¯´ ´{o@R#@@@ T€@»kãÁ×ó×)@@Àu�5kÖH÷î݃åÞwß}Ò³gO×ïC� € �™O<ñ„ ³ë1nÜ8ùÓŸþ”™ ¡Ö € €ž Ðî©î0•ñõ‚6�vï Hj„ € €„ hwm<øzþïš"!€ €¸&pêÔ)³+ûŒ3L™¹¹¹fWöjÕª¹v B@�ìxì±ÇäñÇ7�Ñ€ûÍ7ßœ £ € €i О6ú˜7öõ‚6�vï Hj„ € €„ hwm<øzþïš"!€ €¸"ðæ›oJ^^^°¬AƒIŸ>}\)›B@@ ;}ôQ™0a‚iÜäɓ妛nÊΆÒ*@@�”hO ³­›øzA›@»­±ÂÉ € € €@Ê´»Fîëù¿kŠ„ € àX`ôèÑ&„8–/_.µk×v\. € €@ö <üðÃòä“Oš†>õÔSÒ²eËìo4-D@HŠ�ö¤°:*Ô× ÚÚ�.F@@’.@ Ý5b_Ïÿ]S¤ @@�„Þ}÷]i×®]ðzÝ‘]wfç@@ì<øàƒ2uêTsÉÓO?-M›6µs9ç"€ € `´{o øzA;d@z¯g¨ € € €@P`ÇŽ¶5Î<óLÛ×$ë| âëù²ú•r@@¬ <þøãòØc�O~î¹ç¤~ýúÖ.æ,@@ B`Ĉ2cÆ óÓY³fI“&M0B@@À–€ÖomÕ×ÉÉ¿rrq ¯õõ‚6�öŽ4n… € €8 Ðîï—K}=ÿw¬G € € lݺUZµj¼¶gÏžrß}÷%T!€ €¡úoŠÙ³g›Í�;W®»î:€@@°,@ Ý2UÊNdA;Aêü1Á+¹ @ ÔªUK~ó›ßÈ‚ ¤T©Rþj<­EH«;´‡ñ3ÿOëhäæ € €€ÿ¦L™"£F� 6œÝSý7h1 €É2dˆÌ›7ÏÜæÙgŸ• $û–”� € �%ڽב,h'Ø'Ú„ã2@_ ìÙ³G5jdÚü§?ýIÆŒã«öÓX@�ô hógþŸÞáÈÝ@@ßèÓ•š4ilï­·Þ*#FŒ/ýûÜ7�AC@ð�À½÷Þ+Ï=÷œié¢E‹¤nݺ>h5MD@p*@ Ý© û׳ � )�öḠ@ÀW7n”Î�;Û¬»dðA¢¯†�EÒ*à¥ÀŒ>aþŸÖÑÈÍ@@è.ìÇ6VwioÕª•?O+@@ m4av=–.]*uêÔI[]¸1 € €@fx`ý6eP¿JÙ�œÝˆíý´'Çe €¾xâ‰'DÿŽŠ+ÊÊ•+¥T©R¾r ± €é ÐæÎü?=Ã�»"€ €¾ЧôÕ¯_?ØÖ[n¹ÅìÊÎg@¾è~‰ €€'úõë'Ë–-3uY±b…ÔªUËõ¢ € €Þ Ðî½~aA;Á>!О —!€øJàŽ;î�µk׆µùöÛo—aÆùÊ�Æ"€¤G€@{˜;óÿô CîŠ €d½À‚ dРAÁv>þøãÒ®]»¬o7 D@ï „„’ä…^�+®¸Â{•¤F € €ž Ðî‰n« Ú ö �öḠ@ÀW×_½ìÝ»·@›Ÿ|òIiÚ´©¯,h, €@ê´3ÿOý¨ãŽ € €€öïß/µk×kð;ï¼#\p�h) €xN 4ÔþÒK/IõêÕ=WG*„ € �~íéïƒÈhO°O´'Çe €¾¨R¥JÔöêã¦×¯_Ïc§}5h, �zíaæÌÿS?¹# € �µ+V¬�>}úÛ7zôhÉËËËÚöÒ0@È,�ÐP»>Iø²Ë.ˬP[@@�¤ hO:±í° m›ì— ´'Çe €¾ظq£tîÜ9f{u‡vÝ©�@�d h“eþŸ¬�F¹ € €€�Ž9R`—Ó7ÞxC.¹ä)ÐT@È�ÐPûºuëä·¿ým&T›:"€ €¤H€@{Š m܆mX¡§hOŽË@|#0sæLy衇 m¯Ú5ØÎ� €@2´‡©2ÿOÆ £L@@ÀG«W¯–ž={[ €^ðÀ"¾žÿ‡ø{y˜P7@@ð�ÀîÝ»=TëUaþcÝŠ3@@@D2uþë´ï<°~ë´ –¯'Ðn™Š@@ Û:tè ÿþ÷¿jÖ‹/¾(999 ]ËE €xUÀˆh÷êà ^ € € àI�L]Ð'ÐîÉáD¥@@@Ï dêü×)¨Öo�6ÁòõÚ-Sq" €d›€Ò>œP³ôZ µs € �Mø@„@ûéuê”aà@@@b üêW¿,ófê‚~`þÉü‡AŽ € € P˜@¦Ï�ö®Öo�6ÁòõÚ-Sq" €d“À¡C‡¤F�ŽšÔ¿Ñ? €d‹€>!Ð~z0èÈ–wí@@@ y™¾ O =ycƒ’@@@�lÈôù¯Ó¾ðÀú­Ó&X¾ž@»e*ND@�lÈÏÏ—Ž;:jR‰%äå—_–Š+:*‡‹@ðŠ€>!Ð~z0h÷Ê;‚z € € à]�L_Ð'ÐîݱEÍ@@@/ dúüש¥Öo�6ÁòõÚ-Sq" €d“À¸qãDÿ89Ê”)#�<òˆ4oÞÜI1\‹ €€g<ð��öÓ£�@»gÞT@@Ï dú‚>�vÏ-*† € €xJ Óç¿N1=°~ë´ –¯'Ðn™Š@@ ›zôèavWwz,X°@rss�Ãõ €xBÀˆh?=´{âí@%@@ð´@¦/èh÷ôð¢r € € €€g2}þëÒë·N›`ùzí–©8@²I E‹²}ûvÇMÚ°aƒT¬XÑq9€ €€<ð��öÓ�@»Þ Ô@@o dú‚>�vo�/j‡ € €xE Óç¿N=°~ë´ –¯'Ðn™Š@@ ›BþÁç¨Y»wïvt=#€ à%| B ýô€ Ðî¥wuA@@À›™¾ O Ý›ãŠZ!€ € €^Èôù¯SO¬ß:m‚åë ´[¦âD@È&�=zÈË/¿ì¨I*T�üü|Gep1 €^ðÀ"ÚOí^zWP@@¼)�é úÚ½9®¨ € € à5�LŸÿ:õôÀú­Ó&X¾ž@»e*ND@�lسg�4oÞ\>œp³êÕ«' .Løz.D@Àkø@„@ûéAA Ýkï êƒ € €€÷2}AŸ@»÷Æ5B@@¼(�éó_§¦X¿uÚË×h·Lʼn € �mO?ý´Œ92áfuëÖMFŒ‘ðõ\ˆ €€×<ð��öÓƒ‚@»×ÞÔ@@ï dú‚>�vï�)j„ € €xQ Óç¿NM=°~ë´ –¯'Ðn™Š@@ Z´h!Û·oO¨iýû÷ýÃ� €@¶xàí§�ölyGÑ@@’'�é úÚ“76(@@È&�LŸÿ:í ¬ß:m‚åë ´[¦âD@ÈF�üü|騱cBM›6mš4oÞ<¡k¹@/ xàí§�v/¾;¨ € €ÞÈô}íÞOÔ@@ðª@¦Ï�ºz`ýÖi,_O Ý2'"€ €@¶ Œ1Bf̘a»y ,�ÜÜ\Û×q €^ðÀ"ÚOí^}‡P/@@¼#�é úÚ½3–¨  € € àe�LŸÿ:µõÀú­Ó&X¾ž@»e*ND@�l8tè� ¦>|ØVwïÞmë|NF@Àëø@„@ûéAB Ýëïê‡ € €@ú ú;vì°]™3Ï<Óö5n_@ ÝmQÊC@@²S Óç¿N{Åë·N›`ùzí–©8@²Y`Ñ¢E2pà@[Mt;оuëVÑp½.(ÕªUKŠ-j«>œŒ €€S| B ýt'hw:’¹@@�ìÈô}íÙ?Fi! € € à†@¦Ï�x`ýÖi,_O Ý2'"€ €@¶ tèÐAþýï[jf½zõdáÂ…–Î�wÒ±cÇäÑG•éÓ§›SË–-+ëÖ­“óÎ;/Þ¥¼Ž €€«ø@„@ûé%Ðîê°¦0@@²R Óô ´gå°¤Q € € €€ë™>ÿu â�õ[§M°|=�vËTœˆ €Ù.°mÛ6iÙ²¥¥f6kÖ,@·tAŒ“6mÚ$Ý»w—ýû÷Ï ÐîD”k@œxàí§;�@»“Q̵ € €øC Óô ´ûcœÒJ@@@À©@¦Ï�¶ßë·N›`ùzí–©8@ü 0nÜ8Ñ?ñŽþýû‹þqrÌ™3G†VDÕªUåÀ�ìÐî–k@ðÀ"ÚO÷�ö„‡0"€€Ž=*E‹ Öä§Ÿ~’³Î:KŠ+æ�Úù§ ‘ý¯åÚOzÐWñ¤xðŽ@¦/èh÷ÎX¢& €@4�C‡É—_~)ß|ó�9r$xÊ9çœcž4ü›ßüFJ—. ^–0‡Ì’ޤ €@– dúü×i·x`ýÖi,_O Ý2'"€ €€ôºæÍ›ËÞ½{ mîý÷ßovVwr 6Lžyæ©]»¶<ùä“òÜsÏ™0=;´;QåZ@'ø@„@ûé$Ðîds-¤S`Ñ¢EÒ¡C‡U˜8q¢ôîÝ;�UóÕ½cõƒU�wéÒEôÉdåÊ•³zç!€¤X Óô ´§xÀp;@À‚À×_-«W¯–)S¦È[o½÷ ]ßêÙ³§ÜtÓMR±bŸçs‚7˜Cz³_¨ €ÀòÁÜ IDATÿ2}þë´/=°~ë´ –¯'Ðn™Š@@À/úa�~Wر`ÁÉÍÍuD2xð`óß�wÞiv0 Ü ´;båb@ø@„@ûéþ#Ðî`s)¤U`ñâÅÒ¾}ûu˜4i’ôêÕ+­uËä›>|Ø| ZwB<~ü¸\tÑEÒ©S' ,äD¶-V?$b0bÄót²R¥J%r¹'®±ëç‰JS @À‚@¦/èh·ÐÉœ‚¤HàäÉ“¢ó¶»ï¾;á;êÜáž{î‘sÏ=7á2ܾЯs»íf>òìú¹=n)@  @¦Ï�ö©Öo�6ÁòõÚ-Sq" €øI G�òòË/Çlò† ï6¡�t× {àÐ]ßgΜÉí~h´ð˜€>!Ð~zLh÷Øƒê €€eí–©l�¸víZiÚ´i𚆠ʺuëÂæ“¡ºFÐrK–,)o¾ù¦Ô¬YÓV½½r²]?¯Ô›z €ñ2}AŸ@{¼æu@ 5_}õ•yrñ–-[ß°|ùòfg÷*Uª8.Ë�ü:°Ûnæ�á£Í®Ÿc•2@ Èôù¯ÓþõÀú­Ó&X¾ž@»e*ND@? ìÙ³Ç|€§ßÂ�vìÞ½Ûuí®“R  €€M| B ýtŸh·9p9<#@ Ýý®Ð¿ô ×3fÌÞºukY¾|¹)R$ê Ý#èM4˜²uëV)W®œû�Lb‰‰ø%±:�¸*�é úÚ]†$$°k×.©W¯žìÛ·/¡ëc]ôÁÈ•W^éj™v óë\ ‘v3‡üßèJÄÏîØä|@û™>ÿµßâð+<°~ë´ –¯'Ðn™Š@@ÀoãÆ�ýyT¨PAòóó]ç Ðî:)"€ØðÀ"ÚO÷�v›—Ó@À3ÚÝïŠýû÷KNNNXÀDíË–-Kh‡öÚµkG­äûï¿·òƒ ’Gy$îy^:!?/ÕŸº €… dú‚>�vÆ7 �^�ï¿ÿ^rssãîÌÞ­[7ѧD•*UJŽ?.ï½÷žŒ;¶ÐÊ{á ±~� $ÒîÂíÌ!EâÍÁÓûNæî €€?2}þë´—<°~ë´ –¯'Ðn™Š@@À�-Z´�íÛ·‡5]w«X¸p¡ëÚ]'¥@@›ø@„@ûé>#Ðnsàr:xF ™�ö“'Ošð„º€qÎ9ç¸Ún sœ8q”yÖYgÉÙgŸíjù‰¶bÅ ¹ùæ›Ã.ÏËË“¹sç‡hG¢ý O"›2eŠ<üðÃQË-Y²¤lÞ¼Y*W®l©9Úgúç§Ÿ~2áûd™jßéß�ÚoÅŠ «["~–ÇI €€2}AŸ@»U@_ <ñÄÒ¯_¿˜}úô‘aÆ™§5E� &ˆ®kÅ:úöí+ãÇ�O›q²çÌ!E˜C¦mxsc@Àw™>ÿuÚaX¿uÚË×h·Lʼn € àG݉½cÇŽaM×Ý(FŒá:�v×I)@À¦€>!Ð~ºÏ´Û¸œŽž°¤þðÃå¹çž“âÅ‹Ûðã�?ʽ÷Þ+%J”0?Û¸q£ YÏš5+¬�Mš4‘!C†È 7ܳýÑÊ?tè�ôìÙSªV­*ÇŽ3;�Ožø@ªW¯nBÚzNa‡ž3qâD9÷Üs œ«| ¡ëŽáÕªU‹KײeKó„®@À>ô‚îݻˌ3 ”±aÃÉÍÍ�Z¶îâwõÕWxØ�7Þ(ºƒ Ö+V¹±*z­žc·"ËýùçŸåøƒ¬_¿¾À-—,Y"íÚµ+ðó�>úH4¬®�v«‡ÿ|ðA9ï¼ó¢^«ÿô^ß~û­4hРÀu¸òÉ'£ö‹U?«õç<@ Ý™¾ O =Ý#ˆû#€€Ÿt£óŽhG¬óÇòZ³f�4kÖ,êËûÛßdôèÑ^óê\Š9dxWY ´3‡ôóoÚŽ¤F Óç¿N•<°~ë´ –¯'Ðn™Š@@À¯ºƒß5×\#<ÐcÚ´i…î8‘¨�öDå¸@À-| B ýtghwkDS¤ZÀn�:ÖùJÖù£�>j© ‘�îÀE±ÊöÙgå�(*�u3-_w/V¬XØ)}ûö• &¸¬°]Ìøá©[·n�àwëÖ­Í=t7øXåÆª_èµzŽÝ~ˆ,Wÿjß¾½h�%òP; ®‡/¼ð‚Ùu=‘£|ùòfþJ•*¸xEÀn�:Öù5kÖ´µÓ·¶ÿõ×_/0O‹U~"^úÅæ=z„]š¬†î:?{ölËÕÔùéºuëÌîîzØí‡ÈF˜>}zØNŽk×®•¦M›Z®k´5Ô¾mÛ6)[¶lØË‰ôŸÚuÇ~'~ŽÃÅ €@ 2}AŸ@{  ·BB¾úê+¹òÊ+eß¾}\V®\)ú…Y»Çk¯½&�5Šz™>!+'''£çRv=ô|æ�¿¨1‡Ldôp  €@¤@¦Ï�ö¨Öo�6ÁòõÚ-Sq" €ø] ð�DÝ¥NïöA ÝmQÊC°+à�D´Ÿî4ívG.ç#€€Wì©­–ÿô§?I^^ž|ùå—r×]wÅlª>²^�Å =1^ùºK ž£Oäúî»ïdÊ”)2zôè¨÷¸ôÒKEƒÅ‹¾ž¬@»îX¾sçN9zô¨ôìÙ³@}4>nÜ8³›»¿þõ¯¥Y³fÁóìöCä tÇÆvíÚÉŠ+ Ü{Ö¬YrÛm·™Ÿ8p@êÔ©cêíxøá‡¥sçÎrî¹çÊîÝ»åÞ{ï•5kÖD=wРAòÈ#�„½VXÿU¯^=êûo¿ý¶ÙqÒ‰ŸWÞOÔˆ'�é úÚãõ0¯#€ÉÐ/�ê(òÐyÆÇ,eÊ”±}c�»h™º{ä¡kj:¿=2m.¨;sÈèCƒ9¤í·  €ØÈôù¯Íæ8Ýë·N›`ùzí–©8@ü.pÇwȪU«døðávæsÃFûŸ|òI¹ð eýúõaA 7ʧ @â xàí§;‰@{¼‘Êë àU»Aêx�ógžyFºtélîž={¤yóæQCÑÂ…•¯çkp¼R¥Jaœ0`@Tâüü|©W¯^ðµd…078qâ„ ŒoÙ²%¬>ðŸ;wnXx?ô»ýÙØ_|QZµjÕàƒ>0»9ê¡_¸óÎ;£ž÷Æoȵ×^öšþý6räHó'òÐ`ˆîÒ~ñÅ_Š7>Z¶liæÐúï—cÇŽÉÁƒÍ|:°À•¨ŸWß_Ô ˆÈô}íŒi@ =óçÏ7_<�ôÚ“'OšrÛ·ouˆé.õ›7o–%JH¬ºé…¡»¸G¤óÛ ˆöcäY¿Âú¯C‡2oÞ<)V¬XÌ·C¢~^}Q/@ R Óô ´3¦@�ôÄúwv§N�Ì¿±O‚²[»Xó£tÚ� 0‡ ïuæ�¿xÄ›ƒÛ}¯p> €€}�LŸÿÚoqøX¿uÚË×h·Lʼn € àw��>ú(xÐÇ¥;9>ûì3iܸ±å"tWŒ«®ºÊòùœˆ €@"ø@„@ûéŽ#ОÈèåð‚€[�vÝùNwë.[¶l�fi0Aç‘�h;;´k0{ëÖ­1ŸŠ5cÆ éÞ½{�{GîX˜i�ö&MšH‹-¢ÝÝ|úôé²oß¾˜CI¿Ø­».ê±cÇ©Zµj�sµÔ¶\¹r1ËÑÀ{×®] ¼®;ÏÏ™3' ),LòÉ'ŸÈe—]Vè°O4Äâ…÷u@¬dú‚>�v+½Ì9 €€û±þ�]½zuóÖ¾4«6úY–Î7ôéчÎ3"çW™6—bY°ç™CºÿÞ¤D@�Ø™>ÿuÚ·X¿uÚË×h·Lʼn € €€˜G™ë±iÓ&G;¦ïÙ³Gêׯo™ô…^�+®¸Âòùœˆ €@"ø@„@ûéŽ#ОÈèåð‚€[�ö>}úÈøñã%°PÙ6«á‡Xõ>|¸Œ92&Ù‡~(5kÖ,ðz䎅VëZ���µ�sCïQX<Ñq¢áñ>ø ø%ÝɽmÛ¶Š»ñÆeÅŠÁ§‘E»ßÇ,ÕªU+ðRèðúb¬v4lØP^}õÕ¸A›Dý5â:@ Õ™¾ O =Õ#†û!€¿Äúw¶•ËÇ2ÔϲôK«³gÏ.pÊÔ©S¥gÏža?Ï´¹sHûïæ�ö͸@ ¶@¦Ï�ö­Öo�6ÁòõÚ-Sq" € Ò±cGÉÏÏ7�P×Ý&8@È&| B ýô€"ОMï*Ú‚€¿Ü ´Ç{¬¹ÕðC¬úD T„öT¬ÝÇ+V¬([¶l‘2eʘӭÖ#´l;!k;ç†ÞÃí@»îºþÞ{?pĺǨQ£dÈ�!…ü]»v‰†×#�È]öcÝcìØ±2`À€¸o®DýâÌ  €€G2}AŸ@»GÕ@ß ÄúwväxíÂÄšMš4IzõêV\¦Í¥˜CÚ Ì!íyq6 €@|�LŸÿÆoaágx`ýÖi,_O Ý2'"€ €"º8?eÊéß¿¿ùÃ� €@6 xàí§�ölzWÑü%àV =Zà!TÒjøÁn}÷ˆ¸Ö¶îR^²dIsªÕz„ÖÝNÈÚι¡÷p3ÐÞ¼ys³Ëâ\6˜ý²€¢íºêª«dûöíaeF~a Ñþ š¨Ÿ¿Þµ´2Y Óô ´gòè£î �ɱþ�Ý AY¿~}Ü'!Ek{a;´Oœ8Qz÷îvY¦Í¥âÍQ™Cþ¯{™CfòoêŽxW Óç¿Ne=°~ë´ –¯'Ðn™Š@@‘^xAî¼óN³;»îÒÎ� €@6 xàí§�ölzWÑü%`7€l÷ü€¦ÕðC¢åFؼy³”(QÂTÅj=BG���µ�sCïá4Ю�} œè�W^yeÔAœ¨­vôèQ©Y³¦ìܹ3¬ìÈ/ 8¹‡œ¨Ÿ¿Þµ´2Y Óô ´gòè£î �Éü±T«V­@t°mÛ6¹øâ‹m7ï»ï¾“Úµkø7¾ôÒK/‰†œC�L›K9 ´3‡ü¥ç�Ìï˜CÚ~Kr �u™>ÿuÚ!X¿uÚË×h·Lʼn € €€Èž={¤~ýú†b÷îÝ� € �Uø@„@ûéE =«ÞV4_ Ø] ¶{~Ójø!ÑòwìØ!U«V-Ðw‘;ˆ[­GhAvBÖvÎ ½G¬vßwß}ò—¿üŽ£ÅŠ3»ÏŸwÞyqweLÔVïûŸÿüGBþͬŠ>–^Ã5eÊ”1?sr½>Q?_½ii,d´@¦/èhÏèáGå@ ƒ:$µjÕŠ>�ÜŽÕìU«VÉ�7ÞXàåX!ùL›KÅsaÉ2ƒ%Pu@ #2}þëÙë·N›`ùzí–©8@øE ð�Å×_]*Uª  €Y#à�D´ŸMÚ³æ-ECð�€Ý²Ýó VÉ–kÇBݱ|Þ¼yR¤HS•Xõx÷Ýw媫®ŠÚÿ±voݺµ,_¾�öLuÔ²E@ç,M›6�Úœ{ï½WF�-�¿gâµyîܹҥK—¨§Íš5Kn»í¶°×2q.Å2Þ(ˆÿ:sÈøFœ� [ Óç¿NûÖë·N›`ùzí–©8@øE@ƒì�>ú¨ / >@²FÀˆh?=š´gÍ[І à;»d»ç@�ÚµÝAïòË//ÐGú;Xç+V¬(ðZŸ>}düøñÁ`Ç”)SäÎ;ïŒzÞO!Ð~z4hÏš· AÀwvÈvÏ€ºhoÙ²¥¬\¹²@@cÍš5Ò¬Y³¨}·aÃÉÍÍ ¾«þzÂo¼!×^{mð\ýÝ>räHó'Úa'ÐoôD]íØÁƒ˘1c \vé¥—ÊÆ�¥lÙ²^Óí»wï^àç%K–”Í›7KåÊ•ãúZÝi>VH'žŸ]ÎGÒ%�é úÚÓ5r¸/ ð‹ÀªU«äÆoŒÉ¡k`S§N•ßÿþ÷ÎÑ/¶þ?öîZŠ"møøsŠ˜8*‚T1"f a#æ€T”ÅÁˆ ÈŠˆ¨,‚(‚Š¢‹Q1+êŠ \TL‹ï7Oï7ãܾ=ÓÕi¦ þ}Ξ÷•[Õ]õ«ê™©ª§«ûõë'úö¥R‡Ž·tœt¸6–Ò:0†L~ç0†LnÈ@�åUÀõñoÒv³`ý6iŒóÐnLEB@@à?ýô“l¹å–Þÿÿïÿ@– &DhÏõ&Ú—™[ŠŠ °Ü D ¤Žš>šF@»ž«[·nÞ¸tç@=žxâ 9øàƒÛMƒ´5àºqãÆ…¿OžþMÚv¬ß&­‚q~Ú�©Hˆ €ü% ¯Oÿä“Od„ …àv|@p]À‚ Úÿÿ‚¢ë}‰ò#€Àò)59jú¼j’€v üž7ožhPƬY³¼SjPº«ë¿—:‚v/µxœÖïÞ½»Œ9RêÔ©SȾxñbéСƒ¼ñÆeO©uúàƒ¼=âºÆ)÷ Aƒ¤wïÞ%³ªó:ë¬#O?ýtÉ4ZþÙ³gKÓ¦Mk¤IZ�¸~qȃTCÀõ}Ú«Ñk¸& PS@Õ m]ëJëÐ1Œ¾ùªaÆ%OéÒXŠ1äiu ï<Œ!Såäd €Àr#àúø7iCY°~›´ Æù h7¦"! € ð—€.Ø�3F®½öZ9ú裡A@`™°`B„€ö\Ob‡öeâv¢,—Q�£¦Ï£& h�Ó0�¡;6hРVöG}TŽ8âãÓê8Rƒç¯¼òÊyJ½¾^w-×�ØËÕ h×ï¬Ë.»L®ºê*cƒâ„Zö^xA6ß|óZùãö�âÅñ‹U2!€Up}AŸ€ö*t.‰üù矢oLÒ@㤇ŽktŒTüf«Rçti,Õ…1di1Æ�Q{é@TÀõñoÒV´`ý6iŒóÐnLEB@@à/�ûï¿ß{Uº.Üpà Р€ °LX0!B@{®'оLÜNT�åR jòèÑ£½ÝýGÐnèÅiÎ<óLþ|Ya…¼ÿéN…Íš5‹zšÀôß|ó�üøã�²ÒJ+Éï¿ÿ.«¬²ŠWÖ ¤rþ´N²dÉùꫯä»ï¾“ºuëz§]¼x±WÞæÍ›ïÓºžéy\ñ3­é@×ô h§#€v èïúŸ~úI,Xà�?ô7½~÷èC¸k®¹¦÷«üwQ5±a,Å2�–Œ~Æ�ÑÍÈ�,o®�“¶—ë·I«`œŸ€vc*"€ €5tçAÝ©ýá‡.ì´‡ €. X0!B@{®Ðîò]DÙ@À&Ó`1‰" IDAT›ÊLY@0p}AŸ€vÓ–& €@¥CVJšë € MÀõño´ÚÖNmÁúmÒ*ç' Ý˜Š„ € €@M�K/½Tî¿ÿ~éׯŸœ~úéð € ༀ"´çzíÎßJT, Á’†  €™¸¾ O@{&Ý‚“"€$` ™�¬ €d(àúø7)�ë·I«`œŸ€vc*"€ €5ÆŒ#½{÷–Î�;ËÝwß  €Î X0!B@{®Ðîü­D@À‚,iŠ� �‰€ë ú´gÒ-8) €@Æ� ðÈŠ €@†®�“ÒX°~›´ Æù h7¦"! € PSà“O>‘=÷ÜSÖ[o=yå•WàA@Ày &DhÏõ"Ú�¿•¨X"@0‚% A1@2p}AŸ€öLº'EH À2Y@ÈPÀõñoR Öo“VÁ8?íÆT$D@j l¹å–òÓO?É믿.k¯½6D €8-`Á„í¹D@»Ó·…G‹F°¨1(  €@ê®/èОz—à„ € C&$; € ¸>þMÊbÁúmÒ*ç' Ý˜Š„ € €@m�£�>Zþõ¯É�!Cdß}÷…@§,˜! =׃hwú6¢ð `‘Á5EAH]Àõ}ÚSïœ@ ¡cÈ„€dG@ #×Ç¿IY,X¿MZãü´S‘@¨-pà 7Èí·ß.½zõ’¾}ûB„ €€ÓLˆОëA´;}Qx°H`Ô¨QÒ£G�Z%ºñÆå¼ó㨤@�è®/èнÍÉ� �­cÈl}9; €q\ÿÆ­w>Ÿë·I«`œŸ€vc*"€ €µžyæ9餓¤cÇŽ2räHˆ@pZÀ‚ Ús=ˆ€v§o# � ,Z´H¾úê+©S§N¡TK—.•¦M›ÊÊ+¯lQI)  €Ñ\_Ð' =z›“@ [Æ�Ùúrv@â ¸>þ�[ï|> Öo“VÁ8?íÆT$D@j |÷Ýw²ÝvÛÉ*«¬"³gφ@§,˜! =׃hwú6¢ð € €TDÀõ}Ú+ÒM¸ € € ༀëãߤ `ÁúmÒ*ç' Ý˜Š„ € €@°ÀÎ;ï,sçΕçž{NZµj €Î X0!B@{®÷Ðîì-DÁ@@¨˜€ë ú´W¬«p!@@@Ài×Ç¿Iñ-X¿MZãü´S‘@8ýôÓe„ róÍ7Ëa‡ €Î X0!B@{®÷Ðîì-DÁ@@¨˜€ë ú´W¬«p!@@@Ài×Ç¿Iñ-X¿MZãü´S‘@¸ûî»åÚk¯•ãŽ;N®ºê*˜@pVÀ‚ Ús½‡€vgo! Ž € €@Å\_Ð' ½b]… !€ € €N ¸>þMŠoÁúmÒ*ç' Ý˜Š„ € €@°À+¯¼"Gq„l½õÖ2~üx˜@pVÀ‚ Ús½‡€vgo! Ž € €@Å\_Ð' ½b]… !€ € €N ¸>þMŠoÁúmÒ*ç' Ý˜Š„ € €@°Ào¿ý&›m¶™è�è9sæHݺu¡B@ÀI &DhÏõÚ�¼}(4 € €p}AŸ€öŠv.† € €8+àúø7)¼ë·I«`œŸ€vc*"€ €¥öÝw_yÿý÷eܸqÒ®];¨@pRÀ‚ Ús=‡€v'o � € €@E\_Ð' ½¢Ý…‹!€ € €Î ¸>þM oÁúmÒ*ç' Ý˜Š„ € €@i�¾}ûÊÃ?,W\q…ôìÙ*@œ°`B„€v'{…F@@�j |òÉ'‘/½âŠ+FΓv†¢ñgÚ§æ| € € €Ë €«ãߤMaÁúmÒ*ç' Ý˜Š„ € €@i�‘#GJ¿~ý¤k×®2hÐ ¨@pRÀ‚ Ú�ì9@@ª%àê‚>íÕê1\@@pSÀÕñoRm Öo“VÁ8?íÆT$D@J Ìž=[:wî,o¼±L™2*@œ°`B„€ö\Ïù¿ÿó8@@@�’®¿r=?þdüC'G@@('àúø7iëZ°~›´ Æù h7¦"! € P^ U«V²hÑ"™5k–¬¶Újp!€ àœ€"´çz ÎÝ:@@�Š ¸¾ O@{Å» D@@œp}ü›Ý‚õÛ¤U0ÎO@»1 @@�ò‡v˜¼þúë2bÄÙm·ÝàB@À9 &DhÏõÚ�»u(0 € €p}AŸ€öŠw.ˆ € €8)àúø7)ºë·I«`œŸ€vc*"€ €å®ºê*2dˆœþùröÙgÃ… €€sLˆОë5´;wëP`@@*.àú‚>íï2\@@pRÀõñoRt Öo“VÁ8?íÆT$D@Ê Œ?^zõê%�:u’ûî».@œ°`B„€ö\¯! Ý¹[‡#€ € Pq×ô h¯x—á‚ € € €€“®�“¢[°~›´ Æù h7¦"! € P^à‹/¾�Ž;ÊZk­%Ó§O‡ @ç,˜! =×khwîÖ¡À € €T\Àõ}Ú+Þe¸  € € ऀëãߤè¬ß&­‚q~Ú�©Hˆ €„ ´k×N¾ÿþ{™6mš4oÞ<<)@°HÀ‚ Úsý�€v‹n Š‚ € €€¥®/èÐniÇ¢X € € €€e®�“rZ°~›´ Æù h7¦"! € .гgOyî¹çdðàÁrà�†g  € X0!B@{®?ÐnÑMAQ@@°TÀõ}Ú-íX @@°LÀõñoRN Öo“VÁ8?íÆT$D@ $7ß|³œzê©Ò¿ÿð ¤@@À" &DhÏõÚ-º)(  € €– ¸¾ O@»¥‹b!€ € €– ¸>þMÊiÁúmÒ*ç' Ý˜Š„ € €@¸À”)Sä¸ãŽ“wÜQ}ôÑð ¤@@À" &DhÏõÚ-º)(  € €– ¸¾ O@»¥‹b!€ € €– ¸>þMÊiÁúmÒ*ç' Ý˜Š„ € €@¸À‚ dë­·–† ʇ~ž� €X$`Á„í¹þ@@»E7EA@@ÀR×ô h·´cQ,@@@À2×Ç¿I9-X¿MZãü´S‘@0Øm·Ýä³Ï>“I“&IëÖ­Í2‘ @ ,˜! =×h·àf  € €X.àú‚>í–w0Ї € €X"àúø7)£ë·I«`œŸ€vc*"€ €fgŸ}¶Œ7N(Gy¤Y&R!€ `�€"´çúíÜ @@Ë\_Ð' ÝòFñ@@@K\ÿ&e´`ý6iŒóÐnLEB@@ÀLà¾ûî“+®¸Bzôè! 0ËD*@,°`B„€ö\?  Ý‚›�" € € `¹€ë ú´[ÞÁ( € € `‰€ëãߤŒ¬ß&­‚q~Ú�©Hˆ €˜ LŸ>]ºví*mÚ´‘‰'še" €X0!B@{®ÐnÁÍ@@@°\Àõ}Ú-ï`@@°DÀõñoRF Öo“VÁ8?íÆT$D@Ì/^,o¼±—ø£�>’ ˜e$ €U°`B„€ö\  ½Ê7—G@@À×ô hw “QD@@@À×Ç¿I -X¿MZãü´S‘@08ðÀeÖ¬Y2zôhÙa‡Ì3’@�* X0!B@{®ý h¯âMÀ¥@@pDÀõ}Úéh@@¨²€ëãߤ|¬ß&­‚q~Ú�©Hˆ €˜ ôïß_FŒ!_|±œrÊ)æI‰ €@,˜! =×þ´Wñ&àÒ € €8"àú‚>íŽt4Љ € €TYÀõñoR> Öo“VÁ8?íÆT$D@Ì}ôQ9ÿüóå ƒ’;ï¼Ó<#)@¨¢€"´W±ý¹4 € €¸'ðÉ'ŸD.ôŠ+®9OÚŠÆŸiŸšó!€ € €,ƒ®Ž“6…ë·I«`œŸ€vc*"€ €æ~ø¡têÔIô‡åK/½dž‘” €TQÀ‚ Ú«Øþ\@@ÜpuAŸ€v÷ú%F@@ª)àêø7©™ë·I«`œŸ€vc*"€ €ÑZ·n-¿üò‹¼ùæ›Ò´iÓh™I� €@,˜! =×îÿ÷ € € PRÀõW®çÇŸŒèä € € €å\ÿ&m] Öo“VÁ8?íÆT$D@¢ yä‘2mÚ46l˜ì½÷ÞÑ2“@�*X0!B@{®Ý è¨Bçç’ € €8&àú‚>íŽu8Š‹ € €TIÀõñoR6 Öo“VÁ8?íÆT$D@¢ 0@,çœsŽœwÞyÑ2“@�*X0!B@{®Ý h¯Bçç’ € €8&àú‚>íŽu8Š‹ € €TIÀõñoR6 Öo“VÁ8?íÆT$D@¢ Lœ8QN=õTÙ}÷Ýå�ˆ–™Ô €TAÀ‚ ÚsíN@{:?—D@@À1×ô hw¬ÃQ\@@@ J®�“²Y°~›´ Æù h7¦"! € Mà믿–wÜQV_}u™9sf´Ì¤F@  LˆОkwÚ«Ðù¹$ € €Ž ¸¾ O@»cŽâ"€ € €Up}ü›”Í‚õÛ¤U0ÎO@»1 @@�è;ì°ƒ|óÍ7ò /H‹-¢Ÿ€ €TPÀ‚ ÚsíM@{;=—B@@ÀQ×ô hw´ãQl@@@ Â®�“rY°~›´ Æù h7¦"! € ]à”SN‘I“&É­·Þ*]ºt‰~r € PA &Dhϵ7íìô\ @@G\_Ð' ÝÑŽG±@@@� ¸>þMÊeÁúmÒ*ç' Ý˜Š„ € €@t�;ï¼S('œp‚\~ùåÑO@@*(`Á„í¹ö& ½‚�žK!€ € ਀ë ú´;Úñ(6 € € Pa×Ç¿I¹,X¿MZãü´S‘@ˆ.ðÒK/I�=dÛm·•±cÇF?9@¨ €"´çÚ›€ö vz.… € €€£®/èÐîhÇ£Ø € € €@…\ÿ&å²`ý6iŒóÐnLEB@@ ºÀÏ?ÿ,mÚ´‘zõêÉœ9s¢Ÿ€ €TPÀ‚ ÚsíM@{;=—B@@ÀQ×ô hw´ãQl@@@ Â®�“rY°~›´ Æù h7¦"! € O`ï½÷–�>úHžxâ iÛ¶m¼“� @� X0!B@{®� h¯@gç € €8.àú‚>íŽw@Š� € €THÀõñoR& Öo“VÁ8?íÆT$D@â œwÞy2zôh¹úê«åØc��wr!€ P &Dhϵ3íèì\@@Ç\_Ð' ÝñHñ@@@� ¸>þMÊdÁúmÒ*ç' Ý˜Š„ € €@<�x@.¾øb9üðÃ妛nŠwr!€ P &Dhϵ3íèìŽ^â³Ï>“×^{MV\qEùã�?d»í¶“�7Þ¸P›°¿—«ö”)Sä»ï¾óÎýÛo¿IçÎ�åå—_ö®£ÇâÅ‹eÿý÷—UVYÅQ=Š� €,[®/èоlõÇ4k6® û{ÔqO“&Mb_ÇN&L�ºuëzc¦µÖZKöØc�Øç#c:´K:Ži�Ť=ü÷õ®»î*ë®»nZEà<TEàÉ'Ÿd^­*ò\–E×Ç¿IÛÄ‚õÛ¤U0ÎO@»1 @@�x3gΔƒ:H6Ùd™ºPúo¼Qôm@ù#ì索­oêÖ­[áÏ;v”gžyFüqéÞ½{áß;tèàýžmР�£‚@Xv\_Ð' }Ùé‹i×$l\ö÷¨ãž$ã›>ø@6ß|óc)}XXÜ9ª'@»TÏ>èÊ&í`õû�vÚÉ®Š,#¥Y´h‘,Y²DêÔ©S¨QÜÏÁü¹ôDú¹W¿~ýeD)�j<òÈ#Ì«¥CÉY@q}ü›´ -X¿MZãü´S‘@ˆ/ТE ùóÏ?åÝwß••W^9þ‰È‰ €@†LˆОk_Ú3ì䎟úŸÿü§÷ÖŸüqç�wÊgœQøï°¿UƌҮ]»ÂŸôw«>�Ù¸qcïßîºë®×¸à‚ dàÀ�… dÇI)> € ଀ë ú´;Ûõ2/xظ&ìïqÆ=q+õÉ'ŸH«V­ Ùõ�` Ì-�{nòÅ ]âÛe‘Ó¤=ü÷õo¼!íÛ·Ï¢8Ëõ9ÿûßÿÊ;ì ³fͪá k—­[·Ždã?—nì5nÜ8>ÿ|ŠÌ«EêV$FJ ¸>þMÚ´¬ß&­‚q~Ú�©Hˆ €ÄèÒ¥‹¼ùæ›òÐCÉ.»ìÿDäD@ C &Dhϵ/íÉ:¹ú½öÚkÞnSúêøâÝò’�¹ú¹Ã7Âþî¯Á¯¿þ*Ûo¿½¼÷Þ{…?ù1—.]*|°L˜0¡�fÒ¤I²ï¾ûV„ € €˱€ë ú´'뼌{J?ègÜ·5ü�º•è\–ûAÜ6Ñ|´K½ôóš´;´§ïtF BßvÛmkÌi:}SßóÏ?/õêÕ3.ˆÿ\<ÐLǼšq—"! PVÀõñoÒæµ`ý6iŒóÐnLEB@@ ¾Àå—_.Æ “ /¼PÎ<óÌø'"' € X0!B@{®} hOÖÉçÎ�+Í›7÷N¢ rËÒëÞÃÖÃþî—0`€ôëׯðÏ7Ýt“ôéÓ§V|úé§Ò²eË¿¯¿þúÞn^«¯¾z²Æ"7 € €@l×ô h�Ýô^FÆ=æí¦ãž8-b¨缦y–å~`j”ŽvI¢—~^“öøá‡ä‹/¾�ºuëzl¼ñƼí8ý¦�R;´ë¥F�%”nz°C»©”ójæV¤DJ ¸>þMÚ²¬ß&­‚q~Ú�©Hˆ €Ä;v¬œsÎ9²ß~ûɽ÷ÞÿDäD@ C &Dhϵ/íÉ:ù´iÓ oÄYÖv‡ Xû{±¬1­E‹òöÛo—\04h�ôîÝ»pŠRÁïÉZ�Ü € €¦®/èÐnÚÒÁé÷˜´G÷Dm“@ݨ猒~YîQüii—$zéç­v{¤_#wÏèBoݺua·öµÖZË›Zg�uŒ*H@»S!ójѼH�ø\ÿ&mQ Öo“VÁ8?íÆT$D@â Ì™3Gvß}wo"èµ×^‹"r"€ �¡€"´çÚ7­€v=ÏСC½Ý—~ùå9öØce½õÖ˰Ùqê»îºKÎ8ã ¯0úºw}°PwøZް€õ°¿ç ´oôêÕK\`3fŒtíÚµ$“î–Ö¾}{Ñßµz¬¼òÊÞ¢§îÖÎ� € Py×ôÓh_ÞÆ?Œ{ÂÚãŒ{¢ÞÉÕÔ]–ûAÔ¶(NO»$ÑK?oµÛ#ý¹{Æâ t `Ÿ7ožµŸxâ‰2dÈÉÿÆ(WSÚ£õæÕ¢y‘ð ¸>þMÚ¢¬ß&­‚q~Ú�©Hˆ €$hÛ¶­è„Å«¯¾*ë®»n²“‘@� ,˜! =×®i´ÿöÛo²å–[�_ýuÙn»í2è9éžR_-½hÑ"龜^¿~}ã ¨Ýá‡.œ­G�=äÁ4Zˆ3¾H„Ú.ÅmÜ Aã€û°€õ°¿ç‹þÁÈæ›o^¨‰îÎ>{öliذaÙÚÝ}÷Ýrúé§Ò\vÙerùå—'É&{ç°é¹ëÔ©#ÚvQŽ8ù²¬G”²“@ìp}A?í€ö,Ç?þùg�P¯^½Ä‚qOi°qMØß“Ž{ü%Ó Mm/ŸúÇIu“üÞOsü›¤‰o†˜' ]bÂe”-Ëö0-r¹2�#Ÿ^ÿ¦sOi|¶›–µZéüAèAåxî¹çdÏ=÷ -bÒ€výnÕÿ-]º4òü_hár òç×ÏoýÍ´ÒJ+™d+™&É÷vþ¤.Í«%Â"3 ��€ëãߤ$¬ß&­‚q~Ú�©Hˆ €$Ð]Q_xá¹çž{dÿý÷Ov2r#€ ��€"´çÚ5­€vÿÂÒo¼áí°÷øþûïeüøñòØc�É„  §i×®�œrÊ)ÞîÞM›6õŒn¹å–BPºîà}ÜqÇ• *ÿæ›odâĉµÎ­Ùj«­¤sçÎrÈ!‡È;ìx Öüñǽ2]{íµ5ªxÅWxÁÇzhà¶¹WúxóÍ7½òéï@ÝýÊœ{ÑÖ[o]¶ha�aÏŸüì³Ï–Ûo¿½p­‡~XŽ8âˆP}8s³Í6+ÔÁäuÔo½õ–÷€A>X~ÓM7õ:ÈâHËù�wÞ5Ñ2/\¸PN>ùdiÕª•<óÌ3^@~—zíK#FŒ(ô¯¸ùüIêá¿ÿôáÝ�¿Y³fFäÈrà 7²Ð…‚sÎ9G7nl”ŸD € P9×ôÓhO{ü3kÖ,oÜ£c Ý ¤øÐ1Êßþö7oœ¢ã¡üx#¬õ÷¸1îÑvüòË/EÇW ¨1†Ó7U�yæ™rÌ1Çx»Ç hOò{_Ë–Öø7i9Âú{§]DLç²ð÷Ÿ3Íö˜1c†wÏé8ü÷ß—ž={JË–- —,5ÞÖ¹ª‘#GzsAÅó-ú9­÷ê‘Gé½a.¬ÂI IDATè_çµt~ÆÿÙ®óG:~nÒ¤I%øªr ÿw¥¾áP7¿Pïü¡óxúF¾b· ÂÆ hÿøã�½ïÖÉ“'ט[Ôóëõ´º~ºÛn»•Ýt¡TÐ9�»Ñ]æŸxâ‰ÅÖïë~ýúI—.]Œ7–Hú½íw‹3¯V•ŽÂE@ \ÿ&%µ`ý6iŒóÐnLEB@@ ™ÀM7Ý$·Þz«èÑE]”ìdäF@  &Dhϵ«�íO>ù¤tÐA¡½îÑG•C=Ô ÌÖÅ/=8à/˜[w³óZW ~?ï¼óBÏ­ t‘mìØ±²ÞzëÕHÿÐCÉQGzŽre Í#Áûï¿/‡vXÁ"ìè=hР’oó Xû»^îܹҼyóBQL*ó4 CóÇ�wÞéý¾-uøÛ¦cÇŽ2eÊãÅÃ03ý{ÚÎþ2k0“.–úû˜ÞãÆ�+0ÅÍ—¯cõÐ{jï½÷–矾@wï½÷zAù&ÇÔ©Se×]w-$5ݽßäܤA@�t\_з5 ]“éï[ݘÄäЇîÑ1§>°vèÛªôÁàâ·_ùÇÅçHã÷¾ž/éø7­r„ù¤ýwÚ%Ú}”¶¿ÿ|i·‡¿_û7eðÿ]ß2§;{kàz¹Cƒ¤gΜ)m´‘7®7I¯ÁÖÛo¿}*„ ,Hmž¯Q£F‘ß範?]Ôà�¼òiÓ¦’›¼‘/J@»><¯sI·Ýv›‘«¶›n|Ñ¡C‡Àôþþ D¬³Î:²óÎ;6!(u!�çxñÅEçÃJi}o�?꼚‰@�å@ÀõñoÒ&²`ý6iŒóÐnLEB@@ ™€N‚�p Þ̨Q£’�ŒÜ €d `Á„í¹vµ- ýÙgŸ•}öÙǸÇéo¾YtñQ�R úº“´þ6Ò®£0òÒK/y;fçÝ�ëè£�=MÁÔ¥.êß©/´pÿ?�Öïƒ>�ÕW_½V–°€õ°¿ë ýiL)‹ ¢e+ØPS ž.õjnÿõÊx˜§ËÚYQ5 ¼ÿþµEµ.ºˆŸX£¸®Qòi}Ò¬‡Ž;:uêT`Úd“MDûWXa…Pâ¾}ûz;´ç�¡C‡Ê‰'žš� € Py×ôm hÿüóϽ Ç8ÇÛo¿-mÛ¶­••qO°¦­ãÿoé°¾ »´çæÖ´¥Æ;iþÞO2þM³a6iþ�v‰~¥éï?WíáŸ;𴛎·ƒê­xëîmÚ´ |kž?OÔÿKYǽßJ�OçÚz÷î�¨iK¡¿û‚ýK}¯å `Ю»âëƒ÷ùyÂ(ÐÝôuGuÿáïà~å•W_CçI´ÎAsYi~oÕ5ê¼Z/Ò"€˲€ëãߤmcÁúmÒ*ç' Ý˜Š„ € €@2�ùóç˶Ûn+�76Þ¥3ÙÉ� €@4 &DhÏ5™MíþWáæ{”îÊÝ­[7ïwÍW_}%÷ÝwŸ\wÝu�®Ô‚¾¾ÊYw´+>t§> °Þxã�½E%ÝÉJwÀ>õÔSk¤óïýí·ßÊ×_-Ó§O¯‘V!uñKݹK_]í1O­‹_»ï¾{�Ý­4·îFðÁK³fÍD'`u·tµôïPU*È<,`=ìïÚ·ôõÙƒ.Tæå—_–�vÚɸrº`©;xç"5p[‹w}/>ÙèÑ£½¾’?üAàÆHX)çâKkàÍùçŸ/?þø£Ô¯__.¹ä¯-õðû›æK»¿þú«·›\q`�? Èý§Ÿ~}÷œ9s¼?kÛêBëºë®›¤™È‹ € ¸¾ o[@»þVÖ 9}»TþÐßCú°ßž{î)M›6õÆ:Ç«»·ŸvÚi5Z¶TpãwÆ=¥Æ¿ú&²ú¨Ö\^¹ùÝ<£OŸ>Þì‹-ò6ÐùGݰ£ø(5–æ÷vP›D�W3iWÒ €˃€ëãߤmdÁúmÒ*ç' Ý˜Š„ € €@r�]vÙEþýï{'›nºiòr@R°`B„€ö\{ÚÐô*ó©S§Šþ¦ñújâ ZÐÿì³ÏDƒÒ‹�;î¸CÎ8ãŒBppñß>þøcïÜÅ‹Zº|÷îÝkœÃ¿£¢<øàƒ�çLñÖ©u*ÿnKšàÕW_•vØ!ð²ZF]LÍ¥v+ Xû»¾ây»í¶+:ëutçn Ήr\tÑE2pàÀB–çž{Î ò :ôw¯€ë«Ÿ5`z¿ýö“K/½4•6©”s¾^ºª ®ùÙýõ-µ –/‹z\ýõrá…ŠxÁˆþ[¹CÛQwMË0dÈ�TÚ*Jÿ"- €˜ ¸¾ o[@»ÿ¡@ý­üæ›ozqAGÐŽ³3gά±Ã-ãžšr¶�{4pýˆ#ލQhíúЧÿÐàK}ˆÙ44þÍâ÷¾–'êø7«r˜}bÅOE»Ä»�â‹—Ï™U{Ä hׇŽô ~[mµU�Bû†‹ÿøôÓO×z ¡ÎÃéø¹øme:Ÿ¤o¬S§NlÊ {.öÉro¼ñF9ï¼ó’œBÊ¡ë÷šnxPüp¼¾±NƒÌƒ“€ö3fÔú -˜¯�égkq‚æ3JÍ¿”ÚÑ}ñâÅÞÛ³‹w‰ÚL"«ïm¿_”yµD Nf@`p}ü›´),X¿MZãü´S‘@H.pæ™gÊO<áM<ïT™üÌœ@�äLˆОkÆ´Úu±¦}ûö…ö°Wû{PÐ…å²4ÿ]wÝå¥A ú �~ýú’™Îúƒm;vì(Ï?ÿ|�×ûw¿*µ;|ò»¥üü¯€×rèN�ùIWî …50Œæ Xû»ß'ÈÐÄÆ�«¯¾Zú÷ïo’5Õ4•rÖBkÕþ_n1;hAÕ$_õøôÓO¥eË–ïRIäíÞ_îA…T’“!€ €@,×ôÓhO:þñÿV.õÖ¤âÆzñŽÀ; ¦Ô7i0äæ›o^H¸§f×¶yÜ£¿‡õ�acÆŒ)Zw}Ößó¥Ž/¾øB6ÜpÃÐño¿÷õ¢QÇ¿Y•#Ö˜a&Ú¥6”é}dH)Y–í' }üøñ¢ó-AGÐ 7Ýt“·swÐñÎ;ïÔŒOãírK–,ñÞÌ—Ö<ßj«­&ú¿$GXúäÉ“¥S§N…KèC:?µÑFÕºlع4ƒ?pû¬³Î’[o½µìƒót®o�ËAóAó/ºQ†®Á–:üç=à€¼¹ºâM ²úÞö—É–yµ$}‰¼ €@¥\ÿ&õ²`ý6iŒóÐnLEB@@ ¹€ÁhÀ�îÀ©¯1ä@@À& &DhÏuÓ….}]¸. ¥¯W¯ž·ö>ûìSØÕ\w4×�–tñ3èXc�5dõÕW/üÉ¿+‘.b½ûî»^ F©CwsÚf›mdΜ9…$þ ò Ÿt—ð6mÚ”½‚mýù¢.èguÿékèu7ùŸþYþóŸÿx; ·nݺìåÎ>ûl¹ýöÛ it¡MH(>ÂÖ£þÝ$H'¨Ðþ]Æ‚³²->o¥œõš~ø¡èë¶ËA ª&ù²¨GP �çtíÚ5° þׯëôþjذa%š’k € €@ ×ô£´g=þñÿÆÕ·AipØî¼‹-’úõë×jAÆ=Á�ÚÖq�·sÿêïá¢y’ZÒßÜ={ö”áÇ—ÿê²ø½¯ç�:þͪ1>¾Œ³Ð.ñï#cä ³l�¨íacVÿýQ.0[ üo´«Ö š#VÒ° ô Ï5ý>Ô7løßVv.Ým}‹-¶¨ñ¶Åéӧ˶Ûn[¶ìAóþù  {ˆ?è3Óÿ=Ÿå÷¶¿Â¶Ì«ÅêDdBª$àúø7)›ë·I«`œŸ€vc*"€ €É^{í5o§ݵçÉ'ŸL~B΀ €@ŠLˆОkOÓ€ö¬_]w7ï믿Þ{Usþð/úËe—ðAƒIïÞ½ çö/hE]ÐOñöI|*ÿ.P•h/÷šçrò/ò™û$FJáqœ5�]æÐEÊþUÓ|qªeR�‰'JçÎ� §/÷àÁSO=%ú÷üQnçº8å% €¤/àú‚~Ô€ö¬Ç?º‹®ÿá]Ýíõ´ÓN«ÄgÒšŒ{‚•L~ÇF}P×?® ËT2ÿX²C‡2eÊYa…Ê6·ÿwtZA°&N•ÿš”Ãä~ˆ›†v‰Å5/—/ËöˆÐ~饗ÊW\Q²¸þ²†=ˆœ�…g5ÎiRÏo¿ýVÖYg�Å Ú ?ì\þïÁ°‡Š/èÿì ûœ7yó£Ö«mÛ¶…ûJÎWúÛÚåyµjô[®‰¨€ëãߤ­hÁúmÒ*ç' Ý˜Š„ € €@r�àÉïìøé§Ÿ†Nˆ'¿"g@@À\À‚ ÚsÍeÐî_œ3oéà”þÅ¡3fH»ví ‰M^ ¬‰_|ñEÙ}÷Ý ùü Dú¨eË–…¿ëo#}}ñÒ¥KËî~¨»ÞsÏ=rúé§òúË\‰ý¸ÎºX¥»S-X° £»NéN“ºËÕ3Ï<#ºczþÈ" }ܸqÒ¥K—Â5^yåÙqÇ#WÉ¿h¹þúëˬY³jìðù¤)eÈÂYß°Ô¿ÿЦùÊè4ê´»ØG}$­ZµªQ— ØtáyÓM7 ­3 @@ z®/èG hÏzüô†m]ÝÕ÷ª«®’N�:‰ã5hÐÀ¨Ñ÷¸5îñ�MÓýílš/�ßûiŒÓ(‡Ñ 3íÿ>ŠI^6[–í5 ]߆|òÉ'—,¯ÿþ{?,8; ÏjœÓ´ž=ô�uÔQ…"í€v®¸o>Ñ‹úûƒ>`væ™gÊãÿû�!C䤓N*KVÞ,¿·ý³y^­ý’k"€&®�MêX.�ë·I«`œŸ€vc*"€ €éì¿ÿþÞ��=ö˜´oß>�“r@R°`B„€ö\;V+ Ý¿8ä_¼2YÒn¶ ïßÁ.I×µ= ]ƒÕÿõ¯‰î,ÿÄODªjíií®ç_x3yµs¤ÊGLœµsØBy¾¸þUÓ|ùüi×CÏëcBÐÎëþ×€‡í\±yHŽ € ¸¾ _í€vÿøG›éË/¿”¢qq`ËiPä�(;ï¼³l´ÑF%[—q�YÇ·eÜãÿöíÛWZ‰(AåiÿÞ�rí⊤]ŽP¤ h3¼ ûÈ,g´TY¶GÔ€ö°7ÎE½?‚�£IÙ›Ú´žºéÄ^{í%/¼ðB¡2\p�7Ç�?ÂÎåoÓN8Atn±N�:¡@Ao¿;vlaƒˆ8oâ+o–ßÛþ Û6¯Ú $@,p}ü›”ЂõÛ¤U0ÎO@»1 @@�t.ºè"щ?Ý�S_ƒÇ� €€-LˆÐžë ¦íÚo~ûí·Àô:¹÷Ë/¿ÈN;í$sæÌñºØsÏ='úÚt]¼:tôzõêþgqH3ûw`òïPç?o’þïÎ�º`™äÚay_ýu9òÈ# þaéýÏ"°Cw³ŸMójIêI^@ ’®�“ZY°~›´ Æù h7¦"! € �ŽÀ¨Q£ä /”C=Tn»í¶tNÊY@HAÀ‚ Úsí% ½\³ûG¢.pÆYÒò„½‚:ÍÀ°|œýn�Z§xç�wd«­¶ ]†.º“{Ø¡ëï­5ÖX£�”q�[ã{]sÍ5ò÷¿ÿ=¬ék=@ê£ê ²ú½uü›U9B‘$ ]âßG ØKfͲ=–Õ€ö ¤6ÏרQ#oþ(ÉÔí?·ÿ­oÅɇ�+I@»³Œ³Î:Kn½õVoM�8ó/QË›Ä9è» ø|¶Ì«%©#y@�J ¸>þMêeÁúmÒ*ç' Ý˜Š„ € €@:ï½÷žì·ß~Þ+i_|ñÅtNÊY@HAÀ‚ Úsíhk@û½÷Þ+'Ÿ|rhOûôÓO¥eË–…tþEœñãÇ{;æ}åð AƒD_gõÐ]å‹ó¢.èG½žIzÝ¿S§N5^ˬùž|òIÙsÏ=¥aÆ�§Ñ}à1¤ЮýꤓN’aÆ•½†ImØIªšÎ¥Œâ,¨fY�|9õA‰Î�;Š]¼}úȹ瞛މ9 € ,˜! =×~¶´ûrŠwu.ÕÍü 2šÎ¿ õý÷ßK›6mdÞ¼y…Óèµô 6I�¨ –I¯”߿㵳ŸtÒIe/åßµJgÐ>zôhÑ+óGÜݽæÏŸ/[l±E¡ »wï.#GŽ,´Ÿ¶u5�KÕ%΂j–õ(.§ÿÞ8묳¼±ÈFmTH6tèÐ;ø§Ýfœ@Òp}AßÖ€v T×1Yƒ BlîܹҼyóB:}’þþ×¶aÜS›ÏæqOÐøWölÚ´iÉ~ ý¤gÏžÞ¦5ùÃ?þÍò÷~”ño–å½Q$ ]âßG ØKfͪ=ô‚ËâíK–,ýžHkžoµÕVý_’#N@»^Ï?¤ÿ¦o%™9s¦\yå•^‘üŸþïHMc2ÿ§?íºë®ÞÛPòÇC=$Gydá¿ãÌ¿„Õ=Ëïm›Ù0¯–¤‘¨†€ëãߤf¬ß&­‚q~Ú�©Hˆ €¤'ðä“Oz¯Ük¯½äÿøGz'æL €$°`B„€ö\û¥µÐå_¨ �.×]~ýõWÙ~ûíå½÷Þ+$ ÛÑÛ¿ ¤ƒ^)ì=ö…^(ºƒtØ¡in¹åo(ýÕ®];/X$øW«h­‹lGuT¡LaAãþù§ì¾ûî5ê4sí“'O–N�:ʦAÍ·Þzk ð6пϘ1óÏË›s)£8 ªYö—ârêçŠî™ ÀÊ+¯,ûï¿¿h`‹úßz¯¯¿þú&]€4 € `�€ë ú6´ëoò»îºË{£¦þ>;ùä“kì¼^ª¹.\(Ûl³�Ì™3ÇKÒ£G1bDáAOÆ=ÉÙ>î ÿêîÃ:ž-ukúÇ¿YþÞ�2þͲY~Ò.5u£ÜGY´KVí¡e]Ú³hƒ¤ç ê.w~ÿ& þ´þÏ?�‡Ð † VHzíµ×Öx{\ÐõLœˆ3ÿbR÷¬¾·ýõ´a^-i_"? Pi×Ç¿I½,X¿MZãü´S‘@HOàË/¿”:Èšk®)o½õVz'æL €$°`B„€ö\û¥Ю �[mµU!¸âå—_–�vÚ)R¹âŠ+äòË//äiÑ¢…LŸ>]V_}õZçyÿý÷¥uëÖµþ=( ]ƒµ5X¾øxì±Çj¼ªÝ"ž ã¿ýö[iÛ¶maçp}�ñ»ï¾úšøH(!‰ý‹Rj¦6+®¸b­œÚÖ矾Ü|ó͵þ´³{Ø‚]ØßÕg�uÖ)\+Êëž‹ øàƒÊ1ÇSø§rAûZ÷矾Pÿ 6Ø@öÙgŸÄäÕt.Uø0ÿ |YÖý§žzJôµàAGñn¢‰‡ € €@E\_ÐO; =ÉøgñâÅÒ¾}{™5kV¡íLö÷ÝtÓMÞ[pòãžÿI¸2î¹êª«äÒK/-´Ÿ>ì©m¸öÚk׺§5°ø°Ã+<0šOàÿfù{?Êø7ËrhÝ_|ñEÑ·Ò®±ÆòóÏ?{ãýK.¹$òÃÓAž´K¼û(«/¢,ÚCËJ@{V-Vó¼&AÝ¥J¢;§ëæ/¼ð‚—Dçåôí‹:÷¨ßŸAóúÙ ›8S§N•]vÙ%ð2Z¾½÷޻ƦºÃm·ÝV#}œù“ºgõ½í¯l”yµÊô ®‚Ø/àúø7©°ë·I«`œŸ€vc*"€ €é è"‰Nöèä�öp € Pm &DhÏu‚´Úµ?½òÊ+¢ 6ºØ®¿=‚ÑËõ» WÂë¢þ#�<âŽ7jÔH,X ºs�¾j=èZÐòï�ϧ;5õêÕËÛ-:èùu¡êÔSO­qú  jÿâ”f:t¨|­ûå—_dÝu×ÍôVÓ‹î%ïZºø¦äýµ=ôÁ­SqÐLqÁÔaРAR·nÝÂ?‡-Ø…ý]uô¡J] ÓC�5à¿yóæÆ&A;l• ö9r¤}ôÑ…ówìØQ¦L™R£^Æ/JXMçRå óÊ—e=ü×óï Zü÷°·/Äi#ò € €@¶®/è§ЮÚIÆ?wß}·œ~úé…FÓßÊú{\ßjSüP¨&Ð9ÝqãÆÕ£è[9‹ dÜãÖ¸'hü«›“&Mòvâ×cÉ’%òá‡z&Oœ8±ÖMîÿfù{?Êø7Ër(‚ÿáu;vlâqŸž›v‰weõ ”E{hY hϪÅjž×$¨»\I>ùäiÕªU`’ ù¿Ro3fŒ·©Eñ[?ýôSoþAÈ]B IDATÌÿÙª;¶oºé¦5®gþŤîY}o>ê¼ZezWAìp}ü›TØ‚õÛ¤U0ÎO@»1 @@�ttÄgŸ}Vî¸ã9øàƒÓ=9gC@ †€"´çÚ-Í€öÝ V–¾}ûÊ 7Ü`|*]xÒ]íòAÓA Zz2ÝMNƒ«çÌ™SëÜǼ4hÐ@Þ~ûmyõÕWký½ÔÎâ°½Ç{ÔØÉ©8sЮîÆ‹�°”Y»ví¼ëô7`ñ¡ vgœq†l¾ùæ5þ]Óë®÷Ç{¬têÔ©Ö¯?¨ßdA.’�®£Áûqíü¯WS Š×7þ2uïÞ]4ȽN�:Æ×,•°ZÎ¥Êcâ”7«z]kÀ€µ^ï­o˜9s¦4nÜ8q›p@¨œ€ë úY´'Ñÿí·ß¼7Z=pªÁíØ®¿a?úè£À7nöèÑCFŒQëw.ãž¿ZÅöq�ŽÅÏ9ç¹ýöÛku%Ý�Xž0aB�¿åw&ÎÿcÐø7«ßûQÇ¿Y•Cëî •šˆs�Ò.ñæâX›äɪ=h7ÑOžÆ$¨;ì*ú°WïÞ½k%+ußöÙg¢óþCç“N8áùñÇEßÚ¨‹ù�x Æó�3ÿbZ÷,¾·‹ëu^-¬=ø; °¼¸>þMÚN¬ß&­‚q~Ú�©Hˆ €¤+ ¯È»ñÆåä“Oö^¿É� €@µ,˜! =× l h×Ý”Î>ûl¹ë®»B»¨.îkàõÖ[o-ï½÷ž—¾ÜBöüùó½W —Ú¥<è‚;·b³fÍËã_Ô*NT©€ö~øÁ{}rÞ \·nÝdøðáR¯^=ï5ÌÓ¦M«•\€<óÌ3S h×�¸Õ<èoÑ{î¹ÇøUð¯¿þºì°Ã…üaêA� iíÔ—•³wÁ ·µiÜ|YÕ#¨Œº³™ÿÁ‰›nºIúôéz“@ìp}Aß¶€vmÝ8ãͧäjt©㜗qÏá….΃¼qÇ=ðxØa‡Õ \ºûõAë_|ÑÛ¬¦ø�nÿx'ËßûQÆ¿•,GØ81ê§)íò?±(óQ�£¤Ï¢=ÂÚ£Ž·ý;‰‡½5@jÚn»íŒæ²¢XÙ–6¨žQçh‚Þ¡õ,g¬Aí:´©E)#-ס‡øç¨ýAO¢åÞvÛmk´q©ºgñ½�¯HÔy5ÛúåAª%àúø7©›ë·I«`œŸ€vc*"€ €é èd÷1Ç#Ûo¿½œÄ� €@µ,˜! =× l hÏ÷Ë©S§Ê°aÃäþûï¯ÑUuG:Ý]\VW_}u‰ºh¨õ}òÉ'eÈ�!òÄO”¼ ôí6ú? ì(~%qPÝQw5÷º#”îú®;,f},Z´È{�¾Š>èØsÏ=½¿í·ß~…úè.P½zõ}õrñ1tèP¯îO=õ”pÀ…?�5J4P „ý]Ó-\¸P¶Ùf›Â"¢Zhàýúë¯J¢m¥åþøãR·nÝÐ{Ã4ímþÀÔ5nº´ÛÃ?w0}út/ø8Doþùç²ÑFòë\†n2Uj>Iƒ�wÝu×ÂÃ)¥Þº×Ë–|þ7;”úÌ +¯?([Ó‡™éç�®…ê\YÐÛõúùÚ¿oítÝu×-YŒ¨ýAOäÄ+oßÛqæÕÂÚ‚¿#€Ë‹€ëãߤídÁúmÒ*ç' Ý˜Š„ € €@ºú= «_¿¾|üñÇéžœ³!€ CÀ‚ Úsífk@{¾K-Y²Dôõ¸ZÎVXAVZi¥½ÍЮ�Ø÷Þ{o­ý .ªÁÖº ’l:I©AòM›6•F�EêÕ?ÿü³|÷Ýw^-窫®*«­¶Z¤s¤‘ø÷ß÷‚ÇÕMë ÿ½öÚkËšk®Yòô ,ð|u×vý­Ø Aƒ4ŠR8‡.âžsÎ9…ÿ¾öÚk¥_¿~¡×øôÓO¥eË–…tú€ÀÌ™3KîDzÂØè§zY×cîܹҼyóBѲ4‰Soò € €@t×ôm h/n ýmöÍ7߈Îãæ�üEÓÇý�θçš¶Ž{Šû€¶ÿþóùã�?¼ñY“&Md½õÖ‹ý€nV¿÷£Ž³(‡><ÛµkW/�ÝtŒý“ï9h—¿ä²¾�LÚ(íö0¹&iÜÐ~›ŸÿÓù¯† zŸ¯¥ÞÆXíÚ¦õ½mó¼Zµ�¹> &àúø7¬~a·`ý6¬ˆ©ý�€öÔ(9 € ]`�=ö�ÀÐL¶Øb‹è'  €) X0!B@{®=mhër/¿ü²tèС�Lw§Ó] 9ìøþûï½]$çÍ›çj­µÖ’Ù³g{”;.ºè"8p`!ÉðáÃw·§¦”Ä/0`À€/„í°�  €Ø+àú‚¾ íö¶>%3ˆ;î197ij èƒïú6Z}Ø�wÞé½É�@à/æÕè  €@|×Ç¿ñkþ¿œ¬ß&­‚q~Ú�©Hˆ €¤/ ¯¥}ì±Çäºë®“£Ž:*ý pF@"X0!B@{®½l hÿý÷½ït×uýÍ¢¯U?î¸ãJö*-ûá‡.cÆŒ)¤a!;ÂMXÁ¤ƒ ’Þ½{®¶ƒžç}›vg¯ ›Ó— Ú LdÐ�Ð8@@À=×ô hw¯Ï¹Xâ¨ãëhK™'Nœ(�;wöŠÃƒ³¶´ å@[˜W³¥%(¸*àúø7©»ë·I«`œŸ€vc*"€ €é üãÿ�Ë.»Lºwïîˆq € PM &DhÏu›ÚG�-ݺu+tËõ×__f̘!k®¹f`W�0a‚xà�…¿­¼òÊòî»ïJóæÍ«Ùµ¹v€À?ü íÛ·—9sæxÕ¶Òàæ¢Ï�B.í“={öÝ‘= àV·Ò×cwìØQfÍšU(ø¨Q£¼q €¸)àú‚>ínö;×JeÜãZÝl*ïâÅ‹EßF;mÚ4ÑyÝ¥]ǘ €ÿ›ëe^�ž€$p}ü›¬öìОÔ/‹üËõ‚v œ@ìxë­·äÐC•Í7ß\ž~úi; E)@–[Ú«Ûô6tÌŸ?_ÖZk­0úß#GŽ”:vvÖtwß}·\zé¥5Òž}öÙrë­·VÖwõßÿ]þøã�LÊT¯^=iÔ¨Q&çÎâ¤ãÆ�“.]ºNÝ£G1b„Ô©S§Æå^|ñEÙ}÷Ý ÿ¶ÿþû{;îi}9ìX²d‰üùçŸ^á^ýu9þøã /è¿m²É&Þ +¬°‚� T € *àú‚¾�ãŸPt‡0îù«±LÇ=5¯uEÕ7»pÀ^¹nºé&éÓ§�ue\ Ä}¿<´2utQ€y5[�2#€€m®�“zZ°~›´ ÆùÙ¡Ý˜Š„ € €@úhÒ¢E ïéü>ø@VZi¥ô/Â@0°`Bd¹~ ÝÖ€ÿ.íÅÝi«­¶ò‚šõ!=ÿ¡�ï0Û´iSÃX™d'žx¢ 6,“‹éØS¦L‘ºuëfrþ´Oª¿A?üp3fLáÔúÿwíÚµðßþ õ}ô‘´jÕ*íâp¾ôa’Ûo¿½äõÍ ­[·NñŠœ @*-àú‚¾­ãŸJ·cV×cÜó—¬É¸'«vX^ΫëÿýïE?—Xã¨^«sßWÏž+#PJ€y5ú �Ž€ëãߤ ¬ß&­‚q~Ú�©Hˆ €d#pðÁËÛo¿-�<òˆì´ÓNÙ\„³"€ ` `Á„í¹vÒÅvÛŽx@Ž;î8ãbéÞÿøÇ?¤Y³fÆy*•ðÌ3Ï”Áƒgr¹ƒ:Ht÷?ÿç™\,¥“~ÿý÷Ò¦M™7ožwF}-ü›o¾é¬k_Ôv×]ÛóǨQ£¤{÷î)]�Ód%P. }êÔ©²Ë.»dui΋ €p}AŸ€öl; ãžš¾åÆ=Ù¶gG rÜ÷•³æJ˜0¯f¢D@ÀLÀõñ¯Y-K§²`ý6iŒóÐnLEB@@ �K.¹D†.ÿûßå´ÓNËæ"œ@ &Dhϵ“�íÚ}¾ýö[ï¼k¯½¶üìïVÈ~ñÅËÎ;ïlÐ㪓äòË/—›o¾9“Æ·Þzk2dˆSíÚ ï¿ÿ~�ݺ5¨ýÃ?”ë®»®Æ.ßÚöýúõ«NÃqÕHA;óé¿éØcƒ 6ˆt.#€ €€�®/èОm¿bÜSÛ·Ô¸g�uÖɶ18;ྯ4—AÀPÀ¿Ùój†p$C\ÿ&mT Öo“VÁ8?íÆT$D@²øç?ÿ)}úô‘8@îºë®l.ÂY@0°`B„€ö\;ÙÐ^Ü…,X úÊÜÆ�Ë/¿üâýiÍ5×”UW]Õ §‘ÄFݱ0ß–:9¼ÞzëÉüùóeÑ¢E^qõß„¶±å‚Ë´páBïmР�׆Mš4ñîW@Xv\_Ð' }Ùé‹.Õ$hÜS¯^=—ª@Y@øæ›o˜Ws¤­(&Ø/àúø7©°ë·I«`œŸ€vc*"€ €Ù|üñDz×^{Éúë¯//¿ür6ᬠ€X0!B@{®�\h7èN$A@@� \_Ð' =ÃÎÁ©@@@�eHÀõñoÒ¦°`ý6iŒóÐnLEB@@ ;�-¶ØBtÅ7ÞxCš5k–Ý…83 €e,˜! =×>´s›"€ € €@˜€ë ú´‡µ0G@@P×Ç¿I[Ñ‚õÛ¤U0ÎO@»1 @@�ìŽ:ê(™:uª :TöÙgŸì.Ä™@(#`Á„í¹ö! �Û@@Â\_Ð' =¬…ù; € € €€ ¸>þMÚŠ¬ß&­‚q~Ú�©Hˆ €d'pýõ×ËwÜ!g�u–\pÁÙ]ˆ3#€ PFÀ‚ ÚsíC@;·) € €„ ¸¾ O@{X ów@@@p}ü›´-X¿MZãü´S‘@ÈNàé§Ÿ–“O>YvÝuWyðÁ³»gF@ Œ€"´çÚ‡€vnS@@p}AŸ€ö°æï € € €*àúø7i+Z°~›´ Æù h7¦"! € ��À·ß~+Ûo¿½¬ºêªòÎ;ïdw!ÎŒ €@ &Dhϵíܦ® |öÙgòÚk¯ÉŠ+®(üñ‡÷�æºë®[¨†ÿïÛm·�l¼ñÆ®UÓªòN™2E¾ûî;Ïü·ß~“Î�;K“&M¬*#…A Ž@ØçIœsV#Ï“O>é}ê±xñbÙÿýe•UV©FQªzÍe¥=ã"òýWŽ|¦®/èÐnÚÒ¤ËB@CO˜0AêÖ­ë}W¯µÖZ²Ç{_*íßãIËc\pF ]"qežØ¤=–÷ߟ™7X®æÌ™ã­YשSÇû_ãÆ�¥]»v²òÊ+/WTvÙp}|Î<í¬ßVìÀ€öŠQs!@@ ¼ÀN;í$_}õ•<ÿüóÒ²eK¸@¨¸€"´çZ�€öŠw}.˜P`äÈ‘rôÑGÎòòË/‹þ¶Íþ¿ßxã�rÞyç%¼êò›}ôèÑÒ­[·@ÇŽå™gž‘ ,¿(ÔOl(£IyäéÞ½{!i‡dòäÉNܧiö›e¥=MÚ<(M5¾ÿ4ÐKƒMÒ<–,Y"õë×—zõê¥yZΕ‚í) rŠåVàƒ>�Í7ß¼Æoj R×÷°#‹ßãIÊV^þ_€v‰o—EN“öXÞfá^꜋-ý�XüÛ3î¼Dþ\z-ýÖßžÕ˜?¾œ~úé2f̘Z…ðÏùU¯”\¹šA÷’òÄýìˆrÍjŒÏ£”/,­ËóLau3ý»ëã_Óz–JgÁúmÒ*ç' Ý˜Š„ € €@¶§�vš<õÔSrË-·Èßþö·l/ÆÙ@°`B„€ö\»ÐÎíéšÀ?ÿùO9üðà Å~ã�7¤}ûö…ÿöÿýÎ;ï”3Î8õjZQÞ3fx»aå�-ZÈÌ™3½]²8P�ßÿ]ž}öY¹þúëeÚ´i¡(º³Zÿþý¥K—.²É&›„¦Ï:AØçIÖ×OóüwÝuW�Ϻ .¸@hÕùúfÕo–¥öŒÓ7*ýý÷É'ŸH«V­â54ÏA$ãÆ�K=X>ôÂ$(+àú‚>;´ÓÁ«)àÿÌÔÑ4Ð)ì¡ ¬~�Ç-O5 —‡kÓ.vµ²I{,ï¿?+Õbÿýïe‡v�Y³fÕ¸ä»ï¾+­[·ŽT ÿ¹øÝ‰/õÄþù§ì¼ó΢s{A‡Î/õpBëJÝÿq ®oÉÑ–V_}õ¸§0ÊWéñ¹Q¡"&riž)bÕŒ’»>þ5ªd™D¬ß&­‚q~Ú�©Hˆ €d+ ƒ�ë®»NŽ?þx¹òÊ+³½gG@ @À‚ ÚsíB@{²ÛSý^{í5o—¨&MšÔØu/Ù™íȵ~QÓÇ©eØh..TÂ-ªõ¯¿þ*Ûo¿½¼÷Þ{…¬q‹£^—ôîŒ;Vºví»Àú愔܅¹÷EØçIìÊU!ãÒ¥Kåàƒ– &®>iÒ$Ùwß}«PšÒ—̲ß,Kí§Ñ*ýý—u@»ö“�‹ãX‘'ž€ë ú´Çkwr¥#àÿÌ4  Ìò÷xœò¤#ñ¿³Tâw^šå­Ô¹h—JI›]Ǥ=–÷ߟf’ÉSi@ë¶Ûn[c~BϪo�Ó·PGy³�ÿ\¦%¯gxî¹çdï½÷®õ§ONM~øáùâ‹/¼ 7}�`ã�7Ýõ9T: /Nüy*ᵜhܯ_¿B¶›nºIúôéõ4¤_F† &ºÀ\êèÔ©“¬±Æ¢÷ëÓO?]2Ý 'œ C† Ü�´÷EØç‰kÍ÷é§ŸJË–- Å^ýõ½] ³ÞyÌÔ)ë~³¬µ§©kµ¾ÿtW»Í7ßþMjcÁúmÒ*ç' Ý˜Š„ € €@¶ºÃ‹.<êkMçÌ™úzÓlKÃÙ@–G &DhÏu<Ú“Ý}Ó¦M“]vÙÅ;ɲ¸«SÔúEMŸL?8·‹í6¸kú+Z´h!o¿ýv�²h;Îé†À;ï¼#[mµU­ÂÞzë­¢»�êC>Å;äéƒ'´¤‹Ògžyf­|¥l»/Üh‘AƒIïÞ½ ŵåa”JõWÚ)‹rVúûoáÂ…¡;bêðßÿþwïÁŠüqÉ%—x¿�þøã�’ k®¹¦ì¸ãŽY0qή/èО ñÉšXÀ$0¶’¿Ç£–'1€ïüÎ ¥]ÒîiÉÎWíöHVúe+·? µuëÖ…ÝÚu·e�¯Xg�uŒ*M@»SÅÝ}÷Ýrúé§®gã[Æ*†Á…‚Úûöí+{íµWÙ1e)NÓì·ß~™¿ ¬Òãó,»�­óLYÖYÏíúø7©�ë·I«`œŸ€vc*"€ €Ù ì³Ï>¢;jéíºS; €•°`B„€ö\ƒ§Юç:t¨è$ó/¿ü"Ç{¬¬·Þz•ìRU¹Ö]wÝ%gœq†wm ä;vlæ╬hÔúEMŸE]\\0°Á-ßz/÷êÕK\hž1cÆH×®]³h.Îé €¥÷Ýü�7Þ�µ×^;´6ºƒ¡²¾÷Þ{…´úÀÄìÙ³¥aÆ5òÛt_„VÌ¢jܾ}{ïÁu=ô ê­íTÍ£Rý¦šu¬öµmýþ;ûì³åöÛo/ðhÀQÛ¶m«ÍÅõc¸¾ Ÿv@ûò:þ‰ÑuÈ’ˆ[‰ßãQÊ“Eò;/X•vÉ¢·Å?gµÛ#~É—½œÅ­À>oÞ<)j×·‡é›¿ò¿UÊ ÐnOÿÐﻓN:IôM^ùcúôé²í¶ÛÚSHJRu� €vÆ”¶ŽÏã4¨­óLqê%�ëãß(u JkÁúmÒ*ç' Ý˜Š„ € €@ö\p�<òÈ#rå•WÊñÇŸý¹ €ELˆОk�´Úûí7ÙrË- t¯¿þºl·ÝvÖ÷yÝ9xÑ¢E^9ëÖ­+õë×7.³Ú~øá¢Á¾zôèÑC|ðA£4ã‹$L¨íRÜÆ 40¸�Z¿¨éê¦e×· i™£& Úæþù§g±Â +›D)‡iÚ4Ý’ôç|yõ�W}“Sþ(llZ?M§Öú?-Ÿ.¬´ÒJQ²×J›¤_›\XË´¬Ú?Lú_>½ž[?CŠw'7¹ž?Míçº&y´®»îº«Àž?^yå•H;)ë÷Ã;ìPãrþïŒ4ï ½PÜÏ“´û·É5ÃÒøw¹»ì²ËäòË/Ë–Ùß+ÕoÒ¨@µïÿ|ô{*ÿýmò9¤ùL¾ÿJeù¹ãh×Ï}èÂæ#޹údé[IG×ôÓhÏzü“õï�´ûy’ϯjÞ?i~�—û‰›Öïñ´ÊÔ>Iúgš¿ó’”£’ŸŸÅ×¢]ª%|Ý,Ûô¦¶üþ4-o5Ò´úËñÜsÏÉž{îZ¼¤íùï�¥K—FžG -\.AšßKz=Û‡¦õðk–íg>Ag©½~ç-ëóD&ý:Iš û¿RcÊ$¿3âŽÏµ/IçÓú°mž)IŸ2Íëúø×´ž¥ÒY°~›´ Æù h7¦"! € �½€\髟ÿö·¿É-·Ü’ý¹ €ELˆОk�´Úý“ËI'–¿ÿþ{?~¼<öØc2a„BÏi×®�œrÊ)ÞnÑM›6õzôwL>(]w =î¸ãÊ•óÍ72qâÄZçÖ‹lµÕVÒ¹sg9ä�C¼€Ë Ý�4ðRßp£Çµ×^[ã¾¾âŠ+¼ l=4XƒÜ+}¼ùæ›^ùî¹ço×*ÿqî¹çzF[o½u`Ñ¢Ö/júüEßyçyøá‡½]™.\('Ÿ|²´jÕJžyæïuÃùÝ…ÕpĈ×3fxA{šï÷ß—ž={JË–- u)µ`�ïSºk׫¯¾Z£î'œp‚uÔQ²Ç{”ì;ùz毫åÝpà Ë6¯^Sw¥Õ yŸˆëæ¿`Òþì?Ÿ1QÛèˆ#Ž(YÏRí¨÷¤¶¥z?ñÄ5òë}ܯ_?éÒ¥‹ñÃIûuX¿Ó¶9r¤wOß7ú™ ;Kyä‘ÞN×ùCû¬~>é}æïOú9 »Ü7iÒÄøöO³ßzë-ïA›üŽç›nº©÷ðM‡.æéÃJùÖõ3÷Ýwß•UVYÅøôºð¯¯†^°`�pÀ²Ùf›É�èyŽ/²ú<©DÿÎâûNwÏR×|_Ö] u³uÖY§d;¹Üoò• û~(Õž•¾ÿ‹­‹¿ÿ´Ýôû[ßx3mÚ´B[ég�~×uïÞÝ{x¤ÔΓQÌÓüÜ)÷' ÝÿûN¿SôsµY³fFŸ5úYuà 7¾cÔìœsΑÆ�KVþY?U<ãD®/è§ОöøG›/­ß;Yõó¬Î›ÕýS‰ïñ/¿üÒ› 0 ÆoHýìÖß�ÇsŒ·‹p”€ö¨¿Ç‹ý²(OþüIûgÜßyþþ‘´Tžžv ›‡¨d»¤Ù®üþ¬¤o×òçê›u\ª¿‘ó‡ŽMuœZû r@Š,˜! =×6´?ùä“rÐA…Þ/�>ú¨zè¡^`v>¸RÛèÂ�î¬ì?´®ü~Þyç…ž[èâØØ±ce½õÖ«‘þ¡‡ò‚ŸÃŽre Ëçïï¿ÿ¾vØa‹°sh€ë AƒdÝu×MT?}ãO¿£NÞëïSÿ¹´/Œ7®0aïÏçx¿`pÿý÷‹ôî´ÓNa$¢ �Ú¯4àÍ„]7èäþÝ;vì(S¦LñúgÒ~”V..÷ܹs¥yóæ…2Yö×Cô5hvç�w.<”P ^úxñÅE¯SêH«_çÏï/ïìÙ³E¬u�¨Ü¡‹Ô3gΔ�6ÚÈë§&éu±{ûí·/{Þ,ÚÑ_Çâ~z„$ð/$j ´öóÕW_=Ò©ó;à‡Ýg¥Nêÿ|Íêó$ëþ�Õ÷�ºi`�>8’?î¼óNÑÀ�R‡Ëý¦Ôýíÿ~°åþ÷—C?S4X#ìóBëÙ­[7>|xàÛ#L̳øÜ)÷' ]˨ÁÅsU÷Þ{¯÷ð›É1uêTïmù£øm#Yùç¯Ui_�´Ò¸¾ os@{Ö¿wÒúœqíþÉú{\ÇŠú uØ¡oIÑD‹ß‚äãäÏç÷x>oåÑs§Õ?“ŽÒ*GX{¥ýwÚ¥¦h©yˆ´ÝK�/íö›'°å÷gT_}ø8­ùÂF�½ù¬\ýãPÝøà�ðÈ‹5y3U”€v}HRÇT·Ýv›¡Îè:tLŸõ÷’+¿CM¿1hÔ¬ÛÅt~2ßÈÚ§.¼ðB£~¢s(ÚG40¹ÔQív´ýþ/Õ/ŒnÒ2‰Òþ�a2>×kê<Í /¼`T|í?:�Tî­¸YöŸ¨óLF•²8‘ëãߤ´¬ß&­‚q~Ú�©Hˆ €TF@ƒÙÿøã/(cÕUW­ÌE¹  €9 &DhϵCZ TiíPøì³ÏÊ>ûìc|�Œ5Jn¾ùfo�C�R�ºÓ§î­;}G9t¢ø¥—^òvϺ‹óÑGzš4ƒHÃ.æßñ/,}þïAÁ¨Që§;xÇñ(žØ×E? Tëß¿­ hmS]tÎ?¤à_(ЮçÕ’�îVä¤�²@ï¥kØuƒÎUn'ƨÎÅi¤ÙŸ‹Ëí¯£Éb°¿uaòÊ+¯,Ü“a}q“M6ñvøz…nšý:_Ó~Tn ¤ÔÜÛ´icԟ¨T;–ú\ k› ¿ûwh×4úЈ¾õ!�#î}aÚ®I?OÒìßY}ßåÛ!è� .õºjÿýïR¿ º¿õß¾J}ïd}ÿ-l뛂ު’ä{*è!†¬>wÊÝÿqÚõ|úP�îΘ?ôûBBÊ¿õ¤Ü5ûöíëíО?t×ûO<ÑûϬüõÜÕðMã³×ô®/èÛОõïm_ý<Èês&�ófuÿdù;Õÿvé.íù‡°5m©ïÙ8¿Çƒ>3Ó*Ošý3îï<­Kšå³IóïYõ“4=–§vÉ¢=Âæ LÇ)YÿþŒÒ¯ãö¯R×Ð9»Þ½{G)B­´¥‚Ðu.Áÿ°¹¾™ªmÛ¶%¯gЮoRÒ,óó�Q* osÓ7Òù�,¿—\újú¹£~º›{ñ•hÓùI-ŸÎ‘èXc÷ªÊ\�« €,ÏLˆОë€6´û_]™¿?4Cw$Õ]³¿úê+¹ï¾ûäºë® ¼}Jh …6º3˜ìn¼ñÆ^€Ÿî@£�̧žzj�tÅ»zê¾ýö[ùúë¯eúôé5Òjðª.Zé¡;>7iÒ¤F |V÷»NVï¾ûîµOt7úƒ>Xš5k&:«»ý©¥g)ÐrÔúEMŸwðOìûè‚ÀùçŸ/?þø£Ô¯__.¹ä¯zDY0Ο3@¢5h�›¶•î­ ÚüA„¤¯»(a× jßríqÝô:iöç|¹õ³ W¯^2xðàBUü ‡Au,׎êݧOÏ[_­zj oñt�´ûµI¿Ó€G½gôµ×ºK’?êÎ\¥Žk®¹FŽ9æï^ÿî»ïDÿ{ذa5’Oš4IöÝwßÀSdÑŽz¡Ñ£G{Ÿ™ùÃÄ�ä³Hû‰Þ3cÆŒ©qšR öQ¯÷¾¨äçI¾NIúw–ßwùòi †îP�ÀÐ|4à£ø- Åíãr¿)u‡½Á£¸þ•¼ÿËõ׳Î:Ë{½.lëƒLúºq}`$Î÷TP@{VŸ;åîõ¸í¿þú«·k}q¨IàÂO?ýäíx8gίXÚ÷uá=ÿFš¬ü³ú~Žú9šez×ôm h¯Æï�4?gŠû[’ófuÿdõ;µÔ÷¸¾ééÀ”† zoÞÐ߸º‹pÐKAãÖ¸¿Ç³*OÚý3îï¼´Ë‘åçdñ¹i—hóY·KVí6OPîs¨’¿?£øÆ ì,u�°7E™”-(=¿ñ�ÿ÷mØC�&íúyܳgOïÍHÅÇñÇ/_|±l°ÁÞ?k¿zå•W¼·ù?ë?úè£Zs‚Y}/eõ=jÒ6qÒè÷Á_|á=¨ªÎÅó.wÜq‡÷Æ?݈MmÏüáªÑ.åæ'µ|þ±Žþ›Î;žtÒÿcïL ®(Îüýã®(žDQPTÄq܈ n0Џ Á%FaŒâA�ŒÈ€ ¢(jpˆè¨A\ HÜ¢Q1hDó�£F� <ÿûöL÷Ü[_ßÛÕ·oÝ[ýñÔ9ž„ïVU¿õÔ[ÕÝU¿~ëѲún2þü ÈJqÒßôý|‹-¶(ù{#Þ׊ ÈÃøW{mÞ m}ÓÕsF%A»ú²~ô¢§Ì†Iß[õ£ìÃ;L¶ÜrË`oá“O> ¢·ÿä'?)iN¹à ®ý'í:“møš/ïï¿Y¹z°›µ Öå´[£"# @¨�ž¨ g—]v™èÆ € P/,ˆ h/t¶O‚ö¸#gËE¬ÑÈ7q‘lâ„ï¾û®¨(½8é‰é.LÿööÛouoFi$ø”ÔaFRQ±‚~$W§ËqeFGÑk©n¿ýö‹½¬Ú¨"Ü0•‹&“¶}ió—ÛÈSÁ³nG$/nHµƺ©"A3é¤~P¼�¦ÑõÍHÆI×�ƒ]IÐæOËÍ•?›‘·Õ/4®n¢TJåú±œÀyÍš5ÁñÛÅQÎâ"Á»òë8{uÓHOb0#»™5Åžzê©&§Iè|ªÇLGÖyAO†hÑ¢E FWý¨Ñôƒ�öíÛ›§Gu”\}õÕ5››t3íÐCmâzä±¶_EÔI~“4'¦õžO²ú·«û�ÉuøðáÁ aÒ�tÝ�KÍÁo’æi_Æ9ÕûNÜØÒMl½O »uÕ±Xq?)œËy§Ò˜®VЮu^ýõÁ¼&]»Ò¿UJêçM3LúqÒ”)SÊ~æËÊ¿Q|“æÓZþž÷ }íõ|ÞQ_Èêç®æ/WãÇÕsª ×Í�__yå•àc›w Í÷ÞZíó¸+{\ùgÚçôë_²ë‘h*l'A€êEÀƒí…ή• ]Eª=zôˆDÁIGüš~™¤Ò”–¿ýöÛQzqŠŒ;VFŒe3…Mq>oŠ¡âÎ6båzŒ'ó¨^e Ïxå„õq‚bs\íNÛ¾´ùã6ò´o´ßMño1Çj6ŒÇŒSâf¿ÄE 2£i%]7®¯m˜Øä)®»^þçóqmŒëGý`dÈ�!eÝ_7ž4ên˜úôéølñG ®ü:ÎÞ9s梢¸'<?~|y>.½öÚk%J墣»êÇzÌ9qÑCÍëêÇ*æ×~Þu×]e›m¶IeZÚqQÏù$«»¼ß™�M.£G�–‘#G¦ê‹Ze®‡ß$ÍÓ¾Œÿ8;T´­óB¹·É®ëܹsT$IÐÞ¨y'‹ ý�wÞ‘Ž;Fm¬t¤»fŠó3óCWüÅ·VcÔ¦ž¼oè×ZОõýG™×óyÇÅ<£m¨E½®Æ�‹çÔ¸ÓjtM]ßcÊ%�Bûƒü äç¸÷VóùÇæyÜ¥=®ü3ísž+;læ½jóÐ/MÉÙ®CT˼R9—ý‘—çÏ4\õ4;=á¯Vë…›l²‰èYR’}Þ¼yÒ»wïèúѺ®sé©GfJªKó›‚MÝC½ùæ›+~$n®sÄ=·º¸/©½®î£Yú̶lšw…zö‹Íúäµ×^S¥µ"Í£kO'œpB”ߌ²íC?æaü+ÀZ Ú]=gTz?7Ÿ…â}˜cháÂ…Á‡îºÞ¦'ïi0 ]o S½üǧu&Ûy¦Ú|yÿ­¶Ýa9öo³6Áº<‚vkTd„ @õ! Q4‚ÞÖ[o]%±>Wç*€ °.ð`AA{Ám7¨ôøM݈�˯ÑA5 °~ F5׈æ!I7-ãÒæ›o«¿›QDtóI�^ÕÚrI£“ìµ×^²bÅŠ(‹) ˆ‹Ô¤Q§»víZqøÅm¶šåÒnÄ»ïzœ½rÿòË/åÓO? "#wéÒ¥âål6ŒÒ¶/mþ¸�¼åË—Ç WJi7ŒÕ—´ïŠæœ&Õë1®)I£Ý†Éü "éºq6Û0±ÉÖíÒŸÍöÙl¦¨]f¹$±¡–1Û¬§èRñ‡ ®üÚ´WOoPÿhÛ¶m¬Û™¶VÚ× ÌÈšqb%—ýèjž1ëÕ{�~(¢§)Ø$å¦ éÇ {î¹§è¿+¥4ã"Îõo.æ“Zø·«û]OS÷ñˆMÿÕ*�k¿Iš§}ÿåüU…Ûæi2ÅÜãîSf”ÈJæ�œwlž9ÊùXœmÖ¬YÒ¯_¿Ø"zêÊ.»ì=�ÆÍñqÏYù7’o­Æ§M=yßÐO+hwýþ£Ìëõ¼£×Êêç®æ/—ãÇÅsª]Üæ]Cç²Áƒ‹ž¦¸gÄjžÇ]ÚãÊ?Ó>ç¹²ÃfÞ«6ýO.Ë3Aµ}¡å\öG^ž?³ðó¡l’=nžÕu=À<ý/©.�¶¾Ûn»•œÚøòË/ËÞ{ï]EÜ:¢ùÜêâ¾äò>Z�¾·�êÙ/6ë qÜ“žµV®\¬Gë}mÿý÷— 6Ø@î¾ûîàƒ�¼÷£K_1Ǭ^+m �Jö¹zΨô~n®ÕÄ­‹ÆÙ¼zõjYýõ›üTOÿñm�É¥ïåýý7+öo³6Áº<‚vkTd„ @õ# B0?=÷Üse�³ªŸ5\ €Ö,ˆ h/8›­ Ýõ‘£ÕD£Ó±rýõ×QÃd L»m¢Ü…uMœ8Q.¾øâ¨ns#*íF¼OcÛŒÚÙ&mûÒæ7öÍÈDåx¥Ý0¶ís=êW7ÒÂ䣠ݥ?'Eöµí›ô¨å=öØ#Ú ŽôT3^lüÚl§FÓ5jTÙË™~�$NÚ× ¹ìÇj¸e)³téR™0aB‰@˦>ý`B}¥ÜGK¾Î'µðoW÷»8îæ¦¦í&©MfÉãÊoÒÞ1þ•›i§í}Ê|.IºO Þ9ïØŠTÊùÔܹså˜cމ~®4?ñÄÁ‡3aŠ‹’è‚#ùf‹iËæ}C?­ ÝõûOZþaþjžw\Í3µ¨×åø1Ç»‹ûxÏž=eÁ‚ÒºuëŠ]jÎO6‚vóÃ¥¸ ˜Ïµ´§µñÏ´Ïy®ì¨¦^Û2ôK<)ÿ°eœ&ŸËþÈËóg^>æµyÏÖu†öíÛ—˜w[R]æ})é#øâ š>žôj-îK.ï£õðÛw…zö‹Íú¤i�–Ñ` IÏúÁ¤†1SÞûÑ¥¯Ä Ú5Ø@q´{Ûëk0˜SN9EN<ñDÛ"±ùlîg•ÖYõ s=LOüÉO~Òä#Cëé?¾®3ÙpJ›'ïï¿iÛkæ÷`ÿ6k¬Ë#h·FEF@€ P??úÑ�ä׿þµÜ~ûí%õ³€+A€ÀºHÀƒídz´›€Y}ÖÜÔY¼x±tïÞ=ªÖæ8_Í·4…§cÇŽQ½ºÁ Çk¤ÓâhÐf{4ÚÉ�wÞ)çŸ~ô“is=6â«å¬‹Ë½hÕªUÑB¸F‹Ò��NõôÓO‹ JÃ䃠}ôèÑ2räÈÄ&§Ý0¶‚èE“„ÎI×�3ÜÆGlò„u»ôçÙ³gKß¾}£f<ÿüóAĨ¤dr™2eŠœsÎ9‹%m —+\ ¿6í5¡æµm¢É—±i›Ë~Lê/W¿ë|£Hë‡?Ó§O·¾ŒŠM/ºè¢&óqšq7~]Í'µðoW÷»8è¦?vèÐATL¾é¦›Z÷‘ËŒµö›¤yÚ‡ños¿)Ç<í}ªøþ×ÈyÇV¤Riî×S–-[eyë­·¤S§N%Eâ"sêÆ~çÎ�Kò%q¬†#ùº£fÝyßÐO+hwýþSÏç�¤xB[’ÆGÒï¾�Ï©æ}ÜöÃLsžˆ+WÍó¸K{âú³ÏãiŸó\Ùárþ¤_ª_‡pÑ/.û#/ÏŸ.¸Ö³N›÷lµçÞ{ï•ÓO?=2-î„­¤ºª�œ÷œ¯Õ!C†Dö¸¸/åý9Ôö]¡žýb³ž`s_O3FòÞ�iÚš6oœ =mÅùÍqéê9£’ =î$2µCOþ¹öÚk¥wïÞÁ nmÚ´±jj=ýÇ÷u&+`–™òþþkÙ̲Ù<Ø¿ÍÚëòÚ­Q‘€ Ô�ÀM7Ý$úŸ~ùû³Ÿý¬~æJ€ °Nð`AA{Á%h7�ÍM'Ñ  ¤ 3^–Aç» ]Åê¿ùÍoD#¸>ú裩šêƒ =IX6(톱­ }Þ¼yÁ†A˜Ì°I×�n#Ö°ÉÖíÒŸm¢ ŵ±šÈîIÈÅשµ_§µ7Mÿ¨Ý6msÙ�©¾£Ìzòÿ÷ÑÉ~÷»ßÑÛ?þøã²W»üòËEý/Ü(ÒŒi¹§*»œO’|ÀÕý.°iKœ Ã‘¤®¶~“4Oû0þLµþšö>U|ÿkä¼c+R©ä4æ‰ZO°}W¨g¿Ø<ߘÜãÖ5ÒðË{?¦ikÚ¼õ´×ú9#i¼ÿå/‘¢ý±X,zÊÞ±Ç+x ì°ÃeÑÕÓò´Î”Ö×ÌüyÿÍÚ~öo³6Áº<‚vkTd„ @õ# ÑÙ5J»F¼ºÿþûëwa®@ë4D´<ÐVЮÎúõ×_Çæ×Ž¯¾ú*x–X±bEà×óçÏ=î\ƒã’F@/>b5i‘·Ü`1#ô˜‘îÌz³ :S|xì<¥ÑÝõRn<&E?MÛ¯åÆ”é÷ÕÔ›äÕÔ©ö&Ýïâú2ï�iý&ižNËÞÅø×~JkGµ÷©âûj#ç[‘J¥ùÈì �V§Í´mÛ6*fF6Ö#úõëפZüÉ·^ó¸^'ïúiíÚf—ï?>=ïÔbžI3†‹Ÿ³]ŽŸjÆ{Úû¸M”OecsO©æyÜlc-íQ»]<�Û°0ýÉ….çOúÅŽ®í;§]mås¹ì�¼<feØèòIss±}o¾ù¦téÒ¥Ääâè’ê2ûÔ6àFÜ\Úi§'˜…bøzÜ—²ôUÜG£Yê³)kû®à[¿<øàƒÒ¿ÿ¨‰¶÷ßrL\>ÙôƒÏyâíú±þ 'œ _~ùe*Ó5ÿÙgŸ]rB¥«ç›ñþÞ{ïíГô’’žl«§­ªÀÝüÀ¦žþ“÷u¦$ÎÅ¿çýý7M[ãòz°›µ Öå´[£"# @¨�O?ýTöÚk/Ù`ƒ ‚Mr @ ŒmgZ;B6æG I÷)W‚ö¤ñhöeµ÷—âzÔúöí+�<òHôg=•¦W¯^Á¿Í#Û+ùº þëʼž÷ ýjí.ß|zÞ©Å¥í'ßÇ¿¶gÉ’%²Ç{¤mZl~W÷Õ4Ï¡/¿ürpº¡~‘”4€�¾o¾ùæQÖzúOsYgJ⬿çýýצ�•òx°›µ Öå´[£"# @¨/�ƒ>XôKà§žzJvÝu×ú^œ«A€À:IÀƒíÏóUÐns¼«œwÞyG:vì�!sóeΜ9A¤“0i4À‰'ŠCœ6iTùâM¸´¾i¯g“_#à÷îÝ»ä8e-÷Øc�Éa‡V¹´¸>]ø>ñÄ£?!ho*íÓ§O°AвeË€SÒFu\ÙøˆMž°nWþ¬óÀ9çœ#S§N­èqmL³A–OÚ�ué×iíMÓ?Ú¾¤¶iWýh3gø’GÅëŒÌQ��nH¶k×.ø[Zîiû5¼pÒ¸®¦Þ$0ë¬Õý.®o›[ä¬$¿©u¦õä¾/çw6B-k~Ô�tŸ*®·‘óŽ­H%i~ÒñŽ9æ˜([±@èý÷ß—í·ß>úí²Ë.2Õ꾕Ŀ‘|“¸Õò÷¼oèû&h÷éy'ô“,óL%_«T¯ËñS�û¸­€-éžRíóxµ‚º${\úgÒµ‹}É¥µœͺè—ÿ;A¥˜�Í:„‹~qÕjk^ž?Óp5Çhš²qy'L˜ _|q¦jlŸ³Ã‹üýï—}÷ÝW–-[]7œ¯õô•}öÙ'úÍ\G¼÷Þ{åôÓO/y®7n\$l¬Ô�… Ê!‡eIó¼^®Þ¤¶»¼�fê4ËÂ¶ï ¾õ‹9öÍ�,›eó¥ó0þZÚ@:åúÃåsF5Ï¡ß|ó�,_¾\ôCõy=é0.í¼óÎòÆoD§ßÖÓšÛ:S¥±š÷÷ß´ó�™ßƒýÛ¬M°.� Ý!@€@} è˶.(ê†ß€ê{q®@ë$D´<ÏA»¹ð:fÌ1bDâØÐlݤ S=£¨§ÙˆOlH•â6~ûÛßÊAT±F› £´íK›¿š…}mTÚ c[±è< §œrJÄíòË/Ý´ SÒuã€Û0±ÉÖ�&oZ—R1»Fû “FÚ{ォ©¦“6d]úuZ{Ó2Oj›M[gb'ä0ƒ)>íСCpÄò¦›n´&-£´ýj;®«©7É\ÝïâÜ ¹m4&ùMÒ<�¶?ÓúaRß—ó;W÷©bA{Ú¶ÔrZ±yæ°¹Þ_|œ.¸bÅŠ {qvó^éVíG%•žÉ׆]­òä}Cß7A{=Ÿw\Í3µ¨×åøI;ïëXIšËŸxâ Q�b˜ôýóᇎ>‚-7ÞlÚYÍó¸+{\ú§ ‹rï?ú÷Z½ïÖjnŒ«‡~‰§[«g‚´}çª?ÔŽ¼<¦aVkA«íÇ›•lLš›ãÊšârÍóÖ[oÉ~ðÑǺž¨É\G4£5§‰Zžôñ„‹ûRš95�Ô+¯í¼à[`‘û IDAT¿Ôš{­ë«¶ÿò0þµmµ´»|Þ©f¼›ý¦ïÀ*j:thôæ™5k–ôë×/øg=ý§¹­3U+yÿ­vËy°›µ Öå´[£"# @¨/�»îºKþýßÿ=ˆ<ðÿñõ½8Wƒ �u’€ "Ú žç‹ Ý\x5£•$wÜq‡œþùÑÏæF“yܯm½6ƒ²ž‹ÅåìÑEíbñºF Ÿ2eŠ´hÑ¢l4:½.xëÇŒajÎÚ¯¾új5jTb—š‘•£F-“¹ñ›ßüFzõêU±^�¨£¥aŠÛMãG.ýÙlßüùóƒ(ÿI©š š¤Íh—~�ÖÞ4ý£¬’Ú¦y\öcReùýÛo¿•·ß~;ˆ¥bÑÏ?ÿ\t®4ß”»ž1o]ŠÐîê~ÇÚôGåüú믗=½#‹”+[O¿É‹ È´ÓÕ}ªX<ÔÈyÇV¤bãcÇŽ-ùàñÉ'Ÿ”þð‡ÁýêÙgŸ ªÐhuêç­[·Ž­ÒÿFòµáV«÷Ù<ËÅÝÇgÏžEÈ,7Þ4Oß¾}+¾Tó<îÊ—þ™æùÚ¥µšãê¡_šR±]‡pÑ/®úCmÍËóg®æ¼œ¦l\Þo¼Q.¹ä’LÕØ¼g›ÐõN�Nž<9úI×õcµGOoÔ”#éÙ²øºæ¥)æwq_ryÍÔi–…mßÌqÜè~1¹«=zê\Û¶ñ'T„8V¯^|תU«B¾ôcÆ¿‚«• ÝåsF5ã½Ü°Q¿Ñ±¢“†©øT²zú�ëL–ÓKælyÿÍ ÀƒýÛ¬M°.� Ý!@€@} èËß¿þë¿Ên»í&±‚@pMÀƒí…NöEÐn.¼GÝ,ç‹æªæ37¢V®\)]»v•�?þ8ªF¯Õ¹sçÌ.žf#>óÅÊT`F 5EØqĄ̊Fš§9 ÚM¡lƒ–iÓ¦E?'m<Þzë­2dÈ�Š]{íµ×Š }”UÐîÒŸ|ðAéß¿Ùö—kh54I›Ñ.ý:­½iÇyRÛ”£Ë~t5×h½o¾ù¦téÒ%ºD»víäÕW_•vØ!õeM®æœŸ–{Ú~ ®µEëMòW÷»¸Nøä“O‚wü𨧱͜9³ª�Rwòÿ¨§ßÔº?ÓúaRß—ó;› ª¹OßÇ9ïØŠTl|Ìì=mpذa%ó�l(>qĬ×ô“Zðo$_nµÊ“÷ }ßíõ|Þ©…Ÿ«åmüTó|�4—ÇÝÇõ#š-·Ü²ìP‹›ÃãÞ ªywe�KÿLsuiG­æÆ¸zè—¦Tl×!\ô‹«þˆ›͵•´óPšñaóîQ ϵk׊žŒT«õÂM6ÙDô¿,)in.W·ù>¤ù4ª±¾Çj€/Mæ|lž ¥ylÖõ£�ƒ>8ˆ¦¦{ï½WN=õÔèßiýÁ¦�óþjû®à[¿|ôÑGÒ¾}û×KòÓGôùLƒèIu¾ôcÆ¿B¯• Ýås†ÍxW¡ºÎµmÚ´Iœ"Í1 ï¼º ïhõôÖ™aÕ(CÞß³bð`ÿ6k¬Ë#h·FEF@€ P_5­cÇŽÁE5êÞúë¯__¸ ¬s“’æ¹é¦›‚ÈM‡~¸tïÞ=X$“¹)Ú¡ nŽé ;aJ:ÊùŸÿü§rÈ!%lZ6®¿Ò¶/m~›…ý¸>J+XÔ:ô¨iÝX,—>ûì3Ùe—]J>|Ð(ÔÅâÝ´“¸:ãü3-7Wþàbž©U½®ÆOÒ¼76’žSãîkú,¤ÏùåRœ/î½ šçqWö¸ôÏ4Ïy.í¨ÕÜWýRJ%Í:„‹~qÕjk^ž?]p­g�Iss%[̵3¯9ëó¦žÖ7uêÔ(ë˜1cJN Š»žÍ‡.îKj‹«ûh=úØö]Á·~‰³gôèÑ2räȲØÞ}÷]Q{˜ô�eéÒ¥� =ïýèÒWâÚ¤Ýw(gŸËçŒrã]ß~»¼ð ¢×?÷ÜsK"¯—³õ‹/¾�½öÚKV¬Xd9í´ÓdúôéQÀ‚zÍ>¬3¹ô·âºóþþ›•“û·Y›`]A»5*2B€ �úP�FÊЗ¬}÷Ý·þpE@X§x° ‚ ½àqµ´ëa·nÝ¢EÕçž{N8à€T>=jÔ(¹æšk¢2Å‘j̊̈¯áïqÂ]ä6Ÿmzè¡’#ßÍúÍ2qã5Ï{쉠õxYA§v¦“Ù\DVfÊf½õÖk’;NDfŠ‹ìž¶}ióW³‘§ö¦Ý0Ö2ÊåÅ_”-¶Ø"¹ú�ú_˜âŽ.6&•NÐ͉³Î:+ØX(Nqþ™–›K.Ž.e{|s5ý˜´íÒ¯ÓÚëJÐêªÕßtøõ¯Íßÿþ÷å‡?üaÖé&(¯Ç²_|ñÅ%u�|òÉÁß·ÝvÛÄk|ýõ×2~üø’“ ´�FÉëׯ_T>í¸HÛ¯á…ÒÎ'µ´ëµ]Ýï̘1c† 80ús%û›ƒßÔº?]�ÿ8Au-îSIãÀå¼SiðÛŠT'�ÿÍ § êúU\*ŽRW®>WüÅ×–[-òå}C¿Ö‚ö¬ï?õ|Þ©ÕópÞÆOÒ¼7.’žSµŒy “ ÒtØf›mšT©ï'�tRô!N˜¡Ü{A5Ïã.ìqéŸižó\Ú¡}¡>_wÝu²ùæ›Ë—_~¬\uÕU‰õÚÌ©ôËÿPJ»aöš<.úCíÈËóg5Ì|*c37—³W£bk�ŠgŸ}6È¢k9z‚•®aª˜8n>Ö¹AƒA§ßþö·rÐAÅ^Fí;âˆ#J‚Gh�€[n¹¥$¿«û’ËçP—ó¤ÂIó®àS¿„÷ÓOžzê©Øõ�  T²Nh¾»¸ìGŸÆsZ[\ Ú]>g”ïkÖ¬‘=zóO˜lúæÇ9º¶¦§•×ábÿÃì¯4ëLiûÚ·üyÿÍÊÓƒýÛ¬M°.� Ý!@€@ý üìg?}ÑE[ý"˜@pIÀƒí…®• ]}åùçŸ"/립.̆flý(îjÜÿý�p|à 7”U«VÑ€[mÜF”Í3,¨–†Dó “Ö¯ Îç�w^Iýq"À¸õ»îº+êo_}õ••ÀÓ–O\¾¿üå/R4–‚,ºi¦bÉ�¿ö‡a«m*^,/®O9¨ µeË–ÑŸÓ¶/mþj6òÔ¸´ÆaƒtÓòÉ'Ÿ ¢Ù„I�vUƒnn§8�Üñ­*ä½á†DÃaÒ�4JNë8ÿLËÍ•?ë†JÏž=1Ž&Ú–í·ß¾¢‹VÓ�I›Ñ.ý:­½®­®úQ;kæÌ™rÆgDýÖ«W/Y°`AÉø®vÞQAº~¬çߺ«¾¯'l¼ñÆQ”(-óç?ÿYtcwĈM.­ö©¿øc ´ã"m¿†F¤�Oj%hwu¿+†1®Ò&isð›Z÷§«ñ'Õ¾‹»O}óÍ7òÿñV÷©¤qàrÞ©4§¤©ØÌMfdºâ2I§ûÄ=GTzNHÿQ|m˜Õ*OÞ7ôk-hW®YÞêù¼S+?w5¹?IóbÜØHzNÕ2q÷qs_»v­,_¾<8•fîܹM.÷^Píó¸ {\úgšç<—vh§˜â0í—‡~¸&ÏÍôKu뵺g™õ¸è�¸ç óy;í<äêùÓ×zÕk37W²ÅäZœ7n>.wª€~ˆ­§ŸÞøÎ;ïëpæ\¯Û;wî\bVZÐÂ6mwuu=OjýiÞ|êµ½œ=º¦¨§i¶mÛVÂç�‹.ºHžyæ™0ç —ýX¯±êâ:.í.Ÿ3*�÷;î¸CÎ?ÿü—®Á꺼ž>Vüq£fÐpfÏžÝd¯â±Ç+ùлþ“v�É…?Գμ¿ÿfeåÁþmÖ&X—GÐn�ŠŒ€ @ þT,vÙe—ÉñÇ/·Þzký àŠ€ °Nð`AA{Áãj)h¯…_~ùå�@Ø6é†ÑÕW_‰pã6¢´.�§bÝðXÎâúô£I›6mdÉ’%ÁqŸf*©Z‡zhI¦â²•"xÛ¶Ï&_9fÝ»w"Í™&ºÑvÁÈ®»îZR½æW!ê™gž)½{÷–´íK›¿š�<58­`ÑdxØa‡‘¶tCD?jˆëoõÝx2“¹Ñþ®x hdÝh“ú¦Ú»lÙ²àOå„+iýÈ…?«}Ç—qãÆEöëGydE7¬¦m6d]ùuZ{] \õ£ÙÆ"÷-ZØL)‰y*Ù�XØÈ -é7[o½uÉ/¾Î'µ´kc]ÝïB�9X£s…ó�Þ“ô#•r'U4¿I{HêOW㿜 4ì»ð>õùçŸË„ ¬ïS6ó›«y§ÒØO#R±�CÆŽÛä�rÿꫯÊFm”ê¾Uî9!-­§|m™Õ"_Þ7ô]Ú³r­×óN­üÜÕüåjüØÌ‹&›çT}‡þéO*“&MjâñW?}üñÇK~ #‡,÷ÞZÍó¸+{\ùgÚçR®_ªëôK©pÓv¢Ö6e\õG^ž?mùœÇfnN²?î´1-SnÜ¿ûî»ÁifÒ÷*=‘OŸõôÇâ5 0ï=÷ÜSrRVøwW÷%W÷Q×ó¤ÖŸö]Á—~ û´ÒǺÎú‡?ü!Ö5u=2îƒÿæþ>‘4Nã~w)h×ë¹zΨ4Þ+‹Ð5f¶ëÞ[o½ëCLBO5×ù\ûOÚu¦júÛ§2yÿÍÊÒƒýÛ¬M°.� Ý!@€@ý hÄ=Š^#Lêñy$@€€K,ˆ h/t°o‚v�n£›·ß~{¢û©H@…×{î¹gEÁpXÑ'Ÿ|\.JyÜ÷ßÿ Š)¶ óVuÔKÐþÙgŸÇ‡¢ÅJàú÷ï/Ó¦M "!«¨{Ñ¢EM²ë‡�C† þž¶}iò›Ñð’„…å˜'E@SqºF.>†µ#×j}Ûl³Mlõ!åfÃZ?½æškJ¢Y—‹ø—†›KÖȶ:F¤§6Ýyç�%ÑÇL0Õô£nFí½÷Þ%ãÖŒ„èʯÓÚ'h­µQ7¥öÙg«9IYÖc^ªe¤É°ÿ¿üòË j´þWmRAÁ9眜¾—ÒŒ‹´ýj;ŸTS¯�ëõ]Þï´þ—^zIöÛo¿mÒ‡ q‚®ZE(­—ß$ ŠÒö§«ñoÚyß}÷‰Fv¼òÊ+‡S¥û”­@ÆÅ¼SÉp}¦˜úè£e‡ÕÙgŸ-úŸ.è%W@#¢hTs3i$'Skd×iõêÕÁ ;z¤}\Òh¯úÛQGµG£¶ :TôÈäât×]wmSÚöÙæŸ7o^ðAB˜´¯ ”ˆjΜ9r 'Dù4º³Š£Ãdþnr¨¨]#¦Ç‰øµ¬FGÒ�´�7Þ¸¢ ß|óMà?•1.i¦Ñ£G¬µ_TP~D¡Qén¾ùæXŸ²åV|ÍZûó_|!{íµW´Y«¾«â}P–KÕô£¹‘\.²� ¿Nkï{ï½';ì°CÔ|3·ÜrKÙyAÛvðÁGb¥rmsÙ�æ¨äw‰.!ƒnü)SõßJsjX�Fü×%ôCj›¹Ñv\¤í×ró…9ŸTS¯­‡6¸¸ßéÜ ¾Z,"V¿P±F¹Ôü&éþ�¶?]�ÿrÛzŸºöÚkåÙgŸ�í¦¤ûÔOÏeIß}÷�~øá%¬TäÞ¹sçÄj]ñw9¯'6ªNò¾¡ï£ ]»ÎÅóŽ+?wU¯«ñ“vÞW;ÒÜÇõã´3fÈW\+bÓg�믿^ˆÖzôè½ht_}§0ÅlÕ<�‡ü\ØãÂ?C{mŸó\�­×¼wöéÓGyäiÙ²eÍfVú¥ºuˆšu€QQ­û#/ÏŸ®xÖ«^ód‡rsh’=¦Só'½³ë<¨÷?]s‹;ÕQëÐù~äÈ‘ATöm·Ý¶¬®ïKzáZ?绞'G�ƒ“í»B£ûÅìd]ë3ÁUW]UVØ®k­üÂæ�¥Öý˜46|þ=îd—×_]ºvíZ3³]<ïØ¼Ÿë=éù矞 õ¹¨Rºè¢‹‚ 6ívá?Õ¬3Õ¬ƒTQÞß³bó`ÿ6k¬Ë#h·FEF@€ кÙùÜsÏÉ/ùË`s�@pEÀƒí…ÎõUÐúÝÚµkƒ�íÔÎÖ­[ËlPâ’¦ ]7~ñ‹_”�rS\XŵD7tq\)U$¿å–[–�\nT\µjUÀW£¶¯¿þúÒ¦M›&õ§m_Úüõ¥íýàƒäÛo¿ Ú¬vlëÿO“BÖêŸZVû]£Ö•‹æoSwnµògk‹õ˃lÓžZåqå×µ²¯–õÔªki“m]:—ꜪcLçT:éÿV;·êµ³Œ [Û}ÈWËû�FûîØ±cÔ,ýÀJ…å"x5²ý®ü¦‘mJºvR¤¶Zݧ’ìÏã¼óþûïËöÛo51�±ÞüóÈ·œïä}CßWA{È»–Ï;®üÜU½å|.OãçÃ?”O?ý4zÇØl³Íd»í¶Kü8:®íµx¯¥=jc-ý³¸ÍiŸó\Ø¡Iõë×/²»~÷¡_þ¯÷mÖ!lŸeªÍWëþ¨ÖÊ净úm¸Ž¨ëhmÛ¶�ﳬ¹l}­î£õœ'«áá[¿¨�è3�¾ëjR?Ñw—jßÇkÕ�Õ°]×ʸxΰe¨×ÖûÒçŸ ÷*tm?n½Þ¦îZùOžÖ™l¸ØäÉûû¯M+åñ`ÿ6k¬Ë#h·FEF@€ Ðzdüí·ß.ú¥ï°aÃcW… �u‚€ "Ú žæ» =i0è‡x={öŒ²iD�VB‚ª#°råÊ ÚOxT²Šô5ò‘~èA‚G Íýnøðá2nÜ¸ÈØiӦŞ$Ò¸Ö¬ÛWN„®ÛtìZ?vìX1bD”9é‚âZáoÇ8.WÞ7ô}´Wß3MKºòsWõÖ²íÍ¡.žÇëÛ‹ú�ò¾ûîœLuÛm·'‘ @àÿ0Oâ €Àº¸Î”÷÷߬^ëÁþmÖ&X—GÐn�ŠŒ€ @ 1ô¬Ÿüä'r衇Šnz“ @®x° ‚ ½Ð¹> Úß|óÍàÈo�ºþÐCdz4¨¬ ªí'Ÿ|²Ìš5+Êô«K½ë�‰'ÊÅ_5Ùu¤Âu‰-m…€py¿3O.ñ9:ûºê B³õ|\d8ýðJ#Ú$øÛPŠÏ“÷ }íÙEºŒŸêÇOÚ’<�§%V}þ¹sçÊ1ÇT�æ©ê¯HI@ù"À<™¯þÂZÔšÀººÎ”÷÷߬~àÁþmÖ&X—GÐn�ŠŒ€ @ 1þû¿ÿ[8à€à˜¼%K–4Æ® @ëD´<Í'Aûƒ>(ýû÷�ü¿C‡²xñbÙb‹-bÇÄã�?.Ç{lô[»víä�7ÞŽq%AÕøì³Ï¤G�²bÅŠ [*,𷫝œ’€€¸ºßé=}ðàÁ%§#ÌòÏá„Vß'z\z¯^½déÒ¥Q%÷ÝwŸ 0ÀºRø[£j’1ïúÚ´Wïýõ/Éóx}˜¯Y³&ì³hÑ"Ñõ�Ò®ï>$@øÌ“xÖmëò:SÞß³z®û·Y›`]A»5*2B€ �ÆÐ#6?úè#Y¸p¡ì°Ã�3„+C€@³&àÁ‚‚ö‚‡ù$hÿä“Od«­¶*ñ{ý÷Ì™3¥gÏžQäMÍwÇwÈÕW_]’÷ /”›o¾Ù«qóÍ7ßÈ·ß~ëĦV­ZɆnè¤n*…ÀìÙ³¥oß¾ˆÓN;M¦OŸ.-Z´ �‘€«û�¾ÃrÈ!‘uG}tiTï$ ¨¶ï‹µk×Ê?ÿùÏ ÀK/½$?úÑ�¢�­ôo;ï¼sðÁUëÖ­­+…¿5ª&ó¾¡� A{õÞߘ’<�»ç®'ÄõéÓ'¸Ðøñãeذaî/Êš`ݧ€€¿˜'ýí,ƒ@=¬ËëLyÿÍêìßfm‚uyíÖ¨È@€GàÜsÏ•§žzJn¹å9ñÄgW† �fMÀƒíóIЮoF­-ݺu DyøÃšŒ ¾« iË-·ôjÜœ}öÙ2uêT'6i„Ò HË–-�ÔO¥ë6�N>ùd™5kVBÿ¿~ýÖm0´5"Pëû�ÉUÍ|ë­·¤S§N5²˜jjEAµ=IýXqÒ¤Ie èÉ<]ºt±¯°�þ©p•dÎû†>‚víÕ{cJò<îž»~8õ�üCt~Û`ƒ Ü_�+Ä`ÝÇ€€¿˜'ýí,ƒ€këú:SÞß³ú‡û·Y›`]A»5*2B€ �ƸõÖ[åúë¯]HüùÏÞ8C¸2 4k,ˆ h/x˜o‚vuú{î¹G díÿ�ö—¿ü¥l½õÖÖeê•qÈ�!2yòd'—;î¸ãD£ö1Û ^*-X¹r¥tíÚU>þøã€G»víä•W^A ‹w@ Fju¿Ó{¹Þ7õ…0Ýwß}2`À€YJ5µ$€ Úžf%Aûoû[9è ƒì+ûßœðO�,*�÷ }íÚ«÷þÆ•äy¼qì¹rý°nR?Ö\ € `C€u& >xÔô§?ýÉYIžõÖ[/uß x°[7$Ú놚 A€ �ê è¦àé§Ÿ.=zô�‡z¨úŠ( @¨@Àƒí…þñQЮnóÑGÉý÷ß/cÆŒ‰Ä´¦;©�ýÊ+¯”<ÐÛ±vÍ5×È„ œ€÷ÜsO™2e ‚vo{¿yöæ›o–D¿UQûòåË¥}ûöÍ£�´ &P‹û�)úÕ{çˆ#Ü2._ŽÀÌ™3åŒ3Έ~Ö *ä"5%±UÿvÕUWÉ÷¿ÿýª�Á¿*lA¡¼oè¯K‚vW~îªÞê½rÝ(ÉóøºÑÏër+Y7Y—{Ÿ¶C€€�XgÊÿûoV¿ò`ÿ6k¬Ë#h·FEF@€ Ð8_|ñ…ì¶ÛnÒºukyç�wgW† �fMÀƒíóUÐ^ìü«V­=âr£�6’¯¾ú*øi‹-¶�ï}ï{ÍzŒÐ8øB@#C†cOÅlÛm·�´jÕÊó°͆@µ÷»?üPV¯^pÐ1Z­Ð·Ù€ô¼!_ýuðážž°òÝwßÉV[m<ã�šÐõ)}lÓ¦Màã›m¶YfVð¯ÞÓ´WÏ®Þ%]ù¹«zëÍ'�×ãy<�½†Í€ @ ŸXgBÐîÁþmÝ‚öº¡æB€ @ �Ã?\Þ~ûmyì±Ç¤[·nÙ*£4 @ †€ "Ú ý’A;€ @h,í�åÏÕ!@€ @ >òþþ›•’û·Y›`]A»5*2B€ �Æ6l˜üêW¿’ë®»NØXc¸: 4K,ˆ h/x‚öf9¼h @€jJ ïúáû'ï?5u *ƒ @€ Ðìäýý7k‡x°›µ Öå´[£"# @h,�iÓ¦ÉUW]%ýû÷—o¼±±Æpu@h–X¶ÝvÛ¨¥æïûì³�ì´ÓNÍ�Dýš´`Áùë_ÿ0W_?æ˜cd³Í6«Ÿ\ ) 4—yà±Ç æ9MzÏ:úè£eã�7NIƒì€ �òòþþ›µo=Ø¿ÍÚëòÚ­Q‘€ 4žÀÏþsùå/)Ç— .¸ ña 4+,ˆ h/x‚öf5¬h  @€œÈû†>‚öÊnñÇ?þQvÝu×(S¯^½Dź*pÏ’\՛ŦæZvæÌ™rÆgDÍ{î¹ç䀈þmþ~ã�7Ê%—\Ò\q8o׃>(ýû÷/3O?ý´´iÓÆùµ¹ª%Ð\æ�ûï¿_ aЀ\óæÍcüU딃 �&òþþ›µK=Ø¿ÍÚëòÚ­Q‘€ 4žÀC=$]tQÝàÎ;ïl¼AX@ÍŠ€ "Ú … ½Y +@€ 'ò¾¡� ½²[üéO’N�:E™T(¨ÂÇ-Zdò'Wõf2ª™þÕ¯~%'Ÿ|rÔºßÿþ÷Ò£G�èßæï·ÝvAlªô…Å‹K÷îÝ£Ò;¼úꫲÑFUY#Å PÍi¸ýöÛKæ°Ë.»LÆ�'áóJ}ˆr@h®òþþ›µ_<Ø¿ÍÚëòÚ­Q‘€ 4žÀ;ï¼-«G“¾ð �7 @ Yð`AA{Á£´7«aEc @€ à„@Þ7ô´Wv Sx~ÜqÇÉìÙ³k.h¯U½NœXCÍ’^yåyä‘GäÎ;ï]“5ÓE]$ƒ ’=÷ÜÓú2ùË_DŒcÇŽ-©S×zõDÎ�J—.]$­ ÝU½Ö «"cV¾úÑ‚ö�¦1cÆ”X0jÔ¨@d®I…Î*rO›?¬ðµ×^“ÿú¯ÿ „Ò_|ñ…œ{î¹Ò©S'yúé§Áf…X¯1}úô躋/úZË}óÍ72xð`騱cdg9!ëÊ•+eΜ92eÊyá…JÚuÖYgÉé§Ÿ.‡zhYÁ~ØÎðºjï~ðƒŠ=¤×œ4iR š/f¦ÿ¿Znæ?üðC™;w®<ôÐCòøã�—üÜ­[79æ˜cä„NÆq¹�Š ]xá…�ÍaÒ>:å”Sʶ³\?ª°^ûRy?úè£%å»wï.#FŒ�¾}ûZL�Õ¯“üNûjæÌ™�ÏÏKÊPç�SO=5Ø; “ú¬2×yÌô''å~³Í6³ÁµîGë [dÔ�&NœœÔ¡Iûö'?ù‰õiY\ˇã@ç�aÆÅ~¬R«~N´×z<—ÃXë~U–»ì²Kä£[mµ•,Y²DÚ·ooÑ“d� 4%�÷÷߬}êÁþmÖ&X—GÐn�ŠŒ€ @À={öݤ˜7ožì¼óÎþ†%€ �k,ˆ h/x‚ö\#Œ‡ @€@]ä}CßgAû›o¾)'�t’,[¶Ìª/5:¸Š ·ÝvÛŠù~øaéׯ_b�ÅV…¹Åѧ�;î8™={v$–.®ÄU½‰†V™¡V|õôRw'¥>}úÂ÷ûï¿?U~�®É¼Ž~4¡âhóÚf™åÌ�,L!ëÝwß-�;w–8 ©IrôÑGË< m´Q“¼I×�«ÜŒvÞ«W/Y°`A âNË9ä^G×8nºé&¹ä’KÛ¥ôdõéí¶Û®lþ÷ß_¶ß~ûè÷:ãµXÈm6Û¡¨¸öÀŒ>J(wAý(báÂ…¢×)—jå×aý¦½}þ»ï¾®WJÊàÕW_•vØ!ðS›üºÏ´ï¾ûV¬×E?Z9DŠLj£~|~è¢EõC…sÎ9Ǫ-wâ‰'FyuïMƒJG²¯u?' Úk=žM.ûU?ÜÒBÂtÛm·É\`Õd‚ ˜òþþ›µG=Ø¿ÍÚëòÚ­Q‘€ øC@}{ì1?~|pœ* € P ,ˆ h/t$‚öZx3u@€ @ yÈû†¾¯‚v32º­iôYoºé¦±ET0Ú»woÛê‚(íÅ‚úr‚vWõZš2c-ùj”ê3Î8#Ñ‚Pœ­¼Óä…ÙÅ‚S ÿâ¿�‘#G6Ak©;®œYIЮõê‡ ãÆ�‹= ®‘*Ô×�ŠÅ¶šÏÈÚœVPéD€´œ‹í1[£Êkäú4IÇÓï~÷» ~\2ÛøóŸÿ<àW)™ý¨Báÿ÷úÅ&ʼn›Ãrµôë°N[¿‹³]£òk÷®]»ZùSÒ®úц{Ú0`@ôo×ý¬2EßµÏÅívݯqÊØôEÚ>'? ¬òþþ›µ—<Ø¿ÍÚëòÚ­Q‘€ øC@�‡¼îºëäÌ3ϔѣGûc–@€@® x° ‚ ½àAÚs=Œ0€ @u!�÷ }í*î;ä�CdÑ¢E%}¨Ñ¥�?þxÙzë­E¹kthÞrË-%ùʉj?ûì3Ùe—]šK5Âö±Ç+mÛ¶•o¾ùFž{î99í´Ób¨q‚vWõºràZóýè£�äƒ>�—_~YÎ;ï¼Èlç>ôÐCÁ¿5¢õf›m£Óæ+4¥Å|Tx}饗ÊçŸ.믿¾\uÕU��hJ¢ÆÕ~È "ðË/¿<ˆ¾víZY²dI ¶ýøã�KºGEúÍ¿8%]7®+ Ú«å¦×5jT¡¹¶CÇÊN;툌W­Zó‹ûPËjTt�J®ã£8éšÉСCeòäÉÑŸuì$E¶¯Ô�Ê{ذaïÕ«W‹òP�ü3ÏÑw4â}ñé ®ü€z!@ ùÈûûoÖñ`ÿ6k¬Ë#h·FEF@€ à�^xAú÷ï/{챇<úè£þ†%€ �k,ˆ h/x‚ö\#Œ‡ @€@]ä}CßGA»MV;R×a÷Ûo¿Ø>�1cF  S¹(í*\7Eǯ¼òŠtïÞ½I½*ÆTQ½)N�´»ª×•»âûÞ{ïÉ;ì™­hß„cÄlOÚüå„Ð*x¾òÊ+£ˆìæu’„ååê�6mZÈÆLq¾}:éºqý[IÐæOËíÝwß DéÅéÖ[o=}6®oÞ~ûm9è ƒJDûf„l­ë믿–}öÙ':Á@Ç� ß·ÜrËŠ®[Ž·~üзoß&e׬Y#={ö,‰à÷ÑŠ+¿Ž³WÁ¹¾[·n%öšÂáâŸzê)ùáX’_×�®¸â ¹á†JÆ�FÒoÑ¢EI^WýèjžÑz‡œv&=UA£ÖWJæ|ª L™2%òÕzõs½íõêW³/æÏŸ/‡v˜Ëî§n@h¦òþþ›µ[<Ø¿ÍÚëòÚ­Q‘€ øC@#ötîÜ90hÅŠ‰Ç%úc9–@€€Ï�lƒ @€€ò¾¡_kA»ŠO5*o(×Ⱥ $Mš9s¦œqÆQ‘?òÈ#e…Ñq‚×W_}µDlªïw‘zÖ¬YQ½]YÅ’åÒŸÿügùÁ~Pò³)hwUo^ióºà«6؈±‹mM›?NX¬ý§"YSü[|�$ay\½cÆŒ‘#F”EkÚ®ÍèÓI×�«Ü†‰MžâºÇŽ[ÒS g‡Š]�8âˆè§8Á¾iG\ž¸ºãx«À~È�!eyÿþ÷¿—}÷Ý7ú½OŸ>ÁœÐ²eËèo®ü:ÎÞ9sæˆÎqéÞ{ï•ÓO?½ä§ñãÇ‘çãÒk¯½V2Wi½?üp“4\õcÚù#M~�¤¯#„Iÿÿ³Ï>[vMçSý¨Aû6L . ¢‹‡©^ý\/A{½úÕôc=qzäÈ‘iº“¼€ �€@Þß³v£û·Y›`]A»5*2B€ ¿uÔQA]dÜ{ï½ý2k @ — @€ �ºÈû†~ZAû?ÿùOQ¡wÜûR«V­äoû[…øã�?úA#;pÀ¢¢ó¸´ùæ›Ë¦›nZò“Ñò_~ù¥|úé§AÄç.]ºTì× /¼P&MšåQ¬ ëÃdFµÖËMºèý»IýÚÆÁƒ‹Fê“)hwU¯K'vÁWíM+´N›?NX¼|ùrÙyç�+âJ–›¿ÛøÆwß}'‡~x Î “}:éºqFÛ0±ÉÖýÅ_ãO÷/¤~ßµk׊Ìâ>1˙틋šw³\¹ŠËšm0`€¨°¹øCW~mÚ«Ñî•EÛ¶mcš¶ª?é6ŧ4#ÝÇ�á²]Î5±^OÖ(>éB#¬‡A¢Ìk›ÑÊãX׫Ÿë!h¯g¿š‘íã> qé Ô @͇@Þß³ö„û·Y›`]A»5*2B€ ¿è‘�º1£G›žuÖY~‡5€ �K,ˆ h/x‚ö\Œ† @€@] ä}C?­ ÝÅe…}ã�7Ê%—\’µ1£Üš‚vSdª‘‚,X ­[·®xí'žxBTø&SlêªÞÌ@j\A_½\¡u5ùMa± Ùßxã�ÄSS“„åæï¶‘Æõ ý�"L> ÚÍñjÛ6mÓĉåâ‹/ŽÚ§§ôë×/ú·ÉÍ—sA³œMÄø�>ú(8é!üP&Nô]�ËÛøµiïÕW_-£F�*{9s$ ‡MÑw\Û\öc5ÜÒ”¹þúëE÷ÐÂTÉO~ùË_–ì±%�”`kG5ý\A{=ûÕÏÇ}bË“|€ °nÈûûoÖÞó`ÿ6k¬Ë#h·FEF@€ à=BrøðáÁQˆ7ß|³_Æa  ä’€ "Ú žƒ =—ã!@€ PWyßÐO+h7ÅšYaÛŠ`UŒ÷É'ŸÈªU«¤eË–Áe5Š´FŒ×?ýôÓ¢¢Ãd Ú/^,Ý»w�~·ľóÎ;Ò±cDzå\Õ›•kÚòYùêõê-h=z´Œ92±©ií¶>™$tNºnœá6 mò„u›þ«h´p�0_ÝÜ´eýõ×—;ï¼SÎ?ÿüè'“ËìÙ³ƒ=‘0=ÿüó²ÿþû§î�)S¦È9çœS±œ�è;®‚ZøµÙ�æ‡ æum¢É—±i›Ë~Lì°ŒLѶ~L¤'è‰ÅIçòC9D-ZýÙæ4Í좟ë!h¯g¿š~Ö¡C‡ r¾yBJÆî¦8 ¬òþþ›µ‹<Ø¿ÍÚëòÚ­Q‘€ øE@£À}ôÑ¢Ç1ê—•X@y"àÁ‚‚ö‚à hÏÓ¨ÁV@€ 4†@Þ7ô-h¿õÖ[eÈ�!±�§Çßüæ7A¤èG}4U›‚v Jrúé§Gu\~ùå2nܸÄ:“ÄîêM4¬jÉWÍIbešœ6Zaqx½$ayµ‘ÆçÍ›'½{÷ŽšeFâNºn\Ú0±ÉÖmž0�ÅmL�¯MÔë¸ëUÃÛFô^«Ö~�ÖÞ4ý£6Û´Íe?fñ ›²úñÄÁ\"T×=µ.]º”O­¼Þýìb<׳_M?Ûj«­D™#h·ñbò@€@1�¼¿ÿfíMöo³6Áº<‚vkTd„ @þÐÈ&º ¤ÑMXò¯°€@Þx° ‚ ½à4Úó6r°€ @õ'�÷ ý´‚v%üõ×_Ǿ/)‹¯¾úJ8àY±bEÐóçÏ�È«âø¤‘ ÍH½šï¥—^’SO=5ª'mÏš‚vSŒXIH_|­$qª«zÓ¶7mþZóÕë'±2mL›?­°8¼^’µÚzMûÏ:ë,Ñhãaäó¤ëÆõ™ ›<åÚžÖOŠó�?^† ýI?D™ùdyñÅåž{î ŽE$A€²ð`AA{¡´gñbÊB€ @`Ý �÷ ýjí•zÖÍÙŠ\‹ë|íµ×¤[·n±—éß¿¿ì¶Ûn²õÖ[‹²oݺµ´iÓ&X—�;wnT&IÐ~Ýu×ÉÏ~ö³D'M§š‚ÄZÕ›hX† .øª9I¬L“ÓæOœ–C’$D­¶Þ×_]vß}÷è²gŸ}v h焤ëÆÙkÃÄ&OXw-³ÇwœÌž=;ì_xá…¢Âä0ÙŽõjxÛˆ¾]ùuZ{Óô�²³i›Ë~Ì0•X}ÿý÷eûí·�òk€(?:kZ³fMðá“ú�&�¾dÉiß¾}“k4ªŸ]Œçzöëßþö7Ùd“M"žDh·v_2B€€A ïï¿Y;ÔƒýÛ¬M°.� Ý!@€€F�-¿øÅ/ä’K.)‰"៥X@y àÁ‚‚ö‚£ hÏÃhÁF@€ 4–@Þ7ô}´k$÷Þ½{˳Ï>[Ò±�=ö˜vØaÒ¶mÛØä‘GäÄOŒ~K´›Ñ¦ËyQ’8Õ$Öª^W^튯ڛÄÊlSÚüi…Åáõ’„¨ÕÖ»xñbéÞ½{Ô¬>}úˆúaË–-ƒ¿%]7®�m˜Øä ëž3gŽœp ѥ4Šüĉå»ï¾Kíbzš‚~<¢I×KÎ9ç™:ujÙ1WîÕðN}»ôë´ö¦ée”Ô6ÍãªS;A•Ô_4(Ô¬Y³¢TÐÞµk×àߦHÝü8$,ÔÈ~v1žëÙ¯Dh¯Òy)@Mäýý7k—z°›µ Öå´[£"# @ð�À£�>*zÄæGQ²ˆëŸ¥X@y àÁ‚‚ö‚£ hÏÃhÁF@€ 4–@Þ7ô}´›bPíÝßþö·rÐAUìè¤hÑO<ñ„¨à8LmúᇎÄÇå*O§ºª×•W»â«ö&±2Û”6Zaqx½$!ªù»®9÷Üs»à��SN9%Êwùå—˸qã¢']7î6Llò„u§É›Ø`#ƒŠÙUx¦—_~YöÞ{ïÄjªéÇ$Ñ·K¿NkoZæIm«fl%vB2˜señÇ?cÇŽ•#FDVÍŸ??ø€)iΨÕýAëIêgã9­¯dé6íYèQ€Š äýý7koz°›µ Öå´[£"# @ð�Àÿûÿ/ØTÙrË-å•W^ñÏ@,‚ �\ð`AA{Ác´çjØ`, @€B ïú¾ Ú-ZT"^׈ÒS¦L‘-Z”í_�6ݯ_¿ :v˜Ìí¦pPÅí³gÏ–V­ZUôÍÓ·oß(� áõo¡=®êuåÌ®øª½iÅ™ió' NË1K¢š¿_}õÕ2jÔ¨Ä.˜1c† 80ʧ~ªQËÃdÖû›ßüFzõêU±Þçž{NzöìYÖßÒrþãÿ(»îºkTŸE>±‘2˜í+'B6«¨¦“Dß.ý:­½iý:©mÊÏe?fñ�4e?ûì3éÑ£‡¬X±"(¶óÎ;Ëo¼!u}¿ýö“¥K—ßqÇeÉ’%Ò®]»&Õ7²Ÿ]Œçzö«égÊY£ä—;õ$Mß’€Ö-yÿÍÚ[ìßfm‚uyíÖ¨È@€ü$ ÑG>ùäùÝï~'E²~‹U€ à5D´<A»×Ãã @€ à�¼oèû&h7£^›"á¸Níµ×¤[·n%?™‚vS8¸ÕV[b> PR.é;áàÁƒeÚ´iQSÐîª^WÎ튯ڛVÈ›6ZaqÈ0­ ½’ 6¬3Î7n»í6¹à‚ ¢®3¯{ë­·§¼VJ×^{­¨ >L¦¿¥å¼råJéÚµ«|üñÇQ�ê³�;wÎìb>ø ôïß?ªÇl¹ TÓ�I¢o—~�ÖÞ´~�Ô6åè²3;BŠ ôƒn¸!*ñÞ{ïÉ—_~)»ï¾{ô·Ñ£GËÈ‘#ckmd?»ÏõìWÝ»Üm·Ý¢¹`À€2sæÌŠ‹¥èZ²B)È|i IDAT€À:D ïï¿Y»ÊƒýÛ¬M°.� Ý!@€€Ÿ4ZмyóDnu¡™@¨–€ "Ú �‡ ½Z¦ @€ÖyßÐ÷MÐ~ï½÷Êé§Ÿ9P’HV£ûrÈ!¢‘{‹“)hÿûßÿ.û,[¶,Ê6gΜŠë¸ï¿ÿ¾l¿ýö%õšcWõºA®øª½¦¸?I0™6ZaqÈ0­ ]Ë-\¸P>øà²Ý Ñ¦wÙe—¡¸F›îÒ¥KT&­à;®Î8A{Zn^x¡Lš4)²ëŠ+®�±cÇ&º˜æ¹é¦›ä’K.‘Ã?\ºwï.á|§…u/¤wïÞQ=ÿöoÿ&7ß|sIž¸‹TÓ�I¢o—~�Ö^‚vå誡†Ì”­~lQü!ˆ~h¤aÄ¥Fö³«ñ\¯~]¼xq0†Ã”4?×°Û© €š�¼¿ÿfíöo³6Áº<‚vkTd„ @~ÐÅÚñãÇË�üc¹òÊ+ý4« @ ùd¹á†äûßÿ~T\Eÿ§�vZ¬/Çù[Znº¶¡âuóãŒ1cÆˆŽ“víÚE¶¬ZµJ”—Ž­â'~]³f�¨Y¹š´m‹yª�éÇÕôc’ Ý¥_§µ×• ÝU?jÿ˜ÑÇÕï~øá’ù³VóÑõ×_/zJ€ú‹Šç‹ÓÝwß-ƒ *{©Fö³«ñì²_C�z�sÎ9G¦N�±5O1©UÿR 4yÿÍÚCìßfm‚uyíÖ¨È@€ü$ G‚jš6mÚÈ[o½å§‘X@¹ àÁ‚‚ö‚§ hÏÅpÁH@€ 4”@Þ7ô]Ú³vÈå—_ˆ~ÍÔ½{÷ ‚ð3ÏXõ?îš�ìgãÙe¿†üôä�ºÎ+ÊZ?>)wE­úšz @ yÈûûoÖ^ñ`ÿ6k¬Ë#h·FEF@€ à/]ØÖEa�Þ£ÑWH€ �jx° ‚ ½ÐqÚ«ñ^Ê@€ @`Ý"�÷ }í8ä ƒŠÄw•<ªÿþ2mÚ4iÕªU :ŽSßzë­2dÈ� ÆžtÒIM„ëq×P¡òÂ… åøã��¢P—‹\ìª^£É%_S[l¿)h×ßÒä7£HÇE �ã•VЮ¢Ú_ÿú×2lذDü:t|c›m¶‰ÍûÉ'Ÿ~ I+UxÙe—É5×\#p@ôE9KÃ-¼¦ÚrÄG”=Õ Î¶ý÷ß_fÏž-[o½u¬éóçÏê Ó¹çž+wÞy§„ób\¡júQÇ×Þ{ï]"ô7#ˆ»òë´öÆ Ú+E;ÿúë¯eŸ}ö©øC1GýhúÓ€dæÌ™Ò¢E‹Ä1�6ƒž°7WŸuÖY¢'$]ÓU?Û|¸àj<+CýöÍK/½œ,&—ý›ÖÈ@ù#�÷÷߬Ä=Ø¿ÍÚëòÚ­Q‘€ øK@#üèâ䨱cƒcBI€ �jx° ‚ ½ÐqÚ«ñ^Ê@€ @`Ý"�÷ }íêA«W¯¢_z饱¥Q³õ·£Ž:*Ïjê¡C‡Ê¬Y³JÊÜu×]röÙgGSAåŒ3äŠ+®(‰ŽfШÁ×_½ <8WöèÑ#W]ºª×ňrÉwúôéAT|3í¸ãŽAfå[œlóÏ›7/ˆ´¦»ï¾[ ”ˆgΜ9r 'Dù^~ùå@&ów¨kŸ«¨]#¦—‹8¯ÑÚõC‰�7Þ¸¢ ß|óM ÔÕ½ƒ¸¤' Œ=:ðe힆§èé7ß|s¬@Ü–[ñ5u�ã±Ç ìyôÑGËÚ­ãEÿSA{%qú_|!{íµWù]ûVÅû*ô/—ªéG3B»î½hûMñ³ ¿Nkï{ï½';ì°CÔ|�“n¹å–²µm|pôÑL¹¶¹ìÇ'žxBúôé]Bÿÿ#�<"-[¶L_ÕdÐùwàÀ�%Eõã�×m’‹~6Üwß}¢Âo3¹Ïz�Z�ϰNõÁÉ“'—Ìyú± € P �¼¿ÿVÓæâ2ìßfm‚uyíÖ¨È@€ü%0uêÔ ŠÊ©§žZrÔ¦¿c øHÀƒíÿ»‘ä£` @€ à�¼oèû*h{XŃz"æÚµkeà 7ý·FÃÞb‹-Ê:ÁªU«a Fm_ýõ¥M›6eó~øá‡òé§ŸÊ·ß~äßl³Íd»í¶«(âµñ>WõÚ\;MW|¿üòKùë_ÿ˜ÒºukùÞ÷¾'›l²IYÓÒæOÓÆ¬yÕŸ>øàƒÈGTÀ®‚mõ—4)dý÷¿ÿ=(«\4j}¹èç6ugá¦bt� ­Â׬Yøü¦›n*[n¹e0Öl“е‹Åú*ô1b„mq'ù\ùµc3VZ«~üî»ï¤_¿~��݇>´ÁÒÈ~v1ž‹Û\«~}ç�w¤cÇŽQÕúaÑ«¯¾*m´‘ bò@€šÈûûoÖ.õ`ÿ6k¬Ë#h·FEF@€ à/�W^yEúöí+]ºt‘'Ÿ|Ò_C± €¼&àÁ‚‚ö‚‡¡Ýëa‚q€ @ð‚@Þ7ô}´{ÑÉ”%°råJéÚµktÚ�Šô_ýõ@OÊýÐbß}÷ "ìßvÛmrÁäÇx,-K`øðá%Á·¦M›{‚!@¶òþþkÛÎrù<Ø¿ÍÚëòÚ­Q‘€ øK@�•Õš–/_.mÛ¶õ×X,ƒ o x° ‚ ½àÚ½"@€ oä}CA»7®„!È-�‰'ÊÅ_ÙŸ—ß¹îÀð¹sçÊ1ÇÔTÈob9 Ÿäýý7+Köo³6Áº<‚vkTd„ @~¸òÊ+åž{î‘‘#GÊyç�ç·±X@^ð`AA{Á3´{9<0 € @^Èû†>‚v¯ÜI¾ùæùöÛo�ÕªU+Ùpà �ÔM¥˜={¶ôíÛ7qÚi§ÉôéÓ¥E‹ÀñœÀOoNÖ¬\¹Rºví*üqЬvíÚÉ+¯¼‚�¶9u2mñš€®e4(8!L÷ÝwŸ 0Àk»1€òC ïï¿YI{°›µ Öå´[£"# @ðŸ€.Ú~ùå—òòË/ËV[må¿ÁX@^ð`AA{Á#´{5,0€ @^Èû†>‚v¿Üêšk®‘ &8ï¹çž2eÊí~uy³³æÍ7ß”.]ºDíRQûòåË¥}ûöÍ®­4¾¸ð eÒ¤I‘YcÆŒ‘#Føf&ö@€@Ž äýý7+zöo³6Áº<‚vkTd„ @þ8í´Óäw¿û�üçþ§ôîÝÛƒ±€¼"àÁ‚‚ö‚G h÷jX`  @€¼$�÷ }í^ºFA ×4RûW_}´AçÈí¶ÛNZµj•ë6a<ò@àÃ?”Õ«WGcïûßÿ~ÌÆF@È�¼¿ÿfEíÁþmÖ&X—GÐn�ŠŒ€ @ÀãÆ�“Ûn»M4Â¥—^ê¿ÁX@^ð`AA{Á#´{5,0€ @^Èû†>‚v/Ý £ @€ xG ïï¿Y�z°›µ Öå´[£"# @ðŸÀ“O>)?þñ�å_þå_dúôéþŒ…€ àD´<A»WÃc @€ à%�¼oè#h÷Ò­0 € @€€wòþþ›¨û·Y›`]A»5*2B€ ÿ è±~ûí·Ÿl²É&²téRÿ ÆB@ðŠ€ "Ú � Ý«a�1€ @ð’@Þ7ô´{éV@€ @À;yÿÍ ÔƒýÛ¬M°.� Ý!@€@>ì¿ÿþòÁÈ‚ d§�vʇÑX @^ð`AA{Á´{10€ @^Èû†>‚v¯Ý ã @€ xC ïï¿YAz°›µ Öå´[£"# @È�óÎ;OæÎ�+'N”~ýúåÃh¬„ /x° ‚ ½à Ú½@€ ¯ ä}CA»×î…q€ @€¼!�÷÷߬ =Ø¿ÍÚëòÚ­Q‘€ äƒÀäÉ“eìØ±2xð`5jT>ŒÆJ@ð‚€ "Ú ž€ Ý‹á€€ @ðš@Þ7ô´{í^@€ @ÀyÿÍ ÒƒýÛ¬M°.� Ý!@€@>,Z´HN=õTéÞ½»Ìž=;Fc% xAÀƒíO@ÐîÅpÀ@€ xM ïúÚ½v/Œƒ @€ à �¼¿ÿféÁþmÖ&X—GÐn�ŠŒ€ @ ¾úê+éÒ¥‹´lÙRV¬X!áÃ}>¬ÇJ@h$D´A{#G׆ @€@>ä}CA{>ü +!@€ 4š@Þß³òó`ÿ6k¬Ë#h·FEF@€ �½{÷–åË—Ëœ9sdÏ=÷Ì�áX @ %àÁ‚‚ö‚ hoè0à†@È�¼oè#hÏ…›a$ @€ �†ÈûûoV€ìßfm‚uyíÖ¨È@€òCàÒK/•x@®½öZ4hP~ ÇR@h(D´<A{C‡‡ @€@.ä}CA{.Ü #!@€ 4œ@Þß³ô`ÿ6k¬Ë#h·FEF@€ �Ó§O—‘#GÊI'�$&LÈ�áX @ %àÁ‚‚ö‚ hoè0à†@È�¼oè#hÏ…›a$ @€ �†ÈûûoV€ìßfm‚uyíÖ¨È@€òC`éÒ¥rì±ÇJ§N�dþüùù1K!@ ¡‚ö\»ÆC€ @¨�¼¿ÿfåÁþmÖ&X—GÐn�ŠŒ€ @ ŸV¯^-�:u ŒÿÓŸþ$ë­·^>‚Õ€ àœ€ "Ú ½Œ Ý¹«s@€ äž@Þ7ô´çÞi @€ �ºÈûûoVHìßfm‚uyíÖ¨È@€òKà˜cŽ‘×_]fÍš%ûì³O~‚å€ à”€ "Ú =Œ Ý©›S9 @€š�¼oè#honH# @€ 8'�÷÷߬€<Ø¿ÍÚëòÚ­Q‘€ ä—Àˆ#dæÌ™òÿÙ»ð*ªôñãoLT׈¸ºòÓÿ¢¨€*H$TA:�@è¤Ä°ô&5TéUzé`XŠ�¶ +R\XV±€¸¸ê."øŸ3›{7@BnnæÎ=3ó�ç¹OB2çœ÷ýœÑÜ;óΙ¤¤$‰‹‹sn"DŽ R NˆPÐnÌ0í!=Ìé@@W8ý‚>í®8 I@@¹€Ó?ÿfHƒë·ÙM!àö´LÅŽ € €€s–,Y"½{÷–ºuëJrr²s!r@�� hpB„‚vc†)héaNç € €¸BÀéô)hwÅaH € € €@Èœþù7»@\¿Ín ·§ =`*vD@œ+pôèQ©V­šäË—OvîÜéÜDˆ@ ¤œ¡ Ý˜a ÚCz˜Ó9 € €®pú} Ú]q’ € € r§þÍ.�×o³›BÀí)h˜Š@@g ,XP~þùg9xð äÉ“ÇÙÉ= €@H48!BA»1³´‡äð¦S@@\%àô úi>ºj^H@@@ 4'OžÌrÇ·Ýv[–ÛèÖ@ƒë·¶‘PÐn5!€ €áhܸ±|ðÁ2wî\©T©Rxƒat@-48!BA»qdPЮå… € €€V´k5ƒ € € b ÚÅ)õÞA NIÐÓ´ƒž]"€ €i† Re+× IDAT&Ó¦M“îÝ»›/6@®  =¼Ç„ÏŸ‚öðΣ#€ € à§´gÇø—_~ÉNsÚ"€Ú |÷Ýw²hÑ"éÚµ«v±Ph®\¹"‘‘‘¡„Þ@ð°+´;kò)hwÖ|- € ´Àúõë¥C‡æêìj•v6@®  =¼Çíáõgt@@œ$@A»“f‹X@�ŒΟ?/¥K—6wPíµ{çhQÅì 0 Ú�;æ�ÄÉ@(h·Û‚¡(h·‘.@@'|ñÅR¦LÉ“'�zôhQ…íIIIFHH €á  =œú"´‡×ŸÑ@@p’íNš-bEþ'ðí·ßJ™2eü?Ø»w¯äÎ�" œ={VbbbÌÌ)h÷à@Ê €¶PÐn ³eƒPÐn%!€ €ú ìܹSš7o.%J”�•+Wê0"€Ø*@A»­Ü7 –Æ?¼�0: € €8F ˜8§_Ðÿå—_3?Šö9rD~úé')^¼¸DEEÙ3h£PÌš‹›œ>}ZªT©bfÌßsÓ� €– 8ýó¯‚Ðàú­eó‘YG´g&Äï@@ \¼xQŠ)"êM;'Ç\4±¤‚X$ Á ‘ßT*gΜ±(#guCA»³æ‹h@@ÐA ˜s|N¿ OA»G1  �À¥K—äÅ_” .HJJŠÜsÏ=ú—&’o¾ùFÊ–-ëÿɾ}ûäî»ïÖ2V‚²GàøñãR£F s°`þžÛ%£ € àl§þUú\¿µí   Ý6jB@ô¨T©’yblýúõfq; €> Nˆxº =;G"ÙÑ£-á8þ¼,Z´HºuëÞ@Ý¿ýö›DD8å´¾#H ð¤€Ó/èóùÇ“‡-I#�¡€*hoР�œ;wN¶lÙ"ÑÑÑÚiQ̮ݔh�z²@½zõÌX(h×bJ@À…Nÿü«¦Dƒë·¶N9óÍmÛ B@· tïÞ]V®\)Æ “æÍ›»=]òCÈ‚€'DøüŸ…ùJ»+AÂÑ �0 |ýõ×òüóÏ›Q$$$HçÎ�ÃÃë,ðØc�‰*hW«øÝrË-:‡Jl €š 8ý‚>Ÿ4?À8 :t�~ýúIåÊ•eòäɲjÕ*Y»v­œ:uJ&Mšd®|ݬY3¢¹ùi?÷©=÷ï߯eÑ}Ø¡<€:Ž5jdfNA»RF°EÀ韒×om™+5í¶Q3 €  ‡Àܹs%))I7n,£G�Ö#(¢@ÐB@ƒ"´y$PÐ$Í£À—_~)åË—÷G°wï^É�;w#bhÝ}ôQ3ÄÏ>ûL¢¢¢t—ø@4pú}>ÿh|p6 ôïß_–.]zÓÑzè!ÙºukXß;__Ì® ˜ïºë.›”Fw�?üпðíºÏñ!€8UÀ韕»×om›~ Úm£f @@@�ƒJݺu¥`Á‚æã7Ù@ð hpB„‚ö G :‚„£a8sæŒTªTÉ?:E aš‡ ë+hÿë_ÿ*·ß~»Ã¢'\@�œ~AŸÏ?:MÄ‚@x¾úê+QŸ£>øàY²d‰„z¿¬Š‚«W¯.111R¤H)P €DD„§,FÅX®\9>÷…çqĨ»ví’ØØX3V Ú1e‰ à@§þUä\¿µmæÃóÎ=ëéqA;ëf´@@Ò¸zõª<òÈ#¢¾=zTråÊ… €¦€'Døüä±HAG�p4C §N�’^xÁ?ò¡C‡xO†ypâ�¾‚öÇKΜ9�˜1#€h"àô ú|þÑä@" 4P×96l(ê=²ÚòäÉc.äÖè®/fW ýîw¿ kL ®ŸÀöíÛ%>>Þ Œ‚vý懈@Ü!àôÏ¿j4¸~kÛÁ@A»mÔ „ €è# VhW'PÕÊ%eÊ”Ñ'0"A«€'D(hò  #H8š!`³€º@ýâ‹/úG=räˆäÈ‘Ãæ(Ω¾‚vŠaœ:ƒÄ�è#à† úúh „S`ܸq¢^i·:uêÈ„ $222,¡�={VJ—.í[=a‰bö°L…öƒ®_¿^:tè`Æ©žäƆ €¤' Áõ[Û&†‚vÛ¨@ÐG ))IæÎ�+}ûöõŸ,Ó':"A—€'D(hrò)hŽf®PEá?ýô“/^\¢¢¢´Í騱cR³fM|êiI·Þz«¶ñ˜~¾‚ö}ûöÉÝwß­_€D„ à Ú3UŠ7ؽ{·4mÚÔÜcÍš5’7o^)Y²¤ùïÁƒKË–-m÷ûòË/å¹çžó�K1»íSà¨W¯^-ݺu3c¦ ÝQSG° €Ø* Áõ[Ûò¥ Ý6jB@ôX¹r¥tïÞ]jÔ¨!Ó¦MÓ'0"A«€'D(hò  =H8š9^àÒ¥KæŠç.\�””¹çž{´ÌéÓO?µJ o;~ü¸ÜrË-ZÆJPú ø Ú?úè#É“'�¾� €€ö´k?Eˆ¨'`µjÕÊ| íÈ‘#%""B¦N�*ÉÉÉ2iÒ$©T©R½X·Ë_|qÍqÕçÀ;ï¼ÓºèÉuK—.•^½z™yQÐîºé%!@,Ðàú­e¹dÖí™ ñ{@@À…êD¯:™ûÿ÷òÁ¸0CRBF@ƒ"´3qF Úƒ„£™ãTA{ƒ äܹs²eË‰ŽŽÖ.§O>ùD^zé%\ê½8Áø Úßÿ}¹ï¾û‚é‚6 €˜´s €€[._¾,W®\‘9r˜)©«WΜ9mM‘bv[¹]3Ø‚ ¤ÿþf>´»fZI@Ë4¸~kyNuHA»mÔ „ €è%P¤H¹xñ¢|üñÇò‡?üA¯àˆ@ ,œ¡ =È™§ =H8š9RàÀ�Ò¡CéׯŸT®\Y&Ož,«V­’µk×Ê©S§Ì•øÔ“ˆš5köüþò—¿HÆ ýqPÌö)qt¾‚ö]»vÉ<àè\@ ¼´‡×ŸÑ@À]ÿøÇ?¤lÙ²þ¤Ž=*¹rårW’d�Y³fÉÀ�;)h 1�"€ à  ®ßÚæHA»mÔ „ €è%мysÙ¹s§Ì˜1CªU«¦WpDƒ  NˆPÐäÌSÐ$Í) V/S�å¾ÙöÐCÉÖ­[%***l9îß¿_7nìŸbö°M…kö´ïرC|ðA×äE" €ö PÐn¿9#"€€;(fwç¼Ú•ÕÔ©SeøðáæMû}ûöµkXÆA@‡ hpýÖ61 Úm£f @@@/�Ñ£GKrr²tîÜYz÷î­WpDƒ  NˆPÐäÌSÐ$Í)ðÕW_‰Z¥ýƒ>�%K–˜9¨B_U0^½zu‰‰‰õ4¢ HDDxN~ôÑG׬O1»#5í‚ö´oß¾]þøÇ?j!€8G€‚vçÌ‘"€€¾jEíçŸÞàgŸ}&9sæÔ7`"ÓN`„ 2fÌéÖ­›ôìÙS»ø@ôÐàú­má¹¢“õô¸ �u3Z € €7زe‹ÄÅÅI¹rådÑ¢Eh!€ œáó�Ç!íAÂÑÌÑW¯^•† ÊáÇÍ<òäÉ#ê=ntttXózÿý÷¥E‹þ(fët¸jp_A»:ÎyäWåF2 €ö PÐn¯7£!€€û(fwßœ†##ßÂS½zõ’.]º„#ÆD@hpýÖ6% Úm£f @@@/�o¾ùFJ”(!wÝu—9rD¯àˆ@ ,œ¡ =,3Ï 8S`ܸq¢^i·:uêˆZá,222,IíܹSš7oî[9°!`•€ïïô¶mÛäñÇ·ª[úA@@�,üýï7 òmÇŽ“;î¸# =°+ÿ:t¨LŸ>]ú÷ï/ñññ° € €é hpýÖ¶™¡ Ý6jB@ô(S¦Œ|ñÅ¢YÿØc�é !€Ø* Á  Úm�qCÀ¹»wï–¦M›š ¬Y³FòæÍ+%K–4ÿ=xð`iÙ²¥íɽ÷Þ{ÒªU+ÿ¸³Û>®Ð÷wzóæÍR¨P!×çK‚ € €è&@1»n3âìx|Ÿñ(±±±ÎN†è@@ d\¿ Yn×wLA»mÔ „ €è'СCY¿~½Œ;V6l¨_€D„ `«€'D(h·uÆ ç œ-111þPŽ?.9räÐ!4bp¨€ï3ÞðáÃ¥Y³fÍ‚°@@ Ô\¿ uŠþþ)h·�š�@@ý¦M›&Æ 3W°T+Y²!€x[@ƒ"´{û${²$pùòe¹r势€@ý[½ræÌ™¥~²»ó¦M›¤]»vþn(fÏ®(í3ðý�^·n�<õÔS@!€ € €€M׳Ÿ8qBn¿ýv›Fg· ø>ã�3F^~ùe·¦I^ € �M ®ßf3ƒÀ›Sи{"€ €®øàƒ¤qãÆòôÓOË;ï¼ãºüH@ kœ¡ =kSÆÞ fõ´#õÔ#ßF1{˜'ÄåÃûþN¯Y³FŠ+æòlI@@=N�:%åË—÷C1»óâ†(|ŸñÔÓæê֭놔È@B  ÁõÛd•~—´ÛFÍ@ € €€~?ÿü³,XP"""äoû›DFFê$!€Ø& Á  Úm›mB�ì ¨B;wîìï†böìŠÒ>3ßßé•+WJ‰%2Û�ß#€ € €@6®/f?yò¤ÜvÛmÙì•æüWÀ÷O=M¹F�° € €é hpýÖ¶™¡ Ý6jB@ô¨V­š=zTXåOÏù!*@ÀN NˆPÐnç„3-°jÕ*IHH0Û·oß^úõët_4D PßßéeË–IéÒ¥mÆ~ € € „€Z¨B… þ–³�H“› ø>ãÍž=[ªT©‚ € �®€×om› Úm£f @@@O�Þ½{Ë’%KdàÀ�«g�D… `‹€'D(h·e¦²#°|ùréÙ³§ÙE§N�äõ×_ÏNw´E `ßßé·ß~[Ê–-p;vD@@�¬ \_ÌþùçŸË­·ÞšµNØ�L|Ÿñ.\(111x!€ €é hpýÖ¶™¡ Ý6jB@ôX´h‘ôíÛWêׯ/ãÇ�×3H¢B°E@ƒ"´Û2Ó ‚Á ¨AÕ ¡jëÖ­›¿°=Øþh‡@V(vÈŠû"€ € œ€*^¯X±¢¿1ÅìÁ9Ò*sžÂ•¹{ € €€ˆ×om› Úm£f @@@O�#GŽH�5$þü’’’¢g�D… `‹€'D(h·e¦‚ðݪÚöèÑC‚é†6-àû;=oÞ¼k l‚î�† € € p�ÀõÅìj¥ö¨¨(”‰€ï3Þš5k¤X±b!ƒN@@Àù\¿µ ‘‚vÛ¨@ÐWà±Ç“K—.ÉáÇåî»ïÖ7P"C©€'D(hé Ó9+0þ|ILL4›÷êÕKºtélW´C hßßéÙ³gK•*U‚ € € p£ÀÉ“'¥R¥Jþ_PÌÎQjßg¼ 6HáÂ…C=ý#€ €€C4¸~k›í¶Q3 €  ¯@Æ åã�?– Hùòåõ ”È@B* Á  ÚC:ÃtŽÁÌ™3G `6íÓ§�tìØ1˜nhƒ@¶|§g̘!ÕªUËvt€ € €ÿ8qâ„T®\ÙÏqêÔ)‰ŒŒ„�� ø>ãmÛ¶Müñ�ŽEç € à\ ®ßÚ†GA»mÔ „ €è+0xð`QE¯½öštíÚUß@‰ @ ¤œ¡ =¤3Lç �UõY½WV[ÿþý%>>>«]°?– øþNO›6MjÔ¨aY¿t„ € €€—(f÷òì‡7wßg¼�;wJ¾|ù £#€ €€¶\¿µÍ†‚vÛ¨@ÐWà�wÞ‘Î�;Ë /¼ ³fÍÒ7P"C©€'D(hé Ó9dE@ 6Ìl’””$qqqYiξX.àû;=yòd©]»¶åýÓ! € €^8~ü¸T©RÅŸöéÓ§å–[nñù†IÀ÷ïÃ?”¼yó†) †E@Ý4¸~kí¶Q3 €  ¯Àßÿþw)W®œÜwß}²oß>}%2@�� hpB„‚ö�Î0�#€@ ª`xäÈ‘æîƒ ’V­ZÚ”ý™€ïïôĉ¥^½z!‡Ž@@ð‚Åì^˜e½sô}Æ;pà€Ü{ï½zKt € 6 ®ßÚ–;í¶Q3 €  ·@±bÅäüùó²gÏy衇ô–è@B" Á  ÚC2³tŠYPÅÂo¾ù¦ÙdèСòꫯf¥9û"2ßßéqãÆIƒ B6#€ € àv�cÇŽ™O¬õmjÑŸˆ§”ϸ}vŸß'Ÿ|"?þø£”(QB¢¢¢B�ï3Þ‘#Gä®»î Ù8tŒ €ÎÐàú­m€NyGÎmÛ B@¯ ÄÆÆÊöíÛeÊ”)R«V-¯2�7 ài NˆðùßÓG É#~U(¬^j1b„4mÚ4üA©¾¿ÓꆋF�á‚ € €APÌš‡š\ºtI*W®,.\�]»vIž“ªU«ú³:sæŒË2$�ì ¨‚ö:uêȹsç$%%E¢££³Ûe†í}ŸñNŸ>-·ÜrKÈÆ¡c@p¶€×om¤ Ý6jB@ôP'æZ¶l)¥K—–eË–é,Ñ!€„D@ƒ"´‡dfé2PÂêIEjS7y6lØ0³&üÛ|§‡*¯¾úªíã3  € €N ˜ÝɳÚØ÷ïß/mÛ¶•ÄÄD󆇉'ÊŠ+dýúõrêÔ)™0a‚Ô¬YSZ´haY —/_–üùó›ýqc…e¬t„ €€+4¸~k›+í¶Q3 €  ·€z|âÓO?ÍÉ3½§‰è@�� hpB„‚ö�Î0�#€@zª8xúô鿝ÔEëzõê…€–III2wî\4h�´jÕJË @@Ž=*ÕªUó‡F±Ž³¾˜úôé#‹/¾iê¼éŽ;$**Ê’@úé')T¨�ÙÇ£%¤t‚ €€k4¸~k›-í¶Q3 €  ¿@ùòåÍÕ&6mÚ$O<ñ„þ! €€¥œ¡ ÝÒ¥3ÈL@Ïœ9ÓÜmòäÉR»víÌšð{Â&0pà@™5k– 0@Ú´i¶8@@' PÌî¤Ù O¬gÏžµJûž={ü…í�=ö˜œ8qÂ\™½B… R´hQ)X° DDXSfõý÷ßËSO=e&LA{xæ�Q@pŠ€×om£²æ�VèÃå‚vè�@�®]»Êš5kdäÈ‘òÊ+¯ ‚ à1 NˆðùßcÇé"NU!+99Ù¼±¸R¥J–vôèQÿÓâ(h·”–Î@p�€×om3¥ Ý6jB@œ!P«V-9|ø°,_¾\J•*匠‰@À NˆPÐnÉLÒ ¤'�¶˜}Ñ¢ER®\9 °D@´«ÇП;wNRRR$::Ú’~¯ïdüøñ2vìXIHH�=z„d :E@@ÀÉGŽ‘5jøS PØÉ³iìjѧ+W®ø{RÿV¯œ9sZÌ¡C‡¤víÚf¿§–óÒ! €®Ðàú­mž´ÛFÍ@ € €€3ú÷ï/ ,�ÄÄDi×®�3‚&J@K48!BA»%3I' p½@Úbö%K–˜+®±!��ýû÷KÛ¶mÍÏMU«V•‰'ÊŠ+dýúõrêÔ)s%õš5kJ‹-²3Ì5mÕÊ€£G�–.]ºH¯^½,ë—Ž@@pƒÅìn˜Eïä°wx|Îs IDATï^iР�™0íÞ™w2E@  ®ßvPm(hŠ�F € €€{–-[&¯½öš¹2Ääɓݛ(™!€Ü  Á  Ú9.@Àr�´Åì<…Èr^ÏvاOY¼xñMóWÇÞŽ;$**ʧ)S¦Èˆ#¤cÇŽ¢ÆgC@@�ÿ |òÉ'æ ¥¾�aŽ ÝöìÙ#¯¼òŠ&Ç«î³E| €„W@ƒë·¶PÐn5!€ €Î8v옼ð ¢ÞïÞ½ÛA% €€%œ¡ Ý’™¤ð ¤-f_µj•<ûì³à `‰ÀÙ³gE­Ò®Š|…í�=ö˜œ8qÂ,¤©P¡‚-ZT ,(Öœ†Ÿ>}º :TâããE=Y‹ @@(fçp¦@JJŠ´lÙÒ ž‚vgÎ!Q#€ `—€×oíJU¬9“úp¹ zcF@@ü¾7Ī@ã÷¿ÿ=2 €Ðà„Ÿÿ=r¬‘&v¤-f_»v­<óÌ3v ˸zõªÔ­[W:df~ï½÷Š*LˆŽŽ¶\bæÌ™2hÐ ‰‹‹“¤¤$Ëû§C@@œ&pøða©U«–?l ƒ�6ƒÞ�wóæÍÒ¶m[€ãֻǙ#€ ˆ€×o Ó’}(h·„‘N@@w ¨Çª•gÏž-UªTqWrdƒ �¡€'D(hçøDKÒ³¯_¿^Š)bI¿t‚ÀõãÆ�õJ»Õ©SG&L˜ ‘‘‘–‚Í™3G  ±±±2pà@Kû¦3@@œ& n*­]»¶?lŠ‚�6ƒÞŽwݺuÒ©S'�c×ÛÇÙ#€ �™€×o3 ѲßSÐn%!€ €î1b„L™2Eºuë&={ötObd‚ pS NˆPÐÎ1ŠÙ¸råŠ<üðÃþ>6mÚ$O<ñD¶ú¤1 ìÞ½[š6mjþzÍš5’7o^)Y²¤ùïÁƒûo•àüùó%11QZ´h!C† ±ª[úA@@Àq³;nÊø:�U«VIBB‚ùS Ú9<@@àf\¿µm‚(h·�š�@@çlܸQâãã¥B… ¢Š&Ø@¼! Á  Ú½q¨‘%!øå—_äÑGõ÷½uëV)P @HÆ¢S”ÀÉ“'¥U«VR¦L9r¤DDDÈÔ©S%99Y&Mš$•*U²jÑ¢EÒ·o_iÖ¬™ >ÜÒ¾é @@§üå/õT$ßF1°SfŽ8Ó ,Y²Dz÷î-Mš4‘Q£F�ƒ €d( Áõ[Ûf‡‚vÛ¨@pŽÀÙ³g¥téÒfÀœ vμ) �] NˆPОÝI¤=ø÷¿ÿ}Mñú{ï½wMq»GYHÛ�Ë—/‹z2@Ž9ÌÑÔ¿Õ+gΜ–�NÁƒå¤tˆ € à0ŠÙ6a„›¡Oàâà@@�@4¸~h¨ÙÞ�‚ölÒ € àN�R¥JÉW_}%;vì�GyÄ�I’ €À5œ¡ �c²,ðã�?ÊO<áo÷ç?ÿY~øá,÷CtX¾|¹ôìÙÓ “�uŸ-âC@°ZààÁƒR·n]·¼'¶Z˜þì˜9s¦ 4Hâââ$))ÉΡ @& Áõ[ÛÄ(h·�š�@@g ´k×N6mÚ$&L�—^zÉYÁ- €@Pœ¡ =¨™£Þøç?ÿ)E‹õìÙ³Gzè!ï‚�¹«V­Z% fŽï¸zªI@@à:ŠÙ9$Ü&0eÊ1b„tìØQúôéã¶ôÈ@,Ðàú­…Ùܼ+ Úm£f @@ÀY“'O–‘#GJëÖ­åOú“³‚'Z@� 48!BA{P3G#¼)ðÝwßÉ3Ï<ãOþÃ?”¼yózƒ¬=!°víZéÒ¥‹™+힘r’D@08 õêÕó[ð^˜Ã ãÇ�—±cÇš7-÷èÑà )‘ €„H@ƒë·!ÊìÆn)h·�š�@@g ìÞ½[š6m*Å‹—Õ«W;+x¢EJ@ƒ"´5s4BÀ{ß~û­<ûì³þÄ÷îÝ+÷ß¿÷ ÈØSï¾û®¹zŸÚ(âñÔÔ“, € àYŠÙ=;õ®O|Ô¨Q2iÒ$éÝ»·tîÜÙõù’  €/ ÁõÛàƒÏbK Ú³Æî € €€Wþõ¯É“O>i¦K±„Wf�<@À뜡 Ýë!ù#€ÀW_}%¥J•òï© î½÷ÞZ² Îظq£ÄÇÇóÍÙÓHô € € ìß¿_^zé%ÿÞ\§ŽÝ!0dÈyë­·$11QÚµk爘 @Â# Áõ[Û§ Ý6jB@œ'P¥J9~ü¸¬[·Nžzê)ç%@Ä €YÐà„íYš1vFÀ{_|ñ…”)SÆŸø¡C‡$wîÜÞƒ cO lÙ²EâââÌÜ)æñä!@Ò € €€g(f÷ÌT{6Ѥ¤$™;w® 4HZµjåYG@�Ì4¸~›y�íAA»E�tƒ €¸Q gÏž²|ùrQ+E´hÑÂ�)’ €@ NˆPÐΉ œ>}Zbbbü¿?räˆÜu×]ˆ!à�íÛ·Kll¬™/íž™vE@<'°oß>©_¿¾?oÞûzîðDÂ}úô‘Å‹ˈ#¤iÓ¦žÈ™$@@ 8 ®ßx­UÐD~4A@@@�¬ „ë|�YÐΆ € € € € € € à×õ[Û¦À) rAÛ¶C‚�@@@�p�/àó? € € € € € € p­@¸®ßÚ6®OÐ6IB@Ü) Þ/^MMínãëEw¦IV € €š 5â:”&¶ÛŒï/k+a!j�ŠÆï¥¹ýPkÓ? € €€�σíJp±ñµ™�ƒ3að-ìQß{uÆgH@@@;Nzk7%„ €h'°Çˆ¨ŒñzÁxmÓ.:B@p«@1#±ýi’»Åøž'9¸u¶É+�c§?§îȹý@ÄØ@@À åŒ w¦ºÈøÚÜ A#Ùð�ߨeô³>›}Ñ@p…'½]1�$� €„T`¼Ñ{7ãÕÏx éHtŽ € €ÀJ¯�Ò`p“#�ÿÞh¬n8VÿMpD € €¸A m1ûB#¡WÝ�9 €€¯ �ŤÀb@ð†'½½1Ïd‰ €dG@­†²Àx©GªG²!€ € J�çŒÎßO3ç0C©MßN(eûajÀüwᤙ#V@@�ôÒ>�ˆbvޝ ø ÚÕ»¼–<ù"€ €é pÒ›ã@@ 3�ÆŸ¥îÄûÇÌ´ø= € €@vҮΧúáýgv4ië6�âFBûølæ¶i%@@À“å�¬w¤f®ÔiáI’ö²€¯ ]ݸü±—!È@ð pAˆc@@ ߉µ?;Ÿ ¤û € € �E� Æþ)©m_›e±=»#àv�§�¦&ɹ}·Ï6ù!€ €î ˜Ý½sKf� ø®»©Ïy‡oÆž € €€{8éíÞ¹%3@@ÀJ��Fg/¯†Æk¥•Ó € €†@ãµ5Ub¾ñµ%* pƒ@ã'‡Sʹ}@@' T0‚öÝÈÌg?'Î 1[%à+hÂèð¨U�Ò € àdNz;yöˆ@°O`�1ÔÆk„ñêkß°Œ„ € àu㤺�RmsŒWkäLŠ#PÈhôijCÎí#H@@�p T0÷³Ï3¾oÎ`�0 ø Úóqü-̱0< €  …'½µ˜‚@@´¨mDøŽñÚn¼Ôê™l € €X!PÓèäÝÔŽf_ÛYÑ)} àR�Ç�¼Ž¥æÆ¹}—N2i!€ €.¨häõ^jn³»t’I+K¾‚ö�V_f©%;#€ €.षK'–´@@‹þ`ô÷UjŸ¼‡´—î@@� Ô5ò^“šû4ãk�:�6� @@﨓Ðo¯ÅÆ«™wÒ&S@@� :m’SÛõ7¾ ¢š €Àÿn7¾ýOê?9·Ï‘� €  ‹@Úbvõt.µ(d,ðˆñ«ÏùlÇ!‚ €\+ÀIoŽ@@�¬�qnß2R:B@RàE£ÝÆÔ¶êÉ®ñAöC3¼&ð”‘ð_R“æ³�×fŸ|@@ CÞqp € €dU`‡Ñ ¼ñªi¼6dµ1û#€ €¸Z ·‘ÝÈÔ Œ¯\�-É!`¿€¯ =ÊúŠýÃ3" € €¦Åì/PÒhúQjsê¶‚w¤% €¸L€7F.›PÒA@lmŒ¡VÚ`¼Ù0C € €8C ŸæÐÔP»_'9#l¢DÀQ¾‚öFÔ—9Á"€ €¸E º‘ˆo±›éÆ÷íÝ’y `“@9cœ�©cQ·e:à € €€þ¼1ÒŽˆ@ÐM ‘ÐRãõ®ñª­[pă € �$cÔ�©#w0¾N K Š€û|í¹ŒTvºdˆ €  ™@Úbvõ¹O}þcC�¬ T1vßšÚ„º­¬Ù±7 €¸X€7F.ž\RC@B$ð°Ñïß8Ñ"]ºE@œ'  ÙUA»ÚÚ¯™ÎK�ˆpŒ€¯ =ÚˆøÇDM  € €n¨a$±>5ŠÙÝ0£ä.�šÆÀjÑ(õ¤õ= € €€!@A;‡ € €@0¾"ŠG�ÆŸÓm@@\!0ÔÈ¢_j&­�¯s\‘I  ¯€ï³Ø=Fˆßë&‘!€ €¸L m1ûT#·Ž.Ë�t°Sà%c°UÆkµñªoçÀŒ… €è,@A»Î³Cl € €€¾k�Ðê¯WŒ×}Ã$2@@� Œ0ú~=µÿÆ×!‹®@࿾‚öûŒïÏ�‚ € €€ ¾Õ¤ÕP³Ûήhbdø¶ñR××Ôu66@@C€‚v@@�`�Fƒ�×XãÕ3˜hƒ € àh�7Ó¼lf|¿ØÑÙ<Îð´?`„üµsÂ&R@@‡ Ô2â^—ûãk'‡æAØè$ÐÒf®ñšo¼Ô÷l € €†í € €ÁT3m2^»ŒWL0Ð@@À±ãŒÈR£ol|]æØLç ø Ú4BÿÒyá1 € €€ƒ(fwÐdª£ÚÑN7^3Œ—úž @@À  �Ã@@ �ê7ñ!àu^E�_™d¼º8!`bD@ì øÈeÆ@@Ü)à+¤(n¤wÀ�)’ € €@�©Æ÷íSÿ]Ïøº°]À÷9,¿1òßl�@@¼ �¶˜�‚[/Ì89Ú-ÐÓðMã5Æx½f÷àŒ‡ €è*à”‚vßIz]‰ @@@@@@@@@@°ZÀ)õÞAçí”)hzŠiˆ € € € € € € € € € €pJ½wмNIÐ,h?sæLЉÒ@@ÀZ�H½zõÌNyŸf­-½!€ €ºtïÞ]V®\i†³xñbyþùçu �8ð¤ÀÿûÿÏÌ{ëÖ­R @O�4 € €€õ[¶l‘¸¸8³ãØØX8p õƒÐ#˜#FŒ�)S¦HŸ>}¤cÇŽ¨ € €7ð�6vrJ½wÐ3ê”)hzŠiˆ €„FàÊ•+òðÛ�öÙg’3gÎÐ D¯ € €aèÚµ«¬Y³Æ{éÒ¥òÜsÏ…%E�ÿ ø.^lܸQž|òIh@@ȶÀæÍ›¥mÛ¶f?­[·–?ýéOÙî“@ c�AƒÉÌ™3%))É# ^ € €@F´ëwlPЮߜ € ¾7Î8q0 € €îèÔ©“¬[·ÎLjÅŠR²dIw%H68TÀ÷ìÝwß•¢E‹:4 ÂF@ÐE€bv]f‚8¼$�˜˜(óçÏ—!C†H‹-¼”:¹"€ €@´�â&´‡˜î@@�`Þxã ™7ožôë×OÚ·oL´A@ÐL >>^ÔêÏj[½zµ/^\³ ï ø.^¬]»VžyæïB�9 € €@¶6mÚ$íÚµ3ûiÓ¦� 0 Û}Òd.лwoY²d‰Œ5Jš4i’yö@@O PЮßôSЮߜ € `®ÖÙ£G©Y³¦L�:@@‡ ÄÅÅÉ–-[Ì,Þyçyúé§žá#à.ßÅ‹U«VɳÏ>ë®äÈ@@À6ŠÙm£f nHHHõ™nüøñR¿~}„@@�› PЮßBA»~sBD € €€œ8qB*W®lJœ9s@@‡ üöÛoÒºukÙ¾}»™Á† ¤páÂ͆°p¯€ïâÅòåË¥T©RîM”Ì@@B& žÈ¥žÌ¥6uSsRRRÈÆ¢c¸Q cÇŽòî»ïÊäÉ“¥víÚ!€ €7  ]¿„‚vý愈@@SÀ÷æyïÞ½rÿý÷£‚ € à0�_ýÕ,fß±c‡ùæÍ›¥P¡BË‚pð†€ïó—z<}™2e¼‘4Y"€ €X&@1»e”t„@о§ã͘1CªU«t?4D@oPЮßÌŸ?_*T¨àM²F@ȲÀúõë¥C‡f;ŠÙ³ÌG,hÒ¤‰¼ÿþûòöÛoKÙ²e-í›Î@@À}´ë7§´ë7'D„ €˜›7o6O€ÇÄÄÈÂ… QA@p€À?ü … öGºsçNÉ—/Ÿ"'D¼-+Û·o—9sæHåÊ•½�Aö € € ¤-fo×®�$&&ÔŽ�@ 4¾¢´•+WJ‰%B3½"€ €€k(h×o*)h×oNˆ@0¾þúk)Y²¤ùý™3gPA@Ð\àûï¿—§žzÊ¥ZìÁÔ #%$ÈH@­®© “¦L™"µjÕ @@�tÔÊÏ�:u2G1; z øŠÒRRR$þüzG4 €  �íÚM +´ë7%D„)wnu IDAT €üO`êÔ©2|øpiÕª• 4@@ÍΞ=+¥K—öGuðàAÉ“'�fQd&Ð¥KY»v­$''Kݺu3Û�ß#€ €xP m1»ZŒ¦_¿~T eôð¥íÙ³Gzè!}%2@ÐB€‚v-¦áš X¡]¿9!"@@À/ðþûïK“&MÌŸ9s@@�þñ�HÙ²eý>|Xî¾ûn�"$T !!AV­Z%ãÇ�—úõëÚŒý@@<"ðÎ;ïHçÎ�Íl)f÷Ȥ“¦ã|Ei{÷î•ûï¿ßqñ0 €Ø+@A»½Þ�ŒFA{ Jìƒ €„Ià§Ÿ~’B… ™£Ÿ>}Zn¹å–0E° € €iN�:%åË—7TªT)™3gŽÜyç� !€€Czöì)Ë—/—1cÆÈË/¿ìÐ,@@ i‹Ù;tè }ûö Å0ô‰Ùð¥:tHrçÎ�ÍÞhŽ €n  ]¿¦ ]¿9!"@@àß›èµk×Ê3Ï<ƒ € €@˜Nž<)•*U2£(S¦ŒYÌ~Çw„9*†G�ìôîÝ[–,Y"£F�ò?%+;ýÑ@@Àê¼|—.]Ìd(fwÇœ’…{|×ÓŽ=*¹råro¢d† €–PÐn £¥�PÐn)'�!€ €Ö ôêÕK–.]*ƒ ’V­ZY?="€ €,pìØ1yá…ÌýcbbdöìÙrÛm·Üž@@OµÊæ¢E‹døðáÒ¬Y3=ƒ$*@@[Ò³wìØQúôécëø †Yð¥}þùçrë­·f­1{#€ €€ç(h×oÊ)h×oNˆ@¸F`áÂ…Ò¯_?iР�Œ7@@�0 |úé§òâ‹/š£W¬XÑ,f�ŒŒ S4 ‹V $&&ÊüùóeÈ�!Ò¢E +»¦/@@ ¬Y³FºvíjFÞ©S'yýõט!#à�«W¯J¾|ù̄Ϝ9ã�ÄÉ@‚  =hº�5¤ =d´tŒ €X#ðÉ'ŸHÍš59 g '½ € €A ¤}OV¥J³˜� Ü#0`À™3gŽ 8PbccÝ“™ € €dY€bö,“Ñ�° üç?ÿ‘ÇœkiaŸ @@À9´ë7W´ë7'D„ €Ü à{#­ ©¢££B@°QààÁƒR·n]sÄjժɌ3l�¡@À�AƒÉÌ™3%))Iâââì’1@@4X½zµtëÖÍŒ¬sçÎÒ»wo £$$¸^à‡~�Â… ›?f…vŽ@D€‚ö@”ì݇‚v{½ @JÀ÷FzáÂ…T4B@ȺÀ¾}û¤~ýúfÃ5jÈ´iÓ²Þ -@@{�¡C‡ÊôéÓ¥ÿþ¯}¼ˆ € `½ÅìÖ›Ò#v œ?^Š+fGA»]ꌃ €€³(h×oþ(h×oNˆ@¸AÀ·Z`¯^½¤K—.!R«Àÿøã�R¢D ‰ŠŠ éXtŽ €è,ðÑGÉË/¿l†X§N™4i’ÎádC`Ĉ2eÊéÓ§�tìØ1=Ñ@@À‰«V­’„„3tu^�‹gCçœ={VJ—.mLA»sæ�H@§íáÔOl Úõ›"B@nX»v­¿�=”'â.]º$•+W– .È®]»$Ož<Ì € àI�÷ß_š4ibæþÒK/É„ <é@ÒxE`ôèÑ’œœl.…ú&b¯˜’' € àŠÙ�2Sĉ@ƧOŸö?á8”×ј@p�íúÍ%íúÍ !€ €7Øu"N´«ÕGÏ�;')))Íl € €žP7ö5kÖÌÌ»aÆ2vìXÏ�0^Pÿ��?^zôèá_™Ókä‹ €xQ`åʕҽ{w3õ®]»Êk¯½æErFÀñÇ�—*Uª˜yPÐîøé$@l  Ýæ, BA{–¸Ø@Ÿ€ïÍ´Z-ôÁ´,�ýû÷KÛ¶m%11QªV­*'N”+VÈúõëåÔ©Sæj¤5kÖ”-ZX6&!€ €º ¨›úZ¶li†×¸qcQ«6³!€€ûÔçž1cÆH·nݤgÏžîO˜ @@Ìóàê†FµQÌÎ�€³Ž9"5jÔ0“  ÝÙsIô €Ø%@A»]Ò��CA{àVì‰ €„UÀ÷fzêÔ©f�¹U[Ÿ>}dñâÅ7íN�½cÇ‰ŠŠ²jXúA@´ضm›´nÝÚŒ«iÓ¦2bÄíb$ �À¤I“dÔ¨QÒ¹sgéÝ»wh¡W@@mÒ³sS£6ÓB -pàÀ©W¯ž<óÌ3²víÚ û¡! €xG€‚výæš‚vý愈@@�tÆ�'êÕ¾}{éׯŸeJgÏžµJûž={ü…í�=ö˜œ8qÂ,œ¯P¡‚-ZT ,(–�KG € €€N›7o6ŸZ£6õdš!C†è± €@ˆÔ�ÃÇ—:Hß¾}C<Ý#€ €„S`ùòåþ§òPÌΙ`l¬øðÃ¥Q£FRªT)Qÿ�³!€ €™ PО™�ý¿§ Ý~sFD@‚xï½÷¤U«VfÛP<.ñêÕ«R·n]9tè�9ƽ÷Þ+)))T¼4B@œ"°aÃó¦AµÅÆÆÊÀ��:q"€€Eo½õ–y#K»ví$11Ñ¢^é@@@7�´Åì Ò£GÝB$B`çÎ�Ò¼ys)W®œ,Z´(ˆh‚ €^  ]¿§ ]¿9!"@@ ]�ï¾ûÎ|T¢ÚBQÐî[>íàuêÔ‘ &Hdd$³‚ €¸R`ݺuÒ©S'3·¸¸8IJJrež$…7˜5k–y3K›6mdÀ€p!€ €¸P`Ù²eòÚk¯™™QÌî &%O lÛ¶MZ·n-•+W–9sæxÚ‚ä@@ 0 Ús²s/ ÚíÔf,@@ ›¾7Ô›7o–B… e³·ÿ5ß½{·4mÚÔüÁš5k$oÞ¼R²dIó߃––-[Z6!€ €º¨÷=]»v5É��—þýûëq €€ÍsçÎ5ohQOÅ4h�Í£3 € €@¨Ò³wïÞ]Ô‹ Ü#à{ú^õêÕeúôéîIŒL@@ d´‡Œ6èŽ)hšŽ† € €€ý]ºt‘µk×ʨQ£¤I“&–pòäI³p£L™22räH‰ˆˆ�©S§Jrr²Lš4I*UªdÙXt„ €è °bÅ ÿ£å;vì(}úôÑ!,b@�0 ,X°À¼©E=¢~ذaaŠ‚a@@B!°téRéÕ«—Ù5Å졦OÂ/ ®�©khuëÖ5¯m±!€ €™ PО™�ý¿§ Ý~sFD@‚˜9s¦¹Z`³fÍdøðáA÷“^ÃË—/Ë•+W$GŽæ¯Õ¿Õ+gΜ–ŽCg € €@¸Ò3¨‹�¾Â†pÇÅø >�Å‹›7¶¼òÊ+æM¾l € €îHûù¯G�’��àŽÄÈ®X¾|¹ôìÙS^~ùe3f : € €@¦´gJdû´ÛN΀^8uê”Y¨Vºýõ×_åž{î‘ßÿþ÷^c _@‹öîÝ+ 40{;sæŒE½Ò  € à_Ѫʘ•ù¼3ïdŠ@fË–-“×^{M5j$o¾ùff»ó{@@,Y²Dz÷îmFJ1»&ŒȆÀ¢E‹¤oß¾!Y*aÑ@4  ]¿É¡ ]¿9!" üüóÏRµjÕk ¥]»v.Ê’T@ìøå—_äÑG5‡�Ö ¬\¹Ò¼ÉEÝ@VV­ÞΆ€ª ½råÊræÌw‰‰‰Ò®];+º§@� ,]ºÔ2.íß�r�6 € pSß ]j'nä`A�Œ6lØ`ÞìR£F ™6mP € €X¼x±ôéÓÇŒ\´ªÂV6ð†À˜1cd„ Ò³gOéÖ­›7’&K@È–íÙâ Ic Ú¯c½óÎ;eÕªUR°`Á�€Ó©÷¾ûî;ùõ×_%22Òüú»ßýNråÊå=2F°Là³Ï>“ªU«šýQÐn+!€ €€ ¦L™"#FŒ038p ¨§ô±!€é lÞ¼Ù|VµjÕdÆŒ !€ €8L€bv‡Má"`±ÀðáÃeêÔ©Ò·o_éСƒÅ½Ó €¸Q€‚výfÕ5í5kÖ” ÈåË—3T¾õÖ[Í‚â'NÈœ9säüùóéîûÀÈ–-[$::Z¿#"@@Àð½±>pà€Ü{x € €\'à{Ô´úñ�!C¤E‹!€ lÛ¶MZ·n-UªT‘Ù³g#… € à �E‹™E¬jëÝ»·tîÜÙAÑ*X! 2˜5k– 0@Ú´icE—ô� €.  ]¿ vMAûÈ‘#å•W^É’°*Sl�=zC;A–%JvF@›|o¬ÕMz•+W¶yt†C@ô?~¼Œ;Ö R­ÐÕ¬Y3½&:»@JJŠ´lÙR*V¬(óæÍ {<€ € ˜Åì�9±nèß¿¿,X°@†*¯¾úªÛÓ%?@°@€‚v -îÂ5íÁ®´õÛo¿™6§M›v ­ZétëÖ­’'O‹Éé@Ⱦ€ïщ Ò£G�ìwH € €€KÆŒ#&L0³5j”4iÒÄ%™‘„R`çÎ�Ò¼ys‰‰‰‘… †r(úF@°H@½oëׯŸÙÛ믿.�:u²¨gºA§ ¨E+—.]*£G�–Æ�;-|âE@�0PÐôL†ô|A»òùù知víÚrâĉk¸ÔÝ›åË—Ïò¬}÷ÝwòÅ_Hdd¤DEEI¾|ù$GŽõóÏþSTûÿûßþýï¸ã¹çž{$wîÜõ‘ÙNW®\1ÇøþûïåòåËæëÖ[oUÄÿý÷KDDDf]dúûôò¸óÎ;Í~÷»ßeÚ>³þóŸÿ˜9üøã�¦³/æ\¹rÉÝwß°wFㄺÿÌòËè÷¿üò‹|ýõ×òÃ?˜»¨8Õ±•7o^ó vSÇ„š³ýë_¢¾W[tt´yL°!€è)°aÃiß¾½Ü™3gô ’¨@@›Ôü&OžlŽª Û_~ùe›#`8pªÀž={Ì'€–-[VÞ~ûm§¦AÜ € €€gÔµ|µ"³Ú(f÷Ì´“( tëÖMV¯^m.rðÒK/!… €d*@A{¦D¶ï@A{*ùš5k¤k×®×L@z«¾ÿúë¯æ*¨ÇŽ3 ©�9"K–,‘2eÊÈ… Ì•¿/^|M?êMsñâÅ3œÜS§NÉÆ�eæÌ™rþüù ÷SÅÅmÛ¶•jÕªÉ#�<’åƒåoû›¨<Õc·3ÚÔêÍš5E ge;wîœlÚ´I¦OŸ~Óº… Kll¬T­ZÕ,štSöü±,[¶LV­ZuÓf*þ ˜ Ô �l¡èÿÒ¥KÒ®];Q6jSÅ艉‰R¿~ýkBºþ¸Rû©GaU¯^ÝÜOÝ ¡V˜2eJ†©ÔªUKÔ*½�?þx éšûœ={Ö<&2:öÒ;Æ�gγ:þÕV®\9éÓ§�%7B8;"€È—_~)Ï=÷œ)AA;D8Ô{XuCœº¹®`Á‚Ùz/pñâEQïUÕMŠ øý[8òfL@}† æ/õ�'"C@W�?üP5j$¥K—6Ï?²!€ €è+�¶˜]]§ìر£¾ÁØ"СCY¿~½L�:Õ¬waC@2  =3!ûOA{ªùçŸ.+V¼fTq°*^O»©•ÓUápÚÕÜl>¾ºF�7¬ò®Ú¾ûî»R´hÑfW­¶­ŠË'Mš”å™ïÞ½»téÒÅ\>³MK« ¹¾Çmg¶¿ï÷sçΕJ•*´»*0W^YÝcïÞ½Ó‚ÿŒÆž1c†yÀͶPõŸÞñ’Þ�W-[¶4�Ÿ¬œ„yã�7¤M›6rË-·Ü4gu…2 dS77ÌŸ?_ž}öY³ _}ïÛªT©"ÊØWàHìƒ `�€ïÍõŸÿügyøá‡­é”^P@½?QïSÔ p)))YºQñú!Ô  jõµev3h€á± €€ÇÔ¹õÙTmêí©F‡’Úµk_#–^A»Zq[]=zô¨_õëŸþYT!qz[zíª€¹Y³f²oß¾Ìg)ƒ=Tþ[o½%·ß~{†}¨xUÑò{ï½Ô8ê‚°*ªÎhûí·ßdøðáþЂD=\=Î7£m×®]¦Uv6uãÀõ«¢ûú eÿé/�´�;VòæÍkÞ,‘ÕMµmذa†ÍÔ�Éêå¬nÛ¶m3WÇO»R¼*hW+¼gV@ŸÕ±Ø@ sß›ë‰'J½zõ2oÀX(0`À™3gŽ%í6l�öíÛ›Ñet3¨…¡Ó €€Ëþô§?ÉìٳͬX…Ëe“K:Ø(pàÀósU±bÅÌ'²!€ €è'�¶˜½oß¾A]ïÔ/+"B+^}õUQ @©'8”/_ÞŠ.é@\.@A»~LA{ꜜ>}Zbbb®™¡ØØXQE#""ü?Oo%mµßöíÛåÌ™3éÎðºuëä©§žòÿN�·mÛV¶lÙrÃþj¿Ö­[K‘"E$GŽòã�?𫾫‚ì´«ÂûªbgUž6FßïÔ8êîtõ†ýúM�£ÚªÕ\UÛýû÷˨Q£Ò�ÅŠR²dÉt7nÜ8Q¯ë7µR§*†W+ÓßqÇrñâEQ…zöì™n?Ó§O—êÕ«ßð»o¿ýV^|ñÅtWfWÙeÊ”‘µ*ºšߦþݯ_?yòÉ'Íâñ¯¿þZ6oÞ,j~®ßÔÿlUñ¹:†®ßÒ{AÚãI=~ëþûï7Í×®]+K—.õwQ¨P!9wîÜ5óAA»~ÿc'"ðŽ€Z}TýýV«N¨âb6ì  ÝNmÆBÈH@-.0oÞ<ó×ê¦u� FÀ·à‰:oªÎç²!€ €è% >ûù˜£˜]¯¹!thܸ±Yo¡êž{î9B"@Ð\€‚vý&ˆ‚öÔ9Ù³gÏ «„§·’¶*<Ž‹‹ËpÅsµ‚�*>Ï—/Ÿ¨}UA²*DŽŠŠòϾZ-½U«V7 ™=[=ê¶{÷î7´ËhKUÜݨQ£ö_²d‰Y~ý¦ŠÎÕÊÞj¥Í´›úWNçʕ뚟ÿõ¯M·ýõ×_7WØŒŒŒ¼a uC€ZÑ^]dN»=ðÀf�ttô5?W«!uíÚõšŸ©Ô9sæL÷¿(e®VWži·ôVÜuÿ�´«8}Eaé%¥æ½S§NrÛm·Ýðë#GŽH�5nø¹Z�N›_¿©›Ôêi7uÂòåËo(øWû¨§¨02ºaƒ‚výþÇND à�´OÉèÿÓÞÑ S»(h·[œñ@®P ‹-2Ìã¤9>@ »¾sl… õ!6@@}Ò³«À|O{Ô'B"A�p ¼ôÒKæBŽ«W¯–âÅ‹‡;ÆG@PЮß$QО:'Æ »aeïŒV Ϩð¸V­Z2qâÄkŠ×¯Ÿò«W¯JÆ eß¾}×üJÁׯ_?Ó#D)«UãÓné­ÒžÑ*ð7[m]õ™Q»ë‹àÕ~jµõëWW«Å¿òÊ+™æ¡ çUÎi·ääd©[·î5?»¾øúÙgŸ•eË–ÝÔØ—‡ºC?má¶*šW7¤-ÌuÿV´:TÔã±n¶©Uøû÷ïÍ.éÝ�qá³È]­¾žvS7,¨Õ×3Úþ?{wnSÙÿü›¢ RQ]�ˆ”"R†JID2f¨�¡„ …”’©ÈR’¹d.R¦¢BD*)<�O¤dêÿ|ïç·÷í½×ÚÓÙkŸµ÷zß×Õõ{~çÜë^kgŸ½?ë»´¼†æÃ�ÓþÚc^ît@\Ø»w¯hØB�öĘ<(»wï6ékô5ÚÖ­[å_ÿú—y=¤7{ieÆÀëí¿yófùî»ïÌM‹gžy¦T¬XQòæÍ›ØÄ½Ó±žÀsô©6z“Ü¡C‡ä‚ .0O²Þ|i]¢îUßV+=Fo>,S¦Œ-Z4x3žZé“pô{zcá¿ÿýo3Ĺçžk;®>!gß¾}æI2g�u–é«�¡À‡PN7kêØ7n4O©ÑµèÚ.¾øbóÆîiE:î�dÛ¶mòÃ?˜µè¹Õ'Ñè9×›Ni €™+ЫW/Ñ÷*´½üòËR£F�ÌÝ +GOhQ‡Zµj™×—ú^ @@Àú7ßc�=fC˜Ýç„U àE}½~޳hÑ"ó¹ @ˆ%@ =–Pú¿O ý¿æN•Ƶjû9çœqVìíNÆÃþâ‹/䦛n ùr¥J•̇°NA"kç?ÿüSêÕ«'_}õUðË"Zºt©)R$øµ�;wJõêÕCæÑÊæýúõ‹y•ÙUv×€‘¾AhjªR¥JÒûÐ@Õõ×_�¶s·ÖDÞyç�¸¬¾þúkiÑ¢… bëqzŽn¸á†�à™Ûãç4Ðﵡžúš5ÌØ±cGÑjuÖ¦×IÛ¶mC¾fW¹Þî"±ͬß'ÐóGŠ €€«�ØN!`W'ÏàÁõ Mý]©¯£ô8ý�¹iÓ¦ˆ½ûî»&,­7†ßØ¥Ç.\¸PÎ>ûìK¸¹ qÏœ9Szè!Ûuê>ô =à·6ÝoË–-ÍÓZ›Þ8øí·ßšã¬�v­|ÒµkWÓýõ×_�S¿~ß}÷‰^¯ú:Gý©Oõ‰h×±ôi5vM×=nܸˆó 7^öèÑÃñÜèpºté×ëÊŸ`@H©€Þ`¯OÓ” ³› IDAT6mÚ´ˆ÷?R:ƒ!€€oôFH}Ÿ«dÉ’²lÙ2ßì›�"€ €^°†Ùµ¸×=÷Üãåå²6ÈE�š5kÊ—_~i²3¥J•ÊÅ•05 €dŠ�vï�)ßÚ?ùäð (ÝvÛm¦‚xž5HŸžDCÈ}/BߓЦ7l…ßlŸ9;a¥ à5-Ž¡8.¼ðÂ�׌^['ëA@ü"`ý\�0»_Î:ûD yý`ÇŽbý¬ ùÑ8@ü @ Ý{gÙ—�v èhÅ�É“'‹Vn´kÑ^äÚÚõ1´ú8ÚhME2·ÎY°`As‡èYg�÷Õ±~ýziРAHÿÈ]wÝüZß¾}MµÑ@Ó’V:õÔSãšçðáÃQ«UÚ�¯á “N:)®ñµ“VA ò‡óÃçÑã4”Õ®]»Çt,·ÇÏI ÝZí4ª^ÓuëÖ ©Ü4×sª×�µú¬þ£¼dÉÑë0ž¦Á1k¥~=†@{<(¸±1Õ�vkuv§õ^é zúÚN_›nôÓ×ÈÖŠò�½¯[·ÎöØcÒ®]»ìÙ;Aן•éÓYóçÏïú|L€ €™/@ Ý{çÐ×�v 3i5J­°^¼xñ¸ÏN¬à°Ó@_ýµ\ýõ!ß®T©’¼úê«áœh‹ùã�?¤zõêòóÏ?»YÃFÖjB�Z]sÙ²e¶U-ãÞøÿu´†ž=6V§°•îyôèÑ2nܸXC„|_ÛZÉ­H‘"Q�sk|/Ú­Á½F ô/ªÝµ—hð>Þ¹è‡ ¿@àµÉC=$÷Ýw_üfiÏ?ÿüÓ<½Å.„xí¥¯gôõ”6»p\nÚSµëë�h¿¯?ûì3©]»¶±3fŒyZQ Ðí5d:íáOº‰uÉZ«6-\¸P:wîl{ˆ†ð óÉL±æãû €î =zTî¾ûnY±b…™äÍ7ß”K.¹Ä½ |/ ïµžA Ý÷— € �fÂìig:²P HÛ±cGBù›,¤`K € §�ö8¡ÒØ-kí}ûö5tXš3gN°ÂR´µ¦z|/Úín¨ÑSWåT“ã@@�ø³ÇçD/ˆ. Ÿ'”*UŠ¿ç¸P@@ !í q¥¥3�ö$˜“ ´§*hO =¼*i*íÖpQ€/Pí4·BÍZ½í‡~�uëÖ™€’݇NW^y¥©þ®!¶D[²ã{)Ðn·–5jˆ>ÂOo„ˆ§ÙÝ”a £Å3}@ܼÐþðÃ¥X±bîL’E£º Ï �ë9|ø°4hÐ@6mÚ$:_ºt©)R$b™¯¾úªôìÙÓ|}æÌ™R¾|ù`^¯% jðÛÚô‰=*T0_r ´ëSu¬ÕÒµï¾}û¤víÚæµZ¬@»†æ›7o.kÖ¬‰º~W_ç„?}I_·hÈÞ®M›6M~øaó- ´—,Y2'§�c@R$°ÿ~iÓ¦�¬]»V.¸ày饗Ìÿ¥!€éЃJ—.m¦"Оqæ@@¿ h±6-°¡í‰'ž077Ó@�dôó€K/½”¿ç’Áã@|,@ Ý{'Ÿ@{ç$•�öJ•*É+¯¼’PÈúûï¿—Ê•+‡¬\+i2DŽ9æÛ í9 s‡i`¨qãÆ&hÉì# ú¸ÑðÖ¼yóä�ˆèŸh•v» ßë�öDotøâ‹/䦛n a!Ð×eI'@Àu�À í &ÈÍ7ßìú|™>�òœ˜¸µkpÛî‰Fú ½þùç&4®!ôÓO?Ý<ÚwÒ¤IfKú$œk¯½6d{0Ô™´9Úµ�¾N°6kx>V ]�Ó›|ðA3Ä£�>*íÛ·�`Ö›óúõë'-Z´0}>úè#iÕª•éçôÚoþüùrÿý÷›>vÁûœœKŽEHN@?xÔðÂÇl*²ëï‘sÏ=7¹Á8 HB@Ÿ¸Ñ‘@{€‚ €$  ïcëû•Ú³'GW°°ááï9.@ˆW€@{¼RéëG = ëdíÿüó�ôêÕK4ÈhZíò­·Þ’sÎ9'î•h¥²¦M›†ô(õíÛW¦L™ÒG×mù!Œ:ßöíÛE«’*THN8áÉ—/Ÿ.\8xL¸�îC«[/^<î}¸Ýqîܹҭ[·�iôæ�k®¹&%SÇ3¾×íz=hˆ+¼êª�5èC =%—ƒ €9xæ™gdÔ¨QÒ©S'éÝ»wŽÇËöÜ �'ëæÖz¬oâêÚ†jnLÔ'Ö蛺z½nRÔŠ¸ ×þzS_èk( ùLŸ>]žzê©àV­�vë>ôëFÔ*—”Ù³g› ¼ÝkˆÅ‹˽÷Þk¾e ¡ÿòË/rÅW�Ñk[ÃêúÚE¿·páÂà˜ZÁW_Wë×7êôçâ²Ë.“ ˜×·6l�.]ºÈÏ?ÿlÂøúöÔSOMöÔq €@ ~ýõWf×£/¾øbsSÕYg�•‚‘ˆ_@_+^xá…æñ»Ñ@HTÀf×÷#õ}I ��ï¾ûN®¾újó~’>õ•† €Ä#@ =¥ôö!О„w²�v�Ê.+�5Š9ÍÑ£GMÐiݺuÁ¾ÅŠ“+Vȉ'žh¾f­j™èøÖüñÇq…©5˜¤sk¸>Þ¦ck…ZëPÏ?ÿ¼Ô®];b·Æ÷R ]o¨Ðª¥á7:Ä[•ô¯¿þ’ºuëÊW_}âG =Þ+’~ €€»K—.•¶mÛšI_ĶŽ _´h‘tìØÑ dWå;À¶†¹cÏêÜÃÍõ¼ÿþûÒ²e˨ËÓ§ù¼üòËÁõuC÷îÝEoà‹Õ¬ú:²yóæq½al}RL4o»›9íÖ¤av Ïk;v¬ <8ÖÒÍŽt½4@�ÜЛ¯4Ì®7X•-[ÖÜ uæ™gæÞ‚˜|+ ¯eÏ;ï<þ¦òíÀÆ@@ úYm Xaötˆ3þرc‡yÒìùçŸ/ï¾û®?6Í.@@ ÇÚsL˜ò´'Aš“@û×_-×_}Ȭ•*U­®•2cµ}ûö™@¶5¨¦aó·ß~[N:é¤àávóhPiÖ¬Y1çùöÛo#ª˜7kÖÌ~Ž9æ3ÇîÝ»¥J•*!ËÕª˜Zá2ž}è�úau½zõÌhªX±bðC#ý¾†’ô� ½ @›V·ïܹs,¢à÷cÚÝ_â¥@»®ÇîF­tªÕYcµ×^{Mxà�ˆnÚcÉñ}@ =?þø£èïzmÚc›[äÖ tàH½‘¯uëÖæÿÕ×7%K– 4ð}»×a±g�ìáözôÍ\½)Rod³6­t®¯mõfÆc�=6ä{‡–‰'†Tc×åÊ•3•Þõµœ¾>Ó×€:nàÆÃßÿ]†nªëZÛ-·ÜbæÒ›ô1Â5jÔ�_|Ѽv´zÛ��~øÁŒi}ÒQ`l½‘CÏչ瞜Nùï½÷ž ¶ÛUcÑ›ôô8ë ¡Éœ7ŽAÈ™Àž={L˜}Ë–-æ÷‹†Ù‹-š³A9È�@à }}ž'OžŒÄ¡ € €áÖ0{ÿþýƒï¿"…äTàË/¿”š5kJ©R¥D @Ñ@@�x´Ç£”Þ>Ú“ðÎI Ý©jåˆ#¤aÆ1W£UÖõ?kÓê™úŸµ9Íóä“OJ«V­çÑð�†–gÏžÒgôèÑR¿~ýà×´ŸV-Õ@RøZºuë ¾;M¤Ao Ni*ÐÂ+œ†;kàjùòå¢Á±xÚªU«¤E‹!]­VÝ_'öZ ÝîFuÕª¨z·²SÛ¾}» �Ù5íñ\�ôAÒ#x±mÀNÏ ˜ÅëÿùÏdÿþý&¼®7*)R$æÍˆz“ †Ô5xž/_>9å”SâÚ¦£OxÑy ( … Šë¸h�ôæÎ½{÷šõëztÌã�?>긺~ýO›®E_ûž:”ã1 €@Òú(h}¬ü_|!åË—7aöSO=5éñ8H…@ào*-’7oÞT É € €ÿ?~¼ 8ÐXfç’@�T lÞ¼Y´¨Î¥—^j²4@@ íñ(¥·�ö$¼sh×éÖ®]+M›6�˜ùå—_v k€|òäÉòØc�E§ÁmËWðûv�ný¦VÔ¼ýöÛ#BçôÑ*–cÆŒ ™Cƒæz«ž¬-Pa=|Aú�´VýÖà’]ûþûï¥OŸ>²råÊ�o?úè£Ò¾}ûà×Ö¯_/ 4é£r�92¤§ÝZÝí®»î’Ÿþ9øm /iÀ¯xñâækn�¯sx-ЮבkEkÓs¬Ø/¼ðÂN­’ç�wšðW ¦��WcmÚ“øG„C@—¯† &Mš4qi†E@r&ð¯ýËTfÿꫯÌÓÚ4Ìn}ê\ÎFçh@ y�ÀßTÛ¶m >�(ùÑ8@@@¬aöXÅ×C’d?ô©¬óæÍKfŽA@ h÷ÞI'ОÄ9Éi Ý© º.¥N�:&<|Á˜*@ZÙRƒãÇ—]»vE¬6<níàTE]ûh€¹G�æ‘KGŽ‘ 6ÈàÁƒm5ôñoµk×¶ýžÞI¯oB„7_Ã÷úCþüùåÀ�¢X¿ùæ›2eÊ”ˆþú�Ã’%KLh:Ð>,-[¶”5kÖDô¯R¥Š¹) D‰¦2§VéÔ°µ~>mÚ4Ûczöì)]ºtIÛø:‘×íº&» EïZÖJìgœq†è�/¼ð‚1 ´jÕªÉçŸr£�ö$þá@À%ë#[í^7¸4-ÃþŸÀ¡C‡b>¥ÆK«†ëk €~øæ›oL˜]ÿoåÊ•eÒ¤I<9Ã'ž="�!�0ôý/žê“!'�e"€ €€§Æ�'ƒ 2k$ÌîéSÅâÈhÍ”hQÇ«¯¾Z^}õÕŒÞ ‹G@�ô hOŸu¼3h�WÊÒ/§�vJƒÎ÷Üs�¬X±"‰üï�nݺI÷îÝ£§þúë/ _·n]RóÄšCCó}ûö•©S§&5~à ³—)S&bŒ~øÁ<ÊZi=™‰®¼òJ™9s¦üñ!‡»=¾í 0qâDéׯ_B”z“Åܹs¥U«Væ&‹@#О#�@W¬O�!Ðî*uÄà‹/–{ï½7©I‹+&Ë—/¹±/©�8@� è Óf×Þ«V­j*³Ÿp _5ËC? >ÀÐ'.\ØO[g¯ € €@ʬaö˜§kÓ@7O�¿öÚksœ]qc}Œ‰ €Þ Ðî½óB =‰s¢nk•q ôj%ëD+kjr­¼>f̘„W1bÄiذa\Çi¨ZCø3f̈« “ÎÑ Aƒ˜•F5Ô®Z->Ѧ•ÜõزeË:ª•ÂÛ´ic*ƒ'Ó4ô¯7(PÀöp7Ç× êÖ­RåÜé ›œ^W‰¯îzL²k×®IM¦¯‡V®\)'�tRRÇs €™ ðÅ_˜0û·ß~+úᢆÙõÉt4@ÀK�0´˜Ã)§œâ¥¥±@@ £ÆŽ|:8aöŒ:u,�ŒXºt©´mÛVjÖ¬iŠûÑ@@�x´Ç£”Þ>hß½{·T©R%DjèСҬY³´è 8PÆ�œKçø  “Ÿ~úéqY¹1¾ÞLиqã�ŠæC† ‘æÍ›G¬)§×UøñB×¹¢]—ú³ôÖ[oÙž35lß¾½9OZ�]›]Åy½Ù@ÃîÇsL\ÎtBpW ð‚[Ö*Tpw2F :tH8�”ˆþÕß»4@²Uà³Ï>3av}JZ�5L˜=Ù÷P²Õˆ}!€€7OmذAô½X € €@âÖ0ûSO=%wÞygâƒp �€À¢E‹¤cÇŽ&K+w“À°tE@�, Ð‘�vï1¦fEGŽ‘={ö˜0¸VX=zô¨äË—Ïœ4”}ê©§¦d¢ƒšy~ùå3�6 R�yæ™rÎ9ç¤¤š«Ž­sèø:Ÿ¶üùó›‚Š-š£GŠ[�öíÛü\¿®VÅŠ“3Î8#逵Ûã§ä$º0ˆž+ ¬kÅ}­è[¨P¡ˆYìíN}–È� €q^pëÍF£!€ €¹)ðé§Ÿš›ÍµQ%+7Ïs#€@<�¿§>þøcóþ" @@ 1�çž{ÎÜÒF˜=1;z#€@ò�§è6hÐ@FŽ™ü@‰ €¾ Ðî½ÓM Ý{ç„!àY½QAÖ üO>ù¤´jÕʳkfa €€ßf̘!=ô�Ùö®]»ü¶}ö‹ €Ð Ç·Ýv›YÑÍ7ß,&LðÐêX  )P¹reѧP®]»VŠ/ € €@Ö0»>YúŽ;îHàhº"€É ¼úê«Ò³gOѧØ?ýôÓÉÄ‘ € à+íÞ;ÝÚ½wNX® h5ö½{÷š*úú€Ò¥KÇ5çÛo¿-íÚµ éûÊ+¯È5×\×ñtBp_à³Ï>“Úµk›‰´»ïÍ  € `/ Õ�5jd¾Éãž¹J@ SªT©"»wï–Õ«W›'YÒ@@â3fŒ :Ôt4h�´lÙ2¾é…¤@`êÔ©òÈ#�È�wÞižAC@â Ð�RzûhO¯7³!�«ÿý·Üzë­òùçŸסÿ0¯\¹RòæÍumG�•Æ�˺uë‚ý ,(o½õðåêYer@ R ð¢{Ó¦MrÊ)§@„ €¤U@+7mÚÔÌY¿~}=ztZçg2@ Y�êÕ«ËÎ�;å½÷Þ“óÎ;/Ùa8@ð•av_�n6‹€'&Nœ(ýúõ“¶mÛÊã�?îÉ5²(@ðž�vï�íÞ;'¬×4”Þ¼ysY³fMÈC† 1_wjÿüó�è›Qá�çºñÆå…^�c�=Öµ530 €@â�ÝS¦L‘ë®».ñ8@HR@«þ¾lذ¡Œ1"É‘8 :ÒØ“ IDATH¿Àõ×_/_ýµ)þpá…¦̈ €d˜€ÞÀøüpðàÁÒ¢E‹ ÛËE�l7nœy:DÇŽ¥OŸ>Ù°%ö€ €i Оä§ О ÝÈt­¨Þ¾}ûˆm´iӯܱlù‡Úôùæ›oäÑG•÷ß?â˜Ù³gK¥J•2�„õ#€Y'ø·ü��®]»fÝþØ €xS@+ßqÇfqMš4‘aÆys¡¬ pÐâ Û¶m“eË–IÉ’%qB@ˆ"@˜�˼"0räHó>”~&¦Ÿ�Ñ@@�x´Ç£”Þ>ÚÓëÍl亀ViïСƒ¼ýöÛ¶k)X° è‡wú·nÝ*6l°í׺ukóØ®cŽ9&×÷Ä@B^ýuéÔ©“ùâ®]»àA@\X±b…è߉ښ5k&C‡u}N&@R-P«V-ùüóÏE B”.]:ÕÃ3 € �5£F�’gžyÆì'Ö“ ³fÓl<+ OŠÐ›lzöì)]ºtñì:Y €xK€@»·Î‡®†@»÷Î +BÀu�?þøÃ<~Ó¦MIÍ¥!…Ç{LŽ;Žç @w4Ä^µjU)^¼¸¬]»ÖÝÉ@|/°téRóÄ/m-[¶4�x¦!€™(P§NÙ²e‹,^¼XÊ–-›‰[`Í € €€ë„Ù]'fHPà©§ž’çŸ^yä¹çž{<šî € àWíÞ;óÚ½wNXi8|ø°,X°@ºwï÷|ZµýÙgŸ­VECð¶À÷ßoí4@@ÀM�%K–˜§€ikÕª•<ùä“nNÇØ €€«õêÕ3 ô©WåÊ•su.G@2Q`äÈ‘2lØ0³t}2—>¡‹†ä¶Àã�?.“&M2O˜oÓ¦Mn/‡ù@@ C´{ïDh÷Þ9aE¤UààÁƒ²yófSÁ÷ã�?}L|µjÕdÿþýräÈóxeýïŠ+®�’%KR•=EgÇò 1E#2  € €€“€>¹‚†©X´h‘tìØÑ |÷ÝwËO<‘úIH£Àm·Ý&6l�ùóçKùòåÓ83S!€ €Þ1b„ >Ü,”0»÷Ï+DÀO}úô‘éÓ§›§êÓi € €@<ÚãQJoíéõf6@ÀhçB@@Ò'@ =}ÖÌä�… JçÎ�͆۷o/�>ú¨6ÏN@ k5jd >Ì™3G*V¬˜µûdc € €@¢Ö0ûÓO?-·ß~{¢CÐpMà��×^{Í ´{"ð�@àâ?ÿ˜†i € €.sÌ1fTí.à2¤¯æÎ�+ݺu3÷Þ{¯<üðþö`ó �=M›65O1œ5k–T®\9{6ÆN@@�<ûì³¢ÿi#ÌžHE׺té" ,�Ñ£GKýúõ]›‡�@@ »´{ï|h÷Þ9aE àí>8Él@r]€@{®Ÿ�…ZíJ«^iëÔ©“ôîÝ; wÉ–@À¯Í›7—Õ«WËÌ™3¥J•*~e`ß € €@PÀfæ™gDo¤!€^¸çž{äÍ7ß”ñãÇK�:u¼¶<Öƒ € Ðî½C Ý{ç„!€€´ûà$³E@Èuí¹~ X@– ¼òÊ+Ò«W/³«®]»ƒíY¶M¶ƒ>¸ãŽ;ä½÷Þ“iÓ¦IõêÕ},ÁÖ@@‘áÇˈ# av®ð²@›6mdùòåòÒK/É�7Þè奲6@ð��v�Œÿ[ �vï�V„> ÐÌ@@ ×´çú)`Y$0}útéÓ§�ÙQ�=¤[·nY´;¶‚üO U«V²råJ™ ÐÌ@@ ×´çú)`Y"0eÊéÛ·¯ÙMÏž=¥K—.Y²3¶�„ Ü}÷ݲlÙ2ªúqa € €€¯4À>räHc@˜Ý×—›G cô¦›?üPfÍš%•+WΘu³P@È]í¹ëo7;�vï�V„> ÐÌ@@ ×´çú)`Y 0iÒ$yüñÇÍNzè!¹ï¾û²`Wl°hß¾½¼õÖ[ò /H­Zµ`B@|'` ³k•öÆ�û΀ #€@æ Ô¯__>ùä™?¾”/_>ó6ÀŠ@@ W´ç {ÔI ´{"ð��vœd¶ˆ €¹.@ =×O Èp t>ùä“f�<òˆÜsÏ=¾#–�D¸÷Þ{eñâÅ2~üx©S§\ € €€¯žyæ5j”Ù3av_�z6‹@Æ Ô®][>ûì3ó÷\Ù²e3~?l@Ò#@ ==ΉÌB =-ú"€) О"H†A@¢hçò@ y s8Ð ðØc�I»ví’Œ#@� èÔ©“¼þúëòÜsÏI½zõ2dÕ,@ȹ€5Ìþì³ÏJ£F�r>(# €i¸á†䫯¾’eË–IÉ’%Ó4+Ó € €@¦ h÷Þ$Ðî½sŠ@ÀÚ}p’Ù" €äº�ö\?, C4È9dȳúþýûKëÖ­3t',HLàþûï7�¨×ʤ·Ýv[bÓ@ÈP�§Ÿ~ZF�mV?bÄiذa†î„e#€€_ªU«&ÿú׿ä½÷Þ“óÎ;ϯ ì@ О XºhO2S €áÚ¹&@@÷´»oÌ Ù' !N­Ì§í©§ž’;ï¼3û6ÉŽ@�îݻ˜9s„ª¤\" € àÂì~9Óì�ì¸êª«ä‡~�µk×Jñâų{³ì@R&@ =e”)ˆ@{Ê(ˆ_€@{üVôD@’ О¬ÇùU@œúŸ¶ÁƒK‹-üJÁ¾@À§>ø Ìš5ËÜØÓ´iSŸ*°m@ð‹ÀСCe̘1f»#GŽ” øeëì²LàŠ+®�_~ùE6lØ §�vZ–íŽí € €€[ÚÝ’M~\íÉÛq$ �´�ö¤é8@ˆ[€@{ÜTtD@¬Uùôß~ûí¨ €¾èÝ»·Ìœ9“›z|wæÙ0 €þ Ìî¿sÎŽÈf�K.¹DöíÛ'[¶l‘Â… góVÙ €¤P€@{ 1S4�öA2  �ˆ�öD´è‹ €$'@ =97ŽòŸ€Vc;v¬ÙøðáÃ¥qãÆþC`Ç €À~øa™6mš<õÔSrç�wb‚ €d¥À�!Cä¹çž3{5j”ÜvÛmY¹O6…þ|ö¾mÛ69á„ü³qvŠ €9 О#>W&Ðî +ƒ"€Ñ´s… € €î íÛ·oOx²|ùò%|  �‰Ú|þùçÍÒ 2dâdÍ �J�Ç{L^~ùeéß¿¿´nÝ:•C3 € à Âìž8 ,R,øì}çÎ�’'Ož�Îp € �­Ú½wf ´{"ð��vœd¶ˆ €¹.@ =×O ð¸€6_|ñE³J­ÎW¯^=�¯˜å!€î ôë×O&Nœ(�?þ¸´mÛÖÝÉ@H³€õé\£G�–úõë§yL‡¤^àСCrá…š�wíÚ•ú @²V€@»÷N-�vï�V„> ÐÌ@@ ×´çú)`аæ¤I“Ì Ç�/uêÔñðjY ��È„ ¤oß¾Ò¡C‡ôLÊ, € €@ $ãÆ�33fO8S €@Úöïß/¥K—– È_|‘¶y™@2_€@»÷Î!�vï�V„> ÐÌø¯ÀÊ•+å·ß~ ±ÐJµk×–Â… G%ÚäìÐëeÑ¢Eæ�×@;xð \qÅRªT©´n˜ë1­ÜLæ’�v—`6ã4¨9eʳ�^xAjÕª•ñ{b €@*A¯>}úHÇŽS1$c € €¹.` ³�3Fn½õÖ\_ @R% Ÿ«”+WNN>ùdùôÓOS5,ã € €€´{ï$h÷Þ9aE àí>8ÉÛâŸþ)yòä‰kUGŽ1ýN8á9öØcã:†N‘ÿüó�4iÒDæÌ™ñÍmÛ¶I‰%B¾žhÿL6?pà€ãòõúKôÚÓcþþûoÛküèÑ£!áp¯»-^¼Xn¹å–ˆeêM�:uJÛòýt=¦ •‰rE€@{®°3©Çzè!™1c†YåK/½$7Þx£ÇWÌò@�ô :Tôµw¯^½¤sçÎ雘™@@—hžÊ¥�0»KÈ ‹¹*ðïÿ[*V¬(§Ÿ~º¬[·.W×Âä € �YÚ½w¾´{"ð��vœdmQÃÃ*T�­[·&¼ª»ï¾[7n,ÕªU“‚ &|¼ŸЛô ´p÷ .¸@6nÜ(…  áÑG"VªTɶÿ¦M›²Æ?žëñã�?–+¯¼2®ËGë7Üpƒ¼óÎ;Žýõ¦‚† Æ5^nwÒMïÞ½#–‘ˆI*ö�èõ›Š97´»¡Ê˜™,гgOyõÕWÍ&Ož,×_}&o‡µ#€)6l˜Œ9RxàéÚµkÊÇg@@@ �Ö0ûsÏ='õêÕKçôÌ…¤E`÷îÝR¥J9ûì³åƒ>HËœL‚ €Ù!@ Ý{ç‘@»÷Î +Bh÷ÁIöÐwìØ!¢Îi›8q¢´jÕŠªíqB:¹7kÖL¦OŸQM|ûöíUÛu*§þq.ÃsÝô�E˶ë{ã�7l«”Ûu~÷Ýwåºë®‹ºÏt‡Á“EwªŠ®UEô&ˆbÅŠ%;tÂÇ%zý&< �&íi‚fšŒèÑ£‡Ìž=Û¬U_‹è ‹4@�P�#FÈðáÃ¥[·n¢ÿnÒ@@�Lxê©§äùçŸ7Ë'Ìž©g‘u#€@<_ýµ)Ú Ÿ…F+~ÏXôA@ h÷Þù&Ðî½sŠ@ÀÚ}p’=´Åx¿ñ.Wß úðÃå´ÓN‹÷ßö[¶l™Ô¬Y3bÿZñÏ.¡¡k­ÐÞF��U�º_½zµT­Z5êu¡°Üwß}1¯­Î^½zuÑ1�š†Á·lÙ"E‹�9^nwpªÒ_²dIhÏŸ?Ú–˜èõ›¶…1 hOŒîY+ U†çÍ›' �—^zI®¹æš¬Ý+Cr" =ýôÓÒ¥KѧZÐ@@�L°†ÙÇŽ+uëÖÍÄm°f@ .�Ï?ÿ\jÕª%_|±¼ýöÛqC'@@@´{ï: Ðî½sŠ@ÀÚ}p’=´ÅñãÇKÇŽS¶"­æ¹bÅ 9î¸ãR6f64nÜ8ÛP¶…o¸áÛ-,×]w]°:»VbÿñÇÍ–Ÿ~úipx ñ�5*­aðd­/^löÞâ­XŸì¼áÇqý¦J’q¼ @ Ý g�5ä¦À½÷Þ+úûåä“O6¯¯¼òÊÜ\s#€žxþùçE+šÞsÏ=òÈ#�x~½,@° XÃìZ°Âî½FÄ@�lÐ' 7jÔȼï5wîÜlÛûA@´»ˆ›äÐÚ“„ã0@ 'Ús¢Ç±‰8pÀTá´|ÇëÛ„¶õf‚t5õ8tè�™N÷¡69in¸èz¬�v­¤1oÞ¼¨�v­t_³fM³õÜ·oŸùßáÕÞµ²¹ÞÔ‘HÓ°¼þ§íèÑ£Æ,UòõºÐóát= :Tz÷î±Ô÷Þ{OôÉéj©¼~sr½¤k¿Ì“ÝÚ³ûü²»èíÛ·—·ÞzKŠ)bÂìW\qd €1^|ñEéß¿¿ùâ±Çà @È�'Ÿ|Ò<åQaöŒ9m,R °zõjiÞ¼¹\sÍ5òÊ+¯¤`D†@@¿h÷Þ™&Ðî½sŠ@ÀÚ}p’=²ÅÝ»w‹åXpUZÍzË–-R´hQÇ•j8¼U«V2uêÔˆ>z¼†Þ?øàó_þüùƒ}þüóOóÆÑ¥—^UAû=ýôÓAá½{÷šÇ»ŸwÞy²sçNóxTk˜\�kÒ¤‰ eiß… Êøñフº“véÒE´x"ÕÐõ±„¤Öð×ÚµkCÖ¯{~ðÁMeúsÎ9'êÞœÜ�‚Ú?üðƒŒ1"$è®A`­6®{ oß~û­Œ=:¢Û¶m�›¶÷ß_´’¾~xamµk×6¡ék¯½6î«4U.ºn';k =p}�rÊ)¶kÔ€~•*UDoÊÐ^•ÝzК5k¤råÊQ÷ªáõuëÖÉ’%KlϽ|÷ÝwK‹-¤zõê’7oÞ¸íöìÙcÎÃàÁƒMù@Ó¾^ŸwÞy§ óëÏ›žë9s愌­ýÔÿüóÏ~]æ-Zñsø¹pZÜ´iÓdÇŽ!Õê<(=zô0•{-Ñë7|¾T]/q#Ó�(Ú¹<ü* ¿·ôæ/ý�:iÒ¤˜¯Ëüêľ@�pý7óñÇ7OÜêׯ@ € €@FXÃìú^y�:u2bÝ,H…ÀÊ•+Íç™úTß)S¦¤bHÆ@@Ÿh÷Þ‰&Ðî½sŠ@ÀÚ}p’=²E ¾jð7¼i…u ´Ç æjh½téÒÇk8jãÆ�&ìnWQz̘1&¬­Íš5Kn¿ývÛ±µúûi§�&¯½öš4mÚ4¢��5€+¨¬>÷Üs& ­i˜YÃàv•ìíŽÓpò<àXÞÉ}àÀ�Ò§OŸˆ!µzDÕªU#¾®AŠ'žx"âëóçÏ— D|ý“O>17)ÜtÓM¦|´¦®“'O–N8Á±[ª]4€^©R¥�ùôZjÙ²¥<ûì³Á¯k%{½ .l»6kuöh{ \§Åгí¦AvµÔkÕ6�5æôéÓåÆoŒê«õ±cÇJçÎ�cþk •yäs“Føy»à‚ ÌÏšµªÿ¨Q£¤k×®ã¾ûî»&po×ô&}"Cø5nwsK¢×o`¾T_/1áè€@Úã@¢KV è“@4Ì®ä/^ÜTf×§h €ñ hø¡oß¾r×]wÉ€â;ˆ^ € €@. èÓeô)3Ú³çâ‰`jÈ5-R¥O*ÔÏÆÿæÚb˜@2J€@»÷N�vï�V„> Ðì‘-¾üò˦²\xÓð¶>~4ôsZî�䪫®² zkèuÿþýR³f͈Ã5 ­š�§Áf»ÐµV¨nذ¡SCXºÖðÖ¨Q£ˆJÖÑÈg̘aªÆÛµaÆ™Ê뉶jÕªÉòåËmo Ъ~& oZM^«´‡·DûÛ¹h0yРA&0o8[÷®Õ¹Óå¢çAÃëÖV±bES™¼W¯^!_×..¹ä’ˆ¥…Wg×åË—7}ß& apÇú�À€Z…üæ›oŽüwº.ô×› ì~†4(߸qcY°`AÜ—•Þ¼ ÕôÃÏ�^/ºÏ“'�Kƒòú3­7#X›Þõ&§ øZ)¾\¹rãë /ï¼óNÈ͉^�º7~ŽâÆ£#Q´syøI@Ÿº¡¯?Þ{ï=óû@_/$ò¤?Y±W@ÀI@o^Õ¿©ôïýûІ €xYÀfþùçEŸÎICü&ðú믛ÂEuëÖ5…†h € €@¼Úã•J_?íé³f&@ (@ �‹!]úŽÝ›7fo×®]ÌeüüóÏ&(²Õðì¦M›Ìñoá!Üðï;íÃõáë×y÷íÛg†Óó† bîA;hÅo Ï[«\ë×�ó�A­óÙMäTýÝÉÝ)¤�Óþ±Ö鄤!ø¯¾ú*¢º[.÷ß¿Œ=:d9¼Ó› ÂoŒÐjß*TˆXúâÅ‹å–[n ùú„ äÃ?”‰'FŒ­Õ8ÂCçf×jåñÿ�üœnPЛ9ì*ëÇu±†u ¿é@o2Q»JîNá}R«÷ëÏLxÓs2bĈ£D¯G·®—d¼8�pí\~øë¯¿Ì OzÃáyç�g^ã\tÑE~Ù>ûDR&ðÊ+¯˜›m›5k&C‡MÙ¸ „ €¤Z@߃ ¼ªï�jñ àG�¹sçJ·nÝL±,ý¼ƒ† €Ä+@ =^©ôõ#О>kfB‚Ú¹Ò! Á×êÕ«ËÇ1�†�®¾úê˜Ëظq£\qÅý4ýÅ_ÈñÇ/ZaÛ.\«�÷‚ F»wï^3æ7ß|ñ=]«�µE[àÀž={ÊwÜ! ��;wš*zN!e­¦^£F�àœ›7o–Ë.»ÌÖ iÓ¦&¼pî¹çšï¯ZµJ´*|øØê FÅŠ ŽãTÕ>`vÊ)§„Ì™L§ójXƒâZ]Û¶m¦z¶]Ó5iºhÑ¢®»ØUV×Iµ2¹ð4|gmvaq»1ô† }œdýúõ#®C»b]Wº=ÿgœq†üòË/&8dÈ[?�{ýúõròÉ'Çå§�´Šü�7Þh*®¿ûî»f®hmÉ’%R«V­`—;vØÞDÒ¢E 3v ’{ø˜vÕñµ�Þ\ÒªU«¤¯_·~Ž¢¢ðM О]3Và�?þ0¿Kõæ® /¼Ðüî:ÿüó3v?,ÈM�×^{Mxàó)§¿£rs}Ì� €¨av®@àÿ pc2W €$+@ =Y9÷Ž#Ðîž-##€ŽÚ¹8Ò!°gÏ)W®\\!l»õ>|ØTÍ~ç�w"¾]­Z5Y±b…{ì±&@5yòä�>Námí4~üx騱cĘ]ºt‘‘#G+EÿôÓOR¶lYÇ€ú{ï½'ºkÛ¾}»”(Q–w̘1摃Úto×]w�¬^½:¢ïc�=fªk‡Wõþè£�L@<¼…ß îÖ€{ ¿V_°`�1³¶dúÛ�×À˜Zå~úôéÁ0¾~=ZèxÍš5R¹re×]œÎçÌ™3E×lùCÁ¬Å.ŒnW�}Ò¤Iríµ×Ú†¼í®‘ÁƒKŸ>}"Σ^³z­—.]:â{Ó¦M“;ï¼Óöº²Þ„ñÏ?ÿØþ<è�ú”µÖÿkmŸþ¹”)SÆvlýâgŸ}ò} ÁëµÞœžègW_¿g]¿þÿ‰\�nþ9‚ð  О Ý3Nà?ÿù�èMlúd“’%KŠþ^<çœs2n,ðŠ•ý¼r&X €8 è{×z#³6}«µj €€¦L™"}ûö•»îºK àGöŒ €I hOÎÅô»ˆËÐ €€“�v®�t|òÉ'ý}¥£ IDATR¾|ùˆ©4<¬ÁÝãŽ;ÎqZ]]«Ÿ/]ºÔ¶�>´}ûöæ{£G� ˆ7­\®Ákkûí·ßäâ‹/Ž©*TÈw­,§êð:ž5„m?Z Øøµ Fë86Öjóºžð¦•½+T¨³ ¸„+Uªq¼VþÖÂ[¢ý�Î«Ž«Õ¾5Ìnwn5Ì?vìØ¨çÉM§P½î_¯“Ë/¿<ÄvàÀ�!Ás»êìz¾¾üòKÑêû·ÞzkÈÞì®)}ª€]`]´ ¿tª.¯ß×�ÚµkgºFßîç!0¾Ó¹±»1D?¬jÛ¶mÄyŒ¶~§à¹]…þD®G7¯—ˆ ò’ О$‡e„€>IDÃìúÚ@oŽÒßÅ‹ψµ³H@À«z²Þl­O€Ò¿ui € €€—³{él°ðŠÀ‹/¾(ýû÷7ŸÕhÑ* € ¯�öx¥Ò×�@{ú¬™  hçbH‡ÀŒ3¤eË–¶Si…‚|ùòE|O«3k5êüÑq‰"ÖðyáÂ…MŸeË–™Jîá-¼r¹~Ô¨QÒµk׈¾ú(÷=z„|}Ö¬Yrûí·GômÑ¢…Ycxõ@G§ ð�@»†ÞõññsæÌ‰;™*×áÇ8¹ë| 6Œ˜3Uýõ¼|úé§rÊ)§Øž;» ÝÖÀ´Û.vç3¨ÖðyøÍáí.\hB%Ö“ë#vÃo¸à‚ DoÌ(X°`ð�‡zH† áS¯^=™7o^Dõ|kG§ÊîÖóïÔG¯Ù©S§JžY4h ZIÑÚÂCäû÷ï7•óµxÓ›2n¸á†¨?/¿ü²Ycx›9s¦4kÖÌÑXû/Y²$ê£�íáUý£�Ç-[¶Hþüùm÷àT¿sçÎæ&ëÍ!ñ^�n_/©ø·Š1Pí\Ù(ðÃ?˜0»Þ`¨O8Ñ×>§�vZ6n•=!€ixóÍ7åž{î‘ÚµkËóÏ?Ÿöù™@°ЊÃúþ¤6Âì\# €@¨Àˆ#døðáÒ­[·ˆâYX!€ €Ñ´{ïú Ðî½sŠ@ÀÚ}p’sy‹‡’믿^V¯^�Ò•Ì�;ׄ‡­íÏ?ÿ”Š+F…õMv­œhNÕ«µò¶†—­íðáÃrÝu×E¬ßZQÜncN¬µo xþî»ïš±SÕ¬�vu×�¶5x­ó8­;™þvçUoøâ‹/¤xñâ¶Ûúé§Ÿ¤lÙ²7XCßnº8�϶mÛŠVY×Ài§N�B* ZÍ¢Ugß»w¯\}õÕ×ßèÑ£EÛ�öÑG™Px³«än‡¨oˆvïÞ=â[�óïtcG¬Êù: ÓÓaùÀ¤»wïËTÁµ$[>Pá>0P"×£›×Kª~6 ÐÎu�mß~û­¹ÁêË/¿4O7Ñ0»ÓÓY²mïìH‡ÀÛo¿mžŠ¤O5,FC@r[ÀfŸ8q¢íÓRs{�Ì�ä¦ÀСCe̘1Ò«W¯�Ï…rsMÌ� €™!@ Ý{ç‰@»÷Î +Bh÷ÁIÎå-jõd ï~óÍ7)YI“&Mäé§Ÿ–sÏ=7b< ‘‡W×ÖNRŸ?¾äÉ“GöìÙ#ÅŠ‹8¶Zµj²bÅ Ñ ÝÖöÛo¿É•W^±~­„=}út3¦Ssª0­lZµjåNÊhw Ž;í3ÑþNU±5DÿÎ;ïD8ö¤{­NÞ¬¡o§Pu*\œÎ§5P=~üx騱cp: ´õÕW¢•ñëËkH\«Ò.\X¶oß.%J”ˆXfx%ÿ3fHË–-#úY¯Óh{ ÜúοVy¯Y³fRãÇ[ýƒ>07L„7}£V×çÔœžv°fÍ©\¹rð°D®G7¯—d¯9ŽCÀN€@;×E6 ìܹÓTf×ß}ú{]Ãìú»�† �:ý;Bÿ¾Õ'8Mš4)u3 € �„À£�>|ª%aö$9|!0`À™0a‚ôíÛW:tèà‹=³I@H��öÔ8¦rí©Ôd,@ NíqBÑ-i�Í›7Ëe—]–ôñ�µš�†ÀË”)u¬qãÆÉ}÷ÝÒG+_oÙ²EòçÏ/NÕ­7nÜ(åÊ•‹[«�—.]:â뱂»N¦u uëÖ™J¦NÁæd±4d¬µ9¹÷îÝ[´B}xsê¯îC† ‰»—.]däÈ‘ÁJÄá:íÙúvÓåóÏ?·½†¬v¯½öš4mÚ4¸t ´«Ïúõë¥N�:![ Üœ _\¼x±ÜrË-!ß׊õzœ5è>{ölÑ3Â[ø“쮽iCŸL°`Á‚ˆoÏ™3G6l(v?ÚyàÀ�Ò§OÇËKÇÖŸ±©S§†ô±«ê¯ûÖpMxÓjéÕ«W·�ãèÑ£&Œ£7¥V?‡ÓÂR-Z´È¤¥³V@Èeí¹|l¦'Ðî½sŠ@ÀÚ}p’sy‹úæ�]…ïaÆI�=R¾:­F×À²Vh_´hQDUi áj¸xñâ¶kéׯŸ<ñÄß TYwÚÀ›o¾QÍ[ûjaÅŠ& ïÄuªŸ–“»ÓØNý�öéÔÍš5R¹reÛ¥:U­}»é¢¡t­ŒamÖ þúõíÛ·‡TTׯiEu Y[›µ:»SuózõêÉüùó%Ož<ÁC�ö÷ÜsÏE<] òã�?6�öðf݃S`>Öøv7ƒè<áU÷8 W]u•|úé§QÃרUåo»í¶ˆµ·mÛVôFˆ@ØW;$rýºy½$ò3G_b h�%Ä÷½. O9Ñ0ûwß}gnüÒ ƒ¾ž¡!€¸# O‘jÖ¬™\sÍ5òÊ+¯¸3 £"€ €Q¬av}:—>}‘† à, Ÿ{ja¥áÇKãÆ�¡B@â Ð7UÚ:hO5!€ÿ_€@;Wƒ›G�5orë£ÒÃÛ’%K¤V­Z)Ÿ^ö*Tˆ¨`­s}ÔŸ‚­-ZÈW×ß°aCÑ nx‹h×psçÎ�eìØ±ÇYƒüÉ„åµÊ¼ÃO8áG;­l¯A³Õ«W‡ô±V‡·~#Z½ hÑ¢!ãhÿš5kFœ×ðJçá ܳg�”+W.¢B~xèÛ-=Ÿ·Þz«¹±ÁÚ´JÆÔ©Sƒ¡óÝ»w[çìZ¦L™àuÂwªœnwã†VæèÓ§OÄù‹UÝ^p o0@yä3f2�v½fÛ´i|j‚uqáëÒ§\yå•òÍ7ßDu´~3Ú¿á?ƒ‰^¿n]/)ÿlj}/@ Ý÷—@Fèëý=ñïÿÛ¼¶Ó0»õF¤ŒÞ‹G<*ðᇚkõfÒð›k=ºd–… €Y$ ï5ê{¦Ú³gщe+ ઀~6¸páB3fŒù<І €Ä+@ =^©ôõ#О>kfB‚Ú¹ÜØ»w¯\qÅÁW�S«¢—*U*åÓG æ†O^™;üûÑÖ¯oFi¸Ø®}ûí·rÎ9çØ~Oa—\r‰ùžViЀBx{ñÅE«VÛ5­v¯Õ«»uëfÂö—^z©œ|òÉ!]�ÇUªT1!ôðjª©ê_²dIÑ ðùóç·]û'Ÿ|"åË—�ø^xèÛ-§ó¨¶«@®aýü1¸öÉ“'Ë]wÝüÿíž  ß´»qÃiZ}qúôé!ÕÜ­Xv•ãßß¶m[°ª¼S =Ú5ëTù]Ç×ïi€=Ðôg·téÒçQÏÿgŸ}f[­W8M›6µ½.Þ{ï=óä‚@KôztëzIù?N è{í¾¿2@·keöŸþYnºé&Ñ×)4@÷ô&jý›O_‹Ï�;×ý ™@ø?k˜]ŸRY£F l@âèСƒù\h„ róÍ7Çq]@@�ÿ h÷Þ•@ Ý{ç„!€€´ûà$çâ�‚¯áÕ­S½ÄqãÆÉ}÷Ý1¬†©7lØüz¬*ñÑÄNÕÎ5P¯Aw]Cx»å–[dþüùÁÀ¯V~¿í¶Û"ú9Ï�Ö£FŽ B;¹wéÒÅô ¯¨šªþÂ×jÞN[g̘!-[¶ŒØoøyH·Kx Ú.Ðn]´Þ¡á¾B… ¿¼lÙ2Sµ>¼iÀ[+»[›S_í§kºñÆ#ªîë÷üqyâ‰'‚S8Ú5p®7TäÍ›7d=?ýô“T®\ÙöÆ»ªûNס�‹þ<è“ ôµkj¨FÖ@½ݺ^Rýïã!@ �k Ö¯_oÂìz³QíÚµåùçŸÏÄm°f@ #ô†àúõë››Äíž–‘›bÑ € ày�‡~X¦M›fÞãÕÊì„Ù=ÊX xH uëÖ²bÅ áf �–‚ €@†h÷Þ‰"Ðî½sŠ@ÀÚ}p’sq‹NAÓzõêɼyóäØc�ueuË—/7áßh-<\n×wñâÅ¢ýÂ[ Z·óß|óM)[¶¬é²k×.éÙ³§Ìš5Ëvj]—õ ‡]|ñÅ!•¿jø|À€R¸paó¥µkך0ƒµJx ¯Vzhß¾}pN§ŠÕúZ«V­"Ö–ªþá•ÎÃ'r Z‡‡¾Ýr±»5P½iÓ&9ÿüóC–ë´Ví¤ÁüæÍ›‡ô·»‰B¯Ý[àˆVù_×£Õo¸áó¡Ñ‘#Gä£�>’víÚÉÖ­[#Î�ÝSœnЃõº}b®SCÈùóçw ëÕªU3UŽ;î¸�ñ´zµVtwjM›6•wÞyÇql­º­ÕÂuþ@srwªþ�ªþ|ð�\}õÕ¶[9tè�håy]ƒµ9…¾Ýp±;Ÿz�èµP°`Á�ué9Ñ5„7»¶V!oÓ¦�Lž<9¤»Þ ¡!z»7œBÛ�ôš*Q¢DÈÂ×¢}´r®ö³¶�;wFôc^Ä4À¨OÈ“'O°G´¹Ý0ºÎ}ûö™on ôkÖ¬™LŸ>=düD¯GË�ë%Y3ŽCÀI€@;×F& ¬Y³Æ„Ù÷ïßož&3jÔ¨LZ>kE²B@oŽÕ§cèŸô©V4@@ÀM}<ð>�Vf'Ìî¦6c#€@¶ 4nÜØ)ÒÏ€*Uª”­Ûd_ € à‚�vPs8$�ör8 �Œ�ödÔ8& ù6hÐÀöÑèúa|­Zµâ&©>� *8V†Ö*êC‡�:¶®_«™O�:5¢_Íš5eéÒ¥q¯Í© ¸ k­^½zDÐ;ÞÁë›=zT6lá®Aâ-[¶HÑ¢EC†NUÝ£®åœsα]úž={¤\¹rÁ|§Ðwª]œ®G»@µn@«ìß~ûí{™9s¦è1Öæt½i¥r§"œBðñžwõÖ*N7e<ôÐC2dÈ�x‡sìgw󉮽sçζ�§�œ~fÂÇOôz Ì—êë%Çp €€��v.‹LÐß/f×[5j$Ï>ûl¦,�u"€Y% O<Ò×Ñ¥J•JèïϬB`3 € �k˜]+³_wÝui™—I@�l¸õÖ[Mª… Êå—_žmÛc? € à¢�vq“š@{’p†äD€@{Nô86šÀÞ½{åŠ+®�o¾ù&¢[x;Õ’ÑÂÂìÖ7“Š+uÚ?ÿüS*V¬Š×Šèï¾û®è›RáÕÆíÔ ä+W®”‹.ºÈq>�ë‘G‘#FÄM¡�fWƒûÖæä®ëÖ@{Þ¼y]éoW¹Ü:Ñ'Ÿ|"åË—�Ø_´Ðw:\œž �ƒlÙ²eÈz�öèôD€9s昛 œš^§ZY\Ãቴ^½z‰þW¤H‘¨×”V÷_´hQÌ¡õC*½ž4´Þž{î9¹ï¾û"¾¾{÷n¹òÊ+£>• pÐܹsÍÍ ^xaÄ8áF‰^¿ÖSy½ÄD£IhO�CÒ. ¯qô©#‡ý=òÌ3Ϥ} Lˆ ð?�íÛ·K�5Ìß’ú´/ € à†€ÆÐ÷Bõ)“ú>áµ×^ëÆ4Œ‰øBàæ›o6Ÿ+ja/}Ú @ˆW€@{¼RéëG =}ÖÌ„´s1¸%àT‘[Þh/\¸°[SË¡C‡Lå}Ó(¼M˜0AÚ·osn§�r‹-dÚ´iò×_Éĉåþûï·KƒóÖ*õúa@})Þ¶{÷n±¼X V¯^=™?¾ ÐÓ@HD€@{"Zôu[`Μ9Ò½{w3�ÞاO·§d|@�´ªŸV÷Óª~zƒ6 @ȉ@Ïž=åÕW_ ³keöªU«æd8ŽE°¸è¢‹äàÁƒ²}ûvóï- @ˆW€@{¼RéëG =}ÖÌ„´s1dªÀ×_-úÆP¬¶|ùr©Q£F¬n!ß_½zµíúÆ “=z$4�@Pí\^˜5k–<øàƒf9�;w­ÐGCðžÀï¿ÿ.—^z©œtÒI²yófï-�!€ €@Æè߀ú·àñÇo*³fϘSÇB@ ß»ïÚµ+ÃVÎr@@ ·´çöˆœŸ@»÷Î +Bh÷ÁIÎÒ-¾ùæ›R§NÇÝ*TH´O•*UÐ 5mÚ´‰8nÉ’%R«V­„Çã@´s xA`æÌ™Ò»wo³”nݺq£žN k@ô1õú¤1}L½>®ž† €$#` ³ëûÞɼ_žÌ¼ƒøM@+³k!.­Ì®Úi € €@"ÚÑJO_íéqf@ D€@;D¦ ¼öÚkÒ´iÓˆåk�½ÿþÒªU+9å”SÞÞ?ÿü#Íš53kÂÛ–-[ä’K.IxL@@€@;×@n L›6M~øa³Œx@ºvíšÛKb~@�(ýõ—”*UJòçÏ/_~ù%V € €@Âú·Ÿ¾�~ '˜Êì„Ù&ä@ nnJŽ›ŠŽ € `#@ Ý{—�vï�V„> ÐœÅ[Ü»w¯èDB× à±Ç+§Ÿ~ºùß9i¿ýö›èãÝÃ۹瞛ã±s².ŽEÈ\í™{î²aå“'O–G}Ôl¥W¯^Ò¹sçlØ{@²Z€ê~Y}zÙ €® XÃìZ™ýšk®q}N&@ü,ð믿Êå—_.§žzªlܸÑÏì@’ ОšË‡hw˜á@;í\ € €€ûÚÝ7f{�—^zIžxâ óÍ>}úHÇŽ¡BÈ�#GŽÈùçŸonÜÞ±cG¬˜%"€ €€Wzôè!³gÏ6OzÑÊì„Ù½rfXd³Àž={¤R¥Jræ™gÊG}”Í[eo € à‚�vPs8$�ör8 �Œ�ödÔ8@HL€@{b^ôN�À„ dÀ€f°¾}ûJ‡0óñH IDATR30£ €¤E ðžÍ®]»Ò2“ € €@æ XÃìZ™ýꫯÎüM±@ ôﶪU«Šþ·jÕª X1KD@/ h÷ÒÙøßZ´{"ð��vœd¶ˆ €¹.@ =×O�ï0nÜ84h�Ù·Vh¿ûî»}gÀ†@�LÐ íZ©]+´k¥v € M {÷î2gÎ)P €©ÌN˜�ëHŸÀöíÛ¥F�rÑEÉŠ+Ò713!€ €@Vh÷Þi$Ðî½sŠ@ÀÚ}p’Ù" €äº�ö\?¾ZÀ˜1cdèСfÏO>ù¤´jÕÊWûg³ €@¶hâàÁƒ¢Áˆ|ùòe˶Ø €¸ ` ³keöÊ•+»0 C"€8 lݺUn¾ùf)S¦Œ,Y²(@@ !í q¥¥3�ö´03  *@ �+@p_€@»ûÆÌð?�‘#GʰaÃÌÿ8p ÜqÇÐ €d¨@©R¥ä¯¿þ’/¿üRòçÏŸ¡»`Ù € à¶@·nÝdîܹrâ‰'šÊì„ÙÝg|@ R`ãÆ�rë­·Êå—_. .„@HH€@{B\iéL =-ÌL‚„ hçŠ@@Ü Ðî¾13ˆ >\FŒa(† "Í›7‡@ ƒ´²ßü!Zé¯`Á‚¼–Ž €n XÃìZ™ýª«®rk*ÆEˆ"ðÑGIãÆ�¥R¥J2{öl¬@@�„´'Ä•–ÎÚÓÂÌ$ €@¨�v®@@À}íîû}†¡C‡Ê˜1c Ã3Ï<#M›6õ; ûG2^àÒK/•ßÿ]6oÞ,'�tRÆï‡ € €@jºví*óæÍ37¾i˜]C”4@�ÜXµj•´hÑBªV­*3fÌÈ�E0+ €d¬�vï�:íÞ;'¬| `ù…èƒÝ²E@@ w¶oßžðòåË—ð1à/�Aƒɸqã̦Ÿ}öYiÔ¨‘¿Ø- �¥ú¨ú_ýUôÑõ§žzj–î’m!€ €@2„Ù“Qã@À=�+VHëÖ­¥F�æ&# € �ˆ�öD´ÒÓ—@{zœ™ ÐÎ� €¤O€@{ú¬ý2Ó€d„ f»£G�–úõëûeëìÈz� *ÈO?ý$ëׯ—¢E‹fý~Ù  €Ä'pÿý÷Ëüùó¥P¡B&4Y±bÅø¤ €€kK–,‘:ÈÍ7ß|¯ÎµÉ@²N€@»÷N)�vï�V„>üBüçóÏ0 @@À�cŽ9ÆŒJ Ý\Ù¯_?™8q¢;v¬Ô­[×ÇlÈ>�J•*ý?öîÞÆrýÿøõ3)áe mDf2O G†2?!óÇ™‡È”!sȰͳ(C”yÎx ™¢ˆtœøŸëþ�µþ{-{Xkí5�Ù³g›5M›6M^{íµˆZ‹Aø?�2eÊÈ?ÿùOùöÛo%Æ/4àA@ 8ÂìiÒ¤1�Ù ³ÛðEÀ’@ÀÒsçΕ޽{K“&Mdøðá–®•â@@ÀzÚ­·'Ú­·'T„6 ÐnƒMf‰ € rí!ß‚ˆ) W¯^2oÞ<Ñ×ÔôéÓ¥råʳ6‚ à*P¾|y9sæŒlÙ²E²gÏ €ØT cÇŽ²lÙ2Ñ0»vf/R¤ˆM%X6 `]ýÿ³>Qñÿ÷eðàÁÖ-”Ê@@À’Ú­·-Ú­·'T„6 ÐnƒMf‰ € rí!ß‚ˆ(à¯ý«,X°@’%Kf:òiБ@ r*Uª$'Ož”M›6É‹/¾¹ ee € §@‡dùòåòôÓO›Ÿ ³óbA¬)0eÊ2dˆ¼÷Þ{Ò¯_?kIU € `YíÖÛíÖÛ*Bh·Á&³D@¹�ö�oAØðþûïË¢E‹$EЦ3{™2eÂ~M,@ ~�ªU«ÊñãÇeýúõ’'O¸@@Àf1ÃìÚù·páÂ6`¹ €@øŒ?^FŒ!úT�ž={†OáTŠ €– Ðn‰mp)‚@»õö„Š@ÀÚm°É,@B.@ =ä[ÖtîÜY–.]*O>ù¤ ³—*U*¬×Cñ €ž ¼þúëräÈY½zµä˗ϳ‹8 @"B }ûö²bÅ I›6­éÌN˜="¶•E €@ Œ=ZÆŒ#ݺu“®]»FðJY €B€@{ T7&�öÄùq5 à“�vŸØ¸@ðJ€@»W\œCÀÑ‘/uêÔ&Ì^¼xq|@l"P³fM9xð  ³(PÀ&«f™ € 3Ì®�Ù *  €€Å†.'N”Þ½{‹þœ@ðF€@»7ZÁ9—@{pœ™p ÐÎ @¼�öÀGâ mÚ´1]yµ#Ÿ†Ù‹)‰ËdM €qÔ®][öîÝ+ÑÑÑÙx• € `�víÚÉÊ•+å™gž1�Ù ³ÛdãY&„½ÀàÁƒeÚ´iÒ¿iÕªUد‡ € €@p´×Û“Ù´{¢Ä9 €€Ÿ´û”á@@�X´ó²ðV@ñµnÝ:IŸ>½ ³¿òÊ+ÞÁù €a.P¯^=ùþûïeñâÅR´hÑ0_ å#€ €@B1ÃìÚ™�Ÿãó €€uúõë'3gΔ!C†H³fͬS• € €@Xh·Þ6h·ÞžPØ@€@» 6™%"€ €@È´‡| ¦€‡Ê»ï¾+7n”çž{ÎtäËŸ?ØÔO¡ €þ¨_¿¾|÷Ýw²páB)Q¢„ÿf$@°œ@Û¶meÕªU’.]:ós avËm!€ñ ôìÙS¾üòK1b„4jÔ-@@À+í^qådíAaf@ÀU€@;¯@@ ðÚo 3üûßÿ6aö¯¿þZ2eÊd:³GEEEÂÒX €€‚ؾ}» E¼úê«>ŒÀ% € 1ÃìÚ™½`Á‚áP65"€ÄèÚµ«yºÖ˜1cDŸ¶Å� €x#@ Ý­àœK =8ÎÌ‚¸hç� €^€@{à�Ã}†?þøÃ„Ù¿ùæÑïÑ5Ìž;wîp_õ#€$B iÓ¦æëœ9s¤lÙ²‰‰K@@ÀªmÚ´‘Õ«WKúôéMgvÂìVÝ)êBâhß¾½¬X±B&L˜ µjÕ‚ @ðJ€@»W\A9™@{P˜™p ÐÎ+@¼�öÀ‡ó wïÞ•-ZÈŽ;${öì&Ìž3gÎp^µ#€øA Y³fæ©3gΔ *øaD†@@+ Ä ³kgö X©k¾Öfçe‚„·€ã†ä3fHÅŠÃ{1T� €A Ðtò'$О ' €þ ÐîSFD@Ü´óšˆMàæÍ›&ÌþÃ?Hž C@f;pà€,_¾\ ,ˆ  € à•�v¯¸‚r2�ö 03  à*àï@ûÇE…}ïÞ=ùí·ß¤Y³f’)S&Ø@@[ h·õö»,þÒ¥K&Ì~äÈ)T¨� ³§K— @èÔ©“DGG˸qã¤víÚ!€ €@ ´jÕJÖ­['f×Îìú¤.@�Èxíµ×䨱cæ)QQQ‘³0V‚ €A Ðf¯&!Ðî'#€þðw ýîÝ»òòË/ËéÓ§M�»ví ÈcÕþýï‹þùóÏ?EbO<ñD¢@´n ã;Ž)RHÒ¤I5fÌ‹5௵ê˜:vB‡ã|=ïñÇ—dÉ’%tI¼Ÿ×¹ÿøãsŽÖ cr € €@ð´ÏÚÊ3�?^Z´h!'Nœ�¢E‹š0ûÓO?må’© @ „]»v•Å‹˘1cD_Ï� €á)à³?ÿüó¦3;aöðÜGªFâ¨P¡‚ùýøæÍ›%gΜ`!€ €^ h÷Š+('h 3“ €®þ´k»D‰æ‘jzìÞ½Û„u¼9<(_~ù¥¤L™Rnݺ%úÎ\¹r™@¶v0™:uªy\[Ì£páÂÒ§O©[·®ÇAô~øÁt:›]{ì1û‚°r@�ôItúä·aÆ¹Üøïñœˆ €!ˆf×ÎìQQQ!«…‰@/ �Ë~ùåÓÌ,¶ß'¾f@@�p Ðn½Ý#Ðn½=¡"°�€Õíî[ owëÖÍt`ÿã�?Dê4�ìŒylß¾]J•*åò1 {W¨PÁ<ê-æ¡ÝÌßxã s·¼†Íôqp&L�O?ýÔ弘¹Ü�˜�v÷ÏM›6ÍŒýÄOˆv†×Ç‹8p ÎWÖСCå�wÞ‘gžyF®\¹"úw =Å<Ö¬Y#ÕªU‹uŒAƒ=R£v˜×ÚsæÌiBþ7nÜ�¥K—J›6m\ÆÐîöÚ]žn¡6øŸ%"€]€@{ÐÉ-1¡Þˆ¨ß¿^ºtI*V¬h¾¯Kš4©%j£@ë ôë×OfΜ)C† ‘fÍšY¿`*D@#ТE Ù¸q£yê«vf'ÌÎ ˆ|�—^zIîÞ½k~üä“OFþ‚Y! €øU€@»_9ý2�v¿02 à�@8Ú/^,uëÖ}d�÷ïß—Ò¥K»tp�-|®ÖÝß8Þ¹s§”(Q"V´Ù³g›p¹ãˆ«K{l�ví ®Ïõnü˜Ç½{÷LgÎØºÍ¯]»V^{í5—ó>|(½zõ’‘#G:?Þ¤IÓ�=I’$.çž9sF4”ó?~¼´oßÞõÝ�üQÊ”)ãÒ~Þ¼y<ÆÜ»ÿ„8@�´{ÄQ'ò¨ðQ£F™Î󱆊9§Ž»dÉ’Gº{>\ôqäŽC»ÁO�:5ÎõéyÚ%FVŽ£lÙ²²iÓ&ÓÉ�@ü'@ Ý–á0ÒÞ½{MgöŸþÙù­ÿþÒªU+o/ç|@²Àÿþïÿš÷×3eÊdnJ#Ìä `:@ D<�lÙ²™ßÉž;w.DU0- €„³�vëí�vëí !€€ ¼ ´ë�åúƒ¸vw?4ý믿š.ãÚ‰Ríø]ªT)�íH—.�¤M›ÖåSîñ¸:£Ç¼èäÉ“’+W.ç‡5j$`�ÙÅü÷ß7uݾ}Û„‹ž}öYÉ›7o¼»Ü¹sg7nœó Îk`?æá^¯Þ�èÐ!I™2e¬c»×ªÝÜ5(Ÿ={öXÏ×ÇÓ+VLŽ9b>¯�ö¥K—º¬íÖ­[ÆÙqŽž§5äË—/ÞõÅÚ÷ä:ü§Á@ð«�v¿rZz°ï¿ÿÞ„Ùùå©Q£†|öÙg–®—â@¬+0tèP™fÌyÿý÷�S.Z´HêÕ«ç × € €@ÚíñÒøî»ïL˜]o |ã�7DŸ2Ä� €€¯úžÄĉ¥wïÞÒ¾}{_‡á:@ €6ÿÑ0ûæÍ›%sæÌ¦3;aö‚34 `Am<–?~Ñ&f‡¶`…”„ €V Ðn½"Ðn½=¡"°�€·�v÷0vb‰&L˜ðÈ/eÝíS§NMðÑÚž„¾c«Uß`¸víšÜ¸qC’&MjNÑ®åÚ‰^ÿ¾nÝ:0`€óROíS¦L‘Ö­[ÇIãI7ù˜{²¶S§NÉ‹/¾è¼L»h×w}Ä]Ì.õîEi§íöÖ®];ç§bÛ“Äî3×#€ `wí‘ÿ ؾ}» ³ëÓuêÖ­+cÇŽ�üE³B@�€ Œ9Ò<5®G�Ò©S§€ÎÅà € ཀ{˜];³ë{ó €ö¸~ýº.\XôÉä{÷îµ×âY- €øE€@»_ý:�v¿r2 à™@¨íÚµ²C‡.źÚ= X{úvL¢aõ­[·Šv&_¾|¹gPÿ=Ë“@{BõºÚê&ïÉÚV­Z%ÚéÝGBõûcÆ@@ÀnÚ#{Ç¿ùæiÑ¢…üë_ÿ’·ÞzKF�Ù fu €AЯ'úÞE·nݤk×®A™“I@@À3m&£�Ù¿þúkÉ’%‹éÌN˜Ý3;ÎB"MàÒ¥KR²dIɘ1£è9@@o´{+øó ´Þ˜@�G¼ ´ëÚuR;�¸Ôúí·ß¤T©Rrúôióé�7JéÒ¥MÇóØíž,Y2—O2оk×.iܸ±³>o_V ´»›y»®˜ç�5Êü²œ@ü'@ Ý–VI+¯�ÙÿüóOiذ¡h7]@ü! OûПѻté"Ý»w÷Ç�Œ� €~p³kgö\¹rùad†@G�³gÏJ¹rå$kÖ¬¢�/8@@o´{+øó ´Þ˜@�G| ´ÇÇèÞM<¶xBÛ¨@ûÁƒ¥@�±Nß AÉŸ?¿<÷Üs¢�³äÉ“KŠ)dæÌ™²zõjç5v´'Ô1>¡ýãó € ð¨�öÈ|UlذÁ„ÙõhÒ¤‰ ><2ʪ@B" Oµ1b„tìØQzöì’˜@\ôffí̾eËÑ߯hgvÂì¼J@{ œ8qBªT©b¾h³7@@À[íÞŠþ|í�7f@à»ÚµC|ÕªUÍã?c+V¬�J•*IÊ”)c}uDGGK�:uœŸ³j }Ù²eR»vmg�¬ÒÇ’k§o횯a~@ðŸ�vÿYZe¤µk×JëÖ­M9Íš5“!C†X¥4ê@ˆ�I“&ɰaä]»vÒ§OŸYË@@ |ÜÃìÚ™ýÅ_ ßQ9 €€_:$5jÔ�|ùò¹4JóËà ‚ €¶ Ðn½m&Ðn½=¡"°�€]í'Ož|¤KŠ>ò­L™2ñîrçÎ�eܸqÎs¬hw_]Ömð/KD+íaµ] »jÕ*iÛ¶­9¯E‹2hР¯á@¼˜2eйaê½÷Þ“~ýúy{9ç#€ €€´iŽvfߺu«dÍšU4Ìž3gN?ÎÀP €á*°gÏÓ ­P¡B¢ÍÒ8@@o´{+øó ´Þ˜@�Gìhß¶m›Kx];˜O�:U’$Iç«B»›×«WÏå�«Ú�;&QQQεԬYÓÔ�4iR^õ € `íØ?•°|ùréСƒ­U«VÒ¿?�Ì0 €¸ |þùçæ¦©–-[Ê€àA@� Ü¿ßÜ̬aölÙ²É_|A˜=D{Á´ €€vîÜ) 4�%JÈÂ… ­X"5!€ €€Å´[oƒ´[oO¨l `—@û‚ ¤aÆÎÕ0»�â;<( p9Ū�öëׯ›ÇØ]½z�žK IDATÕY¯†ÜóäÉcƒW1KD@ÀúÚ­¿GžT¸téRÑ'øèѦMéÛ·¯'—q €> hçW½qJ»Á<ا1¸@' avýZ¬O|Õ0»~}Α#Gâåj@�ˆÐžÞ~ûm)[¶¬Ì™3'¢ÖÆb@@ 8ÚƒãìÍ,Ú½Ñâ\@ÀOv ´Ï�;Wš6mêT›0a‚´oß>NE}|h… D;»Ç<¬h×5\5nÜ8g¹½zõ’áÇ'øJÑs>ùäéÞ½»T®\Y .,ŽÐ]‚s €x$@ Ý#&KŸôÕW_I·nÝL�ú}dïÞ½-]/Å!€„¿À¬Y³ÌÍSŒøè£�ÂA¬@�0ø×¿þe:³k˜={öì¦3;aö0ÛDÊE‚ °aÃѧƒëïYõk € à­�voÅ>�öÀ3 ðˆ€¿íwîÜ1]ÍOŸ>mæÚ¾}»”*UÊ+y Õ¯_ßyMBás=ñÞ½{æ1n0×ÕªUK´ƒf’$IÌß÷îÝk‚ÚŽCßt>zô¨<öØc�ÔöðáCùë_ÿ*£G�~äs±uv÷¶Þ“'OJ®\¹œc»×ê>iBksœ¯aûâÅ‹»\¾xñb©[·nœþî×dÈ�A´³{Ú´i½Ú3NF@�ø´‡÷+dþüùÒ£G³ˆN�:9ÿ=¼WEõ €VЛóõªÆ�ËÇlõr©@�ˆÐ0»vfÿöÛoM˜];³ë?9@pXµj•´mÛVªW¯.“'O@ðZ€@»×d¿€@{À‰™xTÀß�v�aÇŽ&`®]΋-êu8ÚÛ€¸Î™Pèûüùóã‹¿�Ð0Ò Aƒœõi½ßÿ½´iÓÆŒwëØ±£Œ3F’&Mêü”·õ*ЮA| ¯GGG»”­]Ü´îT©R9?~ãÆ Ñºu­1Onà¿#@ð^€@»÷fV¹Â&ÔzÞÿ}ó‡@�`,X°ÀÜpß AùûßÿŒ)™@þ#ðǘÎìf׿8Úm—0;/ @�¸ôw³ú{çÚµk»P�v-òòåËRºtig‡ü˜…k7™)RȾ}ûdçÎ��8äÎ�[:$É“'Oìr= €¸ hϗĬY³¤oß¾¦x vîÜ9<BÕ €a)°hÑ"s#Õ›o¾)Ÿ|òIX®�¢@7 ³ë{éÛ¶m3avíÌž-[¶p[õ"€Q`áÂ…Ò½{wóûâQ£Fqf¦B@�H Ðn½�$Ðn½=¡"°�€íúC¿vsžt ×@{‘"EäÈ‘#æ²ZµjÉ’%K\:©ß¼ySÊ”)ã<'¾íÕùg̘!É’%“ *˜7¯Ý�ñãÇK‡ÄÛzc ´»×s®»wïJ±bÅ\Ö¶téRI’$I¬K¸víšT©R%Î.ó±]T²dIÑ1Ÿ{î9¼êY" €Á Ð|óÄΨ¡…þýû›azõêe¾ïã@@ ˜úsºÞLU§NùôÓOƒ95s!€ `Ký=ƒ†Ù·oß.9sæ4�Ù ³Ûò¥À¢@¯æÌ™#}úô‘¦M›Ê°aüº–“@@ Ðn½×�vëí !€€ ¬hß°aƒé|î84LÔ¼yóxwýC{“&MD;jº‡¾µ»ŠѵÃflG¥J•Ìç^ýuqÏ´ëyÇŽE;£Å<¦M›&-[¶oë={ö¬ËãIulýÅ´c>÷ºtmåÊ•“Ý»w›Oŵ¶˜×=|øPV¬X!S§N•åË—Çi§õë ´Ç5¿ þ3`‰ €\€@{À‰ý:�~Ÿ7xð`3æ| mÛ¶õëø † €€'úó¼ÞP¥7íëÍþ € 8�ßÿ]Z´há ³ëï%²f͸ @ bô¨˜¯#ƒ Š˜u±@ž�öàY{:�vO¥8ð£€í~\^œCé›Ó§OŸ–?ÿüSž|òIÑ¿?ÿüó’>}ú8¯¹qã†hP\»¶?þøã’"EŠ`”šè9nݺ%Úµ];½ß¿ß×Ó¦M+Ï>û¬Y; €^€@{à�ý5ÃäÉ“eèСf¸?üPZ·ní¯¡@¯V­ZenªªQ£†|öÙg^]ËÉ € เþ~@;³ïرC^|ñEÓ™�0»ç~œ‰Ø]Àñ~b›6m¤oß¾vç`ý € àƒ�vÐ| �ö3< ›€]í¼@@�` h¦¶ïsMœ8Q†nÐnJÚU‰@�P ¬]»ÖÜXU­Z5ó6@ð¿€6‚ÑŸýavíÌ#Hàÿ @ âÆ�'#GŽ”N�:I�="n},@/@ =ðÆÞÎ@ Ý[1ÎGü @ Ýˆ � €$ @ Ýú/Ç/ž´Ò!C†H³fͬ_4"€D´À† äÝwß•*UªÈôéÓ#z­,@�Ph˜];³ïܹSråÊe:³fÅN0' Þ£F�’±cÇJ÷îÝ¥K—.ὪG@��h {¼“h·ÞžPØ@€@» 6™%"€ €@È´‡| â-`̘12zôhsΰaäiÓ¦Ö.˜ê@l!°yófiÞ¼¹T¬XQf̘a‹5³H@‚%pçÎÓ™Ýf×ÎìY²d Öỗ Aú~â¤I“¤OŸ>Ò®]»ZKA@�` h–´çóh÷ÜŠ3@¿ h÷%!€ €q h·î‹ÃÑAI+1b„4jÔȺÅR €€­¶nÝ*o¿ý¶”+WNfÏžm«µ³X@) avíÌþÝwßIîܹMgvÂì�gl@ ² $Ÿþ¹ 0@Z¶lÙ‹eu € íaMÔ ÚÅÇÅ €€oÚ}sã*@@ÀíÞhïÜ�?þX&L˜`&Ô`{ýúõƒ793!€ �€À¶mÛ¤qãÆRºti™7o^ € à�ß~ûÍtfw„Ùµ3{æÌ™ý02C €ØU oß¾2kÖ,:t¨¼óÎ;ve`Ý € �í‰À Ð¥Ú˰ €@|Úy} € €� Ðxcogøè£�ä³Ï>3—�;VêÖ­ëíœ� €@@vîÜ) 4�’%KÊ‚ :ƒ#€ ` ³kgö]»vIžÝœ>iÒ$©Y³¦§—r €Aسg�Ô©SG .,K—. êÜL† €@$ h˜];³ëÍb/½ô’h˜ý…^ˆ¤%²@� ´k×NV®\É{�!ܦF@ Ü´[o ´[oO¨l @ Ý›Ì@@ äÚC¾¦€?üPf̘aþ}Ê”)òúë¯[£0ª@@ �ýû÷K­Zµ¤`Á‚²|ùrŒ@@À�[·n™Îì„Ù}Àã@�Z¶l)ëׯ—Ï?ÿ\ªV­êÑ5œ„ €Ä Ðn½×�vëí !€€ ´Û`“Y" €„\€@{È·@úôé#sæÌ1…ðË¥Ðï €$,pèÐ!©Q£†äÏŸ_V­Z•ðœ� €.f×Îìßÿ½DEE™Îì3fD @¿ ¼óÎ;²eË™5k–”/_Þ¯c3 €ØC€@»õö™@»õö„Š@ÀÚm°É,@B.@ =´[гgOùòË/%I’$2}út©T©Rh bv@<8zô¨T«VÍðÖ®]ëÁœ‚ €�_ýÕtf×0{Þ¼yå‹/¾ ÌÎË@  6”;vÈüùó¥T©R™ƒA@@ ²´[o ´[oO¨l @ Ý›Ì@@ äÚC·Ý»w—… JòäÉM€¡\¹r¡+†™@ðBàĉR¥JÉ�;·lذÁ‹+9@{ h˜];³ÿðÃ&Ì®�ÙŸþy{£°z@�€ Ô­[×|ÍY²d‰)R$`ó00 €D®�vëí-�vëí !€€ ´Û`“Y" €„\€@{h¶ k×®²xñbI™2¥éÌ^ºtéЬ €ø pêÔ)©X±¢äÌ™S6oÞìÃ\‚ €€ý~ùåÓ™]ƒ…ùòå376f·ßë€#€Á¨Y³¦kºñ(P °2: €¸råŠyʈv—ݽ{wgbh@ÂW@ÃìÚ™}ß¾}òòË/›Ÿ ³‡ï~R9 ŽEŠ‘k×®É?ü`Þ—ä@@¼ Ðî­XàÏ'Ðxcf@p|A|øÐüo˜@@ ÿó?ÿcF%ÐÜÿ ùàÁy÷ÝweÓ¦M’1cFÓ™=_¾|�™ŒQ@’Àõë×Í“FÒ§O/{öì Ò¬Lƒ €@øüüóϦ3»#Ì®�Ù †ÏþQ) )zCÕ¯¿þ*”4iÒDʲX €Q€@{±=œŠ@»‡Pœ†øS€@»?5 @ˆ]€@{à^÷ïß7aö-[¶HæÌ™M˜ý¥—^ Ü„ŒŒ €@�nÞ¼) ”´iÓÊþýûƒ4+Ó € f×Îìú5RŸÎ¥�Ù ³‡ÇÞQ% iyòä‘ßÿ]Ž?.)S¦Œ´å±@‚�ö {9�v/Á8ð‡�v(2 € ¿�öÀ¼BîÝ»gÂìß~û­dÍšÕ„ÙsåʘÉ@� ܺuKòçÏ/©S§–C‡yv¦C@ÀºúíÌî³kgv}¢  €¡Èž=»üùçŸræÌIš4i(J`N@síÖÛ@íÖÛ*Bh·Á&³D@¹�vÿoÁ�;wL€açÎ�’#Gf×r € )úµ.**Jž|òI9zôh¤,‹u € �( ³kgö˜'™hgvÂì‰"åb@�Dh�]íd×@; € à‹�v_Ô{ �öÀú2: «�v^ € €@à´û×ø×_5�ÙwïÞm:²k˜];´s € Iú$’ܹsKŠ)äĉ‘´4Ö‚ €€O×®]376;ÂìÚ™=]ºt>�ÅE €øCà÷ß—|(gÏž•$I’DòRY €± h˜];³ëÏ‚… 2�Ù ³óbA°ŠÀÅ‹¥T©Ræ ’;vì°JYÔ� €a&@ ÝzF Ýz{BE `í6Ød–ˆ €! О¸-Ð_ i7¾cÇŽIáÂ…M˜�CâL¹@ <ræÌ)÷ïß—S§NIòäÉãhªD@ÀOW®\1? :ÂìÚ™=mÚ´~�a@H¼À™3g¤|ùò’={vÙ²eKâd@°¥�vëm;�vëí !€€ ´Û`“Y" €„\€@»ï[pîÜ9Ó™ýÇ”bÅŠ™0{š4i|�+@#�ܹs˽{÷äĉ’"EŠ0ªœR@Hœ€†Ùµ3ûáÇÍ�Ífúé§7(W#€ àg�ãÇ�KÕªU%Ož<²~ýz?�Îp € `íÖÛiíÖÛ*Bh·Á&³D@¹�vß¶àôéÓ&Ì®ÿ,Y²¤y´ü“O>éÛ`\… €@ DEEÉ�;wäèÑ£| Ãý£d@ß._¾l:³k˜½H‘"ægAÂì¾Yr €@`<(5kÖ”—_~YV®\ØÉ@"V€@»õ¶–@»õö„Š@ÀÚm°É,@B.@ Ýû-ÐŽìf×íeÊ”1�ÙéLë½#W €„·@þüùåÖ­[rèÐ!I�:ux/†ê@ð@@ÃìÚ™ýÈ‘#&Ì®�ÙyJ—pœ‚ �~øAêÖ­k¾f-Y²$$50) €„¿�vëí!�vëí !€€ ´[“oÞ¼)ëÖ­“äÉ“›b{ì1óþÅ‹Ϳ߿ß<Êî™gž±þb¨@› h÷nã�;fÂì.\�òåË›0»ã{!ïFâl@Â[ `Á‚¢ï ìß¿_Ò¦MÞ‹¡z@øé§ŸLgv ³-ZÔtf'ÌÎË@ÀÊ;vì�† J©R¥dþüùV.•Ú@@ÀÂÚ­·9Ú­·'T„6 ÐníMÖΤڑôêÕ«ÎBW¬Xa7®oŽ8Ž–-[Ê”)S$I’$Ö^Õ!€ `Sížo¼>R^Ãìd¨T©’ ³ó=Žç~œ‰ Y… –ëׯ˞={$}úô‘µ8Vƒ €@ ýP;³=zÔ„Ùµ3;O'á%‚ `u�-[¶È;ï¼cšrÌš5ËêåR €XT€@»õ6†@»õö„Š@ÀÚ­»ÉúËj}<]ÌCïò/Y²¤<|øPêׯ/‹-r~zûöíæî@°ž�vÏöäÀ�¦ßµk×Ìh>ÿüsÏ.ä,@"T X±bråÊÙ½{·<÷ÜsºJ–… `w�K—.™0»>­K¿öigvÂìvU°~@ <Ö¯_/ÚxŒ÷2Ãc¿¨@« h·ÞÎh·ÞžPØ@€@»57yïÞ½¢]Øb[·n•²eË:?´oß>)T¨�óï5kÖ”èèhIš4©5EU €ØX€@{›¯7óigö7nÈ믿nž>Ã� €€Ýô¦v ùíܹS^xá»s°~@�p³kgöT©REàJY €@$ ¬\¹RÚµk'ú{ÚI“&EâY €A€@{�½œ‚@»—`œŽøC€@»?ý;ÆÉ“'%W®\.ƒ.[¶LjÕªåò1íÒ^·n]bwû÷ï— ø· FC@�D h�ŸP»Îj˜ý×_å—?‰~µ1 €@$ ”.]ZΟ?/Û¶m“,Y²DÒÒX  €rñâEÓ™ýøñãR¼xqÓ™�0;/ @�pX²d‰téÒÅüÎvìØ±áT:µ"€ €€…´[h3þ[ �vëí !€€ ´[k“oÞ¼)E‹•Ó§O; ûä“O¤k×®±ºaÃó;ÇÑ«W/>|¸µE5 € Úã~hÇY ³ÿöÛoR»vm7n¯@ø¯@¹råäìÙ³¢OmË–-. €DŒÀ… ¤E‹Î0»vfê©§"f},@ÀóçÏ—=zHÆ eäÈ‘öX4«D@¿ h÷;i¢$ОhB@¼D ýòåË¢o>'Ož\Ò¥K'Í›7w†¸¼¯Ð>WÄÖq]Ã]S¦L‘¤I“Æ qëÖ-)T¨�3Ÿ9sf9pà€¤M›Ö>p¬@�0 Ðû&i·Yý~ç÷ß—zõêɘ1cÂ`7)@�à T¬XQN�:%›7o–œ9sobfB@ €f×Îì'Nœ�%J˜Îì„ÙÎÐ €˜5k–ôíÛWÞyç:thÀæa`@ˆlíÖÛ_íÖÛ*B"Ð~ìØ1‰ŠŠ2z5kÖ”èèè8Ù‰!¾{÷®$I’DR¤H‘˜aüzíü!Lðà�<ñÄ^�=wî\iÚ´©óOÃé½{÷–�?þØy�vm+[¶¬Wss2 €V€@û£¾ú=‹vã»ÿ¾Ô¯__F�ØM`t@ÂP J•*&ì§OhË�;w®€’@p8þ¼ùYÐf׿8O>ù$L €„¥ÀçŸ.ƒ ’–-[Ê€Âr � €¡ Ðú=p¯€@»õö„Š@À�´Ÿ�FáÃ� €± Ô¨QCô} ½™=þü0!€ ¶ÿüç?MóG˜];³{û”Ó°]<…#€D¬ÀرcÍ“'µ›6gã@@| Ðî‹Z`¯!ÐX_FGb°j ]ƒéuëÖ•èèhSwÕªU¥X±bòÑG=²Ž©S§J«V­ÌÇïܹ#Å‹—#GŽ˜¿gΜÙü»vK�ïÐGÁ 8ÐyŠÕK•*åü»ÖÓ¼ysÓ…]�Â… ›ú>üðC�^YZÇáÇ%uêÔ�œ¯Áø¨¨(—�ëÇòäÉãÑØîkΑ#‡ìß¿_žzê)�®ç$@¼�öÿ3^³f�¼÷Þ{æßõ{«¿ýío�Çg@ÂX@Ÿ|§?ã/_¾\ ,Æ+¡t@; h˜];³ks}ß];³f·ó+‚µ#€‘#0räHÓ ­G�Ò©S§ÈY+A@�  h*·G“h÷ˆ‰“@ÿ X5ÐîÒŽ¹jíÖ>~üxÑÚµk{åÊ•åÅ_4§¸w+×_ü.Y²D’&M'܃¤^½zÎð¼†ß5|ž%Kç5wïÞ5�z Çç͛ט×ôü1cÆHÅŠM`]›ª�äcv‰×óæÎ�+�7v©Cƒò;vté:¯oxŒ1Âã�¾wïž”(QB8`®Q ħM›Öã18@+@ ]dåÊ•æÑ»z¼ûî».7VŸÑ@_�:uêÈž={déÒ¥ææz@ÂMàܹs¦3»#Ì®�ÙS¦LnË ^@b:t¨LžÎ;æ›êgΜ1w?4h S¦L‘4iÒ¸|Ê=$¯ŸÔºÛ·oïrž†ßc|Sb>§k�m®øÑ¡Cg(ž@;ÿµ#€ `=»Ú—-[fnâÓ£uëÖ?åÆz;IE €WàÍ7ߔݻw››æõF{@ÂI@ÃìÚ™]ßó~õÕWMgvÂìá´ƒÔŠ ��À€Ì×7}¸ÞÀÅ� €ø"@ ÝµÀ^C =°¾ŒŽÄ*è@{£F�dΜ9’$I¯v`Æ RµjU—k4<®c%K–,αƎ+]»vu~^ÃSÚ¥=¾C»™GEE9OiÒ¤‰Ìš5Ë¥f_êùꫯ¤~ýúÎqc ´k7š˜on´lÙR¦N�*ŽÐ›§h�;w6�³Óƒ@»§jœ‡ €@ðìh_¼x±óû³¶mÛÊ|4ÿ«w94Ü•5kÖGBê“&Mréf®!míšþì³Ïƹx_ß w̧'ê5ùòå‹,::ZôÞŽcüøñ¢Ïc±Õ³oß>ɘ1cœcÏ�;×å� ÷@»vq¯\¹²|ýõ×Î1Ö¬Y#ÕªUózƒ ´{MÆ €UÀ®�ö… J÷îÝ�µ~Õ«W¯ º3 €á.иqcÙ¶m›Ì›7ϼÇÂ� €@8h˜];³ëSQõë—v®%Ì;G� €Þ è{Ÿúè¨Q£\�y;ç#€ €€½´[oÿ ´[oO¨l àK ]»ˆOŸ>Ýo:»vírylvlÁô¡C‡&ØÍóÞ½{R¤H9r䈩ÍÓNåǽ{ÞqlÙ²EÊ•+çü{lõL™2EZ·n¯ÁÒ¥K¥nݺÎs܃ò—/_v Äk½‡–ôéÓ{e«õ5oÞÜt•÷fÝ^MÂÉ € �(;Ú¿üòKéÙ³§qëÒ¥‹3Øž(H.F@Àfo¿ý¶lݺUfÏžíò^…ÍX. €@ œ9sÆ4žÑ0{™2eL˜ýñÇ£P* €ž têÔI´yš>I»víÚž_È™ € €@ íÖ{9h·ÞžPØ@À—@»vל8q¢ßtvïÞ-E‹uާÁô%JÈ�œó¤Óº¾Qž#Gç55kÖ4o $Mš4ÎZ5 ^¿~}Y´h‘9'UªT²ÿ~Éž={œõè9<Ï’%K¼î]ÝW¬X!Z“ãX½zµÔ¨QÃù÷&Mš˜Pz’$I¼²½{÷®¹!ÀÛ ¿W“p2 €$JÀn�v}Ì®ã†ÁnݺI×®]åÇÅ €ØU@o`ß¼y³y]ÅŠíÊÀº@ÂD@ߣ×ÎìúOÂìa²i”‰ �(�6mÚˆþÎ÷³Ï>sù½o¢åb@°��vëm9�vëí !€€ | ´wîÜÙÜeî¯cß¾}R°`Açpú8Ò˜�r ©k =eÊ”ñN©�Õ+T¨àn—@'жm[Yµj�¯O#W#€PàÔ©S¦3»†Ù5ȧ7:'Ož<€324 €Ö¨_¿¾|÷Ýw²`Á)Y²¤u £@+íÖÛ.íÖÛ*B:Ð[ÇóøX} ¦k@¼D‰ràÀ3¼§ñ#Fˆv€wkÖ¬qéxæk= åïÞ½+ÅŠsÚK—.-›7oöé�~÷òÞšÛàeÎ@¹@¤ÚõI9}ô‘qîß¿¿¹Ñ�@/СCY¾|¹L˜0Aôç}@¬$ avíÌ~îÜ9ÂìVÚjAš@íÚµeïÞ½æ à… Ú¼L„ €‘%@ ÝzûI Ýz{BE `«Ú} ¦Ÿ9sFräÈáÜ1}¬é¦M›$Y²dqîâƒÌ›ìÛ¶msžsìØ1É“'�óï¾Ö3fÌyÿý÷�ã¸åýÙU½sçÎ2nÜ8ç\óæÍíÒÎ� €Öˆä@»ì>þøcƒ=xð`fà@@ÿèÏüŒøôÓO¥N�:þ”Q@ðƒ€¾Ç­?ÿýóŸÿ”òåË›Îìñ½ï‡)@Ë T¯^]>lž¬•?~ËÕGA € Ú­·OÚ­·'T„6°Z ýÂ… ’%K§¼'Át=ùàÁƒR @çuÚtРAñף¢¢œçdΜÙtLO•*•óc¾Ô£]Ýõ«3fÌpŽ£odäÍ›×ùwÚµÓ{©R¥œ�éu÷P¾ ^Æ,@Ë Dj ]Ãuÿûß�ÿСCå�wÞ±ü^P  €á$ 7Ë/Z´H>ùäyóÍ7étjEˆ`�üѼN˜=‚7™¥!€x$P¹reѯ‹6l�ܹs{t '!€ €îÚ­÷š Ðn½=¡"°�€Õí»ví’%J8å{õê%ÇOp'æÎ�+M›6už7eÊiݺuœ×iè¼cÇŽ2qâDç9úèî%K–HÒ¤I�s¯Ç“ ¼†Ì‹+fÂñzhP^í©S§vŽëh�mîýŸ܃üÚ¥þСC’2eJO.ç@’@$Ú5X§ôÐïך4i$M¦A@À>ýë_eÁ‚æ² Øgá¬@À²ÚÓÎìçÏŸ— *˜Îì1ßS·lᆠ€@´9Û¹sçdëÖ­’-[¶ÌÀ� € `íÖÛeíÖÛ*BX-Ð>{öl—ΞToܸq‚;ñÕW_Iýúõ�çM�:UZµjçu«W¯–5j¸|þ£�>’>}ú¸|Ì—zΜ9#,w5kÖ”èèh—7õÝí&L�öíÛ'¸N÷FŒ!úw ��z= € €@`"-Ð>räH7nœAÓoذa`@› èÏüóæÍãæ1›î?ËF¬&pâÄ Ó™ÝfÿÇ?þ!I’$±Z™Ôƒ €@дQÛO?ý$;wî”^x!hó2 €D–�vëí'�vëí !€€ h?vì˜DEE=½+}óæÍwhéܹ³3¥×ïÛ·O ,˜àN¸wh×ùÒ¥K%Y²d�\«õTªTé‘�/[¶L´SzÌ×zô‘rU«Vu3dÈéÛ·¯Ë¸î�öN�:ÉØ±cÅvKpÁÿ9Á½¼^³{÷n)Z´¨'—s €Q ’íÚ�Ýñ”›Ñ£GË[o½DI¦B@À^|ð�èÍöC‡ui`/V‹ ` ³kgö .HÅŠMgvÂìVØj@¥@¡B…ä矖={öHúôéCY s#€ €@ h·Þæh·ÞžPØ@ �ö[·nÉ®]»Lˆý‰'ž½3Ý“ãþýûRºtiÊÖ#C† røða�~ø?xð (PÀešþýûK×®]%mÚ´æãzw¼v6l˜ûêի暘Ï:tHòåËçÃ×z&MšäÒm}Ñ¢ER¯^=—ÚÜí¤_²d‰ÇÁLøµk×vŽ›;wnQ‡Ç{ÌnÎA@� DJ ]Ãt“'O6rŸ~ú©Ô©S'ˆŠL… €€ýô½ í~;xð`"ä@@ Ç�7�ÙavýÚäMs–PÔÌœ €C@·|ûömó{æÔ©ScJæ@@� Ðn½M%Ðn½=¡"°�@ í¾²]¾|Ùtc× ¹nÿúë¯cí²î>GlÝÊç+VLÎ�;çW?^¸paùå—_äôéÓæ4 ¸kgyGø]?æK=>4oìϘ1ÃYâþýû Û»Ú5j$sæÌñ¸£Í½{÷Ì�Ž0¾N¦]ê7nì+?×!€ €@"!ЮAºiÓ¦¥ &<òd›ò14 €¶4h�|þùç2`ÀiÙ²¥mX8 €@è4Ì®7U]¼xÑ<ùT;³fÝ~03 €€µråÊ%üñ‡è“LR¤Ha­â¨@ÂF€@»õ¶Š@»õö„Š@ÀV ´ïÝ»×ÍG§N�dìØ±¿9¾aéZµj‚»¦AùùóçKݺu�ÝàË–-+›6mr ÏûR�{°>¶ ¼xçÎ)^¼¸9rÄÔ›#GÙ·oŸ¤J•*Áúõ„… Jƒ œçêõzçÊ”)=ºž“@@ ¸áh×�†ôøì³Ï¤F�Ád6@l*0dÈ™2eŠôë×OÞ{ï=›*°l@�P hmàâ³kgv@øÿY³fmxvöìY�—ᇠ€¸ h·Þk‚@»õö„Š@ÀV ´¯ZµJjÖ¬éT×7Ç›7oîÕ.hÇò.]º˜ÎîËG�-Ú ýÁƒ¦üîÝ»ÍiMš4‘Y³f¹¼ÑàK=7oÞ”¢E‹:;¿ëz–.]úH—y}cCõÑÑÑf~ ²>|X²dÉ’àzÝçÐ –-[F—Ôå8@�Ð „s ]t3gÎ4xS§N•jÕª…’™@°™À°aÃdÒ¤IÒ§Oi×®�ÍVÏr@B) avíÌ~éÒ%©\¹²ó&çPÖÄÜ €XIàþýû’3gNó{`ÇSÁ­Tµ € €@øh·Þ^h·ÞžPØ@ÀJ�vrÿôÓOrùòeó‚þI›6­<ÿüóþœ"Ñc >ÜüBÚqlܸÑ<²5¡C;¤<ØyZ\¡ù„Æáó € <�p ´÷îÝ[æÎ�k ¦OŸ.UªT 3!€ €€Œ1BÆ�/={ö”Ž;"‚ €@PŽ=j:³f 7“ €„©€>‘;**Jžxâ ÑÁ8@@_´û*¸ë´Ζ‘@�8"5Ð[¾eË©P¡‚³Ô^½z‰†Üã;¶mÛ&eÊ”q9åĉ’+W®pX25"€ `[�p ´÷èÑCæÏŸ/I“&5aöŠ+ÚvÿX8 €¡5j”Œ;VºwïnžHÇ� €@ 4Ì®�ÙµiŒÞÔ¬?r € €À£úTí‚ ÊÓO?-úq@@ÀWí¾Êî:í�³ed@ Ní¡{qܺuK *ä|]æÌ™åðáÃ’:uêX‹ÒŽóú¦ÈÕ«W�ŸŸ7ož4jÔ(t‹`f@ðHÀê�výäöíÛräÈùõ×_eöìÙríÚ5yì±ÇÌcåË–-ëÑ:9 @ü+0fÌ=z´tíÚUºuëæßÁ @7ý™P;³fç¥� €@ÂW®\‘bÅŠI† äûï¿OøÎ@@â Ðn½—�vëí !€€ ´‡v“ $t±fÍ©V­Ú#EÝ»wOÊ•+'»wïv~N»¦~üñÇâÈ…v%ÌŽ €ñ „:Ю7Òi0Aÿ©áõ .˜?çÏŸ7ÿŒíК58G7X^Û €„N`ܸq2räHéÔ©“èû €J@fÔÎìÚX¥jÕªòùçŸj*ÆE@ "ô½ÕÒ¥K‹6-Û¾}{D¬‰E € €@h´‡Æ=¾Y ´[oO¨l @ =´›¬�oÍ›7¯³ˆš5kJtt´$MšÔù±‡JóæÍeÖ¬YÎ�5hÐ@æÌ™#É’% í˜@<F Ý½ËúÎ�;Mm;vìH°Æ¬Y³J¶lÙL¾'N˜æô{�©S§Æz³]‚r €øE`âĉ2|øpiß¾½ôîÝÛ/c2 €îúó¤vf×0ûk¯½&Ó¦M @8uê”T¬XQräÈ!_ý5^ € €€ÏÚ}¦ Ø…ÚFËÀ €@ÜÚCûêРXýúõeÑ¢EÎB´ {Ñ¢EÍßõó½zõ2ÝØGõêÕeñâÅ’"EŠÐÏì € à±€?í¾tY�Y`ùòååé§Ÿ–W^yÅùO ±ë=ô{’¯¾úJÒ¦M+7oÞ4;tè�¤N�Úãur" €øW`òäÉ2tèPiÓ¦�ôíÛ׿ƒ3 €ÿÐ0»vf¿rå av^ €x! �ËôÉÛ/½ô’¬[·Î‹+9@p Ðn½W�vëí !€€ ´‡~“÷îÝ+… vR¶lYÙ´i“éÒÞ¿2dˆósÚ™}ÆŒ„ÙC¿mT€ €€WžÚõ— ·oß}Իؿûî;I’$‰W]ÖcÖ5À^¡B…k­[·®,]ºT2dÈ Ý»w77Ô•,YR,X�൜€ €�йƒ–V­Z™÷8@ð§€ÞĬ�Ù5Ì®�<}J €x&°ÿ~©U«–(P@V¬XáÙEœ… €Ä"@ Ýz/ íÖÛ*Bh·Æ&ëcÃ?þøcg1Ó§O—•+Wºtn0`€|øá‡&èÎ� €á%à´ïÙ³GŽ;fÂêZ¿xñ¢\¸pÁùÏ„V•P—õ„®�íóo¼ñ†,_¾\2fÌhþ9kÖ,;v¬¼ÿþûæ €„Nà‹/¾}?@ƃ ]!ÌŒ qf×ÎìW¯^%Ìq»Ë‚@‚! OÝ~óÍ7Í“·õéÚ € €€¯Ú}• ÜuÚgËÈ €@œÚ­ñâø×¿þ%—.]r£ÝTïÞ½+úq=’'O.™2e²F±T� €^ ìÛ·O *äÑ5Y³f•lÙ²‰/]Ö=š ÆIþù§é ´zõjÉ’%‹,[¶ÌÌ«µjÍóçÏ—R¥Jy;,ç#€ €€fΜ)ýúõ“fÍš¹<ÁÍ�S0 €€ ¬r˜@â˜3gŽôéÓGš6m*Æ à @�D 8pÀ<ùƒ0{¢)@Àæ›7o–æÍ›K… DoFæ@@| Ðî«\à®#Ð8[FFâ Ð΋@¼€#Ð^¹reÙ¸q£™°k×®òÉ'Ÿ~r·îܹcÂì›6m’_|Ñ„Ù_zé%sÖ?þñlxíµ×dÚ´iA¯� @@ÀU@»üõìÙS5j$#FŒ€@�D h˜]o®¾~ýºT¯^]&Ožœ¨ñ¸@; ¬]»VZ·nÍ{©v~°v@ü$@ ÝO�~†@»1 ðT€@»§Rœ‡ €ø.à´Ÿ±«fÍš2iÒ$;s°v@ü*0sæLéׯŸ4kÖL† â×± @ì%@ ÝzûM Ýz{BE `í6Ød–ˆ €!ˆ/ЮÅiçô¶mÛÊíÛ·M˜]CínOìqáÂÓ™]C Å‹7aö 2Ä:l�:uD»ÀŽ5JêׯŸØ©¹@ü °zõjiÓ¦�T¯^]&Ožì‡@À.{÷î5�Ù ³ÛeÇY' €@°¦M›&ƒ–V­ZIÿþýƒ==ó!€ €@ h·Þfh·ÞžPØ@€@» 6™%"€ €@È ´k�·nÝ2¡ö]»v™zµcz—.]|®ýìÙ³&Ì~èÐ!yõÕWeÙ²e’.]º8ÇK›6­üòË/²}ûvÉœ9³Ïór! €øO`ݺu&ñÚk¯‰†%8@ðD@ÃìÚ™ýæÍ›<åÃ0ÎA@À�‰'ÊðáÃÍSµz÷îíÃ\‚ €üŸ�vë½´[oO¨l @ Ý›Ì@@ äžÚE~úé§¢ôЮé_|ñ…éÚîÍqòäIyã�7äèÑ£R¶lYÓ™=Mš4qñõ×_KÅŠ%oÞ¼²fÍo¦â\@ ÀÆ�MwÝÊ•+›ï 8@HH`Ïž=ækaö„¤ø< €‰І$£G�–®]»J·nÝ7W#€ €€­´[oû ´[oO¨l @ Ý›Ì@@ äÞÚµØõë×KÏž=åöíÛ’-[6Y²d‰¼òÊ+­ãøñã¦3û�?þhBêÚ™ý©§žŠ÷Ú�Ê Aƒäõ×_—Ï>ûL’$IâÑ\œ„ €�ЛΚ5k&*T�™3gv2FG{ ³kgv}ú–þ\8a„°_ @@Àª#FŒ�ñãÇ›÷q;vìhÕ2© @Â@€@»õ6‰@»õö„Š@ÀÚm°É,@B.àm ] ¾pá‚´k×ÎtY×C»²j0!¾ãðáæ3ûéÓ§¥J•*¦3{Š)\¿†ä¶lÙârÞ| ¯¾úª(P Áë9@#ðÍ7ßHÓ¦MÍWæÌ™˜I@ "~øáÓ™�0{Dl'‹@�!C†È”)S¤_¿~òÞ{ï…AÅ”ˆÿ�½û€Žªêþ¸¿±£tE¤-iÒ‘Þ}hÒ•^D!�‚4iÒ E:D�^äAzé R¥¤(Å÷Ýç}gþDH2÷ÎÜ™ùžµ²°Ì=gŸÏ �L~w_@§ hwÞÉhwÞ™P„€�ö8d¶ˆ €~ˆO ÝUtÿþýeÒ¤Iæ_5ÐþÕW_I²dÉþ±§Ý»w›|¿ýö›é´®�ÙŸ|òÉGî]ÃÉ“'äëhîÚ1ž� €¾ظq£Ô¯_ßü}ú<²‰½•0; €º�vç� �vç� !€@h�Cf‹ € àwoíO=õ”Ìž=[zõê%×®]“|ùò™níú«kècå5Ì~úôiyóÍ7M˜Ýµæ£6¿`Á©Q£†+VLfÍš%çÏŸ ÏéÇôéÓxy£F�L°N?R¤Hñ¨%øÿ € O�-[¶H�:u¤hÑ¢æï @îؾ}»éÌ®aöjժɈ#@B@tîÜÙ¼ª�@4hàƒY@‚U€@»óN–@»ó΄Š@ ´‡À!³E@𻀷�vÝÀÞ½{¥C‡²oß>Ó¡];µk¾­[·š|gÏž5á ¨Çe„‡‡Ë°aÃäã�?6÷�#GŽ˜p{×®]ÿuÚ?üÐpâ‰'â²<¯E@�‡hH±fÍšR¨P!™7oV €xèŸú}áÕ«W ³ó¹� €€�Ú·o/sæÌ‘¡C‡JíÚµ}¼:Ë!€ €@0 hwÞihwÞ™P„€�ö8d¶ˆ €~°"Ю›Ð�‚vj×”è¨W¯ž¬\¹R.\¸ µjÕrÿ÷¸l8þü²k×.™9s¦/^ü‘—êk5à®�‡þmhw"íÞ³‹ü#'æ € ð��;wš€¢þy½páB„@p lÛ¶Ítf×ï«W¯.ÇG@|(ЦMó¤Ì‘#Gš†# @@ ¾Úã+gßuÚí³ef@à_bü�ˆ € €€Í‡Šó O=õÔ?®™={¶éÖîo¾ù¦,Z´(Îs;vL2fÌ(‰'6àã:îܹcÂíú1jÔ¨½¼ÿþ&àž)S¦¸.Áë@i�Ý»w‹þ9Ÿ'OY¼xqH[°y@�ÿÐ0»vf¿víav>1@ð“@‹-déÒ¥2nÜ8©\¹²Ÿª`Y@íÎ;EíÎ;*B� ЇÌ@@À1VÚ7mÚd 7oÞ4{K–,™DFFš C\ÆäÉ“MG¿Š+Ê„ âré_{ñâEnß°aƒL›6í�¯iР� ·ëÇ /¼àõšL€ €@0 è go¼ñ†äÌ™Ó% €lݺÕ|?øÇH�5dذa  € €€ôÏãÕ«W‹¾ÇZ®\9?TÀ’ € ,Ú�w’Ú�w&T„[fFl Ðn +“"²sçΕO?ýT’&M*W®\1ª×®]²l<ð\ŸÓº“�;wþã míŠý }\°žúõë»î/¾ø¢ßk¦@ìøí·ßÌ£ëõú({ €@è è÷wÚøáÆ�R«V-ùꫯB�#€ à@�Ê•+˾}ûdéÒ¥¦1 @ˆ¯�öøÊÙw�vûl™°E€@»-¬LŠ@È há“'OJdd¤ôêÕKŽ?.‹-"œ²ŸÁ±ñ¾}ûÊäÉ“%C† &Ô®]³c;\ÝÜ]¿ºnðˆMgNíà®Ávׯzƒˆ~¤I“ÆüÊ,íÌÞ²eKS´†úôéóÈ hÀÁr¨^½ºùÚzÿçŸþ÷… Ê�!C¤N�:�œÓ×/П†Û5ä®u>h´hÑÂpæ™g|]"ë!€ `»À©S§¤xñâæÉ=ú4 €@h è×~íÌN˜=´Î�Ý"€†@¹råäСC²zõjáé’�qfT‰ €€S´;ïd´;ïL¨x¨�v>A@À*W'ë—_~Ùt¬—aÆ™àf÷îÝ­Z†yð‹À[o½%û÷ï ÏŸ?ß²4à®­ï¾GEE=r µk(J»ÆhWwíòîêôþÈ‹y�O~øáùè£�ÌšaaaÒ³gÏX¯¿lÙ2iß¾½\»vÍÜT¡Ÿ1}›ùäp/T¨P¬�x! €N8sæŒ)RD^zé%Ùºu«“K¥6@‹4Ì®ï�ݼyÓ1OÖ²x‹L‡ €@@ èÓ´´)‡>M+F- ÷Dñ € àíþqتÚ�w&T„4ü~åÊ•,Ÿ;wnóûŽá,}JÅÇlŠÒNäñ¹ÁG?ÿš5kf}«C;µë×YýÚZ¶lÙ€îöªA|íà>bĈœ>òWÃíú‘5kVg.Õ € K�sçÎIÁ‚%eÊ”²cÇŽX^ÅË@]@¿ßÑÎì„Ùý$©@ ˜ôæc½ Yo>Ö›� € €@|´ÇWξë´ÛgËÌ €€-ÚmaeRBNàþîì.€dÉ’™à­:säÈr.l8¸¶lÙ" 64›ÒÎÒz‡?‡««»†š{÷îmJ=z´T¨PÁŸe±v ½Á@»�ëhÕª•téÒÅ+½‘bÒ¤If ´'MšÔ< £iÓ¦>¹É«âcqñÕ«WÝÝÛ§L™òÀ+êÖ­ë¸ó¦X ò@G\¼xÑ]‰C’ ;^`øðá¢z³ÆÑ£Gͯþááá&Ô¬�d¦OŸîïrXÿÿpÝè£ÿúá‡JçÎ�-±™={¶ ¯ëÓ\cüøñR©R%KæwÒ$Ú™^»·kdþüù,M;×»:¸?ûì³N*ŸZ@p èM¾yòä17£íÙ³@� Ðïc´3;aö ?h¶‡ 9sæ”?þøÃ<3Q¢DA±'6� €þ Ðî÷‡­J ÝygBE €ÀC´ó ‚Þ ¸B›yóæýG·AíN\£F Ó�]»´3� ˜Ç�jç–�;wúuKÇŽ“Œ3šx‚_�Âcñ™3gJ·nÝÌkÛ¶­´k×Nžzê)Ë Ü»w¯tèÐÁü�EGtt´$I’IJù�:‘î[Ãíú±jÕª”Y¬X1w¸]oð` € à GhHBî?¿�Ru €X+ avmìð矊>aêË/¿´vfC@K²dÉ"úóòC‡Yú®¥E2 €„�vç�vç� !€ ÐÎ'x#põêUÑîìúëš5kÌ?ß?´ƒµv$\»v­¤K—Λå¸Gèçû[o½%§N�2A刈¿ÕU¶lYó{‹§ øíþ±°vÉïÑ£‡ùïÚ=¿M›6柭 ´ë|úy¨�Ú5·téRçø°’M›6¹îÛ¶mûÇÊ+VtܳgÏîÃÊX @O�›7oJ¶lÙ$a„ràÀx@‚Tà§Ÿ~2�Ù ³é³-@� pÏ~ûí· Ü›B@ß h÷�ulW"Ð[)^‡8D€@»C‚2P�áÇ‹~”.]Ú„j44h;eÊÓ­X¨Ç@ öïßoBí:æÏŸ/Õ«W÷ù¶ô÷œÚ'N,ëÖ­ ‰Ý>GŽã‚S§N•Þ½{›«´ƒz«V­Ü3XhwM¬ÁöPèÎþ¨£ÐÎ·Ú Q»·GFF>ðåµk×vÜÓ¤Ió¨)ùÿ €X& ï½h×?ýû€výc €Ÿ€†Ùõ=0ýš_¯^=ùâ‹/‚o“ì@� à{µ ;P¶ƒ €€Ÿ´ûù°<�vç� !€ ÐÎ'ÄW 6ÝÙuî H�5$G޲hÑ¢ø.Çu8N@C³ýúõ} �>¡ _¾|>­1cÆŒrìØ1nñ©ú¿/6yòdéÛ·¯yA§N�¤E‹/¶+Ðî�í;® }‚‚†Ûõcîܹ¬¯iÓ¦î€{¢D‰· B�»wïŠþÝíñÇ—£G�ÏÆØ  €€X¿~½iâ ïµ×¯__Œ  €€€6ÉÈ™3§è{ƒú$L € à��voôì¹–@»=®ÌŠ €8N૯¾ýxXwvWÑø½råŠ ¦K—Îq{¡ â+о}{™3gŽ ³k¨]?×}1zõêe:�§M›V6mÚä‹%Yã!ãÆ�s‡Ù»wïþ�0;xþЧ*¸î+V¬øGAEŠq‡Û‹+æÿ‚©@ èxŒ}Ð)BŒ€†Ùµ3ûíÛ· ³ó9� €@€ \¼xѼ·Ÿ"E ÙµkW€UO¹ € à4íN;íÎ;*B@,Ðîì%J”ýUC¼eÊ”yèúƒ½)S¦HÏž=%,,Ìòz˜ èï�ºuëšî-úy®]Ûí—/_6>õ×™3gJñâÅí^’ù"0zôh0`€y…Þh ]¿Îزe‹;à®ÿ|ÿ(_¾¼;à®]š €x+ ÓNíÚ¡];µ3@_àÇ4�Ù5ÌþÎ;ïÈ AƒSì@�8sæŒh£‹—^zI¶nÝB;g« € `‡�v;T½›“@»w~\� €„@\º³ë†,X 5jÔ�\¹rÉ’%Kb�‰@löîÝkBí×®]3�v ¶Û9\7ˆhéY³fÙ¹s?B`äÈ‘îGÉþùçòÞ{ïa€ׯ_w‡Ûµ‹»vs¿Ô¬YÓpçI#xÈ”Œ8@ K–,ò×_É¡C‡ä©§žr@E”€ à�€†Ùõûó;wîf÷’k@ð£Ào¿ý&¯½öšhøì§Ÿ~òc%,� €Á @ Ýy§H ÝygBE € €€¥qíÎîZ�vç� !€ €– Ä·;»°`Á©Q£†äÊ•K–,YbYML„€“ôÍo}¼zõê2þ|KKsýJœ8±lÚ´‰®Ð–êÆ~²ÁƒËÈ‘#Í_~ù¥Ô­[7öóÊ ضm›;ிïÚÕI;·ëGîܹƒbÏl@À{��ÀÊ.ê3f”cÇŽÉ�!C¤N�:NÙrHÕ¡a…Ñ£G›=ë×ÃZµj…ÔþÙì?nÞ¼é·ëŸm{÷îýÇ‹ôWÀ=ÆYp"€„˜€$.^¼(˜H‘"Eˆíží"€�/°fÍiÒ¤ aöÀ?Jv€ €€[`ëÖ­R»vm)R¤ˆÌ™3@@À+í^ñÙr1�v[X™@ð¿€7ÝÙ]Õk¨oáÂ…&èæÿMQ6hÇæzõê™™õÞeÊ”ñz Æ÷îÝ[2eÊ$ÚŽá{�¾}ûʸqãÌÂ#FŒ�jÕªù¾Vt¼ÀÙ³gÝ÷™3gþ£Þ,Y²¸Ãír×½ €¡!P°`A9wîœìرCR¦L›f— €@�è÷öÚ™ýï¿ÿ–F�Iÿþýƒdgl@�Ðøé§Ÿ¤AƒòÚk¯ÉôéÓCƒÝ#€ €€×Ú½&´|í–“2! € à o»³ë.&OžlºYåÊ•K–,YâŒ�Q6¸~¿hXuçÎ�’!C†x¯¢]ÙóçÏ/—/_6sèÍ ü±$I’$ÞsraÜôf‚‰'š‹F�%o¾ùfÜ&àÕ!+ð믿ºîús¯@�÷Ç{,d­Ø8 ìÚñïÌ™3¢_zé¥`ß.ûC‚F`õêÕæ½,ÂìAs¤l@·€þ9¯7­•+WÎüüŠ� €x#@ Ý={®%Ðn�+³"€ €~°¢;»n@¹É“'7{Ù¸q£¤K—ίûbqì¨[·®lÞ¼YòåËgBíñú†ú”)SD¿þí·ßÜÓhÈZ¨Î°W G�îfŒ;VÞxã {dö Ю¼úçŸ~lذá{Õ':hçvýxõÕWƒÚ‚Í!€¡&P¼xq9uê”èÓ|Ò¦MjÛg¿ €@@ ¸BnZü»ï¾+ýúõ È}P4 €ùä™5k–<ñÄ¢7"h��€¯>ì¸/^¼øËë×’%J˜�’%KÊã�?îëY@ åÊ•“C‡™'ídÉ’%WòR@_ ¬\¹Rš6mj–$ÌîKyÖB@À÷Ó§O—Î�;Kƒ dàÀ�¾/€@@ ¨´;ï8 ´;ïL¨@ˆ·€ÕÝÙ]…T¯^].\(C† ‘:uêÄ»>.D P´Ë‹v{Ѫ†Ú5Üþ°¡7~äÏŸ_Ž;öÈß'gÏž½ñDß|w�HÆ …ÇQuê sçÎ57hgö×^{ÍQõQLè ìܹÓp_¿~ý? J•*å¸ÇöÆ™ÐÕdç €€ï*T¨ �+VH¶lÙ|_+"€}úîF¨@!@ ÝÇàQ�vç� !€ €ñ°º;»«WçéŠ+Ê„ â]"(zsHݺueß¾}æÍqíúý°Ñ«W/ÓÕ½X±b¦SxlƶmÛL°ý§Ÿ~2//T¨�„‡‡‹†\±øè£�dÁ‚òÜsÏ™0{ñâÅcw!¯BÀÇ·oßv‡Û7nÜ(QQQ¤H‘Ân×î™2eòq…,‡ p¿@åÊ•Íß—,Y"¹rå@ÀazÃQXX˜©Š0»Ã‡r@°I`ܸqÒ·o_iÑ¢…tïÞݦU˜@BE€@»óNš@»ó΄Š@@�x Äìή]a­ìöªÝ§“'OnêŠŽŽ–$I’Ä«F.B �öîÝkBí×®]3Ás ›?hhWvíή¿OfΜçPµv×.ã®Q£F óï2d$.Ÿ×ÚºukY´h‘ùz¤aö"EŠø¼D ¾.\ð¸=zÔc*ýý¯Ávý(Y²¤<ÿüóñ]Šë@â)PµjUÙ³g�ü÷¿ÿ•W_}5ž³p €€1ÃìthµC˜9@p¦ÀÈ‘#eðàÁÒ¦MéÔ©“3‹¤*@íÎ;*íÎ;*B@â%àêή]©´£ºÕ£zõê²páB2dˆÔ©SÇêé™G Ìž=[:tè`jû·E4€®]Âk×®-C‡�÷>"""<®o×®� ¶?öØcñž3X/lÙ²¥é–ª7Úh˜½`Á‚ÁºUö"h×Îí® ¼Ç¤ŒpòÉ'CD†m"€þ¨V­šùûŸ~¤7/2@œ!°|ùriÖ¬™)†0»3΄*@ð•€¾ÿ®ï£·oßþ_ÐøªÖA@�À Ðî¼3$Ðî¼3¡"@@ Î'Ož4A7г£³³†ä›4i"+V” &ĹF.@ PzõêeBÓúûJCMÉ’%soeíÚµR¶lYIœ8±,[¶LÒ¥KçÕ6Oœ8aºÁÏ™3ÇÌ£óigxíÏøÿ4¸ †^xÁœ‹•O£À§DEE¹ÃíëÖ­ûGY¯½öš;à^ @§”M €@P Ô¬YS¶oß.óæÍ“B… ÕÞØ  ¨1ÃìúUïÞ½u+Ô� €ñ8p Œ5J:wî,~øaÞ\Ÿ\`÷ ·oß6¡ö‘#Gº¹;vì(mÛ¶ þ?ÿüÓ„Ùׯ_/ú¦‚†Ù³fÍ{g“ÄFàøñã÷sçÎy\¦O2ˆpúé§c3-¯AB^ aÆæïÓ¦M“ÿüç?!ï à/�¥K—J‹-Ìòaaaæ&s €„¦@§N�ÌMǃ½ ™� €x#@ Ý={®%Ðn�+³"€ €>ðewvÝ�†w“'OnömKGjŸÀ±ñÐ`¹~¸F±bÅdÖ¬Yñœ-n—if]ûûï¿7f̘Ñtk×''ë¸qㆹq@o&Ðýj˜=sæÌÁº]ö…€%{öìñ¸ëM!1GÌp{Á‚-Y“I@�`ÐnútíþW¦L™`Ü"{B/@˜ÝñGD� €øT <<\æÍ›'R³fMŸ®Íb € |Ú�w¦Ú�w&T„ €ÄZÀ—ÝÙ]EåË—O¢¢¢lïJk^ˆ€�š5k&Ë—/7«.Y²DråÊåÓ Ö¬YcÞ°ß¹s§Y·téÒ&Ø^ @ŸÖa÷bW¯^5�Ù·nÝ*Y²d1aö 2ؽ,ó#Tÿý·G¸}ÇŽûK’$‰G÷öW^y%¨öÏf@oô¦ºU«VIdd¤¼þúëÞLŵ €ñÐï·[¶li®ÔïÃ{ôè�Y¸@‚IàÃ?”ÿþ÷¿òõ×_Ë[o½L[c/ € àí~@Ä’Ú�w&T„ €ÄJÀ×ÝÙ]Ei�Vó+V” &ĪV^„@0 hкR¥JR¼xq:t¨ß¶¦ÝBµcû… L ï¾û®h‡š”)Sú­&«¾té’ ³kø6[¶l&H–.]:«¦gBVàÊ•+÷_ýÕÃBŸi÷’%Kš_S¥J²VlpÝĨßóè÷> @ß üðÃòÁ˜ ³ûÎ�•@pº€ëû´ñãÇ›÷è € €€7ڽѳçZíö¸2+ € `»€?º³ë¦Ž;&3f4û‹ŽŽíîÊ@ ÔöîÝ+éÓ§÷ûçÿõë×M¨}ܸqæž}öYjwýà?Ïåüùó&̾k×.Óý^;³§N�:·BÍ8^àĉ÷³gÏzÔœ3gN�€{„ ¿' D¬ЮÀÚxìØ±òÆoX5-ó €˜0a‚ ¶_»vͬ¨Aq ¶'MšÔGÄm™'NH“&MäàÁƒR¨P!f·ë†œ¸UÆ«@À% _Obvo?pà€Nš4i<î©S§h�ððp™7ožy ކ& €ö ü÷¿ÿ}2†Âìö933 €�. OÏÚ½{·,Z´H´©@ðF€@»7zö\K ÝWfE@lÐ@»†?u\½zÕt3v í¤êÚ¡Ùf�O1&Õ°»k܂׀‡ÛÇ�/•*UŠÏ\ƒ6 \ºtÉ„Ú'OžlVÑ0»†Ú5Üî¤qôèQSÓáÇ¥hÑ¢)Úù™�Î8uê”GÀý÷ß÷(8{öì÷çž{ÎÙ¢:@à>�:ÈìÙ³eÈ�!R§N|@lˆfoÙ²¥tëÖͦ•˜@] bÅŠòË/¿È²eË$GŽ�¾êG@? h÷ó<`yíÎ;*B@,¸?¿~ýzùæ›o$EŠ’5kV÷z”×€Zl‡vh×€�öØŠñ:|/ k4ؾjÕ*³xž6zãÆ�æcÓ¦M›{æ™g<º·çÊ•+¸6Ïn@À1ýúõ“±cÇò=“cN„B@ Ðb†Ùi°è§Iý €øG S¦LrçÎ9|ø°<ùä“þ)‚U@@ h´;ï( ´;ïL¨@°] C‡2{ölùòË/¥nݺ¶¯Ç Æ 3ÛïÝ»g þè£�L°ýñÇ÷j[¶l1avíºüöÛoËÈ‘#½š�‹@ tnÞ¼éѽ}ïÞ½›ñÅ=îéÓ§vж 8PF�ežh¥ÁK €@ü,X`¾¿ÔѺukùôÓOã?W"€ €@H è{Ö2d� ÈñãÇCÒ€M#€ €€µÚ­õ´b6íV(2 € `,]¹r¥Lœ8Q*T¨`ÕS.Ø)pòäIj×›^t¤M›VÂÃÃ¥^½zñZV;,ë×}l�5DCó @ ¾gÏžõ¸Ÿ8qÂcª,Y²xÜ“&Mߥ¸B\à‹/¾�#FHÇŽ¥mÛ¶!®Áö@�ø f�¿W"€ €Àÿ ܺuK²fÍ*O?ý´üúë¯Ð € €^ h÷šÐò ´[NÊ„ € €€ó4TºcÇ™?¾,XÐùS!ø\@ƒèl×îê:Š+fºµ/^<Öµ¬_¿^š4i"ýõ—Ô®][†ëky! ý¦~½r}\¹rÅã2ý{N‰%ÜÚÅ‹�ÄF@ÿÞ!íÛ·77÷1@â. ï;µk×Î\ئMéÔ©SÜ'á @@àÿ¸zõªäÎ�['N,÷?Á @@ >Úã£fï5Úíõev@@À‘¥K—–£G�ÊÚµk%S¦Lެ‘¢@À3gÎ4ÁöÓ§O›‚êÖ­k‚íÚ¹ýacÍš5¦3ûÝ»wMwwírÊ@ìÐöbÜÿþÛ¼õeÆSO=åѽ=OžN×òb@@àAÚ�÷yA ÝygBE € €€­.\�üùóKŠ)d×®]¶®Åä œk×®•ˆˆùùçŸÍõ l?{ö¬|ðÁæ¿5iÒDz÷îœì ‚BàðáÃ÷K—.yìKÿ¾3à®O§` €@èŒ7ÎܘעE éÞ½{èlœ�"€^Ì�;×|o¨CŸèõÉ'Ÿx1—"€ €ÿ'pðàA)_¾¼¼òÊ+²jÕ*h@@¯´{MhùÚ-'eB@@ÀÙ‡’råʉv!]³f�³‹¥:p´ÀÔ©SMÇöóçÏ{ÔÙ¬Y3éÑ£‡£k§8@à~��;wzÜïÞ½ë~ÉO‡Ùy)8A`úôéÒ¹sgyç�wdРAN(‰@Ç Ä ³‡‡‡KûöíW#!€ €@püøã�æ†ãÿüç?2mÚ´àØ»@@¿ h÷+ÿ'Ðî¼3¡"@@ÀV�Ñ£GË€äƒ>�®]»Úº“#€@ð ¸B^ºC ¬ëGÌ1qâDl¿zõªùÏMš41¯I–,Yð¢°3Y�¨¨(w¸}Æ rçηÅã�?în×Gc3@Àù³fÍ’O>ùÄtüòË/�_0"€>p}�Ôe ³ûŸå@A�•+WJÓ¦Måõ×_—ÈÈÈ`Ë € `µ�v«E½Ÿ�@»÷†Ì€ €”@ÿþýe̘1Ò¥KiÕªU@ÕN± à �©S§J·nÝL1ôúè£�XØåË—M¨Ýõ†$I’˜P{XX˜36B €€ wïÞõèÞ¾sçN�U’'OîpÏœ9³ U0%x+0wî\ó÷–Zµj™¿Ï0@þO f˜ýA78c… €X-ðÃ?˜FMo¼ñ†Œ;Öêé™@BP€@»ó�@»ó΄Š@@[:vì(3gΔÁƒKýúõm]‹É@ ø&Ož,=zô0ûôÓO¥uëÖ�Üäž={LL»èèÈ�;· ˆU¨Pá‘×ò@ Ð.]ºäp?|ø°Ç–^~ùe�€{Ê”)}ËÔ�@P,X°ÀÜ´W½zu>|xPì‰M €Vè{JúÞ’ÂìVˆ2 €ÄF`áÂ…Ò¶m[©V­šŒ1"6—ð@@à¡Ú�÷ B ÝygBE € €€­Íš5“åË—Ë„ ¤bÅŠ¶®Åä \úu£OŸ>fS]»v5qâ2/^l‚í4—U©RÅ<š>{öìq™†×"€-püøqwÀ}Æ rþüy�ýäÉ“Ç#àþôÓOô~)�@X´h‘¹qï­·Þ’¯¿þ:P·AÝ €€¥1ÃìíÛ·7ßÏ1@@_Ìž=[:tè uêÔ‘!C†øbIÖ@@�  Ðî¼&Ðî¼3¡"@@ÀV�š5kÊöíÛeîܹR¸pa[×br}Œk¿~ý̆>ûì3iÞ¼y¼77zôh‰ˆˆ�›7oš9Z¶li:û=ûì³ñž“ @�@ЧXlܸÑ|hÀý¯¿þro%A‚áö‚ ê6©�€p=Î^oÀ3fLÀÕOÁ €-Μh IDAT€Õ3fÌ�N�:™i ³[­Ë| € ð(�iÓ¦I—.]¤aÆ2`À€G½œÿ� €'gA@@À¿eË–•ÇËêÕ«%K–,þ-†Õ@ F�%4µöîÝ[š4iâuÝÿûßÿL¨ýÛo¿5s½ð &Ôþî»ïz=7 €�*ð÷ß»Ãípß±c‡ÇV’&Mêpå•Wu«Ô�€ã–-[fnà«T©’Œ?ÞñõR  `§@Ì0»vFm×®��Ë17 € ð�ÈÈHéÙ³§yoZߣf € €Þ h÷VÐúë ´[oÊŒ € €€£òåË'/^”�;wÊóÏ?ïèZ)ü/0bÄùâ‹/L!}ûö•Æ�”†5¿úê+ùñÇͼ 0�­/S¦Œ¥ë0 ˆW®\ñèÞ~èÐ!�m¤OŸÞ#àž*Uª@Ü&5#àH�•+WJÓ¦M¥|ùò2iÒ$GÖHQ €€/¾ûî;ùôÓOÍRŸ|ò‰|ôÑG¾X–5@@×DõiŸÝºuC@ðZ€@»×„–O@ ÝrR&D@œ-ðòË/‹vÿÎÕ®1þ|lׯM:ªU«f:¶gʔɮ%™8�'NxÜõi1G®\¹<î & ¸=R0NX³f�¼÷Þ{¢O¸š2eŠSÊ¢@À§„Ù}ÊÍb € ðWã•¶mÛJÇŽ±B@¼ Ðî5¡åh·œ” @@ç \ºtIòæÍ+É’%“Ý»w;·P*C¿ 2D† fêÜÛïÞ½kÖÓPh°ý‰'žðÉú,‚’ÀÞ½{=î·nÝò(¿xñâî€{áÂ…ikÔŠ€ßôé1�5’R¥JÉ·ß~ë÷z(ðµÀôéÓ¥sçÎfY �é÷f @@À_®÷«;tè íÚµóW¬‹ €A$@ Ýy‡I ÝygBE € €€mGŽ‘2eÊHÆŒeݺu¶­ÃÄ Øƒ ’¯¿þÚlBPP§NŸnèÔ©S&Ô>kÖ,³nš4iL¨½^½z>­ƒÅ@�@Ø´i“ ¸oذA¶oßîQ~âĉ=º·gË–-жG½øT@½óÎ;R²dIÑÅ @ ”³‡Òi³W@C@Ÿ :zôhéÒ¥‹´jÕ*0ЦJ@p´�vç�vç� !€ €¶ ìØ±CjÔ¨! ”ùóçÛ¶#€@à ôïß_ÆŒc6!5kÖôÛf4˜©ÁöÍ›7›Š-j‚í%J”ð[M,ŒŠÀµk×<º·ܼì±Ç3¡övíÚ9ªNŠAE`ÿþý÷7nx”®OÅÐ'bè‡þ3�Pøù知zõêR @Y°`A¨mŸý"€@ |ûí·ÒµkW³óO?ýTZ·n‚ l@œ*бcGÑÆ'_|ñ…Ô«WÏ©eR €��vç�vç� !€ €¶ Œ;Öt¯hÑ¢…tïÞݶu˜Kà³Ï>“)S¦˜¢Ç�'•+WvìŽ=j‚í®`Y† $<<\jÖ¬éØš) �-[¶¸î[·nõ(Y»µ»Âíú«vsg ìQQQòÖ[oIÞ¼yeÑ¢EÁ¾]ö‡!. ÝNµë©Âì!þÉÀö@p¨€66™?¾ 6LjÔ¨áÐ*) @I€@»óN‹@»ó΄Š@@Û(£F�⇓¶ 31�'Ð¥KÑÇÊë˜8q¢T¨P! 6±nÝ:‰ˆˆ�;v˜zÿóŸÿ˜Ží…  ˆú)p²Àõë×=º·ÿòË/å¦N�Ú#àž6mZ'o‡Úˆ—@tt´T©RErçÎ-?üðC¼æà"@ b†Ù;wî,~øa ”M� € b­Zµ’Å‹‹Ÿ.bGÁv@íÎ;JíÎ;*B@lÐ.[ß}÷�h°½Aƒ¶­ÃÄ �:u’3fÈc�=&“&M’råÊFá1ªüöÛoMÇösçΙÿÚ°aClñÅn/Œ8Uà÷ß÷¸Ÿ>}Ú£ÔlÙ²yÜ'NìÔ­P±Ø¿¿TªTÉ<‘`Ù²e±¾Ž"€�$ðÍ7߸ŸàG˜=�NŽZ@=�°°0Y±bE@5e ½SbÇ €–�vç��vç� !€ €¶ ´hÑB–.]*ãÆ�“Ê•+Û¶#€€ó:tè ³gÏ–'Ÿ|R"##¥T©RÎ/ú_*¼yó¦ µ�3Ƽâ™gž‘ððp: ì‰R88]àÀ�÷?þøÃ£äÂ… »îÅ‹wúv¨� ¹K»ž2@@§ ¼ûOíÔ§Ë”.]Ú©eR €��vç�vç� !€ €¶ Ô®][¶nÝjB¬E‹µm&Fg hØ{Þ¼y’0aBÓ™½dÉ’Î.8–ÕiÀ2""Â,eË–•Ì™3Ëš5k|¾> "€v L™2E>ûì3³av;¥™@¬¨W¯žlÚ´IfΜ)ÜÍ&UªT²mÛ6+§f.@Àçú}`Ïž=ͺ„Ù}ÎÏ‚ € `�@éÒ¥åèÑ£²nÝ:ÑæM @@À[íÞ Z=�vëM™@p¤À±cǤT©RòòË/ËúõëY#E!€€µ÷îÝ“¦M›ÊêÕ«%uêÔ¦K¹¹CyüðÃ&Ø®Ý|t¼ñÆ.9räeöŽ8BàÏ?ÿô·ß¦Þ˜³{»þ½–�€]çÏŸ7O´yá…ä矶kæElˆfïÞ½»èÓ» € €@  /^\N�:%›6m’´iÓZùÔ‹ € Ðî¼C!Ðî¼3¡"@@À��;wJµjÕ$þü²páB[Ö`RpŽÀíÛ·M˜];Ö¤K—΄ٳgÏîœý\ɘ1c$""Bnܸa*ÑP‡vlî¹çü\Ë#€¸Î�;çpÿí·ß°W¯^fIÂì¾”g-@°Z `Á‚¢ïè“Þô†w € à­�vo­¿ž@»õ¦Ìˆ €8R`ÕªUÒ¤I)W®œLž<Ù‘5RX#pëÖ-fÿé§ŸÌS4ÄðÊ+¯X3yÍ¢?ÑPûÔ©SÍ®žþyjoܸqí’­ €Á#pøða�€ûåË—=6§7nÆìàþøã�ÏæÙ‰Ï®^½*¹sç–$I’ÈýO ðy1,ˆÄC f˜ý³Ï>“æÍ›Çc.A@œ!�'O¹råŠìÙ³G’&Mꌢ¨@Z€@»óŽ�@»ó΄Š@@[æÌ™#íÛ·—ZµjÉW_}eËLŠþ¸~ýº¹yeóæÍ’)S&f×_ÿ.ðóÏ?›¯‹ÚÍ^‡"ÃÃÃ¥lÙ²°!€8X@¿~oܸÑýqïÞ=wµO>ù¤G¸];m3ˆ‹€þ�*GŽæé-û÷ï�Ë¥¼ð»Àĉ¥wïÞ¦Ž=zH³fÍü^ € €ÞdË–MnÞ¼)�„ z3×"€ €F€@»ó>´;ïL¨@°E`üøñòù矛bê3 |Ú¡F;³oÛ¶Ítd×0»vhgÄN`Á‚&Ø~ôèQsÁÛo¿m:¶gΜ9vð*@¿ ܾ}Û£{ûîÝ»=jѧpÄìÞž1cF¿ÕÊÂ�! O¼Éš5«<óÌ3rðàÁÀ(š*@�ÿW€0;Ÿ € Œú}üÝ»wÍ{·<‘-O˜=!€ à{í¾7ÔŠÚ%ÄÿG@‚D`ðàÁ2räH騱£´mÛ6HvÅ6@À%pñâEf×nµÙ³g—ÈÈHI›6-@ñ1b„ ¶ß¹sÇ\ݦMl×n¿ @�À¸pá‚GÀýرc…ëÂcÜ5ðÎ@ ¦€Þ$¡7µéŸÿ‡� &HŸ>}L­={ö”°°°€¨›"@@�‡ h�]¿�× »« b € €€·Ú½´þzíÖ›2# € àH�.]ºÈ´iÓdÀ€Ò°aCGÖHQ ?�sçΙ0{TT”äÎ�Ûtf饗â7W�Ó§O›PûÌ™3Í¿§N�Ú„Úëׯ� €@ 9rD6nÜèþÐÁb޼yózܹ‰)Ùâ’ïÝ»'2d� ÈñãÇ-ž�é@ë³[oÊŒ € à �›7oJ¶lÙ$a„ràÀgE € ðÚ�w„Ú�w&T„ €Ø"вeKY²d‰Œ3FªT©bËLо8sæŒ ³GGG‹†ñ4Ìž2eJߤ+nÞ¼ÙÛ7mÚdvX¤Hl/Y²d�î˜m!€¡!°k×.�€»ë©º{íø³{{þüùC…]þCàå—_–¿ÿþ[´Ãÿc�=† àX�ñãÇËçŸnêëÕ«—ù‘� €‹À•+W$Ož<’4iRÙ³gO°l‹} € €€Ÿ´ûù°<�vç� !€ €¶Ô­[W4˜©Ý†‹/nËLо8uê”4iÒD~ùå)P € ³§H‘·E„Èj³fÍ’ˆˆ9yò¤ÙqíÚµM°=}úô!"À6@�àÐG—oذÁp×°{Ì‘,tí瓜*0nÜ8éÛ·¯)�0»SO‰º@@À}BiÁ‚M#—;vx3×"€ €níÎûd Ðî¼3¡"@@À� *˜G1._¾\²gÏnËLо8~ü¸éº÷믿JáÂ…M˜];Ô0ì¸wïžéÖ>lØ0³H‚ L¨=<<ܾE™@Àç/^ôèÞ~äÈ�´kwÌî<ÅçGä³³fÍ*·nÝ’ƒÊ3Ï<ã³uYˆ­@Ì0{ïÞ½Í Ï @@ Ø´±‹6jJ›6­ûIšÁ¶Göƒ €¾ Ðî{óG­H ýQBü@@ H *$ÿûßÿdÛ¶m’*Uª ÙÛ@ 44X§avýµX±b)Ï=÷\hbøa×ÇŽ3Áöùóç›Õ5ب¡öZµjù¡–D°[@¿îoܸÑýqþüy�%õ±ç1îO?ý´Ý%1¿�räÈ!ׯ_—ýû÷ów-™³ Ä^`ìØ±Ò¯_?saöØ»ñJ@<�£G�JéÒ¥%cÆŒ²nݺÀÛ#€ €€#´;ïX´;ïL¨@°E sæÌrûöm9|ø°<ùä“¶¬Á¤ `¿€vd×0»vhíµ×Lgv:†Úïþ ~üñG‰ˆˆ�íÛ·›ÿ­ç¡Ûµc>@ xvïÞípÿ믿ܛէwÄ ·ë#Ñ�+�;wn¹zõªDGGK’$Iw#TŽA'3ÌÞ§Oyÿý÷ƒn�l@p èÓ‡õ)ÄÙ²e“+Vƒ €X"@ ÝFK'!Ðn)'“!€ €Î¸víšäÊ•K%J$ûöísf‘T…�øå—_L˜ýäÉ“¦#�†Ù¹Aå‘l¶¿`Ú´i¦c»>CGƒ L°�§aØNÏ €€ßîÝ»çnÿùçŸ=jJš4©GÀý•W^ñ{Í{�¼yóÊ¥K—$**J’'Oû y% `£À˜1c¤ÿþfÂì6B35 €8F`Ïž=RµjUÑ'¤-^¼Ø1uQ €¶�vç��vç� !€ €– üöÛo¦s°þ…ü§Ÿ~²|~&DûöîÝkÂì¿ÿþ»”+W΄Ù{ì1ûf…X ܺuË„ÚG�m^ÿôÓOKxx¸´nÝ:V×ó"@�à¸|ù²GÀýСCKŸ>½GÀ�›Ÿœ}î �óçϋިð /8»XªC��ˆfÿüóÏå½÷Þ ‰}³I@m�;vH�5DŸ‚6þüÐÆ`÷ € `™�vË(-›ˆ@»e”L„ €8W`×®]òöÛo‹v\´h‘s ¥2x ÀîÝ»¥I“&rîÜ9óhÕ‰'"åP�ƒš`»«SP–,YL·ö·ÞzË¡S €€�zcéÆ�Ý®§y¸ÖÔ§(•(QÂý‘0aB;Ëaî8 .\XΞ=+Û¶mãÉ+q´ãå `½€Þ<;`À31avë}™@œ+°iÓ&©W¯ž/^\fΜéÜB© @J€@»óŽ‹@»ó΄Š@@ËÖ¬Ycºv•)SF¾ùæËçgB°O@;‚jgö‹/JåÊ•eܸqö-ÆÌ– ¬ZµÊÛõfÚU_ƒízc@ t¢££=îú„�˜C8ï ¸k˜šá_�bÅŠÉéÓ§eóæÍ’&MÿÃê Ò1Ãì}ûö•Æ�‡´›G@�ÐX·n�¼ûî»Rºti™:ujhmžÝ"€ €€mÚm£�÷ÄÚãMÇ… € €@àÌ›7OÂÃÃÍ#‡ 8…S)!. Ý@5Ì~åÊ©Zµªhˆ�X‘‘‘&Ø~ùòeSøûï¿o¾§H‘"°6Bµ €¶ÄìÞ¾}ûv�5'Nìѽ=[¶l¶ÔÀ¤ÿ.P²dI9qâ„lذAÒ§O à�Q£FÉÀ�ÍÚ„Ùýr,Š €~X±b…„……ñôR?ŸË#€ lÚ�w¢Ú�w&T„ €X.0qâDéÝ»· ÆöêÕËòù™¬ÐN ú{ö�?þ�jժɈ#¬_„}"põêUjׯÅ:%Jdºµ7oÞÜ'ë³ €@`èŸ1îô(øúë¯eРAfMÂì>¥g1@p˜ÀüùóÍ϶x ±Ã†r@píÎ;@íÎ;*B@,èÖ­›L�:•€Z.Ë„X/ @›4i"·oß–:uêÈ�!C¬_„ý*°dÉÓ±ý—_~1uT®\YÂÃÃMÀ�� €À¿ ìÛ·Ï#à~ãÆ �—-ZÔp×fx/P¾|yÑNù+W®”¬Y³z?!3 €±ˆfïß¿¿4jÔ(–Wò2@@ øfΜ);v”zõêÉ_||dG € àí~aè¢Ú�w&T„ €X. �bÔG2꣪ß|óMËçgB°F`õêÕ¦3û½{÷¤~ýú2xð`k&fG Œ;Ötl¿~ýº©¯yóæ¦c{¢D‰Y/E!€8K`óæÍî€ûÖ­[=ŠÓní%J”ph7wFÜô)*û÷ï—eË– †q÷ã ˆŸÀÈ‘#Ýß 0@6l¿‰¸ @mؤ�›Þ}÷]Ó¸‰� €X!@ Ý Ekç Ðn­'³!€ €ŽÐ`ìÆ�å»ï¾“’%K:²FŠB ÔV¬X!aaa†A \`¿ÀùóçM¨ý›o¾1›M‘"… µ¿÷Þ{Á¿yvˆ `™ÀüáѽÝõשS§ö¸§M›Ö²µƒy¢*UªHtt´üðÃ’;wî`Þ*{C‡fwÈAP €8J`âĉһwoóþyÏž=UÅ € €@à hwÞÙhwÞ™P € `¹€«³àÒ¥K%gΜ–ÏÏ„ à�€þÞlÑ¢…™DƒÌŸþ¹wruÀ ìܹS¾úê+Y»v­©=_¾|&Ø^¶lÙ€Û #€ø_àôéÓwý÷˜#[¶lÞðÊÓA|fo½õ–DEEÉ¢E‹$oÞ¼þ?X*@� 1b„|ñÅf�” õ~Ù € [�Ñ£G›0ú4â.]ºÄö2^‡ €A´;ïL¨@°\ H‘"ræÌÙ²e‹hwF8G`ñâÅæ�xM›6•^½z9§8*ñ¹ÀÂ… M°ýÈ‘#fm Òi°=K–,>¯…@‚G@;¶ë›\ÚÑ=æ(\¸°;à^¼xñàÙ¸—;©^½ºüüóϲ`Á)P €—³q9€› w IDAT ðï„Ùùì@@þ]`ذa2dÈi×®�tèÐ*@@Àí–0Z: �vK9™ @p¦À+¯¼"þù§üúë¯òôÓO;³HªB ¾ÿþ{iÓ¦�ÙyóæÍå³Ï> A¶ü �‘#Gš`ûíÛ·Íÿnݺµ ¶?õÔS€!€ àµÀÖ­[ÝáöÍ›7{Ì—0aB�îí¡ü„§ZµjɶmÛdîܹ¢¡ `‡ÀðáÃåË/¿4S4HÞyç;–aN@@ `ô)6z`ÇŽ¥mÛ¶» G@g hwÖyh5Ú�w&T„ €X*pýúuÉ‘#‡<ûì³¢� à �yóæIxx¸)æƒ>�®]»:£0ªpŒÀï¿ÿnBí3fÌ05½ôÒK&ÔNÀÅ1GD! €@Pܸqã{û¾}û<ö•*U*�€{ºté‚bß±ÙDݺuEÿ³fÍ’bÅŠÅæ^ƒÄI€0{œ¸x1 €„¨@¿~ýdìØ±Ò­[7iÙ²eˆ*°m@°Z€@»Õ¢ÞÏG Ý{Cf@@-pòäI@ÑàÉÆ�]+Å!*³gÏv?U;oúé§¡²uö�-[¶˜`»ëkx‘"EÌͯ½öZ –G@àÿaïNÀm*ÿÿÿ¿e(QŠBÅeŒ”y‘$2dŸ„Ì™R "K’™2”y ™”D$E2•!dJ$³ÿï}¯µÿk�Á9gïµÏ^{ïç}]ç*Î^÷zß�µqöÞ¯õ¾ð¼@�=dúôé2hÐ iÒ¤‰çë¥@@íÞ»NÚ½wM¨@pUàË/¿”çŸÞ04ˆÁ@�Ð è›îú滎®]»š.Û ’"pýúuÓ­]0ÖÐP»>—R¤H‘”©x, €~ lÞ¼Ùp·OróÍ7;º·,XЯsxå ^xAÖ­['S§N•Ê•+{¥,ê@�0°‡Ù‡ & 6 óQ> € \�W^yE´Ù‡înS¿~ýàžŒÙ@@ j´{ïRh÷Þ5¡"@@ÀU�E‹I§N�¤V­ZòÁ¸:7“!€@â¦M›&½zõ2tëÖM:vì˜øƒy$1þøãl_°`�ùŽ¾á¢¡özõêa… €@² \¸pÁnÿùçŸç¾ûî»÷pÛU¤E‹²zõjùè£�ä‰'žH6WN„‘+ ?Ãë—Âì‘{�Y €¸+ ï¥/^¼Ø|Æ¥Ÿu1@@7´»¡èîÚÝõd6@@Às“'O–¾}ûJ³fͤÿþž«�‚ˆëÏ¡®µ{÷îÒ¾}ûhX6kL�¯¿þÚtkß´i“9[ùòåE;¶—.]:ÎÎ)@p üõ×_Ž€û�È“'� ¸ë¿Wúß;î¸ÃÓ„­Zµ’•+WÊĉ¥jÕªž®•â@Àûö0û;ï¼# 4ð~ÑTˆ €x@ M›6²|ùr7nœT¯^ÝQ €D‚�vï]EíÞ»&T„ €¸*0|øpvìÚµ«éÞË@�äÐÔ[o½eNúÆoˆ¾ùÎ@Àm�3f˜N�ÇŽ3S7jÔÈÛ³fÍêö©˜@�D ìÙ³Çpÿûï¿Ç+VÌp¿é¦›=wr<°mÛ¶òÙgŸšHlÎ�@„ XïÍè2 ³GøÅfy € àº@óæÍeÍš5ìžåº,"€ ÝÚ½wý ´{ïšP € àª@¯^½dÚ´i&Pû /¸:7“!€À�´cÌÀ�̓úôé#-[¶„ �  \¼xÑ„ÚÇŒcΑ&Ms#S‡‚vN&F@ )[¶lqܯ]»æ;ð|L€Ñ%`³k—özõêE«E@\¨U«–lݺÕÜp\¼xq—fe@ˆvíÞ{h÷Þ5¡"@@ÀU�§žzJ~ùåùì³Ï¤`Á‚®ÎÍd [@Oú¥cðàÁ¢7•0¥À”)SÌsòôéÓ¦Œ^xAºté"Úù–� €€WöíÛ縟:uÊQj‘"E¤|ùò&ä®_©R¥r})¯¾úªé¨7*6hÐÀõù™"W@ÿÞÐ�uf�ÜëÌÊ@@ yøœ+yœ9  €Ñ&@ Ý{Wœ@»÷® !€ €® èö‹þù§lذAî»ï>Wçf2p 6L>øàó›úÿ 6„Oœ={Ö„Ú'MšdêI—.�éÖÞºukOÔG €$$ðã�?:îW®\ñ’2eJG÷v·v#yýõ×eæÌ™Ü¤˜ÐÅáû à°‡ÙÙá�' € ¸Àã�?.»wï–Õ«WK¾|ùŸ�@@�ÿ'@ Ý{OíÞ»&T„ €¸*�?~ùï¿ÿd×®]’6mZWçf2øÿ´û˜1cÌoÐ��g†WtÇŽ#FÈÊ•+M‰ 0ÁöjÕªyµdêB@ –€†Ù¿ýö[ß—†ÝíãÎ;ïtÜsçÎí—bÏž=å“O>‘·ß~[žþy¿æà ˆ.ûMÎúswݺu£ €Õ"€ €Axä‘Gä�?þ�¯¾úJræÌ„30% €D£�vï]uíÞ»&T„ €¸& Av ´k�]í Ž€†œÆ�o&×måk×®œ1+. ¬X±Âtlß¹s§™±jÕªÒ¥Ky衇\:Ó € �|§N�rÜ÷íÛç8yŽ9¤|ùò&ä®_wÝuW¢ŠëÓ§�L™2Eú÷ï/Íš5KÔ1<¢W€0{ô^{VŽ €Á(S¦Œ9rD¾ûî;¹÷Þ{ƒ{2fG@�¨ Ðî½KM Ý{ׄŠ@@×>,åÊ•3oðé} p_@N“&M2�=ZjÖ¬éþI˜� L˜0ÁÛÿý÷_s†–-[šŽí·Ýv[�ÎÈ´ €_à÷ßwÜOœ8á8i¡B…÷4iÒÄYT¿~ýäÃ?”¾}ûÊ‹/¾üÂ9„­ÀСCeÔ¨Q¦þ÷ß_êÔ©¶k¡p@@ÀkÅŠ““'OÊ–-[}ƒ²×Ö@= € à=íÞ»&Ú½wM¨@pMà矖êÕ«›Ž»Ë—/wm^&B�ÿÐpÓäÉ“Íÿ�7Îüyc núaЈ#dêÔ©¦ô;ï¼Ó„ÚéDnW’z@âøé§Ÿ÷K—.ùš"E ÓµÝêà^¼xqß÷  zóW¯^½¤uëÖ#€q fç‰� €W@?ã:{ö¬èg^·ß~{pOÆì € 5Ú½w© ´{ïšP € àšÀ×_-Mš4‘ *ÈŒ3\›—‰@@L°iÚ´i†bâĉRµjUXk�üÑtk_·n�YG‘"EL°ý±Ç ëuQ< €v�k×®9ÂíÚáÏ>2dÈà ·kXbÖ¬YÒ£Gi×®� €@,�!C†˜�ºtŒ9Rj×®� € €€Ë÷ß¿\¼xQ~ûí7¹å–[\ž�é@@ Z´{ïÊh÷Þ5¡"@@À5�%K–H‡¤fÍš¾X]›œ‰ˆb�îÝ»ûnùè£�ä‰'žˆb –iúo‡Û÷îÝk–V£F l׎ €DšÀßÿí¸ïÙ³'Ö .,-[¶4�Ü3gÎi¬ü Ìî'‡!€ €IÈ‘#‡\¿~]~ÿýw¹é¦›’x4G@â Ðî½g�vï]*B@\˜:uªôîÝ[š6m* pm^&B šºuë&³gÏ–”)SІÙ+W®Í¬=‚F�%#FŒ�K—.™U¶oßÞÛo¾ùæ^5KCˆv�øî«V­’ÿý×A¢[Ý—/_Þ„Ûõ‹î€ÑþŒaýÑ*0xð`3fŒYþ| µjÕŠV Ö� €UàòåË’'OI•*•ìÛ·/¨çbr@ˆ.íÞ»ÞÚ½wM¨@pM@ƒˆÃ‡—.]ºH×®]]›—‰ˆVýs4oÞ}ZŠ)"wÜq‡üôÓO¡/ˆ @@ b´{ïRh÷Þ5¡"@@À5 ^,^¼XFŽ)µk×vm^&B Ú:tè K–, )i˜½L™2ÑFÀz0ºC�v«=pà€ùuݺuMÇö9r „ €@Ä Œ3F,íÛ·—îÝ»ÇZß?ÿüã¸ÿöÛoŽÇdË–ÍÜi}eÍš5âŒXÑ$0pà@7nœY2aöhºò¬@B)pìØ1Ó\#sæÌó¦âPÖŹ@@ ü´{ïh÷Þ5¡"@@À5�ÿýïòÕW_ÉÇ,•*Urm^&B šÚ¶m+Ÿ}ö™é£aö’%KFÓòY+±®_¿nBíÚ±ÝÚ­]ƒí)R¤@ @�ˆ?~¼¼ýöÛÒ¦Myã�7\סC‡÷£G�:ŽyðÁwÝù‡�á!`³�=ZjÖ¬…S% € æ4;aé ú[@pK€@»[’îÍC Ý=KfB@<'ðôÓOËöíÛeÙ²eR¸paÏÕGAx] U«V²råJÉ”)“ ³+VÌë%SÉ& ]Ú5Ô>þ|sÎìÙ³‹ÛëׯŸl5p"@‚)0iÒ$éß¿¿´lÙRúôé“äSíØ±Ãp?þ¼c޲eËúî¥K—Nòü€É# 7¶è .:³'�9gA@,�½{÷JåÊ•%wîÜòÅ_ƒ €¸&@ Ý5J×&"Ðî%!€ €ÞЭí­.�Ú½‚�‰hÑ¢…¬^½Úle:yòd)T¨Pâæ‘D‘ÀúõëMÇöï¿ÿÞ¬ZÿíÑníeÊ”‰"–Š ‰ú3`ß¾}¥yóæÒ¯_¿€—øÝwßùîÖ¿›Ö¤éÓ§wtoà�> €@àö0û˜1c¤F��OÊ  € €‰عs§T­ZUô5Ò矞èãx  € ���ö„„’ÿûÚ“ßœ3"€ €É& oði@}Ã/]ºtÉv^N„@8 \½zU4̾nÝ:¹÷Þ{Mgö|0œ—Dí$‹ÀÌ™3MÇö£G�šó=÷Üs&Ø~Ï=÷$Ëù9  €n L›6Mzõê%M›6•¸:ý¹sçÝÛýõWÇüús¨Þ$f}é¯ �¼úç~„ 椄ٓמ³!€ €–À¶mÛ¤fÍšfbÝ�˜� €¸%@ Ý-I÷æ!Ðîž%3!€ €ž¸xñ¢ÜÿýróÍ7ËîÝ»=UÅ àU�K—.™0ûW_}%Ù³g7aöüùó{µ\êBÀsúgHCí£G�6µ¥N�Ú„Ú;tèà¹Z)@�„¦OŸ.=zô�&MšÈ Aƒzx@ßÿóÏ?wýµ}èÍÊö€»vtg €@ðìaö±cÇÊÓO?¼“13 € €@¼›6m’gŸ}VJ–,) ,@ @pM€@»k”®MD Ý5J&B@¼%päÈ)S¦ŒdÍšUbngï­J©oü÷ßÒ¼ys$Ê™3§ ³çÍ›×ÅQa&°gÏl_ºt©©ÒZ99á,`³�7NªW¯ÎË¡v@@ ìôýÅ^xÁ¼¯8mÚ´°_ @@ïh÷ε°*!Ðî½kBE € €€+t­p…‘I¢@àï¿ÿ–-ZÈæÍ›%_¾|2yòdÉž={¬œ%"�|S§N5ÁöS§N™“6mÚTºté"wÝuWòÁ™@H¢€ng¯ÿ^Õ­[WFŒ‘Ä£ƒ÷ðóçÏ;º·ëÍÌö¡»ti÷Bë+[¶lÁ+†™ˆ �þýûˤI“ÌŠ³GÐ…e) € ÖÚ°©U«VR¥JßÏja½ ŠG@Ïh÷Ì¥ðB Ý{ׄŠ@@W–-[&íÛ·—§Ÿ~Úl‘Í@�Ø'Ož4aö­[·Šv²ÔÎì÷Þ{/T Ý ACí'N4³§K—΄Û´i„³1% €� ,^¼X:vì(µjÕ’>ø ð ƒ4ÃÑ£G÷C‡9Τ7mÚî·ß~{�*aZÂWÀf?~¼<õÔSá»*G@"H`éÒ¥òÒK/I�5d̘1´2–‚ €¡ Ðê+ûüÚ½wM¨@pEàã�?–7ÞxCžþyyûí·]™“Iˆ$�¿þúË„Ùúé')T¨� ³gÉ’%’–ÈZð¤€v‘Õ.·+V¬0õ=ðÀòòË/òäÕ¢(@ º¬›„Ã-8ñÛo¿ùîºs×Ù³g²dÉ’Ž€{t_eV�€H¿~ýäÃ?4„ÙyF € €ÞðêÎYÞRŠÌjŽ?.‡ f̘12ùÿV-ëŒØ ÈÂ@ ¬´{ïòh÷Þ5¡"@@À�÷ß_Þ}÷]éÔ©“¼úê«®ÌÉ$DŠÀ‘#GL˜ý—_~‘¢E‹š0û]wÝ)Ëc„…€n¬Û5à®C· ÖŽí  ‹ú)@ ò–/_nvÑNÍr ×±yófGwû:n¹åG¸�‡Ãõ*S·¿ö0û„ ¤ZµjþNÅq € €A˜5k–¼öÚkòÜsÏÉСCƒp¦ôª€vä}¢aÉf�—.]ëõY®\¹äÚµkf§Ë?þøC4­;Î)RDÒ¥Kç{üöíÛåðáÃråÊÉš5«”*UJR§NíºÙ™3gdË–-ròäII“&�\¾|YrçÎ-… ’T©R9ΧMLŽ;f~/Gޱ¾¯¿¯¯KÏž=+ºƒØ}÷Ý'Ç�7¿§;¦8p@š5kfÂíW¯^5s¤H‘Âï5ý÷ßò矚ãµfuÕæGêªn:tWßìÙ³Ç{ޤ¬ß~õ|§OŸ–M›6ÉÅ‹%K–,’>}úD­SoNÿí·ßDwVc}èñúÜÐ9 €þ h÷Ï-˜Gh¦.s#€ €!Ðn_|ñ…L�:U*W®ÂJ85ÞØ»w¯ ³ïß¿_Ê•+gBI·Þz«7Š£ 0Û¶m3ÝÚ×®]k~]¸pal× !@�äÐ×SúºêÑG5]£aè�Ÿß~û­ùúæ›oD?¨·�âÅ‹;î7ÝtS4°°ÆÐ�쬛T4 ¥»00@@¼)0fÌhnRHh�óæÍ“®]»Æ»f}ÿ\ÍbÞLà7"€Q$@ Ý{›@»÷® !€ €®Ô¬YÓ¼ £oVi7Ñ, �+4Ì®Ý<yäÓ™ýæ›oŽfÖŽ€§ôà ý0ACu:ž~úilÏ—/Ÿ§ë¦8@�Èøú믥I“&æçÇéÓ§GÖâ¹íºg…Ûõ¿ú¾5´ßÃ?ìûÒÑx]@»zZ»f÷úÕ¢>@@@Ì®ŽÃ‡—.]ºÜ0ÔiVŸ}ö™´mÛÖ±, Š—/_^4ä×ÐïiHzâĉ¾o×®][Þÿý€ºšëdúZPCÞö›½uG³üùóËâÅ‹MÐܳfÍ2¯uØ×¡!õ¸^7Z7œ>óÌ32räH;v¬ 2g߬u IDATıD]›vW×׿VWz®y\®”oذ¡éÒ¾zõjß´º^í ïÖúcÖûä“OʪU«n¸N{_X©R%)X°  ¯[·Îw¬þ©W¯ž?$ƒDµ�vï]~íÞ»&T„ €¸"P¡BÞ]¿~½Ø~wen&A œvîÜiÂìºÕ¦v×Ô0;�*Âé Rk4 Œ=Ú|h¥[°êЮDl×íg €[@ÜÏ=÷œù ^?��ö¡[¥Û»·ëvöö¡Ýøì÷œ9sF;ë÷˜€=ÌþᇊH € €Þ:t¨Œ5J^{í5éСƒ·‹u±º•+WúÂÔ:­¼õõ©v ?yò¤4hÐ@vïÞí;£¾�ª�®t9rDêׯo>#Ô×i|¤«¹Î© ´¬ùuιsçJžÿ©H.íÉ%�øóhO¼�D@ÂFàòå˦3‚¾ ³wïÞ°©›BpSàÇ4�Ù5h£ojg„¯À¦M›L°]wÑQ²dI³Õ°Õ©'|WFå €^ІëÖ­kþÍY°`�WËôL]ûöí3÷o¾ùÆü÷ôéÓŽÚŠ-긳k’g.]Äb³ëMÎÚù‘� € Ö.;ýû÷—fÍš…GÑ.Ti:üñÇR©R%Ǭö ¸º¶?ÀêðîF‡vÝ=³zõê v×÷m7nlÊ>|¸Ô«WÏÓ�öøžSúš¶Q£FfzCEÆ ý^¿ý:êë’V­Z9®ã�í1o6l˜ ¸§K—Î…gS €¨�vï=´{ïšP € °€v³-Uª”éb«! Ñ&ðÃ?˜0»†hžzê)?~|´°^"V@·¯1b„X]`ëÔ©c:¶çÌ™3b×ÌÂ@B#°uëV©U«–+VL/^š"Âø¬zƒ©=à~õêUßj4ÌnïÞ®awÁÐNžÓ¦M3Sf†0s"€ €ÁèÞ½»Ù¹qðàÁ¾°tpÏüÙ5¨¬Í;.\¸ëdº³UÁ‚Åt^±b…è®Ìö‘PçsëûnÚµKüÎ�;%®nñV]”òåË›_j¼iÓ¦ž´/Z´HŠ/ëÄ\‡Úý]¿ý:Æu¾Úµ0½¹^›ºØ‡ÖS¡B)R¤ï‰ÿ�+g@� Ðî½ L Ý{ׄŠ@@�€¬-ìòçÏ/«V­ x>&@ œ¾ÿþ{fÿ矤fÍš2zôèp*ŸZ@ ‘j׎íú�ŽÎ�;›`»}ëÝDNÅÃ@�xN�:en I‘"…èÏÖiÓ¦E+Š~úé'Óý¬P¡Bòé§ŸFÑÊÝ_ê•+WL¸Ý ¸oÛ¶Íq mØî¹sçv¿fŒ:{˜}òäÉòøã�G� F@Â] k×®2oÞ<_Çïp_�Öþüy³«ì�b-§téÒ2{ölÑPµµ3å²eˤpáŽdž*Ю»én¸q½ûË/¿˜C:F�eBà Õ©�íÛ·¯èÏëö¹ zûûûl¢:´Çµ~7íêùßÿÉ–-[äË/¿”qãÆÅzÞôîÝ[Zµjåß’£@�( ÐO Ý{ׄŠ@@�€¬7ˆ´Âøñãž� �õë×û¶Õ75äÊ@�èøæ›oÌŸyÝ¡AG¹råL·v½±‹�"@ =~½ãÇ�ËáÇEßô×îÚ‘8víÚ%O>ù¤éÎÏÎWÁ½Âú!¿=à~òäIÇ µ¡=àž&MšàÄìa-`³O™2E{ì±°^Å#€ €Ñ,кuksƒñ„ ¤ZµjCqéÒ%ßΓ֢®^½*úZ'UªT’P:¡ ¸õ}½9xݺu’!C¿í4L­»—i_;”Oœ8QR¦Lk>{ û­·Þ’^x!Áíºûf»víÌã¼Ò¡=f×ôúõëû½þ„®cb:´Ç„ÖçÉÑ£GeîܹfçéÓ§—Õ«W˽÷Þë÷uæ@@ ´{ïªh÷Þ5¡"@@ `�éÓ§K�=¤I“&f«9‘. ])žþy³LíÌüÎ;ïDú’YÄ!0kÖ,l?räˆù®îV¢ÁvÞÈçé‚þ œ;wNŽ;fÏ‘#‡ùP™ñcÆŒ1×zõê%0ˆÄ±gτ͛7¯¬]»6—èÙ5ýôÓO¢7¬iÈ]¿._¾ì«U·¶·‡Û‹/îÙuPXò ôìÙS>ùäsbÂìÉïÏ@@·š5kf^�EÛÏv ¡“3Ð~åÊ©S§ŽlÛ¶M4 ¯7|gÊ”)Ö¥ž={¶tëÖÍüþÌ™3¥|ùòŽ@»Þ˜ ; ÙÇÙ³gE›siGúä´Ç×ÕÜn«;h7}ןÐuLL ýÌ™3ñÞ� ¯}ô5� ´çË—Ïí?‚̇D´�vï]^íÞ»&T„ €,ðÁȰaÃD·b|íµ×ž� ð²Àš5k¤yóæ¦ÄF�É�!C¼\.µ!€@�4ð¦¡vý°A‡†O5ÔÞ±cÇ Ÿ™é@�èÐjº•õçŸn~öÒŸÁ"qìß¿_*Uª$¹rå2Ûz3B#píÚ5G÷ö­[·: ¹ãŽ;w½��ö0ûÔ©S¥råÊÑ Áª@@�hܸ±èî¬3fÌ� *DÐÊn¼”„‚ÐÉh×JíÁéHÓ¦M 8þ¼ }ïܹӄÞ5¼ž9sfG ý£�>2¡uû°‡àã ´»ù¾ƒÝMCŒŸ~ú©#,®¯?[´hứ]Ÿ{ú8ןÐu´?æ:µ³¾v¹×±lÙ2Ñ�ËbŽE‹I§N�ÌoÇuÃ@Ôü�a¡ €€ŸÚý„ âaÚƒˆËÔ € €@¨ú÷ï/“&M’øº „ª.΋€Û¢jÙ²¥™öÿûŸ 8ÐíS0„©€nq«Áö%K–˜hQƒíµk×ÓQ6Ñ! [n-›6m2kË’%‹ÙʺM›6¦ƒšvËÓp»ny­F)R¤ðÕpáÂùí·ßÌÎzƒ‘ºèñúw²Îáõ¡ëÓÀ„~°¡b3¼!ð÷ß;îúo¾}èõ²wp×#òt—:Ý­Naöȿެ@¢G ^½zòý÷ß˼yó¤téÒQ³ð„‚ÐÉh?~ü¸”(QÂç?tèPÑk£¯õõµóK/½d:¸ëÐDýúõ3ÿo_‡¾§£¡ö ˜÷sôšöíÛ×7g|�v½QU?ûÑ÷Bî¹çž€vϳ»é‰õ&v èëûúÞË»ï¾+&L05éóMwÕ5º±þ¸BévŸ˜ëü믿¤lÙ²¦µ9r¤ µßzë­æ=}�G›¸œ8qÂ|_;´g̘1jþŒ°P@À ín(º;�vw=™ @ð„€öæÏŸ/Ç7o(1ˆD�åË—› • Ré� @ ¦€v²1b„XÝ\õC ýw²xñâ`!€€´3V»víÌqÓ¦M (ZˆÚËÕnÙœ®[·®ùàÎ>ôX½™%[¶l¯páÂ…Ò¹sg3ÏÒ¥KM˜^‡½NýV?XÕíbކ šFo¾ùæ€j±Î§“è6ëzÃN\.zC«Þìg•kˆ\·úîÞ½{œ5¨×äÉ“}k‹¹>ý W�­¡ÛƒëM1‡Ô7lØàël¦.]»v�wÝÖîúÁ°W‡Þ(Q®\9¹ï¾ûÌÚÞÐðÄ7ß|cBîú¥Aû(X° #à~Ë-·xs!Tå·€=Ì®ÿv<úè£~ÏÅ� € €Þxæ™gäÇ4¯óõFîh ÚíïÄ”¶‚Ûúš_ßÍ�!CÀt_ýµ4iÒä†ó”,YÒ¼oqûí·›Çi]wuKÌkj½ÖÚÖ¦zC}õêÕMãûÐ÷iìÁú¤.*f ýFÇëcõõ¤5üYB×1¡uŽ3Fœà2ÝìbŸàÉx AÚ½w1 ´{ïšP € °€†{×®]k‚)�?þxÀó1^Ð7hÛ·ooÊzñÅ]<¼V+õ €€74ܤPíL¬ãù矗.]ºÈÝwßí�©Œ@\,jˆ¼|ùò¢âÅ5ô{²ž8q¢ïÛºÃûï¿ïvûC_dz¸êÔ@·þÝò믿š‹­¡<êé�ŒøÎ§�yíÒ®]¸¬aߥIÃìúký;ÐÚ)-þü²xñbÇÊÚuL»ZÇw¬ãŸ|òIYµj•c9z ´“™vGÖîùöÖõ�z3‘~¬Ax»�×oÀ=zô¨éȦ�ÿµ# #<~þùgGÀ]öaïÞ®a Fx èÍ:3fÌ0‹ ÌÞ×’ê@@�¸ªU«f^K®X±Â¼ö�–aB¯\¹Òt5·ý P? Ô¡ï äË—/ÎïkGs½ß�@»ž`ÿþý2hÐ s=ìCßÑnëÚd+eÊ”ŽïéŽqúú_?³´�§Ÿ~Ú£ïy¼ùæ›òØc�™Ý§­ß¿ûî;iР�ã˜@Ÿö÷WôF|}nõéÓÇq5Ó×yòä‰õtKêúí×1¾Úo´N}_端¾2ï‰ÅuS@�5Ìgd�„ü£åÏëDâ Ðî½ç�vï]*B@¨U«–éD«!•bÅŠ< à%}^ë6Š:Z·n-½zõòRyÔ‚ø÷ßM¨ÝÚ6V·gÕP{Û¶m=\5¥!]ú!m«V­|‹ÖSÏ=÷œ ¦ë )úA¦½;×èÑ£¥fÍšæñGŽ‘úõë›­®Ýê@_ =f�ÚUþÕW_•Ô©S›ZôCF ›ë°o™íïÕŒhíµ×Ìß]Ö‡¼ö�kôƒ×5kÖˆ~˜¬]Ü-5™;w®ãYÝÕI;¥ëÈ�;·ù\ç\´h‘têÔÉW®ºöìÙÓlk­k«î6`uo×NîÇŽsœòÁtܵ‹ #ôö0û'Ÿ|âÛ1"ô•Q € €€ÛVÐìûï¿n8 LW»€kgï¤=F;±{e²}ßÈÚ­sÙ²eR¸pa¯,‹:@B @ =è œ’@»÷® !€ € è–x¹sç6o.íß¿? ¹8¯hç= -èèÒ¥‹tíÚÕ+¥QD€À¤I“L°ýìÙ³f5-Z´0Áö 2DÀêXÞРó¦M›äÂ… ±ŠÊ˜1£¹™Ä”^±b…h˜Ô>ê¤e}ß�¹ž7¾ó%T§¬@û¢E‹¤xñâ± <(åË—7¿oÚ5h¯»RÄÕíÞš æqM›6u\‡¸Îw£@»Î»`Áó3›}hÀ^;ë)R$lv‘Ò­¼­ç �vïü]ÌJvìØá¸ëŽöQ¶lY_À=æŽÁ¬‹¹ÿ½±YwÅÐ1}útÑê € €‘+`Í~üñGÑ÷þ ØßßHê nì<—ÔsÆ÷ø@סŸ3YïWhwëª0 ¾Ú½wí´{ïšP € �ÀñãÇ¥D‰¢!ž-[¶4#àí¸×³gOSÊ+¯¼"�;wöBYÔ€&púôijŸ2eŠY™†Ù5Ô®áv¸#pþüy©R¥Jœ�®5:{ölÑpuÅŠÍ ãú`Ñ‹�öø> V =¾óénO=õ”±Ó®ñU«V5Av ´?ñÄ¢7ïÜtÓM±.fÌãô{`=®ó%hד,Y²Äìª×Ð×*ƒ 25zyh˜9þü¦Dí^¾RÁ«mÆ ¢�ÛõKoȱ�ôéÓ;º·?ðÀÁ+„™�avž € €@ô XA3½ùTgø' »ÜùûÙŠ[Mü«ÜyT ëÐ]€»wïn&�kç?7jd@�ð Ðî½kE Ý{ׄŠ@@�€~ûí7X¹ÿþûeÍš5ÍÅÁ„Z`êÔ©Ò»woS††â E…ºNÎ�‘#ðÓO?™`»õoh¡B…L°]ÿme €@`/^”êÕ«ËîÝ»cMT»vm1b„ h�íœP�_�Ø»w¯T®\Ù|¸ù¶v×[½zµx5ø«5çÊ•ËÔJ Ýµ¿"f¢Ã‡ûÂíÚÁýÈ‘#޵iÇö‡~Ø÷E7Iÿ/½vQœ3gŽ™€0»ÿމ €„£€4ã5Y8^=jF@ÀÛÚ½w}´{ïšP € �ÀŠ+¤uëÖR­Z5™0aB@sq0¡Ðçî€Ìé{õêežÓ @ cÆŒ1]£/\¸`Nß¶m[Ó±=mÚ´¡(‡s"ñ Å ´‹Ù½&®®æv›Q£F™nøuêÔ‘mÛ¶‰üW­Z%™2eŠõÒŽÇÚùXÇÌ™3¥|ùò®ÚÏœ9#2dˆó9ûÉ'ŸHÏž=Í÷4О/_>Ï>· OxöÒx®°�;w:îÿþû¯£F½éÜ ¸—-[Ösõ{µ {˜Ýú;Ê«µR € €€»ìšå®'³!€ €€S€@»÷žÚ½wM¨@H@»•uïÞ]5j$C† h.F cÇŽ•Aƒ™Sk‡ö-Z„¢ Ήøþúë/Ó­}úôéæ÷î¾ûnjÿßÿþ‡¸,@ =nP{X]ßdÿôÓOañk×®™Ÿ™Ö®]k&X¿~½èãìÁq½Y°iÓ¦Žœ?Þ„Þ5ˆ«¡w½96sæÌI ´ëk}ía�uëÖ™.ï:t·‹Â… ÇZÔ¢E‹¤S§Næ÷õœ>ø ËÏ$÷¦³>ÔØ¿œîÝ;3Eš€îž¦�ÛõKÿß>n½õVG÷v/ÿåuyå•WÌ®:³‡òJpn@@ 4çÎ�ó½^¤C{h®gE@ ’´{ïêh÷Þ5¡"@@ �Ñ£G› {ûöíM°��@8 h7Ñ¡C‡š’ßzë-_*œÖ@­ ¹›7o6Áö¯¿þÚ,²D‰Ò¥K©T©Rä.š•!�ÌÚã·Úõú÷ŽÔsäÈ!ÿý÷Ÿ¼ûݙ´ ô¬Y³$UªTrüøqów•5ôç¬zõê™ïiॗ^2Üu4oÞ\úõëgþ?¡ë`ÿ~åÊ•eàÀ�¢¡ú{î¹Gô& «û´†äGŽiBíà½|ù²lÙ²E:vì('Nœ0!zíО1cÆd~¦%þtÖ‡{öì‘4iÒ$þ@‰€M@»µ[ávý¯ÞDbY³fuܳeËõ~ö0»þ�¦Ýí € €Ñ%pêÔ))Z´¨Y4�öèºö¬@�ä ОÊI;�ö¤yñh@@Àól™0a‚¼ñÆÒ¦MÏ×K�Xï¿ÿ¾ céÐP��yn €€W,X #FŒ0�Oµk×6ÛsåÊåÕ’© �°H(H­�ÉÛµkgÖW÷o+ø­Aiíž!C†€Ön’ÛÏ—P�zÒ^½zÉ´iÓLh;ÐZbÚo´(}lÁ‚}Ñ›pš4irC‡’%KÊ”)SäöÛo7�Kh}/^”êÕ«ËîÝ»ó.\¸ÐèÇŒ#ƒNÐ>fw÷Á¬5víÚ%iÓ¦ Aœ2Ž9â¸>|رÌ|ùò9îÖŸÍH´ˆkM]»v•yóæ™of�–«Î:@@�ØG�½i[�vž! € à¶�v·EŸ�@{à†Ì€ €xJÀêböÎ;ïHƒ Ìô† {÷³gÏ:V¤7�h‡rë+Ì—{ÃòíaöÙ³gK¹rå"y¹¬ @@àb¯P¡‚y�vž* € à¶�v·EŸ�@{à†Ì€ €xJ E‹&Üóá‡Ê“O>é©Ú(�¸†*£F�2ßâF ž# n‡’÷Þ{OæÎ�kJ¿ï¾û¤K—.Ò°aÃp[ õ"€€‡âêùòe9sæŒ\¹rÅTž%KI‘"Å Wñ÷ßË¿ÿþkÂëúØL™2IªT©ü^¹ÖpîÜ93—v/¿ùæ›cÍ¥ß×/ú8 …§K—Îïs&÷�Ö‡Û·oø‰ä®�ó…¯À¦M›|÷˜7…ÜrË-Žp»}G†ð]ñÿU®7Ο?ßü?aöp¿šÔ� €.°gÏyì±Ç$oÞ¼¢7œ3@@7´»©éÎ\ÚÝqd@@À3uêÔ‘~øA.\=G!!ÐN¡cÇŽ5çÖ@è³Ï>’:8) ¨€vUÕ¿Ç6nÜh¦*[¶¬ eÑU4PYŽGˆ+ÐŽLòXjüøã�’1cÆä9)gAÀ&ðßÿ9º·ÿòË/ŸÌ™3;î¶âÂÊÑfŸ3gŽùYŠ� € ݺSVµjÕÌ®h1w‹nV� €nhwCÑÝ9´»ëÉl € €@È*Uª$û÷ï—/¾øBrçÎòz(�ø  &L0ßþàƒ¤V­Z`!€a/ ÝD5Øþ矚µ4hÐÀÛµs;B# ]Äê\WezŒv2wkRÇÊ•+¥mÛ¶¦”eË–IáÂ…Ý*‹y°>ÔЛ†ï¾ûn¼¹À±cÇ÷ƒ:jÒî•?ü°ïëŽ;îyÍ  »Û,X°À<Œ0{BZ|@ˆ½±ø™gž‘¢E‹Ê’%K¢gá¬@’E€@{²0'é$Ú“ÄŃ@@ï X?toÛ¶Mî¼óNïL…Q)Я_?ùðÃÍÚÇŒ#5jÔˆJ�‘)påÊj×›u¬ñꫯJ§N�"sÁ¬  Ø;›'µÌ{î¹GÖ¬Y#éÓ§Oꡱh¯¿þºhàS�ö€/G’&°^_}ÿý÷’5kÖ$˃H�Ý»w;îgΜqœ¶xñ⎀ûM7Ý”e%úö0ûܹs¥L™2‰>–"€ €D¶€¾«W¯ž”.]ZæÍ›Ù‹eu € �ìÚ“�<ÁhO�ˆ € €á#píÚ5É™3§)øÀ�áS8•F•@Ÿ>}dÊ”)fÍãÇ�—§žz*ªÖÏb@ zöíÛg‚í‹/6‹Ö£µ[{�:u¢�•"b�… JçÎ�ýªâ®»î’uëÖI† ü:Þ~P uèM1Ý»w7Sê\%J”¸&&Hœ€õ¡Æ† Øm#qd<*ĺ›À·ß~ëûº~Ý| dFš4iáöPïö ?ëßi:³‡ø‰Ãé@@ ¬_¿^7n,*T�3fx°BJB@�p ÐG Ý{ׄŠ@@¿Nž<)ÅŠ3Çh÷›‘ƒ(гgOùä“OÌ&Mš$UªT âÙ˜ð†À_|!#FŒ�-[¶˜‚*V¬h‚íR½q}¨"²._¾,.\ðk‘)R¤p¥;»ž<Ð:®^½*­)Ožû¬”,YR,Xà÷<ˆ€Û¯¾úªÌ™3ÇL«�ÎJ•*¹} æCÂBàüùóòÞ{ïÉøñã}õöèÑCÚµkõS$ mÖ‡Ú0o޼Ѷ|Öá{÷îuÜOŸ>íXqÑ¢E}÷òåËKÊ”)±‡ÙçÏŸ/¥J• xN&@@ˆL�%K–H‡ä™gž‘Q£FEæ"Y €„L€@{Èèã=1�vï]*B@üøüóÏ¥eË–R¥J™4i’ßóp n ¼üòË¢A3fÌ� *¸9=s!€a)ð믿ʈ#ä³Ï>3õçË—Oôï˧Ÿ~:,×CÑ €@¤ Xj¬ZµJòçÏ©Ëd]�­[·:îW¯^õɤJ•Êѽ]ÃîI;v”Å‹›Ãô&|½Ÿ� € €@|º›O×®]¥^½z2|øp @@W´»ÊéÊdÚ]ad@@À³fÍ’×^{M6l(Æ óFQTÕöî{³gÏ–råÊEµ‹Gb h@R;¶ÿüóÏæ[O<ñ„ ¶*T,@Xj,_¾\zè!TD $�ÀåË—áömÛ¶9Nœ1cFGÀ=wîÜ7,Œ0{ò\7΂ €D’€6ÈéÞ½»4nÜXIKc- € àí¸1J Ðî½kBE € €€ßcÇŽ•AƒIÛ¶m¥gÏž~ÏÃ�¸!ðÒK/ÉÒ¥KÍTÚI¥téÒnLñsœ:uJþøãI‘"…éš6mÚˆ_3 D‘?üPúõëç£hÞ¼¹ ¶ßqÇ𠀄P@wÎØ¾}»,[¶L .ÂJ85¡8yò¤#à¾ÿ~GA9sæôÜË—//™2eò}¿C‡²dÉóë… J‰%B»ÎŽ € S¦L‘>}úH³fͤÿþaQ3E"€ €@øh÷Þµ"Ðî½kBE € €€ß”qãÆI�=¤]»v~ÏÃ�*ЦMÑ.–:,$MS½:wîŒ]ÒØx4!ð÷ß›ní“'Oö­§oß¾òâ‹/FÄúX ޵jÕ’­[·ÊâÅ‹¥X±bá¸jF (hÿöÛo}_x·½äᇖüQ¾ûî;^ßå*0) € Ù&L�HëÖ­¥W¯^‘½XV‡ €É.@ =ÙÉݺu“Ù³gËСCå¹çž ŸÂ©4¢Z¶l)Ÿþ¹Y“vá+Z´hD­/Ø‹!пðñãÇåðáâo.d̘1Ø—‚ù™€vÖ`ûêÕ«M  4ÝÚŸ|òÉ�Õĉ@�h¨[·®lÞ¼Y,X %K–ŒVÖ�@‚Û¶msÜ/_¾ì8¦H‘"RµjUr/^¼x‚óñ@@F�e>ïÒ^{í5@@@W´»ÊéÊdÚ]ad@@ÀV�xÒ¤IR¥JoE üüóÏòÏ?ÿHš4iLGÈ”)S4_0¾~ýº´hÑBÖ¬YcNóÙgŸ™&#içÎ�“cÇŽ™ƒräÈ!©R¥JÚüè1cÆÈàÁƒM7íÊÃ@ Ò>ýôSlÿí·ßÌR«W¯.]ºt‘x Ò—Îú@ÏÔ¯__6nÜ(sçΕ2eÊx¦. ‰N�py}xõêUiÔ¨‘¯3{Ì«uÇw˜`»~•/_^òäÉ�”U#€ €ÜP`øðá2bÄéÚµ«yOŒ� €¸)@ ÝMMwæ"ÐîŽ#³ € €ž°ºΟ?_J•*剚(Â?�ÿþûO† &zs‚Ž»îºKÖ­['2dðo uåÊfÿâ‹/Ì™V®\) òY™>šô†‰V­Z™îÿC† 1Ñ"0vìX4h�o¹mÚ´1Ûo½õÖh!`� €@Ètç«o¿ýVfÍše· B!n¯Û·o/Ë–-3T‹/–œ9s:º·ïÝ»×Á¨Úîwß}w(˜9' € €€Ç´¹‰69éÞ½»èϘ @@ÀMínjº3�vw™@ð„@åÊ•E?^»v­äÍ›×5QDÒ¶nÝ*/¾ø¢œ8qÂw°—í/^4aö¯¿þÚÔ»zõjÉ—/_Ò¢#.]º$4gÏ•+—\»vMvìØ!üñ‡hˆZí‹)"éÒ¥3�ÑÇoß¾]>,äÏš5«¹�$uêÔ®¬@Ã*þù§™ËÞ¡=f�7Ýt“ìÛ·OvïÞ-z ´Ö‡zȵ?ûö:rçÎm\víÚe\tÝ:Š-*Ù³g�wÝgΜ‘-[¶ÈÉ“'Í.—/_�«P¡B±:ÏÛ×§�9}ú´lڴɬ-K–,’>}zÑï�¤Y³f&Ü®�Õ(Eо.\¸`ºY9rÄœC]ôx½¶:�pøë¯¿LGªO>ùÄ·„·ß~[žþùp]u#€a!ФIósîôéÓå‘G ‹š)2²Âíõa»víDw™Ñ¡avÝi,æÐ×z£ˆõuüøqÇCt§/{Àýæ›oެ‹Êj@@%пÓô§OŸ>¢;3@@7´»©éÎ\ÚÝqd@@ÀÖÜú�w¦L™hn2ˆ94 ®Ï=kçuÑígãÚѺcÇŽ±Âô_4&@ ~øáóçñ«¯¾2g-^¼¸ÙrùÑGMÆ*8 =M›65»éωü]=×Ý++ ·×‡ö0»¾>Ò`3ô†a{À]ojµ{¸½D‰‰™’Ç € €D€@¯^½Ìk1}¿[_›1@@7´»©éÎ\ÚÝqd@@ÀÖÜ¿ÿþ»h÷fFø XoÐj@qìØ±2kÖ,\ôb‡öþùÇtfÿþûï ´†+u+ùpŸ}ö™´mÛÖQ¶†ÈË—/o¶3�kè÷4d=qâDß·k×®-ï¿ÿ¾#Øí�…½žeË–IáÂ…Í4qÕ©�níÎüë¯¿š› ¬¡[±6nÜØŸÓûމï|˜×ŽŠÚ‰ßj׎é:4Ì®¿Ö¬¡7=äÏŸßth´n~Ðïéó[Ã)ñ­Ï:þÉ'Ÿ”U«V9Ö£×@»Èk·Tížoàë+Uª$ÚÙQƒðv›áÇK½zõ²á`¼ `¿ùEë©U«–èMºÃ@À=ý9fÍš5æf¼Çܽ‰™ �D„ÓëC}M¥¯!t$%Ì“A_OØÃíz3Ÿ}Ü~ûíŽîíz8@@ 2^{í5óòСCå¹çž‹ÌE²*@™�ö�ÑÇ{bíÞ»&T„ €ø% ¼­NÎðk ½€v´Ö.ßÚÙ.eÊ”b¼h·?ßTMnt'ÅX¹r¥/Œ­ç2dˆys\;ŽŸjŽ%Ðî¡'ºzõª ²[£oß¾¦¤[ai7yâÄ ÑòÖØ¸q£ 4‡ëÐ ¬ ô3Ï<#µÁ5$Ý­[7³<íÐÙ¯_?ÇRµ“þ Aƒ\»FñÚíu–.]Út¦±V5ÔýÊ+¯È¼yós¯‹½}1¯]έð|Ìç§þZ»E—(QB(ãÆ�3ÑZ´Vû°×©¿¯áò|ùò™.óVH,®Ð¹}ý1íÚ©½F�&°®�òõÆ›7o–]»vIöìÙ¥\¹r±Öâ¯Ç!à½iCwóЛ9¬1lØ0ßM.^¨‘@�` üüóÏ¢;¥I“FŠ+æøYÚ�sê |z#ßøñãå©§žrcJæ@ ÑáðúÐú3¢‹r;ÌÔÁƒ÷cÇŽ9¦;iÙîiÓ¦M´7D@Hš@°_�µoß^tS{“™¤UÈ£@@�ø´{ïÙA Ý{ׄŠ@@¿tî:uê˜@©K‘!àµ@»†J•*åÃÕ�pæÌ™ÃÛ”þøã�¥R¥JŽõ؃ÝVhÛþ«sº[7$&Ð>jÔ(Ñð}ÌáæóÅ^Gÿþý¥Y³f±Î÷Í7ßH£F�ÌïkÈ\;ÄW¯^=ÁNèëׯ—Æ�›ã†.õêÕûuèÝ»·£k¾>îF�ö˜!y òjÀ=]ºtaýܤx’* ;5h°ý»ï¾3‡–)SÆtk×Pˆ4½¡MÿÍ×Îé:ÜúY,¦“ 3fŒùù‚�@(Üüyß�uØÃì4*\¸°Ó&iŽ_~ùÅp׿ì£lÙ²¾€»ýµl’Nƒ@@ÉõzÌÚ1kâĉRµjU® € àª�vW9]™Œ@»+ŒL‚ €„^`ÕªUòâ‹/ÊO_ìu,Z´ÈÑ•ßZ»vG,_¾¼ù¥h× ýÎ�;Mà>f·ûøŽkÚ´©ã:Äu¾Úu^ÝzV· µ ØW¨PAŠ)"9sæ ÖÓŒyðœ€}g -®~ýú&Øž-[6ÏÕJA €€?ús¨¾öÑ�ƒ¬áÖÏb1ë騱£,^¼X>øà©U«–?år ® ¸ùó~ EµnÝZôu‰ŽP…ÙãZƒÞà÷í·ßš/}-héÓ§wtoÏŸ?  � €D�@r¾Ó÷�¿øâ ™:uªT®\9ê¬Y0 €W€@{p}ý™�@»?jƒ €xP`Μ9òꫯšÐÚ»ï¾ëÁ )ɯìÁe]ÇO?ý$wÜq‡?KJÖcΟ?/UªT‘Ä:oéÒ¥ECŸº¶Š+šïÇÄðb =¾Àˆ›Ï—„Ö­^Ú ñ©§ž2vÚ5^»äX�v½¹F;¦ÞtÓM±ìc§ÇØëq­/¡@»ždÉ’%Ò¡C‡8Ÿcr4h�|’õO ' ¥ÀÕ«WM·v½±Ä¯¼òŠtîÜ9”eqn@ `ÝQç�7ÞpÌsÿý÷ËéÓ§eݺu’!C†€ÏaŸ@o˜ÓçFŒ!uëÖuun&C ©nþ¼ŸÔsÛo³úé§R¨P¡@¦ Ú±gÏžutoßµk—ã\÷Þ{¯#à~Ï=÷­&F@"A ¹_�Y!³™3gú«D‚#k@@oh÷Æu°WA Ý{ׄŠ@@¿Æ�/o¿ý¶è˽zõòkòž€ û÷ï—J•*ùpvìØ!ÚÙ.ÆÅ‹¥zõê²{÷îXåÖ®]Û“4ìN =öÕLL }ïÞ½¾Î8ƒ–gŸ}Öh¿Q‡ö˜Ç5nÜØ•@»®B·»Ý²e‹|ùå—2nܸX ëÝ»·´jÕ*ž¾Ôˆ€+úw¸ÛuçkÊt…–I@ DúZgÚ´if÷˜±cÇʬY³ÌßsÁêЮ7Í�;×Ü4¬73¥€^êÏÒ+W®4 ^³Çu�>ì¸9rÄñ°xÀpO—.](/7çF@<'�ܯǬ�Ùüùó¥T©Ržó  @oíÞ»~Ú½wM¨@ðK@äcÆŒ‘×_]^zé%¿æà ï „:°°gÏyì±Ç|0ÚÑ.mÚ´ÞƒºAE—.]’ë×ÍKßÐÎÅiÒ¤‘T©R%¤N(Øm}ß­U|çK¨ƒ¹.ÎÍçKBëÖóÅìš®!¯5j˜´Cûĉ%eÊ”±®Ž=ÐþÖ[oÉ /¼�àuHL‡ö˜'Òë|ôèQB>|¸ù¶ÞŒ±zõjÑnŒ ¢I@oòÐ û?ü`–ýÈ#�ÈË/¿,%K–Œ&ÖŠ н{wÉ–-›´k×Îüœa*ÜúY,&‘¾¶Zºt© :Tž{î¹d á,àæÏûþ8ØÃìúz¡`Á‚þLã™cvîÜé¸ÿûᅫÚtW¯òåË›�{™2eúè£8;m{O,é%¤&Ð.¢/æ?ýôSGXüÚµkÒ¢E Y»v­A_¿~½yœ=,;`ÀiÚ´©ã¢œ?Þ„Þµ¢†Þõï- Ò&tìß2dˆ4jÔÈ7ïºuëL—wË–-“Â… Çz",Z´H:uêd~_Ïùàƒ&ýÉÂDˆÀÿý'ï½÷žŒ7η"í°Õ¾}ûY!Ë@�höÏËÚðôéÓR¬X1Y¼xq4ѲV ûùß’£!ÌsíÚ­ýÛo¿õ}éëûÈš5«éÜnÜï»ï>>c( @®@0>=tè�ùyK;÷:2; €Ñ*@ Ý{Wž@»÷® !€ €~ Ô«WO¾ÿþ{™;w.[aû%è̓‚ù†p|+Þ¾}»<ýôÓæÛÚ�NÃì‘< Rhÿ¿«_©R%Ñ€zŽ9Dðï¾û®¹�FGéÒ¥eÖ¬Y’*U*9~ü¸”(QÂ÷”:t¨èßOú½ÈK/½d:¸ëhÞ¼¹ôë×ÏüB×Áþ}½ÉbàÀ�¢¡ú{î¹Gþúë/)[¶¬™GCò#GŽ4¡ö[o½U._¾,[¶l‘Ž;ʉ'Ì÷µC{ÆŒ#ùiÍÚH”€îÄ1bÄsÊ5ÆŒ#5jÔHÔñ<ð‚@0^¶(t­?ÿü³Ü~ûí^X65D©@0Ÿïñ‘¾øâ‹fç%Ñ|cè‘#G÷Ç;Èt(+Ü®Á«Ûn»-JŸ¥,@¢I ˜?Ÿj3ýÒñÈ#�ÈôéÓ£‰–µ"€ €@2hOä$ž‚@{Áx8 € àUë‡m jꩌÈ4h�Œ;Ö„v¿øâ I›ïètÅ IDAT6mP¶uëV©U«–9GÕªUeâĉA=Ÿ&O(H­AÏvíÚ™RãêþmÞ5(­�Â3dÈвâ Ð'T§ž´W¯^2mÚ4Ú´{ -H[°`AßþþúkiÒ¤É +Y²¤L™2Å Kh}/^”êÕ«ËîÝ»ó.\¸Ðè5„;xðà„J•˜ÝÝ<€ ú³ƒî¸`�Ç\^~ùå8w;ˆ–ˆa&�\ eÑúêׯfB”IÉýúP>ПtDs˜=®ç�Þhup_¹re¬‡(PÀ¯ôçªråÊEÒÓз½™Yƒýú~7 Gä%fQ €$(Ì×cú3”ý&Bn0Nðrð@H¢�ö$‚%Ãà ´'2§@@’CÀúaû‡~�»ï¾;9NÉ9\Ю×N™2¥™9EŠòꫯš®Ò:´É�wÞ)ׯ_7]§K•*%™2er­ŠÍ›7KݺuÍ|7nœks{y"{�Zƒ<°�µk×J³fÍÌoÅuÈõ}½éàóÏ?8Ðn?Ÿ½{�ñ…I†nº-ëßú˜ôéÓûMo´Ï›7OvìØ!}úôq̧kž1c†äÉ“'Öyöïß/¸Ñ:ìCkÒ:´k»õ\×ï'f}ß}÷�4hÐÀ1Ÿe¡.¾úê+lß°aC¬z´ã´v—´w�÷‡ˆPÝ‘ãÍ7ßô­NwQèÒ¥‹ù·‡�xU 9=ô�,_¾Ü«Ôa¡~}hû@Oâz�aÜ/gÓ¦M¾€{\¯G4Ø®�ÛõKÿ.‰„aÝT¬7V·nÝ:–Ä@@ ‰Áz=¦?O5lØÐQ 7'ñâðp@HP€@{‚DÉþíÉNÎ @@�àX?lïÛ·OR¥Jœ“0kPöîÝ+•+WNô9¬ÎÔ‰>àܸq£¯Ûä3Ï<#£F�rcZæc�¸:Åë�gΜ‘+W®˜•eÉ’ÅÜxq£ñ÷ßË¿ÿþkÂëúX½ #�¿£´†sçΙ¹tÇ‚›o¾9Öéõûú¥C§!útéÒ…ñÕ t’O@ÿŒë Tn·†ÞÌÒ²eËä+‚3!€IV€â—_~‘§žz*V%Ú‘9[¶lI¨�‡"àŸ@(_f÷ïšéQz£­v¶_³f�éÐ~êÔ)Ñ¿Oì#sæÌR¾|y_À={öìþŸ0DGê:[µjenêf¬]N‹ €€‚õz¬k×®¢MVì£J•*2iÒ$¬š@@ R´{ïJh÷Þ5¡"@@ É>+T¨�9îÀ�I>ž¼!pèÐ!ó�vbǧŸ~ê»î‰=&®Çi(ç¹çž3ߪS§Ž¼ÿþû�LDZ"W =B–Æ2@ º�³ÛW­Zå{´~h¨2@/ +@¡;VØoî±ÖüòË/‹~1¶@¨^ÚÃìV~à�’´ÔK—.ÉÁƒÍ1¹rå’k×®™Ýžþøãö¾ë®»¤H‘"¾NõñÛ·o—Ç›g³fÍjv#K�:u’Λ˜ë{'[¶l‘“'OJš4iÌÎg¹sç6¯«cÞt«7È;vÌL›#GŽ8oÊý믿äìÙ³rË-·È}÷Ý'Ç�ý½6mÚ˜÷ft§/ }ë<úkí4ª¯¿-«æ¼yó:î2dˆµíØÿ矚ßךÕu×®]ÆÕºá¸hÑ¢r£p|RÖo¿Žz¾Ó§O‹v¡¿xñ¢¹±YoŽ¹Î«W¯+ûMÏ.\�ß~ûMŽ9b õ9 Çës#�Ås½y  €_ ¯Çþùç)X°`œÅóùWð¯)g@@ š´{ïjh÷Þ5¡"@@ É¿ÿþ»T¬XÑÇzIæ‹ê¾þúkiÒ¤‰1¨W¯ž ><ª=ÜZ¼#ê\×¹ôídî֤ޕ+WJÛ¶mM)Ë–-“Â… »Uó €@ ØonѲµ[q—.]¤@�a´ JE�HF€B½Ê•+g¶1‡vg×@*�H4Ì®&zãu»víLp}Ú´iÒ£GÙ¶m[,®/¿üÒ¼fª[·®œ8qÂñ}=vÉ’%®í† !ê™3gJ÷îÝã¼lz¾É“'› ½5tG´Î�;›_.]ºÔñ=ë1íÛ·7¯•J—.-³gÏ–Ž;š_ÇÜÖ0»Tß½{·ù{D¿–/_ëñ%J”ðuo×›ÞÕÉrÕO™2ÅÜx—kïÞ½ÍÎ:ö×£þ¬ß~'L˜`®“5þ?öÎüªiÿÿ‹”&¹ÄMä¹B†êÊÔD† �†PQ7©„*$.•é’¡Tæ¤DHR¦ÊP¦n’¡Ì³ÐÅE…�áÿ{ïþûÜsöÙûœ½ÏÙûœ}Îy­çù>×ýž5|Ök­ïi}Öz¯Ïjܸ±uI![?UUÑU½’.‰Y>/x•ãß!}‚ ”�(ü±ûï¿ß 6ÌÃm·Ýf:tèPJˆ°€ �@пÁAп1Á"@€ ˜À²eËÌ‘Gi•CÐ_ÅX¸p¡éÛ·¯Õÿž={𫝾ºbY„Ùq§ø3HÝõë×·ž¦#R]¾v >Ü­*!h2Šä…@y¸ùæ›ÍW\‘èÜÉ'ŸlE(®U«Vyv˜^A%C ÅŠ+¬ <^IÂU ÞI(<+²¶�ôBË®»îšS÷Üü Å÷ßsã�7ºÖ©Ï$’–@ÉNGu”õzX.…“Qß$ò–¸ÞNýúõ³ú7gÎKhn§{ï½7ñjšŸ«ìïŸ#Ž8ÂŒ?ÞÜtÓM檫®Jé£ú¦èêwß}·çºiéÒ¥ �û¢E‹ÒµmÛÖl±ÅfÖ¬Y)ŸÉo”/¯(íO>ùdâ3õW‘á•Âè¿Ó C=4å}îìg²_ŸtÐAV¤U áµa']ª×åz @¥I ¬cÇŽ®—çDˆ <¥9O°€ WÚã72Úã7&X@€�V²íÚµ³"u‘ ��€»û÷ïoeëÕ«—¹òÊ+³ásŸ’#ùù,’ȦȀ:Üw{b>h]ùÚqÎ9ç$"ª.E $A•Mà믿6ãÆ�3Ó¦MK€¸ì²ËÌ 'œPÙ`è= PTÿú׿,©.>ýôÓ¦F�yÛ3jÔ(3yòdÏzQä�˜ bD L1»º¥—žl1µþ¿ÞÇwœ%Lÿïÿkzôèa¡ÜN7Üpƒ9üðíÿûå—_šîÝ»[õÃò�ÅÜ®_u*âçN;í”hÿ�°.é)í¸ãŽ–0\ÃÚ9ä3iÒ$³ñÆ›äôrY'ýòË/ q»"¸¿ñÆ®³E‘IqÞî‹"½ŸrÊ)VÞä‹Ò¹ööìÙæÌ3ÏL´­q¹à‚ LÍš5MÕªUÍÊ•+/:û©9uÞyçYQëÅ|ÆŒfç�wNÔ¥×/´~Ô<ÐÁ±˜W¯^=F˜@€€_aûcÙ.שSÇ,_¾Ü¯yäƒ @ h�ßAп1Á"@€ ˜€ýŒó1Çc=;M‚@&É"ëð™�õë×›Ÿþ9§ %ò#:»Ï׎ßÿÝ|öÙg–ðD" 9 )… P–^yåk½ñÌ3Ï$ú7uêT£è¡$@QP”ãçž{ÎT©RÅjFë]Âûæ›o¬ÿ¯ï&E1–˜Rk¡æÍ››ºuë6IÑ×%¸ôJQHhªÿ%A ” üñÇf‡vHtAÂâ]vÙ%¯.% ºíÈåzÛI"çsÏ=×ú¿º˜?zôè”ötAE¨°ízaF/Í(iï¤E‹)íéûbذaÖgJ6ƒb Ú�ðu¡Pß7âöüóϧ��|µýöÛÏúyöÙgÍ=÷Ücå±/%çÚÿ·ß~ÛvØaV]n¢óLÂ}}WwíÚÕ¬+Ò¾¢×;ÓË/¿lÞyç³ýöÛ[/^H$O‚ @ Þ á�e»\,BzÕG—ûH€ @ _Úó%~yíá3¥F@€ PpÚÀ»ôÒKÍI'�d.¾øâ‚·Oƒ¥C ùPÞM�#F˜»ï¾Ûz–»wïÞá7@�%M`Íš5Öe‡¹sçZý8å”SÌ…^XÒ}Âx@ˆ�/¾øÂŒ;6EÜ© wÇw\¼ Å@YH¨þùçŸûâ$iƒ |å%âB`ÆŒæœsÎI˜ãWÌ®?ýô“9ì°Ã̧Ÿ~šÖ�-ZX뉣%žVzä‘GÌ{ì‘’7›P<*A{²èÜi|òË  ñ{6;UG±ín\eÏk¯½fEdWúÛßþf>ùä“´qÒ纤ûRƒ[ÿ“ëníe´«á‡zÈ袣[Ò…íauèÐ!.Ø@€@‘ (Ïã�?îË ½ZÌ:Â*2A€ ��‚öøMíñ,‚ @� H ,±òÍ7ßl=§M‚€(bÊí·ßn¦OŸnÖ­[gAi×®�™2e € @‘X²d‰éÑ£GJÝ÷ÜsO"êi$�R) ��,^¼Ø(‚°ß$1ë€üf'ŠF@—œgΜiE—Ÿh' Ž÷ÜsOßvýòË/ÖžÃ{ï½—V樣Ž2ãÆ�³Äîq´gŠÐþÁ˜¶mÛZ}ºòÊ+M¯^½JRÐîì‡útôÑG[—tìHíÉ'A»„í;í´“¹ä’KRú† ]j/â•W^1Ï<óŒµgåL]t‘8p ï9HF@€Ê“@ÐèïÇ{¬¹îºëʽ‚ @ `´ µï†´ûFEF@€ _öB[ÑÐQ�TÙ´ù«(¹z¢39m¶ÙfF‘×H€ �¨ 8£¿ê qÈ�!fûí·�ºiê‡ �3�¡C‡Z¢_¿IÑÙ¥��¸��]—œõ£ÿNN}ûö5—^zi`ÓýõWóçŸÖÑR"ýþûï¦Zµjf“M61Ù„ÐÙ"Ÿ‡¡]bê®]»Z|EhW$Ï*Uª¤õ9Y.&b“ÍN14h�•/9ú»ŸÈå�¡ÿ_�lö¨NgÛÝ»wOé¿^jÓåC}o=üðîfpÀæä“O6Ûm·�u!^)×íÎ4OV­ZeíUØ´Úµk›'Ÿ|Òl»í¶¹`¡  @eB@çúñ›êÔ©c–/_î7;ù @€€+íñ›Úã7&X@€°ÚzŽq·Ýv \žåA@%'OžlæÏŸïÚ¡k¯½Öè@›@(�?þøÃ:Œ¼þúëÍI,zöÙg¢yÚ€ ˜@“&MÌÚµk•ÓKY*G‚@œè’³DÃnBvÙ©hÝ/¼ð‚‘(ì'Aûo¿ýfºuëf^{í5³ÕV[™'žxÂÔ­[7­Ë pî¹çZ¿·_–I�Ï›7Ï4nÜ8¥œ¾+:uêdE¤/´ Ý+ªy²Í'N´¢ég꿸HÜ>kÖ,óÎ;ïxN·½?ÂýÕ«W›Í7ßܵ޻îºË\pÁÖg´ï²Ë.aOEêƒ @ „(P“^• ’tY±C‡AŠ�€ ¤@п � =~c‚E€ @ 0{¡ýÒK/™zõê.O�Ò& ‹ *HÐî•]-Óç¥Më!@ Î$x’°ýÁL˜©ÿÌ1ÇÄÙllƒ*Œ€.…80p¯yê>02 DHÀëµ.g“QÎÛ8 ÚÕïdáôe—]fN8á„?ýô“%ú~ë­·,Ñ»Äëýë_S"¢ëâ¸DëÉ)Yï%h¿êª«ÌñÇʈ'‹Õµôè£�¦ˆÅu‘°ÿþfÁ‚V{Ï?ÿ¼Q¾ ý×­Ï^}õU³lÙ²4»»téböÛo?ëG‘î<ð@+�³Ÿ .´¢Ü+¹Ew×ïgÏžmÎ<óL+�Û…�P Q  @%A_¬$† #!@eIA{ü†A{üÆ‹ @€@`öB[ÏdW­Z5py ”&�™3gZÏtK´�-�=!>‡ �¨ <ûì³æÿøGJ3<ð€iÞ¼yÔMS? �¬ô‚„Ö×AOÝ%Fþ(ø²Ûm+*wƒ ¢0ÅÄMÐþõ×_›}öÙ'Ñ×1cÆ ú7Ùd+ºúé§ŸnEpWêׯŸ=z´õßÉý�Ð]¢öÝwßÝüúë¯ÖwÅÈ‘#uz ÚÛ¶mk®¸â #±yýúõ­6sMÉ‚vÕqÐA ôÿö·¿™uëÖùü·Þz«U}‹-̽÷Þkµ—kÿÅäð÷êÓ\É´ï°ÿþû›k®¹&Ñϯ¾úÊ´jÕÊ*+vãÇ�7{챇©Y³¦Y¿~½yå•WÌàÁƒÍ7ß|c}®í[n¹e®h(@€@‰ÈÕÓEëZ @È•‚ö\ÉEWA{tl©€ „€ž¹¶Ÿ¸×a,©¼ ¬Y³ÆŠÆ®çãýÙEc³Í6³¢³Gñœ|yÓ¦w€ �»ï¾ÛŒ1"Qu¯^½Ì�!Cxe& ØÔ ø& ŸJ¾U.‰Ë£¹P£Läç�7.Ðk\QFgWŸ² ÚY|РAV÷Ý¢wÛÂm �é{óÍ7ÏÕsÏ=gz÷î�±ž}÷Ý×L™2%á7K„®èê~^:;âˆ#,ÑöÆol~ùåÓ¹sgóÞ{若§—j’…õA;å´g*¯¼M›6MdÉ¥ÿÎqÔ8H0¦Ÿ9sæx6ÑEYQðµoqå•Wfíf˜Qì³6F@€bG@çÉë– Î�;7q>´,ù!@€‚öøÍíñ,‚ @�HÄÞ¦M« ‚ö@èJ*³Äë±ëPX›¼A’D‚ú!A€âBàçŸ6cÇŽ57ÝtS¤áÇ[QRI€ M ×'îm;;ì03iÒ¤B›M{L !»�+Êèìj#Y­¿-E5ON ,0'žx¢õ+EæÞe—]\?WDóÇ<A»øè£�Ì¿þõ/3oÞ¼”öj×®mE[—пJ•*)Ÿ­^½ÚzíŽ;îHù}—.]¬2Ž�5Ê´k×Îú°#°/Y²ÄôèÑ#¥ŒÚmܸqγ6YЮño¾ù¦¹øâ‹Sê³éÓ§›�vÚ)­� ýOG7Ûßxã + ü´iÓ\û¤¨í²çí·ß6Ë—/OËÓµkW3`À€¼Dþ9ä  @±! óû…œ\Œêß¿¿µ#A€ �\ hÏ…Z´e´GË—Ú!@€@ä^}õU£h`JÚ#Ç]ð‚>ï4�èì2„ �Þ}÷]sÈ!‡¤”¸á†Ìᇠ²BÈ�@®OÜ'·*Á&/"å7”ÎN@Bf‰~V¬X‘=³Kލ£³çdT� }ÿý÷æÇ´Äëm´‘©[·nBˆîeŠ„íëÖ­³ò׬YÓz-[Z¿~½ùᇬ25jÔ0›nºi¶"?O´Û‘íÕ†lûí·ß¬²õêÕ³ÚË”ré¦údƒ^·x饗̲eËÌ /¼`–.]šV¤mÛ¶FQð›7onþþ÷¿›ZµjåŃ€ ”�Ž;ZõrM 4°^�!A€ �\ hÏ…Z´e´GË—Ú!@€@äôwß¾}­v´GŽ»` (âžÄ ŠÊžO":{>ô( @…"ðÔSO™~ýú¥4÷ðÛfÍšÊÚ�*˜@“&M,Af>IÑšm˜�(È7T¤p]xÎ'E�=Û(›™€› =®Ì$²×\ÓÏÔ©S]ÍìÙ³§Ùo¿ý¬ ñI€ T]ÒìÔ©SÞŸ;w®‘OG‚ @A hJ,úüÚ£gL € @ R³fÍ2gŸ}¶Õ‚öHQ¤ò|žŽwHtö‚ �@€@ˆî¸ã#Q¨�N<ñDk�³å–[†Ø UAø�ùóç›�æ�D )H“ÀäɓͤI“ò²Ë¦V­Z™3f„i^ÁêRðl‘ÇÝŒQEb�Kʧú®:õÔS­®ØÚãÒ¯lv|öÙg �ûƒ>˜–]ߟ¶¸]ÿ«ˆö$@€ÊŸ@/e‰RÿþýͨQ£Ê=„ @ tÚCGšw…ÚóFH€ @ ¸ôÜøèÑ£Ù´+î0„ÒºÄì—^z©Y¾|y(õ�=ŒT@&°fÍ3vìX£5Ž�.ºè¢P§î ÍA%@ ,A»ºJôëð2Q¯uéßÃ|£²Û]¾ï¾ûLëÖ­KˆÀS“#“5¾~ýúF¯ÀÔ®];hÑÐóçÛ�áÇ'‚”š Ý SÑXíîgÒ<µîÍ›7},¨€ �x°Ï¶òµ¦AƒÖÚ‚@€‚@ДXôù´GϘ @€@¤®¹æ3~üx3lØ0sÖYgEÚ•GK@OnKÔF":{©€ŠI@¼:wîœbÂm·Ýf:tèPL³h(3a‰(¶Ûn»ÐÖòe†˜îä@@—»$èÕÿ†‘J9:»"zçº×±ÕV[™… šÍ7ß< ŒyÕ‘o?Î9çsþùç[6¨®}öÙ'/{âTXû ¡-Z´È¼üòËi¦iíg ÜwÝu×8™Ž-€ äA@QÕõ"Q¾©”׺ùö�ò€ äGA{~ü¢(� = ªÔ @€ Hà /4Ó¦M3—_~¹éÓ§O[¦©0 (BY§N�B«’èì¡¡¤"@(2�¹sçšSN9%ÅŠy󿙯�Ù2š‡Ê�€ÃM›6Í»+#GŽ4 È»*€€(:».­‡•J5:»ú¿~ýzóóÏ?ç„b£�6ŠEtö0úñûï¿›Ï>ų̂O;í´“©^½zNLâ^híÚµ‰èíwÜq‡«¹Ý»wOÜ…Ÿ@€@iÐKDº´–oºöÚk�Ö$@€ ”‚ö Ä¢Ï� =zÆ´@€"%`/²o¼ñFÓµk×HÛ¢òè :ÔÌœ93”ˆÎ F*� �˜¸å–[¬ |v8p Ñ®ÚµkÇÌRÌ�J�@kq½*Q§N�Rë:öÆ”€.;ëÒs‰ˆ•aP¤ŽbøüóÏÑÛgÍšåj†.ÙÜkÕªU,Si€ �tìØÑ¼ùæ›9”ÜP„—²rFGA@€þ�‚öøMíñ,‚ @�Ø‹ì{î¹Çì¿ÿþ�Ê’9Š i÷†èìñW¬€ �ð |óÍ7fܸqæÎ;ïLT~饗š¾}û†ß5BC ß×’Ž=öXsÝu×U /:-�Å‹›ž={†Ö+CCIE1 ðÖ[o%îO>ùdšE-[¶LˆÛõß$@€âM ß—‰x)+Þã‹u€ �¸@пBп1Á"@€ ˆ€½Èž7ožiܸq ²dŽ�±cÇý„•þýï› „Uõ@€bG`Ù²eæÈ#�L±kêÔ©¦mÛ¶±³ƒ �Ò Ð£G³dÉ’œŒ½ï¾ûLëÖ­s*K!8 „ñb€]'yz�• IDAT+™_åNà…^HÜ_|ñÅ´îzè¡ �ûî»ï^î8è @ ää쇗jKn¸1€ ;Úc7$A{üÆ‹ @€@ ö"[‡xõë×T–Ìñ  §±W®\Š1D‡ #•@€@‰˜3gŽýôÓÍ�!CLµjÕ o -B%K@QÖ?ÿüó@öóÄ} \dÎBàöÛo7£G�…—-BÁH%%LàË/¿LÜgΜéÚ“~ýú%îŠòJ‚ @ ð/^lzöì¸aÎB#£ @Úã7%´ÇoL°€ ø& èSö“ÉŸ~ú©ïrdŒmÔjÃ6ŒDtö0(R ”*‰–Z¶l™bþUW]eŽ?þøRívC&0vìX£Ÿ iùòå¦N�:AŠ�žÂ|½‹èìL4¤xç�w÷Ç< OóæÍâv]p"A€ P8A/sR¸±¡%@€@9@пÑEп1Á"@€ à›ÀÊ•+­Ã6%í¾±Å&ãŠ+L§N�B³‡ˆ$¡¡¤"@(a/¼ð‚éÞ½{J¦OŸnÚ´iS½Ât@ Ö¬Ycš6mê»)D¾Q‘Ñ�ùóç›�úÈ™= ÑÙ³3"^zé%³hÑ"Kä¾dÉ’4 íÛ·OÜ›4i0@€"$ôrñ}÷Ýg¸€á€P5 @ B h�ß@#h�ߘ` @ðMàõ×_7]»vµò#h÷�-6‡j¼ž½j$bš ÄÈ@åNàþûï7Æ KéæóÏ?o’6(Ëýƒr d�Žˆ"Àñ$pÒI'·¨Ñ¹ 2dˆÑ ðG`ݺu‰èí“&Mr-Ô­[·„À}ûí·÷W1¹ @ðE 9xS¶­Zµ23fÌÈ–�Ï!@€@VÚ³"*xíGNƒ€ @ <Ï<óŒéÓ§�U!‚ö𸢦 Ñ³ÙDtöl„ø€*‘ÀŸþiåkܸq‰îKàwöÙg›�6Ú¨‘Ðg@ ¿B "`3•Â$àwÞùis³Í63‹/6uêÔñ“�<€€ �ÿüç? �»—`®oß¾ �ûæ›oG@€ò$à÷‚çµ×^›ö2_žMS€ � %€ =~� =~c‚E€ @À7�Ù³g›3Ï<ÓÊ� Ý7¶Xd ú„f&£‰Î‹!Å@ˆ1�O>ùÄpÀ)^wÝuFÿ†’ 8 ôèÑÃ,Y²$#˜‘#Gš¡5j”™ �û[l­AÔ@(!Zk}ì–† bôC‚ @a@ÐÉðêAÐKj‚ @'`/°'NœhŽ8∂·OƒÁ ¬\¹Ò:´ ’ˆÆ„y!@Á¼÷Þ{¦}ûö)…X[cHn”#�5kÖXBaçs÷ºdšËåÔrdDŸò'�I°ãV»æ_�=¬hì 4Èßj€"'ðÁ$î�=ö˜k{§Ÿ~zBà^¥J•Èm¢@€@œ tìØÑ¼ùæ›i&þûßÿf ç�Ã6@€@ @пACп1Á"@€ à›€½À¾ûî»Í໋G`èСfæÌ™Y {VDd€ „J`Á‚æÄOL©ó¡‡2{î¹g¨íP P:ÜÖî#GŽ´¢b“ ¿þa«V­,»~H€@ixå•WŒyúyþùçÓ:sÐA%ÄíÍš5+íÎb= @ ÷ß¿6lXJIý¹îºër¨�"€ @À›‚öøÍíñ,‚ @¾ Ø lExjÚ´©ïrd,¯(�ÉÖ�½8cC«€ ›À”)SÌÅ_œdÙ²e¦nݺ@‚*Œ€ÛëJË—/7uêÔ©0t7 ò3ùñ›m¶™éСƒ‘è�hìQŒuB øÖ¯_ŸˆÞ~óÍ7»tøá‡'î 6,¾ÑX@ˆ˜€Û:yîܹ¦I“&·Lõ€ Tíñqíñ,‚ @¾ Ø l=y¯ˆÞ¤x˜0a‚¹úê«ÓŒ${¼Ç ë @ ò¬]»6í ôŸÿü§9ùä“+=†@…PDì^xÁ¢@TÀ Ÿ !wüøñæšk®I«µqãÆÖ+³sy"dèT�˜øïÿ›¸OŸ>ÝÕÚÞ½{'î\¸Œù€b äL ù%£-Zøzõ6çÆ(@€@Å@п¡Gп1Á"@€ à›À®»îjÖ­[gÞyçS£F ßåÈXо§è"v"{qÆ�V!@~ ¬X±ÂtêÔ)‘ýÖ[o5;vô[œ|€@˜7o^â2Ë}÷ÝgZ·n]½¢ q €‡QÀÄ›À‡~hþýï[?�<òˆ«±ƒ JÜ«V­ïa @À'qêÙ³§•û–[nIÙ›ñYÙ @€@VÚ³"*xíGNƒ€ @ \´#f—iµÍŸ?ß 8ÐèÙxEwíׯÑö¢M�€ �HÐ:kÖ,#A; ¨<¶ˆ]¢  `û‡ŠÀ.ÿðÄOÄ? ,u@ Ì ¼úê«–¸ýÊ+¯ôìéðáÃ-�û^{íUæ4è ”;ü°raú@(>íŧÚã7&Xø �ôŠ�Üd� @ˆ+�O?ý4®¦að?˜ € @€€“@1ýÙßÿÝ,Z´È¸ßxã�®ƒÓ¥K—Dôö�vÚ‰„@Aà?3�@€ ��@1×é  PZ´Ço¼´ÇoL°ðA€ 1�È@€J€‹%0H˜hð?˜€ @€€“@œüÙo¿ýÖ·ëç®»îr¬ã�?>!pßzë­PDBÿ9¬T @€@qZ§0›¬€@ h/ô,M"h�ߘ` àƒ€ýÊŸZ_c$@€ �#°ÑFY³±XbW¡æâTèÀÓm@€ ¸(öã�?NÜzè!×q<å”S÷M7Ý”±†@(ðŸCÁH%€ ä@ Öé9t‹"€@„´G7Ǫ´çŽb€@q °!V\þ´@€ò%ÀÆb¾)_Hø…¤M[€ @ˆ7�Rôg_ýuKà~ÅWxÂ=÷Üs-�û>ûìïÀºXÀŽõð` @ ¬ ”â:½¬„ÎA  h�ß !h�ߘ` àƒb> ‘€ Ę‹1LK#€ÿÁ¤€ @€l¥îÏþñljèí'NtØN�:%¢·7jÔˆÁ‡€oøÏ¾Q‘€ �� ”ú:=dTø € Ý¤gAÐ^`à4„C€ ±p8R  @(6‹Ežvs!€ÿ‘ 5Ê@€ @ < ”›?ûý÷ß'îwÞy§ë õìÙ3!p¯W¯^y,½ …þs(©€ �”Û:=�@ÐX²#h/dš€Â'À†XøL©€ ’‹…¤M[ùÀÿÈ— å!@€ P>ÊÝŸýôÓO÷Ù³g»ÜÀ�÷5j”ÏàÒ“¼ à?ç�� @È‘@¹¯ÓsÄB1@ íñ›Úã7&Xø À†˜Hd� @1&ÀÆbŒÓÒà0) @€ ›@¥ù³Ë—/·î—]v™ç$6l˜%poÞ¼9¥Â à?Wø û€ �"¨´uzQÓ4ʆ‚öø %‚öø� A>°!æY @€@Œ °±ãÁÁ´4øL @€ @À&Péþ¬Äíú?~¼ç¤=z´%pßu×]™8Fÿ¹ÂœîB€bD Ò×é1 L�@É@п¡Bп1Á"@À6Ä|@"  @ˆ16c<8˜–FÿƒI@€ Øðgÿ7Ö¬Yc‰Ûõ3eÊ×IÒ½{wKÜ®Ÿúõë3‘Êœþs™0݃ Ęëô¦A ¦´Ço`´ÇoL°ðA€ 1�È@€bL€�Ŧ¥Àÿ`R@€ @6üYï¹°råÊ„À}Ö¬Y® �¸×ªU‹‰UfðŸËl@é @ „°N/¡ÁÂTÄ„‚ö˜ D’Úã7&Xø À†˜Hd� @1&ÀÆbŒÓÒà0) @€ ›þ¬ÿ¹ðæ›oZ÷K.¹Ä³Ð�!C,�{Ë–-ýWLÎØÀŽíÐ` @ ì °N/û!¦ƒ�‚öБæ]!‚ö¼R P lˆƒ:mB€ �ð°±KjŠžþGôŒi€ @¥B6÷‘Z²d‰%p7nœg%#GŽ´î»ï¾{î Q²h🋆ž†!@O€uzÅO@ 0í�‘E^A{äˆiˆ‚bQP¥N@€ P8l,Ž5-åOÿ#†Ô@€ �r!€?ÎHþðÖ¸]?“'Ov­ô˜cޱÄíúÙn»íÂi˜Z"%€ÿ)^*‡ @ ÖéL@ (íA‰EŸA{ôŒiˆ€b@¥J@€ P@l,6MåMÿ#o„T@€ �²!€?ÍP~ñÅ �ûÌ™3]éׯŸ%nßÿýMíÚµ£1„Zó"€ÿœ> C€ �ÖéyÀ£(*”‚öø <‚öø� A>°!æY @€@Œ Ø‹ï¿ÿ~`+«U«¸  �ü�|èQ€ @åE¶0ãùöÛo[÷Q£Fy6xÖYgY÷Ö­[Æ(ZÉJÿ9+"2@€ Öé�¥Z”1íñ\íñ,‚|`CÌ$²@€ �`c1ƃƒiið?˜€ @€€M¶8sáÅ_´î×]w�§]t‘½½qãÆÅ1’V þ3“€ �b`�^,ò´ �Ò%€ =~c‡ =~c‚E€€lˆù€D@€ cl,Æxp0-�þ“€ @° àÏ.üôÓO–¸]?“&Mò4èꫯ¶î 4(¾Ñbþs… 4Ý„ Ä�ëô &A æ´Ço€´ÇoL°ðA€ 1�È�"øí·ßÌìÙ³ÍÆoœbIÕªUM—.]Ò~Ÿ«¹…j'Wû(WXo½õ–yíµ×LµjÕ ¯_¿Þzè¡fË-·,˜1ÌË‚¡¦¡&ÀÆb ^šŽÿQ�ƒN—!�²#À½ì†4¯á;æ…�¨xø³ñ›«V­JÜg̘áj`ß¾}Í~ûíg ÜëÔ©¿N”‰EøÏe2�t#¶ü®cñb;„E1Ì)Ú8æeÔ„©Ÿu:sJA{PbÑçGÐ=cZ€" À†XP©²à~þùç”6ÿøãK„»É&›dµå÷ß7*_¥J•´:jÖ¬™µ¼2(ŠM²Ø\unºé¦¾Ú÷ÓÀêÕ«Í.»ìb¾úê«”ì;Y¾|¹©Q£†Ÿj²æ)T;Y )`çØ%7­yäwØå~ùåóçŸÖ²0-åR_Q¤5uþù盫®º*í÷/½ô’Ùwß} fÚš5kL£F�"Ÿÿë A l,F•*##€ÿZ*†€EÀË¿ñ£üúw$èºÜ•Mß1uüsñõðÃÿÂw Ÿ)5B ðg A9¿6Þ}÷]Kà~ñÅ{V4xð`KÜ.‘;)<øÏá±,‡š2�edêŸÎÉüœ••£ }ð{‚ÿƒÿ“LÀï¼ :ƒæÇÿ JŒüA °NJŒü€‚öøÍíñ,‚|`CÌ$²ÄšÀG}d$ìv¦‰'šÓO?=«í—^z©çfü«¯¾jš5k–±Žûï¿ßôèÑ#-O¯^½Ì]wÝe‰CòMo¿ý¶Ù}÷ÝÓª0`€¹í¶ÛBiC•ª�|y„Uþý÷ß·„Ò^é¯ý«Åd‹-¶ðÕä×_mT&SÒP¦6}5T€Ld´oßÞ<ýôÓ)­e†©•6/Ã`F•G€�ÅÊóRî1þG)�¶—�lk\¯>l¶Ùfæ´ÓN3Gu”iÑ¢Eh¯@•3l N ÒÖèÙþ®‚úIøŽÁ真•6/ý0!J�þl)ŒRª�/¿ü²%p¿æšk<�¿ð -�{Ó¦MK¯ƒ1²ÿ9FƒQdS²­G³™·Ç{˜N�:Y?ò÷ ’”­Ý8ä ¤ÒÖ™ÙæþggqþÛÆ¶ð°N�%5A R h�ßH#h�ߘ` àƒb> ‘%Ö¼6’Ž;î8s÷Ýwgb|÷Ýwf·ÝvK‹ülw8[$j=çvðÁ›E‹¥1zê©§L»víBa7sæLÓ½{÷´º$f?餓<ÛPÄŽ[n¹ÅT­ZÕ(úÛ¶ÛnkÄÅKdŸk;¡t²•<öØc¦K—.ž-KÔóÚk¯™† ú²îÊ+¯4#FŒðÌt“ÏW£eÒ߆¢°øá‡)-pÀfÁ‚�è2gÎKXåL·Þz«8p`D¨¥E€�ÅÒ¯J·ÿ£Ògý�šÀC=dŽ<òȼšÑºõ¾ûî³|Üà;¦RÁwÄwä›È�þlîìâPR/ŸJÜ®íÕy%½)�{’À!æÇÞüçØQÁ Ìv–Ô�Ûo¿Ýôë×/´`IAÛ�Cþ g ø?ø?6� ó&êyÎÙYÔ„©Ÿu:sJA{PbÑçGÐ=cZ€" À†XP©² ¼6?üp3{ö쌂öéÓ§›Þ½{{Ú›MÐþÊ+¯˜}öÙ'­ü.»ìb–/_n ÉÃH7Ýt“-Ñ™/^lZµjåÙÄäÉ“�¢¸ÛIbä… š*Uª¸–ɵ�0úXŒ:Æ�g† ’±élsÀ.¼jÕ*S¿~ýŒuI<¯9Y Ï{¾õÖ[¦qãÆiý9ï¼óŒ  ™¼. è ¬uëÖ…4…¶ [l,Ævh0Ì…þÓÑÈvÉ2Hëgœq†;vlI¬_ƒô‹¼ùÀwLgˆï˜Êß1ÿ¿3j€@¥ÀŸ-¯‘þꫯ,qû™gžéÙ±>}úXâöýöÛÏüå/)/!÷ÿ9d %\�Ÿ³Œ Ý“¿§z½Î‹‚ÖWjùƒœ�àÿàÿ؂̛¨ÿ&8;‹š0õ³Ng@A hJ,úüÚ£gL €@Ø‹*U”€¢À´lÙÒ¼þúë)íf´¯_¿ÞÚ8ס³WÊv ­�ù &¤à�ÌÑG*EXOÞXÔsˆÕªUólã›o¾1Mš4I‰>íµ×š¡C‡f´+h;¡v²€•ýùçŸV’©S§&ZUT==µ©ƒ;=ûì³F²%?¡‹/¾ØŒ=:[U±øÜ+âÈ”)SLß¾} f£Æ©[·nF‘&’“¢†êyãí·ß¾`¶ÐâL€�Å8�¶9 à0' ­�ô²“ü‘°’"¬¶¨§´à;â;jÆâ;–Öß-ÖB Žðgã8*áÙôþûï[÷þóŸž•ž~úé �ûÆo^ãePþs b]p;Ë¡Z«ŠJö÷‚®cñððÂú桞R!À:½TF ;!Úã3¶%Úã7&Xø À†˜Hd‰5�\íÚH— =SÊ$h÷ŠÈÝ Aóæ›o‰£‹™ÜÖóæÍ3:t(¦Y±iû§Ÿ~2Í›7·ÆJIãµvíZë¿%–¶Eí'N4:TÉ”V®\™V'×£çvý*Ï=÷˜ãŽ;.6 2rþùç»FbÏvÉ#ìÎýøã�¦E‹)ÕÆŽ;îh½‚  $@À$žÖAqДérTкÈ?ð?üP"r#àµvRm{챇k¤u½:•-­X±Âõõžlåøå@ß1ó(â;–Ã,§(.„2Åå_èÖµöÔ¾ü˜1c<›1b„µo¯õk¥'üçJŸúï\�&Sñòóì<~ü½?üÐ4lذâ`Çe7ðø?ø?q›“ØS<¬Ó‹Çž–!Pª´Çoä´ÇoL°ðA€ 1�Èk^›yß}÷ÝÆ-ª‹[D §øX�Î$Þ�ýÓ%9Ñ›nºiÎ]ùè£�RXx ÚýŒ§¢®�5ʲEÈökÉÂx}öꫯšfÍš²YóBóUì4—«W¯¨¼[fÍ3»^EýwÖ©±:ôÐCÍÓO?�R\ý‘ˆ|ë­·ÎÛ¿HœÛ¨Q£´ì½zõ2Ó¦MsýûöªÛž;ê¿[¿ýÚD>Ä‘‹qlò"€ÿÁÜ€@t´Þßyç�ÓÐ:îí·ß6[l±EÚgëÖ­3‹/642¸¥‘#G&Ö»A¬ÏÕljb lÛ­ºµÞUÒ¿Ÿ5kÖ Ò%ϼQ¬5e§½vÕW­Z5åÕ®\ �ªÞ\íI.‡ï¸�¾cðÙ„ïœ% ø³q‰ÂÛñ믿ZâvýÜ|óÍž(pË~ûígvØa‡ÂYäñŸ‹<1iÞy–a›•ÉϳóÈßÓߨÉ'Ÿìéïù9±ë‹Êf§0öîKå ÿÿ'ׯüŸ\ÉQ.ÖéAh‘íñ›Úã7&Xø À†˜Hd‰5‰}ûöík‰[“Ó`,Xà*wnÚbödQ³êò´{E…×¢žëׯŸ0å™gž1�?þxJ$i “‡j ‰—.]j.¿üróàƒ&ÊLš4)!–×fã£�>šR^"þc�=Öì½÷ÞV1¸ýöÛÍ—_~i‰¼Ÿþyóðç�Cnĺ IDAT[Ÿ>}Ìn»íf‰£ëÕ«gN:é¤DT_?í$W¸hÑ"óØc�yöK¥²ûÎ;ïL³åì³Ï¶ÚnÒ¤‰¯¹¥("÷Ýw_ZäõðàÁæ˜cŽIDĿ뮻ŒÆ×v²Õ€82$Eüñä“OZ¢m;y Ú3]ŒPYç对ägsYõ)J¼ÆOö©/v¤ød;1^vÇ+ZüÔ©SÓ8J„¯ËÝ»w·æîwß}göÝwß´ î]vÙżöÚk)x7ÞÚ|Ö×­[×u|µI{ÅW¤‰Ñ%°4hPÊï5Ǻté’V�ŸÈù²CóT[3fÌHcÙ®];Ó¯_?sä‘GýE_d‚@l,2=J‰þG)�¶–ç×¶?“odçѰM›6i/ãèóÃ?ÜZSIX ”¯�ãäåXmé©|“‡zÈ,Y²$mXµvíÝ»·µö›¢XkJt±lÙ2óÄOxÚª·Ž>úhÓ¹sg£×Áü¤°ëÅwtÝ ßñ³ßÑÏ_&y ›þ,sÁ&ðÍ7ßXÂÛ3Î8##”o¼Ñ¸o¹å–eÿ¹ì‡ØWóñó’ý=·=7/Ù¨(üÕ¯ËÔóçÏ·^µ}î¹çÒ8èÌB>šÎÁ‚iŠâ ÿÿ‡³3__Ud*C¬ÓËpPé"&€ =bÀ9T� =h�ŠO€ ±â�äOàÌ3Ï4&LH©(“€XQ]ôti¶të­·Z‘ �IXzöÔ™.¾øb£hÝÉÉ˶wß}×<ðÀ®QÞµ‰§Ãp¥qãÆYblgÒ&fûöí­_K`¿Ï>û¸ŠO¼ú(QËÂ… ¢?íøé—6"_~ùesðÁgÃk®»î:×¾Ù%—¸Dœ²%mzê c¯½öJDH·Ë¸ xnºé¦´Hú¸Kè/‘¶�²‰’£³g²1›ˆH"]d8ï¼ó²u5ñ¹Ä,7]RȔ֬YcÎ=÷\£ùœ-Í›7ÏH|à¶I,Ñûm·Ý–¸, ‹ݺu3sæÌI©V—Þyç�”‹É¼"ʸ1òš—Qxà�®Ý‘]\Iï¼àÕå?ꨣ²áásÄ–‹± s!€ÿÁ´€@täÉÿp&7?ÅÍŠéÓ§[¢gêÔ©“%°¶_ZÊ×DZë�r ¬6äïH%QƒŸ¤Cê±cÇší¶ÛÎ3{TkÍY³fZ¿Ê@­Ïµ^®]»¶§½QÔ‹ïh¬ËÑøŽøŽ~¾WÈd'€?›�Q¥æÐ>³öá/¸àOz)U{ÂúÉçUи2ÆŽëÈÖ.·³ Yà×ϳ­õò÷ÜÎ@¢ò{‚œùØvŸxâ‰Öù_&¿'ª3Ù€ÿƒÿ£yÀÙYa¿÷h-X§Çc°¥DA{üF A{üÆ‹ Øó‰,±'DÐî}Z‘íT‡SLìöÔ¢6òÕY‘®�I"õF�%~­¼E8ÙÚ€ëØ±cB´î¬Ç~^Ü«- †)»aÆVQ/�p¦�»öÚk­(ñJ~Û±ëóŠŠ/�u×®]­¨é~“%Z·n�–ý­·Þ2Švî7i %DïÑ£Gšˆ9¹¯™úÛ¿S³fM£èßv’°[ã!æÎôÙgŸ%?›d}¬ñþâ‹/¬ÈàÉI‚ò1cƸvGâl?¼XdckƒX—ü »uDÑóAÝ™œÚ$Ö.%']öxúé§=‘¼ú{Ùe—™ /¼0åïÇíoÍ9ÿ“ÛÖ뚊À4©Ï~.º­—ü(6 A™6Â"€ÿIê�@*�L~Jò…ÙLܼž¬N¾,œ¯�c·åX6êâ©ó²±Ÿ9£µ¦"ºï°ÃiÙ£XkÊÖsÎ9Ǻì›Kòº8e½AÖèøŽFßß1—¿oÊ@ RàÏVÊHçßOíÑj/YÁj¼ÒùçŸo‰Û÷ÜsÏüŒA øÏ1„"›†ŸgwaæÌ™Öù…3éE®Ù³g'^N�ÂïQ›^þ¦ĺ̫€=zýØ™¢<áìlmÎÎŒáì,{�8?Ëä)-¬ÓKk¼°q € =£�j‚öø� A>°!æYbM@JàNž<9ÅN¯í÷ß¿%xMN×\s�Ùu×]�6î’“› Ýí ZeœÑ«õ;mü5oÞÜwD@•I¶Û«¼Sd=wî\ëÉù I"{=UŸÉN/1·[Dx‰ÏýF>L¶Ó-ú‡ú-‘»„$a$EN�ØÞN²¿eË–iõkCTOh& ò3Eú×ÉUW]•¨Wâ—)S¦˜‹.º(�…~ß·oß´îx=´ß•Ûì²ëׯ7mÛ¶M×­ÛÎï¼|àµ-~×_}"’»³=ý­êïÅ™’çd.óÒK,“ÜŽž*Í4¯^zé%£§WI(5l,–ÚˆU¶½ø•=þô>:™^m²/ÌfkÝë%*½à£:jÔ¨‘·�#¢\«~¯zÙúoî&�j­9cÆ Ó³gO¿¦¹æÓeëN8!峨êõë£&û^Î×Äð�uaßÑX¾7¾c^þ†@YÀŸ-‹a,x'~ûí7KÜ®Ÿo¼Ñ³}°�ÀÝí5Ê‚�CƒøÏ9@+³"^gê¦_?ÏF¢€@}úôI#”|F•ß“ËY™ÓP·þF}‚ÿ³a8;ËüÅÂÙY™}ñÒ�ÖéL@ (íA‰EŸA{ôŒiˆ€b@¥Ê‚pȺ ‘µ¹¥èÑ­&'Eï½÷ÌG‘ò{7AûøñãÍYg�•ÖG7!¬[ätÙ¥hÙ^ÂZ‰EV¬XaE·öŠ¼îŒ˜¡ ,0µjÕ2Ç{¬+EüûÛßþf~ýõWë§}ûöf»í¶³òúmÇ®ØoDøáÇ›�vÚÉHp¯(ân©K—.Vô�ä'a½Ï*/aºžÒT½Š¶¯1JލîlCB�¥K—¦DÎ÷º” áûü‘r9@åµQê<ôøøã�Óäië™[ 6œÉm~d‹Hrûí·[‚ô:uêXQнü‰'žpåè¡ÄkƒZhê‰QE+úá‡Ì}÷Ýg4^^I4/·ß~ûD–Ç{ÌhüœÉK¼¯|^Pô™^ÐßE¶yæv BeÔ®¢Eº%^�zê©f‹-¶°.-Ü{ï½F/%8S§N�ÌC=T–O{.”6Ëb+¦ø3Ôt´À¼ÖèZ÷i·ÕV[eµèæ›o6ƒ JË—ìäëãD½^¶l™Ù{ï½]ûª5í�wÞiÚ´icV¯^mþõ¯­¹Ý’D÷ò™ìÅZ3“8eäÈ‘ÖEìm·ÝÖŠB(î“&Mrõ}œü¨ê |Ç 3ßßß1ë?)d€€oø³¾Q‘1�o¿ýÖ·ko6SÒ>²î~ÖÆqŽÿ‡Q(® ^gAü<õÀíåb»gÉ/ÜFá÷¨¯èðê‡Ú”OS»vm+P�¢È»Or{y,ê3üüÎÎ6ÌüŸâþ[P¬ÖY§‹<íB t h�ߨ!h�ߘ` àƒb> ‘%öÜ6Ã4h` 7$¶Ó‹/¾hEæNNÚ([¸p¡™5kVZäv§ Ý+ƒ›([md‹<(µ"Á…ÝkÖ¬±6%ÖÎT>yƒ1¹/ wëÖÍÌ™3'¥�Ú” Z‚w·äe§W;Ï<óŒ9øàƒ=ç…„Ñ_K0b'¯�΋b°×^{Yö:Óyç�g‰NœÏJjœÎ8ã W{Ü¢Ì/Z´È±8“Ä5Ú(u^lp£;£³«®7ÞxÃ|ðÁ樣ŽJã¿|ùr³õÖ['~¯(Bb([œICÚˆÝrË-S>ÒøªŸnQ‡‚p”0FQ«W¯žR¿Ûëvq”à\›ÊvÒó¾#F¤?3˜)ʹ—¸ÆíJ�y¹jÕ*S¿~}×9ððÛ®]»¦}vÓM7¥rÉçXyNt>€@Œ°±£ÁÀ”¬ð?²""r"àµF×…Þ§Ÿ~:ë…=­§š5kf]¾u&]´×}ùø8Q¯�½.0«?ZÏʬ[·n¢{™Ö×”�5ÊÊÕZS¾�^¦rŠ5n»í6ë2gò²W~—.ÜÚþCTõÊž ktåÇw4ßÑ|Çœ¾Ö)�Š!€?[1C]ÐŽjm$�»þöJ ~!q»~ªU«VPûü6†ÿì—Tùæó:ËЙ”Î�ªT©’µóï¾û®%÷z¹ÔŽ|•ß#?FAjtFäLÎ 7™|çy]¦³¤°Î@ð6Œggé/Ûs™³³¬_Ad(a¬ÓKxð0E"€ ½Hà34‹ =~c‚E€€lˆù€D–ØðŠî �ºF�YökÓLÑ›%äMNŠÆ¬ˆƒŠ"Þ¹sç”Ïœd^›WŠÎ¦ÈáÎä&˜µó(:´>wŠŠ“ëð*ïÕžW” /Á½ÝVÐv2EPW”›ÓO?=�…„ÌΧæ•I¢~m¤*r¶’×X*Ÿ„É"y»‘L�ÑìUæŽ;î0ýû÷O±QBfE闀Ǟ3v†§žzÊ´k×.‘ß-:{¯^½Ì´iÓ̰aÃ̸qãRê–ˆH—&ªV­šø}&�&Aµ¢ñ'GIOn(ùÉM¯úÝ.zØuè†5nÛΨè^O�f„{ÙïŒ*)›¼æ¥s<”×M$¢ßk\t9ÀÞtIæå4“ ?mbó Ä„‹1ÌðEÿÃ&2A 0¯5º^7=z´g}z½FkÕ¾}ûºŠÙUðå—_N¼B”��õX‚çåR»ãZc7iÒ$�ƒ.¥&¿dg°/>K ÕZóí·ß6»ï¾{šMnë];“"j¬7nl¶ÙfëGBözõê%ꉪÞLkt/ß1ýe/|Ç Sß1ð×< P¶ðgËvhcÕ1íwJ஋š^IÁT$n÷zí§Â.õxµév–a¯¥´ï­}zgÒ÷ª(}þùç–ˆ\_¼ÒàÁƒÍõ×_oíŸGå÷¨m‰åuö¢ŸO>ùÄ h$Û/»ì²´F^þŒóì)ê3üŸÿ­Û9;ãìL³�³³xýûPkX§‚2m@ ¼ h�ßx"h�ߘ` àƒb> ‘%ö¦OŸnz÷î�fg²síö4`²HZ‘µwÞyç”:’íÚ\S‹x %�nß¿õÖ[iQ\¼ô*¼Ë.»XѼ3E~ÉT^z»îºkZ½6ú2‰X‚¶£üŠ(a‚39EÏÉŸ{‰Î“çüñ‡iß¾½AÒ™&Mšd à9Ï<óL3a„´ÏÝ¢ÌKpïŒrn ª%rvŠJœ¥n»ù(²ü�˜u]QÕÇ�ŸUgŠé¼Dáì�¢Zzè¡®Œìù.Žnv¨.¯h�v;~9þøã�¦E‹iÑ$ÝÄûÉ}p{%AŸ;çh�y¹zõjë É-ª¦ 6�Þ{î™Ö6åbÿ•��.ØXdZ”ü�R-l-™ÖèêÃØ±c�Ö>Τµï­·Þš±›¶øV¯$åããb ìåKÈ�Ð:Øí’£.ëõ¦í¶ÛÎ4lØÐºp+�ø;ì`]xVÔ¿¨ÖšŸ}ö™IÚäOŒƒ.ñêâµì’%hŠªÞ ktÙŒïè~ñßqÃŒÆw ú—M~”/üÙòÛ¸öL{§·ëGû¾^IB[ Ü�g…ìþs!idz-·³Œ°,ÕÙ–Ö¦ 6§=v�Á{ì±iÝL¾D[ˆ3üŸ CÀÙÙœ�!hë»·”êa�^J£…­ˆíñ‡d+´ÇoL°ðA€ 1�È{Ë–-s�œ’,L3fŒ>|xJ_’ÅÎnÑš%¶~ðÁ­gÝïªìž{Kt¦LQÃí¨ð™Àz•Ï©Ü+º¹—�j?h;™úå%´W;n ôûdq‰×“–tèéI LÜ’6µ©¦ç7�É)ÐË~[P-ÁŠ¢”+¼�’Eæný°E>ß~û­iÙ²eš°Ú)"÷ºx ~ê³m·ÝÖsjdzžsÅŠV¤F/Öª_y¼"¼«Q¯M9'G¯¿‡äˆ.n�PÔ˜>}ú¤}䜣A楗H^�èPJb$g¤mÂ(é AƒÒ"‘"h�ýW>º`c‘iQJð?Ji´°µTdZ£çÓEP^ºt©%òÎä;è³l>N±ÖÀ²-SÄól|¢\kz½b•l“|X]hÕ…fEc÷#p�²^ù;Î�¼|T|Ç�ŒDBÉ ßñ4ð³}ûð9*‡þlåŒu\{úý÷ß[âöSO=5£‰ X"�»ÖÈ…JøÏ…"Ïv¢òóÔÛ=zX—›7ß|s«óQú=Nº¿ÿþ»% ×åb�Çüúë¯Vó‘¿£Vú{s¦äj qäŒ"›¿ÌÙggø?ñüžÅªÌX§3C � ´%}~íÑ3¦@ lˆE•* NÀMŒ.#laª6Å=;9‚³S$íVGrÄq7A¼îu˜¯ÎäOÞÚ¼r+“\‡Wùd‘½³ÍÑ£G›Q£FeÜèók§W;^v)ºš"«{ ,t( <Δ,ööÊ£ˆöË—/7U«Võœ[^Ïa:7 W®\é*è¶Õ?ýô“iÞ¼yŠ =™…[;öFª¢îï±Çi6.^¼Ø´jÕ*ñû›nºÉœvÚiiù2EŽ´3gŠboÏ÷Ç{ÌtéÒ%­~Íg ÊuAÃ-Iô­hòÎèõÊëäøä“OZÂgÊ5’~òf´ê 2ÿ¯¼òJ3bĈоw´‡†’Š H€�Ŧ©¼ àä�� �FÀkí”*ù-Zß'¿^”��õøþûï·DΔÉgóÃ'굦—çe›òëŬL—`U6Šzƒ¬Ñ3­éñ›Y/¶á;æö ¾£Ÿo.ò@ ´àÏ–ÖxU‚µŸ~ú©%p?ï¼ó2vwÊ”)–À½zõê‘aÁŽ mITìu–‘�ñòón¹åsä‘G¦¼`µß³nÝ:£3ùmÓ¦M Üç¹Z!Î@ð6 gg^ãìŒí�¿¸Ê ëô2Dº�@Ð^`à>šCÐîY �ø`C,~c‚EÁ d´Ïž=ÛtëÖ-¥b§xØ+B»ÊþðÃi‚xU–áÝiµ¢…ë‰xgò#ZV/q÷W\á*Þ•Ðùè£�6sæÌIi2›€>h;^ùGŽé*¦·�ñ°$‹½½òèðફ®òœŠÜqðÁ§Ehw‹èiÄ>ׯT÷îÝ�žµ´“}±á“O>ñŒ°·ñÆ·ù⯺ëׯŸ¨Ïë™ÐäHð^�ÍmÑb{‰W²ÕŸ)âˆ"»×©S'aÖ„ ¬hîΔI ¾~ýzëRƒò$'·9d^†ýì*‚öàßÁ”(>6‹?XàŸþ‡Vä„€_™¢éù­#9Ÿ.ÉžsÎ9¦V­Z)Åóñq¢^{Õï×ÿòâõZÓí…(?c¦K¨§œrŠ‘â–¢¨7È=“O‹ïˆï¨ù�ïèç/�<¨ ø³•1Î¥ÜKZÑ:è²Ë.óì†Öηï»ï¾¡vÿ9Tœ%WY˜~ž^¨Õ9‡^~ró!¢ô{æÏŸo:vì˜ðÑY�P©g ø?†Œ³³Ì/]sv–ן6…cN€uzÌó CÚã7(Úã7&Xø À†˜Hd‰=¯çëu@º÷Þ{›<0Mì쌚�IЮ ·Î�;§pp*'gP„–~ýú¥±³£dƒêUþ¡‡2ŠîLnQè•ç€0 ,ðŒœ´�\ú%‘¸XL�:Õ“¡ò(ÒàäÉ“Óú–ÅÝ�›—ÈÛ-ʼž«ìÝ»wšÉQó�›·6ÃþóŸiÂúäèpnÑÛõļ=jÔ¨‘hÓksxÞ¼y¦C‡§Æ7ß|cZ¶l™òÚ€ èÅÙÒ°aCëò†óbƒò<õÔS¦]»vžõë‰Oº$¿d ÌÚ,V}vdw?ãéÖȪU«L³fÍÌW_}•ò±Ûõšg�<òHJôùLó&ÛߘÛçú»ÖäV[m•KqÊ@ hØX,zÎ�þGÐ(�,ô OŸ>}òâ¤uÐ…^h]îL¾Œ™\i.¾€]>Ê5°Öܺl©K—Δ͗È­Pk͵k׉¼ÇŽh ³=Yv½øŽÆ8/ãâ;~˜2gñý “øÿðg™ ¥F@"WýŒ?ÞÓôK.¹Ä¸ëåÑ|þs>ôJ¿¬ÛY†z%ŸMâtgúõ×_-ŸÎ¹¿¯|^�’ôY”~�×KZAG'ùR¬ì�ú DöáÿàÿØó”³³ s�³³ ß\¥ŸŸuzé�!=€@¡ h/4ñìí!hÏΈ€@ °!ÃAÁ¤À¼¢ÏIH,îî»ïžR§6’åxÛÑô¡— ýÁ4ÇsLš@øÜsÏ5cÆŒñ´ÕK°á|Û«¯òêS“&MÒŠyE©ÏÝ”üÙòÇJí…Î%ôïÖÉ'Ÿœ�ö›%pßf›m¡Â„«ì2çri×K„­€8:[ØvÛmÓ8Eå÷Hœ$jJiWöèï¢mÛ¶–?ª ªU«f]Qà gÒ‹ºz¡X)“½a��¨ücùÙ:ó²ÏR9;K�™œ�•Ý×.r`�Δ€‚@ДXôù´GϘ �°!Tª,8¯Cï™3gšwÞy'EŒ+ãî½÷^Ó³gÏ;Ý„Âzž^›g�5Jë“òëyF·äe�3š›¨\Ê+rû‘G™V¥60ûöíŠ�²KÑî% INÙúõñÇ[‘Ã�I|1Q±WÔo•É$BQ¹3Î8ÃUôáŒæ½~ýzW¡‹6†µg;æš7Šrb'm®ªß�>úhJ’/xEÈ— å´ÓNK”Ë´ÙêGlsÓM7¥ÔgW¬>\ýõÅBÙê÷z¦3y³Xí}ôÑGFzgÊ!R—+®¾úê¬s4Èü÷â©9);kÖ¬Yðï#„@1°±X ê´™+ü�\ÉQî¼Ö¸ÊíGìì—k�5š³Î¨×ÀùÖïÅ XkMùŸ|ò‰ùàƒÌã�?näx¥k¯½Ö :Ô×0æSoÐñÇwÄwÄwôõgI&@àÿàÏ2 ʉÀÊ•+­5ø9眓±[z¥T÷l{—øÏå4;‚õ%“Ÿç|}8¹f½ðª€HÎWR•Ç+J{T~�Û‹¶²£S§NfÆŒ®AeüˆÈ3]˜ë ÿgìâìlÎ΂}‘»|°N/Ÿ±¤'(í…"í¿íþY‘ˆ6Äb4˜’3¯Í¥O<Ñ<öØc)›w 40¯¿þºõ!9¹EyWy‰ÖGŒ‘’792·›Ñ«V­2õë×Oûè€0 ,H‰ ¤¼óùîä²^*™6ð¼ìôjGù›5k–¶š­_Ï<óŒ9øàƒÓºš,öþñÇM‹-\#‹gŠjï±\�) ãÆ�í~ýõצiÓ¦iö;EÿsçÎ5�;w¶ÊI̾víZë¿%’¶7‚“ÅøúÌËŽgŸ}Öˆ��~ûí7‹Å¢E‹Òxd‹ÞŸI¨óÔSO™víÚå,h÷äËH§]šS+gÒåEuKÿùÏ<#9çh�y©�}EqqòÌvÉbݺu¦zõê‰CÓœ¿|(�˜`c1&�¾àøÂD&ø&àµF×:Vëáí·ßÞw]™2æããs œi�-?@ôZÃÊÏ‘¯ ±^½zЏ¬5W¯^mî¼óNËVgÊæ›fÓ õY£«M|G|GÍ|ÇP¾~©eO¶ì‡¸¢;¨—r$p¿ä’K<9èr¢Äínû­øÏ•;}¼Î2´ï-ÇíüɦåuV¤²ò·Új«°Qø=™½ûî»®A¤¼ÄøÎ½þ\íAÎ@ð6LÎÎŒ 2oð*÷;»\{Î:½\G–~A :Ú£c›kÍÚs%G9@ ¨Ø+*~‰@&¡¯ÍÚ8¶Óå—_n.¸à‚´–�›`^BfÌCŸ{mZ(BõUW]•µ×^åGŽiF�åZÞ-r…×¥]AÐv–-[föÞ{ï´ö³õëæ›o6ƒ J+—,öþã�?¬'#çÌ™“–Ï!ÜÎ aÌQG•9]ŸKˆ¢ÍÙ:uê$ê{ã�7Ì{ì‘V¿SPýþûï»n¨&LŽÎ®ßËnÙ’œ¼DD^O…Þzë­fàÀ�žóÃ9ÞΨhé²§F�í™æ­Wäw7aøôéÓMïÞ½Óì¼øâ‹­HΤÍëáÇ»FgWý²}ë­·N :/½xz‰—4×Ú·oo–.]j½ÞбcG³Ë.»XüH(Ul,–êÈU¦Ýø•9îô::^kôä5b­çëãD¹Vÿ¼ê×KV‡~x‚L"ˆ_|Ñ4oÞÜ*ÕZó—_~1ºÔ+¡ÊgŸ}fªT©b]ÔôJ^/Z9_IŠªÞ kt|Çÿ�$¾#¾cßÁÔ�r&€?[ΣKßœ–,Yb ÜõZ¨WÒþ¿î»í¶›rùðÆ IDATÁ®Ü9äu–áÇÏËt¾áu¶ß#kŸ}öI `¤s�×éìÄ™¼Î(œ˜2ùra��àÿlÎÎŒõbZò+Ìö¼åì¬r¿Ÿ+©ç¬Ó+i´é+Â!€ =ŽaÖ‚ =LšÔŒbCMCð:ÜW“NA»žmצŸ3eÚKΛ-¹òz ~õûã�?>+ ¯ò^Ân¯þgÚÌd§W;^vM�:Õœp ®ý’mŠx¨gT““›ØÛkÓTý� "9rˆ¢"H$/›Ü’[”y=cÙ³gÏ”ìn‚êl‚vgtvUèõDóìµ×^K{:Ó+BŠ×ëªÿ£�>2­Zµr}*Ԏή|™æ±DåW^ye.E7oÓ¦�+G·ùî%¬?ãŒ3Ìõ×_o6ÞxãD]Šnê©§zŽÓþûïo.\hªV­š(tþ{=]ê%°Ÿ={¶éÖ­[Zõ²OŸ>Yÿ>É�8`c1Ž£‚M^ð?˜—€×Ú)ŸÈÝnæëãD¹–½^OÓë9{‰Ú7Ùd“”nÉ?Ѻޙ´†×ÅH[`ÅZÓ­N7¿$Ù6¯¨‰W\qEâE±¨ê•A×èøŽFßq¦éÞ½{Úß¾c¸ÿPJ�þl©� öçJà‡~°Äí^/^êÕÓƒ:Ȫ^{ì¤Ê"àv–!~ü<Í­ÁtÎãL^gFaû=™‚P¹½*ü /Xçn)ÙçÑç…8ÁÿÙðb±3ggÿ›¡œ�UÖwr¥ö–uz¥Ž<ý†@î´çÎ.ª’Ú£"K½€@¤”DŠ—Ê HÀM ­h⯼òJŠL›}n›`ÉQÚíJæÍ›g:tè�±gA£Y8+ Z^‘ýyüÃ?L³kÖ¬YVDB ‹%®]»v"OÐv¼ò»m@Ú�xm.ºE¹ôÒK�DÈnIcqã�7š]wÝÕ¼üòË®’Ë]vÙeVôíää¶)«ÈÜ�W¯^=‘5Ûåg„=mK íŒ.ï5ß¼¢«ÈqyôÑG­OrúòË/­‹©ãÇ�O399ÂbTõæ²FÇwÜ0TøŽî¯{‰ ¾c�o`òB ¼ àÏ–÷øÒ;ÿ¾øâ Kà>tèPÓ°aC#A;þ³~å–ÓK`î×ÏÓü9øàƒ]±ÜsÏ=æ¸ãŽKù,l¿ÇëB®•PZçl{íµ—Y½zµ¹÷Þ{­3¯ä´â $¨ÿ‹ÿƒÿ#œ�•Û71ýa�΀‚@ДXôù´GϘ �°!Tª, ¯ £dc´!ܺukWû²‰˜UÈÏsŽÚ¨SÔim&'mÒ­X±"%ʸ›!™Ê¿ýöÛf‹-¶H+¦ ¼¾}ûšiÓ¦ed/�õ]wÝe‰™ƒ¶“)¿3JC²Ÿ}öYbã=ù÷nboE!w‹žïÕ)gôýä|Î(ó¿ýö›µ�«häÉÉ-b^¦è!nù-^óJOe&§ &EžsK^$í¼º  (’É2œõHŒ¯è‘5jÔHùè±Ç3ŠPFrÛ Ï´¹íÖf¦qr>qt^ª=¯ ¶-º !Ú÷JnÂý0øQ E€�ÅB‘¦�0à„A‘: °�€×WŸ=ûì³FÃÂHùú8¶ Q®�3±ðËÀÍß‹b­)Q}‹-Òü‡äõk�=¬K™º°dÉ×.8_½ŠªÞ kt|Ç Ã…ïh¬}Íu¿ ßÑ/)òA ¼àÏ–×xÒ›p à?‡Ë³Tj ÃÏÓy×�˜vN%…ò{üœÙùEœ¯[·®5j”©W¯ž‰ò ÿÇÛŸáìì3–³3¿½ä+e¬ÓKyô°Å!€ ½8Ü3µŠ =~c‚E€€lˆù€D–’ �M!ñ¯„׊Rî–üÚ§N�jN8ᄌ<¾þúkÓ´iSóÕW_¥ä“Èýé§ŸN{êÞY™Wy·çë’ËzEìHÎ3qâD#NJAÛÉÔ¯… zrõŠâ%öÎ¥ÝÉJwÞyg+¢3)êºDávúî»ï̾ûî›ÅÞ)¨V~‰V$DWDxgÒ¥gÄD¯gŸzê)Ó®];×ù"{Ú´iã)bÉöG§MgE@×&®3eÚ¬ÎV¯ós·K ™6-Ýê—ЦI“&®BgýAç¥ÝÞÇlE.Ê%‰¥æKòë¹ÔC“‹Å¤OÛA à%F~xðZãjý¥õM®ë#g‹ùú8Ékò¨ÖÀj#SølóH�õÌý;ì�–5е¦Öò-[¶ÌfVÆÏ�—x•9Šzƒ®Ññ7 ¾£1øŽyý‰SC¶b†šŽæ@ÿ9heP$,?/S´h½Üª—}“SØ~OPÿL6=÷Üs®/�ÙvÚç Qž�àÿl ÍÙYæ/ÎÎÊàË–.d%À:=+"2@Úã7%´ÇoL°ðA€ 1�ÈR2mΩnÏ(&w,ÛA«^Ò“ëQÄl=•èL~#@{•¿øâ‹ÍèÑ£=ÇÂ+zr�d�uÐv¼ò<Ø\ýõVÔw·tóÍ7›Aƒ¥}ä%ö–˜|äÈ‘FÂöL©S§NÖó}Æ 3“'ONÉê6V^›§^QûÝ.HxõuΜ9樣ŽJ±A""1Ëq~íÚµæœsα6ƒ¤I“&™>}ú˜jÕªyS´ûV­Z¥]¬p+°xñbóè£�šË.»,ícEÔÓEgÒ匶mÛf5[c¡è úÛq2Ra§È#è¼L6@]»v5¯¿þzV»ì bôÈ#�¸^ ð] !l,Æ`0Á7üߨÈ�¬¼Ö¸a_ØË×ÇIîH”k`µóÖ[o™c�=6ÐÅÑ:˜Ûo¿Ýl·ÝvžÌ£Xkêõ( üƒ&ùz}¨sçήEî7èßÑ|ÇÿMM|Ç áä‡@åÀŸ­¼1§Çþ à?ûgUN93ùyZkËð“$Œßm·Ý\Ï€É-PQØ~�ü¬“N:)«¹wÞy§uæ‘éœÏù rTg ø?†‹³3c8;ãì,ë—W™g`�^æL÷ í@ͳJíy¤8 Plˆ‡;­†O`æÌ™FϺ¥ X"×-¶ØÂ³álÆŽkÎ>û쬆?ùä“F‘Ã�iÊ”)¦oß¾9—Ï&ÈWÅ+W®´ÄàN�·ÝèŠ+ŒžðVò²Ó«¯üWë)u¯$¾ž�LN~"F*:âå—_n~øá”²ã~ýú™Ž;š5kÖEÞwFÃïÒ¥‹™={vJ4|·Ctm€js²~ýú®ã¥vì$›—.]j5j”–WmuëÖ-å÷ùÝ\VôLÍ/½à•$²8p Ñÿú�$® k±?~¼kµÃ‡7g�u–Õÿ\ÆIb¡ /¼Ð<øàƒiõ‹×UW]eþñ�X슢Úi§¥äÓߥæd�:u¿:/� ëB„êú[ýðÃ=yjÎêG‚v¯ËYÿXÉ�`c1Fƒ�)Y àdEDø&à%íÕ«—™6mšÙxã�}ו)c¾>Ž[ÝQ­�ÕÖo¿ýftéô¼óÎ˺&<õÔS]/pºÙÅZsݺuÖåÒqãÆ Ñ3%Ûjß¾}ÆË­ª#Ìzƒ®Ñññ�óß1”¯b*�@ÙÀŸ-Û¡¥c!Àb V¦Ÿwå•Wš#F¸Rp{QHÃö{tÖ#Õm¿¾G�–}{î¹gÂÆY³fYA’œç>ng.Qœ�àÿÃÙggœ�•à?˜Ì:=¨T �2'€ =~Œ =~c‚E€€lˆù€D”mâ}ûí·–ðYB Š7ß|ó�w©tI‘óׯ_o™[½zu³é¦›&Læ™gÌÁœÖE—кÔÒêիͪU«Ì÷ßoªV­j�WÍš5Í6Ûlã[ÄîÖço¾ùÆ(Z‰6¢U§äŠ>Y£F�Pé N]¦PÝvýÛn»mÑ…â_~ù¥ÅSýVÒÆ‹.µÔ«W/´¾‡�J 6C€H#€ÿQ0Ô4�’ ÕØî¼½&”O¡µjXkÂ(Öš6‹ü1eì´~×%ÔZµjå4¦QÕ›“1ÂwÄwÌuzEñ÷œ«-”ƒ@%ÀŸ­ÄQ§Ï~ à?û%E¾¨„¹NÒ…ή”t6!ç/ù‹§éÊ«|¿üò‹u^¢³¯õHT|ó©ÿÿ'×ùæßu®6P®4°N/�qÂJĉ‚ö8�Æ[´ÇoL°ðA€ 1�ÈŒ€6á~øákc³J•*fç�wvm["eEPw‹j.¡û�X0›i€@± °±Xì ý ð?‚Ð"/ xÀwdn@(ø³å1Žô"øÏÑp¥V”"üŸR5l†@i`�^Úã‡õ(íÅ ž¹Míñ,‚|`CÌ$²@‘PdŽC9$íÉÉ ˜wß}×5šö¢E‹L›6mÒìR™×_ÝŠÄM‚ P)ØX¬”‘.�~â”Ç8Ò @Å €ïX ê´ @ Zø³Ñò¥öÒ&€ÿ\Úã‡õÈ—þO¾)äC€uz>ô( �Ê$€ =~㎠=~c‚E€€lˆù€D@ R«V­²ž—tKƒ6#FŒH|®(3f̰¢³»¥ &˜3Î8#R{©€@ܰ±·ÁžLð?˜€ �+|Ç\ÉQ€@| àÏÆwl°¬øðŸ‹?X�bÀÿ)&}Ú†X§3 � ´%}~íÑ3¦@ lˆE•*!�@þüóOÓ½{wóÀx–Ûc�=,Qûüùó=óì¸ãŽfùòå®ÝDf@%F€�Ű 7ÿ£Â'݇ �|Ç<àQ€@L àÏÆt`0+ðŸc1 �¢Àÿ)z†þ�ët¦ ”‚ö Ä¢Ï� =zÆ´D@€ ± R% ˜Àûï¿o5j¸œ]à¯ý«Y²d‰iذaÎuP€@©`c±TG®2íÆÿ¨Ìq§×€Â"€ïIê� ø³ñ¬ˆ'üçxŽ VA �ð I›¶ �d¬Ó™€@PÚƒ‹>?‚öèÓ 6Ä"€J•€@N^xáÓªU«ÀeÏ<óLsÁ˜zõê.K@å@€�ÅrÅÊéþGåŒ5=… |ǨÈR/ �ÂÀŸ-?‚öèÓ 6Ä"€J•€@ÎÖ­[gžxâ 3iÒ$óðÃ{Ö£ˆì^x¡éÖ­›Ù~ûísn�‚€Ê�‹å0Š•Óü�Êkz @ JøŽQÒ¥n@…#€?[8Ö´TzðŸKo̰QÀÿ‰Š,õB^X§37 � ´%}~íÑ3¦@ lˆE•*!�PüôÓOæ»ï¾3?ÿü³Ù|óÍÍ÷ßo6Ùdó—¿üÅú!A€Àl,2J‰þG)�¶B( øŽ¥1NX @À�þ,óÞ🙀€üæ P¬Ó A™6 P^´Ço<´ÇoL°ðA€ 1�È@€bL€�Ŧ¥Àÿ`R@€ @6üYæ¼ à?3; @(ÖéÅ"O»(]Úã7vÚã7&Xø À†˜Hd� @1&ÀÆbŒÓÒà0) @€ ›þ,sÞ🙀 ‹ëôb‘§]”.íñ;íñ,‚|`CÌ$²@€ �`c1ƃƒiið?˜€ @€€M–¹oøÏÌ@€ŠE€uz±ÈÓ.J—‚öø�‚öø� A>°!æY @€@Œ °±ãÁÁ´4øL @€ @À&€?Ë\€€7ügf @Å"À:½Xäi¥KA{üÆA{üÆ‹ Øó‰,€ @ ÆØXŒñà`Zü& @€ `ÀŸe.@À›þ3³€ �b`�^,ò´ �Ò%€ =~c‡ =~c‚E€€lˆù€D@€ cl,Æxp0-�þ“€ @° àÏ2 àMÿ™Ù@€@±°N/yÚ…@é@п±Cп1Á"@À6Ä|@"  @ˆ16c<8˜–FÿƒI@€ Øðg™ ð&€ÿÌì€ @ XX§‹<íB t h�ߨ!h�ߘ` àƒ@Ò?(>r“€ @ ®ÞÿýÀ¦U«V-p @ øùУ, @€Ê“þlyŽ+½Ê�þs~ü( @€@þX§çÏ� P)´Ço¤´ÇoL°ðA€ 1�È@€J€‹%0H˜hð?˜€ @€€“þ,sé🙀 ›ëôb�íC t h�ßX!h�ߘ` àƒOú€D@€ c<ýãÁÁ´4øL @€ @À&€?Ë\€€7ügf @Å"À:½Xäi¥KA{üÆA{üÆ‹ Øó‰,€ @ ÆØXŒñà`Zü& @€ `ÀŸe.@À›þ3³€ �b`�^,ò´ �Ò%€ =~c‡ =~c‚E€€lˆù€D@€ cl,Æxp0-�þ“€ @° àÏ2 àMÿ™Ù@€@±°N/yÚ…@é@п±Cп1Á"@À6Ä|@"  @ˆ16c<8˜–FÿƒI@€ Øðg™ ð&€ÿÌì€ @ XX§‹<íB t h�ߨ!h�ߘ` àƒb> ‘€ Ę‹1LK#€ÿÁ¤€ @€lø³ÌxÀfv@€ P,¬Ó‹Ežv!Pº´Çoì´ÇoL°ðA€ 1�È@€bL€�Ŧ¥Àÿ`R@€ @6üYæ¼ à?3; @(ÖéÅ"O»(]Úã7vÚã7&Xø À†˜Hd� @àÿ±w@WYß?E� ¸’ƒP |@‘(aE…D‚HÒ”•E]I ,¯ ˜É¢€""˜P2�2, ‚$ßwÆwÆ{ûÎÜî™;=wfî¿«¬]žéé>ý;=s§»ÿ}BL‹!vLK!€ñ:€€€€€€IãYôp&€ñ3z€€€@¶à;=[äQ/D—íáóíáó ,P € 1HÈ   !&€‰Å;¦¥Àø�@@@@@À$€ñ,ú8Àø½@@@ [ð�ž-ò¨¢K‚öðù‚öðù�(À„˜$d�ÀÄbˆ�ÓR`ü�N     `Àx}œ `üŒÞ   �-øNÏyÔ Ñ%A{ø|A{ø|‹@`BL²€€€€@ˆ `b1ÄÎ�i)0þ@§0 `<‹¾Î0~FïÈ|§g‹<ê�耠=|¾ƒ =|>�E  0!¦ Y@@@@ Ä0±bçÀ´ S€€€€€˜0žE_g?£w€€€d‹¾Ó³Eõ‚@t @Ð>ßAÐ>ŸÀ"˜S€„,    b˜X ±s`Z Œ?Ð)@@@@@LÏ¢/€€3ŒŸÑ;@@@²EßéÙ"�zA º hŸï hŸO`€€Lˆ)@BÈ€Àž={èÃ?¤3Î8ƒŽ?N 4 óÏ??ƒsûV‘g‹-¨bÅŠ¹ ­Èy˜XÌù.)DÊ]0@@@@´ÀxV+^q?ûç@¬+øÇ2ˆ’°®æ/eôy¢4ÈøNÏO£� àÚýcéWI´ûEå€Jb�âFe9H`îܹԣG«åO<ñ :4IøÓd‘çúõë©I“&þŽR@@ ¢0±QÇå¨Ùä¨ãÑl°!€ñ,º8ÀøÙ¿Þ�uÿXQÖÕü¥Œþï/O”¹Bßé¹âi´ü#A»,ý* ‚v¿H¢�@ `B,Pܨ, ¼úê«Ô¹sg«åS¦L¡ä š,òüøã�©~ýúþŽR@@ ¢0±QÇå¨Ùä¨ãÑl°!€ñ,º8ÀøÙ¿Þ�uÿXQÖÕü¥Œþï/O”¹Bßé¹âi´ü#A»,ý* ‚v¿H¢�@ `B,PÜ¡«ìôéÓôá‡ÒüA%K–¤¼¼¼ÐÙ˜‰Aah&Þätã'D’�óD�Ü#€‰ÅÜóy”[ŒñG”½ÛA@@@@À_Ïú˥ŋÆÏþùë ê,ݬר—ê.'ÖÕä¼Üø ý_Î9@R à;½@À-ÚÝÓŸ‚výŒQ€€˜Ó5BE~óÍ7T¹reÃâæÍ›ÓêÕ«)þüjAzSÃÐ>L¼É»“?>|˜öîÝkôSÞˆqþùçSñâÅå• €Ę&cìÜ6 ã�:M�0žõ·åŒŸýs3ÖÔYºY¯Q/Õ]N¬«Éy¹ñú¿œ'r€¤Àw:z€€[´»%¦??íú£ 0!¦j„Š\·n5kÖ̰¸k׮ĻôóåË¡¤75 íÃÄ›¼;…ÁOr+‘@ÂK‹áõ ,K%€ñz€€€€€€IãYôp&€ñ3zG6„a½ëjrχÁOr+‘@ Êð�eïÁvÈÚ³Ã=]­´‡Ï'°@@�&Ä Å8Ë´iÓhÀ€F Û·oO ,ˆU„ö0´oò( ~’[‰  ^˜X ¯o`Y*�„ -àƒÀþó×$Î8ã ×÷àˆ¬ßEÉ[ñ±5 ë5XW“÷§0øIn%r€D™Ö�¢ì=ØÙ!A{v¸§«‚öðù�(Û„Øï¿ÿNüñ‡!ª.\¸°´f~ÎX¨P!*P €ôžld8yò$ñ§N�2ÚÆ¶f;�>}š:wîLóçÏ7LéÖ­Í™3‡ÌÁ‰ûŽ;F\.'ÿy©CõíS­;1_&oºú û‰Ÿ3fäÇssäÈËï¦ï¹Ÿ«¤ ý¤‹§ÙN³|fËÏQÑ¢EU €dL‹#D€ =@ب @@@@"B‚öˆ8 fJëw�â–V¦kþëj©è±®–¾;b]Mú¸"€€°îäD9F‚öð9‚öðù�(rBlëÖ­ôÒK/Q‘"Eè矦þýûSõêÕéàÁƒ4wî\=z48pÀ²ºN�:4pà@ºé¦›¨xñâÖßùÞ×^{�žyæúàƒ’Z9jÔ(ºãŽ;¨dÉ’ ­×›åË/¿¤e˖ѪU«ŒÿMLÜžÞ½{S»víèïÿ»ÁÄ)}ôÑG´hÑ"#ÏÑ£G nç�w^Zã™é¤I“¨`Á‚F¾ªU«‚uNfyüÿ™ybb~ùòåK¹‡ÿðé§ŸâwчÌH¤‹ IDAT6ì›9s&ñwf2ÛØµkWjܸ±£P>,íÓÕÜN¼ùÕoÄöìØ±ƒ/^løJ|nØG7ß|3]qÅVŸ‘ñøä“OŒ²ø9L|nÍûîºë.êÕ«Õ­[×¶(¯ýpãÆ�ÄLÍç�Ÿ£jÕª9šëO§÷O6¯X±‚f̘AK–,I²£^½z4|øpêØ±c¬N>�õ \ž&ƒg޽rüáÝJÜ      Œgƒ Œ:¢J ÈñsÜ×ïdë ºæÿ±®æíéúÖÕ°®æíÙÁ] à'|§ûIe�@n€ =|~† =|>�E  ‚œ{á…¨{÷î–UÛ¶m3"–³p=]baôæÍ›©J•*Ä“J*ùYDÞ°aCþgáhÕ,b}ê©§” çö-_¾œš6mj›_äöñÇSýúõÓ–ýÙgŸQ^^ž•§yóæ´zõjCT+–çTÐ5×\c–ÍHÛâ}ì Ø«pîÒ¥ =÷Üs¶QÛÃÒ>%gyȤ:ñæw¿1MåS î½÷^¥þX¶lY£/²Û)íܹ“n¸áb�¼JâS&L˜@+VLÊîW?tzüæ)ÚËà*T Ë.»ŒvïÞ�o(Y³f UªTIò€€€k˜Xt� 7d‘@�ã�,6Uƒ€€€€(ÀxV²ä,� ÇÏq_¿“­CéšÿǺš·ÇëjXWúš·gw�€Ÿð�î'M”¹A‚öðù‚öðù�(rB,q‚EÜÓ§O§#FHÅ Ü ŽJÎÑÄk×®m Zl* GYp›Ù]GÆY8z�E  ²5!&š6sæLêС-Z”8tß¾}iË–-Ž-ø×¿þE={ö¤’%KÒþýû‰ÿ=kÖ¬¤üo¼ñ]yå• üÉrúôiêÝ»·‰<1ÝrË-tÿý÷Ó¹çžküùðáÃôþûï"}Q þÅ_$ Ñ9¿l¢ÍÎút‚ö}ûöÑwß}G6l Ûn»Íº�7°¨žGÎg¶‰¢xÑŽÄzï¼óNºùæ›�(úÑýƒ> ^½z¥´ï¥—^¢o¼1Éä°´ÏŸ^�ZŠlâMW¿aK D“&MJ2êž{î¡~ýú"óŸ~ú‰Þzë-êÓ§OR¾ÆâëÒ¥K[?yò$µlÙ2Eÿä“OÏn¹r制ß|ó M™2%%"ü<@>ø Už_ýP´ëâ™®ÿ3¿!C†Ø�;Füüq[W®\™ÄuýúõÔ¤I]] å‚ä0L,æ°ó#Øô ÇÄ“A@@@@ §`<›SîFc]rüœnþ;ëw²u(]óÿXWsÙéÿ/;ÖÕþ�u5"¬«y{†pøAßé~PD �[ hŸ¿!hŸO`€€�lOˆqõ÷Þ{�êÔ©“díï¿ÿN-Z´°�~üæ›oQ§‹Xï½÷^züñÇ­?wëÖ�fÏžmD%"mܸ‘êÕ«—T { `[ý÷ßo„9’¼™† FcÇŽMÊ/›h³+<� ÝÌÿÕW_t31¯9sæ¢d»ä4ñööÛoS«V­Rn±k_Ó¦Mé�wÞIŠR–öéê#²‰7]ý†#¢çåå%5Ëé—_~ùåI}‘…ðwÜq‡u¿]y¼q¡Q£F¶è¸/ñ¦3ÙEiçk™öCQЮ‹§SÿwbzâÄ âþžxZƒ8ù¨«Ï¡\�Ü#€‰ÅÜóy”[äø#Êœ`;€€€€äŒgsÁËh£WAŽŸíæ¿ã´~'[‡Ò5ÿ�u5o½ëjrúZª¨ß[�Â] ^à;Ý 5ܹM‚öðù‚öðù�(Èö„ØâÅ‹©}ûö¶–¾ð Խ{÷¤kãÆ�3"!Û¥­[·& ã¹Ü ÃH÷Ýw�3ÆªŠ£–Oœ8ÑQ ÎYìÚ°aCë»É ÙD›]ÛTí*y˶›xãM�=ö˜#^;4ÿ­fÍšÖ=aiŸ®>"›xÓÕo~øa9r¤Õ¬tÏgâgñºë®³ò‹GùÍ�;—zôèa]ççkÑ¢EŽýÛNнyóæ”Í+™öCQЮ‹§]ÿŸÙD›]*"a•<‰eÛM¼íÚµ‹Ø�NéÔ©SFäoŽÊn&1j}XÚ§ð¸xÊ’nâMW¿ùù矩I“&I×e¾:xð Õ®]›Ž=J�7¦¢E‹Ò³Ï>KgŸ}¶Ñnþ;÷ï_~ù…~øá*S¦ ÕªU+-“AƒGz7“(>ç¿gÚËÔÅ“íýè#ˆØ¶®]»o êÔO7�D’&#é¶œ5:ÈñGÎBFÃA@@@@ "0ž�ˆ£`fV9~ç¿ã´~g7¿/®UèšÿǺš·GëjXW3{ÖÕ¼=C¸ ü €ït?(¢ È-´‡Ïß´‡Ï'°@@�@6'Ä8zô¨Q£­Å ²èÆ¿ÿþ;5jÔˆ¶lÙb”¤ ]ŒD.›ìKl4G8>|¸õ§° ¾Å ¤æÍ›ÓÛo¿M HÛÓ&L˜@ƒ¶òLŸ>�ú÷ïoý;—íºú�X.G[çÍ# Lë«“'OJý©ðZ±²ˆ}[· ]O» ï¾}ûÒŒ3ÒžÀ°oß>ºøâ‹­�.A¾“Üø yA¢O‹Ñ÷a.µ ÈñG.qE[A@@@@ Š0ž�¢×`sP‚?‹ë4qZ¿³›ß— ÚýšÿǺš·§%� ]×:ÖÕþô•Îõm¿ž+o½ w�¸%€ït·Ä�@‚öðõÚÃçX  @ ›b¢°Y4×mtã0 ÚÝì'f&OžL´p„EðívBÓl€ì¾°´Oáqñ”ÅÍÄ›_ý†£±W«VͲW—�š#ÁsTôC‡Qþüù�úøÔÆó¿W¬XA<ð€eGЂv¿xÚMx³˜½_¿~iûD6ßIž:+nˆ,L,FÖu9ix�ã�œŒFƒ€€€€@„`<!gÁÔÀ 9~×1â´~g7¿/´û5ÿ/[sêT²û°®–g¡ókëj"õ‹'ÖÕÿÉ@… à;|§ûŽ‚@ì @Ð>CÐ>ŸÀ"n'Ä>ýôSzæ™g¨T©RŽ¥³ˆ³fÍštë­·&E/N'ìµ+L´ËD¹Ù�ŠmëÓ§�½9_¾|R/¼þúëÄÑçÍÄí\°`�%ËÄ”Û M³=«V­¢6mÚXí#퇥}RGyÌ�®ßëê7bŸºçž{ˆ£¥›O�M1nc±ú»ï¾KyÉ’%®ŠÒ-h×ÅÓnâMD @ h´M\^írFÈ Bn'ÄĽS“Ä(ã^®n×*“Gº\�ÉÊgŸ}Fyy�wç�wÒĉ­hÚ²‰6»6©°SÉ“X¶×‰7±}}ûö5&DÌAPXÚTßHœ°ÑÕoÄrÇ�GC† ɨ‰[·n¥:uêØ–Áâù /¼�Ê•+gøµ`Á‚T¸pazþùçiùòåÖ=a´»}1ñ–QwÂÍ >ÀÄ¢Ï@QœVnÇZ�Aá     Y%€ñlVñ£ò�p;~Æú�³CeëP^Ö¿TÖ$½”Ë­ÀºÚ«Ô¹sgË¡XW«ŸÔ¹3]ßM\«ÓµNÉ{éÿ*ÏUÈ_Ý0bCßé±q%�€ =0ÔÊAЮŒ AÂDÀí„Ø;ï¼C:t êÕ«§mF³fÍèÉ'ŸL:ÌíÀÕí€<›ƒ\1òŰaÃh̘1–h;¬5kÖPË–-­,bt{ÙD›]Ù*ìTò$–íÖæ½7n¤zõê…¾}ºžËtÜtõ±Nq“„Û¶žT°zõjÚ¿¿á“#GŽÐÕW_M%K–ô¡dx>¢á©¸ø‰ÇâüþçtâÄ j×®�uÖYÑp¬�Ƴè àLÀíøëwÎ,eël^Ö¿TÖ$½”Ë­Àºš³ ]×:ÖÕþ|~t®o#P~ñ@ Zð�-ÁZÚÃà…d hŸO`€€·b E:fq;qãVp­2y”‰ýéî£Ws„ú… & ú�î—EÓ–M´Ù•«ÂN%ObÙ¢Ó§O§þýûK‘¾òÊ+tã�7Zùî¹çCìo¦°´OÚ�Òõ{]ýÆ­oeMËãük×®%Þ¸’. 4ˆ&MšdeÑ-h×Å“àöýÅ÷dó�$ó)®ƒÄ‹&ãåϸ·&ÈñG\YÎ�;—zôèa5oýúõÔ¤IíÍÍV½Ú–abÄæÍ›ÓŠ+Œ‹�r‡@.>‡¦½{÷Ò·ß~kˆ«Ï<óLãù»¤téÒT©R%c³�ù�†Þ?½üòËÔµkW iÓ¦MiÕªUxï„¡“Á×0žu� 7ä� ÇÏnç¿Ý®Ad{®\¶å¶ýªóÿXWóöÀb] ëj[¶l1:�›õvo½ w�8Àw:ú€€[´»%¦??íú£ 0!æTqò®F�´mÛ6*X° ´‚§Ÿ~šn¿ýv+Ÿ¸C]œ¸y÷Ýw‰Eé‹ZxAÓLv~·Ž¢#GޤQ£FIÛ7gÎêÙ³§•oƌԯ_?ëßaiŸ´!3¤›xÓÕoÄã(¹?nÚ´É1’ºÙ´cÇŽQþüù©@�I­]·n]’x½OŸ>Ä~Ì—/Ÿ#•S§NQ§N�ˆ£´›I· ]O¶_ׄ¶Çn…Û@@ ‰&Ñ!¢D ÈñG”¸¸±U¶ï¦,7y³U¯ƒÎ+FÍ«Zµ*mÞ¼™Š+´)¨/Ëråù8zô(­\¹’ÆŽK¢ÎoÁ‚Ʀe¯ièСôØc�¥l 6Ë âý'?ñ¾C‡´lÙ2Ë%o¼ñ]yå•^]„û@@ +0žÍ vTAŽŸÝÎCЮ6ÿ/rźšÚÇuµ¿8a]MýDtµÞ…\ ªð�®J ù@L´‡¯/@Ð>ŸÀ"˜S€¤�…^9êø¬Y³¬Ü£G�¦áǧ½[Œ¤]¶lYC$^¦Lë>ñ{Q.VÀÇ~_pÁtàÀë’� ]¬›�«æÅ]§¨Ûâ.‹og‹(òKûÜì)Kº‰7]ýÆ®ÜGyĈŒç”öìÙC<™j&>ž# ° �7YtïÞݺ&ëƒ'Ož¤–-[¦Dì³›xË´&–©‹'7Üí„>ßA»§G7�x €‰EÐpKÖ9þÈZ#5WÌߨ{÷î5NÖaáøùçŸOYwR©7qƒ!Ÿ*µzõjÃÎ8&æ&Ž÷Æ�GC† ‰cSÑ&*χB1¡ÍÂs}ûöu´¯M›6TªT)bo¾ù¦c¾t§}ñþˆ›ŸvíÚEÕªU³LJ¶3Á0`<‹.΂?»�ÿ† ]mþëjÞžp¬«ýÅ ëj´{{ŠpdNßé™3D �k hŸÇ!hŸO`€€Lˆ)@R̲fÍCÄ›˜Ö®]KÍš5³-�Å®W\qE’è÷Î;蠟žz*)ÿªU«ˆ‡ÍÄ¢w³ÐXL,$æâÙ³g']²´sï‹/¾Ø¾óñß,6/P €­½vo,€æŒ¥K—¶½çÁ¤Q£FY׸ì,XÐú[XÚ§èf×ÙdÁºú�]¹,,hÛ¶mJXÞ«W¯¤~ÃbŽÄ΃Õ�7R½zõ¬ûØï;wî¤3Î8ö¬»ï¾›Æ�ŸrÍ.²{¦ýPœÌÓÅSæG»ŽA»ëÇ7€x$€‰E�àp[V9þÈJs¼ÒuëÖYãÙ†Ù(£E¤ü}¼iÓ¦@6D™l�&»Ó·¸%'N$žkàÓêçx£ ‹Ó9r¸Ýér/¾ø"ñûAL¹òþð»L˜0�l‹Í5~Fy º `<«›0Ê�2� ÇÏnç¿!h÷.hǺšü©”õG]ë@XWûË7~¬oËüˆu5ù³€ �MøNÏ&}Ô Ñ$A{øüA{ø|‹@`BL’b§¨ÔóçϧŽ;Â`3±‚ÅëË—/O*�…ê5kÖLúÛÁƒ©víÚI×;wîL�?þ8�{î¹V^£wëÖ͈ª-&;A»(¶å{fΜI={ö4"Kÿúë¯T±bE«(;A;_d�=k}É%—Xy�=J�>ú(=üðÃI¦Ø šÃÒ>E7»Î&›°ÑÕoœÊep´õ"EŠ=?ÿüsºë®»håÊ•ImKŠýõ×”ðñiäãþË›Ì�\߆ è¶Ûn³íƒ|ÏwÜA¼Øž¥3Ó~( Úuñ”ùÑ®c@ÐîúqÁ  `bÑ#8Ü–~�?xcÚ߯7ß|3�sÎ9Yi*%š6m 0À@Ác� Ä.B;÷9þ®�:uªåróuêÔ ]bI€Eé‰ý�OóâqXùòå¥íåˆè¼ÉÇŽV^ððFw“&¦\xH�yÈÀŒëׯO»wï6îæ;˜7û @¢@ãÙ(x 6f‹€ßãçtíp;ÿ A»wA;ûëjéŸ*YÔµ„uµuIŽÉt}[æG»^€uµlýâ ^H%€ïtô · hwKL~Úõ3F `BÌ_¨{öì!^œONqäôü‘^{íµ$qº™÷ùçŸ7ÄäviРA4iÒ¤”K¼PyÙe—´8`]9r$ñD�¹hl'h?qâµjÕ*)B|bb$x'A»yOëÖ­�õÜF»èÜ��£Š‹Ö|ÚçoOø«4• ]ýFœTNl#G\ÿôÓOm›=zôh>|xÒµ{î¹ÇØD!&.‡�–ñ,xg1S^^^Ò-œ¿V­ZÄb3>y Ó~hwÜ¢ž*~Ù`âM×S…rAD˜XDŸˆ¿ÇGŽ¡‹.ºÈò}ôÑGÔ Aƒ(!ÉŠ­¼HËÿñG~‡-Z4c;XèÍoyÁ“o¶�3gNÒÆÞŒ+ A¼ 9ñ×IœëÆT¿ýÁÏûÃL… öucçrßá�ª\¶,™ù9_¡B…O“•c^çº�;fü“mà2ã�üî~0aßµhѰ›éý÷ß§Æ�+ÏïåF�%åßÕ~¿?øÈ—/ŸRÿTnÈÿe £Ÿž~úiºýöÛ­¦<ðÀÄ'æ!�€@`</ÁÆlð{üœ®nç¿!hÏLÐnúëjö½R¥?êXbk°®–ì“LÖ·Uü(ö¬«eëõ‚@*|§£W€¸%A»[búóCЮŸ1jÐ@ È ±yóæQ—.]¬VL™2ÅŠÞg×4» ±tþxÁ’Å+éDܦɓ(W\q…%ªQ©“Ûuýõ×;fýþûï ±xbT3§ÌÆ 3.›4ibEÊvŠŽ˜N¤.´ó1áiþþûï—6QÁ- í“6ÂcÕ ý†MþòË/�ˆx‰Ò5åÉ'Ÿ46ðâb²‹¬çT?çÏ=÷œ!Tá~ËGÇ‹iòäÉÖñó™ôC;A;×å7O·ï/¶�'Þ.½ôÒ¤wR£”z|4p€€�0±è#L¥�€ßãq¡ËéÛ@{ì€7³ò÷oå“‘z÷îMÕªU³,غu+½ôÒKÆõŸþ™ú÷ïOÕ«W7À+V¬ >±gÉ’%Ió¦CÞÐÈ'K%ž¦“˜É©^¦.Z´ÈÈÊ#Ÿèc~W²ð›EîQOâf\f}ã�7:6K—?Ä ?ùäÃÏ<óŒí·?ŸÊÔ«W/ª[·nZ8ÙË'kÍ�;×ðqâØ¢N�:ÆwýM7ÝdD†6÷=ÞÌÍö|ðÁ)ý‚£Ü—,YR¹;ü÷¿ÿ5N8ã2—-[–tÛpõÕWÓu×]g§OGS® ÃŒÙz.ÙlÞ¨ÌIÌÍã|êo.ñ#‰ó,<¶çÓáÎ:ë,åâO�:E—_~9:tˆ®¹æºà‚ èÚk¯5üïõýáÔOùÇÂn3Z9¿sfÏžm½‡²é'?K‹/NéËü¾õÖ[�ÓÊ”)cl:âñ¹¹yƒÙó3l׿y¼Î\Íg“çs8 @… ”ý„Œ  �-Ïf‹<ê�¿ÇÏéÚìvþ;jëwâ„8wà¶ýÌReþ_¬ëjjOÖÕ°®–¸Ö�u5µç¹@ÀoøN÷›(Ê�ø€ =|>† =|>�E  ‚œ[µj•‘ÙLÏ>û¬±甾úê+ªR¥Šu™¼Ÿzê)ÇÅi1r˜¸p©€Ã·,¼àÈ.,ÚïÍJx±ĈFTöŠ+Jëf± ‹Oþñ�ØæåÅÏGy„®ºê*cÁ“ò·lÙbäåhÙ'N´eÇ‹»-[L,8áPS”à4�ôöÛoÓÃ?Lï¼óŽ­],x`�ƒlÁ;Ûí“:Àc†×_ÝX´7OXvíÚÕ¶4ý†+bGÇüç?ÿé(lïÛ·/ñf?8%¶�ûôÝwßm›…£‰ð5îƒæ wß¾}ÄÏ®©Ó¼qæÌ™ÄušIµòÂ? UÌ´aÃC4n—üäéöýÅöˆ»l¾“çœs”òû•)[Ï%Û/ÖݼysZ½zµ/ÏšøŽ7¾«ò3#úËžÕ÷‡Øfžûà~—ønä²ÄÓê²é'¶géÒ¥†M²ôÊ+¯AxŠ)ä�½C{챤“ÖdA$d6à:€EòBÏ IDATãÙ H£ž(ð{üœŽ�Ûù鶴ßÉÖܶŸYªÌÿc]ÍÛ“‡u5¬«™ëÛXWóö á.ðƒ¾Óý ˆ2@ ·@Ð>CÐ>ŸÀ"ANˆ)˜Ë,…Œ#�›Ç¾sÔ4ŽDV®\9Oíeá7 Q~ûí7#vÁ‚‰•½–ÇFüòË/´ÿ~Ã.ïoû�}öÙIöÉ""p;¿ûî;:~ü¸a ØYàÁÿßMÊVûÜØD^¿û�i3÷Å~øÁè�œ¸?²(§X±bÊÍ2}Ä¢„3Ï<Óˆ Z¾|y*]º´cÜ£p(T¨�íÑï*ýPÙH!£.ž^íÁ}  à'L,úIeé&à÷ø#í²Èr‰×YdÌ·zˆXø®’jÔ¨aD`¿ã�êå¨Ý=zô�í§ÈVZ™¦ "ƒxÀ8+]Òå®3ÝQèélJ'Jí�>}º±[¶‚ëãÓxCsíÚµ•Nˆ’màÕ}úô1"l»Iܾ÷Þ{Ï8™ ¨”­ç’Û'Ö-Џ3a Fhç²dÁ ÜÔçõý¡ÚOÅÓê²é§•+WRÛ¶m•ñðføñãÇ[ïn™_?ûì3ÊË˳Êçw. p;'£l 2‚€€O0žõ $Љ%¿Çϱ„òFa]-XéZºÚc}/Óõí`{jÈ”¾Ó3%ˆûA ÷@Ð>ŸCÐ>ŸÀ"˜S€„,ÙÄ0�€€d‡&³Ãµz#à÷ø‚v2ÄŽõë×·"~·'zŠÅÁC† 16žòi6,ˆfA6 -Óúõë©I“&Is‚ò‰<¼±•Oιí¶Û¬{¸Ž×^{Íø7GøæM½A Œ½õPç»xñ–Oš:uª•ÉŽ“X‚.°Ø»eËÔcÈ9šy‡Œ ÏüûÀQå9R3Ÿv–˜œÄøéìåS–¸ì¢E‹G†ç—̨iväþõ¯'’±ïy5ÿ{Ö¬YIYßxã ºòÊ+mÁ�5*eÃG˜gÛÏ?ÿ|C¨Ë‚…… &õ=.ŒOàèò¼àDr#”íÉä¹ä²æÍ›G]ºt±ŠEÜ™´Ÿû=3OÜâg»cÇŽ™mÜëõý‘®Ÿò†>9ìÇ46SóIeæ·R¶ütøðaºà‚ R6zð³É¾ã æß~û-ýûßÿ¦G}Ô–«LÐ.žZÈšxsRâ©; €€€Ïj€Š"cCÀïñslÀD¨!XW‹�³`*€€@|§£C€¸%A»[búóCЮŸ1jÐ@b Æ´HL¼ÅÔ±h€€@ä `b1ò.Ì©ø=þ€ ]]Ðî$@=qâ5mÚ4)‚»�ØY&�œçc¡çÌ™c I£ÞÑÅHÕ,šeÁt™2eÒ6ÍIx›©?ÄhÌlÄ|@�5²µ‡}Áâr39Ei·³—…±ñ¼N�:Ie‹ÚÄ‹o¾ùfJ4jGß{ï½ôøã�[Y�Žß³g�!JOL“'O¦Øö©/¿ü’š5k–$æ×]»v ¤ëÉž]ý€ÇRXÀ]¡Bú駟誫®¢‘#Gúöì½óÎ;ÔªU«Ž 40üÙ¢E és s‚Û÷‡OÞ sÿý÷Sþüùm«Ì–Ÿø¤ŒîÝ»'Ù´víZ£ÏŠiݺu¶— Ú¹œûî»�ÆŒcùÖ[oQëÖ­eøq@²JãÙ¬âGå!'à÷ø9äÍ�¥yXW‹¥[Ñ(È øNÏ 7£‘ à+Ú}ÅéKa´û‚…€MbA�n}˜x‹®ï`9€€@¼ `b1Þþ�[ëü°›£“›Q¢7mÚD_|qܰ%µÇ‹ “…ÀtäÂQÞ6lh]¿æškhÑ¢EI¢PY½í=1»Šø2JŽÛ×¼yszûí·�(áé’�ðÖÌ�;—zôèaUͼÙgæo‚h“ÝÆ…Í›7§ˆÔíì]¼x1qùvÉN¨;nÜ8ã$»´uëÖ¤:�¢‰?öØc4|øp«Ž?cÆŒ´"mï^qÅÖ=ª>ò£Êž]ýÀÛeeØ�N ÞÛxs¿Gòòò¨|ùò²b“®»}Øñä>2}útÊ—/ŸcÝÙðóãhöü|š‰íìß¿¿£�Ó¦M36o$&•wªØ¾Gy„FŒáÊÈ  AÀx6hâ¨/Jü?G©íq±ëjqñ$Ú ¹Gßé¹çs´2%A{¦ý¿‚vÿ™¢D�äÊ„ØÑ£GéøñãZˆ²€áÌ3ÏÔRv˜ �ãÄúE˜zlðJ‹^Éá¾lp;þ8yò$íÝ»—X(&þçhÄmÛ¶µ¢2sDæ&Mš‹wíR©R¥¨D‰Ùhºouºd:EâN4H“rTkL' Ceõº¤ú$ ‚ÄöÛE±·3E¼Ï/ðXæÀ�ôË/¿Ð?ü`DÈ®U«VZƒ ¢I“&Yyx#oIL¢½%�#Ñ)RĶlÑïÍ�…òUªT±Í/Fº·éþüóÏÆs¼cÇ« ¶¡víÚiÛg'ÚW¹Ï�.${>tõ?lW)ƒßÁ£G�6¢Ÿ«$î,ÈæÍ1uëÖ%þwºäöýa'hÿüóÏ©F�iëɆŸÄÓ˜ÅöíÛ©råÊŽ¶ò3pÉ%—ÐîÝ»­<*‚vñä»ÍI*þC’ƳAÒF]Q#àvüµö™öÆy�ëjîze®¬·º£‚Ü  �øNÏwÔ Q&A{ø¼A{ø|‹@äÊ„Gêš5k–÷Y8êÛêÕ«�´v_b8ïˆãÄúE8û¬pG‹îx!wv ¸ˆâ¼L­â‰'hèС™“ÕûÝ 2U"[ï۷ψlÏiNvÂIY½n©Y…è¡r¯ã!ñ>¿üá¡ $F=W´�9’F�åX�èw™€ö÷ß§F�Y§*Øõ5ñ¹wi}„ 4xð`ËÞùóçS§N�¼àru�ìùS?pÕ0!3Ÿ†1~üxzî¹ç\Ã@¸ï;‰¸Ý¾?Dž,dg‘¸ÛÄg@‡Ÿ¼žî0vìXº÷Þ{-Î*‚vq3ˆÝæ$WŽCf€Æ³@F‘%àvüÕ†Æy�Æë82̾Œ³¿Â̶�€@Ðð�4qÔÑ'A{ø|A{ø|‹@äÊ„ØÀ�iêÔ© DÜgQYTt_jøîˆãÄúEøú,pO‹î™áŽìp;þ…€™Z>eÊ#jp”“[áìŒ3¨_¿~i›¬"2–ÕëV�5,\¸�:vìh™ýþûïSãÆ�¥Í¹ù廊YÌúý÷ßÓ¡C‡¬ ×µœO:ÈŸ??­X±‚XXl&AûôéÓ©ÿþŽíT‰îŸx³J_ÛµkU«Vͺ�ÅÊõýÔ©SI§ˆF*TˆžyæºýöÛ­KA=ó²ç#È~ í”>dà~¶~ýzâ ³gÏV.qܸqt×]w¥øÑíûCäùÈ#�Ј#¤vdÃO7n¤zõêY¶Ýyç�4qâD2¿ßœŒ^³f µlÙÒº¬2÷$>_•*U26�Dýd©c‘@ Ò0ž�´û`¼fnÇÏšÍÑV|œ×i°®æ®Û¨|óº+¹A@¼ÀwºWr¸r—íáó=íáó ,P �+b>ø E¬zõê TÜeáã³Y”�/_>w7F,÷ܹs©G�–Õ“'O&žhŒrB¿ˆ²÷`;€€€I‹è Q"àvüá· =ß°n™*‚^‘±¬^·‚Ô(õ[¶U%º¹]›¼TüaÖÅbõwß}—82ù’%K\aU´Ëú�[¿«´íõ×_'ŽôîG’ÙïG\†ìùÐÝüj‡—rŽ;Fß~û-mÛ¶�Þ{ï=#z»yÚƒ]y÷Üs�ñ<% ºÝö#‘§lã…iG6üô /P÷îÝ-*›Z8³¸±CEÜ#>_eË–%>ñ‚v/=÷€EãÙ H£ž(p;~ŽbÙæ8¯Ó`]Í]¯Ì•õVwT�@²CßéÙáŽZA Ê hŸ÷ hŸO`€€�\™S@�,GŽ¡}ûöÂ}ŽŒÇ £ÅŠ7�,ÀÄb–€ê]ð2þàïÐÓ§�i—¤Ä}ÿ×_¥&MšÐîÝ»�ko½õ5mÚÔˆHm—8‚s�\Ù¶ÌÙd2Y½n©aã*³GŒh'·+C§�ù£�>¢›nºÉêÿ²6ˆ×Ã*h™¹mWb~Ž>dÈ�LŠPºWö|èìJœé»ï¾£uëÖÑ}÷ÝgÛ?9º;¿»Íäöýá…§Ê{ÌK¹²M^Êd[Yˆž——g1‚ =àNŒê@#€ñl`¨QQ x?G°™±6ëj±v/ ±&€ïôX»�- hׂ5£B!hÏnÈLˆe‹<ê`bÑŽ(%~�?D!¡ªÈ8˜Öê©%[ÂYY½n©zèè+uРA4iÒ$«Õ¾æEÌ*Ȳ[·n¥:uêØ6¸K—.tá…R¹råŒ(Ø ¤Â… ÓóÏ?OË—/OÛ·öºõ»JÛü´«ˆ€ýè5²çÃ-W¶I…•¶ë,ƒ7#Íž=›zõê•TM·nÝhΜ9V”v·ýÈ O6 ~òjëÆ�©^½z7•¾üÓO?ÑÙgŸm݃í:{7Êð‹Æ³~‘D9q$à÷ø9ŽŒÐ&=ð�®‡+J�8€ =|Þ… =|>�E  0!¦ Y@@@@ Ä0±bçÀ´~�? h'…Õ^Ä“*ÂY™Ô­ 5J�‹rûõëG³fͲÌΦ �O hÓ¦ ½óÎ;I—.]J­[·¦"EŠØâ]´h]ýõiÛà¶ÿ¸õ»J_[¼x1]wÝu–�}úô¡ &'…¹M|*‹ùu'Ùóá–+Û«ÂJw»ü*ŸÅë={ö´Š«Zµ*mÚ´‰Š/nüÍm?ò“ëɆŸÄ:§OŸNýû÷—¢ÝµkU«VÍʧ"hû íRÌÈ φÀ 0!´ü?‡¶¡0 @@@ tð�:—À =ÚÃç"ÚÃçX  @b ��@@@BL‹!vLK!à÷ø‚vÚƒzÌXÌÞ·o_«º 6Ð¥—^*­Þ‹ðV&dÅ¿lÄÚµk©Y³fiíQ‰2ïÖ^·BdYÛ¸nË”:!€ ÙJÐ,ߪøæ›o¨råÊVy•*U¢-[¶P‰%Œ¿¹õ¹Û~jVœ ?‰4F�MÇ—²åM3 6´òAÐ.E† %€ñlD³!à÷ø9£Q €€€@,à;=nD#@ P´Š[©2Ú•0!€@Ø`B,l�=    àŽ&ÝñBîìð{üA;íAõhQûÖ[oÑÐeÉ‹ðV&ú^·n]’x�#˜Ï˜1ƒòåËçhG7ïÔ©q”v3ÙE™wk¯[!²¬mlÛgŸ}Fyyy–�×\s�awþüùe¸³v=BiÝ�=~ü8}ùå—´}ûvâ ?þø#=ýôÓiû™“M¿ýö›!ÎÞ±c‡‘%—"´‹ÏˆjfÖ·ß~»…Ô‹ �9oÛ¶ÍñÔÝ}僀€ ŒgU(!O®ð{üœ«Ñn÷ð�îžî�\'A{øzíáó ,P € 1HÈ   !&€‰Å;¦¥ð{üA;íA=fóæÍ£.]ºXÕM™2…  ­Þ­@œ ”‰¾_yåºñÆ­ºYÌÞ¯_¿´¶lݺ•êÔ©“”'¬‚öƒRíÚµéÀ�–½,r¯Y³¦”w¶2ÄQоsçNªU«–…´xñâ´yófªR¥ŠkÌ¢¨»lÙ²ÆÆ…\ˆÐ.nÐÛnS|pAû÷ßO^x¡õìtíÚ•æÎ�ëi‚k'ãðHãY�àp[Nð{üœÐÐH_à;ÝŒ(rŠíás7íáó ,P € 1HÈ   !&€‰Å;¦¥ð{üA{xí¢h3nBÊU«VQ›6m¬>}ç�wÒĉÉ|;=î:í/¼ðuïÞݪR&®?yò$µlÙ’8²{b « �m4hMš4É2÷Þ{ï¥Ç{LúVåù$ :”.¿ürªW¯žÔGÒB2ÄQÐ.FUg íÚµ£Å‹S�¨ü™åôéÓt÷ÝwÓøñã­{Ä(ånß^ž+®<~²ã(;áA´“mW´oܸÑèófŠÛ{X¹Ó!#€@¤`<)wÁØ€ ø=~Ø|T   aøN�°ó`:d‰íYŸ¦ZÚÃçX  @b ��@@@BL‹!vLK!à÷øƒ…‚uz÷îÝF]ëׯ§&MšÄš|6™ TVï¾}ûèâ‹/¶"רQƒ¶oßîJøfÇqû*T¨`™ÈíÛ¶m,X0­Ù^„·²í¢hµjÕªÄÑ´Ï8ãŒ[ìÄÄf&»Èîní#oËD·²¶™¶±Ø¾aÆIíyíµ×¨cÇŽŽ¼Å{T"aûÕçdχ[®l—*+öýÛo¿mùÿÜsÏ¥¶mÛúÒ´ &ÐàÁƒ“Êêܹ3ñß+V¬(­ãÈ‘#4nÜ89rdRÞùóçS§N�¬¿¹}xá©òóR®ŠŸF�E>ø Õ^~f7lØ`E¨O„#FÆ7¯Éž-Î7gÎêÙ³§Uœl³‹Ô�È Àx6Ȩ"²ü?G œ¾ÓGŽ A ò hŸ !hŸO`€€�„…ÜÈ    V,*t›ìÄ�nË@~pC@Ç‚üûï¿o?9 uýúõm‚nl {Þl geõŠ¢Næ8sæLC\É×~ýõW%lXùŸ8q‚š6mJ,šæT¼xqC°_¹rå´&ëÈ~ýõ×$Žå9b<‹fK”(aØÃÏ fo»í6Ú²e‹­�wÜq‡!LΟ?¿uÝ­½ºí,Ägñú¢E‹’l=z4±ÝÌßL‡26\p[S�b^Ùóá–+·CE(ÍùæÎ�K=zô°šÞ¼ysZ½zu’_½>W,HçMBv}¨oß¾Ô­[7ªU«�uÖY”/_>£¾gïÞ½ôæ›oÒðáÃSªfûX€ŸåÝíûà O6$[~Úºu«±ù*1UªT‰^~ùec#ЙgžIÜ�9ú}ïÞ½mÝ%´ó3Ó¯_?š5k–u¿Ý) ^ûî] ”ÑEåÆ�ÖïâàE´@@¢MëNÑö¬� @Ð$mµº hWã„\ !#€ ±�9怀€€€G˜Xô·J@‡ =Є ²l 2eõ²à»U«V´nÝ:[JAFËÖå¦ûî»�ÆŒcÿÆoЕW^™¶:/Â[!ó=÷ÜC�?þxJÝõêÕ£R¥JÑÊ•+“®±à}À€”———ôwÎÏ‚ä›o¾™Ú´i“"ø•‰Âu ÚÙHŽÚÍ›Ì ¿å–[¨pá´iÓ&úàƒR8¨FÐ÷«¯Èž]ý€íËîÚµ«!r7æ™¶1�Ü–Í"nÞhQ®\¹¤[ݾ?¼ð´c% ¾½”«ò¼rÝNϬÃåË—‘íÍM42A;ŸX§ìرÃ(’ß¹¼é¦téÒnÝ„ü  (ÚÅ�Ê"FëwsÌ�ÀºS �Š&�€&´k›A±´g·‚d�%Ùc�šA@@@ÀøAeEã�ÌIË„³óæÍ£.]ºXÉÉœ‘™—^z©%„dáä‚ ÒFî¶‹ü+Ú–ØÚ8Úßzë-ºâŠ+¬fõïߟžyæ2ßÃvÞÕå�ÇS³fÍ,Ÿ¥ëYÜž{î9#"vË–-m7Lž<™Hníµ´‹}'Ñ6ŽÞÝ Aƒ¤¾¶páBGñõ÷ßo0wŠ2o×îÆ�—)Ц3úœKÈÖsɉuÛ=¿™¶ý—_~¡G}ÔøÏkâÓ8‚8G#·KnÞnû©Y_6ýħ& 4ˆ¦M›&E¸lÙ2cƒIݺu•Ÿ•�>úˆ5jd•í÷Æ©ÑÈ  `<ënË ?ç„›ÑH%|§‡Ò-0 BM‚öð¹‚öðù�(À„˜$d�ÀÄbˆ�ÓR`ü‘y§X¼x1]wÝuVAí˜ÅèfZµj•!„4Ó³Ï>K½zõJ[±a¸[·n4{öì$‘±¬^³¾�£}‹©jÕªF4íâÅ‹g!K%üüóÏtÉ%—Xù- ™£N;%]þàúŽ;F,D¿ûî»m«oݺµqíª«®²D÷mûŽ;î ùóç'Ý3sæLêÛ·/¹µ÷«¯¾¢*UªXeqÙO=õ”£ÈŸûZ‹-¬¨Óv}MlÌéÓ§iéÒ¥4cÆ Z²d‰#k¶ŸÿcA{ºM:º�ìùpË•mTy.9ŸX7GãŸ8q¢¼‘‚ÛÂÏy:_˜ŒùÞøÑ¶m[¥g_õýá…§« ÞŸ‰}níÚµ4kÖ,âwsbªS§ŽqŠo@)Q¢¹Ù,ÂÏ?{S§NµŠä~Á›�@@ ì0ž »‡`_6 `üœMú¨@@r›¾ÓsÛÿh=x!A»jzï� ]/_”  ‰&Ä4�E±    L,ÕøBã_0†¾Žæ¼ÿ~Ã΂ Òßþö7:ûì³Co·Š�,ÖþÇ?þae=z4 >\åVmyŽ=jˆìÿøã#ú5ÿ»|ùòTºtiÇ::D,‚å¨í… ¢Â… k³ÏÏ‚ySGmçHï'Nœ0DÛ,þ-S¦Œcäo?ëGY`°/¸/±/¸/qäÿÍÄ'q~$öfõÛo¿Ï!¿'‹-šÔ½DA;oÖ˜>}ºíi»ví¢jÕªY÷ó¢Í›7S±bÅÐeA@ ô0ž ½‹`` `üœEø¨@@rœ¾Ós¼ ù à�í i¾‚vÍ€Q<€€˜ÓÃ¥‚€€€@P0±iÔãŒ?ü ˆ2²IààÁƒT»vm:pà€aFÙ²eiÛ¶m†  @ü"°~ýzjÚ´©UŸPЯ_?Ûâï»ï>3fŒuí¹çž³=)Ã/ÛP€øIãY?i¢¬¸Àø9nE{@@@ :ð�_ÁR ÚÃ≿쀠=|>�E  0!¦ Y@@@@ Ä0±bçÀ´ SÄ�À„ hðàÁVSÂ¥=\ш+��;wÒ믿Nuýµ×^£±cÇR¯^½›ËQÛ;wîLóçÏ·òL™2… �r�ÉÑÙãÚ‹Ð.ˆ/Œgãë[´,s?gÎ%€€€x#€ïtoÜpä2ÚÃç}ÚÃçX  @b ��@@@BL‹!vLK!€ñ:E>|˜êׯO»wï6šS¼xq#J{„mš‰6€øD`Þ¼yÔ¥K«´J•*ÑÆ�©téÒ¶5,[¶Œ®½öZë¿c¶oßN•+WNÊÏÂ÷Þ½{Gd7ÓâÅ‹©}ûö>YŽb@@@?Œgõ3F Ñ%€ñst}ËA@@ êð�uÂ~žíÁ3—ÕA»Œ®ƒ„’&ÄBé   Ê0±¨Œ C@ ,ã�£G�ÒñãÇ}'òûï¿Ó©S§¨hÑ¢¾—] @:óÌ3}/7ÈãÄ}áÂ…Ô±cG _·nÝhöìÙ”/_¾ ‘¢.ºúW‡çR!²dHàûï¿§²eË&•Âÿž;w.5mÚ”Š)b\ã|O?ý4�92)ï Aƒhâĉ)V¬Y³†Z¶liý½]»vÄ‚vî—H  ÏFÅS°3Â2~ÎFÛQ'€€€@v à;=»üQ;D‘íáóíáó ,P € 1HÈ   !&€‰Å;¦¥Ëø£oß¾4kÖ¬Hy¨yóæ´zõjÊŸ?¤ìN46NÜ92rçÎ�iþüùVùÿwêÔ)²þ‰«á:û]žË¸ú=lí£´'ÚW§NC„þé§Ÿ¦˜ÍÂw>¢L™2I×Ä“"øâ_|AÕ«W[Óa€¤%€ñ,:8Ëø>È=øNÏ=Ÿ£Å �)Ú3%èÿý´ûÏ%‚@b@F     ‘&5ÂEѾËøcàÀ�4uêTßÛ§³ÀöíÛG�rð¸q?xð Õ®]›8`¸¾xñâôÉ'Ÿ@PªóAðP¶Î~‡çÒRÜâ‘ÀóÏ?O½zõR¾›#®Éolû IDATÿïÿþ/•+W.éÞPÃåð©fzñÅ©k×®Êe##€„…Ƴañì#�°ŒŸÃÈ6�€€è%€ït½|Q:Ä‘íáó*íáó ,P € 1HÈ   !&€‰Å;¦¥ËøãÁ¤ñãÇû.<þïÿK,r¾è¢‹|÷~ݺuiÆŒ‘´Ç‘ûÎ�;©V­Z–¿YÔþùçŸS… |ï(Ð]ýŽ­‰Ãsé�*îòJ`ß¾}ôòË/ÓèÑ£­Í0bY,d¿ÿþûé²Ë.³­fРA4iÒ$ë—5|øp¯&á>È*Œg³Š•‡œ@XÆÏ!Çó@@@4Àwº¨(bN‚öð9‚öðù�(À„˜$d�ÀÄbˆ�ÓR`ü�NG¼‰á×_5šÆïäsÎ9‡ (Ǧ¢M >8tè>|˜Š+f½CJ—.MûÛßÒÖ›§Ž;f½sÎ=÷\­BQ  ,Œgƒå�Ú¢Eãçhù Ö‚€€@œà;=NÞD[@ ´ÃÙM-´»¡…¼ ¡!€ ±Ð¸†€€€€€'˜Xô„ 7e‰ÆY�jA@@@@ „0ž ¡S`Rh`üWÀÈ9øNÏ9—£Á �1Ú3Fè{´ûŽ‚Abé)9r„–-[Fùóç§'NPÙ²e©U«V»FW¹*†íÙ³‡>üðC:ãŒ3èøñãÔ¢E ªX±¢Ê­È‘ °zõjÚ¿¿ÑÇùY»úê«©dÉ’‘°FK .ïÃ¥K—ïsNü[Õ®];:묳‚…‰Ú²J‹YÅ�Ê]ÀøÃ%0d�Àx6ÆÎEÓ2&€ñsz„ºÖÙt•›q‡@ ʰN¨Œ*ç3b�й à;=ç× hw�Lû ´kGŒ @tÀ„XzªŸ}öåååY™š7oN<f�{&IW¹*6Í�;—zôèae]¿~=5iÒDåVä�И7ouéÒ%é™]±b.\8ô¶GÝÀÇÓÞ½{éÛo¿5ÄÕgžy¦ñ¿<áQºtiªT©’±yÆœ C{ãò>|ùå—©k×®Ò¦M›ÒªU«ÐïÃÐɲ‹�F5¾ÀøÃŒ(@@@@bAãÙX¸�ÐDãçô`u­³é*WS7A± ¬f¯K`�0{ìu¬â;={þDÍ U´‡Ïs´‡Ï'°@@�&ÄÒCúÏþCÕ«W·2±`��ùòåS ëœEW¹*F½úê«Ô¹sg+ëÇLõë×W¹y@ Ô6nÜHõêÕ³l¬Zµ*mÞ¼™Š+j»£lÜÑ£GiåÊ•4vìXZ·n�´)Å‹§#FPÇŽ©F�Òüº3Äé}8mÚ40`€…lذa4f̘Pm ÐíÏ\.‹¹ìýèµã�èù ƒ€€€€€.Ïê"‹rã@ãçô^ԵΦ«Ü8ôI´ÂNë„Á{ë„Á3wªÑïuB|§‡Ç·°¢B‚öðy ‚öðù�(À„˜» ±öíÛÓÂ… }´ûU®‚Ë A>"´«�":}ú4}øá‡ôÇPÉ’%“¢õ«•€\ndÂû·ß~£† ÒŽ;¬*·oßNµjÕJ1!“zÜ´'îy,X@�:uòÜÌ¡C‡Òc�=F °-#?Åé}xêÔ)êС-[¶ÌâùÆoЕW^éÙG¸1:0±_ÁR"Œ?Ð @@@@@LÏ¢/€€3ŒŸÓ÷Qxî×:›®rãÚ׃˜ÇÏ&»¸·/›líê΄7Ö ƒ÷&Ö ƒgž®F¿× ñ�.ÿˆÚÃç%ÚÃçX  @b¹7!f÷•?~C¨}þùçGMFJ%ðÍ7ßPåÊ•� Í›7§Õ«WsCÒC Þ,Œ>|¸eظqãhÈ�!¶†fR�ž–G¯ÔY³fQß¾} oÓ¦ •*UŠø}óæ›o:æëÓ§͘1Ãv“P~ŠÛûp×®]T­Z5‹w¥J•hË–-T¢D‰èu2XìŠ&]áBæ,Àø#½Ž9blNâoÎ'NPÙ²e©U«V{MW¹*†íÙ³ÇØ$zÆgÐñãÇ©E‹T±bE•[‘"A€Ç‰û÷ï7ú8?kW_}µ±! Dqy.]ºÔxŸsâߪvíÚÑYg�‡ƒ€€'Ïz†›r„ÆÏé­Kx®«Ü¸vÛ æñ³É.îíË&[»º3á�uÂ`½‰uÂ`y«Öæç:!¾ÓU©#€€I‚öðõÚÃçX  @b˜Sè&9›eݺuÔ¬Y3£ý]»v5¢ÛçË—/gyèn¸WÞâà¼jÕª´iÓ&Ç�^ëÑÝþ¨”¿uëVªS§Nй'N$Ž‚Ã›@£®óÆž„dqÞÀ�Sî{ñÅ�çKLð“·1aÂóÌ3�ÿåï¢Ò¥KoîäÍ3æwRLL©2.ï×_~9i ×´iSZµjú}:l�Àx6‚NƒÉ�Àø9=j]Âs]åÖq®(îóøqo_ÀÝEZ�WÞX'”¢õ5Ö }Åé{a~­â;Ýw× @ˆ=ÚÃçbÚÃçX  @Àï 1> læÌ™ôûï¿Ó¯¿þJ7ß|3�sÎ9 –„3‹®‰+]冓bt­š6m 0Àh uùè4Dh×çO/¼ù�sÇwÐÔ©S-ÃæÏŸO�:ur4ÔK=úZ½’Y”žÈ›…"ü1•/_^Ú�ð&‘;vXyy¶mÛ¨H‘"I÷ÃORœ¶˜qýúõi÷îÝÆu>�ƒy³Ÿ�âK‹ñõm[æ÷ø#nŒÄq‚_›*u•«ÂÿÕW_¥Î�;[Yù»�«�@ ê6nÜHõêÕKú®Ý¼y3+V,êM ­ýG�¥•+WÒØ±c‰…²ÄßÂ#FŒ Ž;R�5dÙµ_�Óû0q¼Æà† FcÆŒ ÕíE ¾ÀxÖŒ($¦ü?cýN­£`ýN�“™+îóøqoŸ;oëÏí…7Ö õûE¬ë„Á3wS£_ë„øNwCyA˜íáë´‡Ï'°@@�€ßb|¼öE]d é>úè#jР�‚%jY¸|˜š‰£že*0fñ=GærÄ(j™L\é*W�”Þ\'Ož$þ�¹ñ`¦hÑ¢¾TxìØ1˿َhÇýŒ…7,ŽæÔ­[7š3gN(gýæfú÷Ô©SÆsQ¨P!_üË…ð3ÌQîEÿzå-FuG› ðZ�ï#ß@k*ˆßk-Z´0ìfzÿý÷©qãÆÊ5òïB£F�’ò‹¿~ú)]¿S6:MF]ïÃLl{úé§éöÛo·Šxà�èÁ̤HÜr˜X ¹ƒ`^¿ÇqÃ›Éø# ]åªð�KDb•¶fš‡¿�>üðCcœU²dɤhý™–�ûS dÂû·ß~£† &mÔܾ};ÕªU+¥¢Lê�ßþ"ÀÌÓm\–±:t(ñô‰§I%Þ„Ÿâô>äù‚:'q™é�7Þ +¯¼Ræ \�$Ï¢C€€3¿ÇÏX¿û‹u×ït®Ûx}ýžÇg;ü^ïòÚ6¾OGû2±'ݽ~sÓÙß°N¨«S.Ö S9Çu�ßéÁùäZ´h=óÌ3tàÀ�”VÝu×]Ô«W/ª[·®B‹ÿÌòõ×_GÆâÅÌÄ29zï*îÙ³§±íVø¡«\å†)dä(rÜvކÌÑÍz÷îMÕªU³îäcÂ^zé%ãúÏ?ÿLýû÷§êÕ«“H|túŒ3hÉ’%I5qTºáÇQÏœ6|ú駆8\,—w ³9²b„5öÛÆQ)Y,ëtL8 aù~³=lïyç�—–ÄÁƒiÒ¤IT°`A#‹ Y°ÎÉ,�ÿÿèÑ£“Ê5j”!ÆïQÀî9‹.n¢A_~ù¥±ÍÇ„'.Hs>ÓíÚµ£¿ÿýï)‘´Ërê?ÜwX`kF�fÞ³gϦ 6þóÊ{РA†/ÍÄ}÷ÆoLáí—_u¼�¸?.^¼˜^{íµ$öü\Ýzë­†h£L™2Æ&’'Ÿ|Òx9qÄm~÷‰ÏFbŸá|5kÖLŠŠê¹3þÿy¢‘7)™ÖÙîœuÖYÊŲðáòË/§C‡Ñ5×\C\p]{íµ†hË«ŸÜö;ó9ÎÖû0–ßþç²ù½Ê\Íß·²eËÒ¦M›¨B… Ê~BÆhÀÄb´ü•ëÖú=þˆO·ãÕöë*W¥~þ]Ú»w¯1N`¡öùçŸo|_"¥øæ›o¨råÊÆ…æÍ›ÓêÕ«3ÞÀ ÎÎ2áÍs <6Ó¸qãhÈ�!¶•eRü÷'�Y³fQß¾}q´iÓ†J•*e|¿ù曎ùúôécÌi˜ã�ÄŒAø)nïÃ]»v%Í'ñø�çK”(�®  Ê0žUF…Œ9HÀïñ3Öïô¬ êìš~­Û„eM×zWXÚ§«/èâ&ÚëWs»^ƒuB"¿×‰°Nø—¾Ái}ë„ò7¾ÓåŒ�@ ™íáë´‡Ï'°@@�@˜'ÄvîÜI7ÜpCRijtMâhÚ&L Š+¦m¹jT/ŽfËÂØ¼¼<«¼öíÛÓÂ… m?u•«àFWY^xáêÞ½»u�¸é@¼Î‚OA^vÙe–Ù©B†¯Y³ÆÚŠI,—'4XPÏQíd©K—.ôÜsÏ¥Dõæûdí±+[Œê�(Ës²�…¸,ÄÎô„YÛuq3ëe�2 1žzê)™)Æu-_¾œš6mj›_´—³Ù׉}Žo4Ÿ% ‹×ì ¶ã�(8à{¸ß±ÐÚN •©_u½�–.]j°�¥W^y…®¿þzcãŽ)&wêƒb[ýC‰‹,”æçÉ­`Á<CöžP}þÜö;s¢JöþÐõ>4Û¥ÃÿfÙ¢ÈjÊ”)4`ÀYWÃõˆÀÄbD—£fû=þˆF]Âs]åÆ�¶ÛÃ|›5kf˜Á{9š³�ð6ÛvÆ¥~¯¼E/�ƒyó ÓF ¯õÄ…s¦íàñd�:uRŠ™8q¢1–âM ‰Q×y¬ÁcEÞ¨Í Äôâ‹/Ï—˜à'ožây¸Áƒ[7§ÛÜá­Ü wÏÆÝÃh_&ü?û)h×5_®k�MW¹™ø7ݽº×mT‚‘éXGÓµÞ%›ç·c­£}ºúƒ.n¦½ºûÖ Óët¬a��ŒÍÖéÖ§±N(cá;]Î9@’ @оA{ø|‹@„uBL\(4ÅÈ"WrôiŽÜ¥š8J»)å{œíºÊUµÓM>ŽÎÎâ3‰W‰×yAž½=ô�m_%Õ¨QÈØ,ã-ÖËâJŽxnuß®ïòfY¹*qé=âàNmöS$œŽ«.n\'Gà¿âŠ+”}›h'GçˆübûÏôéÓiĈ)›!øYâId´÷èÑCÚµìx‹lxàâ�(v)¿êz­\¹’Ú¶m+m»™�…ãÇ�·üåô>¹¤Ûˆ£\ùÿe#´óŸŸ}öY#R¼É«ŸÜö;s#J¶Þ‡ÌJ—ÿM?ØMÈ¿ýöÛ)ïP?ü†2²O‹Ù÷,P'à÷øƒ�æ“~xaþ×_¥›o¾™Î9çuƒB–S—ð\W¹!Ãys¦M›fm@3¿—uo �<´ à…7¿sî¸ãš:uªU3ŸDƧ*9%/õdЬØÝÊ¢ôDÞ¼‘™ÇýåË——¶•#¢ó&‘ÄyÞ€°mÛ¶”“Çà')NÛ Ì¸~ýúÖ˜Ÿç‘˜·] o5à.�¸Àx6îFû2!à÷øÙ/A»®ùr]ëlºÊÍÄ·éîÕ½nÃugkM×z—lžßŽ7Ö ÿ¤¢»¿ñøë„ÎA¡t­a�PÞï°N(ÿÃwºœr€$€ =|=‚öðù�(ã„ØÉ“'©eË–ÄѱÓ“O>I:t råÊ@sÄ-E‹‘¥�D­¼ÈvÁ¤¨9òñµ×^k,frÄðõë×S·nÝl…ÖvÂP]å*¸ÏSÙÄŽx=±>ž›�Qç…ÉcÇŽOø°€˜܉‰6iÒ$éoéʽóÎ; ñQ•*UŒˆç|ð�!�Åî,~æ¨ù‰IÖ·Uûöí£ï¾ûŽøˆ»Ûn»Íº�ÛÌ"nN§N�¢’%KRõêÕ=ùÀÍMº¸±£wïÞFäûÄtË-·Ðý÷ßOçž{®ñgîßï¿ÿ>õïß?Å_|ñE ƒtöò†“»ï¾›~üñG*T¨ýóŸÿ¤ýû÷{âm'$±ëwfÛ¼ú5è÷¿ÓøD‚bÅŠÑ·ß~Kÿþ÷¿éÑGµí2NBõyóæe˜ÉO1sç 1,ÚILNÜôuÎëÕOnû�9 #{èz:ýnøá“9/PµhÑÂÚÁǼو#X"Å�&ãçÓ8·Èïño¶ºè¢‹,!3Ý Aßrùüûg¦Â… g|B¿£ÍÓJ¸¼Ä”‰ð\W¹¾ÁÌ  þ&ãÿ˜¿óŠ-šAiÝÊcÓ¿¢/|©ÀE!âw�IçÌ™c´7lÉon¦yœÅãA+ø•øæ¨[¢½ò7 :‰£Mû½Öc×~ï#¿8ë*Gü¦åzx|Ú¸qcå*ùw¡Q£FIùÅß ?ýÄ9õ;e£ÓdÔõ>ÌͧŸ~šn¿ýv«ˆt›Í3©÷‚ēƳñô+Zå¿ÇÏ~Úƒž/ϵõ» Öm2´ë˜ÇÇ:¡ó{ë„öë²X'tćuÂäçÉn}ë„òo|§Ë!€@2ÚÃ×# hŸO`€€�0Nˆ‰ ÄÜ 8‹ �fóx‘¿gÏžVk�¢´óÄ—(†þä“O¨^½z)¤XlÈ¢z>Ž*1Ù Hu•«à>OY¼ 8�«'Nœ ¦M›&Eù¶[¼tšpáˆÁ­ZµJiË÷ßoø 1’×óÎ;ï$E–µÇ’ŠP諯¾2öfÊ– D·�7¦ô}Ó0À¶_Ùùcذa4vìØ¤üNöòÆÊ;E˜tË[ŒÎÏ=GÙ+S¦LÚçÂm=ºÞGâqlôÚµk�è�bâÍ=vw´óÞ8P¡Bú駟誫®¢‘#Gú&†âgÐî™eñà½÷Þkˆ¨e~�½¼ÜúÉk¿“½?œÊÍô}¨Óÿ‰lï»ï>3fŒõ§·Þz‹Z·n-Ã�ë$€‰Å:-‡MãøCt�-ZDÏ<óŒí&×»îºËØ|Y·n]eO~ýõ×Ä¿+�=öXR™¼áˆ£óx†O‡RùNM¬TW¹Ê SÈÈß�Üvs1oª¬V­šu'ÿËWù:Gã�”¼q”EÓ+V¬ 3fÐ’%K’jâ1ÜðáÃ�ƒœ¾/?ýôScžX.�õØ¿Ù?q5û‚mëÚµ«!–u’³–ï7ÛÃöžwÞyiIÑÊ<9ñìž³èâ&ôå—_Ò²eˈ£%òÿ&&ÓíÚµ£¿ÿýï)‘´ó:õî;,°Ý½{·‘�yÏž=ÛØ¸ÌþóÊ{РA†/Íd·éÚO¿êxq\¼x±±a;‘=?W·Þz«mž¿çY¬Ä�øYäÄ›¼ùÝ'>‰}†óÕ¬Y3éT8Ï�ñÿß(ŽûØÞ¤yÖYg)Ë%.¿ür:tèñÉo考ð&u¯ÏŸÛ~g>ÇÙz&ÂòÛÿ\¶¸a—Çç›6m2ƤH  #€ñ¬Œ®ç2�0ŽŸuÍ—ëZgÓU®®~ÔºM¦‚v³ý~Íãc�0}�Â:¡ýF¬: Ú±Nø×3%[ŸÆ:¡óûßéº~íQ.Ä—íáó-íáó ,P à÷„‹›ù¨aSÎ X_|±‚%e™;w.õèÑÃú‹6yÁÙIP`'¨Þ¼y3Õ©SÇ*Ã.ª0 úöíëhÛÞ½{S„ ¢€TW¹®€¹Ììe`6yòdChã”xò«aÆÖe^$fŸ%ŠKì&\XüÊ¢§d79Êãr3ÉÚcW¶ŠPH%�Kôž²ëâ& ]9úÅĉÓŠžE?Ûm±³—Ÿ3>RÐ\Ä÷ê“ÄûDÿ4oÞœxÒ³@�i9»õ«®÷‹¯L! Ì|X 唦M›–²ÙÀIÐî©£¹¸É.ê…x;¿Y4Äï…¼¼<*_¾¼‹ȵ˜Ïk¿“½?ìÊÍô}Èü‚ò¿hÿ#�µd„ T±bÅ´î]°`�!•%^`á ¸üÛi¦t¿÷ºÊ•Ùéöº¸‘K\8¯ó>‹ /»ì2KŒìT'‹¼×¬YcmÅ$–ËT>‘+qÜâT.ŸvÃ'ÙEm—µÇ®Lq\Ãß®«W¯6ÆKvÝìʰc¹õ…J~]Ü̺yÁ™7#ˆ'­9ÙÆâöåË—›¨í’h/ÏE°¯»wïž”Ý|–X€.^SåͧÄ%žvÃýŽ7`³�²þçÔ>'¿êz-]º”˜…,±ðçúë¯76Ì�l}�Ø¿eõÈ®‹ïx§²rÌS1üò“Û~gŽ…eï]ïC³Ý:üo–Ís;ül›)݆y™¿p@ ·`<›[þFkÝð{üŒõ;266gº.è΋îrµnã— Ýíz‹®õ.Ù<¿�TlWÉãÎÃÞrëâTãVc�0U·Ô:‘·^—þ.¬f¦›ˆÊ:!¾Óu<=(âM‚öðù‚öðù�(p;!ƺXèmËžX I9pÛ¶m­¨ƒ/¾ø"5iÒ„x¢Ì.•*UŠJ”(‘t‰E _~ù…~øá#2G+L—Ä(iâdŒ%€œ9šsÂjJñvGû‰‚]å*¸ÎsÙÄŽx]eÁXœÔᨆ,N0ÛM¸ìڵˈNè”ÌHjÚL⢨¬=qœ¨Ê”G[¿ð “¢ƒr¤ÂK/½4m¿²Û<ÂQ/Zv~þüóÏ©F�iËv;1(Ö£z¤¹Ûzt¼�öìÙ“Ôïù}ÄQÅ1",ŽVzÉ%—$‰º²%hgÛøýÈD9ê¾Jâ6rôÁ°(ÆNô“XŽ[?yíw²÷‡Ž÷a�þÅsA áTúòøK‹þòDiz ¸ȬñKÐ.þöÈê5¯Ë¾•9út›6mT‹3Æ=‰'9ýÞë*WÙPÝüÞò7 :z衤 ÒUÇß™ü-%nlëåqG<籦JâßÍ… JËÍT n t²ÍO‘pºöëâÆuò7íW\¡ìÛD;�NÈI´—ûoå |fdv³ ~–x Ú7лáíf ’‰_u½�8BÏר&žÏ?~¼å/§÷‘ÈÅÏqŠq�möÙg�Hñ~$¯~rÛïÌÍþÙz2+]þ7ý`·qGeÓ¹~D Ñ&€ñl´ýëõp;~Æú]{c g®KEmý.Èu›LDZfÏ÷c?Óõ.¶Eö�m÷¤ªØ®’Gï[àÏÒu¬¯Ù߸ X'üØh¦ ׉tõQ¬&ë[9ËtAú?“uB|§ëzzP.Ä—íáó-íáó ,P àvBÌ.b¶B5ŽYžxâ :th&E÷Š‘ ÄÉqàÀÑÝ8"žy仓¯¿þº!À4“¸0««ÜŒ�¤)@6±#^ç]ó3fÌH¹{ß¾}F$~Sb·€-–«Q›£^<Øj‘ÉZÖ;*“P*ytúÉ,[7ñ9æM¼Á£H‘"Ò&É¢®‰ö: ŒÄŠÜò¶Ú°`Z–ÜÖ#+ÏîºÛ÷‘ê³0vìXâS œÞG^lÍôŽ€É"Ž`ê&ñ~·8‰øÝúÉk¿“½?t¼½ž.àÅÿ,ãMe¦0Òn³‘¿!ox `b1¼¾�e©ÜŽ?d ý´ó¢Ë–-iݺuIÕ=ùä“Ô¡C*W®œñ-ÌÑ™Y-F–vÚXwøðaºà‚ RÔùøÚk¯5¾½xóÜúõë�“Mì„Övßպʕ±özÝíïmb=}úô¡!C†Ø�;fœâ‘ìY˜™˜˜!ÿæ%&»_ó:ŸNtóÍ7S•*UŒ(é|ð�!�}ÀâgŽšŸ®ÜL…<–úî»ïˆ7˜ÞvÛmVUÜfqsâ�¾%K–¤êÕ«{uƒò}º¸Ùmg£n¹åc“ä¹çžkØÈýûý÷ß7N/ýñÅ_¤0Hg/o8¹ûî»éǤB… Ñ?ÿùOÚ¿¿'ÞvÑÇìú� Ú«_ƒ~ñ;�O$(V¬}ûí·ôïÿ›}ôQÛþâ$TŸ7ožQ†™Ì͉'¶)w@!£Ý©xœÅiƒƒÛz¼úÉm¿3¿Õ²õ>túÝðÃÿ&sþ=nÑ¢…µBeã¶[!?€@< `<O¿¢Uþp;~Æú]² =jëwA®Û{Á€É IDATd:Ž5{x¦óøªk#X'|•ø”@3ùÁ-Èþ†uB2ÆI‰‚ö ׉üùEr.ë„©ldº‰ ýŸÉ:!¾Óu?=(âG‚öðù‚öðù�(p;!æ5R˜“)ªGóÇ6ï?tè�!8àÄÑ¢y±—ÿ½bÅ b!‰™Ä�![_¯^=ëºjÄ0Ž P­Z5Çût•«à:ÏYÜ. ²˜½_¿~ië…D*‚ö‘#GeI´W¼OÖ»òU&ÙTòÈl÷㺬ýNu¤»Oœ¨r#rË�<9ùX5ñú#�HŒ�Ï¡ù´¨Äèï²öØqRù¶£ò&îkæ;Þ+·÷éâ&~±]éæìü1lØ0âM~‰ÉÉ^ÞøÀBy'Qµ[Þb¤p~îyS0Ÿ(—.¹­G×ûˆO>èÞ½{’©k×®¥fÍš¥˜Ï›{ìþî4þà &¼q B… Æ©}W]uñ¸Ý¯¾ËÏ Ý3Û AcÃ/‹¨e~�=nýäµßÉÞNåfú>ÔéÿD¶÷Ýw�3ÆúÓ[o½E­[·–áÇu�'€ñlŽw4?-·ãg¬ß% Ú£¶~äºMXíX'T{ b�0Ÿ-(¬a�ð¯®�uBû÷I&ë„øNW{G#€À_ h_o€ =|>�E  ²=!&ŠaMf±ú»ï¾K¼ó~É’% ­ù+‹8#.ÞÝsÏ=I‹lN…Ë„ºÊuÕX—™Ý. ªl:ð"h#­;5cÕªUÔ¦Më2GÌ_´h‘%L�µÇ®\™_ù•<.Ñ{Ê.¶Ïnb™ù’…ºæQœé µ;µ`Á‚ŽþPµ×-oYt¯Ï´Ó}:ßG*"i¶K¶ÁÆSÓpGOåèŽ,ðyï½÷Œèívgͪù}ÌþL¼¸í^ŸÙûC¼îÇûPüÝÐéñÝì$¸ÔÐ PdÀ0±0pT—·ãYe,næG¦|Ó¦MÆÉAnÒܹs©G�Ö-¼Äß›NbL;AõæÍ›©N�:VvQ…gΜiœPâ”öîÝKç�w^ÒeqaJW¹nx¹Íëö÷–ËO7Näë<Þkذ¡eŠ8Fà vÂP¿òw‡S²óßjÖ¬iÝ"k�]Ù*ß6*yܲ÷’_7Q課©Oô³Ý·Œ�½üœñ8$ÝøÆ-o¯Ñ³ÜÖ£ë}Ä›�ù½f&Ù8mÚ´i$ž€•­…r»èøbßæ÷/oá÷B^^•/_ÞU÷wë'¯ýNöþ°+7Ó÷!ó Êÿ^®œ…Ì ±#€ñlì\ŠùHÀíøÙoA;ÖïþtfPßÁA®Û„EÐ.—˜�Ö “#´ûÁ-Èþ¦j¯ÛqÖ ƒ{?¹ýiÃ:áïF s¾Zü‰Ê:!¾ÓÝö|䀠=|}‚öðù�(p;!ÆErd2^Ôþúë¯ÆQï»wï6.s4&ŽjÇbP»ÄÇ~'F»3ó|ôÑGtÓM7Yå(4%)‹8#Ìe ‚fa²Á³®rݶ×M~· ¨~8Ù>/ÂP¾Oô�(¾–µÇŽ�̯võ5i)Ú«ƒ›X¦ª ÖŽ ‹8"·)ñË^ï�ÒÔ©S-\*°^ýªû}¤òŒ±í¢ÀJÆÈÍ{AwÞï¾ûŽ8Ò# ™Ì߇Ä:9º;ÿv¨¾{ýzNdï/ýY¶ÁÇK™^ýA»îžžò1±_À9·ãG°ÐÛnüÁãŽܶm[kóÔ‹/¾hü¦°èÜ.•*U*夊£G�÷ÿòË/ôÃ?‘~kÕª•¶1ƒ ¢I“&9~‹ˆÑ~‹/nlöJ˜ÐK)ŸÛØ»woc3˜™Äß{]åÊ=ç=‡Ûß[• Xâ÷¼Ý‰CvÂPÞ XµjUÇÆœ:uŠ.¿ür#*»™Äo5Y{ì �òøƒÛ“)7޶~á…&mrܰa]zé¥i;–Ýæ>¨S§NÖ}v~þüóω�4O—T|’x¿X�Ý©^}ŸxŸŽ÷Ñž={’ú=¿�¶oßN•+WvDħR]rÉ%IßîÙðûqôèÑFÔ}•ÄmdA>ov©[·.ñ¿uö.[¥ßÉÞâu?Þ‡Aú_³Úm6Rñò€äŒgsËßh­;nÇÏ\:Öï:®Uøµ.è΋깃\·QYOQ3©äI7®R]Á:a² ÝnAö7¯öÊÆ X' ¯ Ýî͇uBçß'ÕgÄË:q&ë„øNWÿ GN�? @оžA{ø|‹@x™KW¬øQ¬2)"–·uë֤膉׻téb,„—+WΈ˜X°`A*\¸0=ÿüó´|ùr+«LÐþ¯ý‹þçþGJH6#øý*WjXÜ. ª ¢dN6×/'GÛc¶9ˆ’µÇ•̯|�Jž Ü |«n™LT‰ƒe1²¢W{Ýò–‰Èœ»­'ˆ÷‘Ê3ÆíñzDªrg # QxD¯^½’jã�sæÌ±žk·~òÚïdï/åÊÞ‡^Êôêyž}öÙkALÝUh €‰E PQ¤6nÇv³31î‰'ž ¡C‡fR„q¯, ”ø[Æ›|W¯^mŒ_Ò%»Ópøesó ®r3’¦·¿·â÷¾]Ñûöí3"ñ›§ÀØ-rŠõ6oÞœÞ~ûmÛ Õ‰uð aƒ¶þ$F“µ'nã?¸‰Ï1o*à EŠ‘v=ñY“m0`!;‹µí6Î'VÔ÷¦Ûz¤@l2¸}©útìØ±Ä§˜I&&ðb»Û{8ºÙøñã“6þ¨”ÁøÝâ$âwë'ñ= ÚïdïñºïC¯§ xñ?o„àMe;vì0Üb·ÙHÅ_È �[0žÍ-£µî¸?ËJÇú�?ë‚2Î^¯¹n£²v«ò�¬’'‘‡×¹qqL‰uÂ))'jÙõ»tÜ‚ìoªk`nûÖ £%h7û(Ö ½k&¼¬g²Nˆït¯¿è¸r—íáó=íáó ,P ¶ 1ŽÀئM›¤ˆxÜŒ¥K—RëÖ­¼ùèì믿Þj±LÐ>nÜ82dˆ”�lð,øý*WjXÜ. ªL4Èœl®×‰*qp&Fø’µÇ•̯|�Jž Ü |«nâQfÆ £1cÆXbâtÆ­Y³†Z¶lie‘ùC¥ÿ¸åÍ“ýúõ£Y³f9>óNmpã× ÞGªÇ-rdÌjÕªYM ƒ D¹# Y¼Þ³gOë¯,jÚ´i“9Ñ�Ÿ2y¿ÈÞ^ž?ÙûP,S§ÿ3‰¼àÕ·¸/;0±øÿØ;¨)Šsý¿‡EAÙe�Cô‚x‘MYâ***AD”Ëâ5"QEˆˆ‚WA@ô‘k«.( Ô¨,ŠÄ ¯ÇDCX(ùÿŸÎíÎL}=ÓÕ=]35ó=uNNä›ZÞú½5=]UO½•îl5�°ó�¸¯L×}7�(Q¥÷íÛ'%K–t:‹hÑx?À¿_{í5�@ÒMêü#Êê ú½7Uo4oê• û{«s{PÐï­ßûÁ¨Q£d̘1�F«öªå‚úã׀λ�Nž@ãcÈÔÿTM¤+§n¢‡¹ªõªQÕÏÇŽ+#GŽ $–7–tíÚÕ«wÓ¦MrÁÄÞŽ_…q?�ÔCÉ©:¡ÎýlšàùŒ[ž±fuÖl†êrË…QÇ]Ðó#Ѝ$èy¨þn˜ô¿jKíÚµ�+Ö+W®¬ë"æ#(†8Ÿ-†Ng—µ „�?Uœ© =[ëåqí³åÛþ]6÷mòMÐÎ}ÂhÚÓqËæxÓ]‹ 3/ã>á?Ÿø6ÍÓƒ~ƒÔϹOØÝCbë>!ßÓÃŽjæ'  Ý¾1@A»}>¡E$@l[ó¬¬_¿^Úµk—¶7A§°ý".Y²Ä§¤ªa~ Ú¹O˜�}âLæé|O�ú‹Îr$P| PÐnŸï)h·Ï'´ˆH@ƒ€m b6lH¯ëltŸ8qBºuë&ˆÒî&u1F]hƒ(Ö‚®!W£°©RSõj¸.r–\l Âب‘þÔÚêæ«Z/DH¸B=]B$·¶mÛzYü„Áa7´#;$   njßp=:6ÿK—.Ø�3fÈí·ßîåS¢¢’¢,T©í¼ñÆÎ-A)Œ_³ù<ÂóË�þšª*ûl Ú�;&Ÿ}ö™lß¾]p€à§Ÿ~ؤ#DRûtèÐ!Gœï^E_œ"´ûýn˜ò¿ºPÎø®—-[6è«ÂÏóŒóÌaÅÜÜ(ó�Ç "/© cÿàÁƒÒ¦MÙ¹s§ó1Þ ðž1¨_:ùä“}çï¾û®Üxã�^=aÝt(*��>±­ w• hÕ©ìª7lÃä·eþuU�“õÇ���|¸ïskøíH5΂úuÜ=?¢ÔtÀ'J�àÅÿ™l”›¬“H ?p>›~¢•¹!eþœÎÒLíÙ\//ŽûwÙÜ·ÉÕ>š‰ý.ŒyîúóÓí¯fs¼éÎAî‹pŸ0û‚vîŠèŒç yz¾ìò==7ïl•ò™íöy�‚vû|B‹H€4ض ¦ž׉¥ž"G·ÕÍeu#®zõꎨ¯Zµj))A4Ó·o_'º—›ÔMMSõj¸.r–\l ú-(é+ý|$ Ö =üðÃ2jÔ¨”~ÅaN";$  ê¯8¸©§ÞaÆrƒ ÒZƒÃ#:tp„nB´7ˆ¿Üu³<,ï… J�=¼vuÂú5›Ï#ðOw »ºø�¾‰,â“;vì�F�yÕ–/_^¶nÝ*gžyfè¦Tã™œÈ ìxˆ:îrñ<ôûÝ0åÿ~øAÎ=÷\/BeÏž=eþüù‘!„v2 d•³Š›�eHÀ¶ùºã7Ÿp»‰÷ kƒúã‡*ȯaßS3pG`QÜ2´«þ¸ãŽ;dòäÉ)ýab^hA·Â¥«ãûIJÙxé2R¯§Ïöü#p°jdÀš@ôéÓ')7F@Ü‘øU¿~}/OP_£~O‚žQê Ú(�R'@Dñÿ_ÿúW©T©’ÇQ�çi¸ŒYH€Š!Îg‹¡ÓÙem¶ÍŸ³¹^^÷ï²¹o“«}4û]~óÿ\õOûË2£ nÙoºsаógîrŸ0ñ«uyz¾ìò==äÚÙI€„‚vûíöù„‘ h°mAL½ 9h’‹È‹]tQ’ÀÝV%j4`äY¾|¹#M•ü&óꦦ©z5\9K.&f~ Jø[Pˆýû÷Ë9眓t]8¢C'ŠjÃ.XøÕé·Y­N&s%Uý7lê0@pŸ›Æ�'#FŒH;®tpD]0Ë{õêÕÒ©S'Ï^UØ’ª#aÚÉæó((¼ß8YD~H¤(è÷¼ëܹ³ó, ºí"±JŒ¿{î¹G&Nœèý·f$F)ã'¿çKÐo‡Ûp.ž‡~Mù_¡äê9÷Xd}E pa‘£"ŸØ6ÿÀ|ïk×®MÂøòË/;·¿¤ºÕ¿[×]w�W&HÐþÄOÈÝwß誠Í;õ·+®z Ë C.~o3y?P?Õ÷” þø¡ ò+ÊèäÉÀ ÚE£¾Ï§ã¦¾W6LÆ�Ó·nÝ:gÎï¦ è¾†áí72¡=[Ï#Ýëé¿øâ ©W¯žÇ>ÛóíA«‘Q�LXœnˆR¿Ó&ýÏ탑YH€Šà|–ƒ‚R°mþœÍõòâ¸gr߯–}4û]øÙÒ?SÏ3ÜLŽ·¨ë a÷…¸O˜}A;÷ ã‰Ðž/û„|O7õTg½$P¸(h·Ï·´ÛçZD$ A î1¼€7iÒÄ»ÒY½Æ9È$u›Œˆ |ÒI')ê'Št3ùEvW£r×®]Û¾Ÿ~úéEêÆFòõ×_ï,“ß®©zƒXEý;ûì³�ÈúˆŒé&uÁ"]0øµ_¿~N„¶ ¿îÞ½[Î;ðÜo~´DgŸMG7‘û„|O7õTg½$P¸(h·Ï·´ÛçZD$ A î14¹iÓ&Á‹:ÄÃ-[¶ôh¦2í믿N¼†ÄÉÑ,^ê+W®ìüõbóø¶Ûns6 ýÒàÁƒ�e¢`ÄïênˆŸ_yåiÖ¬™SÍ/¿ü"Ÿ|ò‰=xÕªUEªöÛx7U¯†û"e `DYhЙ˜ù-¸ ªð·#GŽÈ£�>*8,�˜ü*ìÝ»W7nœÅÛÇ\ äuÄè¸VÜoÌøùUíêyî¹çäæ›ovÆ÷Áƒµ¼‘œ”PÈ·T·@Xܵkפh‰ˆÌ‡ï¡ú�@´„ $u1ÊøAay?~\Ú¶mëˆ,�Ê—/ï8¨S§NZäaÚÉöó‹é/¾ø¢s�âÔSO•}ûö9ÑÏûöíëÛ§TB ùóçKïÞ½½2íÛ·—5kÖ ètÆ*émÚ´ñýAàƒïnP¨P¡‚'tA™¯¾úJ^}õUß[`ø‰‡EÂø vGw¹zúýnÄå×�™DÕ Ìc.,ÚåZ“ž@Üó�Lí6lH:ЇÃ�xç,Q¢DÊŽœ8qÂ9ˆ•xøUÝìPÅ‘·Ct8yð.æ&õ÷ÞT½&Çm®~oÕvq .ñ°lª>«ÂÔ9ˆZoЭSh½ñîšÊ¯ø{XA­)Ÿ™à¦öÍï rªþ̘1Cn¿ývïcuã0ê{`XÞj;A7츇i'›Ï£ÄÛ‘tÙ‡9ˆÇø§jW�~Vœ"´ûýn˜ò¿ú› ÎJ�ê¶“8Æ ë ÈœÏæ¿Ùsâž?ÃRîßÝ“Š²/hÊë¦ömlÙG3µßeKÿL� SÜL�·¨óô°ûBÜ'L-hç>¡ý‚ö|Ø'ä{º©§:ë%�Â%@A»}¾¥ Ý>ŸÐ"  &Ä4šM›ð FVSóæÍå´ÓN“×_=é#m( 6Lú;òCXyË-·H§N�â¾;ï¼S¦L™R¤nD•‡vÅŠIŸáï‰h¿ \SõfÊ1Uy[%ª}—\r‰s•<6©'NœXÄ|ˆ¶lÙ⻪FÈt Cä|á… "mïٳǫbpøøã��¿ùù !_|qRDòD£ÒE‚�Ów©ªÜ62áöå—_ 6˜Õ„¾AÌ_,^¼8‰�›wîܹޏ_MQª¢ð>|xR´LN¹üòËÓâÛŽ©çŒLUwªà@Æ®+âO%(Q}гgOÁâUᇟ-ˆzAÖÎ�;3êqCœR£F�¤ºÂú)ê¸ËÕóФÿ]�ªhßkˆ�R݈‘±3YAN pa1§øÙxHqÏ?2´«Q¥ýPª]ôÛpPíê5ÉxCÔW­Zµ”Ä0¯ÀA¶9sæxyÔß{Sõ†tc¨ì¹ú½UÛÕVúù H@=uêT4hPZ&ê­^~ïqa„Ï¡2³ njtl˜äw8V5‡G:tè�4'{á…äÆoô²F} Ë;ìö®�aÚÉæóüÝ ~CÄOH�mA»zSæ÷[·n•3Ï<3ä¨.z`D�χñ�:îrñ<ôûÝ0åÿ~øAÎ=÷\o !î¹hhdz �@^à|6/ÜD#sD îùsÝ0µ^njŸÍT½q°LU‡‰}´eÃ>šÉý.úgj\˜äfb¼E�/…Ýoîvqh¨ûÜ'´_ÐŽñkjŸØ}eºOÈ÷tSOuÖK…K€‚vû|KA»}>¡E$@l\Û¿¿%ѧëF�=Á"B �ˆfjJ`Söúë¯/"\÷kj\·vÍ5×$ H—,YR$Ò±©z5\:KÐj”�zô×­' ÄUNj» ,Dþ~à�û�îHƦ)ü¯3f† &£G�NŠ2��y?¿¦[$Ê• =Nn`‡ÅªŽ;†'ƒÕu×]çë·(ãÇ­(,oDE„ínºõÖ[å™gžIŠ.ïgd˜vL>�ý‹¬Ó§Oüà° æ4mÚ4íA T¤ö/Õøl4M†¿ýíoÎ- ø_Ô„[4 àD£ÏÔOQÇ]®ž‡è¯)ÿ»,ß}÷]iݺµ‡–b’¨#5?Êqa1?üD+ÿI îùG¦‚vˆcoºé&Ï=Aׯ¦Š^¥ ÚÕ 4€›Wð»œ*ù‰~U©©zMŽÏ ßÛ(�Qoˆ ЦŽw¿sÎ9'é@'„á ´›Â¾wøÕé' VE§¹úíö{WΔ›ßÍ1ãÆ�ó½½'q,êàˆ2~ÐFXÞêö8X?yòäÀ¹G˜v²ù< Š0ï7²-h÷{ÞuîÜÙy–Ýv‘8Ž0þp _âÁyÜš‘¥<ŒŸüæ\A¿®=¹xúq4å@p 7åê9fò7�u“ ÄO€óÙø™²ÆÂ!÷ü92&×ËMí³™ª7ž©êˆ{ßíØ°�Æ}Âh£Æ$7X÷x »n’H%ÌþÊqŸPOÐÎ}ÂÔß½ yz”ñ¬£›€E¶ïò==Ú3›¥H 8  Ý>ïSÐnŸOh �€Ä`öÑ£GBtl:ú%D¥ÆgW\q…·�Œ¨Áƒ–E‹%yî¹ç¤ÿþÞß09À5ò÷ÝwŸoäiDýš0a‚'š[¶léEiGÔjDmô‹tlª^ 7†Ê‚Íßk¯½Ö+ƒèÈ£»IݨŸ={¶ôéÓ'mª ¤W¯^2oÞ¼$N©„o¾ù¦ báÚµk}Û€Ð+T¨�Ö†#GŽ8¾A~¿„MÕ±cÇ:cã BO7ú~:1ú�(ÿjR¯(å„™Mss¿ohß¹·ß~Û×:|/FŽéDe?ãŒ3Rö ÊøI¬, ïH³fÍ<1>lÄ¡€JaÚ1ù<‚�ëׯ—Y³f ¾k‰ 7Dàö ÜAôD¿¨�~1Ô︮Ø&ˆ™ßçØÀ€ÏÁó¥—^ ¬ôqðà²Ë.ø+(éú)ê¸ËÕó0±ßqûuC´ƒßÃiÓ¦yM‰(ƒ|ÁÏí&À…E»ýCë’ Ä=ÿ€P¿™îÍ!7nt.ê&U|‡w餓ŠTá'Št3ùEvW£r§;¤‰¹ÞB`™˜ü¤¦êÕe6_ÐÆPArTA;üûÎ;祿±_ÇŒãu‡œY¿téÒÞßÔ÷Žt]áWÌ!T“]  IDATñNäWÌgÏ;ï¡8{®AúŒ!î¦~rñ=ÝÔS�õ’@á  Ý>ßRÐnŸOh �€[ÄW Nùå—_œ(¾XŒ8ýôÓS Pnß¾}Ž˜ÿ'Ÿ|²”)S&%‰]»vÉ�?þ(ÇŽsòW©REjÕªe-­©zƒÚµùó  _øí»ï¾ó|;D?a#37¡,Ä'™Ô¨Q#2D£þþûï�ò¨¯bÅŠR©R¥Èõ…)˜-n®Mð"u`ãߣ²eË:ß‹Lø…é/ò†á AKâ!�H�®=aÚAwl™z¡^Œ[pÇ8;å”S’Щ‚vÔ™9s¦ï›°ÌãÈ�1ƒ±ƒ1„«!ñýCŸðÿÊT«V-e4ötí‡õS}ÉEqú·_Ô«WÏëK[·n•råÊå¢kl3 ¸°˜Èl"6&æ›6mˆ_±A�è~ÍTøú믽¨ñnˆf!þsëA½~ÞvÛmÞ�Hµ>$‚À²dÉ’ÞG~ø¡#¶OLx/}å•WœCyHxþòÉ'ÎaÝU«V1ÓO@jªÞØœ¬T”+gª¨^ªÜ÷<Ü<ƒÃ‰Éï ÂÞ½{¥qãÆI‡£!°}üñÇB^7AŒŽƒ¾î!ÚÄzýüªŠô‘‡³!ÅgÔðfêGSÜRÝnaq×®]“æàx—Á÷PýN Švƒ ’º4_JÅ#,o¼ß¶mÛÖ»½ ‡3áã:uê¤E¦�l?�0ß~ñÅ�ƒXoÁ{<6rq¸ß/¥´ÏŸ?_z÷îíiß¾½¬Y³¦ÈÍvQÆ&æ8¤ä÷=Â|ß1Ü €õ7ðÊ|õÕWòꫯúÞû ÀO\kã'ô#ê¸ËÕóÐïw#.ÿ»~õ»‰A½½$Ê` �Â'Àùláû˜=ŒNÀÄü9º5EKš^/7µÏfªÞ8ÙªuŽo“«}´ ÷hîú�¢lqs[�{¼Eùn„Ùâ>!÷ £Œ1ËØ¶OÈ÷tG m"» PÐnŸ(h·Ï'´ˆH@ƒ€í b]`–�:ur©áàÝ”)SŠÔ ¡;„°+V¬Hú Onú HMÕkjüåJÀ™îšjô·~!bøO?ý$'N,Ò}DHß²e‹sÀSMC† ñõ+DÎ^x¡ Òöž={¼bˆ {p›’Ÿ_!˜¾øâ‹“"’'¶›.|œ¾3É ×˜ã �šÐ7D²‡//^ì{“Úܹs“"?»uD�/Eá­¾cáp n J—¶cêySÕ�Ê~(ÀØ…(9Õ¸ÅßUôìÙS r÷»Ù.ÊXEôt̃ܛ8¢Ôá–�ˆ”ÔÛaýuÜåêyhÒÿ.[ÎnÕª•÷œÃ÷‡>ªV­š‰ËX–H à|¶8™]ŒLÀÆùsäΰ øÌx£�Þ°ˆ:ÿЫ=ÿsqŸp@þ;‘=Ð"�í}B¾§k¹…™H€PÐnßp  Ý>ŸÐ"  \Ó€Ä,±à‚K4ŒäÌM½†>L”öàÚÍ䨱c‡¬\¹Ru¢� &¤½úÂ5DþDK7q±×Œo²Q«Iÿ«‘ü�=Í}\X̽h�>çû÷ï—víÚy"¼t½éÑ£‡à "ûB ½aÆ"Ù§N�*ƒ rþލ¿×_}áº_P¯[·N®¹æš$é’%KŠD:6U¯¾'õs 8.\(àæýoÑ¢E’@\夶»`ÁAäïx Ðxˆ^!âÅí`~ ·ÒÀÿ®@=]…Æ “Ñ£G'E™† ÝϯéÄä¹´ÇÉ œ jïØ±c(q2X]wÝu¾˜£Œ·¢°¼ßxã Çv7Ýzë­òÌ3ÏÞð¦“Ï#DÉÇaŒéÓ§~pØsš6mšö *Rû—j|6š&¢óáü/j¼qÀ€)o� 㧨ã.WÏC03å×ï¾û®´nÝÚsOÜ¢ú�åH€ì'Àù¬ý>¢…¹#`ãü9w4Ør!à~W4/’[07î3b»äÃ>!ßÓí3´†ò�íöy‰‚vû|B‹H€4pAL³ÄB€ .Ñ0’[07ˆ>Z¶lé‰bQÚ^˜ƒ+ÉrUÑ"y¦Š^AÉÕW_íY‰>"Ú"»2åSþÇÁ‡¾}û:BK7-_¾Ü‰ËTظ°XØþ-´ÞÙ:ÿ8zô¨@ˆ~Ï=÷ø"G4o|vÅWxâUD ÿüórß}÷ùFžÆï:·áŽˆÆx¯q£´#j5neñ‹tlªÞ¸Ç~‹®½öZ¯ZDG†ÝM«W¯v„³nš={vÚƒ~ÈA;„“.§^½zɼyó’8¥z�~óÍ7åᇖµk×úv‡#q ¡B… iQàŠvøQøý"ö�;Ö3_‰ö"ÊÿäÉ“}…Ðè¢ü« ‡Ô1ãÅd2Í ¶ƒÚÁwîí·ßöíú9räH'*ûgœ‘²ËQÆObeax8p@š5k–4ïÀ¡¼Ë¥0í˜|ÁÎõë×ˬY³ߵĄ"pû˜T®\Ù9|[¿~}/K*¡ºúO7¾ƒ8}޹|ž/½ôRPv'‚>\vÙeZß]?Ew¹z&‚ŠÛÿ¨óüN›6ÍkŠó�ÀáÉ $@ÿG€óYHMÀÖù3}FQ p¿+9r æÆ}Â`FÌa�|Ø'ä{º]c†Ö�@>  Ý>/QÐnŸOh �€.ˆi@Êbˆ"Ž;f¤ED‘<õÔS�Ô­Si!.¸dÃ_…ÈMg¼„ͳtéRéÚµ«WÌOÔ¶N“ùÕQ6þ=þ|iÛ¶­”-[Öùùf̘!£F�JʋȊA1å'SþGT_D‹uSçÎ�B<ÿ™ › Û¿…Ö;Ûçx¿Û¹s§üòË/λ3þ�(Ý©�Á?ûöísÄ|xÞž|òÉR¦L™”nÛµk—üøã�Î;?òW©REjÕªá9h˜ª7¨]›?z�†ß¾ûî;ϰC˜öwÓ3‡rÊ–.]ÚyÏ«Q£Fd<ˆFýý÷ß;åQ_ÅŠ¥R¥J‘ë S0[Ü\›à¼>|Øùá=ß‹Lø…é/ò†áýÔSO%bs;T˜v`—éçžs·àŽqvÊ)§$¡Sí8¨3sæLß6a™Ç‘cccèøñãÎ÷}ÂÿC�_­ZµHk aýG_rQGœþÇíõêÕóºÁ[¢ráQ¶IùK€óÙüõ-7OÀöù³yvµ��ý »z¿5AóÍø[4_c6ÆE!r3áîš Ê:Mȇ}B¾§›ò>ë%�Â%@A»}¾¥ Ý>ŸÐ"  \Ó€”Å,Ø F¤4©}ûö²fÍ)Y²¤‰êë,Ä—løkÉ’%Ò½{w�ïÓO?íDÍcJ&8-Z´ÈûÿÝ­[7kQ©§ï EtD1>øàƒ"öC …ôh0å/�¸ý¯F ™O?ý4)ªfþÒ¢åA¸°DˆŸÛD€ó›¼Qضâü##·ô”÷îÝ+�7ön[(äwó�7:‡mÝ„  ��aÈ6, ÆÿÇ—ñãÇ{VãÆ(¿›&,èM °�ç³:…&YC€ógk\á’�ý \íße‹t!Î7³1.¸O¨7B¹O¨Ç‰¹ì!`û>!ßÓí+´„ò…íöyŠ‚vû|B‹H€4pALR³àjûÄ+šãl׃ãtz‰%â¬V»®B\¨Ê†¿/^LA»æ(SÅ%åË——÷ßßjAïܹs¥OŸ>š=AÄíßÿþ÷Y�X©m3†&—ÿ±P‹q4oÞ<φ HÏž=CÛÄùI€ ‹ùé·âj5çÅÕóÙïw!Î?²A‘Ü‚)Oš4Iîºë./c˜(íÁµ›É±cÇY¹r¥ ê:æ˜&LH;ñðpµßd£V“þW#ù3:{6<Ê6H °p>[Xþdoâ%Àùs¼<3­-ûA¹Ú¿Ë”�nùBœofc\pŸPw„‰pŸPŸsÚAÀæ}B¾§Û1Fh ä Úíóíöù„‘ hà‚˜¤,f=z´Lœ8ш·iÓ¦‚ˆj¹Z›?¾ôîÝÛ£9uêTÁBO>§lø ¢ÔBãfÒçØ¨oÔ¨‘×DíŸ|ò‰Ô¬YÓd³Õ½{÷nyñÅB˜={öøÖ!û< ^xaFm±°}âðÿ�!CdÊ”)^çòATeŸ'òÛ".,æ·ÿŠ›õœ7�箿…¸Qž šäLY½sÜ ”°`\I–s¨Q¿j×®-›7o–ªU«úZ²bÅ ¹úê«“æUÛ·o—:uêdÙr6SþÇÁ‡¾}û "²»iùòå‚€ L$@$ K€óY]RÌW pþl—׳±”«ý»l‘æ>a8Òî¾.÷ Ãqã>a8^Ì�{¶îò==÷cƒ�@¾  Ý>�QÐnŸOh �€.ˆi@b–X>|X0!Â܉'W³—+W.–º ¹r ï]D`8xð S“íZµjI©R¥ÂW”ƒûöídðÝpû‘IÅŠs` ›Ì6�¨þßµk—=zÔóuëÖͶél/Ǹ°˜c°ùP8ÿ…Ëxæ#GŽÈ±cÇŒ´ƒ÷¯SO=ÕHÝ:•¢0;þ*Dn:ã%lÜ~ÖµkW¯X¯^½œÛrl üðÃÎ<1áß”´mÛVÊ–-ë|„|3fÌ�Q£F%åÅÊÉ“'‡ÅÄü–0åÿuëÖÉE]äõ±!hÏ—ù·%î¡$Pì p>[쇤!Àù3‡G¡à~W4�’[xnÜ' ÏŒ%ì `Ó>!ßÓí´‚ò‰íöy‹‚vû|B‹H€4pAL³� � � �€Å¸°h±shZœØ5(ú÷ï/³fÍ2bTûöíeÍš5R²dI#õUZˆÂìløkÉ’%Ò½{wïÓO?- Â]ì>GdjpZ´h‘×wüw·nݬe¡FéN4´I“&Žùƒ>(b?„ïˆ@_­Z5kûF Äíõ¦Xðé§Ÿ¹q0¸wÌA$�Ï8ŸÍgïÑvÓ86M˜õ“ � � ¤"À÷tŽ  �°(hKÌ|~ ÚÍ3f $@pAÌTVI$@$@$@Y$À…Å,ÂfSàü#c„±V0hÐ ™6mZ¬uº•uéÒEÅ:W« QО -^¼˜‚vÍo"¾5nÜXöìÙã”(_¾¼¼ÿþûV zçÎ�+}úôÑì¡"nÿþ÷¿—5jh—aF{ ÄåèÀ8­nZ°`�ôìÙÓÞÎÓ2 k p>k­kh˜8¶À 4�H€H€Š)¾§SdzÛ$� Ú3€g¨(í†À²Z ³¸ f–/k'   Ó¸°hš0ë�“çqÒ̼®Ñ£GËĉ�p›6m*Ï>ûlÎíóçÏ—Þ½{{�¦N�*„çsʆ¿ J-4n&}¾cÇiÔ¨‘×DíŸ|ò‰Ô¬YÓd³Õ½{÷nyñÅeܸqž_­Böx@.¼ðÂŒÚbaûÄáÿ!C†È”)S¼Îa,�1¾ÎÒ" �¼ Àùl^¸‰FæˆçÏ9ÏfI€H€H€„ïé$@a PЖ˜ùü´›gÌH€ à‚˜¨¬’H€H€H€²H€ ‹Y„ͦ2&ÀùGÆY�&�Ç „›ˆâÄ ©^½º”+WN³tñÍFná}�Hít â7¹V­ZRªT©ðå Ä¾}ûdÿþýÎwÃíCÕªU¥bÅŠ9°†Mf›@TÿïÚµKŽ=ê�ùºuëfÛt¶G$P@8Ÿ- g²+±àü9v¤¬�H€H€H@“ßÓ5A1 �€G€‚vûíöù„‘ hà‚˜$f!   ‹ paÑbçд"8ÿà      p p>˱@© pþÌÑA$@$@$�+|OÏy¶KùK€‚vû|GA»}>¡E$@¸ ¦)ÇY-íµ×^“Ò¥K;–œtÒIN´´o¿ýÖùïãÇ�K§N�¤J•*9¶”Í“ � � ä‚sA�mF%ÀùGTr,G$@$@$@$@…G€óÙÂó){Οãciª&îß™"ËzI€H€rM€ïé¹öÛ'�ü#@A»}>£ Ý>ŸÐ"  \Ó€”Ã,Ÿ}ö™´k×NöìÙãYñòË/Ë¡C‡ä†nðþÖ¿™9s¦”(Q"‡Ö²i   �\àÂb.¨³Í¨8ÿˆJŽåH€H€H€H€H ðp>[x>e�â#Àùs|,MÔÄý;TY' � �€-øžn‹'h ä Úíóíöù„‘ hà‚˜¤eùàƒ¤E‹I­oÚ´I.¸àùÇ?þ!Ý»w—E‹yŸoܸQÚ´i“#kÙ, � � �@®pa1WäÙnœD¡Æ2$@$@$@$@$P˜8Ÿ-L¿²Wñàü9Ž&jáþ� ª¬“H€HÀ&|O·É´…òƒíöù‰‚vû|B‹H€4pALR²lÞ¼Yš7ožÔò[o½%íÛ·÷þ¶eËiÖ¬™÷ï«®ºJ–-[&%K–Ì�Ål’H€H€H W¸°˜+òl7 Î?¢Pc    (LœÏ¦_Ù«xpþǸkáþ]ÜDY � �€�øžn£Wh ØM€‚vûüCA»}>¡E$@¸ ¦)ËY>ÿüs©_¿~R«Ë—/—.]º$ý QÚ»víêˆØÝ´uëViÒ¤I–-fs$@$@$@¹$À…Å\ÒgÛa pþ‘žØáÇeÅŠÎ!ÕãÇ�KõêÕåâ‹/‹¹H~Sõfl+ Ð&°fÍùþûï夓N|§¯¼òJ©R¥Švyf,>¾üòKyç�wœ±rìØ1éСƒœqÆyàå—_vìGÂobçÎ�¥B… y×L$�žç³!$�šçÏö�îßÙçZD$@$`†ßÓÍpe­$PÈ(h·Ï»´ÛçZD$ A€ b�²˜eÿþýÒ²eKÙ¹s§×ê“O>)C‡õµbõêÕÒ©S'ï³ûî»O{ì±,Z̦H€H€H€rM€ ‹¹öÛC€ó�ô´þò—¿HÆ ½L¸¡ "ÖLoa2Uoß3/ �@t .”=z$=^{í5)S¦LôJYR‹Öi¾úê+ùöÛoqõ©§žêü?Þ¿ªV­*µk×vÄâîû˜V¥†3ÍŸ?_z÷îíµ²qãFiÓ¦�áVã¯þÅ_”ž={z·mÛV°Æq?kÖH¹$Àùl.é³mÛ pþl—‡¸g—?h � �€Y|O7Ë—µ“@!  Ý>¯RÐnŸOh �€ b»wï–Ù³gKéÒ¥å´ÓN“>}úXµ±§�%'Yü"®÷ë×OfΜ™RÀràÀiÖ¬™'€ÇFê¶mÛ¤råÊ9é%  È>.,fŸ9[ŒNÀÄü#º5ö•T£½AÈab‰%22ÖT½ÅÂ$@Z6oÞ,Í›7÷òžuÖY‚ÛÙÊ•+§Už™Â8r䈼þúë2aÂÙ°aC`åË——‘#G:·è�}öÙ�ùMgøãÿ(Ý»w÷šùóŸÿìOÈÇ4}út8p gú°aÃdüøñ\gÌGgÒfHA€óY HMÀÄü™ûwÑF÷ï¢qc)  �ü%À÷ôüõ-'�\  =WäS·KA»}>¡E$@L,ˆ%Fÿ»êª«dÙ²eGôë ®Ø†°Ã¦ÈLG�,l�8qBN9å ü+Ë /¼ 7Ýt“÷]qúðáÃ�Í<7½õÖ[‚HŽL$@$@$@Ń‹‡Ÿ ¥—&æ…ÂýP…ç]ºt‘¥K—Æ.h�«ÞBbŸØÌéÞyçùå—_¤J•*IQó ¡Ï…Þ?Û|” ïC‡I«V­äã�?öºµ}ûviÔ¨Q‘nfÒŽmÌriÏ’%K¤[·n‘MøíoëÜœWªT)ß:²á§B‰Ð€X_»æškdÅŠÏW^yE.¿üòÈ>bA »p>k—?h�]LÌŸ¹Çý;»F9­! °•ßÓmõ í"{ PÐnŸo(h·Ï'´ˆH@ƒ€‰±DF&B DÁZ¹r¥”-[V Ǧ`ÅŠWßyç�²gϧ‡½zõ’yóæy"|þé§Ÿ:ÑšPî7¿ù�ÔªU+-�o¾ùF&Mš$•*UrÊüú׿–Ž;&•Qí:t¨�~ïÞ½²hÑ"džÄÈ]n„®¾}ûJõêÕÓ¶�¨5kÖLÊóÆoÈ%—\èE0ÂÁ7M�:U XŽH€H€H€ ƒ Ã�Å¥&æ…ÄŽ‚v;¼‰ùa�:ucpXxÍš5Fi窷…Þ¿\qMÕn&¼!Œ1b„WõOë{)~‚}_}õ•óÜÂÁœû·¬Qåkúâ‹/¤^½zžùº òµ¿´›ŠÎg‹›ÇÙß0LÌŸ¹÷Opÿ.ÌHd^  �âH€ïéÅÑëì3 dF€‚öÌø™(MA» ª¬“HÀ8›Ć "S¦Lq@¾eË;v¬L›6-‰ 6gΜél"Ò®U†ÀÜM}ô‘4nÜ8-Ë… J�=¼<~›Äª=ˆd±qãF¹úê«ý´~ýzi×®]Ê|j”uU¤Ÿ®Uô¦l áÌ@$@$@$`=.,Zï"˜@ÀÄü£�SÐn‡7qPÙ�¿õìÙSíóÍBI…Þ?Ûü•·*â=묳œu‘TÂä¨íØÆ+Wö|øá‡Ò¤I“"ÍOž}ú8QØ‘š7oîØ÷àƒjyvàZð *ÉŸxÅ£û!þÖ A­ºÕ>c“{ëÖ­R®\9­òÌD$@$@$�߸°˜ßþ+nÖÇ=ÿÀ{úsÏ='ÿûßåàÁƒrË-·ÞÎd3s ÚíðÎôéÓeàÀ�Ž1².Y²¤ "´zÿìEÿ²" o<Ûœt ÷»uë–²{QÚ±�U.í�(=1€Öqþüç?Ëé§Ÿh"¢ã ÖŸÜ„µXÀ­ƒ‰‰~ Ä雌[¶l);wît>Ç:xÃOL$@ùM€óÙüö­7K îù3¬�CÐÎý;îß™ù¬�H€HÀ|O·Á ´�ò‹íöù‹‚vû|B‹H€4غ ¦Š´»‚híS§NØŽèd—^z©wõ°Ÿ$H|pâÄ gSØÏcS âs÷Šy´}øðaGP�ͲF�%mR"?"E]|ñÅŽ`ý믿v"É'F‰G/¼ð‚Üxã�I^ñÛ¤6l˜L˜0AÃ{ÿÌñNëÖ­eÛ¶mοÁ‚øÊ•+k×ÁŒ$@$@$@ùK€ ‹ùë»âhyÜó¼§ÿû¿ÿ»'p{÷Ýw�÷ö¸êÇ;»›Ê”)“±°ïïˆì[²dIA}‰)A»©zãbéWÏÏ?ÿ,øædàqòÉ'›lN«nõÖ/Ü€õüóÏ‹û¬ÕªDÉtôèQo©>�R_&eLô/{Ò•�››Éñ†g¢ø«þ�Ê[=øžJíò‹ÚŽÏ=Sc$®zñüìСƒ#`wÓ¦M›ä‚ .Ðn¿?X›ILêoRœ~B;©Æ�¶Ñi2ºßü^áùwÊ)§ÄQmFu̘1Cn¿ýv¯Ž‡z()8EF•³0 �@Îp>›3ôl8Ä=F°W¤% IDAT—ã´sÿŽûwyðõ¡‰$@$@à{z†YœŠ! Úís:íöù„‘ h°uA,ñjm´÷ìÙãô¦_¿~òôÓOÙ$v»ºjÕ*¹òÊ+½ž#¢ûˆ#Ò’8pà€4kÖÌÁømùå—‚¿«©G�2sæL©X±bÒGªHÂn7ÊŸ›â÷„uçÏè»_[é:‘IŒ‚v��Ï,$@$@$P@¸°X@Î,]‰{þ¡î„\3Iï¿ÿ¾sØõ™gžñæ!‰õ :Ô¹½©iÓ¦ÚÍà½ÿ�ü£<öØcIuâp,Þåo¾ùfçàlXA»©zµ;!ãgŸ}&+V¬�Õ«W;ÿŸ˜À£oß¾Ò¹sgùõ¯]$²qb^Eá'D?>räˆÜzë­ò«_ý*­E{÷î•)S¦HéÒ¥�|˜wA°ŽäÖ‡ÿVoÃ�^+«eðï>øÀ9Ì ;0·„õë×D†}¸A`Æ ž]n{öìéˆeS åmé_k1ÅMm<®ñöá‡ÊÿüÏÿñók¯½ælݨÑO¸Ùí½÷Þó͇O®ýC† qƪ›Ðö 7ÜP„mÔq«Vd⹇ïÛòåËeñâÅIßuÜx÷ŸÿùŸN`�jÕª9‡Zž|òIÁ$DÜÆ3Výn$ŽäívÝ»w×oA™ƒ¸6¤ºe/U]XBÀ…}ûöÉUW]%çœsŽ\}õÕR¥J•ÈÏ—°ãÎ}NmÞ¼ÙùÍqŸ�x®Ö«WÏ3=U½ðÆ4nB|饗’º ¿a} ·âRPŠÛÿhÏUpu×±þ…@5kÖ 2‡Ÿ“ XL€óY‹�CÓrN îù3:‡ �ûwE÷ Ó îßåü«DH€H€"à{zh,BÅœíö Úíó -"Ð `zA ›ôóçÏ÷6ÿ5Lr²@ÜЩS§¤ì�£®R¥J¥¬fòäÉ�‰›°yŠ+âÓ%5ò™»ínFµ›‡‰›«~‚öÙ³g;‚ 7õïßßÙ8 ý/q³›‚vÝQÆ|$@$@$P¸°X~,.½ˆ{þ§ }ÇŽrýõ×'ÝÆ”Î/x×ÇMMgœqFZ÷áÆ(7ƒÒèÑ£ÁjÆ ½¬˜Ë,]ºÔw>eªÞ ;£~Á(Ä�O=õ”V~ãÀrÛ¶m}ó㬛nºÉûLç0ƒ:÷kß¾½¬Y³Ægªõ¥2BUÕ]A§ZBQì[µjØOÌqçÌ™ã{`Û–þv"bSÜ\sL�7Ü�_'ŽA´í~g!@W?óC¥Ž'äùæ›o’nŒƒÀ·Åá;¡¦¨ãÖ­ÇÔsïå—_\‹� øÃäºë®s¡�H~Lðwµ¯‰ß߈ÃÐ+×ÍwîíqùIísиs×±‚žêçÀC~á…z‡3R1ÅA uëÖ9R%þwÛÂÁ°ÄÀ~km™Ž–'È.Îg³Ë›­å�¸çÏè}¢ �ûwÿ:Éý»üúnÐZ  óøžnž1[ �B#@A»}¥ Ý>ŸÐ"  QÄéÇô­‘î ¸âŠ+œÏ!>À=®VV^„ëÖ­[D¤>}úô¤hæiôÑGNô®T õC|(Ó¸qã´$ FÀª›¦N�êDILL~öE�R7Õ17z×Úµk½¦^yå¹üòË5<—œ…‚öÐÈX€H€H€ † ƕŢ#QæéÀÄ%hW#£ë:#è0©ßAÝtu#J»+êD¾T‚vSõêö;l>D.ïØ±£@t6!º3"«I=@¬#hO‡§{÷îhž*¢õ;ÈŒˆîná  !ÞÅ¡õà¶-ý ²?ê禸ÁÓã ÂrÜÔ6räÈ"â_|gqØ‚ö(ã ö«lzè!Á�¿uÜ¢.SϽ×_].»ì2í¡±`Á™8q¢÷|HõÜS¹¤;ð£ÝøÿeT#´ãÏ@€Hñq¤¨~Jì³Î¸sÚ=?Ôz±~õ»ßýNû}öÙg "Øûœ0å×~“Þ|óÍ´Á/âð!ë 0G€óYslYsþˆ2æþ�ˆÎ-.Ü¿Ëÿï{@$@$`–ßÓÍòeí$Pˆ(h·Ï«´ÛçZD$ A Ê‚¢ˆÏš5K£v½,¸"ûüóÏ÷2û Óyä¹ÿþûÓV1K‹-<H�¸Ä­L�î„HS:tHk6¯q•|ºQB¢ðBÊïÞ½;éZdØ‹ ÁªU«ê�û¿\à…MV\kޤÛïP�03 � � �€µ¸°h­kh˜�(ó�t ã´ÿüóÏrÑEɆ ’šzòÉ'åšk®‘5j87(!j2©ªÆS‰M!$8çœsŠ›‘øê«¯–²eË:‘¼7nÜ(¸%ÊOí'Ø4U¯©ë7¿C[ÿñÿ!<ð€sÀ ýÚ´i“3ÏRY|úé§R¿~ý$ƒ›~ýI'hÇüì»ï¾“÷Þ{On»í6¯8¢CT�„CÉUªTI²Eµ#±Ý;î¸Cn¹å9óÌ3�ˆîo¿ý¶3wSûñ3¢ó'&[úgj\˜â–­ñ–Èsð{î¹G~úé'9ùä“åÁ”ï¿ÿ>Òx‚ýƒ–iÓ¦yMàѦM_WD·Ù~îáÙ‰ Ê•+'ß~û­ü÷ÿ·<ú裾}J%T_¸p¡S‡›Üîˆ;“± î¸ucÑ¢EIÕ¤:P¶­¨~J÷=ñwî;aÐó#]½ýúõ“»ï¾Û‰À~ôèQçàT@¨ž˜üÆeªß§8üï¶�ß}¬Ù¹¤ ôÇZZ�:uº…ùI€,!Àù¬%Ž Vˆ2æþ�8‡O¹gå�¦Q$@$@yD€ïéyä,šJ–  ÝG$˜AA»}>¡E$@¢,ˆ!zyâæªF3i³¨‘ôTQ ëDZÿòË/W»)Õ5Չƨ›–ØÛºu«#8p“j�îf™ÕW.Ã&7­ZµJ®¼òJïß±@”î^­ËU�$FA».9æ#  � À…ÅÂðcqéE”ùG:6qÚÕh¯hÂãÖ­[û6ýüóÏËÍ7ßì}–êýÂuU¤üþûïKóæÍ‹Ô  DõÛ¶mKúÌOØiª^ScpóæÍEúìw�»Ûþ?üà°HŒT?lØ0™0aB’‰A‚M¿þ¤´»ùÿ÷ÿ7i>ˆy|î>kÕzS C1øâ‹/.b†_ÿp³nîJŒvlKÿL� Sܲ5Þ\.úâ`F*QuØñä7¿º­¶„mÇÔsO�t ÛÖ¯_/íÚµ+2”pˆÈïï©íTãà@Íš5å¯ý«s3à¨Q£R~7ÃŽ]|ý¾³Àpß}÷9"êt·ê´ÖO©¾'Aã.èù‘ªÞTþãÇ�;70&Þ²áw˜Ë¤ÿù>\Æ�ïýé�7Þ�K.¹DÇÌC$`!Îg-t M²†@”ù3÷ïô»qÿΚaNCH€H€,%À÷tKC³HÀb´Ûç Úíó -"Ð eAlÈ�!2eÊ�Úõ²lÙ²EÎ;ïûì³2`À=h ¹Tí¡² � � ä5.,æµûŠ�ñQæé AdײeKO®Î-tÏŸ?_z÷îíe…˜ïñ©Ì~Â>ŠmÒ¤‰W‡_´ßçž{N-/UúꫯäW¿úUÒǪ°ÓT½:œ¢æQ…‡ˆZ>yòä´"Tˆ&1Os“ß'H°ég¯Ž ]'ObÝ~ÂPˆ_qXªä'&Æß4hà±¥QýTηl�7ôßgDLw(=ìxRóë¬?À–°í˜zî©ë AÑ1U!ú’JÐ4¦2ýÜ/:¾Z'žó8ä‚çSÆ åôÓOÕlX?ù}OtÆ]Ðóï^õVAµcêsY "á·fÊÿªýcÇŽ•‘#G†ò3“ ØC€óY{|AKì#eþÌý;îßÙ7’i � �@>à{z>z�6“@n PО[þ~­SÐnŸOh �€�( b¿üò‹@Dí—• ‘ôZ´há|ܹsgÁµÐ©^„O9唤�Uaz�À-   » paÑnÿкdaç?ÿü³@è ¡œšÍQz/»ì2Ù³g�óñ‚ ¤M›6ѹ_:í´Ó¤råÊI9rÄ)ÿ·¿ýM~üñG'o£F�ÒºN ¨·N©‡b1À;z‚^‘úÑǾ}ûÊœ9s¼ÏTa§©zM�SD#?÷Üs=ÿ �÷Þ{Ï›/¦j×ïЀ:¿ lúÕ­3ÿÓÉ“X·Ÿ0ô‹/¾Hº=Lµåĉr饗:QÙݤF­·¥¦Æ† nÙoàòÉ'ŸÈÙgŸ�Q¦ãIç > ÛŽ‰çžzkžÎív�fÍšÉÎ�;S>÷L�A¿zñ7nœu_'¡�tnãkÚ´iRp„¨Ï  ç‹Î¸ z~¨ŸëFPÇXÏž=#ÜÙô¿z(Hç†F2 �@np>›îl5?„�?£WÜ¿ >èÆý»üÿ´’H€H ·øžž[þl�ò‘íöy�‚vû|B‹H€4DY ª6q“KÝà *‹ÏgÍš•¹PG˜ŽMGD7GY7éDg\µj•\yå•^™'žxBî¾ûî$3£Ø´™ A;Dÿ¹#él¦b´©¨ÃœyH€H€H€ò—ó×wÅÑò°ó¿HÖ™pû¯ÿú/ùío›INYDß1b„W�*hWßÑÛ¶m+kÖ¬‘Ò¥K§m{åÊ•Ž0ÒMª ÝT½IQ�ê?ÝÛ·üÛ*øV…¡ºµ'Mš$wÝu—GN�d$HõC4E�<¦ÆCb½&¸es¼AȾ}ûvÁÁšt),o•‹:îu×LD9ûÜÓý.L˜0AÈ Õs/ãQmcÛ¶m2qâĤF:và"¨×©SÇ7{¦ãAwÜ=?ÔÏa3n Lu3 :³{÷nçvE÷YÐï“Iÿã ¯¹kjQÖuüÉ<$@Ù!Àùlv8³•ü$vþ¬ÓKîßω¸§3’˜‡H€H Ð ð=½Ð=Ìþ‘@ü(h�Ÿi¦5RО)A–'È Ó ba7Q£ Ó±ÀÔºukÁ¦#’®@\Ý8}å•WäòË/÷|Õž ¡üáÇåüóÏ÷6ßt.~ƒD� –yN%  ˆ�cCÉŠ²@ ìüCÿej¢®8b9D{Þ·oŸà*$D GÄxüûµ×^ݤ Ú7oÞ,Í›7÷>×}GGdïzõê¥,gªÞL¹¦*¯ ŒÃˆUÁåÔ©SeРA^SA‚M?›tĤ:yëVí5j”Œ3&iP9[úØ‘ˆ‚úŸªÚtå²9ÞtnnCÂŽ§¥K—J×®]½îoÚ´I.¸à‚@ÊaÛñ«0îçÞwÜ!“'ON+’†6¯iàw`ãÆ�‚@ óæÍ ôƒ›Á†êE/wÿÖOêx×wAÏõsˆÙ$"]R×ÝÔß5õ÷ɤÿU[p+"ÖÕX´ÆŒ$@9%ÀùlNñ³qË „�?ët'ñ}DwžêÖu¿Œûwy® Ë\ǧÌC$@$@&ð=ÝUÖI…M€‚vûüKA»}>¡E$@l[‹º°¥^m¬ ×¼wèÐA6lØà‘Âxƒ ¼GµG�¶§ å㌪>dÈ™2eŠgó‚ B&   �âA€ ‹ÅÃÏ…Ò˰ó�¸íª(:‘+Äêo½õ–à]þ¥—^ …\´¿ð rÓM7yuÜ{ï½2~üøÀ:ƒÄަê 4,bU4Ù¯_?' p‰%kô‹V¿dÉï€A�`Ó¯� ¾(£“'±nÕ5ÒzªŽ®^½Z:uêä}ŒÈüË–-³®�ŽŠ˜Á·lŽ7]?‡OAQÐSáÛŽ[�É瞎Hvä‰8Äb/vôèQùöÛoå£�>’?ýéONôv7Z¹_cxî߉QÏÃú)ê÷$èùå&€ A»úûdÒÿQ×êb¬�H œÏÆ‚‘•(�°óg ™Ú£þsÿŽûw:c“yH€H€ì"À÷t»üAkH PÐnŸ—(h·Ï'´ˆH@ƒ€m bß|óMÒÕÌ:ÂttóÃ?”&Mšx=։ЧFoCD'\W\¾|y¯ž(ö JEß¾}“®¦ÆUä�5òê�KÐŽHï¸fÙ�L�TQ¾Æ0`   �<&À…Å¡E$@l[ÆjëÖ­=Ëï»ï>'¢UPR#AEKƒ fðàÁIÅØHLŒø‡6U{t„òÜœþùŽ8 ByÚ+T¨àuÃoS[m;¨Ïø\]<묳œhaeË–Õ)Î<$@$@$@@€ ‹àÄbÔ…¸çª¨MWü™ˆ\}§Nü¬G�rî¹çJ�5œ(»¥K—–2eÊÈܹseÕªU^Ö Aû#�<"÷ß §ƒÄ¯ª`0®z ‹˜!�±*r¼ãŽ;dòäÉ^´ã Á¦ŸÉA|QF'ObÝq‰8û÷ïïD¯wŸé¶ô/¢ë‹™à–Íñ¦+Ö;žÔØtŸiaÛÉÆsO—ÑæÍ›¥yóæÞ˜ y®dÀÜôéÓ'©uÄyþùç½ïuX?Eýž=?¢ÔkJÐÅÿýë_¥R¥JëêÕ«;ÂøÊ•+çÀûl’H SœÏfJ�å ™@Üógu¾ö½‹ûw%C 7îß…ÂÅÌ$@$@–à{ºe¡9$�(h·ÏI´ÛçZD$ AÀ¶1lôÝ|óÍžåª#RbP »q ñÉ•W^™Tí¸qãdĈI‹b�z}¢zu¼ºhˆënôª&L˜ ý»é¡‡’Ñ£Gáâç$@$@$@D€ ‹äÌbЕ¸ç™ ÚɽS§N²víÚ$ú/¿ü²\rÉ%)Š.[¶L®»î:¯L� ý‰'ž�»ï¾;ÐÃAbGuÞW½�†EÌ <6l˜Œ?Þw¦«vݺurÑEyYÔyU�`Ó¯î ¾~sµ ‘Ga(ÚQEœ¶ö/¢ë‹™à–Íñ¦;‡×s.,ˆ¢  ³fÍJùlI6L;ÙzîpûòÅ_H½zõ¼®}çW3¨kH:°eËï&À0~B7¢~O‚ž�Qê +h7éFhÏá gÓ$`€ç³ ²Ê‚!÷üY�ï„}_æ÷Nƒƒ¼xïM•¸W0C˜! Èc|OÏcçÑtÈ Ús>M³´ÛçZD$ AÀÄ‚Xb$½öíÛËš5k¤dI½Èj42lü�wÞy�=Q7®!Xºt©”*UªHYØqŠš–/_.XÀKLQìY½zµ#ŠqÓØ±ceäÈ‘Iõª›˜j´ÁÀÿÿ j$x”Ñ�Þ¦S?ó� � � ä.,懟hå? Ä=ÿÈTЮ¾—ÃÆõë×K»víÒº,(ŠòÊ•+s7ùÝå×@�ØÑT½¦Æ§‘.Œ`"H¼$ØŒÂe‚| Ö«Ú¡+âüÃþ 7ÜpƒWݽ÷ÞëˆýÝdKÿL� ܲ9ÞLÚÁbvDëwÓ{ï½'-Z´tC˜qk깇5•k¯½Ö³Õ/h€_G°ŽÑªU«¤ç%ÖsJ”(ØoÛ2|óÍ7R§NÏ,ÜØ·mÛ6/rx?¡’(Âs¿rA‡®tÆs� =›þ§ Ý¶‘O{H 3œÏfÆ�¥ ›@ÜógÐâþ÷ï û[ÃÞ‘ � ÄE€ïéq‘d=$P|PÐnŸ¯)h·Ï'´ˆH@ƒ€‰±®„ˆý”SN‘Ö­[kX"rüøqiÛ¶­#ÊF•ÁÛ·o—ªU«–÷».{Ô¨Q2tèPoãp×®]2eÊyôÑG�º÷ìÙ#Mš4q6‘>úè#iܸ±×VT{¦OŸ.ôêY´h‘tëÖ-©~›˜K–,Ñþ£2u³ðì³Ïp8餓y1 � � �@áàÂbáø²8ô$îùG¦‚ö 6$‰×ûõë'ˆ—NHyâÄ çýQÚݤŠÕ÷ýtnýg×®]½?©pSõš{ª½˜³`ÞUºtéÀ&g̘!·ß~»—O\ªBÏ·ÞzKp :]Ú¸q£3çt“ŸÀ>SÁ)æ¡cÆŒ ìŸÝP�RhKÿ;1ƒÚ¿8¸es¼é€�&ÓñôÆoøÈW±‡i'›Ï=<'ƒ¨ßõ0_"¿¤bÇŽ“Ï>ûÌY‚Ÿ~úI`SAý¡C‡qþÇì´Qœ"´ûý>™ò¿úÛÎøm)[¶lC‚u� d™ç³YÎæòŠ@Üógtžûwܿ˫/�% Ⱦ§ç =&�¼%@A»}®£ Ý>ŸÐ"  &Ä4šõͲ{÷n';„æH¬]»Ö7ʺZ�_´r7ÏùçŸ/_}õ•W/þÞ¼ysg“rçÎ�N6Ü™¢råÊ^ÕQìÁá}ûö•9sæxõlݺÕÎ'&u£¯gÏž2þ|í Suóu#Jý�7Þ?Ë‘ � � ä).,æ©ãŠ©ÙqÏ?2´«Q²ƒ®>‡ÛüÓª‚öĨwî|b»jÕª¥ô¼ß\Bvšª×ÔpT££ô¡Aƒi›Ä¡�:„·nRç;ª zêÔ©2hР´õ>üðÃá´›LÚu„•~¾ì窦Ɔê¿8¸es¼™´/\¸Pzôèáa7ÑN6Ÿ{ê:‹:žüÖ6²-hß±c‡4jÔÈ3­|ùò‚uœ3Ï<3ôðW×zÔµ¦0Ðx>Eh÷û}2åÿ~øAÎ=÷\o�/ìšZhDz �€QœÏÅËÊóœ@ÜóçLpDÙ/sÛãþ÷ï2{,K$@$�|OÏ w¶JùL€‚vû¼GA»}>¡E$@lZÛ¼y³#4wÓwÜ!“'O÷e9¨;«W'_˜*?„ò/¾ø¢ùÐ��H~o¾ùf’x>Š=ꜟPvEí ꫺ɭ³ùT'?'  ÈO\XÌO¿W«ãžd*h‡Hú¦›nòÜ$ýùçŸå¢‹.JZ£°*hWß÷‘7,A¨™*ù‰qUa§©zM�G·  ³fÍòš7nœŒ1"m“:Âý°Âßýû÷Ë9眓tÐÙO8«¶$”T§èXP´x?[:QTkKÿL� ÜLŽ·¨Ââ°ãI]×Ð] ÓN6Ÿ{AæýÆA¶í~ÏÕÎ�;;ÏìR¥Ji0þî¹ç™8q¢W·s$F)ã'TuÜ©åÔߨ(õª¿·:¿O¦ü¯®×=§µ�ÈŒ$@9!ÀùlN°³Ñ×­[W.»ì2í1—.ã¤I“ä®»îJÊÒ½{wÁßÏ8ãŒÀ6Üà‰'žHº…-Z$ݺuóʇñ Ew¹´Ã^“þOtÂóÏ?/7ß|³÷§ Ãh�d �œà|6§øÙ¸åâž?gÒÝ(ûej{Ü¿ËÄ,K$@$@Ù%À÷ôìòfk$P(h·Ï‹´ÛçZD$ AÀ¦1 sµ³ìÚµËÙ8†èÿ«\¹²¯pD»B{챤ȄA‘«\zè!ùÝï~çY”J4oÀ%@ IDATdVI$@$@$`!.,Zèš”’€‰ùǦM›"hˆ–q¸ïþºé믿–„E6§ÄÓå¹õ ^2o»í6Á¼_ÒqÊï9Ñ¿ç‚*T¨àB@™¯¾úJ^}õUß['ün ã'ØuÜåJÐî÷û—ÿ]ŸùÝÄ F �:XŽH 78ŸÍ w¶šLÌŸmè9÷ïlðm   �ôøžÎB$–ía‰™ÏOA»yÆl�HÀ�B]3€*ö*ÕÈq÷ÝwŸ@äž.mذ¡H$ÃO?ýTêׯ»}¬�H€H€H ?pa1?üD+ÿIÀÆùǽ÷Þ눠ÕÔ¼ys9í´Óäõ×_Oú‚ë�JÆ “þŽü<ÞrË-Ò©S'�è·GM™2¥Hݺãv¦+V$}†¿' *ý„�¦ê59F¿üòKA¤f5Aô�Èé?ýô“,^¼8Iœîæ�;wnR$ÞÄ:† âË"à /¼P‰zÏž=^‘Q£F9Ñ�?þØù›_Š/¾øâ¤ñ‰mª‘àS ³Ý2—\r‰¡}Ä�ajBtö-[¶HÙ²e‹|fCÿL� “ÜLŒ·¨Ââ°ã ¼‡.ãÇ�÷ÐãÌå—_žÖaÛ1õ܃‘©êNÕ`ÁwÓ½A/• ]õn߃ȽD‰± SEÀM~î�™T 7BÕ¨Q#©š°~Š:îr%h7é$nfiÕª•÷Ç3—R݈‘‰Y–H ;8ŸÍg¶’Ÿlœ?ç'ÉðVsÿ.<3–  (,|O/,²7$� ´gƒr¸6(hÇ‹¹I€,!À±Ü9âÀ�NdFw³žØ„CÄ/¿¤^M�< ,lâ2‘ � � _\X,¾¾ÏÇžÛ8ÿØ¿¿shÔ9§ãÚ£G™3gŽs DÊ8pª¦©S§Ê Aƒœ?#ïõ×__D¸î×„ÍØ4¿æšk’„�K–,)�ØT½&ÇDÆ;v %E߯»îº”fýðÃŽt|7lØ0=ztRfgýø¦[ Ú1GC¤ùx 'æ€ñž~úé¾ymè_`'"fPÇÉ &Å=Þ.\(øþ»éé§Ÿv¶è¤0ã õáö6|WÜtë­·Ê3Ï<“t›�_»aÚ1ùÜC”|Ƙ>}z êÁ ¦M›¦=h‚ŠÔþ¥úþ6š&Ãßþö7çü/jÂm p¢Ñgꧨã.HÐ¥^üî´hÑ"ÉO~ÏOSþwY¾ûî»ÒºukmÜ¢ú�åH€¢à|6:;–,|6Ο Ÿú?{Èý»ââiö“H€H ¾§sl� „%@A{XbæóSÐnž1[ 0@€ b †¨r̘1ލÂM©"¯aã°C‡ž°ù!È@Ô6w2¢Yf%  ( \X, gƒ®Ø:ÿ8zô¨@ˆ~Ï=÷øzQ¶ñÙW\á½ãÀéàÁƒeÑ¢EIež{î9éß¿¿÷7ˆûžþyÁ�L‰ÑÂÝ ˆ&>aÂéÛ·¯i¸eË–^”vD/öÙg}#›ª×ä0g-Áúí·ßöm Ú½×O¨PÏ`¬áÐ@¥0í˜|îÁÎõë×ˬY³¬n¢Àa¨\¹²|þùçI·Î¥ª/_¾\®½öZ¯ªtßß NAŸCðŸƒçK/½”݉ �ƒ—]v™÷lHWH×OQÇ�Ê Ñâ!FwS”z±.•øüìÕ«—Ã'U„ü¸ýÛqC ~w§M›æõ}Řa"È_œÏæ¯ïh¹y¶ÎŸÍ÷ÜŽ¸g‡h � �@nð==7ÜÙ* ä3 Úíóíöù„‘ hà‚˜$ƒYvìØ!�5òZ¸êª«dÙ²eI±a‡�rlº ¿¸Z‘!™H€H€H€Š7.,oÿç[ïmŸ@<Œ”~ùå'º.þ�èÙU«VM‰zß¾}ŽÈïæ'Ÿ|²”)S&eÞ]»vÉ�?þ(ÇŽsòW©REjÕª•ñ!USõš_à†ä‡vø•-[ÖáQ£F�Hͺ¾;tè�ötéÒ‚hêQ냈Öüý÷ß;ö ¾Š+J¥J•’ìK%Ìv3¡Ÿß}÷�çsØ!L;—ËUÿ"9C£P¶¸%ú!Îñ¦ÑÅ"YtÆ“[è©§žJ:¤�#FŒÐj6L;¨ÐôsÏS|/ñ=Ç÷è”SNIê‡*hÇ� ™3g¦JkAˆ1žQ;ø.?~Üùî¢Oøò«U«–2{:3Âú)Æ.eµª8ý�Û/êÕ«çÙ�F[·n•råÊeµOlŒH ^œÏÆË“µÛçÏ…E»ho¸WèfÿH€H€Òà{:Ç �@X´‡%f>?íæ³ ¸ fjˆ*±¡Û½{÷¤¨Ž¸rQ‘ð9"9>þøã^­�;w–Å‹§Ê„0�YI€H€H€òœóÜ�ÅÌ|Î?Š™Ã‹Awƒ„ÙÅA¤.’[zl{÷î•Æ�{·:àpÆG}䈧 -mܸQÚ¶mëu 7. 0 ÐºÉþ¤ ÆÿÇwn*tÓœ9s|oÒ l �ü"Àùl~ù‹Öf—çÏÙå­¶Æý»Üògë$@$@¹%À÷ôÜògë$��(h·Ïk´ÛçZD$ A€ b� gÙ¼y³4oÞÜk¥}ûö‚ëéK–,)£F�’±cÇzŸ!2;6ìÒE}4l.«'  °Œ-sÍIK€ó�B#@av4�’[0·I“&É]wÝåe ¥=¸v39ÅråÊ•‚¨ë8ˆ?aÂçÆ¹TÉO$ôôÓOËÀ�ÍÈZ�0é5’?£³u%+'�¬à|6«¸ÙXžàü9÷ãþ]î}@ H€H€rC€ïé¹áÎVI Ÿ PÐnŸ÷(h·Ï'´ˆH@ƒÄ4 e!‹ejÖ¬Y²bÅŠ¤Èí=ô�<øàƒŽÐ�‰H€H€H€\\XäXÈ'œä“·h« ³u(ÍCnÁÜöïßïÜÞ¶sçN'sùòå�(í Á•d9ÇÂ… ñÝT»vm�¨jÕª¾–`Ýãꫯö>C·oß.uêÔɲål.¦ü�ƒ}ûöu<¸iùòåÒ¥K—8Ìf$@9&ÀùlŽÀæ­&Àù³îáþ�~ $@$@Ù%À÷ôìòfk$P(h·Ï‹´ÛçZD$ A€ b�²�娱còÝwßy-UªTI>,ø;RéÒ¥¥V­ZY°„M� � � ä.,æ›ÇŠ·½œØåÿ#GŽxsޏ-+Uª”œzê©qWk]}…(ÌÎÆ¸(Dn&çÒ¥K¥k×®^Õ½zõ’yóæI‰%L4—q�?üðƒT¯^=©ü{þüùÒ¶m[)[¶¬óò͘1ù•.1 2D&Ožœ±¬ 7LùݺurÑEy�êܹ³@ÐŽß& �ü'Àùlþû�=0G€ógslÃÔÌý»0´˜—H€H Pð=½P<É~�@öPО=Öº-QЮKŠùH€¬"À1«ÜAcH€H€H€H 4.,†FÆ9$ÀùGáû4Ý¿ÁíP&RûöíeÍš5ÃT! ³³1.–,Y"Ý»w÷†ÞÓO?-41óºND¦§E‹yýÀwëÖÍÚ~©Qº mÒ¤‰#BþàƒŠØá;"ÐW«VÍھѰ`qû_½©|úé§R¿~ý`c˜ƒH /p>›n¢‘9"ÀùsŽÀ³Y   á{: �@X´‡%f>?íæ³ ¸ f*«$   �,àÂba³©Œ pþ‘1ÂX+4h�L›6-Ö:Ýʺté"ˆ.mk$é¸:]ˆ‚ölŒ‹Å‹SЮ9÷îÝ+�7–={ö8%Ê—//ï¿ÿ¾Õ‚Þ¹sçJŸ>}4{(‚ˆÛ¿ÿýï¥F�Úe˜Ñ^qù:0Žp+�›,X ={ö´·ó´ŒH 4ÎgC#c�bD€óçbälv•H€H€,#À÷tËBsH PÐnŸ“(h·Ï'´ˆH@ƒÄ4 1 � � � XL€ ‹;‡¦!Àù‡]ƒbôèÑ2qâD#ÂØ¦M›Ê³Ï>[ð‚öùóçKïÞ½=ÇN�:U Ïç”�qQj¡q3éó;vH£F�¼& jÿä“O¤fÍš&›Í¨îÝ»wË‹/¾(ãÆ�óÄøj…²?ðÀrá…fÔ ÛG ÿ2D¦L™âuciĈöu–‘ dD€óÙŒð±p�àü¹ÀÌî‘ � �€Åøžn±sh XJ€‚vûCA»}>¡E$@¸ ¦‰YH€H€H€HÀb\X´Ø94­Î?8( �ÀáÇÂMD¢?qâ„T¯^]Ê•+WhÝŒ½?ä)"µ›Èl"o pþœ·®£á$@$@$�÷øžž÷.dH ë(hÏ:òÀ)hDÄ $@6à‚˜�^¡M$@$@$@$ O€ ‹ú¬˜3÷8ÿȽh � � � � ØB€óY[˱@© pþÌÑA$@$@$�+|OÏy¶KùK€‚vû|GA»}>¡E$@¸ ¦‰YH€H€H€HÀb\X´Ø94­Î?8(H€H€H€H€H€\œÏr,�@jœ?st� � � äŠßÓsEží’@þ  Ý>ßQÐnŸOh �€�„�ÜÌB$@$@$@$`+�Ï?ÿ<´i'�tRè2,@™à†|&ô²SvÿþýòÚk¯IéÒ¥�ñœ¨X±¢|ûí·Î?~\:uê$UªTÉŽAl…H€H€H€H ` P(S°®eÇb Àý» ²    �Œpß)#|,LÅŠíö¹›‚vû|B‹H€4pAL³� � � �@àÂb8‰& ív‚Ï>ûLÚµk'{öìñ }ùå—åСCrà 7xëß¿¿Ìœ9SJ”(aw‡h � � � �€Õ(h·Ú=4.Ǹ—c°y   á¾ �€. ÚuIe/íÙcÍ–H€,!pìØ1K,¡$@$@$@$@Q0B{j,“  Ú3¡g¶ì| -Z´HjdÓ¦MrÁÈ?þñéÞ½»,Z´Èû|ãÆ�Ò¦M³F±v    �‚&@A{A»—�Ë!îßå>›&  �bN€ûNÅ|°ûÅ–íö¹ž‚vû|B‹H€ à‚˜aÀ¬žH€H€H€ à¢aÀ¬¾ Úí›7o–æÍ›'÷Ö[oIûöí½¿mÙ²Eš5kæýûª«®’eË–IÉ’%íì­"   °žíÖ»ˆæ)îßå©ãh6 � � î;€Ùˆ@€‚öÐ ¡ Ý0`VO$`.ˆÙçZD$@$@$@apa1 -æ�ƒíqPŒ·\[¿~ý¤J—/_.]ºtIú¢´wíÚÕ±»iëÖ­Ò¤I“x bm$@$@$@$@ņíÅÆÕìh– pÿ.ËÀÙ � � �€G€ûN $P< PÐnŸß)h·Ï'´ˆHÀ0.ˆÌêI€H€H€HÀ0.,Ìê‹  Ý®A±ÿ~iÙ²¥ìܹÓ3ìÉ'Ÿ”¡C‡úºzõjéÔ©“÷Ù}÷Ý'�=ö˜]�¢5$@$@$@$@yC€‚ö¼q Í3Ü¿Ë3‡Ñ\  ( Üw* g²+$‚í!`e)+íYÍfH€ì!À1{|AKH€H€H€H .,F¡Æ2™0!hß½{·Ìž=[J—.-§�všôéÓG\aL&¶zY¿ˆëýúõ“™3gJÉ’%}»àÀiÖ¬™'€¯]»¶lÛ¶M*W®\è¸Ø?    (h7•U’Àÿ'Àý;   �\à¾S®È³]È- ÚsË߯u Úíó -"0L€ b†³z   0L€ ‹†³ú"LÚÿò—¿HÆ �¶®ºê*Y¶lYJAv&.9|ø°”(QBÊ”)“I5±–=zô¨@˜~âÄ 9å”SBÕý /ÈM7Ýä•ѧ>\Æ�ï•{ë­·¤}ûö¡Úff     Ú9HÀ îß™áÊZI€H€H€‚ pß)˜s�@!  Ý>¯RÐnŸOh �€a\3 ˜Õ“ � � �€a\X4 ˜Õ!`BÐþùçŸKýúõ�¶ºté"K—.u„çaÓ† dåÊ•R¶lY�Pü·¿ý­T¬XQ^|ñE¹óÎ;eÏž=N•½zõ’yóæymàóO?ýÔã Üo~ó©U«VÚæ¿ùæ™4i’TªTÉ)óë_ÿZ:vì˜TFµgèСNú½{÷Ê¢E‹�ÇMåË——‘#GJß¾}¥zõêiÛGTûš5k&åyã�7ä’K. ÄF88঩S§Ê AƒË1 � � � � ¨(hç˜ 3¸g†+k%  &À}§`FÌA…H€‚vû¼JA»}>¡E$@† pAÌ0`VO$@$@$@† paÑ0`V_„€Í‚ö!C†È”)S›!ß²e‹Œ;V¦M›–Ô�þýûËÌ™3A;¢£wïÞݘ»é£�>’Æ�§õþÂ… ¥G�^ž'žxBî¾ûî¤2ª=ˆD¿qãF¹úê«GÖúõë¥]»v)ó©QÖU‘~º _˜²�†3 � � � �@±"@A{±r7;›EÜ¿Ë"l6E$@$@$�D€ûN$P< PÐnŸß)h·Ï'´ˆHÀ0.ˆÌêI€H€H€HÀ0.,Ìê‹°UÐaz×®]eÙ²eŽÍ�:u’óÏ?_Æ�W¤Ï>û¬ 0Àùû¡C‡¤U«VòñÇ;ÿ®]»¶ó߈–ž.�3FF�íe�P½M›6Þ¿aOŸ>}œ(ìHÍ›7wì{ðÁµFìØ¾}»T¨P¡H~ã6l˜ôwü­AƒZu«}>묳dëÖ­R®\9­òÌD$@$@$@$@. Ú9HÀ îß™áÊZI€H€H€‚ pß)˜s�@!  Ý>¯RÐnŸOh �€a\3 ˜Õ“ � � �€a\X4 ˜Õ!`« ]i'ŽhíS§NØŽ¨í—^z©Ô«WÏÉ¢F+ïÒ¥‹,Y²DJ–,™Òû'Nœ�nݺyâyˆß!>¯S§ŽWæðáÃŽ âøF�y‚yd@þI“&ÉÅ_ìÖ¿þúk'’|b”xä{á…äÆoL²BùÁƒ'E�6l˜L˜0A{´þýï—Ö­[˶mÛœ2àA|åÊ•µë`F     Ú9HÀ îß™áÊZI€H€H€‚ pß)˜s�@!  Ý>¯RÐnŸOh �€a\3 ˜Õ“ � � �€a\X4 ˜Õ!`« ý‹/¾ðDêhïٳDZ½_¿~òôÓOK™2e|½¹jÕ*¹òÊ+½ÏÑ}Ĉi=àÀiÖ¬™ìܹÓɇç}ô‘”-[Ö+÷å—_:WS�=dæÌ™R±bŤ�T‘<>„ÝLÊñ{¢¢óúî×VºN 4ÈÅSÐÎ/: � � � �@T´G%Çr$�ž÷ï8BH€H€H€rE€ûN¹"ÏvI ·(hÏ-¿Ö)h·Ï'´ˆHÀ0.ˆÌêI€H€H€HÀ0.,Ìê‹0-hïÙ³§ÌŸ?_J”(ŠþêÕ«¥S§NIe G]¥J•JY×äÉ“eèСÞçË—/DiO—ͼaÆ^–^½zɼyó’lŽbÏÿøGéÞ½»W¯Ÿ }öìÙÒ·o_/OÿþýåÙgŸõ"cêB2dˆL™2ÅÉNA».5æ#   P PÐÎ1AfpÿÎ WÖJ$@$@$L€ûNÁŒ˜ƒ ‘íöy•‚vû|B‹H€ à‚˜aÀ¬žH€H€H€ à¢aÀ¬¾�(‚öýû÷Ë�?þèK³téÒ�øW\á|Þ¶m[™3gŽóßÿø‡³T“” ˜©[·n‘úôéÓ“¢™C¤�¨éÕªUKéEÔq¸Û2¢LãÆ�Óz~Ù²erÝu×yy¦N�*ˆxž˜üìÙ²e‹Ô¬Y3eÝ/¼ð‚ÜtÓMÞ窠QÜ/½ôRY»v­—ç•W^‘Ë/¿<ôH¥ =42    ð!@A;‡ ˜!Àý;3\Y+ � � �@0î;3b(D´ÛçU Úíó -"      °ˆ@A;¢ˆÏš5+¶^¼ûî»rþùç{õù Óyä¹ÿþûÓ¶ù÷¿ÿ]Z´h!ü±“O7Rùc�=&#FŒðê^·n�tèÐ!­=3gΔ[o½5­=K—.•®]»zyT¡üîÝ»“ñ°wûöíRµjÕPlÁ«OŸ>NTù0ýÕ3“ � � �  ´ 7³“$ðÿعßP?ëúñã҄I—Ún4°ÐÀ‘mް3DqcDÂd!’Ý(rý�X"m3FM£HF Ò°%aËMªyÃfÝðŽ»áœ®DÄDŠw Á!Fúý^~çâœã¶sv^×Ûëu]çq ;çó~^×ãõ¹O? @€¹€ =ß‚íùvb" @€ @ ‘Àb‚öæÛËxà�ÎžâØ±ceݺuí}M˜¾aÆrâĉöw ù¦õS§N•5kÖ´g6oÞ\šo__¶lÙYgmbð­[·–ƒN^³råÊòÜsÏ•�~ô£g�§yMž¯^½úœs¿Õýw¿û]ifšþ9räHÙ´iSûï[o½u¥_pÁçe{úôéÉp¾!ÿy½‰ @€, Aû’X³‡$@€ @€�‘ Úó-XОo'&"@€ @€ ,&hß¾}{Ù·o_gOqüøñ²víÚö¾¿ýío³‚ò&Ro‚ö+Vœó=›oV¿öÚkÛ×ìÚµ«ìÞ½ûœgÞ|óÍrõÕW·1xó^Íý3wž©©©rôèѲ|ùò³ÞÝ„òÍ·³7AýôÏK/½T.»ì²ößs|ðÁrÇwœ·ëÜÿ`¡ßLÞoä @€Àèí£_±$@€ @€�% hÏ·dA{¾�˜ˆ @€H$°˜ ý¿ÿýoi"ê3ý4߆Þ|Sø§>õ©ÉŸo¼ñÆòè£�žõ‰›`æâ‹/žõ÷¹aúŽ;ÊÞ½{çUûÙÏ~V¾üå/·¯k¾uý¦›n:ç¹—_~yÏ6=« IDATVd¾eË–rèСYß’>wž={ö”»ï¾ûœ÷Î åW­Z5q™å¿�7–æÛ雟3}3ü¼üÿ_p¦(!ÿÀBï÷: @€�¥# h_:»ö¤ @€ 0^A{¾Ý ÚóíÄD @€ @€@"�Åíó�?3ß¶m[9pàÀ¬@|¾ó=ôPùâ¿Ø¾l!azó�èÍ·›7g§æ~óû™Þ÷È‘#eÓ¦MíŸ~øÃ–o~ó›³^º˜yæ å› ½‰þ›È½ù‰|«úÜ÷ZŒù|;ñw @€�¥! h_{ö” @€ 0nA{¾ý ÚóíÄD @€ @€@"�ÚAû™¾ñü\�¿Ø0½ Ä7lØPNœ81¹~¡�ø}÷ÝWšo€Ÿþyâ‰'Ê 7ÜÐþ{±óÌÊŸ>}º¬_¿¾ Ú¯¹æšòÔSO• /¼ð¼?s¿Aþ|ÍÏû  @€­€ }´«õ` @€ °„íù–-hÏ· @€ @€‰²í‹ ÓO�:UÖ¬YÓÊNMM•£G�–åË—ŸUû�wÞ)7n,O?ýtûš_|±|üãoÿ½Øy~üã—o|ãí=sCù.¿U}ûöíeß¾}í{=òÈ#¥ù–v? @€ÎW@Ð~¾b^O€ @€ò ÚóíDОo'&"@€ @€ d Úÿñ�”Õ«W·B Ó›?ÿüóåÊ+¯lÏíܹ³ÜsÏ=ç”nâõË/¿¼}ͪU«&ߘ¾råÊöw‹™§ùV÷Ûo¿½ìß¿¿½çäÉ“åŠ+®hÿÝUÐÞ|Óû§?ýéö›é›7˜å'ú¸… @ ¹€ =ù‚ŒG€ @€ h_ÒûüAûû îí @€ @€�a d ÚŸy晲aÆqÇŽeïÞ½ó¢þêW¿*ŸÿüçÛ×ýüç?/_úÒ—Îz®‰Î¿úÕ¯–x }Í–-[Êc�=V–-[Öþnî< å›È|ýúõ“8¾ùiBù&h¿ä’KÚ{çígzïyúÿ^07äo¾¥þ…^(+V¬XÈq¯!@€ 0K@ÐîA€ @€†/ hÏ·CA{¾�˜ˆ @€H$�-hÿå/Y¾ð…/´BM¨~Ë-·Ì+ö›ßü¦lݺµ}݃>Xî¸ã޳ž;räHÙ´iÓ¬¿ï{ß+wÝu׬ß-fžS§N•&,ŸþÙ¼ys9|øð¬P~nÐþ“Ÿü¤|å+_™÷9ç¾à¾ûî+Mô?ý³k×®²{÷îó¾Ç @€�€ Ýç€ @€ _@Оo‡‚ö|;1 @€ �H FÐþâ‹/–Ë/¿|ò”SSSå©§žšsŸëñ·oß^öíÛ×¾äøñãeíÚµóŠÍý†ö&"?tèPY¾|ù{Î6ó\wÝuïùýã�?^šoJŸù³˜yž|òÉrýõ×·×ìÙ³§Ü}÷ݳî�´ßyç�åþûïo¢yøÿ^0÷›à›3ÇŽ+ëÖ­[Èq¯!@€ ðA» @€¾€ =ßíùvb" @€ @ ‘@� ý�7Þ(Ï<óÌ$b¿øâ‹Ë† ôÄÿùÏÊ5×\3‰²›Ÿ}èCåäÉ“åþçæ=ÿüóÏ—+¯¼rÖëvîÜY¾þõ¯—K/½tòûW_}uËÿûߟÜýúë¯OΜ8qbò÷^x¡|âŸhïXìö±Ò8\tÑEózy @€�3 Ú}. @€ @€Àðíùv(hÏ· @€ @€‰jí‹}¼×^{mòmìMhÞü4qûŸþô§3~ËúÜ÷8Ó·•O¿fýúõåïÿ{{oóû«®ºªüûßÿ.¯¼òÊäeMàÞ|³ütüÞün1ó¼ûî»åöÛo/û÷ïoG|î¹çÞÛÏ Ú·mÛV8P.¸à‚ñ½õÖ[“ÿP`:Æo5ßRË-·,è¼ @€8“€ Ýç‚ @€ _@Оo‡‚ö|;1 @€ �H SÐþì³ÏNBóéŸ;ï¼³Üÿýe:ª™�íÉ'Ÿ,×_ý|/›„ò¿þõ¯Ëç>÷¹öÛ০¦ÊÑ£GgÅó‹™gnX¦P¾ðÍ7ß,W_}uùË_þ2™wÍš5åøñãeåÊ•óÎß¼àÑG-7ß|sûÚæ|ó ó+V¬XÐy/"@€ p&A»Ï @€¾€ =ßíùvb" @€ @ ‘@¦ ýøCÙ¼ys«ó‹_ü¢ÜvÛmç¥Õ|cù×¾öµÉ7»ÏýiâòýèG¥ù6ôwÞyg¶;vlò²[o½µ<üðó¾!}1óüë_ÿ*ëÖ­k¿ù½yžC‡½ç[æ›oro‚úÇOÞ¿ ÙOžüáwùá»öîÝ[îºë®öž?þñ�åºë®›÷Þ]»v•ï~÷»íëÎÍÏ{‘ @€˜# h÷‘ @€ @€Ã´çÛ¡ =ßNLD€ @€$kОˆø¬£üùÏ.×^{mû÷;v”&r?×ÏÓO?]>ó™ÏÌzÉK/½T.»ì²!<²  @€’ Ú“/Èx @€ @`‚ö ½Ï/´¿ÏàÞŽ @€–€ ½¿}½ñÆ哟üdyå•W&C¬Zµªœªuz @€X¢‚ö|‹´çÛ‰‰ @€ @€�D‚ö~—ÑÄê[·n-li¾…}ݺu“7ß±cGùÁ~ÐþýÆo,¿ýíoË>ð�~‡÷î @€£´�n¥ˆ @€– € =ßÒíùvb" @€ @ ‘€ ½ÿe<ûì³åª«®j™šš*G��|KûÎ�;Ëž={Ú¿5ß̾ÿ~1{ÿk3 @`”‚öQ®ÕC @€ @€À´ç[¸ =ßNLD€ @€$´çXÆ·¿ýírï½÷¶Ã<ôÐCå÷¿ÿý¬onßµkWùÎw¾3 Ýý @€¨! h¯¡êN @€ ðþ Úß_ï…¼› }!J^C€ @€,YA{ŽÕ¿ýöÛåŸÿüg;Ì?øÁrúôéÒü¾ù¹ð ËG>ò‘Ú‚ @`´‚öѮփ @€ @€À´ç[¶ =ßNLD€ @€$´'Z†Q @€ô, hïyÞž @€t hï±ã+íƒºŽ @€—€ }\ûô4 @€"‚öˆž³ @€ @ ‡€ =ÇfN!hÏ· @€ @€‰퉖a @€= Ú{^€·'@€ @€Ú;@ìø A{Ç ®#@€ @€Æ% h×>=  @€�ˆ€ =¢ç, @€È! hϱ‡™SÚóíÄD @€ @€@"A{¢e… @€@Ï‚öžàí  @€ @€@‚ö;¾BÐÞ1¨ë @€ @€�q ÚǵOOC€ @ " h�è9K€ @€rÚsìaæ‚ö|;1 @€ �H@ОhF!@€ г€ ½çx{ @€ Ð�€ ½Äޝ´w ê: @€ @`\‚öqíÓÓ @€ˆÚ#zÎ @€ @€�‚ö{˜9… =ßNLD€ @€$´'Z†Q @€ô, hïyÞž @€t hï±ã+íƒºŽ @€—€ }\ûô4 @€"‚öˆž³ @€ @ ‡€ =ÇfN!hÏ· @€ @€‰퉖a @€= Ú{^€·'@€ @€Ú;@ìø A{Ç ®#@€ @€Æ% h×>=  @€�ˆ€ =¢ç, @€È! hϱ‡™SÚóíÄD @€ @€@"A{¢e… @€@Ï‚öžàí  @€ @€@‚ö;¾BÐÞ1¨ë @€ @€�q ÚǵOOC€ @ " h�è9K€ @€rÚsìaæ‚ö|;1 @€ �H@ОhF!@€ г€ ½çx{ @€ Ð�€ ½Äޝ´w ê: @€ @`\‚öqíÓÓ @€ˆÚ#zÎ @€ @€�‚ö{˜9… =ßNLD€ @€$´'Z†Q @€ô, hïyÞž @€t hï±ã+íƒºŽ @€—€ }\ûô4 @€"‚öˆž³ @€ @ ‡€ =ÇfN!hÏ· @€ @€‰퉖a @€= Ú{^€·'@€ @€Ú;@ìø A{Ç ®#@€ @€Æ% h×>=  @€�ˆ€ =¢ç, @€È! hϱ‡™SÚóíÄD @€ @€@"A{¢e… @€@Ï‚öžàí  @€ @€@‚ö;¾BÐÞ1¨ë @€ @€�q ÚǵOOC€ @ " h�è9K€ @€rÚsìaæ‚ö|;1 @€ �H@ОhF!@€ г€ ½çx{ @€ Ð�€ ½Äޝ´w ê: @€ @`\‚öqíÓÓ @€ˆÚ#zÎ @€ @€�‚ö{˜9… =ßNLD€ @€$´'Z†Q @€ô, hïyÞž @€t hï±ã+íƒºŽ @€—ÀŒÿCk\æi @€X´ÀË/¿|Þg/ºè¢ó>ã @€ н€ ½{Óè�‚ö¨ ó @€ @€À¨í£^¯‡#@€ °(Aû¢Ø"@€ @€)í)Ö0kA{¾�˜ˆ @€‰ÀÛo¿=’'ñ @€D|C{TÐy @€ Ð�€ ½Ç.o´w©é. @€ @€À A»� @€Ó‚vŸ @€ �C@Оc3§´çÛ‰‰ @€ @€�‘ÚG²H�A€ @ A{ˆ® @€ @€Ú;@ìø A{Ç ®#@€ @€L Ú} @€˜´û, @€ @€�‚ö{˜9… =ßNLD€ @€ŒD@Ð>’Ez  @€Ú;@t @€è@@ÐÞbÇWÚ;u @€ @`Z@Ðî³@€ @€À´€ Ýg� @€ä´çØÃÌ)íùvb" @€ @`$‚ö‘,Òc @€è@@ÐÞ¢+ @€ @€@‚ö;¾BÐÞ1¨ë @€ @€Ó‚vŸ @€¦í>  @€ @ ‡€ =ÇfN!hÏ· @€ @€#´�d‘ƒ @€@‚ö]A€ @€:´w€Øñ‚öŽA]G€ @€˜´û, @€ 0- h÷Y @€ @€9í9ö0s A{¾�˜ˆ @€‰€ }$‹ô @€:´w€è  @€ Ð�€ ½Äޝ´w ê: @€ @€À´€ Ýg� @€�iA»Ï @€È! hϱ‡™SÚóíÄD @€ @€ÀHí#Y¤Ç @€ Ð�€ ½DW @€ @€�í v|… ½cP× @€ @€ @€ @€ @€ �S@Оo/‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€[ ù¸ IDAT @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ }E† IDAT @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾= ÚóíÄD @€ @€ @€ @€ @€T´W@ ^)h:N€ @€ @€ @€ @€ @€À0íùö$hÏ· @€ @€ @€ @€ @€ PA@Ð^5x¥ =è8 @€ @€ @€ @€ @€Ã´çÛ“ =ßNLD€ @€ @€ @€ @€ @€@A{Ôà•‚ö  ã @€ @€ @€ @€ @€ C@ОoO‚ö|;1 @€ @€ @€ @€ @€íPƒW Úƒ€Ž @€ @€ @€ @€ @€ 0 A{¾=M‚v? @€ @€ @€ @€ @€XBÿoìÏ:”´�ý“èù @€ @€ @€ @€ @€˜+0”Þ{Ñ›ý.ZÆA @€ @€ @€ @€ @€¨* h¯Êër @€ @€ @€ @€ @€8›€ Ýgƒ @€ @€ @€ @€ @€z´÷ÂîM  @€ @€ @€ @€ @€ @@Ðî3@€ @€ @€ @€ @€ @€½Ú{a÷¦ @€ @€ @€ @€ @€ h÷ @€ @€ @€ @€ @€ @€�^í½°{S @€ @€ @€ @€ @€´û  @€ @€ @€ @€ @€ @€@/‚ö^ؽ) @€ @€ @€ @€ @€Ú} @€ @€ @€ @€ @€ @ A{/ìÞ” @€ @€ @€ @€ @€í> @€ @€ @€ @€ @€ Ћ€ ½voJ€ @€ @€ @€ @€ @€‚vŸ @€ @€ @€ @€ @€èE@ÐÞ »7%@€ @€ @€ @€ @€ @€A»Ï @€ @€ @€ @€ @€ô" hï…Ý› @€ @€ @€ @€ @€ @€€ Ýg€ @€ @€ @€ @€ @€z´÷ÂîM  @€ @€ @€ @€ @€ @@Ðî3@€ @€ @€ @€ @€ @€½Ú{a÷¦ @€ @€ @€ @€ @€ h÷ @€ @€ @€ @€ @€ @€�^í½°{S @€ @€ @€ @€ @€´û  @€ @€ @€ @€ @€ @€@/‚ö^ؽ) @€ @€ @€ @€ @€Ú} @€ @€ @€ @€ @€ @ A{/ìÞ” @€ @€ @€ @€ @€í> @€ @€ @€ @€ @€ Ћ€ ½voJ€ @€ @€ @€ @€ @€‚vŸ @€ @€ @€ @€ @€èE@ÐÞ »7%@€ @€ @€ @€ @€ @€A»Ï @€ @€ @€ @€ @€ô" hï…Ý› @€ @€ @€ @€ @€ @€€ Ýg€ @€ @€ @€ @€ @€z´÷ÂîM  @€ @€ @€ @€ @€ @@Ðî3@€ @€ @€ @€ @€ @€½Ú{a÷¦ @€ @€ @€ @€ @€ h÷ @€ @€ @€ @€ @€ @àÛµc†aþ]WFŸ(„�%@`´/ìF  @€ @€ @€ @€ @€ @@Ðî @€ @€ @€ @€ @€ °Úv£ @€ @€ @€ @€ @€ h÷ @€ @€ @€ @€ @€Xí »Q @€ @€ @€ @€ @€´û @€ @€ @€ @€ @€,‚ö…Ý( @€ @€ @€ @€ @€Ú}€ @€ @€ @€ @€ @€AûÂn” @€ @€ @€ @€ @€í>@€ @€ @€ @€ @€ @€‹€ }a7J€ @€ @€ @€ @€ @€‚v @€ @€ @€ @€ @€ @€�E@о°%@€ @€ @€ @€ @€ @€A» @€ @€ @€ @€ @€ @€À" h_Ø� @€ @€ @€ @€ @€ @€€ Ý @€ @€ @€ @€ @€ @`´/ìF  @€ @€ @€ @€ @€ @@Ðî @€ @€ @€ @€ @€ °ÑaúC¨ÞümIEND®B`‚PynPoint-PynPoint-e5d0637/docs/about.rst000066400000000000000000000021211512720201400201710ustar00rootroot00000000000000.. _about: About ===== .. _contact: Contact ------- Questions can be directed to `Tomas Stolker `_ and `Markus Bonse `_. .. _attribution: Attribution ----------- If you use PynPoint in your publication then please cite `Stolker et al. (2019) `_. Please also cite `Amara & Quanz (2012) `_ as the origin of PynPoint, which focused initially on the use of principal component analysis (PCA) as a PSF subtraction method. In case you use specifically the PCA-based background subtraction module or the wavelet based speckle suppression module, please give credit to `Hunziker et al. (2018) `_ or `Bonse et al. (preprint) `_, respectively. .. _acknowledgements: Acknowledgements ---------------- We would like to thank everyone who provided `contributions `_ to PynPoint. PynPoint-PynPoint-e5d0637/docs/architecture.rst000066400000000000000000000130441512720201400215470ustar00rootroot00000000000000.. _architecture: Architecture ============ .. _architecture_intro: Introduction ------------ PynPoint has evolved from a PSF subtraction toolkit to an end-to-end pipeline for processing and analysis of high-contrast imaging data. The architecture of PynPoint was redesigned in v0.3.0 with the goal to create a generic, modular, and open-source data reduction pipeline, which is extendable to new data processing techniques and data types. The actual pipeline and the processing modules have been separated in a different subpackages. Therefore, it is possible to extend the processing functionalities without intervening with the core of the pipeline. The UML class diagram below illustrates the pipeline architecture: .. image:: _static/uml.png :width: 100% The diagram shows that the architecture is subdivided in three components: * Data management: :class:`pynpoint.core.dataio` * Pipeline modules for reading, writing, and processing of data: :class:`pynpoint.core.processing` * The actual pipeline: :class:`pynpoint.core.pypeline` .. _central_database: Central database ---------------- The data management has been separated from the data processing for the following reasons: 1. Raw datasets can be very large (in particular in the 3--5 μm regime) which challenges the processing on a computer with a small amount of memory (RAM). A central database is used to store the data on a computer's hard drive. 2. Some data is used in different steps of the pipeline. A central database makes it easy to access that data without making a copy. 3. The central data storage on the hard drive will remain updated after each step. Therefore, processing steps that already finished remain unaffected if an error occurs or the data reduction is interrupted by the user. Understanding the central data storage classes can be helpful with the development of new pipeline modules (see :ref:`coding`). When running the pipeline, it is sufficient to understand the concept of database tags. Each pipeline module has input and/or output tags which point to specific dataset in the central database. A module with ``image_in_tag='im_arr'`` will read the input images from the central database under the tag name `im_arr`. Similarly, a module with ``image_out_tag='im_arr_processed'`` will store a the processed images in the central database under the tag `im_arr_processed`. Accessing the data storage occurs through instances of :class:`~pynpoint.core.dataio.Port` which allows pipeline modules to read data from and write data to database. .. _modules: Pipeline modules ---------------- A pipeline module has a specific task that is appended to the internal queue of a :class:`~pynpoint.core.pypeline.Pypeline` instance. Pipeline modules can read and write data tags from and to the central database through dedicated input and output connections. There are three types of pipeline modules: 1. :class:`pynpoint.core.processing.ReadingModule` - A module with only output tags/ports, used to read data to the central database. 2. :class:`pynpoint.core.processing.WritingModule` - A module with only input tags/ports, used to export data from the central database. 3. :class:`pynpoint.core.processing.ProcessingModule` - A module with both input and output tags/ports, used for processing of the data. Typically, a :class:`~pynpoint.core.processing.ProcessingModule` reads one or multiple datasets from the database, applies a specific processing task with user-defined parameter values, and stores the results as a new dataset in the database. In order to create a valid data reduction cascade, one should check that the required input tags are linked to data which were previously created by another pipeline module. In other words, there needs to be a previous module which has stored output under that same tag name. .. _pypeline: Pypeline -------- The :class:`~pynpoint.core.pypeline` module is the central component which manages the order and execution of the different pipeline modules. Each :class:`~pynpoint.core.pypeline.Pypeline` instance has an ``working_place_in`` path which is where the central database and configuration file are stored, an ``input_place_in`` path which is the default data location for reading modules, and an ``output_place_in`` path which is the default output path where the data will be saved by the writing modules: .. code-block:: python from pynpoint import Pypeline, FitsReadingModule pipeline = Pypeline(working_place_in='/path/to/working_place', input_place_in='/path/to/input_place', output_place_in='/path/to/output_place') A pipeline module is created from any of the classes listed in the :ref:`pipeline_modules` section, for example: .. code-block:: python module = FitsReadingModule(name_in='read', image_tag='input') The module is appended to the pipeline queue as: .. code-block:: python pipeline.add_module(module) And can be removed from the queue with the following method: .. code-block:: python pipeline.remove_module('read') The names and order of the pipeline modules can be listed with: .. code-block:: python pipeline.get_module_names() Running all modules attached to the pipeline is achieved with: .. code-block:: python pipeline.run() Or a single module is executed as: .. code-block:: python pipeline.run_module('read') Both run methods will check if the pipeline has valid input and output tags. An instance of :class:`~pynpoint.core.pypeline.Pypeline` can be used to directly access data from the central database. See the :ref:`hdf5_files` section for more information. PynPoint-PynPoint-e5d0637/docs/coding.rst000066400000000000000000000201551512720201400203310ustar00rootroot00000000000000.. _coding: Coding a new module =================== .. _constructor: There are three different types of pipeline modules: :class:`~pynpoint.core.processing.ReadingModule`, :class:`~pynpoint.core.processing.WritingModule`, and :class:`~pynpoint.core.processing.ProcessingModule`. The concept is similar for these three modules so here we will explain only how to code a processing module. Class constructor ----------------- First, we need to import the interface (i.e. abstract class) :class:`~pynpoint.core.processing.ProcessingModule`: : .. code-block:: python from pynpoint.core.processing import ProcessingModule All pipeline modules are classes which contain the parameters of the pipeline step, input ports and/or output ports. So let’s create a simple ``ExampleModule`` class using the ProcessingModule interface (inheritance): .. code-block:: python class ExampleModule(ProcessingModule): When an IDE like *PyCharm* is used, a warning will appear that all abstract methods must be implemented in the ``ExampleModule`` class. The abstract class :class:`~pynpoint.core.processing.ProcessingModule` has some abstract methods which have to be implemented by its children classes (e.g., ``__init__`` and ``run``). We start by implementing the ``__init__`` method (i.e., the constructor of our module): .. code-block:: python def __init__(self, name_in='example', in_tag_1='in_tag_1', in_tag_2='in_tag_2', out_tag_1='out_tag_1', out_tag_2='out_tag_2', parameter_1=0, parameter_2='value'): Each ``__init__`` method of :class:`~pynpoint.core.processing.PypelineModule` requires a ``name_in`` argument which is used by the pipeline to run individual modules by name. Furthermore, the input and output tags have to be defined which are used to access data from the central database. The constructor starts with a call of the :class:`~pynpoint.core.processing.ProcessingModule` interface: .. code-block:: python super().__init__(name_in) Next, the input and output ports behind the database tags need to be defined: .. code-block:: python self.m_in_port_1 = self.add_input_port(in_tag_1) self.m_in_port_2 = self.add_input_port(in_tag_2) self.m_out_port_1 = self.add_output_port(out_tag_1) self.m_out_port_2 = self.add_output_port(out_tag_2) Reading to and writing from the central database should always be done with the ``add_input_port`` and ``add_output_port`` functionalities and not by manually creating an instance of :class:`~pynpoint.core.dataio.InputPort` or :class:`~pynpoint.core.dataio.OutputPort`. Finally, the module parameters should be saved as attributes of the ``ExampleModule`` instance: .. code-block:: python self.m_parameter_1 = parameter_1 self.m_parameter_2 = parameter_2 That's it! The constructor of the ``ExampleModule`` is ready. .. _run_method: Run method ---------- We can now add the functionalities of the module in the ``run`` method which will be called by the pipeline: .. code-block:: python def run(self): The input ports of the module are used to load data from the central database into the memory with slicing or with the ``get_all`` method: .. code-block:: python data1 = self.m_in_port_1.get_all() data2 = self.m_in_port_2[0:4] We want to avoid using the ``get_all`` method because data sets obtained in the $L'$ and $M'$ bands typically consists of thousands of images so loading all images at once in the computer memory might not be possible. Instead, it is recommended to use the ``MEMORY`` attribute that is specified in the configuration file (see :ref:`configuration`) Attributes of a dataset can be read as follows: .. code-block:: python parang = self.m_in_port_1.get_attribute('PARANG') pixscale = self.m_in_port_2.get_attribute('PIXSCALE') And attributes of the central configuration are accessed through the :class:`~pynpoint.core.dataio.ConfigPort`: .. code-block:: python memory = self._m_config_port.get_attribute('MEMORY') cpu = self._m_config_port.get_attribute('CPU') More information on importing of data can be found in the API documentation of :class:`~pynpoint.core.dataio.InputPort`. Next, the processing steps are implemented: .. code-block:: python result1 = 10.*self.m_parameter_1 result2 = 20.*self.m_parameter_1 result3 = [1, 2, 3] attribute = self.m_parameter_2 The output ports are used to write the results to the central database: .. code-block:: python self.m_out_port_1.set_all(result1) self.m_out_port_1.append(result2) self.m_out_port_2[0:2] = result2 self.m_out_port_2.add_attribute(name='new_attribute', value=attribute) More information on storing of data can be found in the API documentation of :class:`~pynpoint.core.dataio.OutputPort`. The data attributes of the input port need to be copied and history information should be added. These steps should be repeated for all the output ports: .. code-block:: python self.m_out_port_1.copy_attributes(self.m_in_port_1) self.m_out_port_1.add_history('ExampleModule', 'history text') self.m_out_port_2.copy_attributes(self.m_in_port_1) self.m_out_port_2.add_history('ExampleModule', 'history text') Finally, the central database and all the open ports are closed: .. code-block:: python self.m_out_port_1.close_port() .. important:: It is enough to close only one port because all other ports will be closed automatically. .. _apply_function: Apply function to images ------------------------ A processing module often applies a specific method to each image of an input port. Therefore, the :func:`~pynpoint.core.processing.ProcessingModule.apply_function_to_images` function has been implemented to apply a function to all images of an input port. This function uses the ``CPU`` and ``MEMORY`` parameters from the configuration file to automatically process subsets of images in parallel. An example of the implementation can be found in the code of the bad pixel cleaning with a sigma filter: :class:`~pynpoint.processing.badpixel.BadPixelSigmaFilterModule`. .. _example_module: Example module -------------- The full code for the ``ExampleModule`` from above is: .. code-block:: python from pynpoint.core.processing import ProcessingModule class ExampleModule(ProcessingModule): def __init__(self, name_in='example', in_tag_1='in_tag_1', in_tag_2='in_tag_2', out_tag_1='out_tag_1', out_tag_2='out_tag_2â€�, parameter_1=0, parameter_2='value'): super(ExampleModule, self).__init__(name_in) self.m_in_port_1 = self.add_input_port(in_tag_1) self.m_in_port_2 = self.add_input_port(in_tag_2) self.m_out_port_1 = self.add_output_port(out_tag_1) self.m_out_port_2 = self.add_output_port(out_tag_2) self.m_parameter_1 = parameter_1 self.m_parameter_2 = parameter_2 def run(self): data1 = self.m_in_port_1.get_all() data2 = self.m_in_port_2[0:4] parang = self.m_in_port_1.get_attribute('PARANG') pixscale = self.m_in_port_2.get_attribute('PIXSCALE') memory = self._m_config_port.get_attribute('MEMORY') cpu = self._m_config_port.get_attribute('CPU') result1 = 10.*self.m_parameter_1 result2 = 20.*self.m_parameter_1 result3 = [1, 2, 3] self.m_out_port_1.set_all(result1) self.m_out_port_1.append(result2) self.m_out_port_2[0:2] = result2 self.m_out_port_2.add_attribute(name='new_attribute', value=attribute) self.m_out_port_1.copy_attributes(self.m_in_port_1) self.m_out_port_1.add_history('ExampleModule', 'history text') self.m_out_port_2.copy_attributes(self.m_in_port_1) self.m_out_port_2.add_history('ExampleModule', 'history text') self.m_out_port_1.close_port() PynPoint-PynPoint-e5d0637/docs/conf.py000066400000000000000000000053351512720201400176360ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.resolve())) # -- Project information ----------------------------------------------------- project = "PynPoint" copyright = "2026, Tomas Stolker & Markus Bonse" author = "Tomas Stolker & Markus Bonse" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.napoleon", "sphinx.ext.viewcode", "sphinx_automodapi.automodapi", "nbsphinx" ] numpydoc_show_class_members = False # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "tutorials/.ipynb_checkpoints/*"] # Disable notebook timeout nbsphinx_timeout = -1 # Allow errors from notebooks nbsphinx_allow_errors = True autoclass_content = "both" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "sphinx_book_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { "path_to_docs": "docs", "repository_url": "https://github.com/PynPoint/PynPoint", "repository_branch": "main", "launch_buttons": { "notebook_interface": "jupyterlab", }, "use_edit_page_button": True, "use_issues_button": True, "use_repository_button": True, "use_download_button": True, } html_context = { "github_user": "PynPoint", "github_repo": "PynPoint", "github_version": "main", "doc_path": "docs", } html_static_path = ["_static"] html_logo = "_static/logo.png" html_favicon = "_static/favicon.png" html_search_language = "en" PynPoint-PynPoint-e5d0637/docs/configuration.rst000066400000000000000000000106161512720201400217360ustar00rootroot00000000000000.. _configuration: Configuration ============= .. _config_intro: Introduction ------------ A configuration file has to be stored in the ``working_place_in`` with the name ``PynPoint_config.ini``. The file will be created with default values in case it does not exist when the pipeline is initiated. The values of the configuration file are stored in a separate group of the central database, each time the pipeline is initiated. .. _config_file: Configuration file ------------------ The file contains two different sections of configuration parameters. The ``header`` section is used to link attributes in PynPoint with header values in the FITS files that will be imported into the database. For example, some of the pipeline modules require values for the dithering position. These attributes are stored as ``DITHER_X`` and ``DITHER_Y`` in the central database and are for example provided by the ``ESO SEQ CUMOFFSETX`` and ``ESO SEQ CUMOFFSETY`` values in the FITS header. Setting ``DITHER_X: ESO SEQ CUMOFFSETX`` in the ``header`` section of the configuration file makes sure that the relevant FITS header values are imported when :class:`~pynpoint.readwrite.fitsreading.FitsReadingModule` is executed. Therefore, FITS files have to be imported again if values in the ``header`` section are changed. Values can be set to ``None`` since ``header`` values are only required for some of the pipeline modules. The second section of the configuration values contains the central settings that are used by the pipeline modules. These values are stored in the ``settings`` section of the configuration file. The pixel scale can be provided in arcsec per pixel (e.g. ``PIXSCALE: 0.027``), the number of images that will be simultaneously loaded into the memory (e.g. ``MEMORY: 1000``), and the number of cores that are used for pipeline modules that have multiprocessing capabilities (e.g. ``CPU: 8``) such as :class:`~pynpoint.processing.psfsubtraction.PcaPsfSubtractionModule` and :class:`~pynpoint.processing.fluxposition.MCMCsamplingModule`. A complete overview of the pipeline modules that support multiprocessing is available in the :ref:`pipeline_modules` section. Note that some of the pipeline modules provide also multithreading support, which by default runs on all available CPUs. The multithreading can be controlled from the command line by setting the ``OMP_NUM_THREADS`` environment variable: .. code-block:: console $ export OMP_NUM_THREADS=8 In this case a maximum of 8 threads is used. So, if a modules provide both multiprocessing and multithreading support, then the total number of used cores is equal to the product of the values chosen for ``CPU`` in the configuration file and ``OMP_NUM_THREADS`` from the command line. .. _config_examples: Examples -------- In this section, several examples are provided of the configuration content for the following instruments: - :ref:`config_naco` - :ref:`config_sphere` - :ref:`config_visir` .. _config_naco: VLT/NACO ^^^^^^^^ .. code-block:: ini [header] INSTRUMENT: INSTRUME NFRAMES: NAXIS3 EXP_NO: ESO DET EXP NO DIT: ESO DET DIT NDIT: ESO DET NDIT PARANG_START: ESO ADA POSANG PARANG_END: ESO ADA POSANG END DITHER_X: ESO SEQ CUMOFFSETX DITHER_Y: ESO SEQ CUMOFFSETY PUPIL: ESO ADA PUPILPOS DATE: DATE-OBS LATITUDE: ESO TEL GEOLAT LONGITUDE: ESO TEL GEOLON RA: RA DEC: DEC [settings] PIXSCALE: 0.027 MEMORY: 1000 CPU: 1 .. _config_sphere: VLT/SPHERE/IRDIS ^^^^^^^^^^^^^^^^ .. code-block:: ini [header] INSTRUMENT: INSTRUME NFRAMES: NAXIS3 EXP_NO: ESO DET EXP ID DIT: EXPTIME NDIT: ESO DET NDIT PARANG_START: ESO TEL PARANG START PARANG_END: ESO TEL PARANG END DITHER_X: ESO INS1 DITH POSX DITHER_Y: ESO INS1 DITH POSY PUPIL: None DATE: DATE-OBS LATITUDE: ESO TEL GEOLAT LONGITUDE: ESO TEL GEOLON RA: ESO INS4 DROT2 RA DEC: ESO INS4 DROT2 DEC [settings] PIXSCALE: 0.01226 MEMORY: 1000 CPU: 1 .. _config_visir: VLT/VISIR ^^^^^^^^^ .. code-block:: ini [header] INSTRUMENT: INSTRUME NFRAMES: NAXIS3 EXP_NO: ESO TPL EXPNO DIT: ESO DET SEQ1 DIT NDIT: ESO DET CHOP NCYCLES PARANG_START: ESO ADA POSANG PARANG_END: ESO ADA POSANG END DITHER_X: None DITHER_Y: None PUPIL: ESO ADA PUPILPOS DATE: DATE-OBS LATITUDE: ESO TEL GEOLAT LONGITUDE: ESO TEL GEOLON RA: RA DEC: DEC [settings] PIXSCALE: 0.045 MEMORY: 1000 CPU: 1 PynPoint-PynPoint-e5d0637/docs/contributing.rst000066400000000000000000000010201512720201400215630ustar00rootroot00000000000000.. _contributing: Contributing ============ We welcome contributions, for example with the development of new pipeline modules and tutorials, improving existing functionalities, and fixing bugs. Please consider forking the Github repository and creating a `pull request `_ for implementations that could be of interest for other users. Bug reports and functionality requests can be provided by creating an `issue `_ on the Github page. PynPoint-PynPoint-e5d0637/docs/index.rst000066400000000000000000000016511512720201400201750ustar00rootroot00000000000000.. _index: PynPoint ======== PynPoint is a pipeline for processing and analysis of high-contrast imaging data of exoplanets. The pipeline uses principal component analysis for the subtraction of the stellar PSF and supports post-processing with ADI, RDI, and SDI techniques. .. figure:: _static/eso.jpg :width: 100% :target: http://www.eso.org/public/news/eso1310/ .. toctree:: :maxdepth: 1 :caption: Getting started :hidden: installation tutorials/first_example.ipynb .. toctree:: :maxdepth: 1 :caption: User documentation :hidden: architecture configuration pipeline_modules running_pynpoint tutorials modules .. toctree:: :maxdepth: 1 :caption: NEAR documentation :hidden: near .. toctree:: :maxdepth: 1 :caption: Developer documentation :hidden: python coding .. toctree:: :maxdepth: 1 :caption: About :hidden: contributing about PynPoint-PynPoint-e5d0637/docs/installation.rst000066400000000000000000000064141512720201400215710ustar00rootroot00000000000000.. _installation: Installation ============ .. _virtual_environment: Virtual Environment ------------------- PynPoint is available on `PyPI `_ and `Github `_. We recommend using a Python virtual environment to install and run PynPoint such that the correct dependency versions are installed without affecting other Python installations. First install ``virtualenv``, for example with the `pip package manager `_: .. code-block:: console $ pip install virtualenv Then create a virtual environment: .. code-block:: console $ virtualenv -p python3 folder_name And activate the environment with: .. code-block:: console $ source folder_name/bin/activate A virtual environment can be deactivated with: .. code-block:: console $ deactivate .. important:: Make sure to adjust the path where the virtual environment is installed and activated. .. _installation_pypi: Installation from PyPI ---------------------- PynPoint can now be installed with pip: .. code-block:: console $ pip install pynpoint If you do not use a virtual environment then you may have to add the ``--user`` argument: .. code-block:: console $ pip install --user pynpoint To update the installation to the most recent version: .. code-block:: console $ pip install --upgrade PynPoint .. _installation_github: Installation from Github ------------------------ Using pip ^^^^^^^^^ The Github repository contains the latest commits. Installation from Github is also possible with ``pip``: .. code-block:: console $ pip install git+https://github.com/PynPoint/PynPoint.git Cloning the repository ^^^^^^^^^^^^^^^^^^^^^^ Alternatively, the Github repository can be cloned, which is in particular useful if you want to look into the code: .. code-block:: console $ git clone https://github.com/PynPoint/PynPoint.git The package is installed by running ``pip`` in the local repository folder: .. code-block:: console $ pip install -e . Instead of running ``setup.py``, the path of the repository can also be added to the ``PYTHONPATH`` environment variable such that PynPoint can be imported from any working folder. When using a ``virtualenv``, the ``PYTHONPATH`` can be added to the activation script: Once a local copy of the repository exists, new commits can be pulled from Github with: .. code-block:: console $ git pull origin main Do you want to makes changes to the code? Please fork the PynPoint repository on the Github page and clone your own fork instead of the main repository. We very much welcome contributions and pull requests (see :ref:`contributing` section). Dependencies ^^^^^^^^^^^^ If needed, the dependencies can be manually installed from the PynPoint folder: .. code-block:: console $ pip install -r requirements.txt Or updated to the latest versions with which PynPoint is compatible: .. code-block:: console $ pip install --upgrade -r requirements.txt .. _testing_pynpoint: Testing Pynpoint ---------------- The installation can be tested by starting Python in interactive mode and creating an instance of the ``Pypeline``: .. code-block:: python >>> import pynpoint >>> pipeline = pynpoint.Pypeline() PynPoint-PynPoint-e5d0637/docs/modules.rst000066400000000000000000000001311512720201400205260ustar00rootroot00000000000000.. _api: API Documentation ================= .. toctree:: :maxdepth: 3 pynpoint PynPoint-PynPoint-e5d0637/docs/near.rst000066400000000000000000000415471512720201400200230ustar00rootroot00000000000000.. _near_data: Data reduction ============== .. _near_intro: Introduction ------------ The documentation on this page contains an introduction into data reduction of the modified `VLT/VISIR `_ instrument for the `NEAR `_ (New Earths in the Alpha Cen Region) experiment. All data are available in the ESO archive under program ID `2102.C-5011(A) `_. The basic processing steps with PynPoint are described in the example below while a complete overview of all available pipeline modules can be found in the :ref:`pipeline_modules` section. Further details about the pipeline architecture and data processing are also available in `Stolker et al. (2019) `_. More in-depth information of the input parameters for individual PynPoint modules can be found in the :ref:`api`. Please also have a look at the :ref:`attribution` section when using PynPoint results in a publication. .. _near_example: Example ------- In this example, we will process the images of chop A (i.e., frames in which alpha Cen A was centered behind the AGPM coronagraph). Note that the same procedure can be applied on the images of chop B (i.e., with alpha Cen B centered behind the coronagraph). Setup ^^^^^ To get started, use the instructions available in the :ref:`installation` section to install PynPoint. We also need to download the NEAR data associated with the ESO program that was provided above. It is recommended to start with downloading only a few files to first validate the pipeline installation. Now that we have the data, we can start the data reduction with PynPoint! The :class:`~pynpoint.core.pypeline.Pypeline` of PynPoint requires a folder for the ``working_place``, ``input_place``, and ``output_place``. Before we start running PynPoint, we have to put the raw NEAR data in the default input folder or the location that will provided as ``input_dir`` in the :class:`~pynpoint.readwrite.nearreading.NearReadingModule`. Then we create a configuration file which contains the global pipeline settings and is used to select the required FITS header keywords. Create a text file called ``PynPoint_config.ini`` in the ``working_place`` folder with the following content: .. code-block:: ini [header] INSTRUMENT: INSTRUME NFRAMES: ESO DET CHOP NCYCLES EXP_NO: ESO TPL EXPNO DIT: ESO DET SEQ1 DIT NDIT: None PARANG_START: ESO ADA POSANG PARANG_END: ESO ADA POSANG END DITHER_X: None DITHER_Y: None PUPIL: ESO ADA PUPILPOS DATE: DATE-OBS LATITUDE: ESO TEL GEOLAT LONGITUDE: ESO TEL GEOLON RA: RA DEC: DEC [settings] PIXSCALE: 0.045 MEMORY: 1000 CPU: 1 The ``MEMORY`` and ``CPU`` setting can be adjusted. They define the number of images that is simultaneously loaded into the computer memory and the number of parallel processes that are used by some of the pipeline modules. Note that in addition to the config file above, the ``working_place`` directory is also used to store the database file (`PynPoint_database.hdf5`). This database stores all intermediate results (typically a stack of images), which allows the user to rerun particular processing steps without having to rerun the complete pipeline. Running PynPoint ^^^^^^^^^^^^^^^^ Example code snippets for the different steps to reduce NEAR data with PynPoint are included below. These code snippets can be executed in Python interactive mode, as a Jupyter notebook.py file, or combined into a python script and executed from the command line. The first steps are to initialize the pipeline and read in the data contained in the given ``input_place_in`` directory. Data are automatically divided into the chop A and chop B data sets. Here we also use the :class:`~pynpoint.processing.psfpreparation.AngleInterpolationModule` to calculate the parallactic angle for each individual frame, which is necessary for derotating and combining the frames after PSF subtraction: .. code-block:: python # Import the Pypeline and the modules that we will use in this example from pynpoint import Pypeline, NearReadingModule, AngleInterpolationModule, \ CropImagesModule, SubtractImagesModule, ExtractBinaryModule, \ StarAlignmentModule, FitCenterModule, ShiftImagesModule, \ FakePlanetModule, PSFpreparationModule, PcaPsfSubtractionModule, \ ContrastCurveModule, FitsWritingModule, TextWritingModule # Create a Pypeline instance (change the directories to the correct paths) pipeline = Pypeline(working_place_in='working_folder/', # directory for database and config files input_place_in='input_folder/', # default directory for reading in input data output_place_in='output_folder/') # default directory for saving output files # (i.e., with FitsWritingModule used below) # Read the raw data (i.e., all the fits files contained in the input_place_in folder above) # and separate the chop A and chop B images module = NearReadingModule(name_in='read', input_dir=None, chopa_out_tag='chopa', chopb_out_tag='chopb') pipeline.add_module(module) # Interpolate the parallactic angles between the start and end value of each FITS file # The angles will be added as PARANG attribute to the chop A and chop B datasets module = AngleInterpolationModule(name_in='angle1', data_tag='chopa') pipeline.add_module(module) module = AngleInterpolationModule(name_in='angle2', data_tag='chopb') pipeline.add_module(module) # Run each of the above modules using their 'name_in' tags pipeline.run_module('read') pipeline.run_module('angle1') pipeline.run_module('angle2') # Note that you can also run all the added modules using this function: # pipeline.run() The next step is to reduce the chop A frames with alpha Cen A behind the corognagraph. Here we crop the chop A and chop B images around the coronagraph position, subtract chop B from chop A to remove the sky background, and center the subtracted chop A frames: .. code-block:: python # Crop the chop A and chop B images around the approximate coronagraph position module = CropImagesModule(size=5., center=(432, 287), name_in='crop1', image_in_tag='chopa', image_out_tag='chopa_crop') pipeline.add_module(module) module = CropImagesModule(size=5., center=(432, 287), name_in='crop2', image_in_tag='chopb', image_out_tag='chopb_crop') pipeline.add_module(module) # Subtract frame-by-frame chop B from chop A module = SubtractImagesModule(name_in='subtract_aminusb', image_in_tags=('chopa_crop', 'chopb_crop'), image_out_tag='chopa_sub', scaling=1.) pipeline.add_module(module) # Fit the center position of chop A, using the images from before the chop-subtraction # For simplicity, only the mean of all images is fitted module = FitCenterModule(name_in='center1', image_in_tag='chopa_crop', fit_out_tag='chopa_fit', mask_out_tag=None, method='mean', radius=1., sign='positive', model='moffat', filter_size=None, guess=(0., 0., 10., 10., 1e4, 0., 0., 1.)) pipeline.add_module(module) # Center the chop-subtracted images module = ShiftImagesModule(shift_xy='chopa_fit', name_in='shift1', image_in_tag='chopa_sub', image_out_tag='chopa_center', interpolation='spline') pipeline.add_module(module) # Run each of the above modules pipeline.run_module('crop1') pipeline.run_module('crop2') pipeline.run_module('subtract_aminusb') pipeline.run_module('center1') pipeline.run_module('shift1') Next, we use the chop B frames where alpha Cen A if off of the coronagraph to extract a reference PSF. This reference PSF will later be used for calculating the detection limits: .. code-block:: python # Subtract chop A from chop B before extracting the non-coronagraphic PSF module = SubtractImagesModule(name_in='subtract_bminusa', image_in_tags=('chopb', 'chopa'), image_out_tag='chopb_sub', scaling=1.) pipeline.add_module(module) # Crop out the non-coronagraphic PSF for chop A from the chop B images module = ExtractBinaryModule(pos_center=(432., 287.), pos_binary=(430., 175.), name_in='extract_refpsf', image_in_tag='chopb_sub', image_out_tag='psfa', image_size=5., search_size=1., filter_size=None) pipeline.add_module(module) # Align the non-coronagraphic PSF images module = StarAlignmentModule(name_in='align_refpsf', image_in_tag='psfa', ref_image_in_tag=None, image_out_tag='psfa_align', interpolation='spline', accuracy=10, resize=None, num_references=10, subframe=1.) pipeline.add_module(module) # Fit the center position of the mean, non-coronagraphic PSF module = FitCenterModule(name_in='center_refpsf', image_in_tag='psfa', fit_out_tag='psfa_fit', mask_out_tag=None, method='mean', radius=1., sign='positive', model='moffat', filter_size=None, guess=(0., 0., 10., 10., 1e4, 0., 0., 1.)) pipeline.add_module(module) # Center the non-coronagraphic PSF images module = ShiftImagesModule(shift_xy='psfa_fit', name_in='shift_refpsf', image_in_tag='psfa', image_out_tag='psfa_center', interpolation='spline') pipeline.add_module(module) # Mask the non-coronagraphic PSF beyond 1 arsec module = PSFpreparationModule(name_in='prep_refpsf', image_in_tag='psfa_center', image_out_tag='psfa_mask', mask_out_tag=None, norm=False, cent_size=None, edge_size=1.) pipeline.add_module(module) # Run each of the above modules pipeline.run_module('subtract_bminusa') pipeline.run_module('extract_refpsf') pipeline.run_module('align_refpsf') pipeline.run_module('center_refpsf') pipeline.run_module('shift_refpsf') pipeline.run_module('prep_refpsf') Finally, we use PCA to subtract the stellar PSF of alpha Cen A. For testing purposes, we first use the reference PSF created above to inject a fake planet into the chop A data. The median combination of the PSF-subtracted and derotated frames is saved in its own tag and then written out to a fits file: .. code-block:: python # Inject a fake planet at a separation of 1 arcsec and a contrast of 10 mag module = FakePlanetModule(position=(1., 0.), magnitude=10., psf_scaling=1., interpolation='spline', name_in='fake', image_in_tag='chopa_center', psf_in_tag='psfa_mask', image_out_tag='chopa_fake') pipeline.add_module(module) # Mask the central and outer part of the chop A images module = PSFpreparationModule(name_in='prep_data', image_in_tag='chopa_fake', image_out_tag='chopa_prep', mask_out_tag=None, norm=False, cent_size=0.3, edge_size=3.) pipeline.add_module(module) # Subtract a PSF model with PCA and median-combine the residuals module = PcaPsfSubtractionModule(pca_numbers=range(1, 51), name_in='pca', images_in_tag='chopa_prep', reference_in_tag='chopa_prep', res_median_tag='chopa_pca', extra_rot=0.0) pipeline.add_module(module) # Datasets can be exported to FITS files by their tag name in the database # Here we will export the median-combined residuals of the PSF subtraction module = FitsWritingModule(name_in='write_result_psfsub', file_name='chopa_pca.fits', output_dir=None, data_tag='chopa_pca', data_range=None, overwrite=True) pipeline.add_module(module) # Run each of the above modules pipeline.run_module('fake') pipeline.run_module('prep_data') pipeline.run_module('pca') pipeline.run_module('write_result_psfsub') PynPoint also includes a module to calculate the detection limits of the final image: .. code-block:: python # Calculate detection limits between 0.8 and 2.0 arcsec # The false positive fraction is fixed to 2.87e-6 (i.e. 5 sigma for Gaussian statistics) module = ContrastCurveModule(name_in='limits', image_in_tag='chopa_center', psf_in_tag='psfa_mask', contrast_out_tag='limits', separation=(0.3, 2., 0.1), angle=(0., 360., 60.), threshold=('fpf', 2.87e-6), psf_scaling=1., aperture=0.15, pca_number=10, cent_size=0.3, edge_size=3., extra_rot=0., residuals='median') pipeline.add_module(module) # And we write the detection limits to a text file header = 'Separation [arcsec] - Contrast [mag] - Variance [mag] - FPF' module = TextWritingModule(name_in='write_result_limits', file_name='contrast_curve.dat', output_dir=None, data_tag='limits', header=header) pipeline.add_module(module) # Run each of the above modules pipeline.run_module('limits') pipeline.run_module('write_result_limits') .. _near_results: Results ------- The images that were exported to a FITS file can be visualized with a tool such as `DS9 `_. We can also use the :class:`~pynpoint.core.pypeline.Pypeline` functionalities to get the data from the database (without having to rerun the pipeline). For example, to get the residuals of the PSF subtraction: .. code-block:: python data = pipeline.get_data('chopa_pca') And to plot the residuals for 10 principal components (Python indexing starts at zero): .. code-block:: python import matplotlib.pyplot as plt plt.imshow(data[9, ], origin='lower') plt.show() .. image:: _static/near_residuals.png :width: 60% :align: center Or to plot the detection limits with the error bars showing the variance of the six azimuthal positions that were tested: .. code-block:: python data = pipeline.get_data('limits') plt.figure(figsize=(7, 4)) plt.errorbar(data[:, 0], data[:, 1], data[:, 2]) plt.xlim(0., 2.5) plt.ylim(12., 0.) plt.xlabel('Separation [arcsec]') plt.ylabel('Contrast [mag]') plt.show() .. image:: _static/near_limits.png :width: 70% :align: center PynPoint-PynPoint-e5d0637/docs/pipeline_modules.rst000066400000000000000000000235011512720201400224210ustar00rootroot00000000000000.. _pipeline_modules: Pipeline modules ================ This page contains a list of all available pipeline modules and a short description of what they are used for. Reading modules import data into the database, writing modules export data from the database, and processing modules run a specific task of the data reduction or analysis. More details on the design of the pipeline can be found in the :ref:`architecture` section. .. note:: All PynPoint classes ending with ``Module`` in their name (e.g. :class:`~pynpoint.readwrite.fitsreading.FitsReadingModule`) are pipeline modules that can be added to an instance of :class:`~pynpoint.core.pypeline.Pypeline` (see :ref:`pypeline` section). .. important:: The pipeline modules with multiprocessing functionalities are indicated with "CPU" in parentheses. The number of parallel processes can be set with the ``CPU`` parameter in the configuration file and the number of images that is simultaneously loaded into the memory with the ``MEMORY`` parameter. Pipeline modules that apply (in parallel) a function to subsets of images use a number of images per subset equal to ``MEMORY`` divided by ``CPU``. .. important:: The pipeline modules that are compatible with both regular imaging and integral field spectroscopy datasets (i.e. 3D and 4D data) are indicated with "IFS" in parentheses. All other modules are only compatible with regular imaging. .. _reading_module: Reading modules --------------- * :class:`~pynpoint.readwrite.fitsreading.FitsReadingModule` (IFS): Import FITS files and relevant header information into the database. * :class:`~pynpoint.readwrite.hdf5reading.Hdf5ReadingModule`: Import datasets and attributes from an HDF5 file (as created by PynPoint). * :class:`~pynpoint.readwrite.attr_reading.AttributeReadingModule`: Import a list of values as dataset attribute. * :class:`~pynpoint.readwrite.attr_reading.ParangReadingModule`: Import a list of parallactic angles as dataset attribute. * :class:`~pynpoint.readwrite.attr_reading.WavelengthReadingModule`: Import a list of calibrated wavelengths as dataset attribute. * :class:`~pynpoint.readwrite.nearreading.NearReadingModule` (CPU): Import VLT/VISIR data for the NEAR experiment. .. _writing_module: Writing modules --------------- * :class:`~pynpoint.readwrite.fitswriting.FitsWritingModule`: Export a dataset from the database to a FITS file. * :class:`~pynpoint.readwrite.hdf5writing.Hdf5WritingModule`: Export part of the database to a new HDF5 file. * :class:`~pynpoint.readwrite.textwriting.TextWritingModule`: Export a dataset to an ASCII file. * :class:`~pynpoint.readwrite.attr_writing.AttributeWritingModule`: Export a list of attribute values to an ASCII file. * :class:`~pynpoint.readwrite.attr_writing.ParangWritingModule`: Export the parallactic angles of a dataset to an ASCII file. .. _processing_module: Processing modules ------------------ Background subtraction ~~~~~~~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.background.SimpleBackgroundSubtractionModule`: Simple background subtraction for dithering datasets. * :class:`~pynpoint.processing.background.MeanBackgroundSubtractionModule`: Mean background subtraction for dithering datasets. * :class:`~pynpoint.processing.background.LineSubtractionModule` (CPU): Subtraction of striped detector artifacts. * :class:`~pynpoint.processing.background.NoddingBackgroundModule`: Background subtraction for nodding datasets. Bad pixel cleaning ~~~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.badpixel.BadPixelSigmaFilterModule` (CPU): Find and replace bad pixels with a sigma filter. * :class:`~pynpoint.processing.badpixel.BadPixelInterpolationModule` (CPU): Interpolate bad pixels with a spectral deconvolution technique. * :class:`~pynpoint.processing.badpixel.BadPixelMapModule`: Create a bad pixel map from dark and flat images. * :class:`~pynpoint.processing.badpixel.BadPixelTimeFilterModule` (CPU): Sigma clipping of bad pixels along the time dimension. * :class:`~pynpoint.processing.badpixel.ReplaceBadPixelsModule` (CPU): Replace bad pixels based on a bad pixel map. Basic processing ~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.basic.SubtractImagesModule`: Subtract two stacks of images. * :class:`~pynpoint.processing.basic.AddImagesModule`: Add two stacks of images * :class:`~pynpoint.processing.basic.RotateImagesModule`: Rotate a stack of images. * :class:`~pynpoint.processing.basic.RepeatImagesModule`: Repeat a stack of images. Centering ~~~~~~~~~ * :class:`~pynpoint.processing.centering.StarAlignmentModule` (CPU): Align the images with a cross-correlation. * :class:`~pynpoint.processing.centering.FitCenterModule` (CPU): Fit the PSF with a 2D Gaussian or Moffat function. * :class:`~pynpoint.processing.centering.ShiftImagesModule`: Shift a stack of images. * :class:`~pynpoint.processing.centering.WaffleCenteringModule` (IFS): Use the waffle spots to center the images. Dark and flat correction ~~~~~~~~~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.darkflat.DarkCalibrationModule`: Dark frame subtraction. * :class:`~pynpoint.processing.darkflat.FlatCalibrationModule`: Flat field correction. Denoising ~~~~~~~~~ * :class:`~pynpoint.processing.timedenoising.WaveletTimeDenoisingModule` (CPU): Wavelet-based denoising in the time domain. * :class:`~pynpoint.processing.timedenoising.TimeNormalizationModule` (CPU): Normalize a stack of images. Detection limits ~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.limits.ContrastCurveModule` (CPU): Compute a contrast curve. * :class:`~pynpoint.processing.limits.MassLimitsModule`: Calculate mass limits from a contrast curve and an isochrones model grid. Extract star ~~~~~~~~~~~~ * :class:`~pynpoint.processing.extract.StarExtractionModule` (CPU): Locate and crop the position of the star. * :class:`~pynpoint.processing.extract.ExtractBinaryModule` (CPU): Extract a PSF which rotates across a stack of images. Filters ~~~~~~~ * :class:`~pynpoint.processing.filter.GaussianFilterModule`: Apply a Gaussian filter to the images. Flux and position ~~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.fluxposition.FakePlanetModule`: Inject an artificial planet in a dataset. * :class:`~pynpoint.processing.fluxposition.SimplexMinimizationModule`: Determine the flux and position with a simplex minimization. * :class:`~pynpoint.processing.fluxposition.FalsePositiveModule`: Compute the signal-to-noise ratio and false positive fraction. * :class:`~pynpoint.processing.fluxposition.MCMCsamplingModule` (CPU): Estimate the flux and position of a planet with MCMC sampling. * :class:`~pynpoint.processing.fluxposition.AperturePhotometryModule` (CPU): Compute the integrated flux at a position. * :class:`~pynpoint.processing.fluxposition.SystematicErrorModule`: Compute the systematic errors on the flux and position. Frame selection ~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.frameselection.RemoveFramesModule`: Remove images by their index number. * :class:`~pynpoint.processing.frameselection.FrameSelectionModule`: Frame selection to remove low-quality image. * :class:`~pynpoint.processing.frameselection.RemoveLastFrameModule`: Remove the last image of a VLT/NACO dataset. * :class:`~pynpoint.processing.frameselection.RemoveStartFramesModule`: Remove images at the beginning of each original data cube. * :class:`~pynpoint.processing.frameselection.ImageStatisticsModule` (CPU): Compute statistics of the pixel values for each image. * :class:`~pynpoint.processing.frameselection.FrameSimilarityModule` (CPU): Compute different similarity measures of a set of images. * :class:`~pynpoint.processing.frameselection.SelectByAttributeModule`: Select images by the ascending/descending attribute values. * :class:`~pynpoint.processing.frameselection.ResidualSelectionModule`: Frame selection on the residuals of the PSF subtraction. Image resizing ~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.resizing.CropImagesModule` (IFS): Crop the images. * :class:`~pynpoint.processing.resizing.ScaleImagesModule` (CPU): Resample the images (spatially and/or in flux). * :class:`~pynpoint.processing.resizing.AddLinesModule`: Add pixel lines on the sides of the images. * :class:`~pynpoint.processing.resizing.RemoveLinesModule`: Remove pixel lines from the sides of the images. PCA background subtraction ~~~~~~~~~~~~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.pcabackground.PCABackgroundPreparationModule`: Preparation for the PCA-based background subtraction. * :class:`~pynpoint.processing.pcabackground.PCABackgroundSubtractionModule`: PCA-based background subtraction. * :class:`~pynpoint.processing.pcabackground.DitheringBackgroundModule`: Wrapper for background subtraction of dithering datasets. PSF preparation ~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.psfpreparation.PSFpreparationModule` (IFS): Mask the images before the PSF subtraction. * :class:`~pynpoint.processing.psfpreparation.AngleInterpolationModule`: Interpolate the parallactic angles between the start and end values. * :class:`~pynpoint.processing.psfpreparation.AngleCalculationModule`: Calculate the parallactic angles. * :class:`~pynpoint.processing.psfpreparation.SortParangModule` (IFS): Sort the images by parallactic angle. * :class:`~pynpoint.processing.psfpreparation.SDIpreparationModule`: Prepare the images for SDI. PSF subtraction ~~~~~~~~~~~~~~~ * :class:`~pynpoint.processing.psfsubtraction.PcaPsfSubtractionModule` (CPU, IFS): PSF subtraction with PCA. * :class:`~pynpoint.processing.psfsubtraction.ClassicalADIModule` (CPU): PSF subtraction with classical ADI. Stacking ~~~~~~~~ * :class:`~pynpoint.processing.stacksubset.StackAndSubsetModule`: Stack and/or select a random subset of the images. * :class:`~pynpoint.processing.stacksubset.StackCubesModule`: Collapse each original data cube separately. * :class:`~pynpoint.processing.stacksubset.DerotateAndStackModule` (IFS): Derotate and/or stack the images. * :class:`~pynpoint.processing.stacksubset.CombineTagsModule`: Combine multiple database tags into a single dataset. PynPoint-PynPoint-e5d0637/docs/pynpoint.core.rst000066400000000000000000000014531512720201400216750ustar00rootroot00000000000000pynpoint.core package ===================== Submodules ---------- pynpoint.core.attributes module ------------------------------- .. automodule:: pynpoint.core.attributes :members: :show-inheritance: :undoc-members: pynpoint.core.dataio module --------------------------- .. automodule:: pynpoint.core.dataio :members: :show-inheritance: :undoc-members: pynpoint.core.processing module ------------------------------- .. automodule:: pynpoint.core.processing :members: :show-inheritance: :undoc-members: pynpoint.core.pypeline module ----------------------------- .. automodule:: pynpoint.core.pypeline :members: :show-inheritance: :undoc-members: Module contents --------------- .. automodule:: pynpoint.core :members: :show-inheritance: :undoc-members: PynPoint-PynPoint-e5d0637/docs/pynpoint.processing.rst000066400000000000000000000057651512720201400231330ustar00rootroot00000000000000pynpoint.processing package =========================== Submodules ---------- pynpoint.processing.background module ------------------------------------- .. automodule:: pynpoint.processing.background :members: :show-inheritance: :undoc-members: pynpoint.processing.badpixel module ----------------------------------- .. automodule:: pynpoint.processing.badpixel :members: :show-inheritance: :undoc-members: pynpoint.processing.basic module -------------------------------- .. automodule:: pynpoint.processing.basic :members: :show-inheritance: :undoc-members: pynpoint.processing.centering module ------------------------------------ .. automodule:: pynpoint.processing.centering :members: :show-inheritance: :undoc-members: pynpoint.processing.darkflat module ----------------------------------- .. automodule:: pynpoint.processing.darkflat :members: :show-inheritance: :undoc-members: pynpoint.processing.extract module ---------------------------------- .. automodule:: pynpoint.processing.extract :members: :show-inheritance: :undoc-members: pynpoint.processing.filter module --------------------------------- .. automodule:: pynpoint.processing.filter :members: :show-inheritance: :undoc-members: pynpoint.processing.fluxposition module --------------------------------------- .. automodule:: pynpoint.processing.fluxposition :members: :show-inheritance: :undoc-members: pynpoint.processing.frameselection module ----------------------------------------- .. automodule:: pynpoint.processing.frameselection :members: :show-inheritance: :undoc-members: pynpoint.processing.limits module --------------------------------- .. automodule:: pynpoint.processing.limits :members: :show-inheritance: :undoc-members: pynpoint.processing.pcabackground module ---------------------------------------- .. automodule:: pynpoint.processing.pcabackground :members: :show-inheritance: :undoc-members: pynpoint.processing.psfpreparation module ----------------------------------------- .. automodule:: pynpoint.processing.psfpreparation :members: :show-inheritance: :undoc-members: pynpoint.processing.psfsubtraction module ----------------------------------------- .. automodule:: pynpoint.processing.psfsubtraction :members: :show-inheritance: :undoc-members: pynpoint.processing.resizing module ----------------------------------- .. automodule:: pynpoint.processing.resizing :members: :show-inheritance: :undoc-members: pynpoint.processing.stacksubset module -------------------------------------- .. automodule:: pynpoint.processing.stacksubset :members: :show-inheritance: :undoc-members: pynpoint.processing.timedenoising module ---------------------------------------- .. automodule:: pynpoint.processing.timedenoising :members: :show-inheritance: :undoc-members: Module contents --------------- .. automodule:: pynpoint.processing :members: :show-inheritance: :undoc-members: PynPoint-PynPoint-e5d0637/docs/pynpoint.readwrite.rst000066400000000000000000000031521512720201400227310ustar00rootroot00000000000000pynpoint.readwrite package ========================== Submodules ---------- pynpoint.readwrite.attr\_reading module --------------------------------------- .. automodule:: pynpoint.readwrite.attr_reading :members: :show-inheritance: :undoc-members: pynpoint.readwrite.attr\_writing module --------------------------------------- .. automodule:: pynpoint.readwrite.attr_writing :members: :show-inheritance: :undoc-members: pynpoint.readwrite.fitsreading module ------------------------------------- .. automodule:: pynpoint.readwrite.fitsreading :members: :show-inheritance: :undoc-members: pynpoint.readwrite.fitswriting module ------------------------------------- .. automodule:: pynpoint.readwrite.fitswriting :members: :show-inheritance: :undoc-members: pynpoint.readwrite.hdf5reading module ------------------------------------- .. automodule:: pynpoint.readwrite.hdf5reading :members: :show-inheritance: :undoc-members: pynpoint.readwrite.hdf5writing module ------------------------------------- .. automodule:: pynpoint.readwrite.hdf5writing :members: :show-inheritance: :undoc-members: pynpoint.readwrite.nearreading module ------------------------------------- .. automodule:: pynpoint.readwrite.nearreading :members: :show-inheritance: :undoc-members: pynpoint.readwrite.textwriting module ------------------------------------- .. automodule:: pynpoint.readwrite.textwriting :members: :show-inheritance: :undoc-members: Module contents --------------- .. automodule:: pynpoint.readwrite :members: :show-inheritance: :undoc-members: PynPoint-PynPoint-e5d0637/docs/pynpoint.rst000066400000000000000000000004441512720201400207450ustar00rootroot00000000000000.. _pynpoint-package: pynpoint package ================ Subpackages ----------- .. toctree:: pynpoint.core pynpoint.readwrite pynpoint.processing pynpoint.util Module contents --------------- .. automodule:: pynpoint :members: :undoc-members: :show-inheritance: PynPoint-PynPoint-e5d0637/docs/pynpoint.util.rst000066400000000000000000000065071512720201400217270ustar00rootroot00000000000000pynpoint.util package ===================== Submodules ---------- pynpoint.util.analysis module ----------------------------- .. automodule:: pynpoint.util.analysis :members: :show-inheritance: :undoc-members: pynpoint.util.apply\_func module -------------------------------- .. automodule:: pynpoint.util.apply_func :members: :show-inheritance: :undoc-members: pynpoint.util.attributes module ------------------------------- .. automodule:: pynpoint.util.attributes :members: :show-inheritance: :undoc-members: pynpoint.util.continuous module ------------------------------- .. automodule:: pynpoint.util.continuous :members: :show-inheritance: :undoc-members: pynpoint.util.image module -------------------------- .. automodule:: pynpoint.util.image :members: :show-inheritance: :undoc-members: pynpoint.util.limits module --------------------------- .. automodule:: pynpoint.util.limits :members: :show-inheritance: :undoc-members: pynpoint.util.mcmc module ------------------------- .. automodule:: pynpoint.util.mcmc :members: :show-inheritance: :undoc-members: pynpoint.util.module module --------------------------- .. automodule:: pynpoint.util.module :members: :show-inheritance: :undoc-members: pynpoint.util.multiline module ------------------------------ .. automodule:: pynpoint.util.multiline :members: :show-inheritance: :undoc-members: pynpoint.util.multipca module ----------------------------- .. automodule:: pynpoint.util.multipca :members: :show-inheritance: :undoc-members: pynpoint.util.multiproc module ------------------------------ .. automodule:: pynpoint.util.multiproc :members: :show-inheritance: :undoc-members: pynpoint.util.multistack module ------------------------------- .. automodule:: pynpoint.util.multistack :members: :show-inheritance: :undoc-members: pynpoint.util.postproc module ----------------------------- .. automodule:: pynpoint.util.postproc :members: :show-inheritance: :undoc-members: pynpoint.util.psf module ------------------------ .. automodule:: pynpoint.util.psf :members: :show-inheritance: :undoc-members: pynpoint.util.remove module --------------------------- .. automodule:: pynpoint.util.remove :members: :show-inheritance: :undoc-members: pynpoint.util.residuals module ------------------------------ .. automodule:: pynpoint.util.residuals :members: :show-inheritance: :undoc-members: pynpoint.util.sdi module ------------------------ .. automodule:: pynpoint.util.sdi :members: :show-inheritance: :undoc-members: pynpoint.util.star module ------------------------- .. automodule:: pynpoint.util.star :members: :show-inheritance: :undoc-members: pynpoint.util.tests module -------------------------- .. automodule:: pynpoint.util.tests :members: :show-inheritance: :undoc-members: pynpoint.util.type\_aliases module ---------------------------------- .. automodule:: pynpoint.util.type_aliases :members: :show-inheritance: :undoc-members: pynpoint.util.wavelets module ----------------------------- .. automodule:: pynpoint.util.wavelets :members: :show-inheritance: :undoc-members: Module contents --------------- .. automodule:: pynpoint.util :members: :show-inheritance: :undoc-members: PynPoint-PynPoint-e5d0637/docs/python.rst000066400000000000000000000036701512720201400204120ustar00rootroot00000000000000.. _python: Python guidelines ================= .. _starting: Getting started --------------- The modular architecture of PynPoint allows for easy implementation of new pipeline modules and we welcome contributions from users. Before writing a new PynPoint module, it is helpful to have a look at the :ref:`architecture` section. In addition, some basic knowledge on Python is required and some understanding on the following items can be helpful: * Python `types `_ such as lists, tuples, and dictionaries. * `Classes `_ and in particular the concept of inheritance. * `Abstract classes `_ as interfaces. .. _conventions: Conventions ----------- Before we start writing a new PynPoint module, please take notice of the following style conventions: * `PEP 8 `_ -- style guide for Python code * We recommend using `pylint `_ and `pycodestyle `_ to analyze newly written code in order to keep PynPoint well structured, readable, and documented. * Names of class member should start with ``m_``. * Images should ideally not be read from and written to the central database at once but in amounts of ``MEMORY``. Unit tests ---------- PynPoint is a robust pipeline package with 95% of the code covered by `unit tests `_. Testing of the package is done by running ``make test`` in the cloned repository. This requires the installation of: * `pytest `_ * `pytest-cov `_ The unit tests ensure that the output from existing functionalities will not change whenever new code. With these things in mind, we are now ready to code! PynPoint-PynPoint-e5d0637/docs/running_pynpoint.rst000066400000000000000000000126341512720201400225110ustar00rootroot00000000000000.. _running_pynpoint: Running PynPoint ================ .. _running_intro: Introduction ------------ The pipeline can be executed with a Python script, in `interactive mode `_, or with a `Jupyter Notebook `_. The main components of PynPoint are the pipeline and the three types of pipeline modules: 1. :class:`~pynpoint.core.pypeline.Pypeline` -- The actual pipeline which capsules a list of pipeline modules. 2. :class:`~pynpoint.core.processing.ReadingModule` -- Module for importing data and relevant header information from FITS, HDF5, or ASCII files into the database. 3. :class:`~pynpoint.core.processing.WritingModule` -- Module for exporting results from the database into FITS, HDF5 or ASCII files. 4. :class:`~pynpoint.core.processing.ProcessingModule` -- Module for processing data with a specific data reduction or analysis recipe. .. _initiating_pypeline: Initiating the Pypeline ----------------------- The pipeline is initiated by creating an instance of :class:`~pynpoint.core.pypeline.Pypeline`: .. code-block:: python pipeline = Pypeline(working_place_in='/path/to/working_place', input_place_in='/path/to/input_place', output_place_in='/path/to/output_place') PynPoint creates an HDF5 database called ``PynPoin_database.hdf5`` in the ``working_place_in`` of the pipeline. This is the central data storage in which the processing results from a :class:`~pynpoint.core.processing.ProcessingModule` are stored. The advantage of the HDF5 format is that reading of data is much faster than from FITS files and it is also possible to quickly read subsets from large datasets. Restoring data from an already existing pipeline database can be done by creating an instance of :class:`~pynpoint.core.pypeline.Pypeline` with the ``working_place_in`` pointing to the path of the ``PynPoint_database.hdf5`` file. .. _running_modules: Running pipeline modules ------------------------ Input data is read into the central database with a :class:`~pynpoint.core.processing.ReadingModule`. By default, PynPoint will read data from the ``input_place_in`` but setting a manual folder is possible to read data to separate database tags (e.g., dark frames, flat fields, and science data). For example, to read the images from FITS files that are located in the default input place: .. code-block:: python module = FitsReadingModule(name_in='read', input_dir=None, image_tag='science') pipeline.add_module(module) The images from the FITS files are stored in the database as a dataset with a unique tag. This tag can be used by other pipeline module to read the data for further processing. The parallactic angles can be read from a text or FITS file and are attached as attribute to a dataset: .. code-block:: python module = ParangReadingModule(name_in='parang', data_tag='science' file_name='parang.dat', input_dir=None) pipeline.add_module(module) Finally, we run all pipeline modules: .. code-block:: python pipeline.run() Alternatively, it is also possible to run each pipeline module individually by their ``name_in`` value: .. code-block:: python pipeline.run_module('read') pipeline.run_module('parang') .. important:: Some pipeline modules require pixel coordinates for certain arguments. Throughout PynPoint, pixel coordinates are zero-indexed, meaning that (x, y) = (0, 0) corresponds to the center of the pixel in the bottom-left corner of the image. This means that there is an offset of -1 in both directions with respect to the pixel coordinates of DS9, for which the center of the bottom-left pixel is (x, y) = (1, 1). .. _hdf5_files: HDF5 database ------------- There are several ways to access the datasets in the HDF5 database that is used by PynPoint: * The :class:`~pynpoint.readwrite.fitswriting.FitsWritingModule` exports a dataset from the database into a FITS file. * Several methods of the :class:`~pynpoint.core.pypeline.Pypeline` class help to easily retrieve data and attributes from the database. For example: * To read a dataset: .. code-block:: python pipeline.get_data('tag_name') * To read an attribute of a dataset: .. code-block:: python pipeline.get_attribute('tag_name', 'attr_name') * The `h5py `_ Python package can be used to access the HDF5 file directly. * There are external tools available such as `HDFCompass `_ or `HDFView `_ to read, inspect, and visualize data and attributes. HDFCompass is easy to use and has a basic plotting functionality. In HDFCompass, the static PynPoint attributes can be opened with the *Reopen as HDF5 Attributes* option. .. _data_attributes: Dataset attributes ------------------ Apart from using :meth:`~pynpoint.core.pypeline.Pypeline.get_attribute`, it is also possible to print and return all attributes of a dataset with the :meth:`~pynpoint.core.pypeline.Pypeline.list_attributes` method of :class:`~pynpoint.core.pypeline.Pypeline`: .. code-block:: python attr_dict = pipeline.list_attributes('tag_name') The method returns a dictionary that contains both the static and non-static attributes. PynPoint-PynPoint-e5d0637/docs/tutorials.rst000066400000000000000000000012211512720201400211050ustar00rootroot00000000000000.. _tutorials: Tutorials ========= Curious to see an example with a more detailed workflow? There are several Jupyter notebooks with tutorials available: .. toctree:: :hidden: tutorials/first_example.ipynb tutorials/zimpol_adi.ipynb * :ref:`First example: PSF subtraction with PCA ` (:download:`download notebook `) * :ref:`Non-coronagraphic angular differential imaging ` (:download:`download notebook `) The notebooks can also be viewed on `Github `_. PynPoint-PynPoint-e5d0637/docs/tutorials/000077500000000000000000000000001512720201400203575ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/docs/tutorials/first_example.ipynb000066400000000000000000002374411512720201400242770ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# First example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this first example, we will run the PSF subtraction on a preprocessed ADI dataset of $\\beta$ Pictoris. This archival dataset was obtained with NACO in $M'$ (4.8 $\\mu$m) at the Very Large Telescope (ESO program ID: [090.C-0653(D)](http://archive.eso.org/wdb/wdb/eso/sched_rep_arc/query?progid=090.C-0653(D))). The exposure time per image was 65 ms and the parallactic rotation was about 50 degrees. Every 200 images have been mean-collapsed to limit the size of the dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting started" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We start by importing the required Python modules for this tutorial." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import os\n", "import urllib\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And also the pipeline and pipeline modules of PynPoint." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from pynpoint import Pypeline, Hdf5ReadingModule, PSFpreparationModule, PcaPsfSubtractionModule" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we download the preprocessed data (13 MB). The dataset is stored in an HDF5 database and contains 263 images of 80 by 80 pixels. The parallactic angles and pixel scale are stored as attributes of the dataset." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('./betapic_naco_mp.hdf5', )" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "urllib.request.urlretrieve('https://home.strw.leidenuniv.nl/~stolker/pynpoint/betapic_naco_mp.hdf5',\n", " './betapic_naco_mp.hdf5')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initiating the Pypeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will now initiate PynPoint by creating an instance of the [Pypeline](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.pypeline.Pypeline) class. The object requires the paths of the working folder, input folder and output folder. Here we simply use the current folder for all three of them." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "===============\n", "PynPoint v0.9.0\n", "===============\n", "\n", "Working folder: ./\n", "Input folder: ./\n", "Output folder: ./\n", "\n", "Database: ./PynPoint_database.hdf5\n", "Configuration: ./PynPoint_config.ini\n", "\n", "Number of CPUs: 8\n", "Number of threads: not set\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/Users/tomasstolker/applications/pynpoint/pynpoint/core/pypeline.py:286: UserWarning: Configuration file not found. Creating PynPoint_config.ini with default values in the working place.\n", " warnings.warn('Configuration file not found. Creating PynPoint_config.ini with '\n" ] } ], "source": [ "pipeline = Pypeline(working_place_in='./',\n", " input_place_in='./',\n", " output_place_in='./')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A configuration file with default values has been created in the working folder. Next, we will add three pipeline modules to the `Pypeline` object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PSF subtraction with PCA" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We start with the [Hdf5ReadingModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.readwrite.html#pynpoint.readwrite.hdf5reading.Hdf5ReadingModule) which will import the preprocessed data from the HDF5 file that was downloaded into the current database. The instance of the `Hdf5ReadingModule` class is added to the `Pypeline` with the [add_module](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=add_module#pynpoint.core.pypeline.Pypeline.add_module) method. The dataset that we need to import has the tag *stack* so we specify this name as input and output in the dictionary of `tag_dictionary`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "module = Hdf5ReadingModule(name_in='read',\n", " input_filename='betapic_naco_mp.hdf5',\n", " input_dir=None,\n", " tag_dictionary={'stack': 'stack'})\n", "\n", "pipeline.add_module(module)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we ise the [PSFpreparationModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html?highlight=psfprep#pynpoint.processing.psfpreparation.PSFpreparationModule) to mask the central (saturated) area of the PSF and also pixels beyond 1.1 arcseconds." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "module = PSFpreparationModule(name_in='prep',\n", " image_in_tag='stack',\n", " image_out_tag='prep',\n", " mask_out_tag=None,\n", " norm=False,\n", " resize=None,\n", " cent_size=0.15,\n", " edge_size=1.1)\n", "\n", "pipeline.add_module(module)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The last pipeline module that we use is [PcaPsfSubtractionModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html?highlight=pcapsf#pynpoint.processing.psfsubtraction.PcaPsfSubtractionModule). This module will run the PSF subtraction with PCA. Here we chose to subtract 20 principal components and store the median-collapsed residuals at the database tag *residuals*." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "module = PcaPsfSubtractionModule(pca_numbers=[20, ],\n", " name_in='pca',\n", " images_in_tag='prep',\n", " reference_in_tag='prep',\n", " res_median_tag='residuals')\n", "\n", "pipeline.add_module(module)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now run the three pipeline modules that were added toe the `Pypeline` with the [run](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.pypeline.Pypeline.run) method." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------\n", "Hdf5ReadingModule\n", "-----------------\n", "\n", "Module name: read\n", "Reading HDF5 file... [DONE] \n", "Output port: stack (263, 80, 80)\n", "\n", "--------------------\n", "PSFpreparationModule\n", "--------------------\n", "\n", "Module name: prep\n", "Input port: stack (263, 80, 80)\n", "Preparing images for PSF subtraction... [DONE] \n", "Output port: prep (263, 80, 80)\n", "\n", "-----------------------\n", "PcaPsfSubtractionModule\n", "-----------------------\n", "\n", "Module name: pca\n", "Input port: prep (263, 80, 80)\n", "Input parameters:\n", " - Post-processing type: ADI\n", " - Number of principal components: [20]\n", " - Subtract mean: True\n", " - Extra rotation (deg): 0.0\n", "Constructing PSF model... [DONE]\n", "Creating residuals. [DONE]\n", "Output port: residuals (1, 80, 80)\n" ] } ], "source": [ "pipeline.run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Accessing results in the database" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `Pypeline` has [several methods](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.pypeline.Pypeline) to access the datasets and attributes that are stored in the database. For example, we can use the [get_shape](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.dataio.InputPort.get_shape) method to check the shape of the *residuals* dataset that was stored by the `PcaPsfSubtractionModule`. The dataset contains 1 image since we ran the PSF subtraction only with 20 principal components." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(1, 80, 80)" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pipeline.get_shape('residuals')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we use the [get_data](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.pypeline.Pypeline.get_data) method to read the median-collapsed residuals of the PSF subtraction." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "residuals = pipeline.get_data('residuals')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will also extract the pixel scale, which is stored as the `PIXSCALE` attribute of the dataset, by using the [get_attribute](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=Pypeline#pynpoint.core.pypeline.Pypeline.get_attribute) method." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pixel scale = 27.0 mas\n" ] } ], "source": [ "pixscale = pipeline.get_attribute('residuals', 'PIXSCALE')\n", "print(f'Pixel scale = {pixscale*1e3} mas')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting the residuals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, let's have a look at the residuals of the PSF subtraction. For simplicity, we define the image size in arcseconds." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "size = pixscale * residuals.shape[-1]/2." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And plot the first image of the *residuals* dataset with `matplotlib`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.imshow(residuals[0, ], origin='lower', extent=[size, -size, -size, size])\n", "plt.xlabel('RA offset (arcsec)', fontsize=14)\n", "plt.ylabel('Dec offset (arcsec)', fontsize=14)\n", "cb = plt.colorbar()\n", "cb.set_label('Flux (ADU)', size=14.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The star is located in the center of the image (which is masked here) and the orientation of the image is such that north is up and east is left. The bright yellow feature in southwest direction is the exoplanet $\\beta$ Pictoris b at an angular separation of 0.46 arcseconds." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" } }, "nbformat": 4, "nbformat_minor": 4 } PynPoint-PynPoint-e5d0637/docs/tutorials/zimpol_adi.ipynb000066400000000000000000005616631512720201400235720ustar00rootroot00000000000000{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Non-coronagraphic angular differential imaging" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial, we will process and analyze an archival [SPHERE/ZIMPOL](https://www.eso.org/sci/facilities/paranal/instruments/sphere/inst.html) dataset of [HD 142527](https://ui.adsabs.harvard.edu/abs/2019A%26A...622A.156C/abstract) that was obtained with the narrowband H$\\alpha$ filter (*N_Ha*) and without coronagraph. A few ZIMPOL specific preprocessing steps were already done so we start the processing of the data with the bad pixel cleaning and image registration. There are pipeline modules available for dual-band simultaneous differential imaging (i.e. [SDIpreparationModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.psfpreparation.SDIpreparationModule) and [SubtractImagesModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.basic.SubtractImagesModule)) but for simplicity we only use the *N_Ha* data in this tutorial in combination with angular differential imaging." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Getting started" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's start by importing the required Python modules. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import configparser\n", "import tarfile\n", "import urllib.request\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from matplotlib.colors import LogNorm\n", "from matplotlib.patches import Circle" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And also the pipeline and required modules of PynPoint." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from pynpoint import Pypeline, FitsReadingModule, ParangReadingModule, \\\n", " StarExtractionModule, BadPixelSigmaFilterModule, \\\n", " StarAlignmentModule, FitCenterModule, ShiftImagesModule, \\\n", " PSFpreparationModule, PcaPsfSubtractionModule, \\\n", " FalsePositiveModule, SimplexMinimizationModule, \\\n", " FakePlanetModule, ContrastCurveModule, \\\n", " FitsWritingModule, TextWritingModule" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will download a tarball with the preprocessed images and the parallactic angles." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('hd142527_zimpol_h-alpha.tgz', )" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "urllib.request.urlretrieve('https://home.strw.leidenuniv.nl/~stolker/pynpoint/hd142527_zimpol_h-alpha.tgz',\n", " 'hd142527_zimpol_h-alpha.tgz')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Will unpack the compressed archive file in a folder called *input*." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "tar = tarfile.open('hd142527_zimpol_h-alpha.tgz')\n", "tar.extractall(path='input')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Creating the configuration file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "PynPoint requires a configuration file with the global settings and the FITS header keywords that have to be imported. The text file should be named `PynPoint_config.ini` (see [documentation](https://pynpoint.readthedocs.io/en/latest/configuration.html) for several instrument specific examples) and located in the working place of the pipeline.\n", "\n", "In this case, we don't need any of the header data but we set the pixel scale to 3.6 mas pixel$^{-1}$ with the `PIXSCALE` keyword. We also set the `MEMORY` keyword to `None` such that that all images of a dataset are loaded at once in the RAM when the data is processed by a certain pipeline module. The number of processes that is used by pipeline modules that support multiprocessing (see [overview of pipeline modules](https://pynpoint.readthedocs.io/en/latest/overview.html)) is set with the `CPU` keyword." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "config = configparser.ConfigParser()\n", "config.add_section('header')\n", "config.add_section('settings')\n", "config['settings']['PIXSCALE'] = '0.0036'\n", "config['settings']['MEMORY'] = 'None'\n", "config['settings']['CPU'] = '1'\n", "\n", "with open('PynPoint_config.ini', 'w') as configfile:\n", " config.write(configfile)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initiating the Pypeline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now initiate the `Pypeline` by setting the working, input, and output folders. The configuration file will be read and the HDF5 database is created in the working place since it does not yet exist." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "===============\n", "PynPoint v0.9.0\n", "===============\n", "\n", "Working place: ./\n", "Input place: input/\n", "Output place: ./\n", "\n", "Database: ./PynPoint_database.hdf5\n", "Configuration: ./PynPoint_config.ini\n", "\n", "Number of CPUs: 1\n", "Number of threads: not set\n" ] } ], "source": [ "pipeline = Pypeline(working_place_in='./',\n", " input_place_in='input/',\n", " output_place_in='./')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some routines by libraries such as `numpy` and `scipy` use multithreading. The number of threads can be set beforehand from the command line with the `OMP_NUM_THREADS` environment variable (e.g. `export OMP_NUM_THREADS=4`). This is in particular important if a pipeline module also uses multiprocessing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing the images and parallactic angles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will now import the images from the FITS files into the PynPoint database. This is done by first adding an instance of the [FitsReadingModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.readwrite.html#pynpoint.readwrite.fitsreading.FitsReadingModule) to the `Pypeline` with the `add_module` method and then running the module with the `run_module` method. The data is stored in the database with the name of the `image_tag` argument." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------\n", "FitsReadingModule\n", "-----------------\n", "\n", "Module name: read\n", "Reading FITS files... [DONE] \n", "Output ports: zimpol (70, 1024, 1024), fits_header/cal_OBS091_0235_cam2.fits (868,), fits_header/cal_OBS091_0237_cam2.fits (868,), fits_header/cal_OBS091_0239_cam2.fits (868,), fits_header/cal_OBS091_0241_cam2.fits (868,), fits_header/cal_OBS091_0243_cam2.fits (868,), fits_header/cal_OBS091_0245_cam2.fits (868,), fits_header/cal_OBS091_0247_cam2.fits (868,)\n" ] } ], "source": [ "module = FitsReadingModule(name_in='read',\n", " input_dir=None,\n", " image_tag='zimpol',\n", " overwrite=True,\n", " check=False,\n", " filenames=None,\n", " ifs_data=False)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('read')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's check the shape of the imported dataset. There are 70 images of 1024 by 1024 pixels." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(70, 1024, 1024)" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pipeline.get_shape('zimpol')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will also import the parallactic angles from a plain text file (a FITS file would also work) by using the [ParangReadingModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.readwrite.html#pynpoint.readwrite.attr_reading.ParangReadingModule). The angles will be stored as the `PARANG` attribute to the dataset that was previously imported and has the database tag *zimpol*." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------\n", "ParangReadingModule\n", "-------------------\n", "\n", "Module name: parang\n", "Reading parallactic angles... [DONE]\n", "Number of angles: 70\n", "Rotation range: -14.31 - 34.36 deg\n", "Output port: zimpol (70, 1024, 1024)\n" ] } ], "source": [ "module = ParangReadingModule(name_in='parang',\n", " data_tag='zimpol',\n", " file_name='parang.dat',\n", " input_dir=None,\n", " overwrite=True)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('parang')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The attributes can be read from the database with the [get_attribute](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=get_data#pynpoint.core.pypeline.Pypeline.get_attribute) method of the `Pypeline`. Let's have a look at the values of `PARANG`." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([-14.3082 , -13.9496 , -13.5902 , -13.23 , -12.8691 , -12.5074 ,\n", " -12.145 , -11.782 , -11.4182 , -11.0538 , -6.43366, -6.0622 ,\n", " -5.69037, -5.3182 , -4.9457 , -4.57291, -4.19983, -3.8265 ,\n", " -3.45294, -3.07917, 1.61837, 1.99275, 2.36701, 2.74112,\n", " 3.11507, 3.48882, 3.86237, 4.23567, 4.60872, 4.98149,\n", " 9.6373 , 10.0041 , 10.3703 , 10.736 , 11.101 , 11.4653 ,\n", " 11.829 , 12.192 , 12.5543 , 12.9158 , 17.3717 , 17.7218 ,\n", " 18.0711 , 18.4193 , 18.7666 , 19.1129 , 19.4581 , 19.8024 ,\n", " 20.1456 , 20.4878 , 24.9167 , 25.243 , 25.568 , 25.8919 ,\n", " 26.2145 , 26.536 , 26.8563 , 27.1753 , 27.4931 , 27.8097 ,\n", " 31.7057 , 32.0052 , 32.3035 , 32.6005 , 32.8962 , 33.1906 ,\n", " 33.4838 , 33.7757 , 34.0663 , 34.3556 ])" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pipeline.get_attribute('zimpol', 'PARANG', static=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bad pixel correction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first processing module that we will use is the [BadPixelSigmaFilterModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.badpixel.BadPixelSigmaFilterModule) to correct bad pixels with a sigma filter. We replace outliers that deviate by more than 3$\\sigma$ from their neighboring pixels and iterate three times. \n", "\n", "The input port of `image_in_tag` points to the dataset that was imported by the `FitsReadingModule` and the output port of `image_out_tag` stores the processed / cleaned dataset in the database." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------------\n", "BadPixelSigmaFilterModule\n", "-------------------------\n", "\n", "Module name: badpixel\n", "Input port: zimpol (70, 1024, 1024)\n", "Bad pixel sigma filter... [DONE] \n", "Output port: bad (70, 1024, 1024)\n" ] } ], "source": [ "module = BadPixelSigmaFilterModule(name_in='badpixel',\n", " image_in_tag='zimpol',\n", " image_out_tag='bad',\n", " map_out_tag=None,\n", " box=9,\n", " sigma=3.,\n", " iterate=3)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('badpixel')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Image centering" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will crop the image around the brightest pixel at the position of the star with the [StarExtractionModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.extract.StarExtractionModule)." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "--------------------\n", "StarExtractionModule\n", "--------------------\n", "\n", "Module name: extract\n", "Input port: bad (70, 1024, 1024)\n", "Extracting stellar position... [DONE] \n", "Output port: crop (70, 57, 57)\n" ] } ], "source": [ "module = StarExtractionModule(name_in='extract',\n", " image_in_tag='bad',\n", " image_out_tag='crop',\n", " index_out_tag=None,\n", " image_size=0.2,\n", " fwhm_star=0.03,\n", " position=(476, 436, 0.1))\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('extract')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at the first image from the processed data that is now centered with pixel precision. The data van be read from the database by using the [get_data](https://pynpoint.readthedocs.io/en/latest/pynpoint.core.html?highlight=get_data#pynpoint.core.pypeline.Pypeline.get_data) method of the `Pypeline`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('crop')\n", "plt.imshow(data[0, ], origin='lower')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After the approximate centering, we apply a relative alignment of the images with the [StarAlignmentModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.centering.StarAlignmentModule) by cross-correlating each images with 10 randomly selected images from the dataset. Each image is then shifted to the average offset from the cross-correlation with the 10 images." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------\n", "StarAlignmentModule\n", "-------------------\n", "\n", "Module name: align\n", "Input port: crop (70, 57, 57)\n", "Aligning images... [DONE] \n", "Output port: aligned (70, 57, 57)\n" ] } ], "source": [ "module = StarAlignmentModule(name_in='align',\n", " image_in_tag='crop',\n", " ref_image_in_tag=None,\n", " image_out_tag='aligned',\n", " interpolation='spline',\n", " accuracy=10,\n", " resize=None,\n", " num_references=10,\n", " subframe=0.1)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('align')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a third centering step, we use the [FitCenterModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.centering.FitCenterModule) to fit the PSF of the mean image with a 2D Moffat function. The best-fit parameters are stored in the database at the argument name of `fit_out_tag`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "---------------\n", "FitCenterModule\n", "---------------\n", "\n", "Module name: center\n", "Input port: aligned (70, 57, 57)\n", "Fitting the stellar PSF... [DONE]\n", "Output port: fit (70, 16)\n" ] } ], "source": [ "module = FitCenterModule(name_in='center',\n", " image_in_tag='aligned',\n", " fit_out_tag='fit',\n", " mask_out_tag=None,\n", " method='mean',\n", " radius=0.1,\n", " sign='positive',\n", " model='moffat',\n", " filter_size=None,\n", " guess=(0., 0., 10., 10., 10000., 0., 0., 1.))\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('center')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The processed images from the `StarAlignmentModule` and the best-fit parameters from the `FitCenterModule` are now used as input for [ShiftImagesModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.centering.ShiftImagesModule). This module shifts all images by the (constant) offset such that the peak of the Moffat function is located in the center of the image." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------\n", "ShiftImagesModule\n", "-----------------\n", "\n", "Module name: shift\n", "Input ports: aligned (70, 57, 57), fit (70, 16)\n", "Shifting the images... [DONE] \n", "Output port: centered (70, 57, 57)\n" ] } ], "source": [ "module = ShiftImagesModule(name_in='shift',\n", " image_in_tag='aligned',\n", " image_out_tag='centered',\n", " shift_xy='fit',\n", " interpolation='spline')\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('shift')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at the central part of the first image. The brightest pixel of the PSF is indeed in the center of the image as expected." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(17.0, 40.0)" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('centered')\n", "plt.imshow(data[0, ], origin='lower')\n", "plt.xlim(17, 40)\n", "plt.ylim(17, 40)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Masking the images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before running the PSF subtraction, we use the [PSFpreparationModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.psfpreparation.PSFpreparationModule) to mask the central part of the PSF and we also create a outer mask with a diameter equal to the field of view of the image. The latter is achieved by simply setting the argument of `edge_size` to a value that is larger than the field of view." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "--------------------\n", "PSFpreparationModule\n", "--------------------\n", "\n", "Module name: prep1\n", "Input port: centered (70, 57, 57)\n", "Preparing images for PSF subtraction... [DONE] \n", "Output port: prep (70, 57, 57)\n" ] } ], "source": [ "module = PSFpreparationModule(name_in='prep1',\n", " image_in_tag='centered',\n", " image_out_tag='prep',\n", " mask_out_tag=None,\n", " norm=False,\n", " cent_size=0.02,\n", " edge_size=0.2)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('prep1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at the first image and show it on a logarithmic color scale." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('prep')\n", "max_flux = np.amax(data[0, ])\n", "plt.imshow(data[0, ], origin='lower', norm=LogNorm(vmin=0.01*max_flux, vmax=max_flux))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Later on, we require a PSF template for both the relative calibration and the estimation of detection limits. Therefore, we create another masked dataset from the centered images but this time we only mask pixels beyond 70 mas and do not use a central mask." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "--------------------\n", "PSFpreparationModule\n", "--------------------\n", "\n", "Module name: prep2\n", "Input port: centered (70, 57, 57)\n", "Preparing images for PSF subtraction... [DONE] \n", "Output port: psf (70, 57, 57)\n" ] } ], "source": [ "module = PSFpreparationModule(name_in='prep2',\n", " image_in_tag='centered',\n", " image_out_tag='psf',\n", " mask_out_tag=None,\n", " norm=False,\n", " cent_size=None,\n", " edge_size=0.07)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('prep2')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look at the first image from this stack of PSF templates." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('psf')\n", "max_flux = np.amax(data[0, ])\n", "plt.imshow(data[0, ], origin='lower', norm=LogNorm(vmin=0.01*max_flux, vmax=max_flux))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## PSF subtraction with PCA" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After masking the images, we will now run the PSF subtraction with an implementation of full-frame PCA. We use the [PcaPsfSubtractionModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.psfsubtraction.PcaPsfSubtractionModule) and set the argument of `pca_numbers` to a range from 1 to 30 principal components. This means that the mean- and median-collapsed residuals that are stored with the output ports to `res_mean_tag` and `res_median_tag` will contain 30 images, so with an increasing number of subtracted principal components. We will also store the PCA basis (i.e. the principal components) and apply an extra rotation of -133 deg such that north will be aligned with the positive *y* axis." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------------\n", "PcaPsfSubtractionModule\n", "-----------------------\n", "\n", "Module name: pca\n", "Input port: prep (70, 57, 57)\n", "Input parameters:\n", " - Post-processing type: ADI\n", " - Number of principal components: range(1, 31)\n", " - Subtract mean: True\n", " - Extra rotation (deg): -133.0\n", "Constructing PSF model... [DONE]\n", "Output ports: pca_mean (30, 57, 57), pca_median (30, 57, 57), pca_basis (30, 57, 57)\n" ] } ], "source": [ "module = PcaPsfSubtractionModule(name_in='pca',\n", " images_in_tag='prep',\n", " reference_in_tag='prep',\n", " res_mean_tag='pca_mean',\n", " res_median_tag='pca_median',\n", " basis_out_tag='pca_basis',\n", " pca_numbers=range(1, 31),\n", " extra_rot=-133.,\n", " subtract_mean=True,\n", " processing_type='ADI')\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('pca')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look and the median-collapsed residuals after subtracting 15 principal components. The H$\\alpha$ emission from the accreting M dwarf companion HD 142527 B is clearly detected east / left of the central star." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('pca_median')\n", "plt.imshow(data[14, ], origin='lower')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's also have a look at the PCA basis that was stored at the *pca_basis* tag. Here we plot the second principal component." ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('pca_basis')\n", "plt.imshow(data[1, ], origin='lower')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Signal-to-noise and false positive fraction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have the residuals of the PSF subtraction, we can calculate the signal-to-noise ratio (S/N) and false positive fraction (FPF) of the detected signal as function of number of principal components that have been subtracted.\n", "\n", "To do so, we will first check at which pixel coordinates the aperture should be placed such that it encompasses most of the companion flux while excluding most of the (negative) self-subtraction regions. We will read the median-collapsed residuals with the `get_data` method." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "data = pipeline.get_data('pca_median')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we use the functionalities of `matplotlib` to overlay an aperture on the residuals after subtracting 15 principal components." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig, ax = plt.subplots()\n", "ax.imshow(data[14, ], origin='lower')\n", "aperture = Circle((11, 26), radius=5, fill=False, ls=':', lw=2., color='white')\n", "ax.add_artist(aperture)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we use the [FalsePositiveModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.fluxposition.FalsePositiveModule) to calculate both the S/N and FPF. We set position of the `aperture` to the coordinates that we tested and the radius of the aperture to 5 pixels. For the reference apertures, we will ignore the neighboring apertures to the companion (i.e. `ignore=True`) such that the self-subtraction regions will not bias the noise estimate." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------\n", "FalsePositiveModule\n", "-------------------\n", "\n", "Module name: snr\n", "Input port: pca_median (30, 57, 57)\n", "Input parameters:\n", " - Aperture position = (11.0, 26.0)\n", " - Aperture radius (pixels) = 5.00\n", " - Optimize aperture position = False\n", " - Ignore neighboring apertures = True\n", " - Minimization tolerance = 0.01\n", "Calculating the S/N and FPF...\n", "Image 001/30 -> (x, y) = (11.00, 26.00), S/N = 5.54, FPF = 7.28e-04\n", "Image 002/30 -> (x, y) = (11.00, 26.00), S/N = 4.85, FPF = 1.43e-03\n", "Image 003/30 -> (x, y) = (11.00, 26.00), S/N = 5.75, FPF = 6.01e-04\n", "Image 004/30 -> (x, y) = (11.00, 26.00), S/N = 7.43, FPF = 1.53e-04\n", "Image 005/30 -> (x, y) = (11.00, 26.00), S/N = 10.16, FPF = 2.64e-05\n", "Image 006/30 -> (x, y) = (11.00, 26.00), S/N = 8.92, FPF = 5.54e-05\n", "Image 007/30 -> (x, y) = (11.00, 26.00), S/N = 8.35, FPF = 8.02e-05\n", "Image 008/30 -> (x, y) = (11.00, 26.00), S/N = 5.59, FPF = 6.99e-04\n", "Image 009/30 -> (x, y) = (11.00, 26.00), S/N = 7.81, FPF = 1.16e-04\n", "Image 010/30 -> (x, y) = (11.00, 26.00), S/N = 6.46, FPF = 3.25e-04\n", "Image 011/30 -> (x, y) = (11.00, 26.00), S/N = 7.34, FPF = 1.63e-04\n", "Image 012/30 -> (x, y) = (11.00, 26.00), S/N = 7.17, FPF = 1.86e-04\n", "Image 013/30 -> (x, y) = (11.00, 26.00), S/N = 6.97, FPF = 2.16e-04\n", "Image 014/30 -> (x, y) = (11.00, 26.00), S/N = 6.32, FPF = 3.66e-04\n", "Image 015/30 -> (x, y) = (11.00, 26.00), S/N = 8.25, FPF = 8.55e-05\n", "Image 016/30 -> (x, y) = (11.00, 26.00), S/N = 9.85, FPF = 3.16e-05\n", "Image 017/30 -> (x, y) = (11.00, 26.00), S/N = 9.98, FPF = 2.94e-05\n", "Image 018/30 -> (x, y) = (11.00, 26.00), S/N = 8.71, FPF = 6.31e-05\n", "Image 019/30 -> (x, y) = (11.00, 26.00), S/N = 11.85, FPF = 1.09e-05\n", "Image 020/30 -> (x, y) = (11.00, 26.00), S/N = 9.01, FPF = 5.23e-05\n", "Image 021/30 -> (x, y) = (11.00, 26.00), S/N = 6.85, FPF = 2.37e-04\n", "Image 022/30 -> (x, y) = (11.00, 26.00), S/N = 6.41, FPF = 3.39e-04\n", "Image 023/30 -> (x, y) = (11.00, 26.00), S/N = 7.57, FPF = 1.38e-04\n", "Image 024/30 -> (x, y) = (11.00, 26.00), S/N = 6.88, FPF = 2.33e-04\n", "Image 025/30 -> (x, y) = (11.00, 26.00), S/N = 5.45, FPF = 7.91e-04\n", "Image 026/30 -> (x, y) = (11.00, 26.00), S/N = 5.32, FPF = 8.99e-04\n", "Image 027/30 -> (x, y) = (11.00, 26.00), S/N = 4.38, FPF = 2.33e-03\n", "Image 028/30 -> (x, y) = (11.00, 26.00), S/N = 3.06, FPF = 1.11e-02\n", "Image 029/30 -> (x, y) = (11.00, 26.00), S/N = 4.45, FPF = 2.18e-03\n", "Image 030/30 -> (x, y) = (11.00, 26.00), S/N = 4.89, FPF = 1.37e-03\n", "Output port: snr (30, 6)\n" ] } ], "source": [ "module = FalsePositiveModule(name_in='snr',\n", " image_in_tag='pca_median',\n", " snr_out_tag='snr',\n", " position=(11., 26.),\n", " aperture=5.*0.0036,\n", " ignore=True,\n", " optimize=False)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('snr')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The results have been stored in the dataset with the tag *snr*. Let's plot the S/N as function of principal components that have been extracted. As expected, for a large number of components the S/N goes towards zero due to increased self-subtraction." ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Signal-to-noise ratio')" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('snr')\n", "plt.plot(range(1, 31), data[:, 4], 'o')\n", "plt.xlabel('Principal components', fontsize=14)\n", "plt.ylabel('Signal-to-noise ratio', fontsize=14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Relative photometric and astrometric calibration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With the next analysis, we will measure the relative brightness and position of the companion. We will use the [SimplexMinimizationModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.fluxposition.SimplexMinimizationModule) to minimize the flux within a large aperture at the position of the companion while iterative injecting negative copies of the PSF. This procedure will be repeated for principal components in the range of 1 to 10. We need to specify two database tags as input, namely the stack of centered images and the PSF templates (i.e. the stack of masked images) that will be injected to remove the companion flux. Apart from an approximate position of the companion, the downhill simplex method of the minimization algorithm also requires an estimate (e.g. within ${\\sim} 1$ magnitude from the actual value) of the flux contrast." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------------\n", "SimplexMinimizationModule\n", "-------------------------\n", "\n", "Module name: simplex\n", "Input ports: centered (70, 57, 57), psf (70, 57, 57)\n", "Input parameters:\n", " - Number of principal components = range(1, 11)\n", " - Figure of merit = gaussian\n", " - Residuals type = median\n", " - Absolute tolerance (pixels/mag) = 0.01\n", " - Maximum offset = None\n", " - Guessed position (x, y) = (11.00, 26.00)\n", " - Aperture position (x, y) = (11, 26)\n", " - Aperture radius (pixels) = 10\n", " - Inner mask radius (pixels) = 5\n", " - Outer mask radius (pixels) = 55\n", "Image center (y, x) = (28.0, 28.0)\n", "Simplex minimization... 1 PC - chi^2 = 3.64e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (12.91, 26.00)\n", " - Separation (mas) = 54.81\n", " - Position angle (deg) = 97.56\n", " - Contrast (mag) = 5.69\n", "Simplex minimization... 2 PC - chi^2 = 1.87e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (13.49, 26.75)\n", " - Separation (mas) = 52.42\n", " - Position angle (deg) = 94.92\n", " - Contrast (mag) = 5.45\n", "Simplex minimization... 3 PC - chi^2 = 2.88e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (13.31, 26.66)\n", " - Separation (mas) = 53.12\n", " - Position angle (deg) = 95.23\n", " - Contrast (mag) = 5.49\n", "Simplex minimization... 4 PC - chi^2 = 4.44e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (13.13, 26.27)\n", " - Separation (mas) = 53.89\n", " - Position angle (deg) = 96.62\n", " - Contrast (mag) = 5.55\n", "Simplex minimization... 5 PC - chi^2 = 3.00e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (12.76, 26.43)\n", " - Separation (mas) = 55.16\n", " - Position angle (deg) = 95.88\n", " - Contrast (mag) = 5.63\n", "Simplex minimization... 6 PC - chi^2 = 2.78e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (12.60, 26.44)\n", " - Separation (mas) = 55.71\n", " - Position angle (deg) = 95.80\n", " - Contrast (mag) = 5.62\n", "Simplex minimization... 7 PC - chi^2 = 3.61e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (12.02, 26.26)\n", " - Separation (mas) = 57.87\n", " - Position angle (deg) = 96.22\n", " - Contrast (mag) = 5.82\n", "Simplex minimization... 8 PC - chi^2 = 4.30e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (12.21, 26.25)\n", " - Separation (mas) = 57.17\n", " - Position angle (deg) = 96.32\n", " - Contrast (mag) = 5.73\n", "Simplex minimization... 9 PC - chi^2 = 2.96e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (11.33, 26.18)\n", " - Separation (mas) = 60.37\n", " - Position angle (deg) = 96.22\n", " - Contrast (mag) = 5.98\n", "Simplex minimization... 10 PC - chi^2 = 2.97e+02 [DONE]\n", "Best-fit parameters:\n", " - Position (x, y) = (11.59, 26.26)\n", " - Separation (mas) = 59.42\n", " - Position angle (deg) = 96.07\n", " - Contrast (mag) = 5.82\n", "Output ports: simplex001 (89, 57, 57), fluxpos001 (89, 6), simplex002 (70, 57, 57), fluxpos002 (70, 6), simplex003 (75, 57, 57), fluxpos003 (75, 6), simplex004 (73, 57, 57), fluxpos004 (73, 6), simplex005 (63, 57, 57), fluxpos005 (63, 6), simplex006 (79, 57, 57), fluxpos006 (79, 6), simplex007 (71, 57, 57), fluxpos007 (71, 6), simplex008 (66, 57, 57), fluxpos008 (66, 6), simplex009 (60, 57, 57), fluxpos009 (60, 6), simplex010 (78, 57, 57), fluxpos010 (78, 6)\n" ] } ], "source": [ "module = SimplexMinimizationModule(name_in='simplex',\n", " image_in_tag='centered',\n", " psf_in_tag='psf',\n", " res_out_tag='simplex',\n", " flux_position_tag='fluxpos',\n", " position=(11, 26),\n", " magnitude=6.,\n", " psf_scaling=-1.,\n", " merit='gaussian',\n", " aperture=10.*0.0036,\n", " sigma=0.,\n", " tolerance=0.01,\n", " pca_number=range(1, 11),\n", " cent_size=0.02,\n", " edge_size=0.2,\n", " extra_rot=-133.,\n", " residuals='median',\n", " reference_in_tag=None,\n", " offset=None)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('simplex')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When running the `SimplexMinimizationModule`, we see the $\\chi^2$ value changing until the tolerance threshold has been reached. The best-fit position and contrast is then printed and also stored in the database at the `flux_position_tag`. If the argument of `pca_number` is a list or range (instead of a single value), then the names of the `flux_position_tag` and `res_out_tag` are appended with the number of principal components in 3 digits (e.g. 003 for 3 principal components).\n", "\n", "The `res_out_tag` contains the PSF subtraction residuals for each iteration so the last image in the dataset shows the best-fit result. Let's have a look at the residuals after subtracting 10 principal components with the best-fit negative PSF injected (i.e. which has fully cancelled the companion flux)." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "data = pipeline.get_data('simplex010')\n", "plt.imshow(data[-1, ], origin='lower')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's also plot the measured separation, position angle, and contrast as function of principal components that have been subtracted." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Text(0, 0.5, 'Contrast (mag)')" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure(figsize=(8, 8))\n", "ax1 = fig.add_subplot(3, 1, 1)\n", "ax2 = fig.add_subplot(3, 1, 2)\n", "ax3 = fig.add_subplot(3, 1, 3)\n", "\n", "for i in range(1, 11):\n", " data = pipeline.get_data(f'fluxpos{i:03d}')\n", " ax1.scatter(i, data[-1, 2], color='black')\n", " ax2.scatter(i, data[-1, 3], color='black')\n", " ax3.scatter(i, data[-1, 4], color='black')\n", "\n", "ax3.set_xlabel('Principal components', fontsize=14)\n", "ax1.set_ylabel('Separation (arcsec)', fontsize=14)\n", "ax2.set_ylabel('Position angle (deg)', fontsize=14)\n", "ax3.set_ylabel('Contrast (mag)', fontsize=14)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Detection limits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a final analysis, we will estimate detection limits from the data. To do so, we will first use the [FakePlanetModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.fluxposition.FakePlanetModule) to remove the flux of the companion from the data since it would otherwise bias the result. We use the PSF template that was stored with the tag *psf* and we adopt the separation and position angle that was determined with the `SimplexMinimizationModule`. We need to apply a correction of -133 degrees which was used previously for `extra_rot`." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "----------------\n", "FakePlanetModule\n", "----------------\n", "\n", "Module name: fake\n", "Input ports: centered (70, 57, 57), psf (70, 57, 57)\n", "Input parameters:\n", " - Magnitude = 6.10\n", " - PSF scaling = -1.0\n", " - Separation (arcsec) = 0.06\n", " - Position angle (deg) = 0.06\n", "Injecting artificial planets... [DONE] \n", "Output port: removed (70, 57, 57)\n" ] } ], "source": [ "module = FakePlanetModule(name_in='fake',\n", " image_in_tag='centered',\n", " psf_in_tag='psf',\n", " image_out_tag='removed',\n", " position=(0.061, 97.3-133.),\n", " magnitude=6.1,\n", " psf_scaling=-1.,\n", " interpolation='spline')\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('fake')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that the data only contains the flux of the central star, we use the [ContrastCurveModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.processing.html#pynpoint.processing.limits.ContrastCurveModule) to calculate the detection limits. We will calculate the brightness limits by setting the false positive fraction (FPF) to $2.87 \\times 10^{-7}$, which corresponds to $5\\sigma$ in the limit of Gaussian noise. At small angular separations, the detection limits are affected by small sample statistics (see [Mawet et al. 2014](https://ui.adsabs.harvard.edu/abs/2014ApJ...792...97M/abstract)) so this FPF would only correspond to a $5\\sigma$ detection at large separation from the star. In this example, we will subtract 10 principal components and use the median-collapsed residuals." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-------------------\n", "ContrastCurveModule\n", "-------------------\n", "\n", "Module name: limits\n", "Input ports: removed (70, 57, 57), psf (70, 57, 57)\n", " \n", "Calculating detection limits... [DONE]\n", "Output port: limits (4, 4)\n" ] } ], "source": [ "module = ContrastCurveModule(name_in='limits',\n", " image_in_tag='removed',\n", " psf_in_tag='psf',\n", " contrast_out_tag='limits',\n", " separation=(0.05, 5., 0.01),\n", " angle=(0., 360., 60.),\n", " threshold=('fpf', 2.87e-7),\n", " psf_scaling=1.,\n", " aperture=0.02,\n", " pca_number=10,\n", " cent_size=0.02,\n", " edge_size=2.,\n", " extra_rot=-133.,\n", " residuals='median',\n", " snr_inject=100.)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('limits')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exporting datasets to FITS and plain text formats" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have finished the data processing and analysis, we will export some of the results from the HDF5 database to other data formats. Since astronomical images are commonly viewed with tools such as [DS9](https://sites.google.com/cfa.harvard.edu/saoimageds9), we will use the [FitsWritingModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.readwrite.html?highlight=fitswr#pynpoint.readwrite.fitswriting.FitsWritingModule) to write the median-collapsed residuals of the PSF subtraction to a FITS file. The database tag is specified as argument of `data_tag` and we will store the FITS file in the default output place of the `Pypeline`. The FITS file contains a 3D dataset of which the first dimension corresponds to an increasing number of subtracted principal components." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------\n", "FitsWritingModule\n", "-----------------\n", "\n", "Module name: write1\n", "Input port: pca_median (30, 57, 57)\n", "Writing FITS file... [DONE]\n" ] } ], "source": [ "module = FitsWritingModule(name_in='write1',\n", " data_tag='pca_median',\n", " file_name='pca_median.fits',\n", " output_dir=None,\n", " data_range=None,\n", " overwrite=True,\n", " subset_size=None)\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('write1')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, we can export 1D and 2D datasets to a plain text file with the [TextWritingModule](https://pynpoint.readthedocs.io/en/latest/pynpoint.readwrite.html#pynpoint.readwrite.textwriting.TextWritingModule). Let's export the detection limits that were estimated with the `ContrastCurveModule`. We specify again the database tag and also add a header as first line in the text file." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "-----------------\n", "TextWritingModule\n", "-----------------\n", "\n", "Module name: write2\n", "Input port: limits (4, 4)\n", "Writing text file... [DONE]\n" ] } ], "source": [ "module = TextWritingModule(name_in='write2',\n", " data_tag='limits',\n", " file_name='limits.dat',\n", " output_dir=None,\n", " header='Separation (arcsec) - Contrast (mag) - Variance (mag) - FPF')\n", "\n", "pipeline.add_module(module)\n", "pipeline.run_module('write2')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.9" } }, "nbformat": 4, "nbformat_minor": 4 } PynPoint-PynPoint-e5d0637/pynpoint/000077500000000000000000000000001512720201400172615ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/__init__.py000066400000000000000000000060501512720201400213730ustar00rootroot00000000000000from pynpoint.core.pypeline import Pypeline from pynpoint.processing.background import ( SimpleBackgroundSubtractionModule, MeanBackgroundSubtractionModule, LineSubtractionModule, NoddingBackgroundModule, ) from pynpoint.processing.badpixel import ( BadPixelSigmaFilterModule, BadPixelInterpolationModule, BadPixelMapModule, BadPixelTimeFilterModule, ReplaceBadPixelsModule, ) from pynpoint.processing.basic import ( SubtractImagesModule, AddImagesModule, RotateImagesModule, RepeatImagesModule, ) from pynpoint.processing.centering import ( StarAlignmentModule, FitCenterModule, ShiftImagesModule, WaffleCenteringModule, ) from pynpoint.processing.darkflat import DarkCalibrationModule, FlatCalibrationModule from pynpoint.processing.extract import StarExtractionModule, ExtractBinaryModule from pynpoint.processing.filter import GaussianFilterModule from pynpoint.processing.fluxposition import ( FakePlanetModule, SimplexMinimizationModule, FalsePositiveModule, MCMCsamplingModule, AperturePhotometryModule, SystematicErrorModule, ) from pynpoint.processing.frameselection import ( RemoveFramesModule, FrameSelectionModule, RemoveLastFrameModule, RemoveStartFramesModule, ImageStatisticsModule, FrameSimilarityModule, SelectByAttributeModule, ResidualSelectionModule, ) from pynpoint.processing.limits import ContrastCurveModule, MassLimitsModule from pynpoint.processing.pcabackground import ( PCABackgroundPreparationModule, PCABackgroundSubtractionModule, DitheringBackgroundModule, ) from pynpoint.processing.psfpreparation import ( PSFpreparationModule, AngleInterpolationModule, AngleCalculationModule, SortParangModule, SDIpreparationModule, ) from pynpoint.processing.psfsubtraction import ( PcaPsfSubtractionModule, ClassicalADIModule, ) from pynpoint.processing.resizing import ( CropImagesModule, ScaleImagesModule, AddLinesModule, RemoveLinesModule, ) from pynpoint.processing.stacksubset import ( StackAndSubsetModule, StackCubesModule, DerotateAndStackModule, CombineTagsModule, ) from pynpoint.processing.timedenoising import ( CwtWaveletConfiguration, DwtWaveletConfiguration, WaveletTimeDenoisingModule, TimeNormalizationModule, ) from pynpoint.readwrite.attr_reading import ( AttributeReadingModule, ParangReadingModule, WavelengthReadingModule, ) from pynpoint.readwrite.attr_writing import AttributeWritingModule, ParangWritingModule from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.readwrite.fitswriting import FitsWritingModule from pynpoint.readwrite.hdf5reading import Hdf5ReadingModule from pynpoint.readwrite.hdf5writing import Hdf5WritingModule from pynpoint.readwrite.textwriting import TextWritingModule from pynpoint.readwrite.nearreading import NearReadingModule __author__ = "Tomas Stolker & Markus Bonse" __license__ = "GPLv3" __maintainer__ = "Tomas Stolker" __email__ = "stolker@strw.leidenuniv.nl" PynPoint-PynPoint-e5d0637/pynpoint/core/000077500000000000000000000000001512720201400202115ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/core/__init__.py000066400000000000000000000000001512720201400223100ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/core/attributes.py000066400000000000000000000077671512720201400227720ustar00rootroot00000000000000""" Module to obtain information about the implemented attributes. """ from typing import Dict, Union from typeguard import typechecked @typechecked def get_attributes() -> Dict[str, Dict[str, Union[str, float, int, None]]]: """ Function to get a dictionary with all attributes. Returns ------- dict Attribute information. """ attr = { "PIXSCALE": { "attribute": "static", "config": "settings", "value": 0.027, "type": "float", }, "MEMORY": { "attribute": "static", "config": "settings", "value": 1000, "type": "int", }, "CPU": {"attribute": "static", "config": "settings", "value": 1, "type": "int"}, "INSTRUMENT": { "attribute": "static", "config": "header", "value": "INSTRUME", "type": "str", }, "NFRAMES": { "attribute": "non-static", "config": "header", "value": "NAXIS3", "type": "int", }, "EXP_NO": { "attribute": "non-static", "config": "header", "value": "ESO DET EXP NO", "type": "int", }, "DIT": { "attribute": "static", "config": "header", "value": "ESO DET DIT", "type": "int", }, "NDIT": { "attribute": "non-static", "config": "header", "value": "ESO DET NDIT", "type": "int", }, "PARANG_START": { "attribute": "non-static", "config": "header", "value": "ESO ADA POSANG", "type": "float", }, "PARANG_END": { "attribute": "non-static", "config": "header", "value": "ESO ADA POSANG END", "type": "float", }, "DITHER_X": { "attribute": "non-static", "config": "header", "value": "ESO SEQ CUMOFFSETX", "type": "float", }, "DITHER_Y": { "attribute": "non-static", "config": "header", "value": "ESO SEQ CUMOFFSETY", "type": "float", }, "PUPIL": { "attribute": "non-static", "config": "header", "value": "ESO ADA PUPILPOS", "type": "float", }, "DATE": { "attribute": "non-static", "config": "header", "value": "DATE-OBS", "type": "str", }, "LATITUDE": { "attribute": "static", "config": "header", "value": "ESO TEL GEOLAT", "type": "float", }, "LONGITUDE": { "attribute": "static", "config": "header", "value": "ESO TEL GEOLON", "type": "float", }, "RA": { "attribute": "non-static", "config": "header", "value": "RA", "type": "float", }, "DEC": { "attribute": "non-static", "config": "header", "value": "DEC", "type": "float", }, "PARANG": { "attribute": "non-static", "config": "header", "value": "None", "type": "float", }, "WAVELENGTH": { "attribute": "non-static", "config": "header", "value": "None", "type": "float", }, "STAR_POSITION": { "attribute": "non-static", "config": None, "value": None, "type": "float", }, "INDEX": { "attribute": "non-static", "config": None, "value": None, "type": "int", }, "FILES": { "attribute": "non-static", "config": None, "value": None, "type": "str", }, } return attr PynPoint-PynPoint-e5d0637/pynpoint/core/dataio.py000066400000000000000000001355751512720201400220440ustar00rootroot00000000000000""" Modules for accessing data and attributes in the central database. """ import os import warnings from abc import ABCMeta, abstractmethod from typing import Dict, List, Optional, Tuple, Union import h5py import numpy as np from typeguard import typechecked from pynpoint.util.type_aliases import NonStaticAttribute, StaticAttribute class DataStorage: """ Instances of DataStorage manage to open and close the Pypeline HDF5 databases. They have an internal h5py data bank (self.m_data_bank) which gives direct access to the data if the storage is open (self.m_open == True). """ @typechecked def __init__(self, location_in: str) -> None: """ Constructor of a DataStorage instance. It needs the location of the HDF5 file (Pypeline database) as input. If the file already exists it is opened and extended, if not a new File will be created. Parameters ---------- location_in : str Location (directory + filename) of the HDF5 database. Returns ------- NoneType None """ assert os.path.isdir(os.path.split(location_in)[0]), ( "Input directory for DataStorage " "does not exist - input requested:" " %s." % location_in ) self._m_location = location_in self.m_data_bank = None self.m_open = False @typechecked def open_connection(self) -> None: """ Opens the connection to the HDF5 file by opening an old file or creating a new one. Returns ------- NoneType None """ if not self.m_open: self.m_data_bank = h5py.File(self._m_location, mode="a") self.m_open = True @typechecked def close_connection(self) -> None: """ Closes the connection to the HDF5 file. All entries of the data bank will be stored on the hard drive and the memory is cleaned. Returns ------- NoneType None """ if self.m_open: self.m_data_bank.close() self.m_data_bank = None self.m_open = False class Port(metaclass=ABCMeta): """ Abstract interface and implementation of common functionality of the InputPort, OutputPort, and ConfigPort. Each Port has a internal tag which is its key to a dataset in the DataStorage. If for example data is stored under the entry ``im_arr`` in the central data storage only a port with the tag (``self._m_tag = im_arr``) can access and change that data. A port knows exactly one DataStorage instance, whether it is active or not (``self._m_data_base_active``). """ @abstractmethod @typechecked def __init__(self, tag: str, data_storage_in: Optional[DataStorage] = None) -> None: """ Abstract constructor of a Port. As input the tag / key is expected which is needed to build the connection to the database entry with the same tag / key. It is possible to give the Port a DataStorage. If this storage is not given the Pypeline module has to set it or the connection needs to be added manually using :func:`~pynpoint.core.dataio.Port.set_database_connection`. Parameters ---------- tag : str Input Tag. data_storage_in : pynpoint.core.dataio.DataStorage The data storage to which the port is connected. Returns ------- NoneType None """ assert isinstance(tag, str), "Port tag needs to be a string." self._m_tag = tag self._m_data_storage = data_storage_in self._m_data_base_active = False @property @typechecked def tag(self) -> str: """ Getter for the internal tag (no setter). Returns ------- str Database tag name. """ return self._m_tag @typechecked def open_port(self) -> None: """ Opens the connection to the :class:`~pynpoint.core.dataio.DataStorage` and activates its data bank. Returns ------- NoneType None """ if not self._m_data_base_active: self._m_data_storage.open_connection() self._m_data_base_active = True @typechecked def close_port(self) -> None: """ Closes the connection to the :class:`~pynpoint.core.dataio.DataStorage` and forces it to save the data to the hard drive. All data that was accessed using the port is cleaned from the memory. Returns ------- NoneType None """ if self._m_data_base_active: self._m_data_storage.close_connection() self._m_data_base_active = False @typechecked def set_database_connection(self, data_base_in: DataStorage) -> None: """ Sets the internal DataStorage instance. Parameters ---------- data_base_in: pynpoint.core.dataio.DataStorage The input DataStorage. Returns ------- NoneType None """ self._m_data_storage = data_base_in class ConfigPort(Port): """ ConfigPort can be used to read the 'config' tag from a (HDF5) database. This tag contains the central settings used by PynPoint, as well as the relevant FITS header keywords. You can use a ConfigPort instance to access a single attribute of the dataset using get_attribute(). """ @typechecked def __init__(self, tag: str, data_storage_in: Optional[DataStorage] = None) -> None: """ Constructor of the ConfigPort class which creates the config port instance which can read the settings stored in the central database under the tag `config`. An instance of the ConfigPort is created in the constructor of PypelineModule such that the attributes in the ConfigPort can be accessed from within all type of modules. For example: .. code-block:: python memory = self._m_config_port.get_attribute('MEMORY') Parameters ---------- tag : str The tag name of the port. The port can be used to get data from the dataset with the key `config`. data_storage_in : pynpoint.core.dataio.DataStorage The input DataStorage. It is possible to give the constructor of an ConfigPort a DataStorage instance which will link the port to that DataStorage. Usually the DataStorage is set later by calling :func:`~pynpoint.core.dataio.Port.set_database_connection`. Returns ------- NoneType None """ super().__init__(tag, data_storage_in) if tag != "config": raise ValueError( "The tag name of the central configuration should be 'config'." ) @typechecked def _check_status_and_activate(self) -> bool: """ Internal function which checks if the ConfigPort is ready to use and open it. Returns ------- bool Returns True if the ConfigPort can be used, False if not. """ if self._m_data_storage is None: warnings.warn( "ConfigPort can not load data unless a database is connected." ) status = False else: if not self._m_data_base_active: self.open_port() status = True return status @typechecked def _check_if_data_exists(self) -> bool: """ Internal function which checks if data exists for the 'config' tag. Returns ------- bool Returns True if data exists, False if not. """ return "config" in self._m_data_storage.m_data_bank @typechecked def _check_error_cases(self) -> bool: """' Internal function which checks the error cases. """ if not self._check_status_and_activate(): status = False elif not self._check_if_data_exists(): warnings.warn("No data under the tag which is linked by the ConfigPort.") status = False else: status = True return status @typechecked def get_attribute(self, name: str) -> Optional[StaticAttribute]: """ Returns a static attribute which is connected to the dataset of the ConfigPort. Parameters ---------- name : str The name of the attribute. Returns ------- str, float, or int The attribute value. Returns None if the attribute does not exist. """ if not self._check_error_cases(): attr_val = None elif name in self._m_data_storage.m_data_bank["config"].attrs: attr_val = self._m_data_storage.m_data_bank["config"].attrs[name] else: warnings.warn(f"The attribute '{name}' was not found.") attr_val = None # Convert numpy types to base types (e.g., np.float64 -> float) if isinstance(attr_val, np.generic): attr_val = attr_val.item() return attr_val class InputPort(Port): """ InputPorts can be used to read datasets with a specific tag from the HDF5 database. This type of port can be used to access: * A complete dataset using the get_all() method. * A single attribute of the dataset using get_attribute(). * All attributes of the dataset using get_all_static_attributes() and get_all_non_static_attributes(). * A part of a dataset using slicing. For example: .. code-block:: python in_port = InputPort('tag') data = in_port[0, :, :] # returns the first 2D image of a 3D image stack. (More information about how 1D, 2D, and 3D data is organized can be found in the documentation of OutputPort (:func:`~pynpoint.core.dataio.OutputPort.append` and :func:`~pynpoint.core.dataio.OutputPort.set_all`) InputPorts can load two types of attributes which give additional information about a dataset the port is linked to: * Static attributes: contain global information about a dataset which is not changing through a dataset in the database (e.g. the instrument name or pixel scale). * Non-static attributes: contain information which changes for different parts of the dataset (e.g. the parallactic angles or dithering positions). """ @typechecked def __init__(self, tag: str, data_storage_in: Optional[DataStorage] = None) -> None: """ Constructor of InputPort. An input port can read data from the central database under the key `tag`. Instances of InputPort should not be created manually inside a PypelineModule but should be created with the add_input_port() function. Parameters ---------- tag : str The tag of the port. The port can be used in order to get data from the dataset with the key `tag`. data_storage_in : pynpoint.core.dataio.DataStorage It is possible to give the constructor of an InputPort a DataStorage instance which will link the port to that DataStorage. Usually the DataStorage is set later by calling :func:`~pynpoint.core.dataio.Port.set_database_connection`. Returns ------- NoneType None """ super().__init__(tag, data_storage_in) if tag == "config": raise ValueError( "The tag name 'config' is reserved for the central configuration " "of PynPoint." ) if tag == "fits_header": raise ValueError( "The tag name 'fits_header' is reserved for storage of the FITS " "headers." ) @typechecked def _check_status_and_activate(self) -> bool: """ Internal function which checks if the InputPort is ready to use and open it. Returns ------- bool Returns True if the InputPort can be used, False if not. """ if self._m_data_storage is None: warnings.warn("InputPort can not load data unless a database is connected.") status = False else: status = True if not self._m_data_base_active: self.open_port() return status @typechecked def _check_if_data_exists(self) -> bool: """ Internal function which checks if data exists for the Port specific tag. Returns ------- bool Returns True if data exists, False if not. """ return self._m_tag in self._m_data_storage.m_data_bank @typechecked def _check_error_cases(self) -> bool: if not self._check_status_and_activate(): status = False elif not self._check_if_data_exists(): warnings.warn("No data under the tag which is linked by the InputPort.") status = False else: status = True return status @typechecked def __getitem__( self, item: Union[slice, int, tuple] ) -> Optional[Union[StaticAttribute, NonStaticAttribute]]: """ Internal function which handles the data access using slicing. See class documentation for a example (:class:`~pynpoint.core.dataio.InputPort`). None if the data does not exist. Parameters ---------- item : tuple Slicing parameter. Returns ------- StaticAttribute, NonStaticAttribute, None The selected data. Returns None if no data exists under the tag of thePort. """ if not self._check_error_cases(): data = None else: data = self._m_data_storage.m_data_bank[self._m_tag][item] return data @typechecked def get_shape(self) -> Optional[Tuple[int, ...]]: """ Returns the shape of the dataset the port is linked to. This can be useful if you need the shape without loading the whole data. Returns ------- tuple(int), None Shape of the dataset. Returns None if the dataset does not exist. """ if self._check_error_cases(): self.open_port() data_shape = self._m_data_storage.m_data_bank[self._m_tag].shape else: data_shape = None return data_shape @typechecked def get_ndim(self) -> Optional[int]: """ Returns the number of dimensions of the dataset the port is linked to. Returns ------- int Number of dimensions of the dataset. Returns None if the dataset does not exist. """ if self._check_error_cases(): self.open_port() ndim = self._m_data_storage.m_data_bank[self._m_tag].ndim else: ndim = None return ndim @typechecked def get_all(self) -> Optional[np.ndarray]: """ Returns the whole dataset stored in the data bank under the tag of the Port. Be careful using this function for loading large datasets. The data type is inferred from the data with numpy.asarray. A 32 bit array will be returned in case the input data is a combination of float32 and float64 arrays. Returns ------- np.ndarray The full dataset. Returns None if the data does not exist. """ if self._check_error_cases(): data = np.asarray(self._m_data_storage.m_data_bank[self._m_tag][...]) else: data = None return data @typechecked def get_attribute( self, name: str ) -> Optional[Union[StaticAttribute, NonStaticAttribute]]: """ Returns an attribute which is connected to the dataset of the port. The function can return static and non-static attributes (static attributes have priority). More information about static and non-static attributes can be found in the class documentation of :class:`~pynpoint.core.dataio.InputPort`. Parameters ---------- name : str The name of the attribute. Returns ------- StaticAttribute, NonStaticAttribute, None The attribute value. Returns None if the attribute does not exist. """ if self._check_error_cases(): if name in self._m_data_storage.m_data_bank[self._m_tag].attrs: # static attribute attr_val = self._m_data_storage.m_data_bank[self._m_tag].attrs[name] elif ( "header_" + self._m_tag + "/" + name in self._m_data_storage.m_data_bank ): # non-static attribute attribute = "header_" + self._m_tag + "/" + name attr_val = np.asarray(self._m_data_storage.m_data_bank[attribute][...]) else: warnings.warn(f"The attribute '{name}' was not found.") attr_val = None else: attr_val = None # Convert numpy types to base types (e.g., np.float64 -> float) if isinstance(attr_val, np.generic): attr_val = attr_val.item() return attr_val @typechecked def get_all_static_attributes(self) -> Optional[Dict[str, StaticAttribute]]: """ Get all static attributes of the dataset which are linked to the Port tag. Returns ------- dict, None Dictionary of all attributes, as `{attr_name:attr_value}`. """ if self._check_error_cases(): attr_dict = dict(self._m_data_storage.m_data_bank[self._m_tag].attrs) else: attr_dict = None return attr_dict @typechecked def get_all_non_static_attributes(self) -> Optional[List[str]]: """ Returns a list of all non-static attribute keys. More information about static and non-static attributes can be found in the class documentation of :class:`~pynpoint.core.dataio.InputPort`. Returns ------- list(str, ), None List of all existing non-static attribute keys. """ if self._check_error_cases(): attr_key = [] if "header_" + self._m_tag + "/" in self._m_data_storage.m_data_bank: for key in self._m_data_storage.m_data_bank[ "header_" + self._m_tag + "/" ]: attr_key.append(key) else: attr_key = None else: attr_key = None return attr_key class OutputPort(Port): """ Output ports can be used to save results under a given tag to the HDF5 DataStorage. An instance of OutputPort with self.tag=`tag` can store data under the key `tag` by using one of the following methods: * set_all(...) - replaces and sets the whole dataset * append(...) - appends data to the existing data set. For more information see function documentation (:func:`~pynpoint.core.dataio.OutputPort.append`). * slicing - sets a part of the actual dataset. Example: .. code-block:: python out_port = OutputPort('Some_tag') data = np.ones(200, 200) # 2D image filled with ones out_port[0,:,:] = data # Sets the first 2D image of a 3D image stack * add_attribute(...) - modifies or creates a attribute of the dataset * del_attribute(...) - deletes a attribute * del_all_attributes(...) - deletes all attributes * append_attribute_data(...) - appends information to non-static attributes. See add_attribute() (:func:`~pynpoint.core.dataio.OutputPort.add_attribute`) for more information about static and non-static attributes. * check_static_attribute(...) - checks if a static attribute exists and if it is equal to a given value * other functions listed below For more information about how data is organized inside the central database have a look at the function documentation of the function :func:`~pynpoint.core.dataio.OutputPort.set_all` and :func:`~pynpoint.core.dataio.OutputPort.append`. Furthermore it is possible to deactivate a OutputPort to stop him saving data. """ @typechecked def __init__( self, tag: str, data_storage_in: Optional[DataStorage] = None, activate_init: bool = True, ) -> None: """ Constructor of the OutputPort class which creates an output port instance which can write data to the the central database under the tag `tag`. If you write a PypelineModule you should not create instances manually! Use the add_output_port() function instead. Parameters ---------- tag : str The tag of the port. The port can be used in order to write data to the dataset with the key = `tag`. data_storage_in : pynpoint.core.dataio.DataStorage It is possible to give the constructor of an OutputPort a DataStorage instance which will link the port to that DataStorage. Usually the DataStorage is set later by calling :func:`~pynpoint.core.dataio.Port.set_database_connection`. Returns ------- NoneType None """ super().__init__(tag, data_storage_in) self.m_activate = activate_init if tag == "config": raise ValueError( "The tag name 'config' is reserved for the central configuration " "of PynPoint." ) if tag == "fits_header": raise ValueError( "The tag name 'fits_header' is reserved for storage of the FITS " "headers." ) @typechecked def _check_status_and_activate(self) -> bool: """ Internal function which checks if the OutputPort is ready to use and open it. Returns ------- :return: Returns True if the OutputPort can be used, False if not. :rtype: bool """ if not self.m_activate: status = False elif self._m_data_storage is None: warnings.warn( "OutputPort can not store data unless a database is connected." ) status = False else: if not self._m_data_base_active: self.open_port() status = True return status @typechecked def _init_dataset( self, first_data: Union[np.ndarray, list], tag: str, data_dim: Optional[int] = None, ) -> None: """ Internal function which is used to initialize a dataset in the HDF5 database. Parameters ---------- first_data : np.ndarray, list The initial data. tag : str Database tag. data_dim : int, None Number of dimensions. The dimensions of ``first_data`` is used if set to ``None``. Returns ------- NoneType None """ @typechecked def _ndim_check(data_dim: int, first_dim: int) -> None: if first_dim > 5 or first_dim < 1: raise ValueError( "Output port can only save numpy arrays from 1D to 5D. Use Port " "attributes to save as int, float, or string." ) if data_dim > 5 or data_dim < 1: raise ValueError("The data dimensions should be 1D, 2D, 3D, 4D, or 5D.") if data_dim < first_dim: raise ValueError( "The dimensions of the data should be equal to or larger than the " "dimensions of the input data." ) if data_dim == 3 and first_dim == 1: raise ValueError("Cannot initialize 1D data in 3D data container.") first_data = np.asarray(first_data) if data_dim is None: data_dim = first_data.ndim _ndim_check(data_dim, first_data.ndim) if data_dim == first_data.ndim: if first_data.ndim == 1: # 1D data_shape = (None,) elif first_data.ndim == 2: # 2D data_shape = (None, first_data.shape[1]) elif first_data.ndim == 3: # 3D data_shape = (None, first_data.shape[1], first_data.shape[2]) elif first_data.ndim == 4: # 4D data_shape = ( first_data.shape[0], None, first_data.shape[2], first_data.shape[3], ) elif first_data.ndim == 5: # 5D data_shape = ( first_data.shape[0], first_data.shape[1], first_data.shape[2], first_data.shape[3], first_data.shape[4], ) else: if data_dim == 2: # 1D -> 2D data_shape = (None, first_data.shape[0]) first_data = first_data[np.newaxis, :] elif data_dim == 3: # 2D -> 3D data_shape = (None, first_data.shape[0], first_data.shape[1]) first_data = first_data[np.newaxis, :, :] elif data_dim == 4: # 3D -> 4D data_shape = ( first_data.shape[0], None, first_data.shape[1], first_data.shape[2], ) first_data = first_data[:, np.newaxis, :, :] if first_data.size == 0: warnings.warn( f"The new dataset that is stored under the tag name '{tag}' is empty." ) else: if isinstance(first_data[0], str): first_data = np.array(first_data, dtype="|S") self._m_data_storage.m_data_bank.create_dataset( tag, data=first_data, maxshape=data_shape ) @typechecked def _set_all_key( self, tag: str, data: np.ndarray, data_dim: Optional[int] = None, keep_attributes: bool = False, ) -> None: """ Internal function which sets the values of a dataset under the *tag* name in the database. If old data exists it will be overwritten. This function is used by :func:`~pynpoint.core.dataio.OutputPort.set_all` and for setting non-static attributes. Parameters ---------- tag : str Database tag of the data that will be modified. data : np.ndarray The data that will be stored and replace any old data. data_dim : int Number of dimension of the data. keep_attributes : bool Keep all static attributes of the dataset if set to True. Non-static attributes will be kept anyway so not needed for setting non-static attributes. Returns ------- NoneType None """ tmp_attributes = {} # check if database entry is new... if tag in self._m_data_storage.m_data_bank: # NO -> database entry exists if keep_attributes: # we have to copy all attributes since deepcopy is not supported for key, value in self._m_data_storage.m_data_bank[tag].attrs.items(): tmp_attributes[key] = value # remove database entry del self._m_data_storage.m_data_bank[tag] # make new database entry self._init_dataset(data, tag, data_dim=data_dim) if keep_attributes: for key, value in tmp_attributes.items(): self._m_data_storage.m_data_bank[tag].attrs[key] = value @typechecked def _append_key( self, tag: str, data: Union[np.ndarray, list], data_dim: Optional[int] = None, force: bool = False, ) -> None: """ Internal function for appending data to a dataset or appending non-static attributes. See :func:`~pynpoint.core.dataio.OutputPort.append` for more information. Parameters ---------- tag : str Database tag where the data will be stored. data : np.ndarray The data that will be appended. data_dim : int Number of dimension of the data. force : bool The existing data will be overwritten if shape or type does not match. Returns ------- NoneType None """ # check if database entry is new... if tag not in self._m_data_storage.m_data_bank: # YES -> database entry is new self._init_dataset(data, tag, data_dim=data_dim) return None # NO -> database entry exists # check if the existing data has the same dim and datatype tmp_shape = self._m_data_storage.m_data_bank[tag].shape tmp_dim = len(tmp_shape) if data_dim is None: data_dim = tmp_dim # convert input data to numpy array data = np.asarray(data) # if the dimension offset is 1 add that dimension (e.g. save 2D image in 3D image stack) if data.ndim + 1 == data_dim: if data_dim == 2: data = data[np.newaxis, :] elif data_dim == 3: data = data[np.newaxis, :, :] elif data_dim == 4: data = data[:, np.newaxis, :, :] @typechecked def _type_check() -> bool: check_result = False if tmp_dim == data.ndim: if tmp_dim == 1: check_result = True elif tmp_dim == 2: check_result = tmp_shape[1] == data.shape[1] elif tmp_dim == 3: # check if the spatial shape is the same check_result = (tmp_shape[1] == data.shape[1]) and ( tmp_shape[2] == data.shape[2] ) elif tmp_dim == 4: # check if the spectral and spatial shape is the same check_result = ( (tmp_shape[0] == data.shape[0]) and (tmp_shape[2] == data.shape[2]) and (tmp_shape[3] == data.shape[3]) ) return check_result if _type_check(): # YES -> dim and type match # we always append in axis one independent of the dimension # 1D case if data.size == 0: warnings.warn( f"The dataset that is appended under the tag name '{tag}' " f"is empty." ) else: if isinstance(data[0], str): data = np.array(data, dtype="|S") if data.ndim == 4: # IFS data: (n_wavelength, n_dit, y_pos, x_pos) self._m_data_storage.m_data_bank[tag].resize( tmp_shape[1] + data.shape[1], axis=1 ) self._m_data_storage.m_data_bank[tag][:, tmp_shape[1] :, :, :] = data else: # Other data: n_dit is the first dimension self._m_data_storage.m_data_bank[tag].resize( tmp_shape[0] + data.shape[0], axis=0 ) self._m_data_storage.m_data_bank[tag][tmp_shape[0] :,] = data return None # NO -> shape or type is different # Check force if force: # YES -> Force is true self._set_all_key(tag, data=data) return None # NO -> Error message raise ValueError( f"The port tag '{self._m_tag}' is already used with a different data " f"type. The 'force' parameter can be used to replace the tag." ) @typechecked def __setitem__( self, key: Union[slice, int, tuple], value: Union[np.ndarray, int] ) -> None: """ Internal function needed to change data using slicing. See class documentation for an example (:class:`~pynpoint.core.dataio.OutputPort`). Parameters ---------- key : slice Index slice to be changed. value : np.ndarray New data. Returns ------- NoneType None """ if self._check_status_and_activate(): self._m_data_storage.m_data_bank[self._m_tag][key] = value @typechecked def del_all_data(self) -> None: """ Delete all data belonging to the database tag. """ if self._check_status_and_activate(): if self._m_tag in self._m_data_storage.m_data_bank: del self._m_data_storage.m_data_bank[self._m_tag] @typechecked def set_all( self, data: Union[np.ndarray, list], data_dim: Optional[int] = None, keep_attributes: bool = False, ) -> None: """ Set the data in the database by replacing all old values with the values of the input data. If no old values exists the data is just stored. Since it is not possible to change the number of dimensions of a data set later in the processing history one can choose a dimension different to the input data. The following cases are implemented: * (#dimension of the first input data#, #desired data_dim#) * (1, 1) 1D input or single value will be stored as list in HDF5 * (1, 2) 1D input, but 2D array stored inside (i.e. a list of lists with a fixed size). * (2, 2) 2D input (single image) and 2D array stored inside (i.e. a list of lists with a fixed size). * (2, 3) 2D input (single image) but 3D array stored inside (i.e. a stack of images with a fixed size). * (3, 3) 3D input and 3D array stored inside (i.e. a stack of images with a fixed size). For 2D and 3D data the first dimension always represents the list / stack (variable size) while the second (or third) dimension has a fixed size. After creation it is possible to extend a data set using :func:`~pynpoint.core.dataio.OutputPort.append` along the first dimension. **Example 1:** Input 2D array with size (200, 200). Desired dimension 3D. The result is a 3D dataset with the dimension (1, 200, 200). It is possible to append other images with the size (200, 200) or other stacks of images with the size (:, 200, 200). **Example 2:** Input 2D array with size (200, 200). Desired dimension 2D. The result is a 2D dataset with the dimension (200, 200). It is possible to append other list with the length 200 or other stacks of lines with the size (:, 200). However it is not possible to append other 2D images along a third dimension. Parameters ---------- data : np.ndarray The data to be saved. data_dim : int Number of data dimensions. The dimension of the *first_data* is used if set to None. keep_attributes : bool All attributes of the old dataset will remain the same if set to True. Returns ------- NoneType None """ data = np.asarray(data) if self._check_status_and_activate(): self._set_all_key( tag=self._m_tag, data=data, data_dim=data_dim, keep_attributes=keep_attributes, ) @typechecked def append( self, data: Union[np.ndarray, list], data_dim: Optional[int] = None, force: bool = False, ) -> None: """ Appends data to an existing dataset along the first dimension. If no data exists for the :class:`~pynpoint.core.dataio.OutputPort`, then a new data set is created. For more information about how the dimensions are organized, see the documentation of :func:`~pynpoint.core.dataio.OutputPort.set_all`. Note it is not possible to append data with a different shape or data type to an existing dataset. **Example:** An internal data set is 3D (storing a stack of 2D images) with shape of ``(233, 300, 300)``, that is, it contains 233 images with a resolution of 300 by 300 pixels. Thus it is only possible to extend along the first dimension by appending new images with a shape of ``(300, 300)`` or by appending a stack of images with a shape of ``(:, 300, 300)``. It is possible to force the function to overwrite existing data set if the shape or type of the input data do not match the existing data. Parameters ---------- data : np.ndarray The data that will be appended. data_dim : int Number of data dimensions used if a new data set is created. The dimension of the ``data`` is used if set to None. force : bool The existing data will be overwritten if the shape or type does not match. Returns ------- NoneType None """ if self._check_status_and_activate(): self._append_key(self._m_tag, data=data, data_dim=data_dim, force=force) @typechecked def activate(self) -> None: """ Activates the port. A non activated port will not save data. Returns ------- NoneType None """ self.m_activate = True @typechecked def deactivate(self) -> None: """ Deactivates the port. A non activated port will not save data. Returns ------- NoneType None """ self.m_activate = False @typechecked def add_attribute( self, name: str, value: Union[StaticAttribute, NonStaticAttribute], static: bool = True, ) -> None: """ Adds an attribute to the dataset of the Port with the attribute name = `name` and the value = `value`. If the attribute already exists it will be overwritten. Two different types of attributes are supported: 1. **static attributes**: Contain a single value or name (e.g. The name of the used Instrument). 2. **non-static attributes**: Contain a dataset which is connected to the actual data set (e.g. Instrument temperature). It is possible to append additional information to non-static attributes later (:func:`~pynpoint.core.dataio.OutputPort.append_attribute_data`). This is not supported by static attributes. Static and non-static attributes are stored in a different way using the HDF5 file format. Static attributes will be direct attributes while non-static attributes are stored in a group with the name *header_* + name of the dataset. Parameters ---------- name : str Name of the attribute. value : StaticAttribute, NonStaticAttribute Value of the attribute. static : bool Indicate if the attribute is static (True) or non-static (False). Returns ------- NoneType None """ if self._check_status_and_activate(): if self._m_tag not in self._m_data_storage.m_data_bank: warnings.warn( f"Can not store the attribute '{name}' because the dataset " f"'{self._m_tag}' does not exist." ) else: if static: self._m_data_storage.m_data_bank[self._m_tag].attrs[name] = value else: self._set_all_key( tag=("header_" + self._m_tag + "/" + name), data=np.asarray(value), ) @typechecked def append_attribute_data( self, name: str, value: Union[StaticAttribute, NonStaticAttribute] ) -> None: """ Function which appends data (either a single value or an array) to non-static attributes. Parameters ---------- name : str Name of the attribute. value : StaticAttribute, NonStaticAttribute Value which will be appended to the attribute dataset. Returns ------- NoneType None """ if self._check_status_and_activate(): self._append_key( tag=("header_" + self._m_tag + "/" + name), data=np.asarray( [ value, ] ), ) @typechecked def copy_attributes(self, input_port: InputPort) -> None: """ Copies all static and non-static attributes from a given InputPort. Attributes which already exist will be overwritten. Non-static attributes will be linked not copied. If the InputPort tag = OutputPort tag (self.tag) nothing will be changed. Use this function in all modules to keep the header information. Parameters ---------- input_port : pynpoint.core.dataio.InputPort The InputPort with the header information. Returns ------- NoneType None """ if self._check_status_and_activate() and input_port.tag != self._m_tag: # link non-static attributes if "header_" + input_port.tag + "/" in self._m_data_storage.m_data_bank: for attr_name, attr_data in self._m_data_storage.m_data_bank[ "header_" + input_port.tag + "/" ].items(): database_name = "header_" + self._m_tag + "/" + attr_name # overwrite existing header information in the database if database_name in self._m_data_storage.m_data_bank: del self._m_data_storage.m_data_bank[database_name] self._m_data_storage.m_data_bank[database_name] = attr_data # copy static attributes attributes = input_port.get_all_static_attributes() for attr_name, attr_val in attributes.items(): self.add_attribute(attr_name, attr_val) self._m_data_storage.m_data_bank.flush() @typechecked def del_attribute(self, name: str) -> None: """ Deletes the attribute of the dataset with the given name. Finds and removes static and non-static attributes. Parameters ---------- name : str Name of the attribute. Returns ------- NoneType None """ if self._check_status_and_activate(): # check if attribute is static if name in self._m_data_storage.m_data_bank[self._m_tag].attrs: del self._m_data_storage.m_data_bank[self._m_tag].attrs[name] elif ( "header_" + self._m_tag + "/" + name in self._m_data_storage.m_data_bank ): # remove non-static attribute del self._m_data_storage.m_data_bank[ ("header_" + self._m_tag + "/" + name) ] else: warnings.warn( f"Attribute '{name}' does not exist and could not be deleted." ) @typechecked def del_all_attributes(self) -> None: """ Deletes all static and non-static attributes of the dataset. Returns ------- NoneType None """ if self._check_status_and_activate(): # static attributes if self._m_tag in self._m_data_storage.m_data_bank: self._m_data_storage.m_data_bank[self._m_tag].attrs.clear() # non-static attributes if "header_" + self._m_tag + "/" in self._m_data_storage.m_data_bank: del self._m_data_storage.m_data_bank[("header_" + self._m_tag + "/")] @typechecked def check_static_attribute( self, name: str, comparison_value: StaticAttribute ) -> Optional[int]: """ Checks if a static attribute exists and if it is equal to a comparison value. Parameters ---------- name : str Name of the static attribute. comparison_value : StaticAttribute Comparison value. Returns ------- int, None Status: 1 if the static attribute does not exist, 0 if the static attribute exists and is equal, and -1 if the static attribute exists but is not equal. """ if not self._check_status_and_activate(): return None if name in self._m_data_storage.m_data_bank[self._m_tag].attrs: if ( self._m_data_storage.m_data_bank[self._m_tag].attrs[name] == comparison_value ): return 0 return -1 return 1 @typechecked def check_non_static_attribute( self, name: str, comparison_value: NonStaticAttribute ) -> Optional[int]: """ Checks if a non-static attribute exists and if it is equal to a comparison value. Parameters ---------- name : str Name of the non-static attribute. comparison_value : NonStaticAttribute Comparison values Returns ------- int, None Status: 1 if the non-static attribute does not exist, 0 if the non-static attribute exists and is equal, and -1 if the non-static attribute exists but is not equal. """ if not self._check_status_and_activate(): return None group = "header_" + self._m_tag + "/" if group in self._m_data_storage.m_data_bank: if name in self._m_data_storage.m_data_bank[group]: if np.array_equal( self._m_data_storage.m_data_bank[group + name][:], comparison_value ): return 0 return -1 return 1 return 1 @typechecked def add_history(self, module: str, history: str) -> None: """ Adds an attribute with history information about the pipeline module. Parameters ---------- module : str Name of the pipeline module which was executed. history : str History information. Returns ------- NoneType None """ self.add_attribute("History: " + module, history) @typechecked def flush(self) -> None: """ Forces the :class:`~pynpoint.core.dataio.DataStorage` to save all data from the memory to the hard drive without closing the :class:`~pynpoint.core.dataio.OutputPort`. Returns ------- NoneType None """ self._m_data_storage.m_data_bank.flush() PynPoint-PynPoint-e5d0637/pynpoint/core/processing.py000066400000000000000000000526071512720201400227510ustar00rootroot00000000000000""" Interfaces for pipeline modules. """ import math import os import time import warnings from abc import ABCMeta, abstractmethod from typing import Callable, List, Optional import numpy as np from typeguard import typechecked from pynpoint.core.dataio import ConfigPort, DataStorage, InputPort, OutputPort from pynpoint.util.module import progress, update_arguments from pynpoint.util.multiline import LineProcessingCapsule from pynpoint.util.multiproc import apply_function from pynpoint.util.multistack import StackProcessingCapsule class PypelineModule(metaclass=ABCMeta): """ Abstract interface for the PypelineModule: * Reading module (:class:`pynpoint.core.processing.ReadingModule`) * Writing module (:class:`pynpoint.core.processing.WritingModule`) * Processing module (:class:`pynpoint.core.processing.ProcessingModule`) Each :class:`~pynpoint.core.processing.PypelineModule` has a name as a unique identifier in the :class:`~pynpoint.core.pypeline.Pypeline` and requires the ``connect_database`` and ``run`` methods. """ @typechecked def __init__(self, name_in: str) -> None: """ Abstract constructor of a :class:`~pynpoint.core.processing.PypelineModule`. Parameters ---------- name_in : str The name of the :class:`~pynpoint.core.processing.PypelineModule`. Returns ------- NoneType None """ self._m_name = name_in self._m_data_base = None self._m_config_port = ConfigPort("config") @property @typechecked def name(self) -> str: """ Returns the name of the :class:`~pynpoint.core.processing.PypelineModule`. This property makes sure that the internal module name can not be changed. Returns ------- str The name of the :class:`~pynpoint.core.processing.PypelineModule` """ return self._m_name @abstractmethod @typechecked def connect_database(self, data_base_in: DataStorage) -> None: """ Abstract interface for the function ``connect_database`` which is needed to connect a :class:`~pynpoint.core.dataio.Port` of a :class:`~pynpoint.core.processing.PypelineModule` with the :class:`~pynpoint.core.dataio.DataStorage`. Parameters ---------- data_base_in : pynpoint.core.dataio.DataStorage The central database. """ @abstractmethod @typechecked def run(self) -> None: """ Abstract interface for the run method of :class:`~pynpoint.core.processing.PypelineModule` which inheres the actual algorithm behind the module. """ class ReadingModule(PypelineModule, metaclass=ABCMeta): """ The abstract class ReadingModule is an interface for processing steps in the Pypeline which have only read access to the central data storage. One can specify a directory on the hard drive where the input data for the module is located. If no input directory is given then default Pypeline input directory is used. Reading modules have a dictionary of output ports (self._m_out_ports) but no input ports. """ @typechecked def __init__(self, name_in: str, input_dir: Optional[str] = None) -> None: """ Abstract constructor of ReadingModule which needs the unique name identifier as input (more information: :class:`pynpoint.core.processing.PypelineModule`). An input directory can be specified for the location of the data or else the Pypeline default directory is used. This function is called in all *__init__()* functions inheriting from this class. Parameters ---------- name_in : str The name of the ReadingModule. input_dir : str Directory where the input files are located. Returns ------- NoneType None """ super().__init__(name_in) assert os.path.isdir(str(input_dir)) or input_dir is None, ( "Input directory for " "reading module does not exist - input requested: %s." % input_dir ) self.m_input_location = input_dir self._m_output_ports = {} @typechecked def add_output_port(self, tag: str, activation: bool = True) -> OutputPort: """ Function which creates an OutputPort for a ReadingModule and appends it to the internal OutputPort dictionary. This function should be used by classes inheriting from ReadingModule to make sure that only output ports with unique tags are added. The new port can be used as: :: port = self._m_output_ports[tag] or by using the returned Port. Parameters ---------- tag : str Tag of the new output port. activation : bool Activation status of the Port after creation. Deactivated ports will not save their results until they are activated. Returns ------- pynpoint.core.dataio.OutputPort The new OutputPort for the ReadingModule. """ port = OutputPort(tag, activate_init=activation) if tag in self._m_output_ports: warnings.warn( f"Tag '{tag}' of ReadingModule '{self._m_name}' is already used." ) if self._m_data_base is not None: port.set_database_connection(self._m_data_base) self._m_output_ports[tag] = port return port @typechecked def connect_database(self, data_base_in: DataStorage) -> None: """ Function used by a ReadingModule to connect all ports in the internal input and output port dictionaries to the database. The function is called by Pypeline and connects the DataStorage object to all module ports. Parameters ---------- data_base_in : pynpoint.core.dataio.DataStorage The central database. Returns ------- NoneType None """ for port in self._m_output_ports.values(): port.set_database_connection(data_base_in) self._m_config_port.set_database_connection(data_base_in) self._m_data_base = data_base_in @typechecked def get_all_output_tags(self) -> List[str]: """ Returns a list of all output tags to the ReadingModule. Returns ------- list(str) List of output tags. """ return list(self._m_output_ports.keys()) @abstractmethod @typechecked def run(self) -> None: """ Abstract interface for the run method of a ReadingModule which inheres the actual algorithm behind the module. """ class WritingModule(PypelineModule, metaclass=ABCMeta): """ The abstract class WritingModule is an interface for processing steps in the pipeline which do not change the content of the internal DataStorage. They only have reading access to the central data base. WritingModules can be used to export data from the HDF5 database. WritingModules know the directory on the hard drive where the output of the module can be saved. If no output directory is given the default Pypeline output directory is used. WritingModules have a dictionary of input ports (self._m_input_ports) but no output ports. """ @typechecked def __init__(self, name_in: str, output_dir: Optional[str] = None) -> None: """ Abstract constructor of a WritingModule which needs the unique name identifier as input (more information: :class:`pynpoint.core.processing.PypelineModule`). In addition one can specify a output directory where the module will save its results. If no output directory is given the Pypeline default directory is used. This function is called in all *__init__()* functions inheriting from this class. Parameters ---------- name_in : str The name of the WritingModule. output_dir : str Directory where the results will be saved. Returns ------- NoneType None """ super().__init__(name_in) assert os.path.isdir(str(output_dir)) or output_dir is None, ( "Output directory for " "writing module does not exist - input requested: %s." % output_dir ) self.m_output_location = output_dir self._m_input_ports = {} @typechecked def add_input_port(self, tag: str) -> InputPort: """ Function which creates an InputPort for a WritingModule and appends it to the internal InputPort dictionary. This function should be used by classes inheriting from WritingModule to make sure that only input ports with unique tags are added. The new port can be used as: :: port = self._m_input_ports[tag] or by using the returned Port. Parameters ---------- tag : str Tag of the new input port. Returns ------- pynpoint.core.dataio.InputPort The new InputPort for the WritingModule. """ port = InputPort(tag) if self._m_data_base is not None: port.set_database_connection(self._m_data_base) self._m_input_ports[tag] = port return port @typechecked def connect_database(self, data_base_in: DataStorage) -> None: """ Function used by a WritingModule to connect all ports in the internal input and output port dictionaries to the database. The function is called by Pypeline and connects the DataStorage object to all module ports. Parameters ---------- data_base_in : pynpoint.core.dataio.DataStorage The central database. Returns ------- NoneType None """ for port in self._m_input_ports.values(): port.set_database_connection(data_base_in) self._m_config_port.set_database_connection(data_base_in) self._m_data_base = data_base_in @typechecked def get_all_input_tags(self) -> List[str]: """ Returns a list of all input tags to the WritingModule. Returns ------- list(str) List of input tags. """ return list(self._m_input_ports.keys()) @abstractmethod @typechecked def run(self) -> None: """ Abstract interface for the run method of a WritingModule which inheres the actual algorithm behind the module. """ class ProcessingModule(PypelineModule, metaclass=ABCMeta): """ The abstract class ProcessingModule is an interface for all processing steps in the pipeline which read, process, and store data. Hence processing modules have read and write access to the central database through a dictionary of output ports (self._m_output_ports) and a dictionary of input ports (self._m_input_ports). """ @typechecked def __init__(self, name_in: str) -> None: """ Abstract constructor of a ProcessingModule which needs the unique name identifier as input (more information: :class:`pynpoint.core.processing.PypelineModule`). Call this function in all __init__() functions inheriting from this class. Parameters ---------- name_in : str The name of the ProcessingModule. """ super().__init__(name_in) self._m_input_ports = {} self._m_output_ports = {} @typechecked def add_input_port(self, tag: str) -> InputPort: """ Function which creates an InputPort for a ProcessingModule and appends it to the internal InputPort dictionary. This function should be used by classes inheriting from ProcessingModule to make sure that only input ports with unique tags are added. The new port can be used as: :: port = self._m_input_ports[tag] or by using the returned Port. Parameters ---------- tag : str Tag of the new input port. Returns ------- pynpoint.core.dataio.InputPort The new InputPort for the ProcessingModule. """ port = InputPort(tag) if self._m_data_base is not None: port.set_database_connection(self._m_data_base) self._m_input_ports[tag] = port return port @typechecked def add_output_port(self, tag: str, activation: bool = True) -> OutputPort: """ Function which creates an :class:`~pynpoint.core.dataio.OutputPort` for a :class:`~pynpoint.core.processing.ProcessingModule` and appends it to the internal :class:`~pynpoint.core.dataio.OutputPort` dictionary. This function should be used by classes inheriting from :class:`~pynpoint.core.processing.ProcessingModule` to make sure that only output ports with unique tags are added. The new port can be used as: .. code-block:: python port = self._m_output_ports[tag] or by using the returned :class:`~pynpoint.core.dataio.Port`. Parameters ---------- tag : str Tag of the new output port. activation : bool Activation status of the :class:`~pynpoint.core.dataio.Port` after creation. Deactivated ports will not save their results until they are activated. Returns ------- pynpoint.core.dataio.OutputPort The new :class:`~pynpoint.core.dataio.OutputPort` for the :class:`~pynpoint.core.processing.ProcessingModule`. """ port = OutputPort(tag, activate_init=activation) if tag in self._m_output_ports: warnings.warn( f"Tag '{tag}' of ProcessingModule '{self._m_name}' is already used." ) if self._m_data_base is not None: port.set_database_connection(self._m_data_base) self._m_output_ports[tag] = port return port @typechecked def connect_database(self, data_base_in: DataStorage) -> None: """ Function used by a ProcessingModule to connect all ports in the internal input and output port dictionaries to the database. The function is called by Pypeline and connects the DataStorage object to all module ports. Parameters ---------- data_base_in : pynpoint.core.dataio.DataStorage The central database. Returns ------- NoneType None """ for port in self._m_input_ports.values(): port.set_database_connection(data_base_in) for port in self._m_output_ports.values(): port.set_database_connection(data_base_in) self._m_config_port.set_database_connection(data_base_in) self._m_data_base = data_base_in @typechecked def apply_function_in_time( self, func: Callable, image_in_port: InputPort, image_out_port: OutputPort, func_args: Optional[tuple] = None, ) -> None: """ Applies a function to all pixel lines in time. Parameters ---------- func : function The input function. image_in_port : pynpoint.core.dataio.InputPort Input port which is linked to the input data. image_out_port : pynpoint.core.dataio.OutputPort Output port which is linked to the results. func_args : tuple, None Additional arguments which are required by the input function. Not used if set to None. Returns ------- NoneType None """ cpu = self._m_config_port.get_attribute("CPU") init_line = image_in_port[:, 0, 0] im_shape = image_in_port.get_shape() size = apply_function(init_line, 0, func, func_args).shape[0] image_out_port.set_all( data=np.zeros((size, im_shape[1], im_shape[2])), data_dim=3, keep_attributes=False, ) image_in_port.close_port() image_out_port.close_port() capsule = LineProcessingCapsule( image_in_port=image_in_port, image_out_port=image_out_port, num_proc=cpu, function=func, function_args=func_args, data_length=size, ) capsule.run() @typechecked def apply_function_to_images( self, func: Callable[..., np.ndarray], image_in_port: InputPort, image_out_port: OutputPort, message: str, func_args: Optional[tuple] = None, ) -> None: """ Function which applies a function to all images of an input port. Stacks of images are processed in parallel if the CPU and MEMORY attribute are set in the central configuration. The number of images per process is equal to the value of MEMORY divided by the value of CPU. Note that the function *func* is not allowed to change the shape of the images if the input and output port have the same tag and ``MEMORY`` is not set to None. Parameters ---------- func : function The function which is applied to all images. Its definitions should be similar to:: def function(image_in, parameter1, parameter2, parameter3) The function must return a numpy array. image_in_port : pynpoint.core.dataio.InputPort Input port which is linked to the input data. image_out_port : pynpoint.core.dataio.OutputPort Output port which is linked to the results. message : str Progress message. func_args : tuple Additional arguments that are required by the input function. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") cpu = self._m_config_port.get_attribute("CPU") nimages = image_in_port.get_shape()[0] if memory == 0: memory = nimages if image_out_port.tag == image_in_port.tag: # load all images in the memory at once if the input and output tag are the # same or if the MEMORY attribute is set to None in the configuration file images = image_in_port.get_all() result = [] start_time = time.time() for i in range(nimages): progress(i, nimages, message + "...", start_time) args = update_arguments(i, nimages, func_args) if args is None: result.append(func(images[i,], i)) else: result.append(func(images[i,], i, *args)) image_out_port.set_all(np.asarray(result), keep_attributes=True) elif cpu == 1: # process images one-by-one with a single process if CPU is set to 1 start_time = time.time() for i in range(nimages): progress(i, nimages, message + "...", start_time) args = update_arguments(i, nimages, func_args) if args is None: result = func(image_in_port[i,], i) else: result = func(image_in_port[i,], i, *args) if result.ndim == 1: image_out_port.append(result, data_dim=2) elif result.ndim == 2: image_out_port.append(result, data_dim=3) else: # process images in parallel in stacks of MEMORY/CPU images print(message, end="") args = update_arguments(0, nimages, func_args) result = apply_function(image_in_port[0, :, :], 0, func, args) result_shape = result.shape out_shape = [nimages] for item in result_shape: out_shape.append(item) image_out_port.set_all( data=np.zeros(out_shape), data_dim=len(result_shape) + 1, keep_attributes=False, ) image_in_port.close_port() image_out_port.close_port() capsule = StackProcessingCapsule( image_in_port=image_in_port, image_out_port=image_out_port, num_proc=cpu, function=func, function_args=func_args, stack_size=math.ceil(memory / cpu), result_shape=result_shape, nimages=nimages, ) capsule.run() print(" [DONE]") @typechecked def get_all_input_tags(self) -> List[str]: """ Returns a list of all input tags to the ProcessingModule. Returns ------- list(str) List of input tags. """ return list(self._m_input_ports.keys()) @typechecked def get_all_output_tags(self) -> List[str]: """ Returns a list of all output tags to the ProcessingModule. Returns ------- list(str) List of output tags. """ return list(self._m_output_ports.keys()) @abstractmethod @typechecked def run(self) -> None: """ Abstract interface for the run method of a :class:`~pynpoint.core.processing.ProcessingModule` which inheres the actual algorithm behind the module. """ PynPoint-PynPoint-e5d0637/pynpoint/core/pypeline.py000066400000000000000000000650321512720201400224160ustar00rootroot00000000000000""" Module which capsules the methods of the Pypeline. """ import collections import configparser import json import multiprocessing import os import urllib.request import warnings from typing import Any, Dict, List, Optional, Tuple, Union from urllib.error import URLError import h5py import numpy as np from typeguard import typechecked from pynpoint.core.attributes import get_attributes from pynpoint.core.dataio import DataStorage from pynpoint.core.processing import ( ProcessingModule, PypelineModule, ReadingModule, WritingModule, ) from pynpoint.util.module import input_info, module_info, output_info from pynpoint.util.type_aliases import NonStaticAttribute, StaticAttribute from .._version import __version__, __version_tuple__ class Pypeline: """ The :class:`~pynpoint.core.pypeline.Pypeline` class manages the pipeline modules. It inheres an internal dictionary of pipeline modules and has a :class:`~pynpoint.core.dataio.DataStorage` which is accessed by the various modules. The order in which the pipeline modules are executed depends on the order they have been added to the :class:`~pynpoint.core.pypeline.Pypeline`. It is possible to run all modules at once or run a single module by name. """ @typechecked def __init__( self, working_place_in: Optional[str] = None, input_place_in: Optional[str] = None, output_place_in: Optional[str] = None, ) -> None: """ Parameters ---------- working_place_in : str, None Working location where the central HDF5 database and the configuration file will be stored. Sufficient space is required in the working folder since each pipeline module stores a dataset in the HDF5 database. The current working folder of Python is used as working folder if the argument is set to None. input_place_in : str, None Default input folder where a :class:`~pynpoint.core.processing.ReadingModule` that is added to the :class:`~pynpoint.core.pypeline.Pypeline` will look for input data. The current working folder of Python is used as input folder if the argument is set to None. output_place_in : str, None Default output folder where a :class:`~pynpoint.core.processing.WritingModule` that is added to the :class:`~pynpoint.core.pypeline.Pypeline` will store output data. The current working folder of Python is used as output folder if the argument is set to None. Returns ------- NoneType None """ pynpoint_version = "PynPoint v" + __version__ print(len(pynpoint_version) * "=") print(pynpoint_version) print(len(pynpoint_version) * "=" + "\n") try: contents = urllib.request.urlopen( "https://pypi.org/pypi/pynpoint/json" ).read() data = json.loads(contents) latest_version = data["info"]["version"] except URLError: latest_version = None if latest_version is not None and __version__ != latest_version: print(f"A new version ({latest_version}) is available!\n") print("Want to stay informed about updates, bug fixes, and new features?") print("Please consider using the 'Watch' button on the Github page:") print("https://github.com/PynPoint/PynPoint\n") if working_place_in is None: self._m_working_place = os.getcwd() else: self._m_working_place = working_place_in if input_place_in is None: self._m_input_place = os.getcwd() else: self._m_input_place = input_place_in if output_place_in is None: self._m_output_place = os.getcwd() else: self._m_output_place = output_place_in print(f"Working place: {self._m_working_place}") print(f"Input place: {self._m_input_place}") print(f"Output place: {self._m_output_place}\n") self._m_modules = collections.OrderedDict() hdf5_path = os.path.join(self._m_working_place, "PynPoint_database.hdf5") self.m_data_storage = DataStorage(hdf5_path) print(f"Database: {self.m_data_storage._m_location}") self._config_init() @typechecked def __setattr__(self, key: str, value: Any) -> None: """ Internal method which assigns a value to an object attribute. This method is called whenever and attribute of the :class:`~pynpoint.core.pypeline.Pypeline` is changed and checks if the chosen working, input, or output folder exists. Parameters ---------- key : str Attribute name. value : str Value for the attribute. Returns ------- NoneType None """ if key == "_m_working_place": error_msg = f"The folder that was chosen for the working place does not exist: {value}." assert os.path.isdir(str(value)), error_msg elif key == "_m_input_place": error_msg = f"The folder that was chosen for the input place does not exist: {value}." assert os.path.isdir(str(value)), error_msg elif key == "_m_output_place": error_msg = f"The folder that was chosen for the output place does not exist: {value}." assert os.path.isdir(str(value)), error_msg super().__setattr__(key, value) @staticmethod @typechecked def _validate( module: Union[ReadingModule, WritingModule, ProcessingModule], tags: List[str] ) -> Tuple[bool, Optional[str]]: """ Internal method to validate a :class:`~pynpoint.core.processing.PypelineModule`. Parameters ---------- module : ReadingModule, WritingModule, ProcessingModule Pipeline module that will be validated. tags : list(str) Tags that are present in the database. Returns ------- bool Validation of the pipeline module. str, None Pipeline module name in case it is not valid. Returns None if the module was validated. """ if isinstance(module, ReadingModule): tags.extend(module.get_all_output_tags()) elif isinstance(module, WritingModule): for tag in module.get_all_input_tags(): if tag not in tags: return False, module.name elif isinstance(module, ProcessingModule): tags.extend(module.get_all_output_tags()) for tag in module.get_all_input_tags(): if tag not in tags: return False, module.name return True, None @typechecked def _config_init(self) -> None: """ Internal method to initialize the configuration file. The configuration parameters are read from *PynPoint_config.ini* in the working folder. The file is created with default values (ESO/NACO) in case the file is not present. Returns ------- NoneType None """ @typechecked def _create_config(filename: str, attributes: dict) -> None: file_obj = open(filename, "w") file_obj.write("[header]\n\n") for key, val in attributes.items(): if val["config"] == "header": file_obj.write(key + ": " + str(val["value"]) + "\n") file_obj.write("\n[settings]\n\n") for key, val in attributes.items(): if val["config"] == "settings": file_obj.write(key + ": " + str(val["value"]) + "\n") file_obj.close() @typechecked def _read_config(config_file: str, attributes: dict) -> dict: config = configparser.ConfigParser() with open(config_file) as cf_open: config.read_file(cf_open) for key, val in attributes.items(): if config.has_option(val["config"], key): if config.get(val["config"], key) == "None": if val["config"] == "header": attributes[key]["value"] = "None" # elif val['type'] == 'str': # attributes[key]['value'] = 'None' elif val["type"] == "float": attributes[key]["value"] = float(0.0) elif val["type"] == "int": attributes[key]["value"] = int(0) else: if val["config"] == "header": attributes[key]["value"] = str( config.get(val["config"], key) ) # elif val['type'] == 'str': # attributes[key]['value'] = str(config.get(val['config'], key)) elif val["type"] == "float": attributes[key]["value"] = float( config.get(val["config"], key) ) elif val["type"] == "int": attributes[key]["value"] = int( config.get(val["config"], key) ) return attributes @typechecked def _write_config(attributes: dict) -> None: hdf = h5py.File(self._m_working_place + "/PynPoint_database.hdf5", "a") if "config" in hdf: del hdf["config"] config = hdf.create_group("config") for key in attributes.keys(): if attributes[key]["value"] is not None: config.attrs[key] = attributes[key]["value"] config.attrs["WORKING_PLACE"] = self._m_working_place hdf.close() config_file = os.path.join(self._m_working_place, "PynPoint_config.ini") print(f"Configuration: {config_file}\n") attributes = get_attributes() attributes["CPU"]["value"] = multiprocessing.cpu_count() if not os.path.isfile(config_file): warnings.warn( "Configuration file not found. Creating PynPoint_config.ini with " "default values in the working place." ) _create_config(config_file, attributes) attributes = _read_config(config_file, attributes) _write_config(attributes) n_cpu = attributes["CPU"]["value"] if "OMP_NUM_THREADS" in os.environ: n_thread = os.environ["OMP_NUM_THREADS"] else: n_thread = "not set" print(f"Number of CPUs: {n_cpu}") print(f"Number of threads: {n_thread}") @typechecked def add_module(self, module: PypelineModule) -> None: """ Method for adding a :class:`~pynpoint.core.processing.PypelineModule` to the internal dictionary of the :class:`~pynpoint.core.pypeline.Pypeline`. The module is appended at the end of this ordered dictionary. If the input module is a reading or writing module without a specified input or output location then the default location is used. The module is connected to the internal data storage of the :class:`~pynpoint.core.pypeline.Pypeline`. Parameters ---------- module : ReadingModule, WritingModule, ProcessingModule Pipeline module that will be added to the :class:`~pynpoint.core.pypeline.Pypeline`. Returns ------- NoneType None """ if isinstance(module, ReadingModule): if module.m_input_location is None: module.m_input_location = self._m_input_place if isinstance(module, WritingModule): if module.m_output_location is None: module.m_output_location = self._m_output_place module.connect_database(self.m_data_storage) if module.name in self._m_modules: warnings.warn( f"Names of pipeline modules that are added to the Pypeline need to " f"be unique. The current pipeline module, '{module.name}', does " f"already exist in the Pypeline dictionary so the previous module " f"with the same name will be overwritten." ) self._m_modules[module.name] = module @typechecked def remove_module(self, name: str) -> bool: """ Method to remove a :class:`~pynpoint.core.processing.PypelineModule` from the internal dictionary with pipeline modules that are added to the :class:`~pynpoint.core.pypeline.Pypeline`. Parameters ---------- name : str Name of the module that has to be removed. Returns ------- bool Confirmation of removing the :class:`~pynpoint.core.processing.PypelineModule`. """ if name in self._m_modules: del self._m_modules[name] removed = True else: warnings.warn( f"Pipeline module '{name}' is not found in the Pypeline dictionary " f"so it could not be removed. The dictionary contains the following " f"modules: {list(self._m_modules.keys())}." ) removed = False return removed @typechecked def get_module_names(self) -> List[str]: """ Method to return a list with the names of all pipeline modules that are added to the :class:`~pynpoint.core.pypeline.Pypeline`. Returns ------- list(str) Ordered list of all Pypeline modules. """ return list(self._m_modules.keys()) @typechecked def validate_pipeline(self) -> Tuple[bool, Optional[str]]: """ Method to check if each :class:`~pynpoint.core.dataio.InputPort` is pointing to an :class:`~pynpoint.core.dataio.OutputPort` of a previously added :class:`~pynpoint.core.processing.PypelineModule`. Returns ------- bool Validation of the pipeline. str, None Name of the pipeline module that can not be validated. Returns None if all modules were validated. """ self.m_data_storage.open_connection() # Create list with all datasets that are stored in the database data_tags = list(self.m_data_storage.m_data_bank.keys()) # Initiate the validation in case self._m_modules.values() is empty validation = (True, None) # Loop over all pipline modules in the ordered dictionary for module in self._m_modules.values(): # Validate the pipeline module validation = self._validate(module, data_tags) if not validation[0]: # Break the for loop if a module could not be validated break return validation @typechecked def validate_pipeline_module(self, name: str) -> Tuple[bool, Optional[str]]: """ Method to check if each :class:`~pynpoint.core.dataio.InputPort` of a :class:`~pynpoint.core.processing.PypelineModule` with label ``name`` points to an existing dataset in the database. Parameters ---------- name : str Name of the pipeline module instance that will be validated. Returns ------- bool Validation of the pipeline module. str, None Pipeline module name in case it is not valid. Returns None if the module was validated. """ self.m_data_storage.open_connection() # Create list with all datasets that are stored in the database data_tags = list(self.m_data_storage.m_data_bank.keys()) # Check if the name is included in the internal dictionary with added modules if name in self._m_modules: # Validate the pipeline module validate = self._validate(self._m_modules[name], data_tags) else: validate = (False, name) return validate @typechecked def run(self) -> None: """ Method for running all pipeline modules that are added to the :class:`~pynpoint.core.pypeline.Pypeline`. Returns ------- NoneType None """ # Validate the pipeline validation = self.validate_pipeline() if not validation[0]: # Check if the input data is available raise AttributeError( f"Pipeline module '{validation[1]}' is looking for data " f"under a tag which is not created by a previous module or " f"the data does not exist in the database." ) # Loop over all pipeline modules and run them for name in self._m_modules: self.run_module(name) @typechecked def run_module(self, name: str) -> None: """ Method for running a pipeline module. Parameters ---------- name : str Name of the pipeline module. Returns ------- NoneType None """ if name in self._m_modules: # Validate the pipeline module validation = self.validate_pipeline_module(name) if not validation[0]: raise AttributeError( f"Pipeline module '{validation[1]}' is looking for data " f"under a tag which does not exist in the database." ) # Print information about the pipeline module module_info(self._m_modules[name]) # Check if the module has any input ports if hasattr(self._m_modules[name], "_m_input_ports"): # Check if the list of input ports is not empty if len(self._m_modules[name]._m_input_ports) > 0: # Print information about the input ports input_info(self._m_modules[name]) # Check if the module has any output ports if hasattr(self._m_modules[name], "_m_output_ports"): for item in self._m_modules[name]._m_output_ports: # Check if the module is a ProcessingModule if isinstance(self._m_modules[name], ProcessingModule): # Check if the database tag is already used if item in self.m_data_storage.m_data_bank: # Check if the output port is not used as input port if ( hasattr(self._m_modules[name], "_m_input_ports") and item not in self._m_modules[name]._m_input_ports ): print(f"Deleting data and attributes: {item}") # Delete existing data and attributes self._m_modules[name]._m_output_ports[ item ].del_all_data() self._m_modules[name]._m_output_ports[ item ].del_all_attributes() # Run the pipeline module self._m_modules[name].run() # Check if the module has any output ports if hasattr(self._m_modules[name], "_m_output_ports"): output_shape = {} for item in self._m_modules[name]._m_output_ports: # Get the shape of the output port output_shape[item] = self.get_shape(item) # Print information about the output ports output_info(self._m_modules[name], output_shape) else: warnings.warn(f"Pipeline module '{name}' not found.") @typechecked def get_data( self, tag: str, data_range: Optional[Tuple[int, int]] = None ) -> np.ndarray: """ Method for reading data from the database. Parameters ---------- tag : str Database tag. data_range : tuple(int, int), None Slicing range for the first axis of a dataset. This argument can be used to select a subset of images from dataset. The full dataset is read if the argument is set to None. Returns ------- np.ndarray The selected dataset from the database. """ self.m_data_storage.open_connection() if data_range is None: data = np.array(self.m_data_storage.m_data_bank[tag]) else: data = np.array( self.m_data_storage.m_data_bank[tag][data_range[0] : data_range[1],] ) self.m_data_storage.close_connection() return data @typechecked def delete_data(self, tag: str) -> None: """ Method for deleting a dataset and related attributes from the central database. Disk space does not seem to free up when using this method. Parameters ---------- tag : str Database tag. Returns ------- NoneType None """ self.m_data_storage.open_connection() if tag in self.m_data_storage.m_data_bank: del self.m_data_storage.m_data_bank[tag] else: warnings.warn(f"Dataset '{tag}' not found in the database.") if "header_" + tag + "/" in self.m_data_storage.m_data_bank: del self.m_data_storage.m_data_bank[f"header_{tag}"] else: warnings.warn(f"Attributes of '{tag}' not found in the database.") self.m_data_storage.close_connection() @typechecked def get_attribute( self, data_tag: str, attr_name: str, static: bool = True ) -> Union[StaticAttribute, NonStaticAttribute]: """ Method for reading an attribute from the database. Parameters ---------- data_tag : str Database tag. attr_name : str Name of the attribute. static : bool Static (True) or non-static attribute (False). Returns ------- StaticAttribute, NonStaticAttribute Attribute value. For a static attribute, a single value is returned. For a non-static attribute, an array of values is returned. """ self.m_data_storage.open_connection() if static: attr = self.m_data_storage.m_data_bank[data_tag].attrs[attr_name] else: attr = self.m_data_storage.m_data_bank[f"header_{data_tag}/{attr_name}"] attr = np.asarray(attr) self.m_data_storage.close_connection() return attr @typechecked def set_attribute( self, data_tag: str, attr_name: str, attr_value: Union[StaticAttribute, NonStaticAttribute], static: bool = True, ) -> None: """ Method for writing an attribute to the database. Existing values will be overwritten. Parameters ---------- data_tag : str Database tag. attr_name : str Name of the attribute. attr_value : StaticAttribute, NonStaticAttribute Attribute value. static : bool Static (True) or non-static attribute (False). Returns ------- NoneType None """ self.m_data_storage.open_connection() if static: self.m_data_storage.m_data_bank[data_tag].attrs[attr_name] = attr_value else: if isinstance(attr_value[0], str): attr_value = np.array(attr_value, dtype="|S") if attr_name in list( self.m_data_storage.m_data_bank[f"header_{data_tag}"].keys() ): del self.m_data_storage.m_data_bank[f"header_{data_tag}/{attr_name}"] attr_key = f"header_{data_tag}/{attr_name}" self.m_data_storage.m_data_bank[attr_key] = np.asarray(attr_value) self.m_data_storage.close_connection() @typechecked def get_tags(self) -> List[str]: """ Method for returning a list with all database tags, except header and configuration tags. Returns ------- list(str) Database tags. """ self.m_data_storage.open_connection() tags = list(self.m_data_storage.m_data_bank.keys()) selected_tags = [] for item in tags: if item in ["config", "fits_header"] or item[0:7] == "header_": continue selected_tags.append(item) self.m_data_storage.close_connection() return selected_tags @typechecked def get_shape(self, tag: str) -> Optional[Tuple[int, ...]]: """ Method for returning the shape of a database entry. Parameters ---------- tag : str Database tag. Returns ------- tuple(int, ...), None Shape of the dataset. None is returned if the database tag is not found. """ self.m_data_storage.open_connection() if tag in self.m_data_storage.m_data_bank: data_shape = self.m_data_storage.m_data_bank[tag].shape else: data_shape = None self.m_data_storage.close_connection() return data_shape @typechecked def list_attributes( self, data_tag: str ) -> Dict[str, Union[str, np.float64, np.ndarray]]: """ Method for printing and returning an overview of all attributes of a dataset. Parameters ---------- data_tag : str Database tag of which the attributes will be extracted. Returns ------- dict(str, bool) Dictionary with all attributes, both static and non-static. """ print_text = f"Attribute overview of {data_tag}" print("\n" + len(print_text) * "-") print(print_text) print(len(print_text) * "-" + "\n") self.m_data_storage.open_connection() attributes = {} print("Static attributes:") for key, value in self.m_data_storage.m_data_bank[data_tag].attrs.items(): attributes[key] = value print(f"\n - {key} = {value}") print("\nNon-static attributes:") for key, value in self.m_data_storage.m_data_bank[f"header_{data_tag}"].items(): attributes[key] = list(value) print(f"\n - {key} = {list(value)}") self.m_data_storage.close_connection() return attributes PynPoint-PynPoint-e5d0637/pynpoint/processing/000077500000000000000000000000001512720201400214355ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/processing/__init__.py000066400000000000000000000000001512720201400235340ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/processing/background.py000066400000000000000000000467731512720201400241470ustar00rootroot00000000000000""" Pipeline modules for subtraction of the background emission. """ import time import warnings from typing import Any, Optional, Union import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.image import create_mask from pynpoint.util.module import progress from pynpoint.util.apply_func import subtract_line class SimpleBackgroundSubtractionModule(ProcessingModule): """ Pipeline module for simple background subtraction. Only applicable on data obtained with dithering. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, shift: int ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. shift : int Frame index offset for the background subtraction. Typically equal to the number of frames per dither location. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_shift = shift @typechecked def run(self) -> None: """ Run method of the module. Simple background subtraction with a constant index offset. Returns ------- NoneType None """ nframes = self.m_image_in_port.get_shape()[0] subtract = ( self.m_image_in_port[0] - self.m_image_in_port[(0 + self.m_shift) % nframes] ) if self.m_image_in_port.tag == self.m_image_out_port.tag: self.m_image_out_port[0] = subtract else: self.m_image_out_port.set_all(subtract, data_dim=3) start_time = time.time() for i in range(1, nframes): progress(i, nframes, "Subtracting background...", start_time) subtract = ( self.m_image_in_port[i] - self.m_image_in_port[(i + self.m_shift) % nframes] ) if self.m_image_in_port.tag == self.m_image_out_port.tag: self.m_image_out_port[i] = subtract else: self.m_image_out_port.append(subtract) history = f"shift = {self.m_shift}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("SimpleBackgroundSubtractionModule", history) self.m_image_out_port.close_port() class MeanBackgroundSubtractionModule(ProcessingModule): """ Pipeline module for mean background subtraction. Only applicable on data obtained with dithering. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, shift: Optional[int] = None, cubes: int = 1, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Should be different from *image_in_tag*. shift : int, None Image index offset for the background subtraction. Typically equal to the number of frames per dither location. If set to None, the ``NFRAMES`` attribute will be used to select the background frames automatically. The *cubes* parameters should be set when *shift* is set to None. cubes : int Number of consecutive cubes per dithering position. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_shift = shift self.m_cubes = cubes @typechecked def run(self) -> None: """ Run method of the module. Mean background subtraction which uses either a constant index offset or the ``NFRAMES`` attributes. The mean background is calculated from the cubes before and after the science cube. Returns ------- NoneType None """ # Use NFRAMES values if shift=None if self.m_shift is None: self.m_shift = self.m_image_in_port.get_attribute("NFRAMES") nframes = self.m_image_in_port.get_shape()[0] if not isinstance(self.m_shift, np.ndarray) and nframes < self.m_shift * 2.0: raise ValueError( "The input stack is too small for a mean background subtraction. The " "position of the star should shift at least once." ) if self.m_image_in_port.tag == self.m_image_out_port.tag: raise ValueError( "The tag of the input port should be different from the output port." ) # Number of substacks if isinstance(self.m_shift, np.ndarray): nstacks = np.size(self.m_shift) else: nstacks = int(np.floor(nframes / self.m_shift)) # First mean subtraction to set up the output port array if isinstance(self.m_shift, np.ndarray): next_start = np.sum(self.m_shift[0 : self.m_cubes]) next_end = np.sum(self.m_shift[0 : 2 * self.m_cubes]) if 2 * self.m_cubes > np.size(self.m_shift): raise ValueError( "Not enough frames available for the background subtraction." ) bg_data = self.m_image_in_port[next_start:next_end,] bg_mean = np.mean(bg_data, axis=0) else: bg_data = self.m_image_in_port[self.m_shift : 2 * self.m_shift,] bg_mean = np.mean(bg_data, axis=0) # Initiate the result port data with the first frame bg_sub = self.m_image_in_port[0,] - bg_mean self.m_image_out_port.set_all(bg_sub, data_dim=3) # Mean subtraction of the first stack (minus the first frame) if isinstance(self.m_shift, np.ndarray): tmp_data = self.m_image_in_port[1:next_start,] tmp_data = tmp_data - bg_mean self.m_image_out_port.append(tmp_data) else: tmp_data = self.m_image_in_port[1 : self.m_shift,] tmp_data = tmp_data - bg_mean self.m_image_out_port.append(tmp_data) # Processing of the rest of the data start_time = time.time() if isinstance(self.m_shift, np.ndarray): for i in range(self.m_cubes, nstacks, self.m_cubes): progress(i, nstacks, "Subtracting background...", start_time) prev_start = np.sum(self.m_shift[0 : i - self.m_cubes]) prev_end = np.sum(self.m_shift[0:i]) next_start = np.sum(self.m_shift[0 : i + self.m_cubes]) next_end = np.sum(self.m_shift[0 : i + 2 * self.m_cubes]) # calc the mean (previous) tmp_data = self.m_image_in_port[prev_start:prev_end,] tmp_mean = np.mean(tmp_data, axis=0) if i < nstacks - self.m_cubes: # calc the mean (next) tmp_data = self.m_image_in_port[next_start:next_end,] tmp_mean = (tmp_mean + np.mean(tmp_data, axis=0)) / 2.0 # subtract mean tmp_data = self.m_image_in_port[prev_end:next_start,] tmp_data = tmp_data - tmp_mean self.m_image_out_port.append(tmp_data) else: # the last and the one before will be performed afterwards top = int(np.ceil(nframes / self.m_shift)) - 2 for i in range(1, top, 1): progress(i, top, "Subtracting background...", start_time) # calc the mean (next) tmp_data = self.m_image_in_port[ (i + 1) * self.m_shift : (i + 2) * self.m_shift, ] tmp_mean = np.mean(tmp_data, axis=0) # calc the mean (previous) tmp_data = self.m_image_in_port[ (i - 1) * self.m_shift : (i + 0) * self.m_shift, ] tmp_mean = (tmp_mean + np.mean(tmp_data, axis=0)) / 2.0 # subtract mean tmp_data = self.m_image_in_port[ (i + 0) * self.m_shift : (i + 1) * self.m_shift, ] tmp_data = tmp_data - tmp_mean self.m_image_out_port.append(tmp_data) # last and the one before # 1. ------------------------------- one before ------------------- # calc the mean (previous) tmp_data = self.m_image_in_port[ (top - 1) * self.m_shift : (top + 0) * self.m_shift, ] tmp_mean = np.mean(tmp_data, axis=0) # calc the mean (next) # 'nframes' is important if the last step is to huge tmp_data = self.m_image_in_port[(top + 1) * self.m_shift : nframes,] tmp_mean = (tmp_mean + np.mean(tmp_data, axis=0)) / 2.0 # subtract mean tmp_data = self.m_image_in_port[ top * self.m_shift : (top + 1) * self.m_shift, ] tmp_data = tmp_data - tmp_mean self.m_image_out_port.append(tmp_data) # 2. ------------------------------- last ------------------- # calc the mean (previous) tmp_data = self.m_image_in_port[ (top + 0) * self.m_shift : (top + 1) * self.m_shift, ] tmp_mean = np.mean(tmp_data, axis=0) # subtract mean tmp_data = self.m_image_in_port[(top + 1) * self.m_shift : nframes,] tmp_data = tmp_data - tmp_mean self.m_image_out_port.append(tmp_data) # ----------------------------------------------------------- if isinstance(self.m_shift, np.ndarray): history = "shift = NFRAMES" else: history = f"shift = {self.m_shift}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("MeanBackgroundSubtractionModule", history) self.m_image_out_port.close_port() class LineSubtractionModule(ProcessingModule): """ Pipeline module for subtracting the background emission from each pixel by computing the mean or median of all values in the row and column of the pixel. The module can for example be used if no background data is available or to remove a detector bias. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, combine: str = "median", mask: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. combine : str The method by which the column and row pixel values are combined ('median' or 'mean'). Using a mean-combination is computationally faster than a median-combination. mask : float, None The radius of the mask within which pixel values are ignored. No mask is used if set to None. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_combine = combine self.m_mask = mask @typechecked def run(self) -> None: """ Run method of the module. Selects the pixel values in the column and row at each pixel position, computes the mean or median value while excluding pixels within the radius of the mask, and subtracts the mean or median value from each pixel separately. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") im_shape = self.m_image_in_port.get_shape()[-2:] if self.m_mask: size = (self.m_mask / pixscale, None) else: size = (None, None) mask = create_mask(im_shape, size) self.apply_function_to_images( subtract_line, self.m_image_in_port, self.m_image_out_port, "Background subtraction", func_args=(mask, self.m_combine, im_shape), ) history = f"combine = {self.m_combine}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("LineSubtractionModule", history) self.m_image_out_port.close_port() class NoddingBackgroundModule(ProcessingModule): """ Pipeline module for background subtraction of data obtained with nodding (e.g., NACO AGPM data). Before using this module, the sky images should be stacked with the StackCubesModule such that each image in the stack of sky images corresponds to the mean combination of a single FITS data cube. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, science_in_tag: str, sky_in_tag: str, image_out_tag: str, mode: str = "both", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. science_in_tag : str Tag of the database entry with science images that are read as input. sky_in_tag : str Tag of the database entry with sky images that are read as input. The :class:`~pynpoint.processing.stacksubset.StackCubesModule` should be used on the sky images beforehand. image_out_tag : str Tag of the database entry with sky subtracted images that are written as output. mode : str Sky images that are subtracted, relative to the science images. Either the next, previous, or average of the next and previous cubes of sky frames can be used by choosing 'next', 'previous', or 'both', respectively. Returns ------- NoneType None """ super().__init__(name_in) self.m_science_in_port = self.add_input_port(science_in_tag) self.m_sky_in_port = self.add_input_port(sky_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_time_stamps = [] if mode in ["next", "previous", "both"]: self.m_mode = mode else: raise ValueError("Mode needs to be 'next', 'previous', or 'both'.") @typechecked def _create_time_stamp_list(self) -> None: """ Internal method for assigning a time stamp, based on the exposure number ID, to each cube of sky and science images. """ class TimeStamp: """ Class for creating a time stamp. """ @typechecked def __init__( self, time_in: Any, im_type: str, index: Union[int, slice] ) -> None: self.m_time = time_in self.m_im_type = im_type self.m_index = index @typechecked def __repr__(self) -> str: return repr((self.m_time, self.m_im_type, self.m_index)) exp_no_sky = self.m_sky_in_port.get_attribute("EXP_NO") exp_no_science = self.m_science_in_port.get_attribute("EXP_NO") nframes_sky = self.m_sky_in_port.get_attribute("NFRAMES") nframes_science = self.m_science_in_port.get_attribute("NFRAMES") if np.all(nframes_sky != 1): warnings.warn( "The NFRAMES values of the sky images are not all equal to unity. " "The StackCubesModule should be applied on the sky images before the " "NoddingBackgroundModule is used." ) for i, item in enumerate(exp_no_sky): self.m_time_stamps.append(TimeStamp(item, "SKY", i)) current = 0 for i, item in enumerate(exp_no_science): frames = slice(current, current + nframes_science[i]) self.m_time_stamps.append(TimeStamp(item, "SCIENCE", frames)) current += nframes_science[i] self.m_time_stamps = sorted( self.m_time_stamps, key=lambda time_stamp: time_stamp.m_time ) @typechecked def calc_sky_frame(self, index_of_science_data: int) -> np.ndarray: """ Method for finding the required sky frame (next, previous, or the mean of next and previous) by comparing the time stamp of the science frame with preceding and following sky frames. """ if not any(x.m_im_type == "SKY" for x in self.m_time_stamps): raise ValueError("List of time stamps does not contain any SKY images.") @typechecked def search_for_next_sky() -> np.ndarray: for i in range(index_of_science_data, len(self.m_time_stamps)): if self.m_time_stamps[i].m_im_type == "SKY": return self.m_sky_in_port[self.m_time_stamps[i].m_index,] # no next sky found, look for previous sky return search_for_previous_sky() @typechecked def search_for_previous_sky() -> np.ndarray: for i in reversed(list(range(0, index_of_science_data))): if self.m_time_stamps[i].m_im_type == "SKY": return self.m_sky_in_port[self.m_time_stamps[i].m_index,] # no previous sky found, look for next sky return search_for_next_sky() if self.m_mode == "next": return search_for_next_sky() if self.m_mode == "previous": return search_for_previous_sky() if self.m_mode == "both": previous_sky = search_for_previous_sky() next_sky = search_for_next_sky() return (previous_sky + next_sky) / 2.0 @typechecked def run(self) -> None: """ Run method of the module. Create list of time stamps, get sky and science images, and subtract the sky images from the science images. Returns ------- NoneType None """ self._create_time_stamp_list() start_time = time.time() for i, time_entry in enumerate(self.m_time_stamps): progress( i, len(self.m_time_stamps), "Subtracting background...", start_time ) if time_entry.m_im_type == "SKY": continue sky = self.calc_sky_frame(i) science = self.m_science_in_port[time_entry.m_index,] self.m_image_out_port.append(science - sky[None,], data_dim=3) history = f"mode = {self.m_mode}" self.m_image_out_port.copy_attributes(self.m_science_in_port) self.m_image_out_port.add_history("NoddingBackgroundModule", history) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/badpixel.py000066400000000000000000000353071512720201400236070ustar00rootroot00000000000000""" Pipeline modules for the detection and interpolation of bad pixels. """ import warnings from typing import Optional, Tuple import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import ( bad_pixel_sigma_filter, image_interpolation, replace_pixels, time_filter, ) class BadPixelSigmaFilterModule(ProcessingModule): """ Pipeline module for finding bad pixels with a sigma filter and replacing them with the mean value of the surrounding pixels. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, map_out_tag: Optional[str] = None, box: int = 9, sigma: float = 5.0, iterate: int = 1, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. map_out_tag : str, None Tag of the database entry with the bad pixel map that is written as output. No data is written if set to None. This output port can not be used if CPU > 1. box : int Size of the sigma filter. The area of the filter is equal to the squared value of *box*. sigma : float Sigma threshold. iterate : int Number of iterations. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) if map_out_tag is None: self.m_map_out_port = None else: self.m_map_out_port = self.add_output_port(map_out_tag) self.m_box = box self.m_sigma = sigma self.m_iterate = iterate if self.m_iterate < 1: raise ValueError("The argument of 'iterate' should be 1 or larger.") @typechecked def run(self) -> None: """ Run method of the module. Finds bad pixels with a sigma filter, replaces bad pixels with the mean value of the surrounding pixels, and writes the cleaned images to the database. Returns ------- NoneType None """ cpu = self._m_config_port.get_attribute("CPU") if cpu > 1 and self.m_map_out_port is not None: warnings.warn( "The 'map_out_port' can only be used if CPU = 1. No data will " "be stored to this output port." ) del self._m_output_ports[self.m_map_out_port.tag] self.m_map_out_port = None self.apply_function_to_images( bad_pixel_sigma_filter, self.m_image_in_port, self.m_image_out_port, "Bad pixel sigma filter", func_args=(self.m_box, self.m_sigma, self.m_iterate, self.m_map_out_port), ) history = f"sigma = {self.m_sigma}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("BadPixelSigmaFilterModule", history) if self.m_map_out_port is not None: self.m_map_out_port.copy_attributes(self.m_image_in_port) self.m_map_out_port.add_history("BadPixelSigmaFilterModule", history) self.m_image_out_port.close_port() class BadPixelMapModule(ProcessingModule): """ Pipeline module to create a bad pixel map from the dark frames and flat fields. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, dark_in_tag: Optional[str], flat_in_tag: Optional[str], bp_map_out_tag: str, dark_threshold: float = 0.2, flat_threshold: float = 0.2, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. dark_in_tag : str, None Tag of the database entry with the dark frames that are read as input. Not read if set to None. flat_in_tag : str, None Tag of the database entry with the flat fields that are read as input. Not read if set to None. bp_map_out_tag : str Tag of the database entry with the bad pixel map that is written as output. dark_threshold : float Fractional threshold with respect to the maximum pixel value in the dark frame to flag bad pixels. Pixels `brighter` than the fractional threshold are flagged as bad. flat_threshold : float Fractional threshold with respect to the maximum pixel value in the flat field to flag bad pixels. Pixels `fainter` than the fractional threshold are flagged as bad. Returns ------- NoneType None """ super().__init__(name_in) if dark_in_tag is None: self.m_dark_port = None else: self.m_dark_port = self.add_input_port(dark_in_tag) if flat_in_tag is None: self.m_flat_port = None else: self.m_flat_port = self.add_input_port(flat_in_tag) self.m_bp_map_out_port = self.add_output_port(bp_map_out_tag) self.m_dark_threshold = dark_threshold self.m_flat_threshold = flat_threshold @typechecked def run(self) -> None: """ Run method of the module. Collapses a cube of dark frames and flat fields if needed, flags bad pixels by comparing the pixel values with the threshold times the maximum value, and writes a bad pixel map to the database. For the dark frame, pixel values larger than the threshold will be flagged while for the flat frame pixel values smaller than the threshold will be flagged. Returns ------- NoneType None """ if self.m_dark_port is not None: dark = self.m_dark_port.get_all() if dark.ndim == 3: dark = np.mean(dark, axis=0) max_dark = np.max(dark) print(f"Threshold dark frame = {max_dark*self.m_dark_threshold}") bpmap = np.ones(dark.shape) bpmap[np.where(dark > max_dark * self.m_dark_threshold)] = 0 if self.m_flat_port is not None: flat = self.m_flat_port.get_all() if flat.ndim == 3: flat = np.mean(flat, axis=0) max_flat = np.max(flat) print(f"Threshold flat field (ADU) = {max_flat*self.m_flat_threshold:.2e}") if self.m_dark_port is None: bpmap = np.ones(flat.shape) bpmap[np.where(flat < max_flat * self.m_flat_threshold)] = 0 if self.m_dark_port is not None and self.m_flat_port is not None: if not dark.shape == flat.shape: raise ValueError("Dark and flat images should have the same shape.") self.m_bp_map_out_port.set_all(bpmap, data_dim=3) if self.m_dark_port is not None: self.m_bp_map_out_port.copy_attributes(self.m_dark_port) elif self.m_flat_port is not None: self.m_bp_map_out_port.copy_attributes(self.m_flat_port) history = f"dark = {self.m_dark_threshold}, flat = {self.m_flat_threshold}" self.m_bp_map_out_port.add_history("BadPixelMapModule", history) self.m_bp_map_out_port.close_port() class BadPixelInterpolationModule(ProcessingModule): """ Pipeline module to interpolate bad pixels with spectral deconvolution. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, bad_pixel_map_tag: str, image_out_tag: str, iterations: int = 1000, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the images that are read as input. bad_pixel_map_tag : str Tag of the database entry with the bad pixel map that is read as input. image_out_tag : str Tag of the database entry that is written as output. iterations : int Number of iterations of the spectral deconvolution. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_bp_map_in_port = self.add_input_port(bad_pixel_map_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_iterations = iterations @typechecked def run(self) -> None: """ Run method of the module. Interpolates bad pixels with an iterative spectral deconvolution. Returns ------- NoneType None """ bad_pixel_map = self.m_bp_map_in_port.get_all()[0,] im_shape = self.m_image_in_port.get_shape() if self.m_iterations > im_shape[1] * im_shape[2]: raise ValueError( "Maximum number of iterations needs to be smaller than the number of " "pixels in the image." ) if ( bad_pixel_map.shape[0] != im_shape[-2] or bad_pixel_map.shape[1] != im_shape[-1] ): raise ValueError( "The shape of the bad pixel map does not match the shape of the " "images." ) self.apply_function_to_images( image_interpolation, self.m_image_in_port, self.m_image_out_port, "Bad pixel interpolation", func_args=(self.m_iterations, bad_pixel_map), ) history = f"iterations = {self.m_iterations}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("BadPixelInterpolationModule", history) self.m_image_out_port.close_port() class BadPixelTimeFilterModule(ProcessingModule): """ Pipeline module for finding bad pixels with a sigma filter along a pixel line in time. This module is suitable for removing bad pixels that are only present at a position in a small number of images, for example because a dither pattern has been applied. Pixel lines can be processed in parallel by setting the CPU keyword in the configuration file. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, sigma: Tuple[float, float] = (5.0, 5.0), ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. sigma : tuple(float, float) Lower and upper sigma threshold as (lower, upper). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_sigma = sigma @typechecked def run(self) -> None: """ Run method of the module. Finds bad pixels along a pixel line, replaces the bad pixels with the mean value of the pixels (excluding the bad pixels), and writes the cleaned images to the database. Returns ------- NoneType None """ print("Temporal filtering of bad pixels ...", end="") self.apply_function_in_time( time_filter, self.m_image_in_port, self.m_image_out_port, func_args=(self.m_sigma,), ) print(" [DONE]") history = f"sigma = {self.m_sigma}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("BadPixelTimeFilterModule", history) self.m_image_out_port.close_port() class ReplaceBadPixelsModule(ProcessingModule): """ Pipeline module for replacing bad pixels with the mean are median value of the surrounding pixels. The bad pixels are selected from the input bad pixel map. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, map_in_tag: str, image_out_tag: str, size: int = 2, replace: str = "median", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. size : int Number of pixel lines around the bad pixel that are used to calculate the median or mean replacement value. For example, a 5x5 window is used if ``size=2``. replace : str Replace the bad pixel with the 'median', 'mean' or 'nan'. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_map_in_port = self.add_input_port(map_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_size = size self.m_replace = replace @typechecked def run(self) -> None: """ Run method of the module. Masks the bad pixels with NaN and replaces the bad pixels with the mean or median value (excluding the bad pixels) within a window centered on the bad pixel. The original value is used if there are only NaNs within the window. Returns ------- NoneType None """ bpmap = self.m_map_in_port.get_all()[0,] index = np.argwhere(bpmap == 0) self.apply_function_to_images( replace_pixels, self.m_image_in_port, self.m_image_out_port, "Running ReplaceBadPixelsModule", func_args=(index, self.m_size, self.m_replace), ) history = f"replace = {self.m_replace}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("ReplaceBadPixelsModule", history) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/basic.py000066400000000000000000000215261512720201400230760ustar00rootroot00000000000000""" Pipeline modules for basic image operations. """ import time from typing import Tuple from typeguard import typechecked from scipy.ndimage import rotate from pynpoint.core.processing import ProcessingModule from pynpoint.util.module import progress, memory_frames class SubtractImagesModule(ProcessingModule): """ Pipeline module for subtracting two sets of images. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tags: Tuple[str, str], image_out_tag: str, scaling: float = 1.0, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tags : tuple(str, str) Tuple with two tags of the database entry that are read as input. image_out_tag : str Tag of the database entry with the subtracted images that are written as output. scaling : float Additional scaling factor. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in1_port = self.add_input_port(image_in_tags[0]) self.m_image_in2_port = self.add_input_port(image_in_tags[1]) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_scaling = scaling @typechecked def run(self) -> None: """ Run method of the module. Subtracts the images from the second database tag from the images of the first database tag, on a frame-by-frame basis. Returns ------- NoneType None """ if self.m_image_in1_port.get_shape() != self.m_image_in2_port.get_shape(): raise ValueError("The shape of the two input tags has to be the same.") memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in1_port.get_shape()[0] frames = memory_frames(memory, nimages) start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Subtracting images...", start_time) images1 = self.m_image_in1_port[frames[i] : frames[i + 1],] images2 = self.m_image_in2_port[frames[i] : frames[i + 1],] self.m_image_out_port.append( self.m_scaling * (images1 - images2), data_dim=3 ) history = f"scaling = {self.m_scaling}" self.m_image_out_port.add_history("SubtractImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in1_port) self.m_image_out_port.close_port() class AddImagesModule(ProcessingModule): """ Pipeline module for adding two sets of images. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tags: Tuple[str, str], image_out_tag: str, scaling: float = 1.0, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tags : tuple(str, str) Tuple with two tags of the database entry that are read as input. image_out_tag : str Tag of the database entry with the added images that are written as output. scaling: float Additional scaling factor. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in1_port = self.add_input_port(image_in_tags[0]) self.m_image_in2_port = self.add_input_port(image_in_tags[1]) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_scaling = scaling @typechecked def run(self) -> None: """ Run method of the module. Add the images from the two database tags on a frame-by-frame basis. Returns ------- NoneType None """ if self.m_image_in1_port.get_shape() != self.m_image_in2_port.get_shape(): raise ValueError("The shape of the two input tags has to be the same.") memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in1_port.get_shape()[0] frames = memory_frames(memory, nimages) start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Adding images...", start_time) images1 = self.m_image_in1_port[frames[i] : frames[i + 1],] images2 = self.m_image_in2_port[frames[i] : frames[i + 1],] self.m_image_out_port.append( self.m_scaling * (images1 + images2), data_dim=3 ) history = f"scaling = {self.m_scaling}" self.m_image_out_port.add_history("AddImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in1_port) self.m_image_out_port.close_port() class RotateImagesModule(ProcessingModule): """ Pipeline module for rotating images. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, angle: float ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. angle : float Rotation angle (deg). Rotation is clockwise for positive values. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_angle = angle @typechecked def run(self) -> None: """ Run method of the module. Rotates all images by a constant angle. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Rotating images...", start_time) images = self.m_image_in_port[frames[i] : frames[i + 1],] for j in range(frames[i + 1] - frames[i]): im_tmp = images[j,] # ndimage.rotate rotates in clockwise direction for positive angles im_tmp = rotate(im_tmp, self.m_angle, reshape=False) self.m_image_out_port.append(im_tmp, data_dim=3) history = f"angle [deg] = {self.m_angle}" self.m_image_out_port.add_history("RotateImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() class RepeatImagesModule(ProcessingModule): """ Pipeline module for repeating the images from a dataset. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, repeat: int ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry with the added images that are written as output. repeat: int The number of times the input images get repeated. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_repeat = repeat @typechecked def run(self) -> None: """ Run method of the module. Repeats the stack of input images a specified number of times. Returns ------- NoneType None """ nimages = self.m_image_in_port.get_shape()[0] memory = self._m_config_port.get_attribute("MEMORY") frames = memory_frames(memory, nimages) start_time = time.time() for i in range(self.m_repeat): progress(i, self.m_repeat, "Repeating images...", start_time) for j, _ in enumerate(frames[:-1]): images = self.m_image_in_port[frames[j] : frames[j + 1],] self.m_image_out_port.append(images, data_dim=3) history = f"repeat = {self.m_repeat}" self.m_image_out_port.add_history("RepeatImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/centering.py000066400000000000000000001007751512720201400237770ustar00rootroot00000000000000""" Pipeline modules for aligning and centering of the star. """ import math import time import warnings from typing import Optional, Tuple, Union import numpy as np from astropy.modeling import fitting, models from scipy.ndimage import gaussian_filter from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.image import ( center_pixel, crop_image, pixel_distance, shift_image, subpixel_distance, ) from pynpoint.util.module import memory_frames, progress from pynpoint.util.apply_func import align_image, apply_shift, fit_2d_function class StarAlignmentModule(ProcessingModule): """ Pipeline module to align the images with a cross-correlation in Fourier space. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, ref_image_in_tag: Optional[str] = None, interpolation: str = "spline", accuracy: float = 10.0, resize: Optional[float] = None, num_references: int = 10, subframe: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the stack of images that is read as input. ref_image_in_tag : str, None Tag of the database entry with the reference image(s) that are read as input. If it is set to None, a random subsample of *num_references* elements of *image_in_tag* is taken as reference images. image_out_tag : str Tag of the database entry with the images that are written as output. interpolation : str Type of interpolation that is used for shifting the images (spline, bilinear, or fft). accuracy : float Upsampling factor for the cross-correlation. Images will be registered to within 1/accuracy of a pixel. resize : float, None Scaling factor for the up/down-sampling before the images are shifted. Not used if set to None. num_references : int Number of reference images for the cross-correlation. subframe : float, None Size (arcsec) of the subframe around the image center that is used for the cross-correlation. The full image is used if set to None. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) if ref_image_in_tag is not None: self.m_ref_image_in_port = self.add_input_port(ref_image_in_tag) else: self.m_ref_image_in_port = None self.m_interpolation = interpolation self.m_accuracy = accuracy self.m_resize = resize self.m_num_references = num_references self.m_subframe = subframe @typechecked def run(self) -> None: """ Run method of the module. Applies a cross-correlation of the input images with respect to a stack of reference images, rescales the image dimensions, and shifts the images to a common center. Returns ------- NoneType None """ if self.m_ref_image_in_port is None: random = np.random.choice( self.m_image_in_port.get_shape()[0], self.m_num_references, replace=False, ) ref_images = self.m_image_in_port[np.sort(random), :, :] else: n_ref = self.m_ref_image_in_port.get_shape()[0] if n_ref < self.m_num_references: warnings.warn( f"Number of available images ({n_ref}) is smaller than " f"num_references ({self.m_num_references}). Using all " f"available images instead." ) self.m_num_references = n_ref ref_index = np.sort( np.random.choice(n_ref, self.m_num_references, replace=False) ) ref_images = self.m_ref_image_in_port[ref_index, :, :] if self.m_subframe is not None: pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_subframe = int(self.m_subframe / pixscale) self.apply_function_to_images( align_image, self.m_image_in_port, self.m_image_out_port, "Aligning images", func_args=( self.m_interpolation, self.m_accuracy, self.m_resize, self.m_num_references, self.m_subframe, ref_images.reshape(-1), ref_images.shape, ), ) self.m_image_out_port.copy_attributes(self.m_image_in_port) if self.m_resize is not None: pixscale = self.m_image_in_port.get_attribute("PIXSCALE") new_pixscale = pixscale / self.m_resize self.m_image_out_port.add_attribute("PIXSCALE", new_pixscale) print(f"New pixel scale (arcsec) = {new_pixscale:.2f}") history = f"resize = {self.m_resize}" self.m_image_out_port.add_history("StarAlignmentModule", history) self.m_image_out_port.close_port() class FitCenterModule(ProcessingModule): """ Pipeline module for fitting the PSF with a 2D Gaussian or Moffat function. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, fit_out_tag: str, mask_out_tag: Optional[str] = None, method: str = "full", mask_radii: Tuple[Optional[float], float] = (None, 0.1), sign: str = "positive", model: str = "gaussian", filter_size: Optional[float] = None, **kwargs: Union[ Tuple[float, float, float, float, float, float, float], Tuple[float, float, float, float, float, float, float, float], float, ], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Database tag of the images that are read as input. fit_out_tag : str Database tag where the best-fit results and 1σ errors will be stored. The data are written in the following format: x offset (pix), x offset error (pix) y offset (pix), y offset error (pix), FWHM major axis (arcsec), FWHM major axis error (arcsec), FWHM minor axis (arcsec), FWHM minor axis error (arcsec), amplitude (ADU), amplitude error (ADU), angle (deg), angle error (deg) measured in counterclockwise direction with respect to the upward direction (i.e. east of north), offset (ADU), offset error (ADU), power index (only for Moffat function), and power index error (only for Moffat function). The ``fit_out_tag`` can be used as argument of ``shift_xy`` when running the :class:`~pynpoint.processing.centering.ShiftImagesModule`. mask_out_tag : str, None Database tag where the masked images will be stored. The unmasked part of the images is used for the fit. The effect of the smoothing that is applied by setting the ``fwhm`` argument is also visible in the data of the ``mask_out_tag``. The data are not stored if the argument is set to None. The :class:`~pynpoint.core.dataio.OutputPort` of ``mask_out_tag`` can only be used when ``CPU = 1``. method : str Fit and shift each image individually ('full') or only fit the mean of the cube and shift each image by this constant offset ('mean'). The 'mean' method can be used in case the images are already aligned with :class:`~pynpoint.processing.centering.StarAlignmentModule`. mask_radii : tuple(float, float), tuple(None, float) Inner and outer radius (arcsec) within and beyond which pixels are neglected during the fit. The radii are centered at the position that specified with the argument of ``guess``, which is the center of the image by default. The outer mask (second value of ``mask_radii``) is mandatory whereas radius of the inner mask is optional and can be set to None. sign : str Fit a 'positive' or 'negative' Gaussian/Moffat function. A 'negative' model can be used to center coronagraphic data in which a dark hole. model : str Type of 2D model that is used for the fit ('gaussian' or 'moffat'). Both models are elliptical in shape. filter_size : float, None Standard deviation (arcsec) of the Gaussian filter that is used to smooth the images before fitting the model. No filter is applied if set to None. Keyword arguments ----------------- guess : tuple(float, float, float, float, float, float, float, float), tuple(float, float, float, float, float, float, float, float, float) The initial parameter values for the least squares fit: x offset with respect to center (pix), y offset with respect to center (pix), FWHM x (pix), FWHM y (pix), amplitude (ADU), angle (deg), offset (ADU), and power index (only for Moffat function). Returns ------- NoneType None """ if "guess" in kwargs: self.m_guess = kwargs["guess"] else: if model == "gaussian": self.m_guess = (0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0) elif model == "moffat": self.m_guess = (0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0) if "radius" in kwargs: mask_radii = (None, kwargs["radius"]) warnings.warn( f"The 'radius' parameter has been deprecated. Please use the " f"'mask_radii' parameter instead. The argument of 'mask_radii' " f"is set to {mask_radii}.", DeprecationWarning, ) super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_fit_out_port = self.add_output_port(fit_out_tag) if mask_out_tag is None: self.m_mask_out_port = None else: self.m_mask_out_port = self.add_output_port(mask_out_tag) self.m_method = method self.m_mask_radii = mask_radii self.m_sign = sign self.m_model = model self.m_filter_size = filter_size @typechecked def run(self) -> None: """ Run method of the module. Uses a non-linear least squares (Levenberg-Marquardt) method to fit the the individual images or the mean of all images with a 2D Gaussian or Moffat function. The best-fit results and errors are stored and contain zeros in case the algorithm could not converge. The ``fit_out_tag`` can be used as argument of ``shift_xy`` when running the :class:`~pynpoint.processing.centering.ShiftImagesModule`. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") cpu = self._m_config_port.get_attribute("CPU") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") if cpu > 1 and self.m_mask_out_port is not None: warnings.warn( "The mask_out_port can only be used if CPU=1. No data will be " "stored to this output port." ) del self._m_output_ports[self.m_mask_out_port.tag] self.m_mask_out_port = None if self.m_mask_radii[0] is None: # Convert from arcsec to pixels and change None to 0 self.m_mask_radii = (0.0, self.m_mask_radii[1] / pixscale) else: # Convert from arcsec to pixels self.m_mask_radii = ( self.m_mask_radii[0] / pixscale, self.m_mask_radii[1] / pixscale, ) if self.m_filter_size: # Convert from arcsec to pixels self.m_filter_size /= pixscale _, xx_grid, yy_grid = pixel_distance( self.m_image_in_port.get_shape()[-2:], position=None ) rr_ap = subpixel_distance( self.m_image_in_port.get_shape()[-2:], position=(self.m_guess[1], self.m_guess[0]), shift_center=False, ) # (y, x) nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) if self.m_method == "full": self.apply_function_to_images( fit_2d_function, self.m_image_in_port, self.m_fit_out_port, "Fitting the stellar PSF", func_args=( self.m_mask_radii, self.m_sign, self.m_model, self.m_filter_size, self.m_guess, self.m_mask_out_port, xx_grid, yy_grid, rr_ap, pixscale, ), ) elif self.m_method == "mean": print("Fitting the stellar PSF...", end="") im_mean = np.zeros(self.m_image_in_port.get_shape()[1:3]) for i, _ in enumerate(frames[:-1]): im_mean += np.sum( self.m_image_in_port[frames[i] : frames[i + 1],], axis=0 ) best_fit = fit_2d_function( im_mean / float(nimages), 0, self.m_mask_radii, self.m_sign, self.m_model, self.m_filter_size, self.m_guess, self.m_mask_out_port, xx_grid, yy_grid, rr_ap, pixscale, ) best_fit = best_fit[np.newaxis, ...] best_fit = np.repeat(best_fit, nimages, axis=0) self.m_fit_out_port.set_all(best_fit, data_dim=2) print(" [DONE]") history = f"model = {self.m_model}" self.m_fit_out_port.copy_attributes(self.m_image_in_port) self.m_fit_out_port.add_history("FitCenterModule", history) if self.m_mask_out_port: self.m_mask_out_port.copy_attributes(self.m_image_in_port) self.m_mask_out_port.add_history("FitCenterModule", history) self.m_fit_out_port.close_port() class ShiftImagesModule(ProcessingModule): """ Pipeline module for shifting a stack of images. """ __author__ = "Tomas Stolker, Benedikt Schmidhuber" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, shift_xy: Union[Tuple[float, float], str], interpolation: str = "spline", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. shift_xy : tuple(float, float), str The shift (pix) in x and y direction as (delta_x, delta_y). Or, a database tag with the fit results from the :class:`~pynpoint.processing.centering.FitCenterModule`. interpolation : str Interpolation type for shifting of the images ('spline', 'bilinear', or 'fft'). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_interpolation = interpolation if isinstance(shift_xy, str): self.m_fit_in_port = self.add_input_port(shift_xy) self.m_shift = None else: self.m_fit_in_port = None self.m_shift = (shift_xy[1], shift_xy[0]) @typechecked def run(self) -> None: """ Run method of the module. Shifts an image with a fifth order spline, bilinear, or a Fourier shift interpolation. Returns ------- NoneType None """ constant = True # read the fit results from the self.m_fit_in_port if available if self.m_fit_in_port is not None: self.m_shift = -1.0 * self.m_fit_in_port[:, [0, 2]] # (x, y) self.m_shift = self.m_shift[:, [1, 0]] # (y, x) # check if data in self.m_fit_in_port is constant for all images using the # constant flag if not np.allclose( self.m_fit_in_port.get_all() - self.m_fit_in_port[0,], 0.0 ): constant = False if constant: # if the offset is constant then use the first element for all images self.m_shift = self.m_shift[0,] else: # if the offset is not constant, then apply the shifts to each frame individually for i, shift in enumerate(self.m_shift): shifted_image = shift_image( self.m_image_in_port[i,], shift, self.m_interpolation ) # append the shifted images to the selt.m_image_out_port database entry self.m_image_out_port.append(shifted_image, data_dim=3) mean_shift = np.mean(self.m_shift, axis=0) history = f"shift_xy = {mean_shift[0]:.2f}, {mean_shift[1]:.2f}" # apply a constant shift if constant: self.apply_function_to_images( apply_shift, self.m_image_in_port, self.m_image_out_port, "Shifting the images", func_args=(self.m_shift, self.m_interpolation), ) # if self.m_fit_in_port is None or constant: history = f"shift_xy = {self.m_shift[0]:.2f}, {self.m_shift[1]:.2f}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("ShiftImagesModule", history) self.m_image_out_port.close_port() class WaffleCenteringModule(ProcessingModule): """ Pipeline module for centering of coronagraphic data for which dedicated center frames with satellite spots are available. """ __author__ = "Alexander Bohn" @typechecked def __init__( self, name_in: str, image_in_tag: str, center_in_tag: str, image_out_tag: str, size: Optional[float] = None, center: Optional[Tuple[float, float]] = None, radius: float = 45.0, pattern: str = None, angle: float = 45.0, sigma: float = 0.06, dither: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with science images that are read as input. center_in_tag : str Tag of the database entry with the center frame that is read as input. image_out_tag : str Tag of the database entry with the centered images that are written as output. Should be different from *image_in_tag*. size : float, None Image size (arcsec) for both dimensions. Original image size is used if set to None. center : tuple(float, float), None Approximate position (x0, y0) of the coronagraph. The center of the image is used if set to None. radius : float Approximate separation (pix) of the satellite spots from the star. For IFS data, the separation of the spots in the image with the shortest wavelength is required. pattern : str, None Waffle pattern that is used ('x' or '+'). This parameter will be deprecated in a future release. Please use the ``angle`` parameter instead. The parameter will be ignored if set to None. angle : float Angle offset (deg) in clockwise direction of the satellite spots with respect to the '+' orientation (i.e. when the spots are located along the horizontal and vertical axis). The previous use of the '+' pattern corresponds to 0 degrees and 'x' pattern corresponds to 45 degrees. SPHERE/IFS data requires an angle of 55.48 degrees. sigma : float Standard deviation (arcsec) of the Gaussian kernel that is used for the unsharp masking. dither : bool Apply dithering correction based on the ``DITHER_X`` and ``DITHER_Y`` attributes. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_center_in_port = self.add_input_port(center_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_size = size self.m_center = center self.m_radius = radius self.m_pattern = pattern self.m_angle = angle self.m_sigma = sigma self.m_dither = dither @typechecked def run(self) -> None: """ Run method of the module. Locates the position of the calibration spots in the center frame. From the four spots, the position of the star behind the coronagraph is fitted, and the images are shifted and cropped. Returns ------- NoneType None """ @typechecked def _get_center( image_number: int, center: Optional[Tuple[int, int]] ) -> Tuple[np.ndarray, Tuple[int, int]]: if center_shape[-3] > 1: warnings.warn( "Multiple center images found. Using the first image of the stack." ) if ndim == 3: center_frame = self.m_center_in_port[0,] elif ndim == 4: center_frame = self.m_center_in_port[ image_number, 0, ] if center is None: center = center_pixel(center_frame) else: center = (int(np.floor(center[0])), int(np.floor(center[1]))) return center_frame, center center_shape = self.m_center_in_port.get_shape() im_shape = self.m_image_in_port.get_shape() ndim = self.m_image_in_port.get_ndim() center_frame, self.m_center = _get_center(0, self.m_center) # Read in wavelength information or set it to default values if ndim == 4: wavelength = self.m_image_in_port.get_attribute("WAVELENGTH") if wavelength is None: raise ValueError( "The wavelength information is required to centre IFS data. " "Please add it via the WavelengthReadingModule before using " "the WaffleCenteringModule." ) if im_shape[0] != center_shape[0]: raise ValueError( f"Number of science wavelength channels: {im_shape[0]}. " f"Number of center wavelength channels: {center_shape[0]}. " "Exactly one center image per wavelength is required." ) wavelength_min = np.min(wavelength) elif ndim == 3: # for none ifs data, use default value wavelength = [1.0] wavelength_min = 1.0 # check if science and center images have the same shape if im_shape[-2:] != center_shape[-2:]: raise ValueError("Science and center images should have the same shape.") # Setting angle via pattern (used for backwards compability) if self.m_pattern is not None: if self.m_pattern == "x": self.m_angle = 45.0 elif self.m_pattern == "+": self.m_angle = 0.0 else: raise ValueError( f"The pattern {self.m_pattern} is not valid. Please select " f"either 'x' or '+'." ) warnings.warn( f"The 'pattern' parameter will be deprecated in a future release. " f"Please Use the 'angle' parameter instead and set it to " f"{self.m_angle} degrees.", DeprecationWarning, ) pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_sigma /= pixscale if self.m_size is not None: self.m_size = int(math.ceil(self.m_size / pixscale)) if self.m_dither: dither_x = self.m_image_in_port.get_attribute("DITHER_X") dither_y = self.m_image_in_port.get_attribute("DITHER_Y") nframes = self.m_image_in_port.get_attribute("NFRAMES") nframes = np.cumsum(nframes) nframes = np.insert(nframes, 0, 0) # size of center image, only works with odd value ref_image_size = 21 # Arrays for the positions x_pos = np.zeros(4) y_pos = np.zeros(4) # Arrays for the center position for each wavelength x_center = np.zeros((len(wavelength))) y_center = np.zeros((len(wavelength))) # Loop for 4 waffle spots for w, wave_nr in enumerate(wavelength): # Prapre centering frame center_frame, _ = _get_center(w, self.m_center) center_frame_unsharp = center_frame - gaussian_filter( input=center_frame, sigma=self.m_sigma ) for i in range(4): # Approximate positions of waffle spots radius = self.m_radius * wave_nr / wavelength_min x_0 = np.floor( self.m_center[0] + radius * np.cos(self.m_angle * np.pi / 180 + np.pi / 4.0 * (2 * i)) ) y_0 = np.floor( self.m_center[1] + radius * np.sin(self.m_angle * np.pi / 180 + np.pi / 4.0 * (2 * i)) ) tmp_center_frame = crop_image( image=center_frame_unsharp, center=(int(y_0), int(x_0)), size=ref_image_size, ) # find maximum in tmp image coords = np.unravel_index( indices=np.argmax(tmp_center_frame), shape=tmp_center_frame.shape ) y_max, x_max = coords[0], coords[1] pixmax = tmp_center_frame[y_max, x_max] max_pos = np.array([x_max, y_max]).reshape(1, 2) # Check whether it is the correct maximum: second brightest pixel should be nearby tmp_center_frame[y_max, x_max] = 0.0 # introduce distance parameter dist = np.inf while dist > 2: coords = np.unravel_index( indices=np.argmax(tmp_center_frame), shape=tmp_center_frame.shape, ) y_max_new, x_max_new = coords[0], coords[1] pixmax_new = tmp_center_frame[y_max_new, x_max_new] # Caculate minimal distance to previous points tmp_center_frame[y_max_new, x_max_new] = 0.0 dist = np.amin( np.linalg.norm( np.vstack( (max_pos[:, 0] - x_max_new, max_pos[:, 1] - y_max_new) ), axis=0, ) ) if dist <= 2 and pixmax_new < pixmax: break max_pos = np.vstack((max_pos, [x_max_new, y_max_new])) x_max = x_max_new y_max = y_max_new pixmax = pixmax_new x_0 = x_0 - (ref_image_size - 1) / 2 + x_max y_0 = y_0 - (ref_image_size - 1) / 2 + y_max # create reference image around determined maximum ref_center_frame = crop_image( image=center_frame_unsharp, center=(int(y_0), int(x_0)), size=ref_image_size, ) # Fit the data using astropy.modeling gauss_init = models.Gaussian2D( amplitude=np.amax(ref_center_frame), x_mean=x_0, y_mean=y_0, x_stddev=1.0, y_stddev=1.0, theta=0.0, ) fit_gauss = fitting.LevMarLSQFitter() y_grid, x_grid = np.mgrid[ y_0 - (ref_image_size - 1) / 2 : y_0 + (ref_image_size - 1) / 2 + 1, x_0 - (ref_image_size - 1) / 2 : x_0 + (ref_image_size - 1) / 2 + 1, ] gauss = fit_gauss(gauss_init, x_grid, y_grid, ref_center_frame) x_pos[i] = gauss.x_mean.value y_pos[i] = gauss.y_mean.value # Find star position as intersection of two lines x_center[w] = ( ( y_pos[0] - x_pos[0] * (y_pos[2] - y_pos[0]) / (x_pos[2] - float(x_pos[0])) ) - ( y_pos[1] - x_pos[1] * (y_pos[1] - y_pos[3]) / (x_pos[1] - float(x_pos[3])) ) ) / ( (y_pos[1] - y_pos[3]) / (x_pos[1] - float(x_pos[3])) - (y_pos[2] - y_pos[0]) / (x_pos[2] - float(x_pos[0])) ) y_center[w] = x_center[w] * (y_pos[1] - y_pos[3]) / ( x_pos[1] - float(x_pos[3]) ) + ( y_pos[1] - x_pos[1] * (y_pos[1] - y_pos[3]) / (x_pos[1] - float(x_pos[3])) ) # Adjust science images nimages = self.m_image_in_port.get_shape()[-3] npix = self.m_image_in_port.get_shape()[-2] nwavelengths = len(wavelength) start_time = time.time() for i in range(nimages): im_storage = [] for j in range(nwavelengths): im_index = i * nwavelengths + j progress( im_index, nimages * nwavelengths, "Centering the images...", start_time, ) if ndim == 3: image = self.m_image_in_port[i,] elif ndim == 4: image = self.m_image_in_port[ j, i, ] shift_yx = np.array( [ (float(im_shape[-2]) - 1.0) / 2.0 - y_center[j], (float(im_shape[-1]) - 1.0) / 2.0 - x_center[j], ] ) if self.m_dither: index = np.digitize(i, nframes, right=False) - 1 shift_yx[0] -= dither_y[index] shift_yx[1] -= dither_x[index] if npix % 2 == 0 and self.m_size is not None: im_tmp = np.zeros((image.shape[0] + 1, image.shape[1] + 1)) im_tmp[:-1, :-1] = image image = im_tmp shift_yx[0] += 0.5 shift_yx[1] += 0.5 im_shift = shift_image(image, shift_yx, "spline") if self.m_size is not None: im_crop = crop_image(im_shift, None, self.m_size) im_storage.append(im_crop) else: im_storage.append(im_shift) if ndim == 3: self.m_image_out_port.append(im_storage[0], data_dim=3) elif ndim == 4: self.m_image_out_port.append(np.asarray(im_storage), data_dim=4) print(f"Center [x, y] = [{x_center}, {y_center}]") history = f"[x, y] = [{round(x_center[j], 2)}, {round(y_center[j], 2)}]" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("WaffleCenteringModule", history) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/darkflat.py000066400000000000000000000140111512720201400235740ustar00rootroot00000000000000""" Pipeline modules for dark frame and flat field calibrations. """ import time import warnings from typing import Tuple import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.module import progress, memory_frames @typechecked def _master_frame(data: np.ndarray, im_shape: Tuple[int, int, int]) -> np.ndarray: """ Internal function which creates a master dark/flat by calculating the mean (3D data) and cropping the frames to the shape of the science images if needed. Parameters ---------- data : numpy.ndarray Input array (2D) with mean of the dark or flat frames. im_shape : tuple(int, int, int) Shape of the science images (3D). Returns ------- numpy.ndarray Master dark/flat frame. """ shape_in = (im_shape[1], im_shape[2]) if data.shape[0] < shape_in[0] or data.shape[1] < shape_in[1]: raise ValueError( "Shape of the calibration images is smaller than the science images." ) if data.shape != shape_in: cal_shape = data.shape x_off = (cal_shape[0] - shape_in[0]) // 2 y_off = (cal_shape[1] - shape_in[1]) // 2 data = data[x_off : x_off + shape_in[0], y_off : y_off + shape_in[1]] warnings.warn( "The calibration images were cropped around their center to match the shape " "of the science images." ) return data class DarkCalibrationModule(ProcessingModule): """ Pipeline module to subtract a master dark from the science data. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, dark_in_tag: str, image_out_tag: str ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the science images that are read as input. dark_in_tag : str Tag of the database with the dark frames that are read as input. image_out_tag : str Tag of the database entry that is written as output. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_dark_in_port = self.add_input_port(dark_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) @typechecked def run(self) -> None: """ Run method of the module. Creates a master dark with the same shape as the science data and subtracts the dark frame from the science data. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) dark = self.m_dark_in_port.get_all() master = _master_frame( data=np.mean(dark, axis=0), im_shape=self.m_image_in_port.get_shape() ) start_time = time.time() for i in range(len(frames[:-1])): progress(i, len(frames[:-1]), "Subtracting the dark current...", start_time) images = self.m_image_in_port[frames[i] : frames[i + 1],] self.m_image_out_port.append(images - master, data_dim=3) history = f"dark_in_tag = {self.m_dark_in_port.tag}" self.m_image_out_port.add_history("DarkCalibrationModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() class FlatCalibrationModule(ProcessingModule): """ Pipeline module to apply a flat field correction to the science data. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, flat_in_tag: str, image_out_tag: str ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the science database that is read as input. flat_in_tag : str Tag of the flat field database that is read as input. image_out_tag : str Tag of the database entry that is written as output. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_flat_in_port = self.add_input_port(flat_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) @typechecked def run(self) -> None: """ Run method of the module. Creates a master flat with the same shape as the science image and divides the science images by the flat field. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) flat = self.m_flat_in_port.get_all() master = _master_frame( data=np.mean(flat, axis=0), im_shape=self.m_image_in_port.get_shape() ) # shift all values to greater or equal to +1.0 flat_min = np.amin(master) master -= flat_min - 1.0 # normalization, median value is 1 afterwards master /= np.median(master) start_time = time.time() for i in range(len(frames[:-1])): progress(i, len(frames[:-1]), "Flat fielding the images...", start_time) images = self.m_image_in_port[frames[i] : frames[i + 1],] self.m_image_out_port.append(images / master, data_dim=3) history = f"flat_in_tag = {self.m_flat_in_port.tag}" self.m_image_out_port.add_history("FlatCalibrationModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/extract.py000066400000000000000000000214071512720201400234650ustar00rootroot00000000000000""" Pipeline modules for locating and extracting the position of a star. """ import math import warnings from typing import Optional, Tuple, Union import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import crop_around_star, crop_rotating_star from pynpoint.util.image import rotate_coordinates class StarExtractionModule(ProcessingModule): """ Pipeline module to locate the position of the star in each image and to crop all the images around this position. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, index_out_tag: Optional[str] = None, image_size: float = 2.0, fwhm_star: float = 0.2, position: Optional[ Union[Tuple[int, int, float], Tuple[None, None, float]] ] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the dataset with the input images. image_out_tag : str Tag of the dataset that is stored as output, containing the extracted images. index_out_tag : str, None List with image indices for which the image size is too large to be cropped around the brightest pixel. No data is written if set to None. This tag name can be provided to the ``frames``` parameter in :class:`~pynpoint.processing.frameselection.RemoveFramesModule`. This argument is ignored if ``CPU`` is set to a value larger than 1. image_size : float Cropped image size (arcsec). fwhm_star : float Full width at half maximum (arcsec) of the Gaussian kernel that is used to smooth the images to lower contributions of bad pixels. position : tuple(int, int, float), None Subframe that is selected to search for the star. The tuple should contain a position (pix) and size (arcsec) as (pos_x, pos_y, size). The full image is used if set to None. The center of the image will be used with ``position=(None, None, size)``. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) if index_out_tag is None: self.m_index_out_port = None else: self.m_index_out_port = self.add_output_port(index_out_tag) self.m_image_size = image_size self.m_fwhm_star = fwhm_star self.m_position = position @typechecked def run(self) -> None: """ Run method of the module. Locates the position of the star (only pixel precision) by selecting the highest pixel value. A Gaussian kernel with a FWHM similar to the PSF is used to lower the contribution of bad pixels which may have higher values than the peak of the PSF. Images are cropped and written to an output port. Returns ------- NoneType None """ cpu = self._m_config_port.get_attribute("CPU") if cpu > 1 and self.m_index_out_port is not None: warnings.warn( "The 'index_out_port' can only be used if CPU = 1. No data will " "be stored to this output port." ) del self._m_output_ports[self.m_index_out_port.tag] self.m_index_out_port = None pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_image_size = int(math.ceil(self.m_image_size / pixscale)) self.m_fwhm_star = int(math.ceil(self.m_fwhm_star / pixscale)) self.apply_function_to_images( crop_around_star, self.m_image_in_port, self.m_image_out_port, "Extracting stellar position", func_args=( self.m_position, self.m_image_size, self.m_fwhm_star, pixscale, self.m_index_out_port, self.m_image_out_port, ), ) history = f"fwhm_star (pix) = {self.m_fwhm_star}" if self.m_index_out_port is not None: self.m_index_out_port.copy_attributes(self.m_image_in_port) self.m_index_out_port.add_history("StarExtractionModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("StarExtractionModule", history) self.m_image_out_port.close_port() class ExtractBinaryModule(ProcessingModule): """ Pipeline module to extract a binary star (or another point source) which is rotating across the image stack. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, pos_center: Tuple[float, float], pos_binary: Tuple[float, float], image_size: float = 2.0, search_size: float = 0.1, filter_size: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the dataset with the input images. image_out_tag : str Tag of the dataset that is stored as output, containing the extracted images. pos_center : tuple(float, float) Approximate position (x, y) of the center of rotation (pix). pos_binary : tuple(float, float) Approximate position (x, y) of the binary star in the first image (pix). image_size : float Cropped image size (arcsec). search_size : float Window size (arcsec) in which the brightest pixel is selected as position of the binary star. The search window is centered on the position that for each image is calculated from the ``pos_center``, ``pos_binary``, and parallactic angle (``PARANG``) of the image. filter_size : float, None Full width at half maximum (arcsec) of the Gaussian kernel that is used to smooth the images to lower contributions of bad pixels. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_pos_center = (pos_center[1], pos_center[0]) # (y, x) self.m_pos_binary = (pos_binary[1], pos_binary[0]) # (y, x) self.m_image_size = image_size self.m_search_size = search_size self.m_filter_size = filter_size @typechecked def run(self) -> None: """ Run method of the module. Locates the position of a binary star (or some other point source) which rotates across the stack of images due to parallactic rotation. The approximate position of the binary star is calculated by taking into account the parallactic angle of each image separately. The brightest pixel is then selected as center around which the image is cropped. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") parang = self.m_image_in_port.get_attribute("PARANG") positions = np.zeros((parang.shape[0], 2), dtype=int) for i, item in enumerate(parang): # rotates in counterclockwise direction, hence the minus sign in angle positions[i, :] = rotate_coordinates( center=self.m_pos_center, position=self.m_pos_binary, angle=item - parang[0], ) self.m_image_size = int(math.ceil(self.m_image_size / pixscale)) self.m_search_size = int(math.ceil(self.m_search_size / pixscale)) if self.m_filter_size is not None: self.m_filter_size = int(math.ceil(self.m_filter_size / pixscale)) self.apply_function_to_images( crop_rotating_star, self.m_image_in_port, self.m_image_out_port, "Extracting binary position", func_args=( positions, self.m_image_size, self.m_filter_size, self.m_search_size, ), ) history = f"filter (pix) = {self.m_filter_size}" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("ExtractBinaryModule", history) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/filter.py000066400000000000000000000046601512720201400233020ustar00rootroot00000000000000""" Pipeline modules for spatial filtering of images. """ import math import time from typeguard import typechecked from scipy.ndimage import gaussian_filter from pynpoint.core.processing import ProcessingModule from pynpoint.util.module import memory_frames, progress class GaussianFilterModule(ProcessingModule): """ Pipeline module for applying a Gaussian filter. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, fwhm: float = 1.0 ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : tuple(str, str) Tuple with two tags of the database entry that are read as input. image_out_tag : str Tag of the database entry with the subtracted images that are written as output. fwhm : float Full width at half maximum (arcsec) of the Gaussian kernel. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_fwhm = fwhm @typechecked def run(self) -> None: """ Run method of the module. Applies a Gaussian filter to the spatial dimensions of the images. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") pixscale = self._m_config_port.get_attribute("PIXSCALE") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) sigma = (self.m_fwhm / pixscale) / ( 2.0 * math.sqrt(2.0 * math.log(2.0)) ) # [pix] start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Applying Gaussian filter...", start_time) images = self.m_image_in_port[frames[i] : frames[i + 1],] im_filter = gaussian_filter(images, (0, sigma, sigma)) self.m_image_out_port.append(im_filter, data_dim=3) history = f"fwhm [arcsec] = {self.m_fwhm}" self.m_image_out_port.add_history("GaussianFilterModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/fluxposition.py000066400000000000000000001714651512720201400245700ustar00rootroot00000000000000""" Pipeline modules for photometric and astrometric measurements. """ import os import time import warnings from typing import Any, List, Optional, Tuple, Union from multiprocessing import Pool import numpy as np import emcee from typeguard import typechecked from scipy.optimize import minimize from sklearn.decomposition import PCA from photutils.aperture import CircularAperture from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import photometry from pynpoint.util.analysis import ( fake_planet, merit_function, false_alarm, pixel_variance, ) from pynpoint.util.image import ( create_mask, polar_to_cartesian, cartesian_to_polar, center_subpixel, rotate_coordinates, ) from pynpoint.util.mcmc import lnprob from pynpoint.util.module import progress, memory_frames from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.residuals import combine_residuals class FakePlanetModule(ProcessingModule): """ Pipeline module to inject a positive or negative artificial planet into a stack of images. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, psf_in_tag: str, image_out_tag: str, position: Tuple[float, float], magnitude: float, psf_scaling: float = 1.0, interpolation: str = "spline", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with images that are read as input. psf_in_tag : str Tag of the database entry that contains the reference PSF that is used as fake planet. Can be either a single image (2D) or a cube (3D) with the dimensions equal to ``image_in_tag``. image_out_tag : str Tag of the database entry with images that are written as output. position : tuple(float, float) Angular separation (arcsec) and position angle (deg) of the fake planet. Angle is measured in counterclockwise direction with respect to the upward direction (i.e., East of North). magnitude : float Magnitude of the fake planet with respect to the star. psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). A negative value will inject a negative planet signal. interpolation : str Type of interpolation that is used for shifting the images (spline, bilinear, or fft). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) if psf_in_tag == image_in_tag: self.m_psf_in_port = self.m_image_in_port else: self.m_psf_in_port = self.add_input_port(psf_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_position = position self.m_magnitude = magnitude self.m_psf_scaling = psf_scaling self.m_interpolation = interpolation @typechecked def run(self) -> None: """ Run method of the module. Shifts the PSF template to the location of the fake planet with an additional correction for the parallactic angle and an optional flux scaling. The stack of images with the injected planet signal is stored. Returns ------- NoneType None """ print("Input parameters:") print(f" - Magnitude = {self.m_magnitude:.2f}") print(f" - PSF scaling = {self.m_psf_scaling}") print(f" - Separation (arcsec) = {self.m_position[0]:.2f}") print(f" - Position angle (deg) = {self.m_position[1]:.2f}") memory = self._m_config_port.get_attribute("MEMORY") parang = self.m_image_in_port.get_attribute("PARANG") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_position = (self.m_position[0] / pixscale, self.m_position[1]) im_shape = self.m_image_in_port.get_shape() psf_shape = self.m_psf_in_port.get_shape() if psf_shape[0] != 1 and psf_shape[0] != im_shape[0]: raise ValueError( "The number of frames in psf_in_tag does not match with the number " "of frames in image_in_tag. The DerotateAndStackModule can be " "used to average the PSF frames (without derotating) before applying " "the FakePlanetModule." ) if psf_shape[-2:] != im_shape[-2:]: raise ValueError( f"The images in '{self.m_image_in_port.tag}' should have the same " f"dimensions as the images images in '{self.m_psf_in_port.tag}'." ) frames = memory_frames(memory, im_shape[0]) start_time = time.time() for j, _ in enumerate(frames[:-1]): progress(j, len(frames[:-1]), "Injecting artificial planets...", start_time) images = self.m_image_in_port[frames[j] : frames[j + 1]] angles = parang[frames[j] : frames[j + 1]] if psf_shape[0] == 1: psf = self.m_psf_in_port.get_all() else: psf = self.m_psf_in_port[frames[j] : frames[j + 1]] im_fake = fake_planet( images=images, psf=psf, parang=angles, position=self.m_position, magnitude=self.m_magnitude, psf_scaling=self.m_psf_scaling, interpolation="spline", ) self.m_image_out_port.append(im_fake, data_dim=3) history = ( f"(sep, angle, mag) = ({self.m_position[0]*pixscale:.2f}, " f"{self.m_position[1]:.2f}, {self.m_magnitude:.2f})" ) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("FakePlanetModule", history) self.m_image_out_port.close_port() class SimplexMinimizationModule(ProcessingModule): """ Pipeline module to retrieve the contrast and position of a planet by injecting negative artificial planets and using a downhill simplex method. The module supports both ADI and RDI. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, psf_in_tag: str, res_out_tag: str, flux_position_tag: str, position: Tuple[float, float], magnitude: float, psf_scaling: float = -1.0, merit: str = "gaussian", aperture: float = 0.1, sigma: float = 0.0, tolerance: float = 0.1, pca_number: Union[int, range, List[int]] = 10, cent_size: Optional[float] = None, edge_size: Optional[float] = None, extra_rot: float = 0.0, residuals: str = "median", reference_in_tag: Optional[str] = None, offset: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the science images that are read as input. psf_in_tag : str Tag of the database entry with the reference PSF that is used as fake planet. Can be either a single image or a stack of images equal in size to ``image_in_tag``. res_out_tag : str Tag of the database entry with the image residuals that are written as output. The residuals are stored for each step of the minimization. The last image contains the best-fit residuals. flux_position_tag : str Tag of the database entry with the flux and position results that are written as output. Each step of the minimization stores the x position (pixels), y position (pixels), separation (arcsec), angle (deg), contrast (mag), and the chi-square value. The last row contains the best-fit results. position : tuple(float, float) Approximate position of the planet (x, y), provided with subpixel precision (i.e. as floats). The figure of merit is calculated within an aperture of radius ``aperture`` centered at the rounded (i.e. integers) coordinates of ``position``. When setting, ``offset=0.``, the ``position`` is used as fixed position of the planet while only retrieving the contrast. magnitude : float Approximate magnitude of the planet relative to the star. psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should be a negative value in order to inject negative fake planets. merit : str Figure of merit for the minimization ('hessian', 'gaussian', or 'poisson'). Either the determinant of the Hessian matrix is minimized ('hessian') or the flux of each pixel ('gaussian' or 'poisson'). For the latter case, the estimated noise is assumed to follow a Poisson (see Wertz et al. 2017) or Gaussian distribution (see Stolker et al. 2020). aperture : float Aperture radius (arcsec) at the position specified at ``position``. sigma : float Standard deviation (arcsec) of the Gaussian kernel that is used to smooth the images before the figure of merit is calculated (in order to reduce small pixel-to-pixel variations). tolerance : float Absolute error on the input parameters, position (pixels) and contrast (mag), that is used as acceptance level for convergence. Note that only a single value can be specified which is used for both the position and flux so ``tolerance=0.1`` corresponds to a precision of 0.1 mag and 0.1 pix. The tolerance on the output (i.e., the chi-square value) is set to ``np.inf`` such that the condition is always met. pca_number : int, range, list(int, ) Number of principal components (PCs) used for the PSF subtraction. Can be either a single value, or a range or list of values. In the latter case, the ``res_out_tag`` and ``flux_position_tag`` contain a 3 digit number with the number of PCs. cent_size : float, None Radius of the central mask (arcsec). No mask is used when set to ``None``. The mask is applied after the artificial planet is injected. edge_size : float, None Outer radius (arcsec) beyond which pixels are masked. No outer mask is used when set to ``None``. The radius will be set to half the image size if the argument is larger than half the image size. The mask is applied after the artificial planet is injected. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). residuals : str Method for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). reference_in_tag : str, None Tag of the database entry with the reference images that are read as input. The data of the ``image_in_tag`` itself is used as reference data for the PSF subtraction if set to ``None``. Note that the mean is not subtracted from the data of ``image_in_tag`` and ``reference_in_tag`` in case the ``reference_in_tag`` is used, to allow for flux and position measurements in the context of RDI. offset : float, None Offset (pixels) by which the injected negative PSF may deviate from ``position``. The constraint on the position is not applied if set to None. Only the contrast is optimized and the position if fixed to ``position`` if ``offset=0``. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) if psf_in_tag == image_in_tag: self.m_psf_in_port = self.m_image_in_port else: self.m_psf_in_port = self.add_input_port(psf_in_tag) if reference_in_tag is None: self.m_reference_in_port = None else: self.m_reference_in_port = self.add_input_port(reference_in_tag) self.m_res_out_port = [] self.m_flux_pos_port = [] if isinstance(pca_number, int): self.m_res_out_port.append(self.add_output_port(res_out_tag)) self.m_flux_pos_port.append(self.add_output_port(flux_position_tag)) else: for item in pca_number: self.m_res_out_port.append( self.add_output_port(res_out_tag + f"{item:03d}") ) self.m_flux_pos_port.append( self.add_output_port(flux_position_tag + f"{item:03d}") ) self.m_position = position self.m_magnitude = magnitude self.m_psf_scaling = psf_scaling self.m_merit = merit self.m_aperture = aperture self.m_sigma = sigma self.m_tolerance = tolerance self.m_cent_size = cent_size self.m_edge_size = edge_size self.m_extra_rot = extra_rot self.m_residuals = residuals self.m_offset = offset if isinstance(pca_number, int): self.m_pca_number = [pca_number] else: self.m_pca_number = pca_number @typechecked def run(self) -> None: """ Run method of the module. The position and contrast of a planet is measured by injecting negative copies of the PSF template and applying a downhill simplex method (Nelder-Mead) for minimization of a figure of merit at the planet location. Returns ------- NoneType None """ print("Input parameters:") print(f" - Number of principal components = {self.m_pca_number}") print(f" - Figure of merit = {self.m_merit}") print(f" - Residuals type = {self.m_residuals}") print(f" - Absolute tolerance (pixels/mag) = {self.m_tolerance}") print(f" - Maximum offset = {self.m_offset}") print( f" - Guessed position (x, y) = ({self.m_position[0]:.2f}, " f"{self.m_position[1]:.2f})" ) parang = self.m_image_in_port.get_attribute("PARANG") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") aperture = ( round(self.m_position[1]), round(self.m_position[0]), self.m_aperture / pixscale, ) print(f" - Aperture position (x, y) = ({aperture[1]}, {aperture[0]})") print(f" - Aperture radius (pixels) = {int(aperture[2])}") self.m_sigma /= pixscale if self.m_cent_size is not None: self.m_cent_size /= pixscale print(f" - Inner mask radius (pixels) = {int(self.m_cent_size)}") if self.m_edge_size is not None: self.m_edge_size /= pixscale print(f" - Outer mask radius (pixels) = {int(self.m_edge_size)}") psf = self.m_psf_in_port.get_all() images = self.m_image_in_port.get_all() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError( "The number of frames in psf_in_tag does not match with the number " "of frames in image_in_tag. The DerotateAndStackModule can be " "used to average the PSF frames (without derotating) before applying " "the SimplexMinimizationModule." ) center = center_subpixel(psf) print(f"Image center (y, x) = {center}") # Rotate the initial position, (y, x), by the extra rotation angle to (y_rot, x_rot) pos_init = rotate_coordinates( center, (self.m_position[1], self.m_position[0]), self.m_extra_rot ) if self.m_reference_in_port is not None and self.m_merit != "poisson": raise NotImplementedError( "The reference_in_tag can only be used in combination with " "the 'poisson' figure of merit." ) @typechecked def _objective( arg: np.ndarray, count: int, n_components: int, sklearn_pca: Optional[PCA], var_noise: Optional[float], ) -> float: # Extract the contrast, y position, and x position from the argument tuple mag = arg[0] if self.m_offset is None or self.m_offset > 0.0: pos_y = arg[1] pos_x = arg[2] else: pos_y = pos_init[0] pos_x = pos_init[1] # Calculate the absolute offset (pixels) with respect to the initial guess pos_offset = np.sqrt( (pos_x - pos_init[1]) ** 2 + (pos_y - pos_init[0]) ** 2 ) if self.m_offset is not None and pos_offset > self.m_offset: # Return chi-square = inf if the offset needs to be tested and is too large return np.inf # Convert the cartesian position to a separation and position angle sep_ang = cartesian_to_polar(center, pos_y, pos_x) # Inject the negative artifical planet at the position and contrast that is tested fake = fake_planet( images=images, psf=psf, parang=parang, position=(sep_ang[0], sep_ang[1]), magnitude=mag, psf_scaling=self.m_psf_scaling, ) # Create a mask mask = create_mask(fake.shape[-2:], (self.m_cent_size, self.m_edge_size)) if self.m_reference_in_port is None: # PSF subtraction with the science data as reference data (ADI) im_res_rot, im_res_derot = pca_psf_subtraction( images=fake * mask, angles=-1.0 * parang + self.m_extra_rot, pca_number=n_components, pca_sklearn=sklearn_pca, im_shape=None, indices=None, ) else: # PSF subtraction with separate reference data (RDI) im_reshape = np.reshape( fake * mask, (im_shape[0], im_shape[1] * im_shape[2]) ) im_res_rot, im_res_derot = pca_psf_subtraction( images=im_reshape, angles=-1.0 * parang + self.m_extra_rot, pca_number=n_components, pca_sklearn=sklearn_pca, im_shape=im_shape, indices=None, ) # Collapse the residuals of the PSF subtraction res_stack = combine_residuals( method=self.m_residuals, res_rot=im_res_derot, residuals=im_res_rot, angles=parang, ) # Appedn the collapsed residuals to the output port self.m_res_out_port[count].append(res_stack, data_dim=3) # Calculate the chi-square for the tested position and contrast chi_sq = merit_function( residuals=res_stack[0,], merit=self.m_merit, aperture=aperture, sigma=self.m_sigma, var_noise=var_noise, ) # Apply the extra rotation to the y and x position # The returned position is given as (y, x) position = rotate_coordinates(center, (pos_y, pos_x), -self.m_extra_rot) # Create and array with the x position, y position, separation (arcsec), position # angle (deg), contrast (mag), and chi-square res = np.asarray( [ position[1], position[0], sep_ang[0] * pixscale, (sep_ang[1] - self.m_extra_rot) % 360.0, mag, chi_sq, ] ) # Append the results to the output port self.m_flux_pos_port[count].append(res, data_dim=2) print( f"\rSimplex minimization... {n_components} PC - chi^2 = {chi_sq:.2e}", end="", ) return chi_sq for i, n_components in enumerate(self.m_pca_number): print(f"\rSimplex minimization... {n_components} PC ", end="") if self.m_reference_in_port is None: sklearn_pca = None else: ref_data = self.m_reference_in_port.get_all() im_shape = images.shape ref_shape = ref_data.shape if ref_shape[1:] != im_shape[1:]: raise ValueError( "The image size of the science data and the reference data " "should be identical." ) # reshape reference data and select the unmasked pixels ref_reshape = ref_data.reshape( ref_shape[0], ref_shape[1] * ref_shape[2] ) mean_ref = np.mean(ref_reshape, axis=0) ref_reshape -= mean_ref # create the PCA basis sklearn_pca = PCA(n_components=n_components, svd_solver="arpack") sklearn_pca.fit(ref_reshape) # add mean of reference array as 1st PC and orthogonalize it to the PCA basis mean_ref_reshape = mean_ref.reshape((1, mean_ref.shape[0])) q_ortho, _ = np.linalg.qr( np.vstack((mean_ref_reshape, sklearn_pca.components_[:-1,])).T ) sklearn_pca.components_ = q_ortho.T if self.m_merit == "poisson": var_noise = None elif self.m_merit in ["gaussian", "hessian"]: var_noise = pixel_variance( var_type=self.m_merit, images=images, parang=parang, cent_size=self.m_cent_size, edge_size=self.m_edge_size, pca_number=n_components, residuals=self.m_residuals, aperture=aperture, sigma=self.m_sigma, ) if self.m_offset == 0.0: x0_minimize = np.array([self.m_magnitude]) else: x0_minimize = np.array([self.m_magnitude, pos_init[0], pos_init[1]]) min_result = minimize( fun=_objective, x0=x0_minimize, args=(i, n_components, sklearn_pca, var_noise), method="Nelder-Mead", tol=None, options={"xatol": self.m_tolerance, "fatol": float("inf")}, ) print(" [DONE]") if self.m_offset == 0.0: pos_x = pos_init[1] pos_y = pos_init[0] else: pos_x = min_result.x[2] pos_y = min_result.x[1] pos_rot_yx = rotate_coordinates(center, (pos_y, pos_x), -self.m_extra_rot) sep_ang = cartesian_to_polar(center, pos_rot_yx[0], pos_rot_yx[1]) print("Best-fit parameters:") print(f" - Position (x, y) = ({pos_rot_yx[1]:.2f}, {pos_rot_yx[0]:.2f})") print(f" - Separation (mas) = {sep_ang[0]*pixscale*1e3:.2f}") print(f" - Position angle (deg) = {sep_ang[1]:.2f}") print(f" - Contrast (mag) = {min_result.x[0]:.2f}") history = f"merit = {self.m_merit}" for item in self.m_flux_pos_port: item.copy_attributes(self.m_image_in_port) item.add_history("SimplexMinimizationModule", history) for item in self.m_res_out_port: item.copy_attributes(self.m_image_in_port) item.add_history("SimplexMinimizationModule", history) self.m_res_out_port[0].close_port() class FalsePositiveModule(ProcessingModule): """ Pipeline module to calculate the signal-to-noise ratio (SNR) and false positive fraction (FPF) at a specified location in an image by using the Student's t-test (Mawet et al. 2014). Optionally, the SNR can be optimized with the aperture position as free parameter. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, snr_out_tag: str, position: Tuple[float, float], aperture: float = 0.1, ignore: bool = False, optimize: bool = False, **kwargs: Any, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the images that are read as input. The SNR/FPF is calculated for each image in the dataset. snr_out_tag : str Tag of the database entry that is written as output. The output format is: (x position (pix), y position (pix), separation (arcsec), position angle (deg), SNR, FPF). The position angle is measured in counterclockwise direction with respect to the upward direction (i.e., East of North). position : tuple(float, float) The x and y position (pix) where the SNR and FPF is calculated. Note that the bottom left of the image is defined as (-0.5, -0.5) so there is a -1.0 offset with respect to the DS9 coordinate system. Aperture photometry corrects for the partial inclusion of pixels at the boundary. aperture : float Aperture radius (arcsec). ignore : bool Ignore the two neighboring apertures that may contain self-subtraction from the planet. optimize : bool Optimize the SNR. The aperture position is stored in the `snr_out_tag`. The size of the aperture is kept fixed. Keyword arguments ----------------- tolerance : float The absolute tolerance on the position for the optimization to end. Default is set to 0.01 (pix). offset : float, None Offset (pix) by which the aperture may deviate from ``position`` when ``optimize=True`` (default: None). Returns ------- NoneType None """ if "tolerance" in kwargs: self.m_tolerance = kwargs["tolerance"] else: self.m_tolerance = 1e-2 if "offset" in kwargs: self.m_offset = kwargs["offset"] else: self.m_offset = None if "bounds" in kwargs: warnings.warn( "The 'bounds' keyword argument has been deprecated. Please use " "'offset' instead (e.g. offset=3.0).", DeprecationWarning, ) super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_snr_out_port = self.add_output_port(snr_out_tag) self.m_position = position self.m_aperture = aperture self.m_ignore = ignore self.m_optimize = optimize @typechecked def run(self) -> None: """ Run method of the module. Calculates the SNR and FPF for a specified position in a post- processed image with the Student's t-test (Mawet et al. 2014). This approach assumes Gaussian noise but accounts for small sample statistics. Returns ------- NoneType None """ @typechecked def _snr_optimize(arg: np.ndarray) -> float: pos_x, pos_y = arg pos_offset = np.sqrt( (pos_x - self.m_position[0]) ** 2 + (pos_y - self.m_position[1]) ** 2 ) if self.m_offset is not None: if pos_offset > self.m_offset: snr = 0.0 else: snr = None if self.m_offset is None or snr is None: _, _, snr, _ = false_alarm( image=image, x_pos=pos_x, y_pos=pos_y, size=self.m_aperture, ignore=self.m_ignore, ) return -1.0 * snr pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_aperture /= pixscale print("Input parameters:") print(f" - Aperture position = {self.m_position}") print(f" - Aperture radius (pixels) = {self.m_aperture:.2f}") print(f" - Optimize aperture position = {self.m_optimize}") print(f" - Ignore neighboring apertures = {self.m_ignore}") print(f" - Minimization tolerance = {self.m_tolerance}") nimages = self.m_image_in_port.get_shape()[0] print("Calculating the S/N and FPF...") for j in range(nimages): image = self.m_image_in_port[j,] center = center_subpixel(image) if self.m_optimize: result = minimize( fun=_snr_optimize, x0=np.array([self.m_position[0], self.m_position[1]]), method="Nelder-Mead", tol=None, options={"xatol": self.m_tolerance, "fatol": float("inf")}, ) _, _, snr, fpf = false_alarm( image=image, x_pos=result.x[0], y_pos=result.x[1], size=self.m_aperture, ignore=self.m_ignore, ) x_pos, y_pos = result.x[0], result.x[1] else: _, _, snr, fpf = false_alarm( image=image, x_pos=self.m_position[0], y_pos=self.m_position[1], size=self.m_aperture, ignore=self.m_ignore, ) x_pos, y_pos = self.m_position[0], self.m_position[1] print( f"Image {j+1:03d}/{nimages} -> (x, y) = ({x_pos:.2f}, {y_pos:.2f}), " f"S/N = {snr:.2f}, FPF = {fpf:.2e}" ) sep_ang = cartesian_to_polar(center, y_pos, x_pos) result = np.column_stack( (x_pos, y_pos, sep_ang[0] * pixscale, sep_ang[1], snr, fpf) ) self.m_snr_out_port.append(result, data_dim=2) history = f"aperture (arcsec) = {self.m_aperture*pixscale:.2f}" self.m_snr_out_port.copy_attributes(self.m_image_in_port) self.m_snr_out_port.add_history("FalsePositiveModule", history) self.m_snr_out_port.close_port() class MCMCsamplingModule(ProcessingModule): """ Pipeline module to measure the separation, position angle, and contrast of a planet with injection of negative artificial planets and sampling of the posterior distribution with ``emcee``, an affine invariant Markov chain Monte Carlo (MCMC) ensemble sampler. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, psf_in_tag: str, chain_out_tag: str, param: Tuple[float, float, float], bounds: Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]], nwalkers: int = 100, nsteps: int = 200, psf_scaling: float = -1.0, pca_number: int = 20, aperture: Union[float, Tuple[int, int, float]] = 0.1, mask: Optional[ Union[ Tuple[float, float], Tuple[None, float], Tuple[float, None], Tuple[None, None], ] ] = None, extra_rot: float = 0.0, merit: str = "gaussian", residuals: str = "median", resume: bool = False, **kwargs: Union[float, Tuple[float, float, float]], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Database tag with the science images. psf_in_tag : str Database tag with the reference PSF that is used as artificial planet. The dataset can be either a single image, or a stack of images with the dimensions equal to ``image_in_tag``. chain_out_tag : str Database tag were the posterior samples will be stored. The shape of the array is ``(nsteps, nwalkers, 3)``. The mean acceptance fraction and the integrated autocorrelation time are stored as attributes. param : tuple(float, float, float) The approximate separation (arcsec), angle (deg), and contrast (mag), for example obtained with the :class:`~pynpoint.processing.fluxposition.SimplexMinimizationModule`. The angle is measured in counterclockwise direction with respect to the upward direction (i.e., East of North). The separation and angle are also used as (fixed) position for the aperture if ``aperture`` contains a float (i.e. the radius). bounds : tuple(tuple(float, float), tuple(float, float), tuple(float, float)) The prior boundaries for the separation (arcsec), angle (deg), and contrast (mag). Each set of boundaries is specified as a tuple. nwalkers : int Number of walkers. nsteps : int Number of steps per walker. psf_scaling : float Additional scaling factor of the planet flux (e.g. to correct for a neutral density filter or difference in exposure time). The value should be negative in order to inject negative fake planets. pca_number : int Number of principal components used for the PSF subtraction. aperture : float, tuple(int, int, float) Either the aperture radius (arcsec) at the position of ``param`` or tuple with the position and aperture radius (arcsec) as ``(pos_x, pos_y, radius)``. mask : tuple(float, float), None Inner and outer mask radius (arcsec) for the PSF subtraction. Both elements of the tuple can be set to ``None``. Masked pixels are excluded from the PCA computation, resulting in a smaller runtime. Masking is done after the artificial planet is injected. extra_rot : float Additional rotation angle of the images (deg). merit : str Figure of merit for the minimization ('hessian', 'gaussian', or 'poisson'). Either the determinant of the Hessian matrix is minimized ('hessian') or the flux of each pixel ('gaussian' or 'poisson'). For the latter case, the estimate noise is assumed to follow a Poisson (see Wertz et al. 2017) or Gaussian distribution (see Wertz et al. 2017 and Stolker et al. 2020). residuals : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). resume : bool Resume from the last state of the chain that was stored by the backend of ``emcee``. Set to ``True`` for continuing with the samples from a previous run, for example when it was interrupted or to create more steps for the walkers. The backend data of ``emcee`` is stored with the tag ``[chain_out_tag]_backend`` in the HDF5 database. Keyword arguments ----------------- sigma : tuple(float, float, float) The standard deviations that randomly initializes the start positions of the walkers in a small ball around the a priori preferred position. The tuple should contain a value for the separation (arcsec), position angle (deg), and contrast (mag). The default is set to ``(1e-5, 1e-3, 1e-3)``. Returns ------- NoneType None """ if "sigma" in kwargs: self.m_sigma = kwargs["sigma"] else: self.m_sigma = (1e-5, 1e-3, 1e-3) super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) if psf_in_tag == image_in_tag: self.m_psf_in_port = self.m_image_in_port else: self.m_psf_in_port = self.add_input_port(psf_in_tag) self.m_chain_out_port = self.add_output_port(chain_out_tag) self.m_param = param self.m_bounds = bounds self.m_nwalkers = nwalkers self.m_nsteps = nsteps self.m_psf_scaling = psf_scaling self.m_pca_number = pca_number self.m_aperture = aperture self.m_extra_rot = extra_rot self.m_merit = merit self.m_residuals = residuals self.m_resume = resume if mask is None: self.m_mask = (None, None) else: self.m_mask = mask @typechecked def run(self) -> None: """ Run method of the module. The posterior distributions of the separation, position angle, and flux contrast are sampled with the affine invariant Markov chain Monte Carlo (MCMC) ensemble sampler ``emcee``. At each step, a negative copy of the PSF template is injected and the likelihood function is evaluated at the approximate position of the planet. Returns ------- NoneType None """ print("Input parameters:") print(f" - Number of principal components: {self.m_pca_number}") print(f" - Figure of merit: {self.m_merit}") ndim = 3 cpu = self._m_config_port.get_attribute("CPU") work_place = self._m_config_port.get_attribute("WORKING_PLACE") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") parang = self.m_image_in_port.get_attribute("PARANG") images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() im_shape = self.m_image_in_port.get_shape()[-2:] self.m_image_in_port.close_port() self.m_psf_in_port.close_port() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError( "The number of frames in psf_in_tag does not match with the number of " "frames in image_in_tag. The DerotateAndStackModule can be used to " "average the PSF frames (without derotating) before applying the " "MCMCsamplingModule." ) if self.m_mask[0] is not None: self.m_mask = (self.m_mask[0] / pixscale, self.m_mask[1]) if self.m_mask[1] is not None: self.m_mask = (self.m_mask[0], self.m_mask[1] / pixscale) # create the mask and get the unmasked image indices mask = create_mask(im_shape[-2:], self.m_mask) indices = np.where(mask.reshape(-1) != 0.0)[0] if isinstance(self.m_aperture, float): yx_pos = polar_to_cartesian( images, self.m_param[0] / pixscale, self.m_param[1] ) aperture = (round(yx_pos[0]), round(yx_pos[1]), self.m_aperture / pixscale) elif isinstance(self.m_aperture, tuple): aperture = ( self.m_aperture[1], self.m_aperture[0], self.m_aperture[2] / pixscale, ) print(f" - Aperture position (x, y): ({aperture[1]}, {aperture[0]})") print(f" - Aperture radius (pixels): {int(aperture[2])}") if self.m_merit == "poisson": var_noise = None elif self.m_merit in ["gaussian", "hessian"]: var_noise = pixel_variance( var_type=self.m_merit, images=images, parang=parang, cent_size=self.m_mask[0], edge_size=self.m_mask[1], pca_number=self.m_pca_number, residuals=self.m_residuals, aperture=aperture, sigma=0.0, ) if self.m_merit == "gaussian": print(f"Gaussian standard deviation (counts): {np.sqrt(var_noise):.2e}") elif self.m_merit == "hessian": print(f"Hessian standard deviation: {np.sqrt(var_noise):.2e}") initial = np.zeros((self.m_nwalkers, ndim)) initial[:, 0] = self.m_param[0] + np.random.normal( 0, self.m_sigma[0], self.m_nwalkers ) initial[:, 1] = self.m_param[1] + np.random.normal( 0, self.m_sigma[1], self.m_nwalkers ) initial[:, 2] = self.m_param[2] + np.random.normal( 0, self.m_sigma[2], self.m_nwalkers ) print("Sampling the posterior distributions with MCMC...") backend = emcee.backends.HDFBackend( os.path.join(work_place, "PynPoint_database.hdf5"), name=self.m_chain_out_port.tag + "_backend", read_only=False, ) if not self.m_resume: print("Reset backend of emcee...", end="", flush=True) backend.reset(self.m_nwalkers, ndim) print(" [DONE]") with Pool(processes=cpu) as pool: sampler = emcee.EnsembleSampler( self.m_nwalkers, ndim, lnprob, pool=pool, args=( [ self.m_bounds, images, psf, mask, parang, self.m_psf_scaling, pixscale, self.m_pca_number, self.m_extra_rot, aperture, indices, self.m_merit, self.m_residuals, var_noise, ] ), backend=backend, ) sampler.run_mcmc(initial, self.m_nsteps, progress=True) samples = sampler.get_chain() self.m_image_in_port._check_status_and_activate() self.m_chain_out_port._check_status_and_activate() self.m_chain_out_port.set_all(samples) print(f"Number of samples stored: {samples.shape[0]*samples.shape[1]}") burnin = int(0.2 * samples.shape[0]) samples = samples[burnin:, :, :].reshape((-1, ndim)) sep_percen = np.percentile(samples[:, 0], [16.0, 50.0, 84.0]) ang_percen = np.percentile(samples[:, 1], [16.0, 50.0, 84.0]) mag_percen = np.percentile(samples[:, 2], [16.0, 50.0, 84.0]) print("Median and uncertainties (20% removed as burnin):") print( f"Separation (mas) = {1e3*sep_percen[1]:.2f} " f"(-{1e3*sep_percen[1]-1e3*sep_percen[0]:.2f} " f"+{1e3*sep_percen[2]-1e3*sep_percen[1]:.2f})" ) print( f"Position angle (deg) = {ang_percen[1]:.2f} " f"(-{ang_percen[1]-ang_percen[0]:.2f} " f"+{ang_percen[2]-ang_percen[1]:.2f})" ) print( f"Contrast (mag) = {mag_percen[1]:.2f} " f"(-{mag_percen[1]-mag_percen[0]:.2f} " f"+{mag_percen[2]-mag_percen[1]:.2f})" ) history = f"walkers = {self.m_nwalkers}, steps = {self.m_nsteps}" self.m_chain_out_port.copy_attributes(self.m_image_in_port) self.m_chain_out_port.add_history("MCMCsamplingModule", history) mean_accept = np.mean(sampler.acceptance_fraction) print(f"Mean acceptance fraction: {mean_accept:.3f}") self.m_chain_out_port.add_attribute("ACCEPTANCE", mean_accept, static=True) try: autocorr = emcee.autocorr.integrated_time(sampler.get_chain()) print(f"Integrated autocorrelation time = {autocorr}") except emcee.autocorr.AutocorrError: autocorr = [np.nan, np.nan, np.nan] print( "The chain is too short to reliably estimate the autocorrelation time. [WARNING]" ) self.m_chain_out_port.add_attribute("AUTOCORR_0", autocorr[0], static=True) self.m_chain_out_port.add_attribute("AUTOCORR_1", autocorr[1], static=True) self.m_chain_out_port.add_attribute("AUTOCORR_2", autocorr[2], static=True) self.m_chain_out_port.close_port() class AperturePhotometryModule(ProcessingModule): """ Pipeline module for calculating the counts within a circular area. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, phot_out_tag: str, radius: float = 0.1, position: Optional[Tuple[float, float]] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. phot_out_tag : str Tag of the database entry with the photometry values that are written as output. radius : float Radius (arcsec) of the circular aperture. position : tuple(float, float), None Center position (pix) of the aperture, (x, y), with subpixel precision. The center of the image will be used if set to None. Python indexing starts at zero so the bottom left corner of the image has coordinates (-0.5, -0.5). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_phot_out_port = self.add_output_port(phot_out_tag) self.m_phot_in_port = None self.m_radius = radius self.m_position = position @typechecked def run(self) -> None: """ Run method of the module. Computes the flux within a circular aperture for each frame and saves the values in the database. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_radius /= pixscale if self.m_position is None: # Returns the center position as (y, x) self.m_position = center_subpixel(self.m_image_in_port[0,]) # Store the center position as (x, y) self.m_position = (self.m_position[1], self.m_position[0]) print( f"Aperture position (x, y) = ({self.m_position[0]:.1f}, {self.m_position[1]:.1f})" ) print(f"Aperture radius (pixels) = ({self.m_radius:.1f})") # Position in CircularAperture is defined as (x, y) aperture = CircularAperture( (self.m_position[0], self.m_position[1]), self.m_radius ) self.apply_function_to_images( photometry, self.m_image_in_port, self.m_phot_out_port, "Aperture photometry", func_args=(aperture,), ) self.m_phot_in_port = self.add_input_port(self.m_phot_out_port.tag) data = self.m_phot_in_port.get_all() print( f"Mean flux (counts) = {np.mean(data):.2f} +/- {np.std(data)/np.sqrt(data.size):.2f}" ) history = f"radius (pixels) = {self.m_radius:.3f}" self.m_phot_out_port.copy_attributes(self.m_image_in_port) self.m_phot_out_port.add_history("AperturePhotometryModule", history) self.m_phot_out_port.close_port() class SystematicErrorModule(ProcessingModule): """ Pipeline module for estimating the systematic error of the flux and position measurement. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, psf_in_tag: str, offset_out_tag: str, position: Tuple[float, float], magnitude: float, angles: Tuple[float, float, int] = (0.0, 359.0, 360), psf_scaling: float = 1.0, merit: str = "gaussian", aperture: float = 0.1, tolerance: float = 0.01, pca_number: int = 10, mask: Optional[ Union[ Tuple[float, float], Tuple[None, float], Tuple[float, None], Tuple[None, None], ] ] = None, extra_rot: float = 0.0, residuals: str = "median", offset: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the science images for which the systematic error is estimated. psf_in_tag : str Tag of the database entry with the PSF template that is used as fake planet. Can be either a single image or a stack of images equal in size to ``image_in_tag``. offset_out_tag : str Tag of the database entry at which the differences are stored between the injected and and retrieved values of the separation (arcsec), position angle (deg), contrast (mag), x position (pix), and y position (pix). position : tuple(float, float) Separation (arcsec) and position angle (deg) that are used to remove the planet signal. The separation is also used to estimate the systematic error. magnitude : float Magnitude that is used to remove the planet signal and estimate the systematic error. angles : tuple(float, float, int) The start, end, and number of the position angles (linearly sampled) that are used to estimate the systematic errors (default: 0., 359., 360). The endpoint is also included. psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should be a positive value. merit : str Figure of merit for the minimization ('hessian', 'gaussian', or 'poisson'). Either the determinant of the Hessian matrix is minimized ('hessian') or the flux of each pixel ('gaussian' or 'poisson'). For the latter case, the estimate noise is assumed to follow a Poisson (see Wertz et al. 2017) or Gaussian distribution (see Wertz et al. 2017 and Stolker et al. 2020). aperture : float Aperture radius (arcsec) that is used for measuring the figure of merit. tolerance : float Absolute error on the input parameters, position (pix) and contrast (mag), that is used as acceptance level for convergence. Note that only a single value can be specified which is used for both the position and flux so tolerance=0.1 will give a precision of 0.1 mag and 0.1 pix. The tolerance on the output (i.e., the chi-square value) is set to np.inf so the condition is always met. pca_number : int Number of principal components (PCs) used for the PSF subtraction. mask : tuple(float, float), None Inner and outer mask radius (arcsec) which is applied before the PSF subtraction. Both elements of the tuple can be set to None. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). residuals : str Method for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). offset : float, None Offset (pixels) by which the negative PSF may deviate from the positive injected PSF. No constraint on the position is applied if set to None. Only the contrast is optimized and the position is fixed to the injected value if ``offset=0``. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_tag = image_in_tag self.m_psf_in_tag = psf_in_tag self.m_image_in_port = self.add_input_port(image_in_tag) self.m_offset_out_port = self.add_output_port(offset_out_tag) self.m_position = position self.m_magnitude = magnitude self.m_angles = angles self.m_psf_scaling = psf_scaling self.m_merit = merit self.m_aperture = aperture self.m_tolerance = tolerance self.m_mask = mask self.m_extra_rot = extra_rot self.m_residuals = residuals self.m_pca_number = pca_number self.m_offset = offset @typechecked def run(self) -> None: """ Run method of the module. Removes the planet signal, then artificial planets are injected (one at a time) at equally separated position angles and their position and contrast is determined with the :class:`~pynpoint.processing.fluxposition.SimplexMinimizationModule`. The differences between the injected and retrieved separation, position angle, and contrast are then stored as output. Returns ------- NoneType None """ print("Input parameters:") print(f" - Number of principal components = {self.m_pca_number}") print(f" - Figure of merit = {self.m_merit}") print(f" - Residuals type = {self.m_residuals}") print(f" - Absolute tolerance (pixels/mag) = {self.m_tolerance}") print(f" - Maximum offset = {self.m_offset}") print(f" - Aperture radius (arcsec) = {self.m_aperture}") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") image = self.m_image_in_port[0,] module = FakePlanetModule( name_in=f"{self._m_name}_fake", image_in_tag=self.m_image_in_tag, psf_in_tag=self.m_psf_in_tag, image_out_tag=f"{self._m_name}_empty", position=(self.m_position[0], self.m_position[1] + self.m_extra_rot), magnitude=self.m_magnitude, psf_scaling=-self.m_psf_scaling, ) module.connect_database(self._m_data_base) module._m_output_ports[f"{self._m_name}_empty"].del_all_data() module._m_output_ports[f"{self._m_name}_empty"].del_all_attributes() module.run() sep = float(self.m_position[0]) angles = np.linspace( self.m_angles[0], self.m_angles[1], self.m_angles[2], endpoint=True ) print("Testing the following parameters:") print(f" - Contrast (mag) = {self.m_magnitude:.2f}") print(f" - Separation (mas) = {sep*1e3:.1f}") print(f" - Position angle range (deg) = {angles[0]} - {angles[-1]}") if angles.size > 1: print(f" in steps of {np.mean(np.diff(angles)):.2f} deg") # Image center (y, x) with subpixel accuracy im_center = center_subpixel(image) for i, ang in enumerate(angles): print(f"\nProcessing position angle: {ang} deg...") # Convert the polar coordiantes of the separation and position angle that is tested # into cartesian coordinates (y, x) planet_pos_yx = polar_to_cartesian(image, sep / pixscale, ang) planet_pos_xy = (planet_pos_yx[1], planet_pos_yx[0]) # Convert the planet position to polar coordinates planet_sep_ang = cartesian_to_polar( im_center, planet_pos_yx[0], planet_pos_yx[1] ) # Change the separation units to arcsec planet_sep_ang = (planet_sep_ang[0] * pixscale, planet_sep_ang[1]) # Inject the artifical planet module = FakePlanetModule( position=(planet_sep_ang[0], planet_sep_ang[1] + self.m_extra_rot), magnitude=self.m_magnitude, psf_scaling=self.m_psf_scaling, name_in=f"{self._m_name}_fake_{i}", image_in_tag=f"{self._m_name}_empty", psf_in_tag=self.m_psf_in_tag, image_out_tag=f"{self._m_name}_fake", ) module.connect_database(self._m_data_base) module._m_output_ports[f"{self._m_name}_fake"].del_all_data() module._m_output_ports[f"{self._m_name}_fake"].del_all_attributes() module.run() # Retrieve the position and contrast of the artificial planet module = SimplexMinimizationModule( position=planet_pos_xy, magnitude=self.m_magnitude, psf_scaling=-self.m_psf_scaling, name_in=f"{self._m_name}_fake_{i}", image_in_tag=f"{self._m_name}_fake", psf_in_tag=self.m_psf_in_tag, res_out_tag=f"{self._m_name}_simplex", flux_position_tag=f"{self._m_name}_fluxpos", merit=self.m_merit, aperture=self.m_aperture, sigma=0.0, tolerance=self.m_tolerance, pca_number=self.m_pca_number, cent_size=self.m_mask[0], edge_size=self.m_mask[1], extra_rot=self.m_extra_rot, residuals=self.m_residuals, offset=self.m_offset, ) module.connect_database(self._m_data_base) module._m_output_ports[f"{self._m_name}_simplex"].del_all_data() module._m_output_ports[f"{self._m_name}_simplex"].del_all_attributes() module._m_output_ports[f"{self._m_name}_fluxpos"].del_all_data() module._m_output_ports[f"{self._m_name}_fluxpos"].del_all_attributes() module.run() # Add the input port to collect the results of SimplexMinimizationModule fluxpos_out_port = self.add_input_port(f"{self._m_name}_fluxpos") # Create a list with the offset between the injected and retrieved values of the # separation (arcsec), position angle (deg), contrast (mag), x position (pixels), # and y position (pixels). data = [ planet_sep_ang[0] - fluxpos_out_port[-1, 2], # Separation (arcsec) planet_sep_ang[1] - fluxpos_out_port[-1, 3], # Position angle (deg) self.m_magnitude - fluxpos_out_port[-1, 4], # Contrast (mag) planet_pos_xy[0] - fluxpos_out_port[-1, 0], # Position x (pixels) planet_pos_xy[1] - fluxpos_out_port[-1, 1], ] # Position y (pixels) if data[1] > 180.0: data[1] -= 360.0 elif data[1] < -180.0: data[1] += 360.0 print( f"Offset: {data[0]*1e3:.2f} mas, {data[1]:.2f} deg, {data[2]:.2f} mag" ) self.m_offset_out_port.append(data, data_dim=2) offset_in_port = self.add_input_port(self.m_offset_out_port.tag) offsets = offset_in_port.get_all() sep_percen = np.percentile(offsets[:, 0], [16.0, 50.0, 84.0]) ang_percen = np.percentile(offsets[:, 1], [16.0, 50.0, 84.0]) mag_percen = np.percentile(offsets[:, 2], [16.0, 50.0, 84.0]) x_pos_percen = np.percentile(offsets[:, 3], [16.0, 50.0, 84.0]) y_pos_percen = np.percentile(offsets[:, 4], [16.0, 50.0, 84.0]) print("\nMedian offset and uncertainties:") print( f" - Position x (pixels) = {x_pos_percen[1]:.2f} " f"(-{x_pos_percen[1]-x_pos_percen[0]:.2f} " f"+{x_pos_percen[2]-x_pos_percen[1]:.2f})" ) print( f" - Position y (pixels) = {y_pos_percen[1]:.2f} " f"(-{y_pos_percen[1]-y_pos_percen[0]:.2f} " f"+{y_pos_percen[2]-y_pos_percen[1]:.2f})" ) print( f" - Separation (mas) = {1e3*sep_percen[1]:.2f} " f"(-{1e3*sep_percen[1]-1e3*sep_percen[0]:.2f} " f"+{1e3*sep_percen[2]-1e3*sep_percen[1]:.2f})" ) print( f" - Position angle (deg) = {ang_percen[1]:.2f} " f"(-{ang_percen[1]-ang_percen[0]:.2f} " f"+{ang_percen[2]-ang_percen[1]:.2f})" ) print( f" - Contrast (mag) = {mag_percen[1]:.2f} " f"(-{mag_percen[1]-mag_percen[0]:.2f} " f"+{mag_percen[2]-mag_percen[1]:.2f})" ) history = ( f"sep = {self.m_position[0]:.3f}, " f"pa = {self.m_position[1]:.1f}, " f"mag = {self.m_magnitude:.1f}" ) self.m_offset_out_port.copy_attributes(self.m_image_in_port) self.m_offset_out_port.add_history("SystematicErrorModule", history) self.m_offset_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/frameselection.py000066400000000000000000001204651512720201400250170ustar00rootroot00000000000000""" Pipeline modules for frame selection. """ import time import math import warnings import multiprocessing as mp from typing import Optional, Tuple, Union import numpy as np from typeguard import typechecked from skimage.metrics import structural_similarity, mean_squared_error from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import image_stat from pynpoint.util.image import crop_image, pixel_distance, center_pixel from pynpoint.util.module import progress from pynpoint.util.remove import write_selected_data, write_selected_attributes from pynpoint.util.star import star_positions class RemoveFramesModule(ProcessingModule): """ Pipeline module for removing images by their index number. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, selected_out_tag: str, removed_out_tag: str, frames: Union[str, range, list, np.ndarray], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. selected_out_tag : str Tag of the database entry with the remaining images after removing the specified images. Should be different from *image_in_tag*. removed_out_tag : str Tag of the database entry with the images that are removed. Should be different from *image_in_tag*. frames : str, list, range, numpy.ndarray The frame indices that have to be removed or a database tag pointing to a list of frame indices. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_selected_out_port = self.add_output_port(selected_out_tag) self.m_removed_out_port = self.add_output_port(removed_out_tag) if isinstance(frames, str): self.m_index_in_port = self.add_input_port(frames) else: self.m_index_in_port = None if isinstance(frames, (range, list)): self.m_frames = np.asarray(frames, dtype=int) elif isinstance(frames, np.ndarray): self.m_frames = frames @typechecked def run(self) -> None: """ Run method of the module. Removes the frames and corresponding attributes, updates the NFRAMES attribute, and saves the data and attributes. Returns ------- NoneType None """ if self.m_index_in_port is not None: self.m_frames = self.m_index_in_port.get_all() if np.size(np.where(self.m_frames >= self.m_image_in_port.get_shape()[0])) > 0: raise ValueError( f"Some values in 'frames' are larger than the total number of " f"available frames, {self.m_image_in_port.get_shape()[0]}." ) write_selected_data( memory=self._m_config_port.get_attribute("MEMORY"), indices=self.m_frames, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, ) write_selected_attributes( indices=self.m_frames, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, module_type="RemoveFramesModule", history=f"frames removed = {np.size(self.m_frames)}", ) self.m_image_in_port.close_port() class FrameSelectionModule(ProcessingModule): """ Pipeline module for applying a frame selection. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, selected_out_tag: str, removed_out_tag: str, index_out_tag: Optional[str] = None, method: str = "median", threshold: Union[float, Tuple[float, float]] = 4.0, fwhm: Optional[float] = 0.1, aperture: Union[Tuple[str, float], Tuple[str, float, float]] = ( "circular", 0.2, ), position: Optional[ Union[ Tuple[int, int, float], Tuple[None, None, float], Tuple[int, int, None] ] ] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. selected_out_tag : str Tag of the database entry with the selected images that are written as output. Should be different from ``image_in_tag``. removed_out_tag : str Tag of the database entry with the removed images that are written as output. Should be different from ``image_in_tag``. index_out_tag : str, None Tag of the database entry with the list of frames indices that are removed with the frames selection. No data is written when set to ``None``. method : str Method used for the frame selection. Either sigma clipping is applied with respect to the median (``method='median'``) or maximum (``method='max'``) aperture flux or fluxes outside a specified range (``method='range'``) are removed. In the latter case, the ``threshold`` argument should be a tuple with the minimum and maximum flux value. threshold : float, tuple(float, float) Threshold in units of sigma for the frame selection in case ``method='median'`` or ``method='max'``. All images that are a ``threshold`` number of sigmas away from the median/maximum aperture flux will be removed. In case ``method='range'``, the argument should be a tuple with the minimum and maximum flux. Aperture fluxes within this range will be selected and stored at ``selected_out_tag``. fwhm : float, None The FWHM (arcsec) of the Gaussian kernel that is used to smooth the images before the brightest pixel is located. Recommended to be similar in size to the FWHM of the stellar PSF. No smoothing is applied if set to ``None``. aperture : tuple(str, float), tuple(str, float, float) Tuple with the aperture properties for measuring the photometry around the location of the brightest pixel. The first element contains the aperture type ('circular', 'annulus', or 'ratio'). For a circular aperture, the second element contains the aperture radius (arcsec). For the other two types, the second and third element are the inner and outer radii (arcsec) of the aperture. position : tuple(int, int, float), None Subframe that is selected to search for the star. The tuple contains the center (pix) and size (arcsec) (pos_x, pos_y, size). Setting ``position`` to None will use the full image to search for the star. If `position=(None, None, size)` then the center of the image will be used. If ``position=(pos_x, pos_y, None)`` then a fixed position is used for the aperture. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_selected_out_port = self.add_output_port(selected_out_tag) self.m_removed_out_port = self.add_output_port(removed_out_tag) if index_out_tag is None: self.m_index_out_port = None else: self.m_index_out_port = self.add_output_port(index_out_tag) self.m_method = method self.m_fwhm = fwhm self.m_aperture = aperture self.m_threshold = threshold self.m_position = position @staticmethod @typechecked def aperture_phot( image: np.ndarray, position: np.ndarray, aperture: Union[Tuple[str, float, float], Tuple[str, None, float]], ) -> np.float64: """ Parameters ---------- image : np.ndarray Input image (2D). position : np.ndarray Center position (y, x) of the aperture. aperture : tuple(str, float, float) Tuple with the aperture properties for measuring the photometry around the location of the brightest pixel. The first element contains the aperture type ('circular', 'annulus', or 'ratio'). For a circular aperture, the second element is empty and the third element contains the aperture radius (pix). For the other two types, the second and third element are the inner and outer radii (pix) of the aperture. Returns ------- np.float64 Photometry value. """ check_pos_in = any(np.floor(position[:] - aperture[2]) < 0.0) check_pos_out = any(np.ceil(position[:] + aperture[2]) > image.shape[0]) if check_pos_in or check_pos_out: phot = np.nan else: im_crop = crop_image( image, tuple(position), 2 * int(math.ceil(aperture[2])) ) npix = im_crop.shape[0] x_grid = y_grid = np.linspace(-(npix - 1) / 2, (npix - 1) / 2, npix) xx_grid, yy_grid = np.meshgrid(x_grid, y_grid) rr_grid = np.sqrt(xx_grid**2 + yy_grid**2) if aperture[0] == "circular": phot = np.sum(im_crop[rr_grid < aperture[2]]) elif aperture[0] == "annulus": phot = np.sum( im_crop[(rr_grid > aperture[1]) & (rr_grid < aperture[2])] ) elif aperture[0] == "ratio": phot = np.sum(im_crop[rr_grid < aperture[1]]) / np.sum( im_crop[(rr_grid > aperture[1]) & (rr_grid < aperture[2])] ) return phot @typechecked def run(self) -> None: """ Run method of the module. Smooths the images with a Gaussian kernel, locates the brightest pixel in each image, measures the integrated flux around the brightest pixel, calculates the median and standard deviation of the photometry, and applies sigma clipping to remove low quality images. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") nimages = self.m_image_in_port.get_shape()[0] if self.m_fwhm is not None: self.m_fwhm = int(math.ceil(self.m_fwhm / pixscale)) if self.m_position is not None and self.m_position[2] is not None: self.m_position = ( self.m_position[0], self.m_position[0], int(math.ceil(self.m_position[2] / pixscale)), ) if len(self.m_aperture) == 2: self.m_aperture = (self.m_aperture[0], None, self.m_aperture[1] / pixscale) elif len(self.m_aperture) == 3: self.m_aperture = ( self.m_aperture[0], self.m_aperture[1] / pixscale, self.m_aperture[2] / pixscale, ) starpos = star_positions(self.m_image_in_port, self.m_fwhm, self.m_position) phot = np.zeros(nimages) start_time = time.time() for i in range(nimages): progress(i, nimages, "Aperture photometry...", start_time) phot[i] = self.aperture_phot( self.m_image_in_port[i,], starpos[i, :], self.m_aperture ) if self.m_method == "median": phot_ref = np.nanmedian(phot) print(f"Median = {phot_ref:.2f}") elif self.m_method == "max": phot_ref = np.nanmax(phot) print(f"Maximum = {phot_ref:.2f}") elif self.m_method == "range": phot_ref = np.nanmedian(phot) print(f"Median = {phot_ref:.2f}") phot_std = np.nanstd(phot) print(f"Standard deviation = {phot_std:.2f}") if self.m_method in ["median", "max"]: index_del = np.logical_or( (phot > phot_ref + self.m_threshold * phot_std), (phot < phot_ref - self.m_threshold * phot_std), ) elif self.m_method == "range": index_del = np.logical_or( (phot < self.m_threshold[0]), (phot > self.m_threshold[1]) ) index_del[np.isnan(phot)] = True index_del = np.where(index_del)[0] index_sel = np.ones(nimages, dtype=bool) index_sel[index_del] = False write_selected_data( memory=self._m_config_port.get_attribute("MEMORY"), indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, ) history = f"frames removed = {np.size(index_del)}" if self.m_index_out_port is not None: self.m_index_out_port.set_all(index_del, data_dim=1) self.m_index_out_port.copy_attributes(self.m_image_in_port) self.m_index_out_port.add_attribute("STAR_POSITION", starpos, static=False) self.m_index_out_port.add_history("FrameSelectionModule", history) write_selected_attributes( indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, module_type="FrameSelectionModule", history=history, ) self.m_selected_out_port.add_attribute( "STAR_POSITION", starpos[index_sel], static=False ) self.m_selected_out_port.add_history("FrameSelectionModule", history) self.m_removed_out_port.add_attribute( "STAR_POSITION", starpos[index_del], static=False ) self.m_removed_out_port.add_history("FrameSelectionModule", history) self.m_image_in_port.close_port() class RemoveLastFrameModule(ProcessingModule): """ Pipeline module for removing every NDIT+1 image from NACO dataset obtained in cube mode. This frame contains the average pixel values of the cube. """ __author__ = "Tomas Stolker" @typechecked def __init__(self, name_in: str, image_in_tag: str, image_out_tag: str) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) @typechecked def run(self) -> None: """ Run method of the module. Removes every NDIT+1 frame and saves the data and attributes. Returns ------- NoneType None """ ndit = self.m_image_in_port.get_attribute("NDIT") nframes = self.m_image_in_port.get_attribute("NFRAMES") index = self.m_image_in_port.get_attribute("INDEX") nframes_new = [] index_new = [] start_time = time.time() for i, item in enumerate(ndit): progress( i, len(ndit), "Removing the last image of each FITS cube...", start_time ) if nframes[i] != item + 1: warnings.warn( f"Number of frames ({nframes[i]}) is not equal to NDIT+1." ) frame_start = np.sum(nframes[0:i]) frame_end = np.sum(nframes[0 : i + 1]) - 1 nframes_new.append(nframes[i] - 1) index_new.extend(index[frame_start:frame_end]) self.m_image_out_port.append(self.m_image_in_port[frame_start:frame_end,]) nframes_new = np.asarray(nframes_new, dtype=int) index_new = np.asarray(index_new, dtype=int) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_attribute("NFRAMES", nframes_new, static=False) self.m_image_out_port.add_attribute("INDEX", index_new, static=False) history = "frames removed = NDIT+1" self.m_image_out_port.add_history("RemoveLastFrameModule", history) self.m_image_out_port.close_port() class RemoveStartFramesModule(ProcessingModule): """ Pipeline module for removing a fixed number of images at the beginning of each cube. This can be useful for NACO data in which the background is higher at the beginning of the cube. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, frames: int = 1 ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. frames : int Number of frames that are removed at the beginning of each cube. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_frames = int(frames) @typechecked def run(self) -> None: """ Run method of the module. Removes a constant number of images at the beginning of each cube and saves the data and attributes. Returns ------- NoneType None """ if self.m_image_out_port.tag == self.m_image_in_port.tag: raise ValueError("Input and output port should have a different tag.") nframes = self.m_image_in_port.get_attribute("NFRAMES") index = self.m_image_in_port.get_attribute("INDEX") index_new = [] if "PARANG" in self.m_image_in_port.get_all_non_static_attributes(): parang = self.m_image_in_port.get_attribute("PARANG") parang_new = [] else: parang = None if "STAR_POSITION" in self.m_image_in_port.get_all_non_static_attributes(): star = self.m_image_in_port.get_attribute("STAR_POSITION") star_new = [] else: star = None start_time = time.time() for i, _ in enumerate(nframes): progress( i, len(nframes), "Removing images at the begin of each cube...", start_time, ) frame_start = np.sum(nframes[0:i]) + self.m_frames frame_end = np.sum(nframes[0 : i + 1]) if frame_start >= frame_end: raise ValueError( "The number of frames in the original data cube is equal or " "smaller than the number of frames that have to be removed." ) index_new.extend(index[frame_start:frame_end]) if parang is not None: parang_new.extend(parang[frame_start:frame_end]) if star is not None: star_new.extend(star[frame_start:frame_end]) self.m_image_out_port.append(self.m_image_in_port[frame_start:frame_end,]) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_attribute( "NFRAMES", nframes - self.m_frames, static=False ) self.m_image_out_port.add_attribute("INDEX", index_new, static=False) if parang is not None: self.m_image_out_port.add_attribute("PARANG", parang_new, static=False) if star is not None: self.m_image_out_port.add_attribute( "STAR_POSITION", np.asarray(star_new), static=False ) history = "frames removed = " + str(self.m_frames) self.m_image_out_port.add_history("RemoveStartFramesModule", history) self.m_image_out_port.close_port() class ImageStatisticsModule(ProcessingModule): """ Pipeline module for calculating image statistics for the full images or a subsection of the images. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, stat_out_tag: str, position: Optional[ Union[Tuple[int, int, float], Tuple[None, None, float]] ] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the images that are read as input. stat_out_tag : str Tag of the database entry with the statistical results that are written as output. The result is stored in the following order: minimum, maximum, sum, mean, median, and standard deviation. position : tuple(int, int, float) Position (x, y) (pix) and radius (arcsec) of the circular area in which the statistics are calculated. The full image is used if set to None. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_stat_out_port = self.add_output_port(stat_out_tag) self.m_position = position @typechecked def run(self) -> None: """ Run method of the module. Calculates the minimum, maximum, sum, mean, median, and standard deviation of the pixel values of each image separately. NaNs are ignored for each calculation. The values are calculated for either the full images or a circular subsection of the images. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") nimages = self.m_image_in_port.get_shape()[0] im_shape = self.m_image_in_port.get_shape()[1:] if self.m_position is None: indices = None else: if self.m_position[0] is None and self.m_position[1] is None: center = center_pixel(self.m_image_in_port[0,]) self.m_position = ( center[0], # y position center[1], # x position self.m_position[2] / pixscale, ) # radius (pix) else: self.m_position = ( int(self.m_position[1]), # y position int(self.m_position[0]), # x position self.m_position[2] / pixscale, ) # radius (pix) rr_grid, _, _ = pixel_distance(im_shape, position=self.m_position[0:2]) rr_reshape = np.reshape(rr_grid, (rr_grid.shape[0] * rr_grid.shape[1])) indices = np.where(rr_reshape <= self.m_position[2])[0] self.apply_function_to_images( image_stat, self.m_image_in_port, self.m_stat_out_port, "Calculating image statistics", func_args=(indices,), ) history = f"number of images = {nimages}" self.m_stat_out_port.copy_attributes(self.m_image_in_port) self.m_stat_out_port.add_history("ImageStatisticsModule", history) self.m_stat_out_port.close_port() class FrameSimilarityModule(ProcessingModule): """ Pipeline module for measuring the similarity between frames. """ __author__ = "Benedikt Schmidhuber, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_tag: str, method: str = "MSE", mask_radius: Tuple[float, float] = (0.0, 5.0), window_size: float = 0.1, temporal_median: str = "full", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_tag : str Tag of the database entry that is read as input. method : str Method for the similarity measure. There are three measures available: - `MSE` - Mean Squared Error - `PCC` - Pearson Correlation Coefficient - `SSIM` - Structural Similarity These measures compare each image to the temporal median of the image set. mask_radius : tuple(float, float) Inner and outer radius (arcsec) of the mask that is applied to the images. The inner radius is actually not used so can be set to zero. window_size : float Size (arcsec) of the sliding window that is used when the SSIM similarity is calculated. temporal_median : str Option to calculate the temporal median for each position ('full') or as a constant value ('constant') for the entire set. The latter is computationally less expensive. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_tag) self.m_image_out_port = self.add_output_port(image_tag) if method not in ("MSE", "PCC", "SSIM"): raise ValueError( f"The chosen method '{method}' is not available. Please ensure " f"that you have selected one of 'MSE', 'PCC', or 'SSIM'." ) if temporal_median not in ("full", "constant"): raise ValueError( f"The chosen temporal_median '{temporal_median}' is not " f"available. Please ensure that you have selected one of 'full', " f"'constant'." ) self.m_method = method self.m_temporal_median = temporal_median self.m_mask_radii = mask_radius self.m_window_size = window_size @staticmethod @typechecked def _similarity( images: np.ndarray, reference_index: int, mode: str, window_size: int, temporal_median: Union[bool, np.ndarray] = False, ) -> Tuple[int, float]: """ Internal function to compute the MSE as defined by Ruane et al. 2019. """ @typechecked def _temporal_median(reference_index: int, images: np.ndarray) -> np.ndarray: """ Internal function to calculate the temporal median for all frames, except the one with the ``reference_index``. """ image_m = np.concatenate( (images[:reference_index], images[reference_index + 1 :]) ) return np.median(image_m, axis=0) image_x_i = images[reference_index] if isinstance(temporal_median, bool): image_m = _temporal_median(reference_index, images=images) else: image_m = temporal_median if mode == "MSE": return reference_index, mean_squared_error(image_x_i, image_m) if mode == "PCC": # calculate the covariance matrix of the flattened images cov_mat = np.cov(image_x_i.flatten(), image_m.flatten(), ddof=1) # the variances are stored in the diagonal, therefore take the sqrt to obtain std std = np.sqrt(np.diag(cov_mat)) # does not matter whether [0, 1] or [1, 0] as cov_mat is symmetric return reference_index, cov_mat[0, 1] / (std[0] * std[1]) if mode == "SSIM": # winsize needs to be odd if int(window_size) % 2 == 0: winsize = int(window_size) + 1 else: winsize = int(window_size) # TODO Unclear what value to pass to data_range # Previously the argument was not requires data_range = np.amax(image_x_i) - np.amin(image_x_i) struc_sim = structural_similarity( im1=image_x_i, im2=image_m, win_size=winsize, data_range=data_range ) return reference_index, struc_sim @typechecked def run(self) -> None: """ Run method of the module. Computes the similarity between frames based on the Mean Squared Error (MSE), the Pearson Correlation Coefficient (PCC), or the Structural Similarity (SSIM). The correlation values are stored as non-static attribute (``MSE``, ``PCC``, or ``SSIM``) to the input data. After running this module, the :class:`~pynpoint.processing.frameselection.SelectByAttributeModule` can be used to select the images with the highest correlation. Returns ------- NoneType None """ cpu = self._m_config_port.get_attribute("CPU") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") # get number of images nimages = self.m_image_in_port.get_shape()[0] # convert arcsecs to pixels self.m_mask_radii = ( math.floor(self.m_mask_radii[0] / pixscale), math.floor(self.m_mask_radii[1] / pixscale), ) self.m_window_size = int(self.m_window_size / pixscale) # overlay the same mask over all images images = self.m_image_in_port.get_all() # close the port during the calculations self.m_image_out_port.close_port() # Change the radius to the image size images = crop_image(images, None, int(2.0 * self.m_mask_radii[1])) if self.m_temporal_median == "constant": temporal_median = np.median(images, axis=0) else: temporal_median = False # compare images and store similarity similarities = np.zeros(nimages) pool = mp.Pool(cpu) async_results = [] for i in range(nimages): async_results.append( pool.apply_async( FrameSimilarityModule._similarity, args=( images, i, self.m_method, self.m_window_size, temporal_median, ), ) ) pool.close() start_time = time.time() # wait for all processes to finish while mp.active_children(): # number of finished processes nfinished = sum([i.ready() for i in async_results]) progress(nfinished, nimages, "Calculating image similarity...", start_time) # check if new processes have finished every 5 seconds time.sleep(5) if nfinished != nimages: print("\r ") print("\rCalculating image similarity... [DONE]") # get the results for every async_result object for async_result in async_results: reference, similarity = async_result.get() similarities[reference] = similarity pool.terminate() # reopen the port after the calculation self.m_image_out_port.open_port() self.m_image_out_port.add_attribute( f"{self.m_method}", similarities, static=False ) self.m_image_out_port.close_port() class SelectByAttributeModule(ProcessingModule): """ Pipeline module for selecting frames based on attribute values. """ __author__ = "Benedikt Schmidhuber, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, selected_out_tag: str, removed_out_tag: str, attribute_tag: str, number_frames: int = 100, order: str = "descending", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. selected_out_tag : str Tag of the database entry to which the selected frames are written. removed_out_tag : str Tag of the database entry to which the removed frames are written. attribute_tag : str Name of the attribute which is used to sort and select the frames. number_frames : int Number of frames that are selected. order : str Order in which the frames are selected. Can be either 'descending' (will select the lowest attribute values) or 'ascending' (will select the highest attribute values). Returns ------- NoneType None Examples -------- The example below selects the first 100 frames with an ascending order of the ``INDEX`` values that are stored to the 'im_arr' dataset:: SelectByAttributeModule(name_in='frame_selection', image_in_tag='im_arr', attribute_tag='INDEX', selected_frames=100, order='ascending', selected_out_tag='im_arr_selected', removed_out_tag='im_arr_removed')) The example below selects the 200 frames with the largest ``SSIM`` values that are stored to the 'im_arr' dataset:: SelectByAttributeModule(name_in='frame_selection', image_in_tag='im_arr', attribute_tag='SSIM', selected_frames=200, order='descending', selected_out_tag='im_arr_selected', removed_out_tag='im_arr_removed')) """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_selected_out_port = self.add_output_port(selected_out_tag) self.m_removed_out_port = self.add_output_port(removed_out_tag) if order not in ("ascending", "descending"): raise ValueError( "The selected order is not available. The available options are " "'ascending' or 'descending'." ) self.m_attribute_tag = attribute_tag self.m_number_frames = number_frames self.m_order = order @typechecked def run(self) -> None: """ Run method of the module. Selects images according to a specified attribute tag and ordering, e.g. the highest 150 ``INDEX`` frames, or the lowest 50 ``PCC`` frames. The order of the selected images is determined by the `descending` or `ascending` attribute values. To sort the images again by their original order, the :class:`~pynpoint.processing.psfpreparation.SortParangModule` can be used. Returns ------- NoneType None """ nimages = self.m_image_in_port.get_shape()[0] attribute = self.m_image_in_port.get_attribute(f"{self.m_attribute_tag}") if nimages != attribute.size: raise ValueError( f"The attribute {{self.m_attribute_tag}} does not have the same " f"length ({len(attribute)}) as the tag has images ({nimages}). " f"Please check the attribute you have chosen for selection." ) if self.m_order == "descending": # sort attribute in descending order sorting_order = np.argsort(attribute)[::-1] else: # sort attribute in ascending order sorting_order = np.argsort(attribute) index_del = sorting_order[self.m_number_frames :] write_selected_data( memory=self._m_config_port.get_attribute("MEMORY"), indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, ) write_selected_attributes( indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, module_type="SelectByAttributeModule", history=f"selected tag = {self.m_attribute_tag}", ) self.m_image_in_port.close_port() class ResidualSelectionModule(ProcessingModule): """ Pipeline module for applying a frame selection on the residuals of the PSF subtraction. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, selected_out_tag: str, removed_out_tag: str, percentage: float = 10.0, annulus_radii: Tuple[float, float] = (0.1, 0.2), ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. selected_out_tag : str Tag of the database entry with the selected images that are written as output. removed_out_tag : str Tag of the database entry with the removed images that are written as output. percentage : float The percentage of best frames that is selected. annulus_radii : tuple(float, float) Inner and outer radius of the annulus (arcsec). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_selected_out_port = self.add_output_port(selected_out_tag) self.m_removed_out_port = self.add_output_port(removed_out_tag) self.m_percentage = percentage self.m_annulus_radii = annulus_radii @typechecked def run(self) -> None: """ Run method of the module. Applies a frame selection on the derotated residuals from the PSF subtraction. The pixels within an annulus (e.g. at the separation of an expected planet) are selected and the standard deviation is calculated. The chosen percentage of images with the lowest standard deviation are stored as output. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") nimages = self.m_image_in_port.get_shape()[0] npix = self.m_image_in_port.get_shape()[-1] rr_grid, _, _ = pixel_distance((npix, npix), position=None) pixel_select = np.where( (rr_grid > self.m_annulus_radii[0] / pixscale) & (rr_grid < self.m_annulus_radii[1] / pixscale) ) start_time = time.time() phot_annulus = np.zeros(nimages) for i in range(nimages): progress(i, nimages, "Aperture photometry...", start_time) phot_annulus[i] = np.sum(np.abs(self.m_image_in_port[i][pixel_select])) print( f"Minimum, maximum = {np.amin(phot_annulus):.2f}, {np.amax(phot_annulus):.2f}" ) print( f"Mean, median = {np.nanmean(phot_annulus):.2f}, {np.nanmedian(phot_annulus):.2f}" ) print(f"Standard deviation = {np.nanstd(phot_annulus):.2f}") n_select = int(nimages * self.m_percentage / 100.0) index_del = np.argsort(phot_annulus)[n_select:] write_selected_data( memory=self._m_config_port.get_attribute("MEMORY"), indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, ) write_selected_attributes( indices=index_del, image_in_port=self.m_image_in_port, selected_out_port=self.m_selected_out_port, removed_out_port=self.m_removed_out_port, module_type="ResidualSelectionModule", history=f"frames removed = {index_del.size}", ) self.m_image_in_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/limits.py000066400000000000000000000525611512720201400233210ustar00rootroot00000000000000""" Pipeline modules for estimating detection limits. """ import os import math import time import warnings import multiprocessing as mp from typing import List, Optional, Tuple, Union import numpy as np from scipy.interpolate import griddata from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.image import create_mask from pynpoint.util.limits import contrast_limit from pynpoint.util.module import progress from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.residuals import combine_residuals class ContrastCurveModule(ProcessingModule): """ Pipeline module to calculate contrast limits for a given sigma level or false positive fraction, with a correction for small sample statistics. Positions are processed in parallel if ``CPU`` is set to a value larger than 1 in the configuration file. """ __author__ = "Tomas Stolker, Jasper Jonker, Benedikt Schmidhuber" @typechecked def __init__( self, name_in: str, image_in_tag: str, psf_in_tag: str, contrast_out_tag: str, separation: Tuple[float, float, float] = (0.1, 1.0, 0.01), angle: Tuple[float, float, float] = (0.0, 360.0, 60.0), threshold: Tuple[str, float] = ("sigma", 5.0), psf_scaling: float = 1.0, aperture: float = 0.05, pca_number: int = 20, cent_size: Optional[float] = None, edge_size: Optional[float] = None, extra_rot: float = 0.0, residuals: str = "mean", snr_inject: float = 100.0, **kwargs: float, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that contains the stack with images. psf_in_tag : str Tag of the database entry that contains the reference PSF that is used as fake planet. Can be either a single image or a stack of images equal in size to *image_in_tag*. contrast_out_tag : str Tag of the database entry that contains the separation, azimuthally averaged contrast limits, the azimuthal variance of the contrast limits, and the threshold of the false positive fraction associated with sigma. separation : tuple(float, float, float) Range of separations (arcsec) where the contrast is calculated. Should be specified as (lower limit, upper limit, step size). Apertures that fall within the mask radius or beyond the image size are removed. angle : tuple(float, float, float) Range of position angles (deg) where the contrast is calculated. Should be specified as (lower limit, upper limit, step size), measured counterclockwise with respect to the vertical image axis, i.e. East of North. threshold : tuple(str, float) Detection threshold for the contrast curve, either in terms of 'sigma' or the false positive fraction (FPF). The value is a tuple, for example provided as ('sigma', 5.) or ('fpf', 1e-6). Note that when sigma is fixed, the false positive fraction will change with separation. Also, sigma only corresponds to the standard deviation of a normal distribution at large separations (i.e., large number of samples). psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should have a positive value. aperture : float Aperture radius (arcsec). pca_number : int Number of principal components used for the PSF subtraction. cent_size : float, None Central mask radius (arcsec). No mask is used when set to None. edge_size : float, None Outer edge radius (arcsec) beyond which pixels are masked. No outer mask is used when set to None. If the value is larger than half the image size then it will be set to half the image size. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). residuals : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). snr_inject : float Signal-to-noise ratio of the injected planet signal that is used to measure the amount of self-subtraction. Returns ------- NoneType None """ super().__init__(name_in) if "sigma" in kwargs: warnings.warn( "The 'sigma' parameter has been deprecated. Please use the " "'threshold' parameter instead.", DeprecationWarning, ) if "norm" in kwargs: warnings.warn( "The 'norm' parameter has been deprecated. It is not recommended to " "normalize the images before PSF subtraction.", DeprecationWarning, ) if "accuracy" in kwargs: warnings.warn( "The 'accuracy' parameter has been deprecated. The parameter is no " "longer required.", DeprecationWarning, ) if "magnitude" in kwargs: warnings.warn( "The 'magnitude' parameter has been deprecated. The parameter is no " "longer required.", DeprecationWarning, ) if "ignore" in kwargs: warnings.warn( "The 'ignore' parameter has been deprecated. The parameter is no " "longer required.", DeprecationWarning, ) self.m_image_in_port = self.add_input_port(image_in_tag) if psf_in_tag == image_in_tag: self.m_psf_in_port = self.m_image_in_port else: self.m_psf_in_port = self.add_input_port(psf_in_tag) self.m_contrast_out_port = self.add_output_port(contrast_out_tag) self.m_separation = separation self.m_angle = angle self.m_psf_scaling = psf_scaling self.m_threshold = threshold self.m_aperture = aperture self.m_pca_number = pca_number self.m_cent_size = cent_size self.m_edge_size = edge_size self.m_extra_rot = extra_rot self.m_residuals = residuals self.m_snr_inject = snr_inject if ( self.m_angle[0] < 0.0 or self.m_angle[0] > 360.0 or self.m_angle[1] < 0.0 or self.m_angle[1] > 360.0 or self.m_angle[2] < 0.0 or self.m_angle[2] > 360.0 ): raise ValueError( "The angular positions of the fake planets should lie between " "0 deg and 360 deg." ) @typechecked def run(self) -> None: """ Run method of the module. An artificial planet is injected (based on the noise level) at a given separation and position angle. The amount of self-subtraction is then determined and the contrast limit is calculated for a given sigma level or false positive fraction. A correction for small sample statistics is applied for both cases. Note that if the sigma level is fixed, the false positive fraction changes with separation, following the Student's t-distribution (see Mawet et al. 2014 for details). Returns ------- NoneType None """ images = self.m_image_in_port.get_all() psf = self.m_psf_in_port.get_all() if psf.shape[0] != 1 and psf.shape[0] != images.shape[0]: raise ValueError( f"The number of frames in psf_in_tag {psf.shape} does not match with " f"the number of frames in image_in_tag {images.shape}. The " f"DerotateAndStackModule can be used to average the PSF frames " f"(without derotating) before applying the ContrastCurveModule." ) cpu = self._m_config_port.get_attribute("CPU") working_place = self._m_config_port.get_attribute("WORKING_PLACE") parang = self.m_image_in_port.get_attribute("PARANG") pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.m_image_in_port.close_port() self.m_psf_in_port.close_port() if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale self.m_aperture /= pixscale pos_r = np.arange( self.m_separation[0] / pixscale, self.m_separation[1] / pixscale, self.m_separation[2] / pixscale, ) pos_t = np.arange( self.m_angle[0] + self.m_extra_rot, self.m_angle[1] + self.m_extra_rot, self.m_angle[2], ) if self.m_cent_size is None: index_del = np.argwhere(pos_r - self.m_aperture <= 0.0) else: index_del = np.argwhere(pos_r - self.m_aperture <= self.m_cent_size) pos_r = np.delete(pos_r, index_del) if self.m_edge_size is None or self.m_edge_size > images.shape[1] / 2.0: index_del = np.argwhere(pos_r + self.m_aperture >= images.shape[1] / 2.0) else: index_del = np.argwhere(pos_r + self.m_aperture >= self.m_edge_size) pos_r = np.delete(pos_r, index_del) positions = [] for sep in pos_r: for ang in pos_t: positions.append((sep, ang)) result = [] async_results = [] # Create temporary files tmp_im_str = os.path.join(working_place, "tmp_images.npy") tmp_psf_str = os.path.join(working_place, "tmp_psf.npy") np.save(tmp_im_str, images) np.save(tmp_psf_str, psf) mask = create_mask(images.shape[-2:], (self.m_cent_size, self.m_edge_size)) _, im_res = pca_psf_subtraction( images=images * mask, angles=-1.0 * parang + self.m_extra_rot, pca_number=self.m_pca_number, ) noise = combine_residuals(method=self.m_residuals, res_rot=im_res) pool = mp.Pool(cpu) for pos in positions: async_results.append( pool.apply_async( contrast_limit, args=( tmp_im_str, tmp_psf_str, noise, mask, parang, self.m_psf_scaling, self.m_extra_rot, self.m_pca_number, self.m_threshold, self.m_aperture, self.m_residuals, self.m_snr_inject, pos, ), ) ) pool.close() start_time = time.time() # wait for all processes to finish while mp.active_children(): # number of finished processes nfinished = sum([i.ready() for i in async_results]) progress( nfinished, len(positions), "Calculating detection limits...", start_time ) # check if new processes have finished every 5 seconds time.sleep(5) if nfinished != len(positions): print("\r ") print("\rCalculating detection limits... [DONE]") # get the results for every async_result object for item in async_results: result.append(item.get()) pool.terminate() os.remove(tmp_im_str) os.remove(tmp_psf_str) result = np.asarray(result) # Sort the results first by separation and then by angle indices = np.lexsort((result[:, 1], result[:, 0])) result = result[indices] result = result.reshape((pos_r.size, pos_t.size, 4)) result[np.isinf(result)] = np.nan mag_mean = np.nanmean(result, axis=1)[:, 2] mag_var = np.nanvar(result, axis=1)[:, 2] res_fpf = result[:, 0, 3] limits = np.column_stack((pos_r * pixscale, mag_mean, mag_var, res_fpf)) self.m_image_in_port._check_status_and_activate() self.m_contrast_out_port._check_status_and_activate() self.m_contrast_out_port.set_all(limits, data_dim=2) history = f"{self.m_threshold[0]} = {self.m_threshold[1]}" self.m_contrast_out_port.add_history("ContrastCurveModule", history) self.m_contrast_out_port.copy_attributes(self.m_image_in_port) self.m_contrast_out_port.close_port() class MassLimitsModule(ProcessingModule): """ Pipeline module to calculate mass limits from the contrast limits and any isochrones model grid downloaded from https://phoenix.ens-lyon.fr/Grids/. """ __author__ = "Benedikt Schmidhuber, Tomas Stolker" @typechecked def __init__( self, name_in: str, contrast_in_tag: str, mass_out_tag: str, model_file: str, star_prop: dict, instr_filter: str = "L'", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. contrast_in_tag : str Tag of the database entry that contains the contrast curve data, as computed with the :class:`~pynpoint.processing.limits.ContrastCurveModule`. mass_out_tag : str Tag of the database entry with the output data containing the separation, the mass limits, and the upper and lower one sigma deviation as calculated for the azimuthal variance on the contrast limits. model_file: str Path to the file containing the model data. Must be in the same format as the grids found on https://phoenix.ens-lyon.fr/Grids/. Any of the isochrones files from this website can be used. star_prop : dict Dictionary containing host star properties. Must have the following keys: - ``magnitude`` - Apparent magnitude, in the same band as the `instr_filter`. - ``distance`` - Distance in parsec. - ``age`` - Age of the system in the Myr. instr_filter: str Instrument filter in the same format as listed in the `model_file`. Returns ------- NoneType None """ super().__init__(name_in) self.m_star_age = star_prop["age"] / 1000.0 # [Myr] self.m_star_abs = star_prop["magnitude"] - 5.0 * math.log10( star_prop["distance"] / 10.0 ) self.m_instr_filter = instr_filter self.m_model_file = model_file if not os.path.exists(self.m_model_file): raise ValueError( "The path does not appear to be an existing file. Please check the" "path. If you are unsure about the path, pass the absolute path to the" "model file." ) self.m_contrast_in_port = self.add_input_port(contrast_in_tag) self.m_mass_out_port = self.add_output_port(mass_out_tag) @staticmethod @typechecked def read_model( model_file_path: str, ) -> Tuple[List[float], List[np.ndarray], List[str]]: """ Reads the data from the model file and structures it. Returns an array of available model ages and a list of model data for each age. Parameters ------- model_file: str Path to the file containing the model data. Returns ------- list(float) List with all the ages from the model grid. list(np.ndarray) List with all the isochrone data, so the length is the same as the number of ages. list(str) List with all the column names from the model grid. """ # read in all the data, selecting out empty lines or '---' lines data = [] with open(model_file_path) as file: for line in file: if ("---" in line) or line == "\n": continue else: data += [list(filter(None, line.rstrip().split(" ")))] # initialize list of ages ages = [] # initialize the header header = [] # initialize a new data list, where the data is separated by age isochrones = [] k = -1 for _line in data: if "(Gyr)" in _line: # get time line ages += [float(_line[-1])] isochrones += [[]] k += 1 elif "lg(g)" in _line: # get header line header = ["M/Ms", "Teff(K)"] + _line[1:] else: # save the data isochrones[k] += [_line] for index, _ in enumerate(isochrones): isochrones[index] = np.array(isochrones[index], dtype=float) return ages, isochrones, header @staticmethod @typechecked def interpolate_model( age_eval: np.ndarray, mag_eval: np.ndarray, filter_index: Union[int, np.int64], model_age: List[float], model_data: List[np.ndarray], ) -> np.ndarray: """ Interpolates the grid based model data. Parameters ---------- age_eval : np.ndarray Age at which the system is evaluated. Must be of the same shape as `mag_eval`. mag_eval : np.ndarray Absolute magnitude for which the system is evaluated. Must be of the same shape as `age_eval`. filter_index: int Column index where the filter is located. model_age: list(float) List of ages which are given by the model. model_data: list(np.ndarray) List of arrays containing the model data. Returns ------- griddata : np.ndarray Interpolated values for the given evaluation points (age_eval, mag_eval). Has the same shape as age_eval and mag_eval. """ grid_points = np.array([]) grid_values = np.array([]) # create array of available points for age_index, age_item in enumerate(model_age): iso_mag = model_data[age_index][:, filter_index] iso_age = np.ones_like(iso_mag) * age_item iso_mass = model_data[age_index][:, 0] grid_points = np.append(grid_points, np.column_stack((iso_age, iso_mag))) grid_values = np.append(grid_values, iso_mass) grid_points = grid_points.reshape(-1, 2) interp = np.column_stack((age_eval, mag_eval)) return griddata(grid_points, grid_values, interp, method="cubic", rescale=True) @typechecked def run(self) -> None: """ Run method of the module. Calculates the mass limits from the contrast limits (as calculated with the :class:`~pynpoint.processing.limits.ContrastCurveModule`) and the isochrones of an evolutionary model. The age and the absolute magnitude of the isochrones are linearly interpolated such that the mass limits can be calculated for a given contrast limits (which is converted in an absolute magnitude with the apparent magnitude and distance of the central star). Returns ------- NoneType None """ model_age, model_data, model_header = self.read_model(self.m_model_file) assert self.m_instr_filter in model_header, ( "The selected filter was not found in the " "list of available filters from the model." ) # find the column index of the filter # simple argwhere gives empty list?! filter_index = np.argwhere([self.m_instr_filter == j for j in model_header])[0][ 0 ] contrast_data = self.m_contrast_in_port.get_all() separation = contrast_data[:, 0] contrast = contrast_data[:, 1] contrast_std = np.sqrt(contrast_data[:, 2]) age_eval = self.m_star_age * np.ones_like(contrast) mag_eval = self.m_star_abs + contrast print("Interpolating isochrones...", end="") mass = self.interpolate_model( age_eval=age_eval, mag_eval=mag_eval, filter_index=filter_index, model_age=model_age, model_data=model_data, ) mass_upper = ( self.interpolate_model( age_eval=age_eval, mag_eval=mag_eval - contrast_std, filter_index=filter_index, model_age=model_age, model_data=model_data, ) - mass ) mass_lower = ( self.interpolate_model( age_eval=age_eval, mag_eval=mag_eval + contrast_std, filter_index=filter_index, model_age=model_age, model_data=model_data, ) - mass ) mass_limits = np.column_stack((separation, mass, mass_upper, mass_lower)) self.m_mass_out_port.set_all(mass_limits, data_dim=2) print(" [DONE]") history = f"filter = {self.m_instr_filter}" self.m_mass_out_port.add_history("MassLimitsModule", history) self.m_mass_out_port.copy_attributes(self.m_contrast_in_port) self.m_mass_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/pcabackground.py000066400000000000000000001002521512720201400246120ustar00rootroot00000000000000""" Pipeline modules for PCA-based background subtraction. """ import math import time import warnings from typing import List, Optional, Tuple, Union import numpy as np from scipy.optimize import curve_fit from scipy.sparse.linalg import svds from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.processing.psfpreparation import SortParangModule from pynpoint.processing.resizing import CropImagesModule from pynpoint.processing.stacksubset import CombineTagsModule from pynpoint.util.module import memory_frames, progress from pynpoint.util.star import locate_star class PCABackgroundPreparationModule(ProcessingModule): """ Pipeline module for preparing the images for a PCA-based background subtraction. """ __author__ = "Tomas Stolker, Silvan Hunziker" @typechecked def __init__( self, name_in: str, image_in_tag: str, star_out_tag: str, subtracted_out_tag: str, background_out_tag: str, dither: Union[Tuple[int, int, int], Tuple[int, None, Tuple[float, float]]], combine: str = "mean", ) -> None: """ Parameters ---------- name_in : str Unique name of the pipeline module instance. image_in_tag : str Database tag with the images that are read as input. star_out_tag : str Database tag to store the images that contain the star. subtracted_out_tag : str Database tag to store the mean/median background subtracted images with the star. background_out_tag : str Database tag to store the images that contain only background emission. dither : tuple(int, int, int), tuple(int, None, tuple(float, float)) Tuple with the parameters for separating the star and background frames. The tuple should contain three values, ``(positions, cubes, first)``, with ``positions`` the number of unique dithering position, ``cubes`` the number of consecutive cubes per dithering position, and ``first`` the index value of the first cube which contains the star (Python indexing starts at zero). Sorting is based on the ``DITHER_X`` and ``DITHER_Y`` attributes when ``cubes`` is set to None. In that case, the ``first`` value should be a tuple with the ``DITHER_X`` and ``DITHER_Y`` values in which the star appears first. combine : str Method for combining the background images ('mean' or 'median'). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_star_out_port = self.add_output_port(star_out_tag) self.m_subtracted_out_port = self.add_output_port(subtracted_out_tag) self.m_background_out_port = self.add_output_port(background_out_tag) if len(dither) != 3: raise ValueError("The 'dither' argument should contain three values.") self.m_dither = dither self.m_combine = combine @typechecked def _prepare(self) -> Tuple[np.ndarray, np.ndarray]: nframes = self.m_image_in_port.get_attribute("NFRAMES") cube_mean = np.zeros( ( nframes.shape[0], self.m_image_in_port.get_shape()[2], self.m_image_in_port.get_shape()[1], ) ) count = 0 for i, item in enumerate(nframes): if self.m_combine == "mean": cube_mean[i,] = np.mean( self.m_image_in_port[count : count + item,], axis=0 ) elif self.m_combine == "median": cube_mean[i,] = np.median( self.m_image_in_port[count : count + item,], axis=0 ) count += item if self.m_dither[1] is None: dither_x = self.m_image_in_port.get_attribute("DITHER_X") dither_y = self.m_image_in_port.get_attribute("DITHER_Y") star = np.logical_and( dither_x == self.m_dither[2][0], dither_y == self.m_dither[2][1] ) bg_frames = np.invert(star) else: bg_frames = np.ones(nframes.shape[0], dtype=bool) for i in range( self.m_dither[2] * self.m_dither[1], np.size(nframes), self.m_dither[1] * self.m_dither[0], ): bg_frames[i : i + self.m_dither[1]] = False return bg_frames, cube_mean @typechecked def _separate( self, bg_frames: np.ndarray, bg_indices: np.ndarray, parang: Optional[np.ndarray], cube_mean: np.ndarray, ) -> Tuple[ np.array, Optional[np.ndarray], np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray, ]: @typechecked def _initialize() -> Tuple[ np.array, Optional[np.ndarray], np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray, ]: background_nframes = np.empty(0, dtype=np.int64) star_nframes = np.empty(0, dtype=np.int64) background_index = np.empty(0, dtype=np.int64) star_index = np.empty(0, dtype=np.int64) if parang is None: background_parang = None star_parang = None else: background_parang = np.empty(0, dtype=np.float64) star_parang = np.empty(0, dtype=np.float64) return ( star_index, star_parang, star_nframes, background_index, background_parang, background_nframes, ) @typechecked def _select_background(i: int) -> np.ndarray: # Previous background cube if np.size(bg_indices[bg_indices < i]) > 0: index_prev = np.amax(bg_indices[bg_indices < i]) bg_prev = cube_mean[index_prev,] else: bg_prev = None # Next background cube if np.size(bg_indices[bg_indices > i]) > 0: index_next = np.amin(bg_indices[bg_indices > i]) bg_next = cube_mean[index_next,] else: bg_next = None # Select background: previous, next, or mean of previous and next if bg_prev is None and bg_next is not None: background = bg_next elif bg_prev is not None and bg_next is None: background = bg_prev elif bg_prev is not None and bg_next is not None: background = (bg_prev + bg_next) / 2.0 else: raise ValueError( "Neither previous nor next background frames are found." ) return background nframes = self.m_image_in_port.get_attribute("NFRAMES") index = self.m_image_in_port.get_attribute("INDEX") ( star_index, star_parang, star_nframes, background_index, background_parang, background_nframes, ) = _initialize() # Separate star and background cubes. Subtract mean background. count = 0 start_time = time.time() for i, item in enumerate(nframes): progress( i, len(nframes), "Preparing PCA background subtraction...", start_time ) im_tmp = self.m_image_in_port[count : count + item,] # Background frames if bg_frames[i]: self.m_background_out_port.append(im_tmp) background_nframes = np.append(background_nframes, nframes[i]) background_index = np.append( background_index, index[count : count + item] ) if parang is not None: background_parang = np.append( background_parang, parang[count : count + item] ) # Star frames else: background = _select_background(i) self.m_star_out_port.append(im_tmp) self.m_subtracted_out_port.append(im_tmp - background) star_nframes = np.append(star_nframes, nframes[i]) star_index = np.append(star_index, index[count : count + item]) if parang is not None: star_parang = np.append(star_parang, parang[count : count + item]) count += item return ( star_index, star_parang, star_nframes, background_index, background_parang, background_nframes, ) @typechecked def run(self) -> None: """ Run method of the module. Separates the star and background frames, subtracts the mean or median background from both the star and background frames, and writes the star and background frames separately to their respective output ports. Returns ------- NoneType None """ if "PARANG" in self.m_image_in_port.get_all_non_static_attributes(): parang = self.m_image_in_port.get_attribute("PARANG") else: parang = None bg_frames, cube_mean = self._prepare() bg_indices = np.nonzero(bg_frames)[0] ( star_index, star_parang, star_nframes, background_index, background_parang, background_nframes, ) = self._separate(bg_frames, bg_indices, parang, cube_mean) history = f"frames = {sum(star_nframes)}, {len(background_nframes)}" self.m_star_out_port.copy_attributes(self.m_image_in_port) self.m_star_out_port.add_history("PCABackgroundPreparationModule", history) self.m_star_out_port.add_attribute("NFRAMES", star_nframes, static=False) self.m_star_out_port.add_attribute("INDEX", star_index, static=False) if parang is not None: self.m_star_out_port.add_attribute("PARANG", star_parang, static=False) self.m_subtracted_out_port.copy_attributes(self.m_image_in_port) self.m_subtracted_out_port.add_history( "PCABackgroundPreparationModule", history ) self.m_subtracted_out_port.add_attribute("NFRAMES", star_nframes, static=False) self.m_subtracted_out_port.add_attribute("INDEX", star_index, static=False) if parang is not None: self.m_subtracted_out_port.add_attribute( "PARANG", star_parang, static=False ) self.m_background_out_port.copy_attributes(self.m_image_in_port) self.m_background_out_port.add_history( "PCABackgroundPreparationModule", history ) self.m_background_out_port.add_attribute( "NFRAMES", background_nframes, static=False ) self.m_background_out_port.add_attribute( "INDEX", background_index, static=False ) if parang is not None: self.m_background_out_port.add_attribute( "PARANG", background_parang, static=False ) self.m_star_out_port.close_port() class PCABackgroundSubtractionModule(ProcessingModule): """ Pipeline module applying a PCA-based background subtraction (see Hunziker et al. 2018). """ __author__ = "Tomas Stolker, Silvan Hunziker" @typechecked def __init__( self, name_in: str, star_in_tag: str, background_in_tag: str, residuals_out_tag: str, fit_out_tag: Optional[str] = None, mask_out_tag: Optional[str] = None, pca_number: int = 60, mask_star: float = 0.7, subframe: Optional[float] = None, gaussian: float = 0.15, **kwargs, ) -> None: """ Parameters ---------- name_in : str Unique name of the pipeline module instance. star_in_tag : str Database tag with the input images that contain the star. background_in_tag : str Database tag with the input images that contain only background emission. residuals_out_tag : str Database tag to store the background-subtracted images of the star. fit_out_tag : str, None Database tag to store the modeled background images. The data is not stored if the arguments is set to None. mask_out_tag : str, None Database tag to store the mask. The data is not stored if the argument is set to None. pca_number : int Number of principal components that is used to model the background emission. mask_star : float Radius of the central mask (arcsec). gaussian : float Full width at half maximum (arcsec) of the Gaussian kernel that is used to smooth the image before the star is located. subframe : float, None Size (arcsec) of the subframe that is used to find the star. Cropping of the subframe is done around the center of the image. The full images is used if set to None. Returns ------- NoneType None """ super().__init__(name_in) if "subtract_mean" in kwargs: warnings.warn( "The 'subtract_mean' parameter has been deprecated. Subtracting of " "the mean is no longer optional so subtract_mean=True.", DeprecationWarning, ) self.m_star_in_port = self.add_input_port(star_in_tag) self.m_background_in_port = self.add_input_port(background_in_tag) self.m_residuals_out_port = self.add_output_port(residuals_out_tag) if fit_out_tag is None: self.m_fit_out_port = None else: self.m_fit_out_port = self.add_output_port(fit_out_tag) if mask_out_tag is None: self.m_mask_out_port = None else: self.m_mask_out_port = self.add_output_port(mask_out_tag) self.m_pca_number = pca_number self.m_mask_star = mask_star self.m_gaussian = gaussian self.m_subframe = subframe @typechecked def run(self) -> None: """ Run method of the module. Creates a PCA basis set of the background frames after subtracting the mean background frame from both the star and background frames, masks the PSF of the star, projects the star frames onto the principal components, and stores the residuals of the background subtracted images. Returns ------- NoneType None """ @typechecked def _create_mask( radius: float, position: np.ndarray, nimages: int ) -> np.ndarray: """ Method for creating a circular mask at the star or planet position. """ npix = self.m_star_in_port.get_shape()[1] x_grid = np.arange(0, npix, 1) y_grid = np.arange(0, npix, 1) xx_grid, yy_grid = np.meshgrid(x_grid, y_grid) mask = np.ones((nimages, npix, npix)) cent_x = position[:, 1] cent_y = position[:, 0] for i in range(nimages): rr_grid = np.sqrt( (xx_grid - cent_x[i]) ** 2 + (yy_grid - cent_y[i]) ** 2 ) mask[i,][rr_grid < radius] = 0.0 return mask @typechecked def _create_basis(images: np.ndarray, pca_number: int) -> np.ndarray: """ Method for calculating the principal components for a stack of background images. Parameters ---------- images : np.ndarray Background images with the mean subtracted from all images. pca_number : int Number of principal components that is used to model the background emission. Returns ------- np.ndarray Principal components with the second and third dimension reshaped to ``images``. """ _, _, v_svd = svds( images.reshape(images.shape[0], images.shape[1] * images.shape[2]), k=pca_number, ) v_svd = v_svd[::-1,] return v_svd.reshape(v_svd.shape[0], images.shape[1], images.shape[2]) @typechecked def _model_background( basis: np.ndarray, im_arr: np.ndarray, mask: np.ndarray ) -> np.ndarray: """ Method for creating a model of the background. """ @typechecked def _dot_product(x_dot: np.ndarray, *p: np.float64) -> np.ndarray: return np.dot(p, x_dot) fit_im_chi = np.zeros(im_arr.shape) # fit_coeff_chi = np.zeros((im_arr.shape[0], basis.shape[0])) basis_reshaped = basis.reshape(basis.shape[0], -1) for i in range(im_arr.shape[0]): basis_reshaped_masked = (basis * mask[i]).reshape(basis.shape[0], -1) data_to_fit = im_arr[i,] init = np.ones(basis_reshaped_masked.shape[0]) fitted = curve_fit( _dot_product, basis_reshaped_masked, data_to_fit.reshape(-1), init ) fit_im = np.dot(fitted[0], basis_reshaped) fit_im = fit_im.reshape(data_to_fit.shape[0], data_to_fit.shape[1]) fit_im_chi[i,] = fit_im # fit_coeff_chi[i, ] = fitted[0] return fit_im_chi memory = self._m_config_port.get_attribute("MEMORY") pixscale = self.m_star_in_port.get_attribute("PIXSCALE") nimages = self.m_star_in_port.get_shape()[0] frames = memory_frames(memory, nimages) self.m_mask_star /= pixscale self.m_gaussian = int(math.ceil(self.m_gaussian / pixscale)) if self.m_subframe is not None: self.m_subframe /= pixscale self.m_subframe = int(math.ceil(self.m_subframe)) bg_mean = np.mean(self.m_background_in_port.get_all(), axis=0) star = np.zeros((nimages, 2)) for i, _ in enumerate(star): star[i, :] = locate_star( image=self.m_star_in_port[i,] - bg_mean, center=None, width=self.m_subframe, fwhm=self.m_gaussian, ) print("Creating PCA basis set...", end="") basis_pca = _create_basis( self.m_background_in_port.get_all() - bg_mean, self.m_pca_number ) print(" [DONE]") start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Calculating background model...", start_time) # Subtract the mean background from the star frames im_star = self.m_star_in_port[frames[i] : frames[i + 1],] - bg_mean mask = _create_mask( self.m_mask_star, star[frames[i] : frames[i + 1],], int(frames[i + 1] - frames[i]), ) fit_im = _model_background(basis_pca, im_star * mask, mask) self.m_residuals_out_port.append(im_star - fit_im) if self.m_fit_out_port is not None: self.m_fit_out_port.append(fit_im) if self.m_mask_out_port is not None: self.m_mask_out_port.append(mask) history = f"PC number = {self.m_pca_number}" self.m_residuals_out_port.copy_attributes(self.m_star_in_port) self.m_residuals_out_port.add_history("PCABackgroundSubtractionModule", history) self.m_residuals_out_port.add_attribute("STAR_POSITION", star, static=False) if self.m_fit_out_port is not None: self.m_fit_out_port.copy_attributes(self.m_star_in_port) self.m_fit_out_port.add_history("PCABackgroundSubtractionModule", history) if self.m_mask_out_port is not None: self.m_mask_out_port.copy_attributes(self.m_star_in_port) self.m_mask_out_port.add_history("PCABackgroundSubtractionModule", history) self.m_residuals_out_port.close_port() class DitheringBackgroundModule(ProcessingModule): """ Pipeline module for PCA-based background subtraction of dithering data. This is a wrapper that applies the processing modules for either a mean or the PCA-based background subtraction. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, center: Optional[List[Tuple[int, int]]] = None, cubes: Optional[int] = None, size: float = 2.0, gaussian: float = 0.15, subframe: Optional[float] = None, pca_number: Optional[int] = 5, mask_star: float = 0.7, **kwargs, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Database tag with input images. image_out_tag : str Database tag to store the background subtracted images. center : list(tuple(int, int)), None Tuple with the centers of the dithering positions, e.g. ((x0, y0), (x1, y1)). The order of the coordinates should correspond to the order in which the star is present. If ``center`` and ``cubes`` are both set to None then sorting and subtracting of the background frames is based on ``DITHER_X`` and ``DITHER_Y``. If ``center`` is specified and ``cubes`` is set to None then the ``DITHER_X`` and ``DITHER_Y`` attributes will be used for sorting and subtracting of the background but not for selecting the dither positions. cubes : int, None Number of consecutive cubes per dither position. If ``cubes`` is set to None then sorting and subtracting of the background frames is based on ``DITHER_X`` and ``DITHER_Y``. size : float Cropped image size (arcsec). gaussian : float Full width at half maximum (arcsec) of the Gaussian kernel that is used to smooth the image before the star is located. subframe : float, None Size (arcsec) of the subframe that is used to search for the star. Cropping of the subframe is done around the center of the dithering position. The full image size (i.e. ``size``) will be used if set to None then. pca_number : int, None Number of principal components that is used to model the background emission. The PCA background subtraction is skipped if the argument is set to None. In that case, the mean background subtracted images are written toe ``image_out_tag``. mask_star : float Radius of the central mask (arcsec) that is used to exclude the star when fitting the principal components. The region behind the mask is included when subtracting the PCA background model. Returns ------- NoneType None """ if "mask_planet" in kwargs: warnings.warn( "The 'mask_planet' parameter has been deprecated.", DeprecationWarning ) if "crop" in kwargs: warnings.warn( "The 'crop' parameter has been deprecated. The step to crop the " "images is no longer optional so crop=True.", DeprecationWarning, ) if "prepare" in kwargs: warnings.warn( "The 'prepare' parameter has been deprecated. The preparation step " "is no longer optional so prepare=True.", DeprecationWarning, ) if "pca_background" in kwargs: warnings.warn( "The 'pca_background' parameter has been deprecated. The PCA " "background is no longer optional when combine='pca' so " "pca_background=True.", DeprecationWarning, ) if "subtract_mean" in kwargs: warnings.warn( "The 'subtract_mean' parameter has been deprecated. Subtracting of " "the mean is no longer optional so subtract_mean=True.", DeprecationWarning, ) if "combine" in kwargs: warnings.warn( "The 'combine' parameter has been deprecated. To write the mean " "background subtracted images to image_out_tag is done by setting " "pca_number=None.", DeprecationWarning, ) super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_center = center self.m_cubes = cubes self.m_size = size self.m_gaussian = gaussian self.m_pca_number = pca_number self.m_mask_star = mask_star self.m_subframe = subframe self.m_image_in_tag = image_in_tag self.m_image_out_tag = image_out_tag @typechecked def _initialize(self) -> Tuple[int, np.ndarray]: if self.m_cubes is None: dither_x = self.m_image_in_port.get_attribute("DITHER_X") dither_y = self.m_image_in_port.get_attribute("DITHER_Y") dither_xy = np.zeros((dither_x.shape[0], 2)) dither_xy[:, 0] = dither_x dither_xy[:, 1] = dither_y _, index = np.unique(dither_xy, axis=0, return_index=True) dither = dither_xy[np.sort(index)] npix = self.m_image_in_port.get_shape()[1] # Compute center from dither and make sure all positions are actually Python integers if self.m_center is None: self.m_center = np.copy(dither) + float(npix) / 2.0 self.m_center = tuple( zip(map(int, self.m_center[:, 0]), map(int, self.m_center[:, 1])) ) else: if np.size(dither, axis=0) != np.size(self.m_center, axis=0): raise ValueError( "Number of specified center positions should be equal to the " "number of unique dithering positions." ) n_dither = np.size(self.m_center, 0) if self.m_cubes is None: star_pos = np.copy(dither) else: star_pos = np.arange(0, n_dither, 1) return n_dither, star_pos @typechecked def run(self) -> None: """ Run method of the module. Cuts out the detector sections at the different dither positions, prepares the PCA background subtraction, locates the star in each image, runs the PCA background subtraction, combines the output from the different dither positions is written to a single database tag. Returns ------- NoneType None """ @typechecked def _admin_start( count: int, n_dither: int, position: Tuple[int, int], star_pos: Union[np.ndarray, np.int64], ) -> None: print(f"Processing dither position {count+1} out of {n_dither}...") print(f"Center position = {position}") if self.m_cubes is None and self.m_center is not None: print(f"DITHER_X, DITHER_Y = {tuple(star_pos)}") @typechecked def _admin_end(count: int) -> None: if self.m_pca_number is None: tags.append(f"{self.m_image_in_tag}_dither_mean{count+1}") else: tags.append(f"{self.m_image_in_tag}_dither_pca_res{count+1}") n_dither, star_pos = self._initialize() tags = [] for i, position in enumerate(self.m_center): _admin_start(i, n_dither, position, star_pos[i]) im_out_tag = f"{self.m_image_in_tag}_dither_crop{i+1}" module = CropImagesModule( name_in=f"crop{i}", image_in_tag=self.m_image_in_tag, image_out_tag=im_out_tag, size=self.m_size, center=(int(math.ceil(position[0])), int(math.ceil(position[1]))), ) module.connect_database(self._m_data_base) module._m_output_ports[im_out_tag].del_all_data() module._m_output_ports[im_out_tag].del_all_attributes() module.run() if self.m_cubes is None: dither_val = (n_dither, self.m_cubes, tuple(star_pos[i])) else: dither_val = (n_dither, self.m_cubes, int(star_pos[i])) im_in_tag = f"{self.m_image_in_tag}_dither_crop{i+1}" star_out_tag = f"{self.m_image_in_tag}_dither_star{i+1}" sub_out_tag = f"{self.m_image_in_tag}_dither_mean{i+1}" back_out_tag = f"{self.m_image_in_tag}_dither_background{i+1}" module = PCABackgroundPreparationModule( name_in=f"prepare{i}", image_in_tag=im_in_tag, star_out_tag=star_out_tag, subtracted_out_tag=sub_out_tag, background_out_tag=back_out_tag, dither=dither_val, combine="mean", ) module.connect_database(self._m_data_base) module._m_output_ports[star_out_tag].del_all_data() module._m_output_ports[star_out_tag].del_all_attributes() module._m_output_ports[sub_out_tag].del_all_data() module._m_output_ports[sub_out_tag].del_all_attributes() module._m_output_ports[back_out_tag].del_all_data() module._m_output_ports[back_out_tag].del_all_attributes() module.run() if self.m_pca_number is not None: star_in_tag = f"{self.m_image_in_tag}_dither_star{i+1}" back_in_tag = f"{self.m_image_in_tag}_dither_background{i+1}" res_out_tag = f"{self.m_image_in_tag}_dither_pca_res{i+1}" fit_out_tag = f"{self.m_image_in_tag}_dither_pca_fit{i+1}" mask_out_tag = f"{self.m_image_in_tag}_dither_pca_mask{i+1}" module = PCABackgroundSubtractionModule( name_in=f"pca_background{i}", star_in_tag=star_in_tag, background_in_tag=back_in_tag, residuals_out_tag=res_out_tag, fit_out_tag=fit_out_tag, mask_out_tag=mask_out_tag, pca_number=self.m_pca_number, mask_star=self.m_mask_star, subframe=self.m_subframe, gaussian=self.m_gaussian, ) module.connect_database(self._m_data_base) module._m_output_ports[res_out_tag].del_all_data() module._m_output_ports[res_out_tag].del_all_attributes() module._m_output_ports[fit_out_tag].del_all_data() module._m_output_ports[fit_out_tag].del_all_attributes() module._m_output_ports[mask_out_tag].del_all_data() module._m_output_ports[mask_out_tag].del_all_attributes() module.run() _admin_end(i) module = CombineTagsModule( name_in="combine", check_attr=True, index_init=False, image_in_tags=tags, image_out_tag=self.m_image_in_tag + "_dither_combine", ) module.connect_database(self._m_data_base) module._m_output_ports[self.m_image_in_tag + "_dither_combine"].del_all_data() module._m_output_ports[ self.m_image_in_tag + "_dither_combine" ].del_all_attributes() module.run() module = SortParangModule( name_in="sort", image_in_tag=self.m_image_in_tag + "_dither_combine", image_out_tag=self.m_image_out_tag, ) module.connect_database(self._m_data_base) module._m_output_ports[self.m_image_out_tag].del_all_data() module._m_output_ports[self.m_image_out_tag].del_all_attributes() module.run() PynPoint-PynPoint-e5d0637/pynpoint/processing/psfpreparation.py000066400000000000000000000652021512720201400250510ustar00rootroot00000000000000""" Pipeline modules to prepare the data for the PSF subtraction. """ import time import warnings from typing import Optional, Tuple import numpy as np from astropy.coordinates import EarthLocation from astropy.time import Time from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.module import progress, memory_frames from pynpoint.util.image import create_mask, scale_image, shift_image class PSFpreparationModule(ProcessingModule): """ Module to prepare the data for PSF subtraction with PCA. The preparation steps include masking and an optional normalization. """ __author__ = "Markus Bonse, Tomas Stolker, Timothy Gebhard, Sven Kiefer" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, mask_out_tag: Optional[str] = None, norm: bool = False, resize: Optional[float] = None, cent_size: Optional[float] = None, edge_size: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry with images that is written as output. mask_out_tag : str, None, optional Tag of the database entry with the mask that is written as output. If set to None, no mask array is saved. norm : bool Normalize each image by its Frobenius norm. Only supported for 3D datasets (i.e. regular imaging). resize : float, None DEPRECATED. This parameter is currently ignored by the module and will be removed in a future version of PynPoint. cent_size : float, None, optional Radius of the central mask (in arcsec). No mask is used when set to None. edge_size : float, None, optional Outer radius (in arcsec) beyond which pixels are masked. No outer mask is used when set to None. If the value is larger than half the image size then it will be set to half the image size. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) if mask_out_tag is None: self.m_mask_out_port = None else: self.m_mask_out_port = self.add_output_port(mask_out_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_cent_size = cent_size self.m_edge_size = edge_size self.m_norm = norm # Raise a DeprecationWarning if the resize argument is used if resize is not None: warnings.warn( "The 'resize' parameter has been deprecated. Its value is currently " "being ignored, and the argument will be removed in a future version " "of PynPoint.", DeprecationWarning, ) @typechecked def run(self) -> None: """ Run method of the module. Masks and normalizes the images. Returns ------- NoneType None """ # Get the PIXSCALE and MEMORY attributes pixscale = self.m_image_in_port.get_attribute("PIXSCALE") memory = self._m_config_port.get_attribute("MEMORY") # Get the numnber of dimensions and shape ndim = self.m_image_in_port.get_ndim() im_shape = self.m_image_in_port.get_shape() if ndim == 3: # Number of images nimages = im_shape[-3] # Split into batches to comply with memory constraints frames = memory_frames(memory, nimages) elif ndim == 4: # Process all wavelengths per exposure at once frames = np.linspace(0, im_shape[-3], im_shape[-3] + 1) if self.m_norm and ndim == 4: warnings.warn( "The 'norm' parameter does not support 4D datasets and will therefore " "be ignored." ) # Convert m_cent_size and m_edge_size from arcseconds to pixels if self.m_cent_size is not None: self.m_cent_size /= pixscale if self.m_edge_size is not None: self.m_edge_size /= pixscale # Create 2D disk mask which will be applied to every frame mask = create_mask( (int(im_shape[-2]), int(im_shape[-1])), (self.m_cent_size, self.m_edge_size) ).astype(bool) # Keep track of the normalization vectors in case we are normalizing the images (if # we are not normalizing, this list will remain empty) norms = list() start_time = time.time() # Run the PSFpreparationModule for each subset of frames for i in range(frames[:-1].size): # Print progress to command line progress( i, len(frames[:-1]), "Preparing images for PSF subtraction...", start_time, ) if ndim == 3: # Get the images and ensure they have the correct 3D shape with the following # three dimensions: (batch_size, height, width) images = self.m_image_in_port[frames[i] : frames[i + 1],] if images.ndim == 2: warnings.warn( "The input data has 2 dimensions whereas 3 dimensions are " "required. An extra dimension has been added." ) images = images[np.newaxis, ...] elif ndim == 4: # Process all wavelengths per exposure at once images = self.m_image_in_port[ :, i, ] # Apply the mask, i.e., set all pixels to 0 where the mask is False images[:, ~mask] = 0.0 # If desired, normalize the images using the Frobenius norm if self.m_norm and ndim == 3: im_norm = np.linalg.norm(images, ord="fro", axis=(1, 2)) images /= im_norm[:, np.newaxis, np.newaxis] norms.append(im_norm) # Write processed images to output port if ndim == 3: self.m_image_out_port.append(images, data_dim=3) elif ndim == 4: self.m_image_out_port.append(images, data_dim=4) # Store information about mask if self.m_mask_out_port is not None: self.m_mask_out_port.set_all(mask) self.m_mask_out_port.copy_attributes(self.m_image_in_port) # Copy attributes from input port self.m_image_out_port.copy_attributes(self.m_image_in_port) # If the norms list is not empty (i.e., if we have computed the norm for every image), # we can also save the corresponding norm vector as an additional attribute if norms: self.m_image_out_port.add_attribute( name="norm", value=np.hstack(norms), static=False ) # Save cent_size and edge_size as attributes to the output port if self.m_cent_size is not None: self.m_image_out_port.add_attribute( name="cent_size", value=self.m_cent_size * pixscale, static=True ) if self.m_edge_size is not None: self.m_image_out_port.add_attribute( name="edge_size", value=self.m_edge_size * pixscale, static=True ) class AngleInterpolationModule(ProcessingModule): """ Module for calculating the parallactic angle values by interpolating between the begin and end value of a data cube. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__(self, name_in: str, data_tag: str) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry for which the parallactic angles are written as attributes. Returns ------- NoneType None """ super().__init__(name_in) self.m_data_in_port = self.add_input_port(data_tag) self.m_data_out_port = self.add_output_port(data_tag) @typechecked def run(self) -> None: """ Run method of the module. Calculates the parallactic angles of each frame by linearly interpolating between the start and end values of the data cubes. The values are written as attributes to *data_tag*. A correction of 360 deg is applied when the start and end values of the angles change sign at +/-180 deg. Returns ------- NoneType None """ parang_start = self.m_data_in_port.get_attribute("PARANG_START") parang_end = self.m_data_in_port.get_attribute("PARANG_END") steps = self.m_data_in_port.get_attribute("NFRAMES") if "NDIT" in self.m_data_in_port.get_all_non_static_attributes(): ndit = self.m_data_in_port.get_attribute("NDIT") if len(ndit) != len(steps) or not np.all(ndit == steps): warnings.warn( "There is a mismatch between the NDIT and NFRAMES values. The " "parallactic angles are calculated with a linear interpolation by " "using NFRAMES steps. A frame selection should be applied after " "the parallactic angles are calculated." ) new_angles = [] start_time = time.time() for i, _ in enumerate(parang_start): progress( i, len(parang_start), "Interpolating parallactic angles...", start_time ) if parang_start[i] < -170.0 and parang_end[i] > 170.0: parang_start[i] += 360.0 elif parang_end[i] < -170.0 and parang_start[i] > 170.0: parang_end[i] += 360.0 if steps[i] == 1: new_angles = np.append( new_angles, [(parang_start[i] + parang_end[i]) / 2.0] ) elif steps[i] != 1: new_angles = np.append( new_angles, np.linspace(parang_start[i], parang_end[i], num=steps[i]), ) self.m_data_out_port.add_attribute("PARANG", new_angles, static=False) class SortParangModule(ProcessingModule): """ Module to sort the images and attributes with increasing ``INDEX``. """ __author__ = "Tomas Stolker" @typechecked def __init__(self, name_in: str, image_in_tag: str, image_out_tag: str) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Database tag with the input data. image_out_tag : str Database tag where the output data will be stored. Should be different from ``image_in_tag``. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) @typechecked def run(self) -> None: """ Run method of the module. Sorts the images and attributes with increasing ``INDEX``. Therefore, the images are sorted by there original (usually chronological) order. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") index = self.m_image_in_port.get_attribute("INDEX") ndim = self.m_image_in_port.get_ndim() nimages = self.m_image_in_port.get_shape()[-3] index_new = np.zeros(index.shape, dtype=int) if "PARANG" in self.m_image_in_port.get_all_non_static_attributes(): parang = self.m_image_in_port.get_attribute("PARANG") parang_new = np.zeros(parang.shape) else: parang_new = None if "STAR_POSITION" in self.m_image_in_port.get_all_non_static_attributes(): star = self.m_image_in_port.get_attribute("STAR_POSITION") star_new = np.zeros(star.shape) else: star_new = None index_sort = np.argsort(index) frames = memory_frames(memory, nimages) start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Sorting images in time...", start_time) index_new[frames[i] : frames[i + 1]] = index[ index_sort[frames[i] : frames[i + 1]] ] if parang_new is not None: parang_new[frames[i] : frames[i + 1]] = parang[ index_sort[frames[i] : frames[i + 1]] ] if star_new is not None: star_new[frames[i] : frames[i + 1]] = star[ index_sort[frames[i] : frames[i + 1]] ] # HDF5 indexing elements must be in increasing order for item in index_sort[frames[i] : frames[i + 1]]: if ndim == 3: self.m_image_out_port.append( self.m_image_in_port[item,], data_dim=3 ) elif ndim == 4: self.m_image_out_port.append( self.m_image_in_port[ :, item, ], data_dim=4, ) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("SortParangModule", "sorted by INDEX") self.m_image_out_port.add_attribute("INDEX", index_new, static=False) if parang_new is not None: self.m_image_out_port.add_attribute("PARANG", parang_new, static=False) if star_new is not None: self.m_image_out_port.add_attribute("STAR_POSITION", star_new, static=False) self.m_image_out_port.close_port() class AngleCalculationModule(ProcessingModule): """ Module for calculating the parallactic angles. The start time of the observation is taken and multiples of the exposure time are added to derive the parallactic angle of each frame inside the cube. Instrument specific overheads are included. """ __author__ = "Alexander Bohn, Tomas Stolker" @typechecked def __init__(self, name_in: str, data_tag: str, instrument: str = "NACO") -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry for which the parallactic angles are written as attributes. instrument : str Instrument name ('NACO', 'SPHERE/IRDIS', or 'SPHERE/IFS'). Returns ------- NoneType None """ super().__init__(name_in) # Parameters self.m_instrument = instrument # Set parameters according to choice of instrument if self.m_instrument == "NACO": # pupil offset in degrees self.m_pupil_offset = 0.0 # No offset here # no overheads in cube mode, since cube is read out after all individual exposures # see NACO manual page 62 (v102) self.m_O_START = 0.0 self.m_DIT_DELAY = 0.0 self.m_ROT = 0.0 # rotator offset in degrees self.m_rot_offset = 89.44 # According to NACO manual page 65 (v102) elif self.m_instrument == "SPHERE/IRDIS": # pupil offset in degrees self.m_pupil_offset = -135.99 # According to SPHERE manual page 64 (v102) # overheads in cube mode (several NDITS) in hours self.m_O_START = ( 0.3 / 3600.0 ) # According to SPHERE manual page 90/91 (v102) self.m_DIT_DELAY = ( 0.1 / 3600.0 ) # According to SPHERE manual page 90/91 (v102) self.m_ROT = 0.838 / 3600.0 # According to SPHERE manual page 90/91 (v102) # rotator offset in degrees self.m_rot_offset = 0.0 # no offset here elif self.m_instrument == "SPHERE/IFS": # pupil offset in degrees self.m_pupil_offset = ( -135.99 - 100.48 ) # According to SPHERE manual page 64 (v102) # overheads in cube mode (several NDITS) in hours self.m_O_START = ( 0.3 / 3600.0 ) # According to SPHERE manual page 90/91 (v102) self.m_DIT_DELAY = ( 0.2 / 3600.0 ) # According to SPHERE manual page 90/91 (v102) self.m_ROT = 1.65 / 3600.0 # According to SPHERE manual page 90/91 (v102) # rotator offset in degrees self.m_rot_offset = 0.0 # no offset here else: raise ValueError( "The instrument argument should be set to either 'NACO', " "'SPHERE/IRDIS', or 'SPHERE/IFS'." ) self.m_data_in_port = self.add_input_port(data_tag) self.m_data_out_port = self.add_output_port(data_tag) @typechecked def _attribute_check(self, ndit: np.ndarray, steps: np.ndarray) -> None: if not np.all(ndit == steps): warnings.warn( "There is a mismatch between the NDIT and NFRAMES values. A frame " "selection should be applied after the parallactic angles are " "calculated." ) if self.m_instrument == "SPHERE/IFS": warnings.warn( "AngleCalculationModule has not been tested for SPHERE/IFS data." ) if self.m_instrument in ("SPHERE/IRDIS", "SPHERE/IFS"): if self._m_config_port.get_attribute("RA") != "ESO INS4 DROT2 RA": warnings.warn( "For SPHERE data it is recommended to use the header keyword " "'ESO INS4 DROT2 RA' to specify the object's right ascension. " "The input will be parsed accordingly. Using the regular " "'RA' keyword will lead to wrong parallactic angles." ) if self._m_config_port.get_attribute("DEC") != "ESO INS4 DROT2 DEC": warnings.warn( "For SPHERE data it is recommended to use the header keyword " "'ESO INS4 DROT2 DEC' to specify the object's declination. " "The input will be parsed accordingly. Using the regular " "'DEC' keyword will lead to wrong parallactic angles." ) @typechecked def run(self) -> None: """ Run method of the module. Calculates the parallactic angles from the position of the object on the sky and the telescope location on earth. The start of the observation is used to extrapolate for the observation time of each individual image of a data cube. The values are written as PARANG attributes to *data_tag*. Returns ------- NoneType None """ # Load cube sizes steps = self.m_data_in_port.get_attribute("NFRAMES") ndit = self.m_data_in_port.get_attribute("NDIT") self._attribute_check(ndit, steps) # Load exposure time [hours] exptime = self.m_data_in_port.get_attribute("DIT") / 3600.0 # Load telescope location tel_lat = self.m_data_in_port.get_attribute("LATITUDE") tel_lon = self.m_data_in_port.get_attribute("LONGITUDE") # Load temporary target position tmp_ra = self.m_data_in_port.get_attribute("RA") tmp_dec = self.m_data_in_port.get_attribute("DEC") # Parse to degree depending on instrument if "SPHERE" in self.m_instrument: # get sign of declination tmp_dec_sign = np.sign(tmp_dec) tmp_dec = np.abs(tmp_dec) # parse RA tmp_ra_s = tmp_ra % 100 tmp_ra_m = ((tmp_ra - tmp_ra_s) / 1e2) % 100 tmp_ra_h = (tmp_ra - tmp_ra_s - tmp_ra_m * 1e2) / 1e4 # parse DEC tmp_dec_s = tmp_dec % 100 tmp_dec_m = ((tmp_dec - tmp_dec_s) / 1e2) % 100 tmp_dec_d = (tmp_dec - tmp_dec_s - tmp_dec_m * 1e2) / 1e4 # get RA and DEC in degree ra = (tmp_ra_h + tmp_ra_m / 60.0 + tmp_ra_s / 3600.0) * 15.0 dec = tmp_dec_sign * (tmp_dec_d + tmp_dec_m / 60.0 + tmp_dec_s / 3600.0) else: ra = tmp_ra dec = tmp_dec # Load start times of exposures obs_dates = self.m_data_in_port.get_attribute("DATE") # Load pupil positions during observations if self.m_instrument == "NACO": pupil_pos = self.m_data_in_port.get_attribute("PUPIL") elif self.m_instrument == "SPHERE/IRDIS": pupil_pos = np.zeros(steps.shape) elif self.m_instrument == "SPHERE/IFS": pupil_pos = np.zeros(steps.shape) new_angles = np.array([]) pupil_pos_arr = np.array([]) start_time = time.time() # Calculate parallactic angles for each cube for i, tmp_steps in enumerate(steps): progress(i, len(steps), "Calculating parallactic angles...", start_time) t = Time( obs_dates[i].decode("utf-8"), location=EarthLocation(lat=tel_lat, lon=tel_lon), ) sid_time = t.sidereal_time("apparent").value # Extrapolate sideral times from start time of the cube for each frame of it sid_time_arr = np.linspace( sid_time + self.m_O_START, (sid_time + self.m_O_START) + (exptime + self.m_DIT_DELAY + self.m_ROT) * (tmp_steps - 1), tmp_steps, ) # Convert to degrees sid_time_arr_deg = sid_time_arr * 15.0 # Calculate hour angle in degrees hour_angle = sid_time_arr_deg - ra[i] # Conversion to radians: hour_angle_rad = np.deg2rad(hour_angle) dec_rad = np.deg2rad(dec[i]) lat_rad = np.deg2rad(tel_lat) p_angle = np.arctan2( np.sin(hour_angle_rad), ( np.cos(dec_rad) * np.tan(lat_rad) - np.sin(dec_rad) * np.cos(hour_angle_rad) ), ) new_angles = np.append(new_angles, np.rad2deg(p_angle)) pupil_pos_arr = np.append(pupil_pos_arr, np.ones(tmp_steps) * pupil_pos[i]) # Correct for rotator (SPHERE) or pupil offset (NACO) if self.m_instrument == "NACO": # See NACO manual page 65 (v102) new_angles_corr = new_angles - (90.0 + (self.m_rot_offset - pupil_pos_arr)) elif self.m_instrument == "SPHERE/IRDIS": # See SPHERE manual page 64 (v102) new_angles_corr = new_angles - self.m_pupil_offset elif self.m_instrument == "SPHERE/IFS": # See SPHERE manual page 64 (v102) new_angles_corr = new_angles - self.m_pupil_offset indices = np.where(new_angles_corr < -180.0)[0] if indices.size > 0: new_angles_corr[indices] += 360.0 indices = np.where(new_angles_corr > 180.0)[0] if indices.size > 0: new_angles_corr[indices] -= 360.0 self.m_data_out_port.add_attribute("PARANG", new_angles_corr, static=False) class SDIpreparationModule(ProcessingModule): """ Module for preparing continuum frames for dual-band simultaneous differential imaging. """ __author__ = "Gabriele Cugno, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, wavelength: Tuple[float, float], width: Tuple[float, float], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Should be different from *image_in_tag*. wavelength : tuple(float, float) The central wavelengths of the line and continuum filter, (line, continuum), in arbitrary but identical units. width : tuple(float, float) The equivalent widths of the line and continuum filter, (line, continuum), in arbitrary but identical units. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_line_wvl = wavelength[0] self.m_cnt_wvl = wavelength[1] self.m_line_width = width[0] self.m_cnt_width = width[1] @typechecked def run(self) -> None: """ Run method of the module. Normalizes the images for the different filter widths, upscales the images, and crops the images to the initial image shape in order to align the PSF patterns. Returns ------- NoneType None """ wvl_factor = self.m_line_wvl / self.m_cnt_wvl width_factor = self.m_line_width / self.m_cnt_width nimages = self.m_image_in_port.get_shape()[0] start_time = time.time() for i in range(nimages): progress(i, nimages, "Preparing images for dual-band SDI...", start_time) image = self.m_image_in_port[i,] im_scale = width_factor * scale_image(image, wvl_factor, wvl_factor) if i == 0: npix_del = im_scale.shape[-1] - image.shape[-1] if npix_del % 2 == 0: npix_del_a = int(npix_del / 2) npix_del_b = int(npix_del / 2) else: npix_del_a = int((npix_del - 1) / 2) npix_del_b = int((npix_del + 1) / 2) im_crop = im_scale[npix_del_a:-npix_del_b, npix_del_a:-npix_del_b] if npix_del % 2 == 1: im_crop = shift_image(im_crop, (-0.5, -0.5), interpolation="spline") self.m_image_out_port.append(im_crop, data_dim=3) history = f"(line, continuum) = ({self.m_line_wvl}, {self.m_cnt_wvl})" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("SDIpreparationModule", history) self.m_image_in_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/psfsubtraction.py000066400000000000000000001001051512720201400250520ustar00rootroot00000000000000""" Pipeline modules for PSF subtraction. """ import time import math import warnings from copy import deepcopy from typing import List, Optional, Tuple, Union import numpy as np from scipy.ndimage import rotate from sklearn.decomposition import PCA from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import subtract_psf from pynpoint.util.module import progress from pynpoint.util.multipca import PcaMultiprocessingCapsule from pynpoint.util.residuals import combine_residuals from pynpoint.util.postproc import postprocessor from pynpoint.util.sdi import scaling_factors class PcaPsfSubtractionModule(ProcessingModule): """ Pipeline module for PSF subtraction with principal component analysis (PCA). The module can be used for ADI, RDI (see ``subtract_mean`` parameter), SDI, and ASDI. The residuals are calculated in parallel for the selected numbers of principal components. This may require a large amount of memory in case the stack of input images is very large. The number of processes can therefore be set with the ``CPU`` keyword in the configuration file. """ __author__ = "Markus Bonse, Tomas Stolker, Sven Kiefer" @typechecked def __init__( self, name_in: str, images_in_tag: str, reference_in_tag: str, res_mean_tag: Optional[str] = None, res_median_tag: Optional[str] = None, res_weighted_tag: Optional[str] = None, res_rot_mean_clip_tag: Optional[str] = None, res_arr_out_tag: Optional[str] = None, basis_out_tag: Optional[str] = None, pca_numbers: Union[ range, List[int], np.ndarray, Tuple[range, range], Tuple[List[int], List[int]], Tuple[np.ndarray, np.ndarray], ] = range(1, 21), extra_rot: float = 0.0, subtract_mean: bool = True, processing_type: str = "ADI", ) -> None: """ Parameters ---------- name_in : str Name tag of the pipeline module. images_in_tag : str Database entry with the images from which the PSF model will be subtracted. reference_in_tag : str Database entry with the reference images from which the PSF model is created. Usually ``reference_in_tag`` is the same as ``images_in_tag``, but a different dataset can be used as reference images in case of RDI. res_mean_tag : str, None Database entry where the the mean-collapsed residuals will be stored. The residuals are not calculated and stored if set to None. res_median_tag : str, None Database entry where the the median-collapsed residuals will be stored. The residuals are not calculated and stored if set to None. res_weighted_tag : str, None Database entry where the the noise-weighted residuals will be stored (see Bottom et al. 2017). The residuals are not calculated and stored if set to None. res_rot_mean_clip_tag : str, None Tag of the database entry of the clipped mean residuals. Not calculated if set to None. res_arr_out_tag : str, None Database entry where the derotated, but not collapsed, residuals are stored. The number of principal components is was used is appended to the ``res_arr_out_tag``. The residuals are not stored if set to None. This parameter is not supported with multiprocessing (i.e. ``CPU`` > 1). For IFS data and if the processing type is either ADI+SDI or SDI+ADI the residuals can only be calculated if exactly 1 principal component for each ADI and SDI is given with the pca_numbers parameter. basis_out_tag : str, None Database entry where the principal components are stored. The data is not stored if set to None. Only supported for imaging data with ``processing_type='ADI'``. pca_numbers : range, list(int), np.ndarray, tuple(range, range), tuple[list(int), list(int)), tuple(np.ndarray, np.ndarray)) Number of principal components that are used for the PSF model. With ADI or SDI, a single list/range/array needs to be provided while for SDI+ADI or ADI+SDI a tuple is required with twice a list/range/array. extra_rot : float Additional rotation angle of the images (deg). subtract_mean : bool The mean of the science and reference images is subtracted from the corresponding stack, before the PCA basis is constructed and fitted. Set the argument to ``False`` for RDI, that is, in case ``reference_in_tag`` is different from ``images_in_tag`` and there is no or limited field rotation. The parameter is only supported with ``processing_type='ADI'``. processing_type : str Post-processing type: - ADI: Angular differential imaging. Can be used both on imaging and IFS datasets. This argument is also used for RDI, in which case the ``PARANG`` attribute should contain zeros a derotation angles (e.g. with :func:`~pynpoint.core.pypeline.Pypeline.set_attribute` or :class:`~pynpoint.readwrite.attr_writing.ParangWritingModule`). The collapsed residuals are stored as 3D dataset with one image per principal component. - SDI: Spectral differential imaging. Can only be applied on IFS datasets. The collapsed residuals are stored as $D dataset with one image per wavelength and principal component. - SDI+ADI: Spectral and angular differential imaging. Can only be applied on IFS datasets. The collapsed residuals are stored as 5D datasets with one image per wavelength and each of the principal components. - ADI+SDI: Angular and spectral differential imaging. Can only be applied on IFS datasets. The collapsed residuals are stored as 5D datasets with one image per wavelength and each of the principal components. - CODI: Perform ADI and SDI simultaniously. Usually requires a lot more PC than the other techniques. The collapsed residuals are stored as 4D datasets with one image per wavelength and each of the principal components. Returns ------- NoneType None """ super().__init__(name_in) self.m_pca_numbers = pca_numbers if isinstance(pca_numbers, tuple): self.m_components = ( np.sort(np.atleast_1d(pca_numbers[0])), np.sort(np.atleast_1d(pca_numbers[1])), ) else: self.m_components = np.sort(np.atleast_1d(pca_numbers)) self.m_pca = PCA( n_components=np.amax(self.m_components), svd_solver="arpack" ) self.m_extra_rot = extra_rot self.m_subtract_mean = subtract_mean self.m_processing_type = processing_type self.m_reference_in_port = self.add_input_port(reference_in_tag) self.m_star_in_port = self.add_input_port(images_in_tag) if res_mean_tag is None: self.m_res_mean_out_port = None else: self.m_res_mean_out_port = self.add_output_port(res_mean_tag) if res_median_tag is None: self.m_res_median_out_port = None else: self.m_res_median_out_port = self.add_output_port(res_median_tag) if res_weighted_tag is None: self.m_res_weighted_out_port = None else: self.m_res_weighted_out_port = self.add_output_port(res_weighted_tag) if res_rot_mean_clip_tag is None: self.m_res_rot_mean_clip_out_port = None else: self.m_res_rot_mean_clip_out_port = self.add_output_port( res_rot_mean_clip_tag ) if res_arr_out_tag is None: self.m_res_arr_out_ports = None else: if isinstance(self.m_components, tuple): self.m_res_arr_out_ports = self.add_output_port(res_arr_out_tag) else: self.m_res_arr_out_ports = {} for pca_number in self.m_components: self.m_res_arr_out_ports[pca_number] = self.add_output_port( res_arr_out_tag + str(pca_number) ) if basis_out_tag is None: self.m_basis_out_port = None else: self.m_basis_out_port = self.add_output_port(basis_out_tag) if self.m_processing_type in ["ADI", "SDI", "CODI"]: if not isinstance(self.m_components, (range, list, np.ndarray)): raise ValueError( f"The post-processing type '{self.m_processing_type}' requires " f"a single range/list/array as argument for 'pca_numbers'." ) elif self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: if not isinstance(self.m_components, tuple): raise ValueError( f"The post-processing type '{self.m_processing_type}' requires " f"a tuple for with twice a range/list/array as argument for " f"'pca_numbers'." ) if ( res_arr_out_tag is not None and len(self.m_components[0]) + len(self.m_components[1]) != 2 ): raise ValueError( f"If the post-processing type '{self.m_processing_type}' " "is selected, residuals can only be calculated if no more than " "1 principal component for ADI and SDI is given." ) else: raise ValueError("Please select a valid post-processing type.") @typechecked def _run_multi_processing( self, star_reshape: np.ndarray, im_shape: tuple, indices: Optional[np.ndarray] ) -> None: """ Internal function to create the residuals, derotate the images, and write the output using multiprocessing. """ cpu = self._m_config_port.get_attribute("CPU") parang = -1.0 * self.m_star_in_port.get_attribute("PARANG") + self.m_extra_rot if self.m_ifs_data: if "WAVELENGTH" in self.m_star_in_port.get_all_non_static_attributes(): wavelength = self.m_star_in_port.get_attribute("WAVELENGTH") else: raise ValueError( "The wavelengths are not found. These should be stored " "as the 'WAVELENGTH' attribute." ) scales = scaling_factors(wavelength) else: scales = None if self.m_processing_type in ["ADI", "SDI", "CODI"]: pca_first = self.m_components pca_secon = [-1] # Not used elif self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: pca_first = self.m_components[0] pca_secon = self.m_components[1] if self.m_ifs_data: if self.m_processing_type in ["ADI", "SDI", "CODI"]: res_shape = ( len(pca_first), len(wavelength), im_shape[-2], im_shape[-1], ) elif self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: res_shape = ( len(pca_first), len(pca_secon), len(wavelength), im_shape[-2], im_shape[-1], ) else: res_shape = (len(self.m_components), im_shape[1], im_shape[2]) tmp_output = np.zeros(res_shape) if self.m_res_mean_out_port is not None: self.m_res_mean_out_port.set_all(tmp_output, keep_attributes=False) if self.m_res_median_out_port is not None: self.m_res_median_out_port.set_all(tmp_output, keep_attributes=False) if self.m_res_weighted_out_port is not None: self.m_res_weighted_out_port.set_all(tmp_output, keep_attributes=False) if self.m_res_rot_mean_clip_out_port is not None: self.m_res_rot_mean_clip_out_port.set_all(tmp_output, keep_attributes=False) self.m_star_in_port.close_port() self.m_reference_in_port.close_port() if self.m_res_mean_out_port is not None: self.m_res_mean_out_port.close_port() if self.m_res_median_out_port is not None: self.m_res_median_out_port.close_port() if self.m_res_weighted_out_port is not None: self.m_res_weighted_out_port.close_port() if self.m_res_rot_mean_clip_out_port is not None: self.m_res_rot_mean_clip_out_port.close_port() if self.m_basis_out_port is not None: self.m_basis_out_port.close_port() capsule = PcaMultiprocessingCapsule( self.m_res_mean_out_port, self.m_res_median_out_port, self.m_res_weighted_out_port, self.m_res_rot_mean_clip_out_port, cpu, deepcopy(self.m_components), deepcopy(self.m_pca), deepcopy(star_reshape), deepcopy(parang), deepcopy(scales), im_shape, indices, self.m_processing_type, ) capsule.run() @typechecked def _run_single_processing( self, star_reshape: np.ndarray, im_shape: tuple, indices: Optional[np.ndarray] ) -> None: """ Internal function to create the residuals, derotate the images, and write the output using a single process. """ start_time = time.time() # Get the parallactic angles parang = -1.0 * self.m_star_in_port.get_attribute("PARANG") + self.m_extra_rot if self.m_ifs_data: # Get the wavelengths if "WAVELENGTH" in self.m_star_in_port.get_all_non_static_attributes(): wavelength = self.m_star_in_port.get_attribute("WAVELENGTH") else: raise ValueError( "The wavelengths are not found. These should be stored " "as the 'WAVELENGTH' attribute." ) # Calculate the wavelength ratios scales = scaling_factors(wavelength) else: scales = None if self.m_processing_type in ["ADI", "SDI", "CODI"]: pca_first = self.m_components pca_secon = [-1] # Not used n_pca_total = len(pca_first) elif self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: pca_first = self.m_components[0] pca_secon = self.m_components[1] n_pca_total = len(pca_first) + len(pca_secon) # Setup output arrays out_array_res = np.zeros(im_shape) if self.m_ifs_data: if self.m_processing_type in ["ADI", "SDI", "CODI"]: res_shape = ( len(pca_first), len(wavelength), im_shape[-2], im_shape[-1], ) elif self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: res_shape = ( len(pca_first), len(pca_secon), len(wavelength), im_shape[-2], im_shape[-1], ) else: res_shape = (len(pca_first), im_shape[-2], im_shape[-1]) out_array_mean = np.zeros(res_shape) out_array_medi = np.zeros(res_shape) out_array_weig = np.zeros(res_shape) out_array_clip = np.zeros(res_shape) # loop over all different combination of pca_numbers and applying the reductions for i, pca_1 in enumerate(pca_first): for j, pca_2 in enumerate(pca_secon): progress(i + j, n_pca_total, "Creating residuals...", start_time) # process images residuals, res_rot = postprocessor( images=star_reshape, angles=parang, scales=scales, pca_number=(pca_1, pca_2), pca_sklearn=self.m_pca, im_shape=im_shape, indices=indices, processing_type=self.m_processing_type, ) # 1.) derotated residuals if self.m_res_arr_out_ports is not None: if not self.m_ifs_data: self.m_res_arr_out_ports[pca_1].set_all(res_rot) self.m_res_arr_out_ports[pca_1].copy_attributes( self.m_star_in_port ) self.m_res_arr_out_ports[pca_1].add_history( "PcaPsfSubtractionModule", f"max PC number = {pca_first}" ) else: out_array_res = residuals # 2.) mean residuals if self.m_res_mean_out_port is not None: if self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: out_array_mean[i, j] = combine_residuals( method="mean", res_rot=res_rot, angles=parang ) else: out_array_mean[i] = combine_residuals( method="mean", res_rot=res_rot, angles=parang ) # 3.) median residuals if self.m_res_median_out_port is not None: if self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: out_array_medi[i, j] = combine_residuals( method="median", res_rot=res_rot, angles=parang ) else: out_array_medi[i] = combine_residuals( method="median", res_rot=res_rot, angles=parang ) # 4.) noise-weighted residuals if self.m_res_weighted_out_port is not None: if self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: out_array_weig[i, j] = combine_residuals( method="weighted", res_rot=res_rot, residuals=residuals, angles=parang, ) else: out_array_weig[i] = combine_residuals( method="weighted", res_rot=res_rot, residuals=residuals, angles=parang, ) # 5.) clipped mean residuals if self.m_res_rot_mean_clip_out_port is not None: if self.m_processing_type in ["SDI+ADI", "ADI+SDI"]: out_array_clip[i, j] = combine_residuals( method="clipped", res_rot=res_rot, angles=parang ) else: out_array_clip[i] = combine_residuals( method="clipped", res_rot=res_rot, angles=parang ) # Configurate data output according to the processing type # 1.) derotated residuals if self.m_res_arr_out_ports is not None and self.m_ifs_data: if pca_secon[0] == -1: history = f"max PC number = {pca_first}" else: history = f"max PC number = {pca_first} / {pca_secon}" # squeeze out_array_res to reduce dimensionallity as the residuals of # SDI+ADI and ADI+SDI are always of the form (1, 1, ...) squeezed = np.squeeze(out_array_res) if isinstance(self.m_components, tuple): self.m_res_arr_out_ports.set_all(squeezed, data_dim=squeezed.ndim) self.m_res_arr_out_ports.copy_attributes(self.m_star_in_port) self.m_res_arr_out_ports.add_history("PcaPsfSubtractionModule", history) else: for i, pca in enumerate(self.m_components): self.m_res_arr_out_ports[pca].append(squeezed[i]) self.m_res_arr_out_ports[pca].add_history( "PcaPsfSubtractionModule", history ) # 2.) mean residuals if self.m_res_mean_out_port is not None: self.m_res_mean_out_port.set_all( out_array_mean, data_dim=out_array_mean.ndim ) # 3.) median residuals if self.m_res_median_out_port is not None: self.m_res_median_out_port.set_all( out_array_medi, data_dim=out_array_medi.ndim ) # 4.) noise-weighted residuals if self.m_res_weighted_out_port is not None: self.m_res_weighted_out_port.set_all( out_array_weig, data_dim=out_array_weig.ndim ) # 5.) clipped mean residuals if self.m_res_rot_mean_clip_out_port is not None: self.m_res_rot_mean_clip_out_port.set_all( out_array_clip, data_dim=out_array_clip.ndim ) @typechecked def run(self) -> None: """ Run method of the module. Subtracts the mean of the image stack from all images, reshapes the stack of images into a 2D array, uses singular value decomposition to construct the orthogonal basis set, calculates the PCA coefficients for each image, subtracts the PSF model, and writes the residuals as output. Returns ------- NoneType None """ print("Input parameters:") print(f" - Post-processing type: {self.m_processing_type}") print(f" - Number of principal components: {self.m_pca_numbers}") print(f" - Subtract mean: {self.m_subtract_mean}") print(f" - Extra rotation (deg): {self.m_extra_rot}") cpu = self._m_config_port.get_attribute("CPU") if cpu > 1 and self.m_res_arr_out_ports is not None: warnings.warn( "Multiprocessing not possible if 'res_arr_out_tag' is not set " "to None." ) # Read the data star_data = self.m_star_in_port.get_all() im_shape = star_data.shape # Parse input processing types to internal processing types if star_data.ndim == 3: self.m_ifs_data = False elif star_data.ndim == 4: self.m_ifs_data = True else: raise ValueError( f"The input data has {star_data.ndim} dimensions while only 3 or 4 " f" are supported by the pipeline module." ) if self.m_processing_type == "ADI" and not self.m_ifs_data: # select the first image and get the unmasked image indices im_star = star_data[0,].reshape(-1) indices = np.where(im_star != 0.0)[0] # reshape the star data and select the unmasked pixels star_reshape = star_data.reshape(im_shape[0], im_shape[1] * im_shape[2]) star_reshape = star_reshape[:, indices] if self.m_reference_in_port.tag == self.m_star_in_port.tag: ref_reshape = deepcopy(star_reshape) else: ref_data = self.m_reference_in_port.get_all() ref_shape = ref_data.shape if ref_shape[-2:] != im_shape[-2:]: raise ValueError( "The image size of the science data and the reference data " "should be identical." ) # reshape reference data and select the unmasked pixels ref_reshape = ref_data.reshape( ref_shape[0], ref_shape[1] * ref_shape[2] ) ref_reshape = ref_reshape[:, indices] # subtract mean from science data, if required if self.m_subtract_mean: mean_star = np.mean(star_reshape, axis=0) star_reshape -= mean_star # subtract mean from reference data mean_ref = np.mean(ref_reshape, axis=0) ref_reshape -= mean_ref # create the PCA basis print("Constructing PSF model...", end="") self.m_pca.fit(ref_reshape) # add mean of reference array as 1st PC and orthogonalize it with respect to # the other principal components if not self.m_subtract_mean: mean_ref_reshape = mean_ref.reshape((1, mean_ref.shape[0])) q_ortho, _ = np.linalg.qr( np.vstack((mean_ref_reshape, self.m_pca.components_[:-1,])).T ) self.m_pca.components_ = q_ortho.T print(" [DONE]") if self.m_basis_out_port is not None: pc_size = self.m_pca.components_.shape[0] basis = np.zeros((pc_size, im_shape[1] * im_shape[2])) basis[:, indices] = self.m_pca.components_ basis = basis.reshape((pc_size, im_shape[1], im_shape[2])) self.m_basis_out_port.set_all(basis) else: # This setup is used for SDI processes. No preparations are possible because SDI/ADI # combinations are case specific and need to be conducted in pca_psf_subtraction. self.m_pca = None indices = None star_reshape = star_data # Running a single processing PCA analysis if cpu == 1 or self.m_res_arr_out_ports is not None: self._run_single_processing(star_reshape, im_shape, indices) # Running multiprocessed PCA analysis else: print("Creating residuals", end="") self._run_multi_processing(star_reshape, im_shape, indices) print(" [DONE]") # write history if isinstance(self.m_components, tuple): history = ( f"max PC number = {np.amax(self.m_components[0])} / " f"{np.amax(self.m_components[1])}" ) else: history = f"max PC number = {np.amax(self.m_components)}" # save history for all other ports if self.m_res_mean_out_port is not None: self.m_res_mean_out_port.copy_attributes(self.m_star_in_port) self.m_res_mean_out_port.add_history("PcaPsfSubtractionModule", history) if self.m_res_median_out_port is not None: self.m_res_median_out_port.copy_attributes(self.m_star_in_port) self.m_res_median_out_port.add_history("PcaPsfSubtractionModule", history) if self.m_res_weighted_out_port is not None: self.m_res_weighted_out_port.copy_attributes(self.m_star_in_port) self.m_res_weighted_out_port.add_history("PcaPsfSubtractionModule", history) if self.m_res_rot_mean_clip_out_port is not None: self.m_res_rot_mean_clip_out_port.copy_attributes(self.m_star_in_port) self.m_res_rot_mean_clip_out_port.add_history( "PcaPsfSubtractionModule", history ) self.m_star_in_port.close_port() class ClassicalADIModule(ProcessingModule): """ Pipeline module for PSF subtraction with classical ADI by subtracting a median-combined reference image. A rotation threshold can be set for a fixed separation to prevent self-subtraction. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, res_out_tag: str, stack_out_tag: str, threshold: Optional[Tuple[float, float, float]], nreference: Optional[int] = None, residuals: str = "median", extra_rot: float = 0.0, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry with the science images that are read as input. res_out_tag : str Tag of the database entry with the residuals of the PSF subtraction that are written as output. stack_out_tag : str Tag of the database entry with the stacked residuals that are written as output. threshold : tuple(float, float, float), None Tuple with the separation for which the angle threshold is optimized (arcsec), FWHM of the PSF (arcsec), and the threshold (FWHM) for the selection of the reference images. No threshold is used if set to None. nreference : int, None Number of reference images, closest in line to the science image. All images are used if *threshold* is None or *nreference* is None. residuals : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). extra_rot : float Additional rotation angle of the images (deg). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_res_out_port = self.add_output_port(res_out_tag) self.m_stack_out_port = self.add_output_port(stack_out_tag) self.m_threshold = threshold self.m_nreference = nreference self.m_extra_rot = extra_rot self.m_residuals = residuals @typechecked def run(self) -> None: """ Run method of the module. Selects for each image the reference images closest in line while taking into account a rotation threshold for a fixed separation, median-combines the references images, and subtracts the reference image from each image separately. Alternatively, a single, median-combined reference image can be created and subtracted from all images. All images are used if the rotation condition can not be met. Both the individual residuals (before derotation) and the stacked residuals are stored. Returns ------- NoneType None """ parang = -1.0 * self.m_image_in_port.get_attribute("PARANG") + self.m_extra_rot nimages = self.m_image_in_port.get_shape()[0] if self.m_threshold: parang_thres = 2.0 * math.atan2( self.m_threshold[2] * self.m_threshold[1], 2.0 * self.m_threshold[0] ) parang_thres = math.degrees(parang_thres) reference = None else: parang_thres = None reference = self.m_image_in_port.get_all() reference = np.median(reference, axis=0) ang_diff = np.zeros((nimages, parang.shape[0])) for i in range(nimages): ang_diff[i, :] = np.abs(parang[i] - parang) self.apply_function_to_images( subtract_psf, self.m_image_in_port, self.m_res_out_port, "Classical ADI", func_args=( parang_thres, self.m_nreference, reference, ang_diff, self.m_image_in_port, ), ) self.m_res_in_port = self.add_input_port(self.m_res_out_port._m_tag) im_res = self.m_res_in_port.get_all() res_rot = np.zeros(im_res.shape) for i, item in enumerate(parang): res_rot[i,] = rotate(im_res[i,], item, reshape=False) stack = combine_residuals( self.m_residuals, res_rot, residuals=im_res, angles=parang ) self.m_stack_out_port.set_all(stack) if self.m_threshold: history = f"threshold [deg] = {parang_thres:.2f}" else: history = "threshold [deg] = None" self.m_res_out_port.copy_attributes(self.m_image_in_port) self.m_res_out_port.add_history("ClassicalADIModule", history) self.m_stack_out_port.copy_attributes(self.m_image_in_port) self.m_stack_out_port.add_history("ClassicalADIModule", history) self.m_res_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/resizing.py000066400000000000000000000306541512720201400236510ustar00rootroot00000000000000""" Pipeline modules for resizing of images. """ import math import time from typing import Tuple, Union import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import image_scaling from pynpoint.util.image import crop_image from pynpoint.util.module import memory_frames, progress class CropImagesModule(ProcessingModule): """ Pipeline module for cropping of images around a given position. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, size: float, center: Union[Tuple[int, int], None], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Should be different from *image_in_tag*. size : float New image size (arcsec). The same size will be used for both image dimensions. center : tuple(int, int), None Tuple (x0, y0) with the new image center. Python indexing starts at 0. The center of the input images will be used when *center* is set to *None*. Note that if the image is even-sized, it is not possible to a uniquely define a pixel position in the center of the image. The image center is determined (with pixel precision) with the :func:`~pynpoint.util.image.center_pixel` function. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_size = size self.m_center = center if self.m_center is not None: self.m_center = (self.m_center[1], self.m_center[0]) # (y, x) @typechecked def run(self) -> None: """ Run method of the module. Decreases the image size by cropping around an given position. The module always returns odd-sized images. Returns ------- NoneType None """ # Get memory and number of images to split the frames into chunks memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] # Get the numnber of dimensions and shape ndim = self.m_image_in_port.get_ndim() im_shape = self.m_image_in_port.get_shape() if ndim == 3: # Number of images nimages = im_shape[-3] # Split into batches to comply with memory constraints frames = memory_frames(memory, nimages) elif ndim == 4: # Process all wavelengths per exposure at once frames = np.linspace(0, im_shape[-3], im_shape[-3] + 1) # Convert size parameter from arcseconds to pixels pixscale = self.m_image_in_port.get_attribute("PIXSCALE") print(f"New image size (arcsec) = {self.m_size}") self.m_size = int(math.ceil(self.m_size / pixscale)) print(f"New image size (pixels) = {self.m_size}") if self.m_center is not None: print(f"New image center (x, y) = ({self.m_center[1]}, {self.m_center[0]})") # Crop images chunk by chunk start_time = time.time() for i in range(len(frames[:-1])): # Update progress bar progress(i, len(frames[:-1]), "Cropping images...", start_time) # Select images in the current chunk if ndim == 3: images = self.m_image_in_port[frames[i] : frames[i + 1],] elif ndim == 4: # Process all wavelengths per exposure at once images = self.m_image_in_port[ :, i, ] # crop images according to input parameters images = crop_image(images, self.m_center, self.m_size, copy=False) # Write processed images to output port if ndim == 3: self.m_image_out_port.append(images, data_dim=3) elif ndim == 4: self.m_image_out_port.append(images, data_dim=4) # Save history and copy attributes history = f"image size (pix) = {self.m_size}" self.m_image_out_port.add_history("CropImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() class ScaleImagesModule(ProcessingModule): """ Pipeline module for rescaling of an image. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, scaling: Union[ Tuple[float, float, float], Tuple[None, None, float], Tuple[float, float, None], ], pixscale: bool = True, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Should be different from ``image_in_tag``. scaling : tuple(float, float, float) Tuple with the scaling factors for the image size and flux, (scaling_x, scaling_y, scaling_flux). Upsampling and downsampling of the image corresponds to ``scaling_x/y`` > 1 and 0 < ``scaling_x/y`` < 1, respectively. pixscale : bool Adjust the pixel scale by the average scaling in x and y direction. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) if scaling[0] is None: self.m_scaling_x = 1.0 else: self.m_scaling_x = scaling[0] if scaling[1] is None: self.m_scaling_y = 1.0 else: self.m_scaling_y = scaling[1] if scaling[2] is None: self.m_scaling_flux = 1.0 else: self.m_scaling_flux = scaling[2] self.m_pixscale = pixscale @typechecked def run(self) -> None: """ Run method of the module. Rescales an image with a fifth order spline interpolation and a reflecting boundary condition. Returns ------- NoneType None """ pixscale = self.m_image_in_port.get_attribute("PIXSCALE") self.apply_function_to_images( image_scaling, self.m_image_in_port, self.m_image_out_port, "Scaling images", func_args=(self.m_scaling_y, self.m_scaling_x, self.m_scaling_flux), ) history = ( f"scaling = ({self.m_scaling_x:.2f}, {self.m_scaling_y:.2f}, " f"{self.m_scaling_flux:.2f})" ) self.m_image_out_port.add_history("ScaleImagesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) if self.m_pixscale: mean_scaling = (self.m_scaling_x + self.m_scaling_y) / 2.0 self.m_image_out_port.add_attribute("PIXSCALE", pixscale / mean_scaling) self.m_image_out_port.close_port() class AddLinesModule(ProcessingModule): """ Module to add lines of pixels to increase the size of an image. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, lines: Tuple[int, int, int, int], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output, including the images with increased size. Should be different from *image_in_tag*. lines : tuple(int, int, int, int) The number of lines that are added in left, right, bottom, and top direction. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_lines = np.asarray(lines) @typechecked def run(self) -> None: """ Run method of the module. Adds lines of zero-value pixels to increase the size of an image. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) shape_in = self.m_image_in_port.get_shape() shape_out = ( shape_in[-2] + int(self.m_lines[2]) + int(self.m_lines[3]), shape_in[-1] + int(self.m_lines[0]) + int(self.m_lines[1]), ) self.m_lines[1] = shape_out[1] - self.m_lines[1] # right side of image self.m_lines[3] = shape_out[0] - self.m_lines[3] # top side of image start_time = time.time() for i in range(len(frames[:-1])): progress(i, len(frames[:-1]), "Adding lines...", start_time) image_in = self.m_image_in_port[frames[i] : frames[i + 1],] image_out = np.zeros( (frames[i + 1] - frames[i], shape_out[0], shape_out[1]) ) image_out[ :, int(self.m_lines[2]) : int(self.m_lines[3]), int(self.m_lines[0]) : int(self.m_lines[1]), ] = image_in self.m_image_out_port.append(image_out, data_dim=3) history = f"number of lines = {self.m_lines}" self.m_image_out_port.add_history("AddLinesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() class RemoveLinesModule(ProcessingModule): """ Module to decrease the dimensions of an image by removing lines of pixels. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, lines: Tuple[int, int, int, int], ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output, including the images with decreased size. Should be different from *image_in_tag*. lines : tuple(int, int, int, int) The number of lines that are removed in left, right, bottom, and top direction. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_lines = lines @typechecked def run(self) -> None: """ Run method of the module. Removes the lines given by *lines* from each frame. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") nimages = self.m_image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) start_time = time.time() for i in range(len(frames[:-1])): progress(i, len(frames[:-1]), "Removing lines...", start_time) image_in = self.m_image_in_port[frames[i] : frames[i + 1],] image_out = image_in[ :, int(self.m_lines[2]) : image_in.shape[1] - int(self.m_lines[3]), int(self.m_lines[0]) : image_in.shape[2] - int(self.m_lines[1]), ] self.m_image_out_port.append(image_out, data_dim=3) history = f"number of lines = {self.m_lines}" self.m_image_out_port.add_history("RemoveLinesModule", history) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/stacksubset.py000066400000000000000000000604361512720201400243530ustar00rootroot00000000000000""" Pipeline modules for stacking and subsampling of images. """ import time import warnings from typing import List, Optional, Tuple import numpy as np from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.module import progress, memory_frames, stack_angles, angle_average from pynpoint.util.image import rotate_images class StackAndSubsetModule(ProcessingModule): """ Pipeline module for stacking subsets of images and/or selecting a random sample of images. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, random: Optional[int] = None, stacking: Optional[int] = None, combine: str = "mean", max_rotation: Optional[float] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. Should be different from *image_in_tag*. random : int, None Number of random images. All images are used if set to None. stacking : int, None Number of stacked images per subset. No stacking is applied if set to None. combine : str Method for combining images ('mean' or 'median'). The angles are always mean-combined. max_rotation : float, None Maximum allowed field rotation (deg) throughout each subset of stacked images when `stacking` is not None. No restriction on the field rotation is applied if set to None. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_random = random self.m_stacking = stacking self.m_combine = combine self.m_max_rotation = max_rotation if self.m_stacking is None and self.m_random is None: warnings.warn("Both 'stacking' and 'random' are set to None.") @typechecked def run(self) -> None: """ Run method of the module. Stacks subsets of images and/or selects a random subset. Also the parallactic angles are mean-combined if images are stacked. Returns ------- NoneType None """ @typechecked def _stack_subsets( nimages: int, im_shape: Tuple[int, ...], parang: Optional[np.ndarray] ) -> Tuple[Tuple[int, ...], np.ndarray, Optional[np.ndarray]]: im_new = None parang_new = None if self.m_stacking is not None: if self.m_max_rotation is not None: frames = stack_angles(self.m_stacking, parang, self.m_max_rotation) else: frames = memory_frames(self.m_stacking, nimages) nimages_new = np.size(frames) - 1 if parang is None: parang_new = None else: parang_new = np.zeros(nimages_new) im_new = np.zeros((nimages_new, im_shape[1], im_shape[2])) start_time = time.time() for i in range(nimages_new): progress( i, nimages_new, "Stacking subsets of images...", start_time ) if parang is not None: # parang_new[i] = np.mean(parang[frames[i]:frames[i+1]]) parang_new[i] = angle_average(parang[frames[i] : frames[i + 1]]) im_subset = self.m_image_in_port[frames[i] : frames[i + 1],] if self.m_combine == "mean": im_new[i,] = np.mean(im_subset, axis=0) elif self.m_combine == "median": im_new[i,] = np.median(im_subset, axis=0) im_shape = im_new.shape else: if parang is not None: parang_new = np.copy(parang) return im_shape, im_new, parang_new @typechecked def _random_subset( im_shape: Tuple[int, ...], im_new: np.ndarray, parang_new: Optional[np.ndarray] ) -> Tuple[int, np.ndarray, Optional[np.ndarray]]: if self.m_random is not None: choice = np.random.choice(im_shape[0], self.m_random, replace=False) choice = list(np.sort(choice)) if parang_new is None: parang_new = None else: parang_new = parang_new[choice] if self.m_stacking is None: im_new = self.m_image_in_port[list(choice),] else: im_new = im_new[choice,] if self.m_random is None and self.m_stacking is None: nimages = 0 elif im_new.ndim == 2: nimages = 1 elif im_new.ndim == 3: nimages = im_new.shape[0] return nimages, im_new, parang_new non_static = self.m_image_in_port.get_all_non_static_attributes() im_shape = self.m_image_in_port.get_shape() nimages = im_shape[0] if self.m_random is not None: if self.m_stacking is None and im_shape[0] < self.m_random: raise ValueError( "The number of images of the destination subset is larger than " "the number of images in the source." ) if ( self.m_stacking is not None and int(float(im_shape[0]) / float(self.m_stacking)) < self.m_random ): raise ValueError( "The number of images of the destination subset is larger than " "the number of images in the stacked source." ) if "PARANG" in non_static: parang = self.m_image_in_port.get_attribute("PARANG") else: parang = None im_shape, im_new, parang_new = _stack_subsets(nimages, im_shape, parang) nimages, im_new, parang_new = _random_subset(im_shape, im_new, parang_new) if self.m_random or self.m_stacking: self.m_image_out_port.set_all(im_new, keep_attributes=True) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_attribute( "INDEX", np.arange(0, nimages, 1), static=False ) if parang_new is not None: self.m_image_out_port.add_attribute("PARANG", parang_new, static=False) if "NFRAMES" in non_static: self.m_image_out_port.del_attribute("NFRAMES") history = f"stacking = {self.m_stacking}, random = {self.m_random}" self.m_image_out_port.add_history("StackAndSubsetModule", history) self.m_image_out_port.close_port() class StackCubesModule(ProcessingModule): """ Pipeline module for calculating the mean or median of each original data cube associated with a database tag. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, combine: str = "mean" ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry with the mean or median collapsed images that are written as output. Should be different from *image_in_tag*. combine : str Method to combine the images ('mean' or 'median'). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_combine = combine @typechecked def run(self) -> None: """ Run method of the module. Uses the ``NFRAMES`` attribute to select the images of each cube, calculates the mean or median of each cube, and saves the data and attributes. Returns ------- NoneType None """ if self.m_image_in_port.tag == self.m_image_out_port.tag: raise ValueError("Input and output port should have a different tag.") non_static = self.m_image_in_port.get_all_non_static_attributes() nframes = self.m_image_in_port.get_attribute("NFRAMES") if "PARANG" in non_static: parang = self.m_image_in_port.get_attribute("PARANG") else: parang = None current = 0 parang_new = [] start_time = time.time() for i, frames in enumerate(nframes): progress(i, len(nframes), "Stacking images per FITS cube...", start_time) if self.m_combine == "mean": im_stack = np.mean( self.m_image_in_port[current : current + frames,], axis=0 ) elif self.m_combine == "median": im_stack = np.median( self.m_image_in_port[current : current + frames,], axis=0 ) self.m_image_out_port.append(im_stack, data_dim=3) if parang is not None: parang_new.append(np.mean(parang[current : current + frames])) current += frames nimages = np.size(nframes) self.m_image_out_port.copy_attributes(self.m_image_in_port) if "INDEX" in non_static: index = np.arange(0, nimages, 1, dtype=int) self.m_image_out_port.add_attribute("INDEX", index, static=False) if "NFRAMES" in non_static: nframes = np.ones(nimages, dtype=int) self.m_image_out_port.add_attribute("NFRAMES", nframes, static=False) if "PARANG" in non_static: self.m_image_out_port.add_attribute("PARANG", parang_new, static=False) self.m_image_out_port.close_port() class DerotateAndStackModule(ProcessingModule): """ Pipeline module for derotating and/or stacking (i.e., taking the median or average) of the images, either along the time or the wavelengths dimension. """ @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, derotate: bool = True, stack: Optional[str] = None, extra_rot: float = 0.0, dimension: str = "time", ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tag : str Tag of the database entry that is read as input. image_out_tag : str Tag of the database entry that is written as output. The shape of the output data is equal to the data from ``image_in_tag``. If the argument of ``stack`` is not None, then the size of the collapsed dimension is equal to 1. derotate : bool Derotate the images with the ``PARANG`` attribute. stack : str Type of stacking applied after optional derotation ('mean', 'median', or None for no stacking). extra_rot : float Additional rotation angle of the images in clockwise direction (deg). dimension : str Dimension along which the images are stacked. Can either be 'time' or 'wavelength'. If the ``image_in_tag`` has three dimensions then ``dimension`` is always fixed to 'time'. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_derotate = derotate self.m_stack = stack self.m_extra_rot = extra_rot self.m_dimension = dimension @typechecked def run(self) -> None: """ Run method of the module. Uses the ``PARANG`` attributes to derotate the images (if ``derotate`` is set to ``True``) and applies an optional mean or median stacking along the time or wavelengths dimension afterwards. Returns ------- NoneType None """ @typechecked def _initialize( ndim: int, npix: int ) -> Tuple[int, np.ndarray, Optional[np.ndarray], Optional[np.ndarray]]: if ndim == 2: nimages = 1 elif ndim == 3: nimages = self.m_image_in_port.get_shape()[-3] if self.m_stack == "median": frames = np.array([0, nimages]) else: frames = memory_frames(memory, nimages) elif ndim == 4: nimages = self.m_image_in_port.get_shape()[-3] nwave = self.m_image_in_port.get_shape()[-4] if self.m_dimension == "time": frames = np.linspace(0, nwave, nwave + 1) elif self.m_dimension == "wavelength": frames = np.linspace(0, nimages, nimages + 1) else: raise ValueError( "The dimension should be set to 'time' or 'wavelength'." ) if self.m_stack == "mean": if ndim == 4: if self.m_dimension == "time": im_tot = np.zeros((nwave, npix, npix)) elif self.m_dimension == "wavelength": im_tot = np.zeros((nimages, npix, npix)) else: im_tot = np.zeros((npix, npix)) else: im_tot = None if self.m_stack is None and ndim == 4: im_none = np.zeros((nwave, nimages, npix, npix)) else: im_none = None return nimages, frames, im_tot, im_none memory = self._m_config_port.get_attribute("MEMORY") if self.m_derotate: parang = self.m_image_in_port.get_attribute("PARANG") ndim = self.m_image_in_port.get_ndim() npix = self.m_image_in_port.get_shape()[-2] nimages, frames, im_tot, im_none = _initialize(ndim, npix) start_time = time.time() for i, _ in enumerate(frames[:-1]): progress( i, len(frames[:-1]), "Derotating and/or stacking images...", start_time ) if ndim == 3: # Get the images and ensure they have the correct 3D shape with the following # three dimensions: (batch_size, height, width) images = self.m_image_in_port[frames[i] : frames[i + 1],] elif ndim == 4: # Process all time frames per exposure at once if self.m_dimension == "time": images = self.m_image_in_port[ i, :, ] elif self.m_dimension == "wavelength": images = self.m_image_in_port[ :, i, ] if self.m_derotate: if ndim == 4: if self.m_dimension == "time": angles = -1.0 * parang + self.m_extra_rot elif self.m_dimension == "wavelength": n_wavel = self.m_image_in_port.get_shape()[-4] angles = np.full(n_wavel, -1.0 * parang[i]) + self.m_extra_rot else: angles = -parang[frames[i] : frames[i + 1]] + self.m_extra_rot images = rotate_images(images, angles) if self.m_stack is None: if ndim == 2: self.m_image_out_port.set_all(images[np.newaxis, ...]) elif ndim == 3: self.m_image_out_port.append(images, data_dim=3) elif ndim == 4: if self.m_dimension == "time": im_none[i] = images elif self.m_dimension == "wavelength": im_none[:, i] = images elif self.m_stack == "mean": if ndim == 4: im_tot[i] = np.sum(images, axis=0) else: im_tot += np.sum(images, axis=0) if self.m_stack == "mean": if ndim == 4: im_stack = im_tot / float(im_tot.shape[0]) if self.m_dimension == "time": self.m_image_out_port.set_all(im_stack[:, np.newaxis, ...]) elif self.m_dimension == "wavelength": self.m_image_out_port.set_all(im_stack[np.newaxis, ...]) else: im_stack = im_tot / float(nimages) self.m_image_out_port.set_all(im_stack[np.newaxis, ...]) elif self.m_stack == "median": if ndim == 4: images = self.m_image_in_port[:] if self.m_dimension == "time": im_stack = np.median(images, axis=1) self.m_image_out_port.set_all(im_stack[:, np.newaxis, ...]) elif self.m_dimension == "wavelength": im_stack = np.median(images, axis=0) self.m_image_out_port.set_all(im_stack[np.newaxis, ...]) else: im_stack = np.median(images, axis=0) self.m_image_out_port.set_all(im_stack[np.newaxis, ...]) elif self.m_stack is None and ndim == 4: if self.m_dimension == "time": self.m_image_out_port.set_all(im_none) elif self.m_dimension == "wavelength": self.m_image_out_port.set_all(im_none) if self.m_derotate or self.m_stack is not None: self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.close_port() class CombineTagsModule(ProcessingModule): """ Pipeline module for combining tags from multiple database entries into a single tag. """ @typechecked def __init__( self, name_in: str, image_in_tags: List[str], image_out_tag: str, check_attr: bool = True, index_init: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. image_in_tags : list(str, ) Tags of the database entries that are read as input and combined. image_out_tag : str Tag of the database entry that is written as output. Should not be present in *image_in_tags*. check_attr : bool Compare non-static attributes between the tags or combine all non-static attributes into the new database tag. index_init : bool Reinitialize the ``INDEX`` attribute. The frames are indexed in the order of tags names that are provided in *image_in_tags*. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_out_port = self.add_output_port(image_out_tag) if image_out_tag in image_in_tags: raise ValueError( "The 'image_out_tag' should not be present in 'image_in_tags'." ) if len(image_in_tags) < 2: raise ValueError("The 'image_in_tags' should contain at least two tags.") self.m_image_in_tags = image_in_tags self.m_check_attr = check_attr self.m_index_init = index_init @typechecked def run(self) -> None: """ Run method of the module. Combines the frames of multiple tags into a single dataset and adds the static and non-static attributes. The values of the attributes are compared between the input tags to make sure that the input tags descent from the same data set. Returns ------- NoneType None """ memory = self._m_config_port.get_attribute("MEMORY") image_in_port = [] im_shape = [] for i, item in enumerate(self.m_image_in_tags): image_in_port.append(self.add_input_port(item)) im_shape.append(image_in_port[i].get_shape()[-2:]) if len(set(im_shape)) > 1: raise ValueError( "The size of the images should be the same for all datasets." ) count = 0 start_time = time.time() for i, item in enumerate(self.m_image_in_tags): progress(i, len(self.m_image_in_tags), "Combining datasets...", start_time) nimages = image_in_port[i].get_shape()[0] frames = memory_frames(memory, nimages) for j, _ in enumerate(frames[:-1]): im_tmp = image_in_port[i][frames[j] : frames[j + 1],] self.m_image_out_port.append(im_tmp) if self.m_index_init: index = np.arange(frames[j], frames[j + 1], 1) + count if i == 0 and j == 0: self.m_image_out_port.add_attribute( "INDEX", index, static=False ) else: for ind in index: self.m_image_out_port.append_attribute_data("INDEX", ind) static_attr = image_in_port[i].get_all_static_attributes() non_static_attr = image_in_port[i].get_all_non_static_attributes() for key in static_attr: status = self.m_image_out_port.check_static_attribute( key, static_attr[key] ) if status == 1: self.m_image_out_port.add_attribute( key, static_attr[key], static=True ) elif status == -1 and key[0:7] != "History": warnings.warn( f"The static keyword {key} is already used but with a different " f"value. It is advisable to only combine tags that descend from " f"the same data set." ) for key in non_static_attr: values = image_in_port[i].get_attribute(key) status = self.m_image_out_port.check_non_static_attribute(key, values) if key != "INDEX" or (key == "INDEX" and not self.m_index_init): if self.m_check_attr: if key in ("PARANG", "STAR_POSITION", "INDEX", "NFRAMES"): if status == 1: self.m_image_out_port.add_attribute( key, values, static=False ) else: for j in values: self.m_image_out_port.append_attribute_data(key, j) else: if status == 1: self.m_image_out_port.add_attribute( key, values, static=False ) if status == -1: warnings.warn( f"The non-static keyword {key} is already used but " f"with different values. It is advisable to only " f"combine tags that descend from the same data set." ) else: if status == 1: self.m_image_out_port.add_attribute( key, values, static=False ) else: for j in values: self.m_image_out_port.append_attribute_data(key, j) count += nimages history = f"number of input tags = {np.size(self.m_image_in_tags)}" self.m_image_out_port.add_history("CombineTagsModule", history) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/processing/timedenoising.py000066400000000000000000000173221512720201400246520ustar00rootroot00000000000000""" Continuous wavelet transform (CWT) and discrete wavelet transform (DWT) denoising for speckle suppression in the time domain. The module can be used as additional preprocessing step. See Bonse et al. (arXiv:1804.05063) more information. """ from typing import Union import pywt from typeguard import typechecked from pynpoint.core.processing import ProcessingModule from pynpoint.util.apply_func import ( cwt_denoise_line_in_time, dwt_denoise_line_in_time, normalization, ) class CwtWaveletConfiguration: """ Configuration capsule for a CWT based time denoising. Standard configuration as in the original paper. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, wavelet: str = "dog", wavelet_order: int = 2, keep_mean: bool = False, resolution: float = 0.5, ) -> None: """ Parameters ---------- wavelet : str Wavelet. wavelet_order : int Wavelet order. keep_mean : bool Keep mean. resolution : float Resolution. Returns ------- NoneType None """ if wavelet not in ["dog", "morlet"]: raise ValueError("CWT supports only 'dog' and 'morlet' wavelets.") self.m_wavelet = wavelet self.m_wavelet_order = wavelet_order self.m_keep_mean = keep_mean self.m_resolution = resolution class DwtWaveletConfiguration: """ Configuration capsule for a DWT based time denoising. A cheap alternative of the CWT based wavelet denoising. However, the supported wavelets should perform worse compared to the CWT DOG wavelet. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__(self, wavelet: str = "db8") -> None: """ Parameters ---------- wavelet : str Wavelet. Returns ------- NoneType None """ # create list of supported wavelets supported = [] for item in pywt.families(): supported += pywt.wavelist(item) # check if wavelet is supported if wavelet not in supported: raise ValueError(f"DWT supports only {supported} as input wavelet.") self.m_wavelet = wavelet class WaveletTimeDenoisingModule(ProcessingModule): """ Pipeline module for speckle subtraction in the time domain by using CWT or DWT wavelet shrinkage. See Bonse et al. (arXiv:1804.05063) for details. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, image_in_tag: str, image_out_tag: str, wavelet_configuration: Union[CwtWaveletConfiguration, DwtWaveletConfiguration], padding: str = "zero", median_filter: bool = False, threshold_function: str = "soft", ) -> None: """ Parameters ---------- name_in : str Unique name for the pipeline module. image_in_tag : str Database tag with the input data. image_out_tag : str Database tag for the output data. wavelet_configuration : pynpoint.processing.timedenoising.CwtWaveletConfiguration or \ pynpoint.processing.timedenoising.DwtWaveletConfiguration Instance of :class:`~pynpoint.processing.timedenoising.DwtWaveletConfiguration` or :class:`~pynpoint.processing.timedenoising.CwtWaveletConfiguration` which contains the parameters for the wavelet transformation. padding : str Padding method (``'zero'``, ``'mirror'``, or ``'none'``). median_filter : bool Apply a median filter in time to remove outliers, for example due to cosmic rays. threshold_function : str Threshold function that is used for wavelet shrinkage in the wavelet space (``'soft'`` or ``'hard'``). Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) self.m_wavelet_configuration = wavelet_configuration self.m_median_filter = median_filter assert padding in ["zero", "mirror", "none"] self.m_padding = padding assert threshold_function in ["soft", "hard"] self.m_threshold_function = threshold_function == "soft" assert isinstance( wavelet_configuration, (DwtWaveletConfiguration, CwtWaveletConfiguration) ) @typechecked def run(self) -> None: """ Run method of the module. Applies the time denoising for the lines in time in parallel. Returns ------- NoneType None """ if isinstance(self.m_wavelet_configuration, DwtWaveletConfiguration): if self.m_padding == "const_mean": self.m_padding = "constant" if self.m_padding == "none": self.m_padding = "periodic" self.apply_function_in_time( dwt_denoise_line_in_time, self.m_image_in_port, self.m_image_out_port, func_args=( self.m_threshold_function, self.m_padding, self.m_wavelet_configuration, ), ) elif isinstance(self.m_wavelet_configuration, CwtWaveletConfiguration): self.apply_function_in_time( cwt_denoise_line_in_time, self.m_image_in_port, self.m_image_out_port, func_args=( self.m_threshold_function, self.m_padding, self.m_median_filter, self.m_wavelet_configuration, ), ) if self.m_threshold_function: history = "threshold_function = soft" else: history = "threshold_function = hard" self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history("WaveletTimeDenoisingModule", history) self.m_image_out_port.close_port() class TimeNormalizationModule(ProcessingModule): """ Pipeline module for normalization of global brightness variations of the detector. See Bonse et al. (arXiv:1804.05063) for details. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__(self, name_in: str, image_in_tag: str, image_out_tag: str) -> None: """ Parameters ---------- name_in : str Unique name for the pipeline module. image_in_tag : str Database tag with the input data. image_out_tag : str Database tag for the output data. Returns ------- NoneType None """ super().__init__(name_in) self.m_image_in_port = self.add_input_port(image_in_tag) self.m_image_out_port = self.add_output_port(image_out_tag) @typechecked def run(self) -> None: """ Run method of the module. Returns ------- NoneType None """ self.apply_function_to_images( normalization, self.m_image_in_port, self.m_image_out_port, "Time normalization", ) self.m_image_out_port.copy_attributes(self.m_image_in_port) self.m_image_out_port.add_history( "TimeNormalizationModule", "normalization = median" ) self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/000077500000000000000000000000001512720201400212475ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/readwrite/__init__.py000066400000000000000000000000001512720201400233460ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/readwrite/attr_reading.py000066400000000000000000000246051512720201400242730ustar00rootroot00000000000000""" Modules for reading attributes from a FITS or ASCII file. """ import os import warnings from typing import Optional import numpy as np from astropy.io import fits from typeguard import typechecked from pynpoint.core.attributes import get_attributes from pynpoint.core.processing import ReadingModule class AttributeReadingModule(ReadingModule): """ Module for reading a list of values from a FITS or ASCII file and appending them as a non-static attributes to a dataset. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str, attribute: str, input_dir: Optional[str] = None, overwrite: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry to which the attribute is written. file_name : str Name of the input file with the attribute value. Should be equal in size to the number of images in ``data_tag``. In case the ``file_name`` is ending with ``.fits``, then a FITS file is read. Otherwise, a single column of values is expected in an ASCII file. file_name : str Name of the input file with a list of values. attribute : str Name of the attribute as to be written in the database. input_dir : str, None Input directory where the input file is located. If not specified the Pypeline default directory is used. overwrite : bool Overwrite if the attribute is already exists. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) self.m_data_port = self.add_output_port(data_tag) self.m_file_name = file_name self.m_attribute = attribute self.m_overwrite = overwrite @typechecked def run(self) -> None: """ Run method of the module. Reads a list of values from a FITS or ASCII file and writes them as non-static attribute to a dataset. Returns ------- NoneType None """ print("Reading attribute data...", end="") attributes = get_attributes() if self.m_attribute not in attributes: raise ValueError(f"'{self.m_attribute}' is not a valid attribute.") if self.m_file_name.endswith("fits"): values = fits.getdata(os.path.join(self.m_input_location, self.m_file_name)) else: values = np.loadtxt( os.path.join(self.m_input_location, self.m_file_name), dtype=attributes[self.m_attribute]["type"], ) if values.ndim != 1: raise ValueError( f"The input file {self.m_file_name} should contain a 1D list with " f"attributes." ) status = self.m_data_port.check_non_static_attribute(self.m_attribute, values) if status == 1: self.m_data_port.add_attribute(self.m_attribute, values, static=False) elif status == -1 and self.m_overwrite: self.m_data_port.add_attribute(self.m_attribute, values, static=False) elif status == -1 and not self.m_overwrite: warnings.warn( f"The attribute '{self.m_attribute}' is already present. Set the " f"'overwrite' parameter to True in order to overwrite the values with " f"{self.m_file_name}." ) elif status == 0: warnings.warn( f"The '{self.m_attribute}' attribute is already present and " f"contains the same values as are present in {self.m_file_name}." ) print(" [DONE]") self.m_data_port.close_port() class ParangReadingModule(ReadingModule): """ Module for reading a list of parallactic angles from a FITS or ASCII file. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str, input_dir: Optional[str] = None, overwrite: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry to which the ``PARANG`` attribute is written. file_name : str Name of the input file with the parallactic angles (deg). Should be equal in size to the number of images in ``data_tag``. In case the ``file_name`` is ending with ``.fits``, then a FITS file is read. Otherwise, a single column of values is expected in an ASCII file. input_dir : str, None Input directory where the input file is located. If not specified the Pypeline default directory is used. overwrite : bool Overwrite if the ``PARANG`` attribute already exists. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) self.m_data_port = self.add_output_port(data_tag) self.m_file_name = file_name self.m_overwrite = overwrite @typechecked def run(self) -> None: """ Run method of the module. Reads the parallactic angles from a FITS or ASCII file and writes the values as non-static attribute (``PARANG``) to the database tag. Returns ------- NoneType None """ print("Reading parallactic angles...", end="") if self.m_file_name.endswith("fits"): parang = fits.getdata(os.path.join(self.m_input_location, self.m_file_name)) else: parang = np.loadtxt(os.path.join(self.m_input_location, self.m_file_name)) print(" [DONE]") if parang.ndim != 1: raise ValueError( f"The input file {self.m_file_name} should contain a 1D data set with " f"the parallactic angles." ) print(f"Number of angles: {parang.size}") print(f"Rotation range: {parang[0]:.2f} -> {parang[-1]:.2f} deg") status = self.m_data_port.check_non_static_attribute("PARANG", parang) if status == 1: self.m_data_port.add_attribute("PARANG", parang, static=False) elif status == -1 and self.m_overwrite: self.m_data_port.add_attribute("PARANG", parang, static=False) elif status == -1 and not self.m_overwrite: warnings.warn( f"The PARANG attribute is already present. Set the 'overwrite' " f"parameter to True in order to overwrite the values with " f"{self.m_file_name}." ) elif status == 0: warnings.warn( f"The PARANG attribute is already present and contains the same values " f"as are present in {self.m_file_name}." ) self.m_data_port.close_port() class WavelengthReadingModule(ReadingModule): """ Module for reading a list of wavelengths from a FITS or ASCII file. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str, input_dir: Optional[str] = None, overwrite: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry to which the ``WAVELENGTH`` attribute is written. file_name : str Name of the input file with the wavelengths (a.u.). Should be equal in size to the number of images in ``data_tag``. In case the ``file_name`` is ending with ``.fits``, then a FITS file is read. Otherwise, a single column of values is expected in an ASCII file. input_dir : str, None Input directory where the input file is located. If not specified the Pypeline default directory is used. overwrite : bool Overwrite if the ``WAVELENGTH`` attribute already exists. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) self.m_data_port = self.add_output_port(data_tag) self.m_file_name = file_name self.m_overwrite = overwrite @typechecked def run(self) -> None: """ Run method of the module. Reads the parallactic angles from a FITS or ASCII file and writes the values as non-static attribute (``WAVELENGTH``) to the database tag. Returns ------- NoneType None """ print("Reading wavelengths...", end="") if self.m_file_name.endswith("fits"): wavelength = fits.getdata( os.path.join(self.m_input_location, self.m_file_name) ) else: wavelength = np.loadtxt( os.path.join(self.m_input_location, self.m_file_name) ) print(" [DONE]") if wavelength.ndim != 1: raise ValueError( f"The input file {self.m_file_name} should contain a 1D data set with " f"the wavelengths." ) print(f"Number of wavelengths: {wavelength.size}") print(f"Wavelength range: {wavelength[0]:.2f} - {wavelength[-1]:.2f}") status = self.m_data_port.check_non_static_attribute("WAVELENGTH", wavelength) if status == 1: self.m_data_port.add_attribute("WAVELENGTH", wavelength, static=False) elif status == -1 and self.m_overwrite: self.m_data_port.add_attribute("WAVELENGTH", wavelength, static=False) elif status == -1 and not self.m_overwrite: warnings.warn( f"The WAVELENGTH attribute is already present. Set the 'overwrite' " f"parameter to True in order to overwrite the values with " f"{self.m_file_name}." ) elif status == 0: warnings.warn( f"The WAVELENGTH attribute is already present and contains the same " f"values as are present in {self.m_file_name}." ) self.m_data_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/attr_writing.py000066400000000000000000000110211512720201400243310ustar00rootroot00000000000000""" Modules for writing data as text file. """ import os from typing import Optional import numpy as np from typeguard import typechecked from pynpoint.core.processing import WritingModule class AttributeWritingModule(WritingModule): """ Module for writing a 1D or 2D array of non-static attributes to a text file. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, attribute: str, file_name: str = "attributes.dat", output_dir: Optional[str] = None, header: Optional[str] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry from which the ``PARANG`` attribute is read. attribute : str Name of the non-static attribute as given in the central database (e.g., 'INDEX' or 'STAR_POSITION'). file_name : str Name of the output file. output_dir : str, None Output directory where the text file will be stored. If no path is specified then the Pypeline default output location is used. header : str, None Header that is written at the top of the text file. Returns ------- NoneType None """ super().__init__(name_in, output_dir=output_dir) self.m_data_port = self.add_input_port(data_tag) self.m_file_name = file_name self.m_attribute = attribute self.m_header = header @typechecked def run(self) -> None: """ Run method of the module. Writes the non-static attributes (1D or 2D) to a a text file. Returns ------- NoneType None """ if self.m_header is None: self.m_header = "" print("Writing attribute data...", end="") out_name = os.path.join(self.m_output_location, self.m_file_name) if self.m_attribute not in self.m_data_port.get_all_non_static_attributes(): raise ValueError( f"The '{self.m_attribute}' attribute is not present in " f"'{self.m_data_port.tag}'." ) values = self.m_data_port.get_attribute(self.m_attribute) np.savetxt(out_name, values, header=self.m_header, comments="# ") print(" [DONE]") self.m_data_port.close_port() class ParangWritingModule(WritingModule): """ Module for writing a list of parallactic angles to a text file. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str = "parang.dat", output_dir: Optional[str] = None, header: Optional[str] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry from which the ``PARANG`` attribute is read. file_name : str Name of the output file. output_dir : str, None Output directory where the text file will be stored. If no path is specified then the Pypeline default output location is used. header : str, None Header that is written at the top of the text file. Returns ------- NoneType None """ super().__init__(name_in, output_dir=output_dir) self.m_data_port = self.add_input_port(data_tag) self.m_file_name = file_name self.m_header = header @typechecked def run(self) -> None: """ Run method of the module. Writes the parallactic angles from the ``PARANG`` attribute of the specified database tag to a a text file. Returns ------- NoneType None """ print("Writing parallactic angles...", end="") if self.m_header is None: self.m_header = "" out_name = os.path.join(self.m_output_location, self.m_file_name) if "PARANG" not in self.m_data_port.get_all_non_static_attributes(): raise ValueError( f"The PARANG attribute is not present in '{self.m_data_port.tag}'." ) parang = self.m_data_port.get_attribute("PARANG") np.savetxt(out_name, parang, header=self.m_header, comments="# ") print(" [DONE]") self.m_data_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/fitsreading.py000066400000000000000000000243571512720201400241330ustar00rootroot00000000000000""" Module for reading FITS files. """ import os import time import warnings from typing import List, Optional, Tuple, Union import numpy as np from astropy.io import fits from typeguard import typechecked from pynpoint.core.processing import ReadingModule from pynpoint.util.attributes import set_static_attr, set_nonstatic_attr, set_extra_attr from pynpoint.util.module import progress class FitsReadingModule(ReadingModule): """ Reads FITS files from the given *input_dir* or the default directory of the Pypeline. The FITS files need to contain either single images (2D) or cubes of images (3D). Individual images should have the same shape and type. The header of the FITS is scanned for the required static attributes (should be identical for each FITS file) and non-static attributes. Static entries will be saved as HDF5 attributes while non-static attributes will be saved as separate data sets in a subfolder of the database named *header_* + image_tag. If the FITS files in the input directory have changing static attributes or the shape of the input images is changing a warning appears. FitsReadingModule overwrites by default all existing data with the same tags in the central database. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, input_dir: Optional[str] = None, image_tag: str = "im_arr", overwrite: bool = True, check: bool = True, filenames: Optional[Union[str, List[str]]] = None, ifs_data: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. input_dir : str, None Input directory where the FITS files are located. If not specified the Pypeline default directory is used. image_tag : str Tag of the read data in the HDF5 database. Non static header information is stored with the tag: *header_* + image_tag / header_entry_name. overwrite : bool Overwrite existing data and header in the central database. check : bool Print a warning if certain attributes from the configuration file are not present in the FITS header. If set to `False`, attributes are still written to the dataset but there will be no warning if a keyword is not found in the FITS header. filenames : str, list(str, ), None If a string, then a path of a text file should be provided. This text file should contain a list of FITS files. If a list, then the paths of the FITS files should be provided directly. If set to None, the FITS files in the `input_dir` are read. All paths should be provided either relative to the Python working folder (i.e., the folder where Python is executed) or as absolute paths. ifs_data : bool Import IFS data which is stored as a 4D array with the wavelength and temporal dimensions as first and second dimension, respectively. If set to ``False`` (default), the data is imported as a 3D array with the temporal dimension as first dimension. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) self.m_image_out_port = self.add_output_port(image_tag) self.m_overwrite = overwrite self.m_check = check self.m_filenames = filenames self.m_ifs_data = ifs_data @typechecked def read_single_file( self, fits_file: str, overwrite_tags: list ) -> Tuple[fits.header.Header, tuple]: """ Function which reads a single FITS file and appends it to the database. The function gets a list of *overwriting_tags*. If a new key (header entry or image data) is found that is not on this list the old entry is overwritten if *self.m_overwrite* is active. After replacing the old entry the key is added to the *overwriting_tags*. This procedure guaranties that all previous database information, that does not belong to the new data set that is read by FitsReadingModule is replaced and the rest is kept. Parameters ---------- fits_file : str Absolute path and filename of the FITS file. overwrite_tags : list(str, ) The list of database tags that will not be overwritten. Returns ------- astropy.io.fits.header.Header FITS header. tuple(int, ) Image shape. """ hdu_list = fits.open(fits_file) if hdu_list[0].data is not None: images = hdu_list[0].data.byteswap() images = images.view(images.dtype.newbyteorder("=")) elif len(hdu_list) > 1: for i, item in enumerate(hdu_list[1:]): if isinstance(item, fits.hdu.image.ImageHDU): warnings.simplefilter("always", UserWarning) warnings.warn( f"No data was found in the PrimaryHDU " f"so reading data from the ImageHDU " f"at number {i+1} instead." ) images = hdu_list[i + 1].data.byteswap() images = images.view(images.dtype.newbyteorder("=")) break else: raise RuntimeError(f"No data was found in {fits_file}.") images = np.nan_to_num(images) if images.ndim == 4 and not self.m_ifs_data: raise ValueError( "The input data is 4D but ifs_data is set to False. Reading in 4D " "data is only possible by setting the argument to True." ) if images.ndim < 3 and self.m_ifs_data: raise ValueError( "It is only possible to read 3D or 4D data when ifs_data is set to " "True." ) if self.m_overwrite and self.m_image_out_port.tag not in overwrite_tags: overwrite_tags.append(self.m_image_out_port.tag) if self.m_ifs_data: self.m_image_out_port.set_all(images, data_dim=4) else: self.m_image_out_port.set_all(images, data_dim=3) self.m_image_out_port.del_all_attributes() else: if self.m_ifs_data: self.m_image_out_port.append(images, data_dim=4) else: self.m_image_out_port.append(images, data_dim=3) header = hdu_list[0].header fits_header = [] for key in header: fits_header.append(f"{key} = {header[key]}") hdu_list.close() header_out_port = self.add_output_port( "fits_header/" + os.path.basename(fits_file) ) header_out_port.set_all(fits_header) return header, images.shape @typechecked def _txt_file_list(self) -> list: """ Internal function to import a list of FITS files from a text file. """ with open(self.m_filenames) as file_obj: files = file_obj.readlines() # remove newlines files = [x.strip() for x in files] # remove of empty lines files = filter(None, files) return list(files) @typechecked def run(self) -> None: """ Run method of the module. Looks for all FITS files in the input directory and imports the images into the database. Note that previous database information is overwritten if ``overwrite=True``. The filenames are stored as attributes. Returns ------- NoneType None """ files = [] if isinstance(self.m_filenames, str): files = self._txt_file_list() for item in files: if not os.path.isfile(item): raise ValueError( f"The file {item} does not exist. Please check that the " f"path is correct." ) elif isinstance(self.m_filenames, list): files = self.m_filenames for item in files: if not os.path.isfile(item): raise ValueError( f"The file {item} does not exist. Please check that the " f"path is correct." ) elif isinstance(self.m_filenames, type(None)): for filename in os.listdir(self.m_input_location): if filename.endswith(".fits") and not filename.startswith("._"): files.append(os.path.join(self.m_input_location, filename)) assert files, "No FITS files found in %s." % self.m_input_location files.sort() overwrite_tags = [] first_index = 0 start_time = time.time() for i, fits_file in enumerate(files): progress(i, len(files), "Reading FITS files...", start_time) header, shape = self.read_single_file(fits_file, overwrite_tags) if len(shape) == 2: nimages = 1 elif len(shape) == 3: if self.m_ifs_data: nimages = 1 else: nimages = shape[0] elif len(shape) == 4: nimages = shape[1] else: raise ValueError("Data read from FITS file has an invalid shape.") set_static_attr( fits_file=fits_file, header=header, config_port=self._m_config_port, image_out_port=self.m_image_out_port, check=self.m_check, ) set_nonstatic_attr( header=header, config_port=self._m_config_port, image_out_port=self.m_image_out_port, check=self.m_check, ) set_extra_attr( fits_file=fits_file, nimages=nimages, config_port=self._m_config_port, image_out_port=self.m_image_out_port, first_index=first_index, ) first_index += nimages self.m_image_out_port.flush() self.m_image_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/fitswriting.py000066400000000000000000000131221512720201400241710ustar00rootroot00000000000000""" Module for exporting a dataset from the HDF5 database to a FITS file. """ import os import warnings from typing import Optional, Tuple from astropy.io import fits from typeguard import typechecked import numpy as np from pynpoint.core.processing import WritingModule from pynpoint.util.module import memory_frames class FitsWritingModule(WritingModule): """ Module for writing a dataset from the central HDF5 database to a FITS file. The static attributes will be stored as header information. The dataset is selected from the database by its tag name. :class:`~pynpoint.readwrite.fitswriting.FitsWritingModule` is a :class:`~pynpoint.core.processing.WritingModule` and uses either the default output directory of a :class:`~pynpoint.core.pypeline.Pypeline` or a specified location to store the FITS data. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str, output_dir: Optional[str] = None, data_range: Optional[Tuple[int, int]] = None, overwrite: bool = True, subset_size: Optional[int] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry that has to be exported to a FITS file. file_name : str Name of the FITS output file. Requires the FITS extension. output_dir : str, None Output directory where the FITS file will be stored. If no folder is specified the Pypeline default is chosen. data_range : tuple, None A two element tuple which specifies a begin and end frame of the export. This can be used to save a subsets of a large dataset. The whole dataset will be exported if set to None. overwrite : bool Overwrite an existing FITS file with an identical filename. subset_size : int, None Size of the subsets that are created when storing the data. This can be useful if the dataset contains a large number of images. An increasing index value is appended to the FITS file names. All images are written to a single FITS file if set to None. Returns ------- NoneType None """ super().__init__(name_in, output_dir=output_dir) if not file_name.endswith(".fits"): raise ValueError("Output 'file_name' requires the FITS extension.") self.m_file_name = file_name self.m_data_port = self.add_input_port(data_tag) self.m_range = data_range self.m_overwrite = overwrite self.m_subset_size = subset_size @typechecked def run(self) -> None: """ Run method of the module. Creates a FITS file and stores the data and the corresponding static attributes. Returns ------- NoneType None """ out_name = os.path.join(self.m_output_location, self.m_file_name) print("Writing FITS file...", end="") if os.path.isfile(out_name) and not self.m_overwrite: warnings.warn( "Filename already present. Use overwrite=True to overwrite an existing " "FITS file." ) else: header = fits.Header() attributes = self.m_data_port.get_all_static_attributes() for attr in attributes: if len(attr) > 8: # Check if the header keyword together with its value is # too long for the FITS format. If that is the case, raise # a warning and truncate the value to avoid a ValueError. key = "hierarch " + attr value = str(attributes[attr]) max_val_len = 75 - len(key) if len(key + value) > 75: warnings.warn( f"Key '{key}' with value '{value}' is too long for " f"the FITS format. To avoid an error, the value was " f"truncated to '{value[:max_val_len]}'." ) header[key] = value[:max_val_len] else: header[attr] = attributes[attr] if self.m_subset_size is None: if self.m_range is None: frames = [0, self.m_data_port.get_shape()[0]] else: frames = [self.m_range[0], self.m_range[1]] else: if self.m_range is None: nimages = self.m_data_port.get_shape()[0] frames = memory_frames(self.m_subset_size, nimages) else: nimages = self.m_range[1] - self.m_range[0] frames = memory_frames(self.m_subset_size, nimages) frames = np.asarray(frames) + self.m_range[0] for i, _ in enumerate(frames[:-1]): data_select = self.m_data_port[frames[i] : frames[i + 1],] if len(frames) == 2: fits.writeto( out_name, data_select, header, overwrite=self.m_overwrite ) else: filename = f"{out_name[:-5]}{i:03d}.fits" fits.writeto( filename, data_select, header, overwrite=self.m_overwrite ) print(" [DONE]") self.m_data_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/hdf5reading.py000066400000000000000000000117741512720201400240130ustar00rootroot00000000000000""" Module for reading HDF5 files that were created with the :class:`~pynpoint.readwrite.hdf5writing.Hdf5WritingModule`. """ import os import time import warnings from typing import Optional import h5py import numpy as np from typeguard import typechecked from pynpoint.core.processing import ReadingModule from pynpoint.util.module import progress class Hdf5ReadingModule(ReadingModule): """ Reads an HDF5 file from the given *input_dir* or the default directory of the Pypeline. A tag dictionary has to be set in order to choose the datasets which will be imported into the database. Also the static and non-static attributes are read from the HDF5 file and stored in the database with the corresponding data set. This module should only be used for reading HDF5 files that are created with the Hdf5WritingModule. Reading different type of HDF5 files may lead to inconsistencies in the central database. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, input_filename: Optional[str] = None, input_dir: Optional[str] = None, tag_dictionary: Optional[dict] = None, ): """ Parameters ---------- name_in : str Unique name of the module instance. input_filename : str, None The file name of the HDF5 input file. All files inside the input location will be imported if no filename is provided. input_dir : str, None The directory of the input HDF5 file. If no location is given, the default input location of the Pypeline is used. tag_dictionary : dict, None Dictionary of all data sets that will be imported. The dictionary format is {*tag_name_in_input_file*:*tag_name_in_database*, }. All data sets in the input HDF5 file that match one of the *tag_name_in_input_file* will be imported. The tag name inside the internal Pypeline database will be changed to *tag_name_in_database*. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) if tag_dictionary is None: tag_dictionary = {} for out_tag in tag_dictionary.values(): self.add_output_port(out_tag) self.m_filename = input_filename self._m_tag_dictionary = tag_dictionary @typechecked def read_single_hdf5(self, file_in: str) -> None: """ Function which reads a single HDF5 file. Parameters ---------- file_in : str Path and name of the HDF5 file. Returns ------- NoneType None """ hdf5_file = h5py.File(file_in, mode="r") for tag_in in self._m_tag_dictionary: tag_in = str(tag_in) # unicode keys cause errors tag_out = self._m_tag_dictionary[tag_in] if tag_in not in hdf5_file: warnings.warn( f"The dataset with tag name '{tag_in}' is not found in the HDF5 " f"file." ) continue # add data port_out = self._m_output_ports[tag_out] port_out.set_all(np.asarray(hdf5_file[tag_in][...])) # add static attributes for attr_name, attr_value in hdf5_file[tag_in].attrs.items(): port_out.add_attribute(name=attr_name, value=attr_value) # add non-static attributes if "header_" + tag_in in hdf5_file: for attr_name in hdf5_file["header_" + tag_in]: attr_val = hdf5_file["header_" + tag_in + "/" + attr_name][...] port_out.add_attribute(name=attr_name, value=attr_val, static=False) @typechecked def run(self) -> None: """ Run method of the module. Looks for all HDF5 files in the input directory and reads the datasets that are provided in the tag dictionary. Returns ------- NoneType None """ # create list of files to be read files = [] tmp_dir = os.path.join(self.m_input_location, "") # check if a single input file is given if self.m_filename is not None: # create file path + filename assert os.path.isfile( (tmp_dir + str(self.m_filename)) ), f"Error: Input file does not exist. Input requested: {self.m_filename}" files.append((tmp_dir + str(self.m_filename))) else: # look for all HDF5 files in the directory for tmp_file in os.listdir(self.m_input_location): if tmp_file.endswith(".hdf5") or tmp_file.endswith(".h5"): files.append(tmp_dir + str(tmp_file)) start_time = time.time() for i, tmp_file in enumerate(files): progress(i, len(files), "Reading HDF5 file...", start_time) self.read_single_hdf5(tmp_file) PynPoint-PynPoint-e5d0637/pynpoint/readwrite/hdf5writing.py000066400000000000000000000072471512720201400240650ustar00rootroot00000000000000""" Module for writing a list of tags from the database to a separate HDF5 file. """ import os from typing import Optional import h5py from typeguard import typechecked from pynpoint.core.processing import WritingModule class Hdf5WritingModule(WritingModule): """ Module which exports a part of the PynPoint internal database to a separate HDF5 file. The datasets of the database can be chosen using the *tag_dictionary*. The module will also export the static and non-static attributes. """ __author__ = "Markus Bonse, Tomas Stolker" @typechecked def __init__( self, name_in: str, file_name: str, output_dir: Optional[str] = None, tag_dictionary: Optional[dict] = None, keep_attributes: bool = True, overwrite: bool = False, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. file_name : str Name of the file which will be created by the module. output_dir : str, None Location where the HDF5 file will be stored. The Pypeline default output location is used when no location is given. tag_dictionary : dict, None Directory containing all tags / keys of the datasets which will be exported from the PynPoint internal database. The datasets will be exported as {*input_tag*:*output_tag*, }. keep_attributes : bool If True all static and non-static attributes will be exported. overwrite : bool Overwrite an existing HDF5 file. Returns ------- NoneType None """ super().__init__(name_in, output_dir=output_dir) if tag_dictionary is None: tag_dictionary = {} self.m_file_name = file_name self.m_tag_dictionary = tag_dictionary self.m_keep_attributes = keep_attributes self.m_overwrite = overwrite @typechecked def run(self) -> None: """ Run method of the module. Exports all datasets defined in the *tag_dictionary* to an external HDF5 file. Returns ------- NoneType None """ print("Writing HDF5 file...", end="") if self.m_overwrite: out_file = h5py.File( os.path.join(self.m_output_location, self.m_file_name), mode="w" ) else: out_file = h5py.File( os.path.join(self.m_output_location, self.m_file_name), mode="a" ) for in_tag, out_tag in self.m_tag_dictionary.items(): tmp_port = self.add_input_port(in_tag) tmp_data = tmp_port.get_all() if tmp_data is None: continue data_set = out_file.create_dataset(out_tag, data=tmp_data) if self.m_keep_attributes: # static attributes tmp_attr = tmp_port.get_all_static_attributes() # it is not possible to copy attributes all together for key, value in tmp_attr.items(): data_set.attrs[key] = value # non-static attributes non_static_attr_keys = tmp_port.get_all_non_static_attributes() if non_static_attr_keys is not None: for key in non_static_attr_keys: tmp_data_attr = tmp_port.get_attribute(key) attr_tag = "header_" + out_tag + "/" + key out_file.create_dataset(attr_tag, data=tmp_data_attr) tmp_port.close_port() out_file.close() print(" [DONE]") PynPoint-PynPoint-e5d0637/pynpoint/readwrite/nearreading.py000066400000000000000000000407621512720201400241110ustar00rootroot00000000000000""" Module for reading FITS files obtained with VLT/VISIR for the NEAR experiment. """ import os import math import time import shlex import subprocess import threading import warnings from typing import Optional, Union, Tuple import numpy as np from astropy.io import fits from typeguard import typechecked from pynpoint.core.processing import ReadingModule from pynpoint.util.attributes import set_static_attr, set_nonstatic_attr, set_extra_attr from pynpoint.util.module import progress, memory_frames from pynpoint.util.image import crop_image class NearReadingModule(ReadingModule): """ Pipeline module for reading VLT/VISIR data of the NEAR experiment. The FITS files and required header information are read from the input directory and stored in two datasets, corresponding to chop A and chop B. The primary HDU of the FITS files should contain the main header information, while the subsequent HDUs contain each a single image (alternated for chop A and chop B) and some additional header information for that image. The last HDU is ignored as it contains the average of all images. """ __author__ = "Jasper Jonker, Tomas Stolker, Anna Boehle" @typechecked def __init__( self, name_in: str, input_dir: Optional[str] = None, chopa_out_tag: str = "chopa", chopb_out_tag: str = "chopb", subtract: bool = False, crop: Optional[Union[Tuple[int, int, float], Tuple[None, None, float]]] = None, combine: Optional[str] = None, ): """ Parameters ---------- name_in : str Unique name of the instance. input_dir : str, None Input directory where the FITS files are located. The default input folder of the Pypeline is used if set to None. chopa_out_tag : str Database entry where the chop A images will be stored. Should be different from ``chop_b_out_tag``. chopb_out_tag : str Database entry where the chop B images will be stored. Should be different from ``chop_a_out_tag``. subtract : bool If True, the other chop position is subtracted before saving out the chop A and chop B images. crop: tuple(int, int, float), None The pixel position (x, y) around which the chop A and chop B images are cropped and the new image size (arcsec), together provided as (pos_x, pos_y, size). The same size will be used for both image dimensions. It is recommended to crop the images around the approximate coronagraph position. No cropping is applied if set to None. combine: str, None Method ('mean' or 'median') for combining (separately) the chop A and chop B frames from each cube into a single frame. All frames are stored if set to None. Returns ------- NoneType None """ super().__init__(name_in, input_dir=input_dir) self.m_chopa_out_port = self.add_output_port(chopa_out_tag) self.m_chopb_out_port = self.add_output_port(chopb_out_tag) self.m_subtract = subtract self.m_crop = crop self.m_combine = combine @staticmethod @typechecked def _uncompress_file(filename: str) -> None: """ Internal function to uncompress a .Z file. Parameters ---------- filename : str Compressed .Z file. Returns ------- NoneType None """ try: # try running a subprocess with the 'uncompress' command command = "uncompress " + filename subprocess.check_call(shlex.split(command)) except (FileNotFoundError, OSError): # or else run a subprocess with the 'gunzip' command command = "gunzip -d " + filename subprocess.check_call(shlex.split(command)) @typechecked def uncompress(self) -> None: """ Method to check if the input directory contains compressed files ending with .fits.Z. If this is the case, the files will be uncompressed using multithreading. The number of threads can be set with the ``CPU`` parameter in the configuration file. Returns ------- NoneType None """ cpu = self._m_config_port.get_attribute("CPU") # list all files ending with .fits.Z in the input location files = [] for item in os.listdir(self.m_input_location): if item.endswith(".fits.Z"): files.append(os.path.join(self.m_input_location, item)) if files: # subdivide the file indices by number of CPU indices = memory_frames(cpu, len(files)) start_time = time.time() for i, _ in enumerate(indices[:-1]): progress(i, len(indices[:-1]), "Uncompressing NEAR data...", start_time) # select subset of compressed files subset = files[indices[i] : indices[i + 1]] # create a list of threads to uncompress CPU number of files # each file is processed by a different thread threads = [] for filename in subset: thread = threading.Thread( target=self._uncompress_file, args=(filename,) ) threads.append(thread) # start the threads for item in threads: item.start() # join the threads for item in threads: item.join() @staticmethod @typechecked def check_header(header: fits.header.Header) -> None: """ Method to check the header information and prompt a warning if a value is not as expected. Parameters ---------- header : astropy.io.fits.header.Header Header information from the FITS file that is read. Returns ------- NoneType None """ if str(header["ESO DET CHOP ST"]) == "F": warnings.warn("Dataset was obtained without chopping.") skipped = int(header["ESO DET CHOP CYCSKIP"]) if skipped != 0: warnings.warn(f"Chop cycles ({skipped}) have been skipped.") if str(header["ESO DET CHOP CYCSUM"]) == "T": warnings.warn("FITS file contains averaged images.") @typechecked def read_header( self, filename: str ) -> Tuple[fits.header.Header, Tuple[int, int, int]]: """ Function that opens a FITS file and separates the chop A and chop B images. The primary HDU contains only a general header. The subsequent HDUs contain a single image with a small extra header. The last HDU is the average of all images, which will be ignored. Parameters ---------- filename : str Absolute path and filename of the FITS file. Returns ------- astropy.io.fits.header.Header Primary header, which is valid for all images. tuple(int, int, int) Shape of a stack of images for chop A or B. """ # open the FITS file hdulist = fits.open(filename) # number of images = total number of HDUs - primary HDU - last HDU (average image) nimages = len(hdulist) - 2 # check if the file contains an even number of images, as expected with two chop positions if nimages % 2 != 0: warnings.warn(f"FITS file contains odd number of images: {filename}") # decreasing nimages to an even number such that nimages // 2 gives the correct size nimages -= 1 # primary header header = hdulist[0].header # number of chop cycles ncycles = header["ESO DET CHOP NCYCLES"] # number of chop cycles should be equal to half the number of available images if ncycles != nimages // 2: warnings.warn( f"The number of chop cycles ({ncycles}) is not equal to half the " f"number of available HDU images ({nimages // 2})." ) # header of the first image header_image = hdulist[1].header # create a list of key = value from the primary header fits_header = [] for key in header: if key: fits_header.append(str(key) + " = " + str(header[key])) # write the primary header information to the fits_header group header_out_port = self.add_output_port("fits_header/" + filename) header_out_port.set_all(np.array(fits_header)) # shape of the image stacks for chop A/B (hence nimages/2) im_shape = (nimages // 2, header_image["NAXIS2"], header_image["NAXIS1"]) # set the NAXIS image shape in the primary header # required by util.attributes.set_nonstatic_attr header.set("NAXIS", 3) header.set("NAXIS1", im_shape[2]) header.set("NAXIS2", im_shape[1]) header.set("NAXIS3", im_shape[0]) # check primary header self.check_header(header) hdulist.close() return header, im_shape @staticmethod @typechecked def read_images( filename: str, im_shape: Tuple[int, int, int] ) -> Tuple[np.ndarray, np.ndarray]: """ Function that opens a FITS file and separates the chop A and chop B images. The primary HDU contains only a general header. The subsequent HDUs contain a single image with a small extra header. The last HDU is the average of all images, which will be ignored. Parameters ---------- filename : str Absolute path and filename of the FITS file. im_shape : tuple(int, int, int) Shape of a stack of images for chop A or B. Returns ------- numpy.array Array containing the images of chop A. numpy.array Array containing the images of chop B. """ # open the FITS file hdulist = fits.open(filename) # initialize the image arrays for chop A and B chopa = np.zeros(im_shape, dtype=np.float32) chopb = np.zeros(im_shape, dtype=np.float32) count_chopa, count_chopb = 0, 0 prev_cycle = None for i in range(2 * im_shape[0]): # get the chop position (HCYCLE1 = chop A, HCYCLE2 = chop B) # primary HDU is skipped with +1 if "ESO DET FRAM TYPE" in hdulist[i + 1].header: cycle = hdulist[i + 1].header["ESO DET FRAM TYPE"] else: hdulist.close() raise ValueError( f"Frame type not found in the FITS header. Image number: {i}." ) # write the HDU image to the chop A or B array # count the number of chop A and B images if cycle == "HCYCLE1" and cycle != prev_cycle: data_tmp = hdulist[i + 1].data.byteswap() chopa[count_chopa,] = data_tmp.view(data_tmp.dtype.newbyteorder("=")) count_chopa += 1 prev_cycle = cycle elif cycle == "HCYCLE2" and cycle != prev_cycle: data_tmp = hdulist[i + 1].data.byteswap() chopb[count_chopb,] = data_tmp.view(data_tmp.dtype.newbyteorder("=")) count_chopb += 1 prev_cycle = cycle elif cycle == prev_cycle: warnings.warn( f"Previous and current chop position ({cycle}) are the same. " "Skipping the current image." ) else: hdulist.close() raise ValueError( f"Frame type ({cycle}) not a valid value. Expecting HCYCLE1 or " "HCYCLE2 as value for ESO DET FRAM TYPE." ) # check if the number of chop A and B images is equal, this error should never occur if count_chopa != count_chopb: warnings.warn("The number of images is not equal for chop A and chop B.") hdulist.close() return chopa, chopb @typechecked def run(self) -> None: """ Run the module. The FITS files are collected from the input directory and uncompressed if needed. The images are then sorted by the two chop positions (chop A and chop B). The required FITS header keywords (which should be set in the configuration file) are also imported and stored as attributes to the two output datasets in the HDF5 database. Returns ------- NoneType None """ # clear the output ports self.m_chopa_out_port.del_all_data() self.m_chopa_out_port.del_all_attributes() self.m_chopb_out_port.del_all_data() self.m_chopb_out_port.del_all_attributes() # uncompress the FITS files if needed self.uncompress() # find and sort the FITS files files = [] for filename in os.listdir(self.m_input_location): if filename.endswith(".fits"): files.append(os.path.join(self.m_input_location, filename)) files.sort() # check if there are FITS files present in the input location assert files, f"No FITS files found in {self.m_input_location}." # if cropping chop A, get pixscale and convert crop_size to pixels and swap x/y if self.m_crop is not None: pixscale = self._m_config_port.get_attribute("PIXSCALE") self.m_crop = ( self.m_crop[1], self.m_crop[0], int(math.ceil(self.m_crop[2] / pixscale)), ) start_time = time.time() for i, filename in enumerate(files): progress(i, len(files), "Preprocessing NEAR data...", start_time) # get the primary header data and the image shape header, im_shape = self.read_header(filename) # get the images of chop A and chop B chopa, chopb = self.read_images(filename, im_shape) if self.m_subtract: chopa = chopa - chopb chopb = -1.0 * np.copy(chopa) if self.m_crop is not None: chopa = crop_image( chopa, center=self.m_crop[0:2], size=self.m_crop[2], copy=False ) chopb = crop_image( chopb, center=self.m_crop[0:2], size=self.m_crop[2], copy=False ) if self.m_combine is not None: if self.m_combine == "mean": chopa = np.mean(chopa, axis=0) chopb = np.mean(chopb, axis=0) elif self.m_combine == "median": chopa = np.median(chopa, axis=0) chopb = np.median(chopb, axis=0) header[self._m_config_port.get_attribute("NFRAMES")] = 1 # append the images of chop A and B self.m_chopa_out_port.append(chopa, data_dim=3) self.m_chopb_out_port.append(chopb, data_dim=3) # starting value for the INDEX attribute first_index = 0 for port in (self.m_chopa_out_port, self.m_chopb_out_port): # set the static attributes set_static_attr( fits_file=filename, header=header, config_port=self._m_config_port, image_out_port=port, check=True, ) # set the non-static attributes set_nonstatic_attr( header=header, config_port=self._m_config_port, image_out_port=port, check=True, ) # set the remaining attributes set_extra_attr( fits_file=filename, nimages=im_shape[0] // 2, config_port=self._m_config_port, image_out_port=port, first_index=first_index, ) # increase the first value of the INDEX attribute first_index += im_shape[0] // 2 # flush the output port port.flush() # add history information self.m_chopa_out_port.add_history("NearReadingModule", "Chop A") self.m_chopb_out_port.add_history("NearReadingModule", "Chop B") # close all connections to the database self.m_chopa_out_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/readwrite/textwriting.py000066400000000000000000000050361512720201400242150ustar00rootroot00000000000000""" Modules for writing data as text file. """ import os from typing import Optional import numpy as np from typeguard import typechecked from pynpoint.core.processing import WritingModule class TextWritingModule(WritingModule): """ Module for writing a 1D or 2D data set from the central HDF5 database as text file. TextWritingModule is a :class:`pynpoint.core.processing.WritingModule` and supports the use of the Pypeline default output directory as well as a specified location. """ __author__ = "Tomas Stolker" @typechecked def __init__( self, name_in: str, data_tag: str, file_name: str, output_dir: Optional[str] = None, header: Optional[str] = None, ) -> None: """ Parameters ---------- name_in : str Unique name of the module instance. data_tag : str Tag of the database entry from which data is exported. file_name : str Name of the output file. output_dir : str, None Output directory where the text file will be stored. If no path is specified then the Pypeline default output location is used. header : str, None Header that is written at the top of the text file. Returns ------- NoneType None """ super().__init__(name_in, output_dir=output_dir) self.m_data_port = self.add_input_port(data_tag) self.m_file_name = file_name self.m_header = header @typechecked def run(self) -> None: """ Run method of the module. Saves the specified data from the database to a text file. Returns ------- NoneType None """ if self.m_header is None: self.m_header = "" print("Writing text file...", end="") out_name = os.path.join(self.m_output_location, self.m_file_name) data = self.m_data_port.get_all() if data.ndim == 3 and data.shape[0] == 1: data = np.squeeze(data, axis=0) if data.ndim > 2: raise ValueError("Only 1D or 2D arrays can be written to a text file.") if data.dtype == "int32" or data.dtype == "int64": np.savetxt(out_name, data, header=self.m_header, comments="# ", fmt="%i") elif data.dtype == "float32" or data.dtype == "float64": np.savetxt(out_name, data, header=self.m_header, comments="# ") print(" [DONE]") self.m_data_port.close_port() PynPoint-PynPoint-e5d0637/pynpoint/util/000077500000000000000000000000001512720201400202365ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/util/__init__.py000066400000000000000000000000001512720201400223350ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/pynpoint/util/analysis.py000066400000000000000000000416431512720201400224430ustar00rootroot00000000000000""" Functions for point source analysis. """ import math from typing import Optional, Tuple import numpy as np from typeguard import typechecked from scipy.stats import t from scipy.ndimage import gaussian_filter from skimage.feature import hessian_matrix from photutils.aperture import aperture_photometry, CircularAperture from pynpoint.util.image import ( shift_image, center_subpixel, pixel_distance, select_annulus, cartesian_to_polar, create_mask, ) from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.residuals import combine_residuals def compute_aperture_flux_elements( image: np.ndarray, x_pos: float, y_pos: float, size: float, ignore: bool ): """ Computes the average fluxes inside apertures with the same separation from the center. This function can be used to to estimate the residual flux of a planet at position (x_pos, y_pos) and the respective noise elements with same separation (see function false_alarm) It can also be used to compute the noise apertures is if no planet is present (needed for contrast curves). Parameters ---------- image : numpy.ndarray The input image as a 2D numpy array. For example, this could be a residual frame returned by a :class:`.PcaPsfSubtractionModule`. x_pos : float The planet position (in pixels) along the horizontal axis. The pixel coordinates of the bottom-left corner of the image are (-0.5, -0.5). If no planet is present x_pos and y_pos determine the separation from the center. y_pos : float The planet position (pix) along the vertical axis. The pixel coordinates of the bottom-left corner of the image are (-0.5, -0.5). If no planet is present x_pos and y_pos determine the separation from the center. size : float The radius of the reference apertures (in pixels). Usually, this value is chosen close to one half of the typical FWHM of the PSF (0.514 lambda over D for a perfect Airy pattern; in practice, however, the FWHM is often larger than this). ignore : bool Whether or not to ignore the immediate neighboring apertures for the noise estimate. This is desirable in case there are "self-subtraction wings" left and right of the planet which would bias the estimation of the noise level at the separation of the planet if not ignored. Returns ------- ap_phot : A list of aperture photometry values. If a planet was present ap_phot[0] contains the flux of the planet and ap_phot[1:] contains the noise. If not planet was present ap_phot[...] gives the aperture photometry of the noise elements. """ # Compute the center of the current frame (with subpixel precision) and use it to compute the # radius of the given position in polar coordinates (with the origin at the center of the frame) center = center_subpixel(image) radius = math.sqrt((center[0] - y_pos) ** 2 + (center[1] - x_pos) ** 2) # Compute the number of apertures which we can place at the separation of the given position num_ap = int(math.pi * radius / size) # Compute the angles at which to place the reference apertures ap_theta = np.linspace(0, 2 * math.pi, num_ap, endpoint=False) # If ignore is True, delete the apertures immediately right and left of the aperture placed on # the planet signal. These apertures often contain "self-subtraction wings", which means they # cannot be considered to originate from the same distribution. In accordance with section 3.2 # of Mawet et al. (2014), such apertures are ignored to prevent bias. if ignore: num_ap -= 2 ap_theta = np.delete(ap_theta, [1, np.size(ap_theta) - 1]) # If the number of apertures is 2 or less, we cannot compute the false positive fraction if num_ap < 3: raise ValueError( f"Number of apertures (num_ap={num_ap}) is too small to calculate the " "false positive fraction." ) # Initialize a numpy array in which we will store the integrated flux of all reference apertures ap_phot = np.zeros(num_ap) # Loop over all reference apertures and measure the integrated flux for i, theta in enumerate(ap_theta): # Compute the position of the current aperture in polar coordinates and convert to Cartesian x_tmp = ( center[1] + (x_pos - center[1]) * math.cos(theta) - (y_pos - center[0]) * math.sin(theta) ) y_tmp = ( center[0] + (x_pos - center[1]) * math.sin(theta) + (y_pos - center[0]) * math.cos(theta) ) # Place a circular aperture at a position and sum up the flux inside the aperture aperture = CircularAperture((x_tmp, y_tmp), size) phot_table = aperture_photometry(image, aperture, method="exact") ap_phot[i] = phot_table["aperture_sum"][0] return ap_phot @typechecked def false_alarm( image: np.ndarray, x_pos: float, y_pos: float, size: float, ignore: bool ) -> Tuple[float, float, float, float]: """ Compute the signal-to-noise ratio (SNR), which is formally defined as the test statistic of a two-sample t-test, and related quantities (such as the FPF) at a given position in an image. For more detailed information about the definition of the signal-to-noise ratio and the motivation behind it, please see the following paper: Mawet, D. et al. (2014): "Fundamental limitations of high contrast imaging set by small sample statistics". *The Astrophysical Journal*, 792(2), 97. DOI: `10.1088/0004-637X/792/2/97 `_. Parameters ---------- image : numpy.ndarray The input image as a 2D numpy array. For example, this could be a residual frame returned by a :class:`.PcaPsfSubtractionModule`. x_pos : float The planet position (in pixels) along the horizontal axis. The pixel coordinates of the bottom-left corner of the image are (-0.5, -0.5). y_pos : float The planet position (pix) along the vertical axis. The pixel coordinates of the bottom-left corner of the image are (-0.5, -0.5). size : float The radius of the reference apertures (in pixels). Usually, this value is chosen close to one half of the typical FWHM of the PSF (0.514 lambda over D for a perfect Airy pattern; in practice, however, the FWHM is often larger than this). ignore : bool Whether or not to ignore the immediate neighboring apertures for the noise estimate. This is desirable in case there are "self-subtraction wings" left and right of the planet which would bias the estimation of the noise level at the separation of the planet if not ignored. Returns ------- signal_sum : The integrated (summed up) flux inside the signal aperture. Please note that this is **not** identical to the numerator of the fraction defining the SNR (which is given by the `signal_sum` minus the mean of the noise apertures). noise : The denominator of the SNR, i.e., the standard deviation of the integrated flux of the noise apertures, times a correction factor that accounts for small sample statistics. snr : The signal-to-noise ratio (SNR) as defined by Mawet et al. (2014) in eq. (8). fpf : The false positive fraction (FPF) as defined by Mawet et al. (2014) in eq. (10). """ ap_phot = compute_aperture_flux_elements( image=image, x_pos=x_pos, y_pos=y_pos, size=size, ignore=ignore ) # Define shortcuts to the signal and the noise aperture sums signal_aperture = ap_phot[0] noise_apertures = ap_phot[1:] # Compute the "signal", that is, the numerator of the signal-to-noise ratio: According to # eq. (8) in Mawet et al. (2014), this is given by the difference between the integrated flux # in the signal aperture and the mean of the integrated flux in the noise apertures signal = signal_aperture - np.mean(noise_apertures) # Compute the "noise", that is, the denominator of the signal-to-noise-ratio: According to # eq. (8) in Mawet et al. (2014), this is given by the standard deviation of the integrated flux # in the noise apertures times a correction factor to account for the small sample statistics. # NOTE: `ddof=1` is a necessary argument for np.std() in order to compute the *unbiased* # estimate (i.e., including Bessel's corrections) of the standard deviation. noise = np.std(noise_apertures, ddof=1) * math.sqrt( 1 + 1 / (noise_apertures.shape[0]) ) # Compute the signal-to-noise ratio by dividing the "signal" through the "noise" snr = signal / noise # Compute the false positive fraction (FPF). According to eq. (10) in Mawet et al. (2014), the # FPF is given by 1 - F_nu(SNR), where F_nu is the cumulative distribution function (CDF) of a # t-distribution with `nu = n-1` degrees of freedom (see Section 3 of Mawet et al. (2014) for # more details on the Student's t distribution). # For numerical reasons, we use the survival function (SF), which is defined precisely as 1-CDF, # but may give more accurate results according to the scipy documentation. fpf = t.sf(snr, df=(noise_apertures.shape[0] - 1)) return signal_aperture, noise, snr, fpf @typechecked def student_t( t_input: Tuple[str, float], radius: float, size: float, ignore: bool ) -> float: """ Function to calculate the false positive fraction for a given sigma level (Mawet et al. 2014). Parameters ---------- t_input : tuple(str, float) Tuple with the input type ('sigma' or 'fpf') and the input value. radius : float Aperture radius (in pixels). size : float Separation of the aperture center from the center of the frame (in pixels). ignore : bool Whether or not to ignore the immediate neighboring apertures of the point source to exclude potential self-subtraction lobes. Returns ------- float False positive fraction (FPF). """ num_ap = int(math.pi * radius / size) if ignore: num_ap -= 2 # Note that the number of degrees of freedom is given by nu = n-1 with n the number of samples. # The number of samples is equal to the number of apertures minus 1 (i.e. the planet aperture). # See Section 3 of Mawet et al. (2014) for more details on the Student's t distribution. if t_input[0] == "sigma": t_result = t.sf(t_input[1], num_ap - 2, loc=0.0, scale=1.0) elif t_input[0] == "fpf": t_result = t.ppf(1.0 - t_input[1], num_ap - 2, loc=0.0, scale=1.0) else: raise ValueError('First element of t_input needs to be "sigma" or "fpf"!') return t_result @typechecked def fake_planet( images: np.ndarray, psf: np.ndarray, parang: np.ndarray, position: Tuple[float, float], magnitude: float, psf_scaling: float, interpolation: str = "spline", ) -> np.ndarray: """ Function to inject artificial planets in a dataset. Parameters ---------- images : numpy.ndarray Input images (3D). psf : numpy.ndarray PSF template (3D). parang : numpy.ndarray Parallactic angles (deg). position : tuple(float, float) Separation (pix) and position angle (deg) measured in counterclockwise with respect to the upward direction. magnitude : float Magnitude difference used to scale input PSF. psf_scaling : float Extra factor used to scale input PSF. interpolation : str Interpolation type ('spline', 'bilinear', or 'fft'). Returns ------- numpy.ndarray Images with artificial planet injected. """ sep = position[0] ang = np.radians(position[1] + 90.0 - parang) flux_ratio = 10.0 ** (-magnitude / 2.5) psf = psf * psf_scaling * flux_ratio x_shift = sep * np.cos(ang) y_shift = sep * np.sin(ang) im_shift = np.zeros(images.shape) for i in range(images.shape[0]): if psf.shape[0] == 1: im_shift[i,] = shift_image( psf[0,], (float(y_shift[i]), float(x_shift[i])), interpolation, mode="reflect", ) else: im_shift[i,] = shift_image( psf[i,], (float(y_shift[i]), float(x_shift[i])), interpolation, mode="reflect", ) return images + im_shift @typechecked def merit_function( residuals: np.ndarray, merit: str, aperture: Tuple[int, int, float], sigma: float, var_noise: Optional[float], ) -> float: """ Function to calculate the figure of merit at a given position in the image residuals. Parameters ---------- residuals : numpy.ndarray Residuals of the PSF subtraction (2D). merit : str Figure of merit for the chi-square function ('hessian', 'poisson', or 'gaussian'). aperture : tuple(int, int, float) Position (y, x) of the aperture center (pix) and aperture radius (pix). sigma : float Standard deviation (pix) of the Gaussian kernel which is used to smooth the residuals before the chi-square is calculated. var_noise : float, None Variance of the noise which is required when `merit` is set to 'gaussian' or 'hessian'. Returns ------- float Chi-square value. """ rr_grid, _, _ = pixel_distance(residuals.shape, position=(aperture[0], aperture[1])) indices = np.where(rr_grid <= aperture[2]) if merit == "hessian": hessian_rr, hessian_rc, hessian_cc = hessian_matrix( image=residuals, sigma=sigma, mode="constant", cval=0.0, order="rc", use_gaussian_derivatives=False, ) hes_det = (hessian_rr * hessian_cc) - (hessian_rc * hessian_rc) chi_square = np.sum(hes_det[indices] ** 2) / var_noise elif merit == "poisson": if sigma > 0.0: residuals = gaussian_filter(input=residuals, sigma=sigma) chi_square = np.sum(np.abs(residuals[indices])) elif merit == "gaussian": chi_square = np.sum(residuals[indices] ** 2) / var_noise else: raise ValueError( "Figure of merit not recognized. Please use 'hessian', 'poisson' " "or 'gaussian'. Previous use of 'sum' should now be set as " "'poisson'." ) return chi_square @typechecked def pixel_variance( var_type: str, images: np.ndarray, parang: np.ndarray, cent_size: Optional[float], edge_size: Optional[float], pca_number: int, residuals: str, aperture: Tuple[int, int, float], sigma: float, ) -> float: """ Function to calculate the variance of the noise. After the PSF subtraction, images are rotated in opposite direction of the regular derotation, therefore dispersing any companion or disk signal. The noise is measured within an annulus. Parameters ---------- var_type : str Variance type ('gaussian' or 'hessian'). images : numpy.ndarray Input images (3D). parang : numpy.ndarray Parallactic angles. cent_size : float, None Radius of the central mask (pix). No mask is used when set to None. edge_size : float, None Outer radius (pix) beyond which pixels are masked. No outer mask is used when set to None. pca_number : int Number of principal components (PCs) used for the PSF subtraction. residuals : str Method for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). aperture : tuple(int, int, float) Aperture position (y, x) and radius (pix). sigma : float, None Standard deviation (pix) of the Gaussian kernel which is used to smooth the images. Returns ------- float Variance of the pixel values. Either the variance of the pixel values ('gaussian') or the variance of the determinant of the Hessian ('hessian'). """ mask = create_mask(images.shape[-2:], (cent_size, edge_size)) _, im_res_derot = pca_psf_subtraction(images * mask, parang, pca_number) res_noise = combine_residuals(residuals, im_res_derot) sep_ang = cartesian_to_polar(center_subpixel(res_noise), aperture[0], aperture[1]) if var_type == "gaussian": selected = select_annulus( res_noise[0,], sep_ang[0] - aperture[2], sep_ang[0] + aperture[2] ) elif var_type == "hessian": hessian_rr, hessian_rc, hessian_cc = hessian_matrix( image=res_noise[0,], sigma=sigma, mode="constant", cval=0.0, order="rc", use_gaussian_derivatives=False, ) hes_det = (hessian_rr * hessian_cc) - (hessian_rc * hessian_rc) selected = select_annulus( hes_det, sep_ang[0] - aperture[2], sep_ang[0] + aperture[2] ) return float(np.var(selected)) PynPoint-PynPoint-e5d0637/pynpoint/util/apply_func.py000066400000000000000000000637021512720201400227600ustar00rootroot00000000000000""" Functions that are executed with :func:`~pynpoint.core.processing.ProcessingModule.apply_function_to_images` and :func:`~pynpoint.core.processing.ProcessingModule.apply_function_in_time`. The functions are placed here such that they are pickable by the multiprocessing functionalities. The first two parameters are always the sliced data and the index in the dataset. TODO Docstrings are missing for most of the functions. """ import warnings from copy import deepcopy from typing import List, Optional, Union, Tuple import cv2 import numpy as np import pywt from numba import jit from photutils.aperture import Aperture, aperture_photometry from scipy.ndimage import gaussian_filter from scipy.optimize import curve_fit from skimage.registration import phase_cross_correlation from skimage.transform import rescale from statsmodels.robust import mad from typeguard import typechecked from pynpoint.core.dataio import InputPort, OutputPort from pynpoint.util.image import center_pixel, crop_image, scale_image, shift_image from pynpoint.util.star import locate_star from pynpoint.util.wavelets import WaveletAnalysisCapsule warnings.filterwarnings("always") @typechecked def image_scaling( image_in: np.ndarray, im_index: int, scaling_y: float, scaling_x: float, scaling_flux: float, ) -> np.ndarray: return scaling_flux * scale_image(image_in, scaling_y, scaling_x) @typechecked def subtract_line( image_in: np.ndarray, im_index: int, mask: np.ndarray, combine: str, im_shape: Tuple[int, int], ) -> np.ndarray: image_tmp = np.copy(image_in) image_tmp[mask == 0.0] = np.nan if combine == "mean": row_mean = np.nanmean(image_tmp, axis=1) col_mean = np.nanmean(image_tmp, axis=0) x_grid, y_grid = np.meshgrid(col_mean, row_mean) subtract = (x_grid + y_grid) / 2.0 elif combine == "median": col_median = np.nanmedian(image_tmp, axis=0) col_2d = np.tile(col_median, (im_shape[1], 1)) image_tmp -= col_2d image_tmp[mask == 0.0] = np.nan row_median = np.nanmedian(image_tmp, axis=1) row_2d = np.tile(row_median, (im_shape[0], 1)) row_2d = np.rot90(row_2d) # 90 deg rotation in clockwise direction subtract = col_2d + row_2d return image_in - subtract @typechecked def align_image( image_in: np.ndarray, im_index: int, interpolation: str, accuracy: float, resize: Optional[float], num_references: int, subframe: Optional[float], ref_images_reshape: np.ndarray, ref_images_shape: Tuple[int, int, int], ) -> np.ndarray: offset = np.array([0.0, 0.0]) # Reshape the reference images back to their original 3D shape # The original shape can not be used directly because of util.module.update_arguments ref_images = ref_images_reshape.reshape(ref_images_shape) for i in range(num_references): if subframe is None: tmp_offset, _, _ = phase_cross_correlation( ref_images[i, :, :], image_in, normalization=None, upsample_factor=accuracy, ) else: sub_in = crop_image(image_in, None, subframe) sub_ref = crop_image(ref_images[i, :, :], None, subframe) tmp_offset, _, _ = phase_cross_correlation( sub_ref, sub_in, normalization=None, upsample_factor=accuracy ) offset += tmp_offset offset /= float(num_references) if resize is not None: offset *= resize sum_before = np.sum(image_in) tmp_image = rescale( image_in, (resize, resize), order=5, mode="reflect", channel_axis=None, anti_aliasing=True, ) sum_after = np.sum(tmp_image) # Conserve flux because the rescale function normalizes all values to [0:1]. tmp_image = tmp_image * (sum_before / sum_after) else: tmp_image = image_in return shift_image(tmp_image, offset, interpolation) @typechecked def fit_2d_function( image: np.ndarray, im_index: int, mask_radii: Tuple[float, float], sign: str, model: str, filter_size: Optional[float], guess: Union[ Tuple[float, float, float, float, float, float, float], Tuple[float, float, float, float, float, float, float, float], ], mask_out_port: Optional[OutputPort], xx_grid: np.ndarray, yy_grid: np.ndarray, rr_ap: np.ndarray, pixscale: float, ) -> np.ndarray: @typechecked def gaussian_2d( grid: Union[Tuple[np.ndarray, np.ndarray], np.ndarray], x_center: float, y_center: float, fwhm_x: float, fwhm_y: float, amp: float, theta: float, offset: float, ) -> np.ndarray: """ Function to create a 2D elliptical Gaussian model. Parameters ---------- grid : tuple(np.ndarray, np.ndarray), np.ndarray A tuple of two 2D arrays with the mesh grid points in x and y direction, or an equivalent 3D numpy array with 2 elements along the first axis. x_center : float Offset of the model center along the x axis (pix). y_center : float Offset of the model center along the y axis (pix). fwhm_x : float Full width at half maximum along the x axis (pix). fwhm_y : float Full width at half maximum along the y axis (pix). amp : float Peak flux. theta : float Rotation angle in counterclockwise direction (rad). offset : float Flux offset. Returns ------- np.ndimage Raveled 2D elliptical Gaussian model. """ (xx_grid, yy_grid) = grid x_diff = xx_grid - x_center y_diff = yy_grid - y_center sigma_x = fwhm_x / np.sqrt(8.0 * np.log(2.0)) sigma_y = fwhm_y / np.sqrt(8.0 * np.log(2.0)) a_gauss = 0.5 * ( (np.cos(theta) / sigma_x) ** 2 + (np.sin(theta) / sigma_y) ** 2 ) b_gauss = 0.5 * ( (np.sin(2.0 * theta) / sigma_x**2) - (np.sin(2.0 * theta) / sigma_y**2) ) c_gauss = 0.5 * ( (np.sin(theta) / sigma_x) ** 2 + (np.cos(theta) / sigma_y) ** 2 ) gaussian = offset + amp * np.exp( -(a_gauss * x_diff**2 + b_gauss * x_diff * y_diff + c_gauss * y_diff**2) ) return gaussian[(rr_ap > mask_radii[0]) & (rr_ap < mask_radii[1])] @typechecked def moffat_2d( grid: Union[Tuple[np.ndarray, np.ndarray], np.ndarray], x_center: float, y_center: float, fwhm_x: float, fwhm_y: float, amp: float, theta: float, offset: float, beta: float, ) -> np.ndarray: """ Function to create a 2D elliptical Moffat model. The parametrization used here is equivalent to the one in AsPyLib: http://www.aspylib.com/doc/aspylib_fitting.html#elliptical-moffat-psf Parameters ---------- grid : tuple(np.ndarray, np.ndarray), np.ndarray A tuple of two 2D arrays with the mesh grid points in x and y direction, or an equivalent 3D numpy array with 2 elements along the first axis. x_center : float Offset of the model center along the x axis (pix). y_center : float Offset of the model center along the y axis (pix). fwhm_x : float Full width at half maximum along the x axis (pix). fwhm_y : float Full width at half maximum along the y axis (pix). amp : float Peak flux. theta : float Rotation angle in counterclockwise direction (rad). offset : float Flux offset. beta : float Power index. Returns ------- np.ndimage Raveled 2D elliptical Moffat model. """ (xx_grid, yy_grid) = grid x_diff = xx_grid - x_center y_diff = yy_grid - y_center if 2.0 ** (1.0 / beta) - 1.0 < 0.0: alpha_x = np.nan alpha_y = np.nan else: alpha_x = 0.5 * fwhm_x / np.sqrt(2.0 ** (1.0 / beta) - 1.0) alpha_y = 0.5 * fwhm_y / np.sqrt(2.0 ** (1.0 / beta) - 1.0) if alpha_x == 0.0 or alpha_y == 0.0: a_moffat = np.nan b_moffat = np.nan c_moffat = np.nan else: a_moffat = (np.cos(theta) / alpha_x) ** 2.0 + ( np.sin(theta) / alpha_y ) ** 2.0 b_moffat = (np.sin(theta) / alpha_x) ** 2.0 + ( np.cos(theta) / alpha_y ) ** 2.0 c_moffat = ( 2.0 * np.sin(theta) * np.cos(theta) * (1.0 / alpha_x**2.0 - 1.0 / alpha_y**2.0) ) a_term = a_moffat * x_diff**2 b_term = b_moffat * y_diff**2 c_term = c_moffat * x_diff * y_diff moffat = offset + amp / (1.0 + a_term + b_term + c_term) ** beta return moffat[(rr_ap > mask_radii[0]) & (rr_ap < mask_radii[1])] if filter_size: image = gaussian_filter(image, filter_size) if mask_out_port is not None: mask = np.copy(image) mask[(rr_ap < mask_radii[0]) | (rr_ap > mask_radii[1])] = 0.0 mask_out_port.append(mask, data_dim=3) if sign == "negative": image = -1.0 * image + np.abs(np.min(-1.0 * image)) image = image[(rr_ap > mask_radii[0]) & (rr_ap < mask_radii[1])] if model == "gaussian": model_func = gaussian_2d elif model == "moffat": model_func = moffat_2d if np.sum(np.abs(image) < 1e-6) > 0: # Skip the fit if the region is partially masked. # This could happen for a source close to the edge # of the field of view of derotated images. In that # case, the source might be visible only in part of # the data, depending on the parallactic angles if model == "gaussian": popt = np.full(7, np.nan) perr = np.full(7, np.nan) elif model == "moffat": popt = np.full(8, np.nan) perr = np.full(8, np.nan) warnings.warn(f"Could not run the fit on image number {im_index}.") else: try: popt, pcov, infodict, _, _ = curve_fit( model_func, (xx_grid, yy_grid), image, p0=guess, sigma=None, method="lm", full_output=True, maxfev=0, ) perr = np.sqrt(np.diag(pcov)) except RuntimeError: if model == "gaussian": popt = np.full(7, np.nan) perr = np.full(7, np.nan) elif model == "moffat": popt = np.full(8, np.nan) perr = np.full(8, np.nan) warnings.warn(f"Fit could not converge on image number {im_index}.") infodict = None if model == "gaussian": best_fit = np.asarray( ( popt[0], perr[0], popt[1], perr[1], popt[2] * pixscale, perr[2] * pixscale, popt[3] * pixscale, perr[3] * pixscale, popt[4], perr[4], np.degrees(popt[5]) % 360.0, np.degrees(perr[5]), popt[6], perr[6], ) ) elif model == "moffat": best_fit = np.asarray( ( popt[0], perr[0], popt[1], perr[1], popt[2] * pixscale, perr[2] * pixscale, popt[3] * pixscale, perr[3] * pixscale, popt[4], perr[4], np.degrees(popt[5]) % 360.0, np.degrees(perr[5]), popt[6], perr[6], popt[7], perr[7], ) ) return best_fit @typechecked def crop_around_star( image: np.ndarray, im_index: int, position: Optional[Union[Tuple[int, int, float], Tuple[None, None, float]]], im_size: int, fwhm: int, pixscale: float, index_out_port: Optional[OutputPort], image_out_port: OutputPort, ) -> np.ndarray: if position is None: center = None width = None else: if position[0] is None and position[1] is None: center = None else: center = (position[1], position[0]) # (y, x) width = int(np.ceil(position[2] / pixscale)) starpos = locate_star(image, center, width, fwhm) try: im_crop = crop_image(image, tuple(starpos), im_size) except ValueError: warnings.warn( f"Chosen image size is too large to crop the image around the " f"brightest pixel (image index = {im_index}, pixel [x, y] " f"= [{starpos[0]}, {starpos[1]}]). Using the center of the " f"image instead." ) if index_out_port is not None: index_out_port.append([im_index], data_dim=1) starpos = center_pixel(image) im_crop = crop_image(image, tuple(starpos), im_size) return im_crop @typechecked def crop_rotating_star( image: np.ndarray, im_index: int, position: Union[Tuple[float, float], np.ndarray], im_size: int, filter_size: Optional[int], search_size: int, ) -> np.ndarray: starpos = locate_star( image=image, center=tuple(position), width=search_size, fwhm=filter_size ) return crop_image(image=image, center=tuple(starpos), size=im_size) @typechecked def photometry( image: np.ndarray, im_index: int, aperture: Union[Aperture, List[Aperture]] ) -> np.ndarray: # https://photutils.readthedocs.io/en/stable/overview.html # In Photutils, pixel coordinates are zero-indexed, meaning that (x, y) = (0, 0) # corresponds to the center of the lowest, leftmost array element. This means that # the value of data[0, 0] is taken as the value over the range -0.5 < x <= 0.5, # -0.5 < y <= 0.5. Note that this is the same coordinate system as used by PynPoint. return np.array( aperture_photometry(image, aperture, method="exact")["aperture_sum"] ) @typechecked def image_stat( image_in: np.ndarray, im_index: int, indices: Optional[np.ndarray] ) -> np.ndarray: if indices is None: image_select = np.copy(image_in) else: image_reshape = np.reshape(image_in, (image_in.shape[0] * image_in.shape[1])) image_select = image_reshape[indices] nmin = np.nanmin(image_select) nmax = np.nanmax(image_select) nsum = np.nansum(image_select) mean = np.nanmean(image_select) median = np.nanmedian(image_select) std = np.nanstd(image_select) return np.asarray([nmin, nmax, nsum, mean, median, std]) @typechecked def subtract_psf( image: np.ndarray, im_index: int, parang_thres: Optional[float], nref: Optional[int], reference: Optional[np.ndarray], ang_diff: np.ndarray, image_in_port: InputPort, ) -> np.ndarray: if parang_thres: index_thres = np.where(ang_diff > parang_thres)[0] if index_thres.size == 0: reference = image_in_port.get_all() warnings.warn( "No images meet the rotation threshold. Creating a reference " "PSF from the median of all images instead." ) else: if nref: index_diff = np.abs(im_index - index_thres) index_near = np.argsort(index_diff)[:nref] index_sort = np.sort(index_thres[index_near]) reference = image_in_port[index_sort, :, :] else: reference = image_in_port[index_thres, :, :] reference = np.median(reference, axis=0) return image - reference @typechecked def dwt_denoise_line_in_time( signal_in: np.ndarray, im_index: int, threshold_function: bool, padding: str, wavelet_conf, ) -> np.ndarray: """ Definition of the temporal denoising for DWT. Parameters ---------- signal_in : np.ndarray 1D input signal. Returns ------- np.ndarray Multilevel 1D inverse discrete wavelet transform. """ if threshold_function: threshold_mode = "soft" else: threshold_mode = "hard" coef = pywt.wavedec( signal_in, wavelet=wavelet_conf.m_wavelet, level=None, mode=padding ) sigma = mad(coef[-1]) threshold = sigma * np.sqrt(2 * np.log(len(signal_in))) denoised = coef[:] denoised[1:] = ( pywt.threshold(i, value=threshold, mode=threshold_mode) for i in denoised[1:] ) return pywt.waverec(denoised, wavelet=wavelet_conf.m_wavelet, mode=padding) @typechecked def cwt_denoise_line_in_time( signal_in: np.ndarray, im_index: int, threshold_function: bool, padding: str, median_filter: bool, wavelet_conf, ) -> np.ndarray: """ Definition of temporal denoising for CWT. Parameters ---------- signal_in : np.ndarray 1D input signal. Returns ------- np.ndarray 1D output signal. """ cwt_capsule = WaveletAnalysisCapsule( signal_in=signal_in, padding=padding, wavelet_in=wavelet_conf.m_wavelet, order=wavelet_conf.m_wavelet_order, frequency_resolution=wavelet_conf.m_resolution, ) cwt_capsule.compute_cwt() cwt_capsule.denoise_spectrum(soft=threshold_function) if median_filter: cwt_capsule.median_filter() cwt_capsule.update_signal() return cwt_capsule.get_signal() @typechecked def normalization(image_in: np.ndarray, im_index: int) -> np.ndarray: return image_in - np.median(image_in) @typechecked def time_filter( timeline: np.ndarray, im_index: int, sigma: Tuple[float, float] ) -> np.ndarray: median = np.median(timeline) std = np.std(timeline) index_lower = np.argwhere(timeline < median - sigma[0] * std) index_upper = np.argwhere(timeline > median + sigma[1] * std) if index_lower.size > 0: mask = np.ones(timeline.shape, dtype=bool) mask[index_lower] = False timeline[index_lower] = np.mean(timeline[mask]) if index_upper.size > 0: mask = np.ones(timeline.shape, dtype=bool) mask[index_upper] = False timeline[index_upper] = np.mean(timeline[mask]) return timeline # This function cannot by @typechecked because of a compatibility issue with numba @jit(cache=True, nopython=True) def calc_fast_convolution( F_roof_tmp: np.complex128, W: np.ndarray, tmp_s: tuple, N_size: float, tmp_G: np.ndarray, N: Tuple[int, ...], ) -> np.ndarray: new = np.zeros(N, dtype=np.complex64) if ( ((tmp_s[0] == 0) and (tmp_s[1] == 0)) or ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == 0)) or ((tmp_s[0] == 0) and (tmp_s[1] == N[1] / 2)) or ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == N[1] / 2)) ): for m in range(0, N[0], 1): for j in range(0, N[1], 1): new[m, j] = F_roof_tmp * W[m - tmp_s[0], j - tmp_s[1]] else: for m in range(0, N[0], 1): for j in range(0, N[1], 1): new[m, j] = ( F_roof_tmp * W[m - tmp_s[0], j - tmp_s[1]] + np.conjugate(F_roof_tmp) * W[(m + tmp_s[0]) % N[0], (j + tmp_s[1]) % N[1]] ) if ( ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == 0)) or ((tmp_s[0] == 0) and (tmp_s[1] == N[1] / 2)) or ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == N[1] / 2)) ): # causes problems, unknown why res = new / float(N_size) else: res = new / float(N_size) tmp_G = tmp_G - res return tmp_G @typechecked def bad_pixel_interpolation( image_in: np.ndarray, bad_pixel_map: np.ndarray, iterations: int ) -> np.ndarray: """ Internal function to interpolate bad pixels. Parameters ---------- image_in : np.ndarray Input image. bad_pixel_map : np.ndarray Bad pixel map. iterations : int Number of iterations. Returns ------- np.ndarray Image in which the bad pixels have been interpolated. """ image_in = image_in * bad_pixel_map # for names see ref paper g = deepcopy(image_in) G = np.fft.fft2(g) w = deepcopy(bad_pixel_map) W = np.fft.fft2(w) N = g.shape N_size = float(N[0] * N[1]) F_roof = np.zeros(N, dtype=complex) tmp_G = deepcopy(G) iteration = 0 while iteration < iterations: # 1.) select line using max search and compute conjugate tmp_s = np.unravel_index( np.argmax(abs(tmp_G.real[:, 0 : N[1] // 2])), (N[0], N[1] // 2) ) tmp_s_conjugate = (np.mod(N[0] - tmp_s[0], N[0]), np.mod(N[1] - tmp_s[1], N[1])) # 2.) compute the new F_roof # special cases s = 0 or s = N/2 no conjugate line exists if ( ((tmp_s[0] == 0) and (tmp_s[1] == 0)) or ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == 0)) or ((tmp_s[0] == 0) and (tmp_s[1] == N[1] / 2)) or ((tmp_s[0] == N[0] / 2) and (tmp_s[1] == N[1] / 2)) ): F_roof_tmp = N_size * tmp_G[tmp_s] / W[(0, 0)] # 3.) update F_roof F_roof[tmp_s] += F_roof_tmp # conjugate line exists else: a = np.power(np.abs(W[(0, 0)]), 2) b = np.power(np.abs(W[(2 * tmp_s[0]) % N[0], (2 * tmp_s[1]) % N[1]]), 2) if a == b: W[(2 * tmp_s[0]) % N[0], (2 * tmp_s[1]) % N[1]] += 0.00000000001 a = np.power(np.abs(W[(0, 0)]), 2) b = ( np.power(np.abs(W[(2 * tmp_s[0]) % N[0], (2 * tmp_s[1]) % N[1]]), 2.0) + 0.01 ) c = a - b F_roof_tmp = ( N_size * ( tmp_G[tmp_s] * W[(0, 0)] - np.conj(tmp_G[tmp_s]) * W[(2 * tmp_s[0]) % N[0], (2 * tmp_s[1]) % N[1]] ) / c ) # 3.) update F_roof F_roof[tmp_s] += F_roof_tmp F_roof[tmp_s_conjugate] += np.conjugate(F_roof_tmp) # 4.) calc the new error spectrum using fast numba function tmp_G = calc_fast_convolution(F_roof_tmp, W, tmp_s, N_size, tmp_G, N) iteration += 1 return image_in * bad_pixel_map + np.fft.ifft2(F_roof).real * (1 - bad_pixel_map) @typechecked def image_interpolation( image_in: np.ndarray, im_index: int, iterations: int, bad_pixel_map: np.ndarray ) -> np.ndarray: return bad_pixel_interpolation(image_in, bad_pixel_map, iterations) @typechecked def replace_pixels( image: np.ndarray, im_index: int, index: np.ndarray, size: int, replace: str ) -> np.ndarray: im_mask = np.copy(image) for _, item in enumerate(index): im_mask[item[0], item[1]] = np.nan for _, item in enumerate(index): im_tmp = im_mask[ item[0] - size : item[0] + size + 1, item[1] - size : item[1] + size + 1 ] if np.size(np.where(im_tmp != np.nan)[0]) == 0: im_mask[item[0], item[1]] = image[item[0], item[1]] else: if replace == "mean": im_mask[item[0], item[1]] = np.nanmean(im_tmp) elif replace == "median": im_mask[item[0], item[1]] = np.nanmedian(im_tmp) elif replace == "nan": im_mask[item[0], item[1]] = np.nan return im_mask # This function cannot by @typechecked because of a compatibility issue with numba @jit(cache=True, nopython=True) def sigma_filter( dev_image: np.ndarray, var_image: np.ndarray, mean_image: np.ndarray, source_image: np.ndarray, out_image: np.ndarray, bad_pixel_map: np.ndarray, ) -> None: for i in range(source_image.shape[0]): for j in range(source_image.shape[1]): if dev_image[i][j] < var_image[i][j]: out_image[i][j] = source_image[i][j] else: out_image[i][j] = mean_image[i][j] bad_pixel_map[i][j] = 0 return out_image, bad_pixel_map @typechecked def bad_pixel_sigma_filter( image_in: np.ndarray, im_index: int, box: int, sigma: float, iterate: int, map_out_port: Optional[OutputPort], ) -> np.ndarray: # Algorithm adapted from http://idlastro.gsfc.nasa.gov/ftp/pro/image/sigma_filter.pro # Initialize bad pixel map bad_pixel_map = np.ones(image_in.shape) while iterate > 0: # Source image source_image = deepcopy(image_in) source_blur = cv2.blur(deepcopy(source_image), (box, box)) # Mean image box2 = box * box mean_image = (source_blur * box2 - source_image) / (box2 - 1) # Squared deviation between mean and source image dev_image = (mean_image - source_image) ** 2 dev_blur = cv2.blur(deepcopy(dev_image), (box, box)) # Compute variance by smoothing the image with the deviations from the mean fact = float(sigma**2) / (box2 - 2) var_image = fact * (dev_blur * box2 - dev_image) # Update image_in for the next iteration by setting out_image equal to image_in out_image = image_in # Apply the sigma filter out_image, bad_pixel_map = sigma_filter( dev_image, var_image, mean_image, source_image, out_image, bad_pixel_map ) # Subtract 1 from the number of iterations iterate -= 1 if map_out_port is not None: # Write bad pixel map to the database when CPU = 1 map_out_port.append(bad_pixel_map, data_dim=3) return out_image @typechecked def apply_shift( image_in: np.ndarray, im_index: int, shift: Union[Tuple[float, float], np.ndarray], interpolation: str, ) -> np.ndarray: return shift_image(image_in, shift, interpolation) PynPoint-PynPoint-e5d0637/pynpoint/util/attributes.py000066400000000000000000000122271512720201400230020ustar00rootroot00000000000000""" Functions for adding attributes to a dataset in the central database. """ import warnings import numpy as np from astropy.io import fits from typeguard import typechecked from pynpoint.core.attributes import get_attributes from pynpoint.core.dataio import ConfigPort, OutputPort @typechecked def set_static_attr( fits_file: str, header: fits.header.Header, config_port: ConfigPort, image_out_port: OutputPort, check: bool = True, ) -> None: """ Function which adds the static attributes to the central database. Parameters ---------- fits_file : str Name of the FITS file. header : astropy.io.fits.header.Header Header information from the FITS file that is read. config_port : pynpoint.core.dataio.ConfigPort Configuration port. image_out_port : pynpoint.core.dataio.OutputPort Output port of the images to which the static attributes are stored. check : bool Print a warning if certain attributes from the configuration file are not present in the FITS header. If set to `False`, attributes are still written to the dataset but there will be no warning if a keyword is not found in the FITS header. Returns ------- NoneType None """ attributes = get_attributes() static = [] for key, value in attributes.items(): if value["config"] == "header" and value["attribute"] == "static": static.append(key) for attr in static: fitskey = config_port.get_attribute(attr) if isinstance(fitskey, np.bytes_): fitskey = str(fitskey.decode("utf-8")) if fitskey != "None": if fitskey in header: status = image_out_port.check_static_attribute(attr, header[fitskey]) if status == 1: image_out_port.add_attribute(attr, header[fitskey], static=True) elif status == 0: pass elif status == -1: warnings.warn( f"Static attribute {fitskey} has changed. Possibly the " f"current file {fits_file} does not belong to the data set " f"'{image_out_port.tag}'. Attribute value is updated." ) elif check: warnings.warn( f"Static attribute {attr} (={fitskey}) not found in the FITS " "header." ) @typechecked def set_nonstatic_attr( header: fits.header.Header, config_port: ConfigPort, image_out_port: OutputPort, check: bool = True, ) -> None: """ Function which adds the non-static attributes to the central database. Parameters ---------- header : astropy.io.fits.header.Header Header information from the FITS file that is read. config_port : pynpoint.core.dataio.ConfigPort Configuration port. image_out_port : pynpoint.core.dataio.OutputPort Output port of the images to which the non-static attributes are stored. Returns ------- NoneType None """ attributes = get_attributes() nonstatic = [] for key, value in attributes.items(): if value["attribute"] == "non-static": nonstatic.append(key) for attr in nonstatic: if attributes[attr]["config"] == "header": fitskey = config_port.get_attribute(attr) # if type(fitskey) == np.bytes_: # fitskey = str(fitskey.decode('utf-8')) if fitskey != "None": if fitskey in header: image_out_port.append_attribute_data(attr, header[fitskey]) elif header["NAXIS"] == 2 and attr == "NFRAMES": image_out_port.append_attribute_data(attr, 1) elif check: warnings.warn( "Non-static attribute %s (=%s) not found in the " "FITS header." % (attr, fitskey) ) image_out_port.append_attribute_data(attr, -1) @typechecked def set_extra_attr( fits_file: str, nimages: int, config_port: ConfigPort, image_out_port: OutputPort, first_index: int, ) -> int: """ Function which adds extra attributes to the central database. Parameters ---------- fits_file : str Absolute path and filename of the FITS file. nimages : int Number of images. config_port : pynpoint.core.dataio.ConfigPort Configuration port. image_out_port : pynpoint.core.dataio.OutputPort Output port of the images to which the attributes are stored. first_index : int First image index of the current subset. Returns ------- int First image index for the next subset. """ pixscale = config_port.get_attribute("PIXSCALE") image_index = np.arange(first_index, first_index + nimages, 1) for item in image_index: image_out_port.append_attribute_data("INDEX", item) image_out_port.append_attribute_data("FILES", fits_file) image_out_port.add_attribute("PIXSCALE", pixscale, static=True) return first_index + nimages PynPoint-PynPoint-e5d0637/pynpoint/util/continuous.py000066400000000000000000000170521512720201400230230ustar00rootroot00000000000000# This code is written by Davide Albanese, # (C) 2011 mlpy Developers. # See: Practical Guide to Wavelet Analysis - C. Torrence and G. P. Compo. # Changes made by the PynPoint developers: # - added type hints and type checks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . from typing import Union import numpy as np from scipy.special import gamma from typeguard import typechecked @typechecked def normalization( s: Union[np.ndarray, np.generic], dt: int ) -> Union[np.ndarray, np.generic]: """ " Parameters ---------- s : numpy.ndarray Scales. dt : int Time step. Returns ------- numpy.ndarray Normalized data. """ return np.sqrt((2 * np.pi * s) / dt) @typechecked def morletft(s: np.ndarray, w: np.ndarray, w0: int, dt: int) -> np.ndarray: """ " Fourier transformed morlet function. Parameters ---------- s : numpy.ndarray Scales. w : numpy.ndarray Angular frequencies. w0 : int Omega0 frequency. dt : int Time step. Returns ------- numpy.ndarray Normalized Fourier transformed morlet function """ p = 0.75112554446494251 # pi**(-1.0/4.0) pos = w > 0 wavelet = np.zeros((s.shape[0], w.shape[0])) for i in range(s.shape[0]): n = normalization(s[i], dt) wavelet[i][pos] = n * p * np.exp(-((s[i] * w[pos] - w0) ** 2) / 2.0) return wavelet @typechecked def dogft(s: np.ndarray, w: np.ndarray, order: int, dt: int) -> np.ndarray: """ Fourier transformed DOG function. Parameters ---------- s : numpy.ndarray Scales. w : numpy.ndarray Angular frequencies. order : int Wavelet order. dt : int Time step. Returns ------- numpy.ndarray Normalized Fourier transformed DOG function. """ p = -((0.0 + 1.0j) ** order) / np.sqrt(gamma(order + 0.5)) wavelet = np.zeros((s.shape[0], w.shape[0]), dtype=np.complex128) for i in range(s.shape[0]): n = normalization(s[i], dt) h = s[i] * w wavelet[i] = n * p * h**order * np.exp(-(h**2) / 2.0) return wavelet @typechecked def angularfreq(N: int, dt: int) -> np.ndarray: """ Compute angular frequencies. Parameters ---------- N : int Number of data samples. dt : int Time step. Returns ------- numpy.ndarray Angular frequencies (1D). """ # See (5) at page 64. N2 = int(N / 2.0) w = np.empty(N) for i in range(w.shape[0]): if i <= N2: w[i] = (2 * np.pi * i) / (N * dt) else: w[i] = (2 * np.pi * (i - N)) / (N * dt) return w @typechecked def autoscales(N: int, dt: int, dj: float, wf: str, p: int) -> np.ndarray: """ Compute scales as fractional power of two. Parameters ---------- N : int Number of data samples. dt : int Time step. dj : float Scale resolution (smaller values of give finer resolution). wf : str Wavelet function ("morlet", "paul", or "dog"). p : int omega0 ("morlet") or order ("paul", "dog"). Returns ------- numpy.ndarray Scales (1D). """ if wf == "dog": s0 = (dt * np.sqrt(p + 0.5)) / np.pi elif wf == "morlet": s0 = (dt * (p + np.sqrt(2 + p**2))) / (2 * np.pi) else: raise ValueError("Wavelet function not available.") # See (9) and (10) at page 67. J = int(np.floor(dj**-1 * np.log2((N * dt) / s0))) s = np.empty(J + 1) for i in range(s.shape[0]): s[i] = s0 * 2 ** (i * dj) return s # def fourier_from_scales(scales, wf, p): # """Compute the equivalent fourier period # from scales. # # :Parameters: # scales : list or 1d numpy array # scales # wf : string ('morlet', 'paul', 'dog') # wavelet function # p : float # wavelet function parameter ('omega0' for morlet, 'm' for paul # and dog) # # :Returns: # fourier wavelengths # """ # # scales_arr = np.asarray(scales) # # if wf == 'dog': # return (2 * np.pi * scales_arr) / np.sqrt(p + 0.5) # elif wf == 'morlet': # return (4 * np.pi * scales_arr) / (p + np.sqrt(2 + p ** 2)) # else: # raise ValueError('wavelet function not available') # def scales_from_fourier(f, wf, p): # """Compute scales from fourier period. # # :Parameters: # f : list or 1d numpy array # fourier wavelengths # wf : string ('morlet', 'paul', 'dog') # wavelet function # p : float # wavelet function parameter ('omega0' for morlet, 'm' for paul # and dog) # # :Returns: # scales # """ # # f_arr = np.asarray(f) # # if wf == 'dog': # return (f_arr * np.sqrt(p + 0.5)) / (2 * np.pi) # elif wf == 'morlet': # return (f_arr * (p + np.sqrt(2 + p ** 2))) / (4 * np.pi) # else: # raise ValueError('wavelet function not available') @typechecked def cwt( x: np.ndarray, dt: int, scales: np.ndarray, wf: str = "dog", p: int = 2 ) -> np.ndarray: """ Continuous Wavelet Transform. Parameters ---------- x : numpy.ndarray Data (1D). dt : int Time step. scales : numpy.ndarray Scales (1D). wf : str Wavelet function ("morlet", "paul", or "dog"). p : int omega0 ("morlet") or order ("paul", "dog"). Returns ------- numpy.ndarray Transformed data (2D). """ x_arr = np.asarray(x) - np.mean(x) scales_arr = np.asarray(scales) if x_arr.ndim != 1: raise ValueError("x must be an 1d numpy array of list") if scales_arr.ndim != 1: raise ValueError("scales must be an 1d numpy array of list") w = angularfreq(N=x_arr.shape[0], dt=dt) if wf == "dog": wft = dogft(s=scales_arr, w=w, order=p, dt=dt) elif wf == "morlet": wft = morletft(s=scales_arr, w=w, w0=p, dt=dt) else: raise ValueError("wavelet function is not available") X_ARR = np.empty((wft.shape[0], wft.shape[1]), dtype=np.complex128) x_arr_ft = np.fft.fft(x_arr) for i in range(X_ARR.shape[0]): X_ARR[i] = np.fft.ifft(x_arr_ft * wft[i]) return X_ARR @typechecked def icwt(X: np.ndarray, scales: np.ndarray) -> np.ndarray: """ Inverse Continuous Wavelet Transform. The reconstruction factor is not applied. Parameters ---------- X : numpy.ndarray Transformed data (2D). scales : numpy.ndarray Scales (1D). Returns ------- numpy.ndarray 1D data. """ X_arr = np.asarray(X) scales_arr = np.asarray(scales) if X_arr.shape[0] != scales_arr.shape[0]: raise ValueError("X, scales: shape mismatch") # See (11), (13) at page 68 X_ARR = np.empty_like(X_arr) for i in range(scales_arr.shape[0]): X_ARR[i] = X_arr[i] / np.sqrt(scales_arr[i]) return np.sum(np.real(X_ARR), axis=0) PynPoint-PynPoint-e5d0637/pynpoint/util/image.py000066400000000000000000000357361512720201400217100ustar00rootroot00000000000000""" Functions for image processing. """ import math from typing import Optional, Tuple, Union import numpy as np from scipy.ndimage import fourier_shift, shift, rotate from skimage.transform import rescale from typeguard import typechecked @typechecked def center_pixel(image: np.ndarray) -> Tuple[int, int]: """ Function to get the pixel position of the image center. Note that this position can not be unambiguously defined for an even-sized image. Python indexing starts at 0 so the coordinates of the pixel in the bottom-left corner are (0, 0). Parameters ---------- image : np.ndarray Input image (2D or 3D). Returns ------- tuple(int, int) Pixel position (y, x) of the image center. """ if image.shape[-2] % 2 == 0 and image.shape[-1] % 2 == 0: center = (image.shape[-2] // 2 - 1, image.shape[-1] // 2 - 1) elif image.shape[-2] % 2 == 0 and image.shape[-1] % 2 == 1: center = (image.shape[-2] // 2 - 1, (image.shape[-1] - 1) // 2) elif image.shape[-2] % 2 == 1 and image.shape[-1] % 2 == 0: center = ((image.shape[-2] - 1) // 2, image.shape[-1] // 2 - 1) elif image.shape[-2] % 2 == 1 and image.shape[-1] % 2 == 1: center = ((image.shape[-2] - 1) // 2, (image.shape[-1] - 1) // 2) else: raise RuntimeError("Unexpected image shape. This error should not occur.") return center @typechecked def center_subpixel(image: np.ndarray) -> Tuple[float, float]: """ Function to get the precise position of the image center. The center of the pixel in the bottom left corner of the image is defined as (0, 0), so the bottom left corner of the image is located at (-0.5, -0.5). Parameters ---------- image : np.ndarray Input image (2D or 3D). Returns ------- tuple(float, float) Subpixel position (y, x) of the image center. """ center_x = float(image.shape[-1]) / 2 - 0.5 center_y = float(image.shape[-2]) / 2 - 0.5 return center_y, center_x @typechecked def crop_image( image: np.ndarray, center: Optional[tuple], size: int, copy: bool = True ) -> np.ndarray: """ Function to crop square images around a specified position. Parameters ---------- image : np.ndarray Input image (2D or 3D). center : tuple(int, int), None The new image center (y, x). The center of the image is used if set to None. size : int Image size (pix) for both dimensions. Increased by 1 pixel if size is an even number. copy : bool Whether or not to return a copy (instead of a view) of the cropped image (default: True). Returns ------- np.ndarray Cropped odd-sized image (2D or 3D). """ if center is None or (center[0] is None and center[1] is None): center = center_pixel(image) # if image.shape[-1] % 2 == 0: # warnings.warn('The image is even-size so there is not a uniquely defined pixel in ' # 'the center of the image. The image center is determined (with pixel ' # 'precision) with the pynpoint.util.image.center_pixel function.') if size % 2 == 0: size += 1 x_start = center[1] - (size - 1) // 2 x_end = center[1] + (size - 1) // 2 + 1 y_start = center[0] - (size - 1) // 2 y_end = center[0] + (size - 1) // 2 + 1 if x_start < 0 or y_start < 0 or x_end > image.shape[-1] or y_end > image.shape[-2]: raise ValueError( "Target image resolution does not fit inside the input image resolution." ) return np.array(image[..., y_start:y_end, x_start:x_end], copy=copy) @typechecked def rotate_images(images: np.ndarray, angles: np.ndarray) -> np.ndarray: """ Function to rotate all images in clockwise direction. Parameters ---------- images : np.ndarray Stack of images (3D). angles : np.ndarray Rotation angles (deg). Returns ------- np.ndarray Rotated images. """ im_rot = np.zeros(images.shape) for i, item in enumerate(angles): im_rot[i,] = rotate(input=images[i,], angle=item, reshape=False) return im_rot @typechecked def create_mask( im_shape: Tuple[int, int], size: Union[ Tuple[float, float], Tuple[float, None], Tuple[None, float], Tuple[None, None] ], ) -> np.ndarray: """ Function to create a mask for the central and outer image regions. Parameters ---------- im_shape : tuple(int, int) Image size in both dimensions. size : tuple(float, float) Size (pix) of the inner and outer mask. Returns ------- np.ndarray Image mask. """ mask = np.ones(im_shape) npix = im_shape[0] if size[0] is not None or size[1] is not None: if npix % 2 == 0: x_grid = y_grid = np.linspace(-npix / 2 + 0.5, npix / 2 - 0.5, npix) else: x_grid = y_grid = np.linspace(-(npix - 1) / 2, (npix - 1) / 2, npix) xx_grid, yy_grid = np.meshgrid(x_grid, y_grid) rr_grid = np.sqrt(xx_grid**2 + yy_grid**2) if size[0] is not None: mask[rr_grid < size[0]] = 0.0 if size[1] is not None: if size[1] > npix / 2: size = (size[0], npix / 2) mask[rr_grid > size[1]] = 0.0 return mask @typechecked def shift_image( image: np.ndarray, shift_yx: Union[Tuple[float, float], np.ndarray], interpolation: str, mode: str = "constant", ) -> np.ndarray: """ Function to shift an image. Parameters ---------- image : np.ndarray Input image (2D or 3D). If 3D the image is not shifted along the 0th axis. shift_yx : tuple(float, float), np.ndarray Shift (y, x) to be applied (pix). An additional shift of zero pixels will be added for the first dimension in case the input image is 3D. interpolation : str Interpolation type ('spline', 'bilinear', or 'fft'). mode : str Interpolation mode. Returns ------- np.ndarray Shifted image. """ if image.ndim == 2: shift_val = (shift_yx[0], shift_yx[1]) elif image.ndim == 3: shift_val = (0, shift_yx[0], shift_yx[1]) else: raise ValueError("Invalid number of dimensions for image: must be 2 or 3") if interpolation == "spline": im_center = shift(image, shift_val, order=5, mode=mode) elif interpolation == "bilinear": im_center = shift(image, shift_val, order=1, mode=mode) elif interpolation == "fft": fft_shift = fourier_shift(np.fft.fftn(image), shift_val) im_center = np.fft.ifftn(fft_shift).real else: raise ValueError( "interpolation must be one of the following: spline, bilinear, fft" ) return im_center @typechecked def scale_image( image: np.ndarray, scaling_y: Union[float, np.float32], scaling_x: Union[float, np.float32], ) -> np.ndarray: """ Function to spatially scale an image. Parameters ---------- image : np.ndarray Input image (2D). scaling_y : float Scaling factor y. scaling_x : float Scaling factor x. Returns ------- np.ndarray Shifted image (2D). """ sum_before = np.sum(image) im_scale = rescale( image, (scaling_y, scaling_x), order=5, mode="reflect", channel_axis=None, anti_aliasing=True, ) sum_after = np.sum(im_scale) return im_scale * (sum_before / sum_after) @typechecked def cartesian_to_polar( center: Tuple[float, float], y_pos: float, x_pos: float ) -> Tuple[float, float]: """ Function to convert pixel coordinates to polar coordinates. Parameters ---------- center : tuple(float, float) Image center (y, x) from :func:`~pynpoint.util.image.center_subpixel`. y_pos : float Pixel coordinate along the vertical axis. The bottom left corner of the image is (-0.5, -0.5). x_pos : float Pixel coordinate along the horizontal axis. The bottom left corner of the image is (-0.5, -0.5). Returns ------- tuple(float, float) Separation (pix) and position angle (deg). The angle is measured counterclockwise with respect to the positive y-axis. """ sep = math.sqrt((center[1] - x_pos) ** 2 + (center[0] - y_pos) ** 2) ang = math.atan2(y_pos - center[1], x_pos - center[0]) ang = (math.degrees(ang) - 90) % 360 return sep, ang @typechecked def polar_to_cartesian( image: np.ndarray, sep: float, ang: float ) -> Tuple[float, float]: """ Function to convert polar coordinates to pixel coordinates. Parameters ---------- image : np.ndarray Input image (2D or 3D). sep : float Separation (pixels). ang : float Position angle (deg), measured counterclockwise with respect to the positive y-axis. Returns ------- tuple(float, float) Cartesian coordinates (y, x). The bottom left corner of the image is (-0.5, -0.5). """ center = center_subpixel(image) # (y, x) x_pos = center[1] + sep * math.cos(math.radians(ang + 90)) y_pos = center[0] + sep * math.sin(math.radians(ang + 90)) return y_pos, x_pos @typechecked def pixel_distance( im_shape: Tuple[int, int], position: Optional[Tuple[int, int]] = None ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: """ Function to calculate the distance of each pixel with respect to a given pixel position. Supports both odd and even sized images. Parameters ---------- im_shape : tuple(int, int) Image shape (y, x). position : tuple(int, int) Pixel center (y, x) from which the distance is calculated. The image center is used if set to None. Python indexing starts at zero so the center of the bottom left pixel is (0, 0). Returns ------- np.ndarray 2D array with the distances of each pixel from the provided pixel position. np.ndarray 2D array with the x coordinates. np.ndarray 2D array with the y coordinates. """ if im_shape[0] % 2 == 0: y_grid = np.linspace(-im_shape[0] / 2 + 0.5, im_shape[0] / 2 - 0.5, im_shape[0]) else: y_grid = np.linspace(-(im_shape[0] - 1) / 2, (im_shape[0] - 1) / 2, im_shape[0]) if im_shape[1] % 2 == 0: x_grid = np.linspace(-im_shape[1] / 2 + 0.5, im_shape[1] / 2 - 0.5, im_shape[1]) else: x_grid = np.linspace(-(im_shape[1] - 1) / 2, (im_shape[1] - 1) / 2, im_shape[1]) if position is not None: y_shift = y_grid[position[0]] x_shift = x_grid[position[1]] y_grid -= y_shift x_grid -= x_shift xx_grid, yy_grid = np.meshgrid(x_grid, y_grid) return np.sqrt(xx_grid**2 + yy_grid**2), xx_grid, yy_grid @typechecked def subpixel_distance( im_shape: Tuple[int, int], position: Tuple[float, float], shift_center: bool = True ) -> np.ndarray: """ Function to calculate the distance of each pixel with respect to a given subpixel position. Supports both odd and even sized images. Parameters ---------- im_shape : tuple(int, int) Image shape (y, x). position : tuple(float, float) Pixel center (y, x) from which the distance is calculated. Python indexing starts at zero so the bottom left image corner is (-0.5, -0.5). shift_center : bool Apply the coordinate correction for the image center. Returns ------- np.ndarray 2D array with the distances of each pixel from the provided pixel position. """ # Get 2D x and y coordinates with respect to the image center _, xx_grid, yy_grid = pixel_distance(im_shape, position=None) if im_shape[0] % 2 == 0: # Distance from the image center to the center of the outermost pixel # Even sized images y_size = im_shape[0] / 2 + 0.5 x_size = im_shape[1] / 2 + 0.5 else: # Distance from the image center to the center of the outermost pixel # Odd sized images y_size = (im_shape[0] - 1) / 2 x_size = (im_shape[1] - 1) / 2 if shift_center: # Shift the image center to the center of the bottom left pixel yy_grid += y_size xx_grid += x_size # Apply a subpixel shift of the coordinate system to the requested position yy_grid -= position[0] xx_grid -= position[1] return np.sqrt(xx_grid**2 + yy_grid**2) @typechecked def select_annulus( image_in: np.ndarray, radius_in: float, radius_out: float, mask_position: Optional[Tuple[float, float]] = None, mask_radius: Optional[float] = None, ) -> np.ndarray: """ image_in : np.ndarray Input image. radius_in : float Inner radius of the annulus (pix). radius_out : float Outer radius of the annulus (pix). mask_position : tuple(float, float), None Center (pix) position (y, x) in of the circular region that is excluded. Not used if set to None. mask_radius : float, None Radius (pix) of the circular region that is excluded. Not used if set to None. """ im_shape = image_in.shape if im_shape[0] % 2 == 0: y_grid = np.linspace(-im_shape[0] / 2 + 0.5, im_shape[0] / 2 - 0.5, im_shape[0]) else: y_grid = np.linspace(-(im_shape[0] - 1) / 2, (im_shape[0] - 1) / 2, im_shape[0]) if im_shape[1] % 2 == 0: x_grid = np.linspace(-im_shape[1] / 2 + 0.5, im_shape[1] / 2 - 0.5, im_shape[1]) else: x_grid = np.linspace(-(im_shape[1] - 1) / 2, (im_shape[1] - 1) / 2, im_shape[1]) xx_grid, yy_grid = np.meshgrid(x_grid, y_grid) rr_grid = np.sqrt(xx_grid**2 + yy_grid**2) mask = np.ones(im_shape) indices = np.where((rr_grid < radius_in) | (rr_grid > radius_out)) mask[indices[0], indices[1]] = 0.0 if mask_position is not None and mask_radius is not None: distance = subpixel_distance(im_shape=im_shape, position=mask_position) indices = np.where(distance < mask_radius) mask[indices[0], indices[1]] = 0.0 indices = np.where(mask == 1.0) return image_in[indices[0], indices[1]] @typechecked def rotate_coordinates( center: Tuple[float, float], position: Union[Tuple[float, float], np.ndarray], angle: float, ) -> Tuple[float, float]: """ Function to rotate coordinates around the image center. Parameters ---------- center : tuple(float, float) Image center (y, x) with subpixel accuracy. position : tuple(float, float) Position (y, x) in the image, or a 2D numpy array of positions. angle : float Angle (deg) to rotate in counterclockwise direction. Returns ------- tuple(float, float) New position (y, x). """ pos_y = (position[1] - center[1]) * math.sin(np.radians(angle)) + ( position[0] - center[0] ) * math.cos(np.radians(angle)) pos_x = (position[1] - center[1]) * math.cos(np.radians(angle)) - ( position[0] - center[0] ) * math.sin(np.radians(angle)) return center[0] + pos_y, center[1] + pos_x PynPoint-PynPoint-e5d0637/pynpoint/util/limits.py000066400000000000000000000141031512720201400221100ustar00rootroot00000000000000""" Functions for calculating detection limits. """ import math from typing import Tuple import numpy as np from scipy.stats import t from photutils.aperture import aperture_photometry, CircularAperture from typeguard import typechecked from pynpoint.util.analysis import ( student_t, fake_planet, compute_aperture_flux_elements, ) from pynpoint.util.image import polar_to_cartesian, center_subpixel from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.residuals import combine_residuals @typechecked def contrast_limit( path_images: str, path_psf: str, noise: np.ndarray, mask: np.ndarray, parang: np.ndarray, psf_scaling: float, extra_rot: float, pca_number: int, threshold: Tuple[str, float], aperture: float, residuals: str, snr_inject: float, position: Tuple[float, float], ) -> Tuple[float, float, float, float]: """ Function for calculating the contrast limit at a specified position for a given sigma level or false positive fraction, both corrected for small sample statistics. Parameters ---------- path_images : str System location of the stack of images (3D). path_psf : str System location of the PSF template for the fake planet (3D). Either a single image or a stack of images equal in size to science data. noise : numpy.ndarray Residuals of the PSF subtraction (3D) without injection of fake planets. Used to measure the noise level with a correction for small sample statistics. mask : numpy.ndarray Mask (2D). parang : numpy.ndarray Derotation angles (deg). psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should have a positive value. extra_rot : float Additional rotation angle of the images in clockwise direction (deg). pca_number : int Number of principal components used for the PSF subtraction. threshold : tuple(str, float) Detection threshold for the contrast curve, either in terms of 'sigma' or the false positive fraction (FPF). The value is a tuple, for example provided as ('sigma', 5.) or ('fpf', 1e-6). Note that when sigma is fixed, the false positive fraction will change with separation. Also, sigma only corresponds to the standard deviation of a normal distribution at large separations (i.e., large number of samples). aperture : float Aperture radius (pix) for the calculation of the false positive fraction. residuals : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). snr_inject : float Signal-to-noise ratio of the injected planet signal that is used to measure the amount of self-subtraction. position : tuple(float, float) The separation (pix) and position angle (deg) of the fake planet. Returns ------- float Separation (pix). float Position angle (deg). float Contrast (mag). float False positive fraction. """ images = np.load(path_images) psf = np.load(path_psf) # Cartesian coordinates of the fake planet yx_fake = polar_to_cartesian(images, position[0], position[1] - extra_rot) # Determine the noise level noise_apertures = compute_aperture_flux_elements( image=noise[0,], x_pos=yx_fake[1], y_pos=yx_fake[0], size=aperture, ignore=False ) t_noise = np.std(noise_apertures, ddof=1) * math.sqrt( 1 + 1 / (noise_apertures.shape[0]) ) # get sigma from fpf or fpf from sigma # Note that the number of degrees of freedom is given by nu = n-1 with n the number of samples. # See Section 3 of Mawet et al. (2014) for more details on the Student's t distribution. if threshold[0] == "sigma": sigma = threshold[1] # Calculate the FPF for a given sigma level fpf = t.sf(sigma, noise_apertures.shape[0] - 1, loc=0.0, scale=1.0) elif threshold[0] == "fpf": fpf = threshold[1] # Calculate the sigma level for a given FPF sigma = t.isf(fpf, noise_apertures.shape[0] - 1, loc=0.0, scale=1.0) else: raise ValueError("Threshold type not recognized.") # Aperture properties im_center = center_subpixel(images) # Measure the flux of the star ap_phot = CircularAperture((im_center[1], im_center[0]), aperture) phot_table = aperture_photometry(psf_scaling * psf[0,], ap_phot, method="exact") star = phot_table["aperture_sum"][0] # Magnitude of the injected planet flux_in = snr_inject * t_noise mag = -2.5 * math.log10(flux_in / star) # Inject the fake planet fake = fake_planet( images=images, psf=psf, parang=parang, position=(position[0], position[1]), magnitude=mag, psf_scaling=psf_scaling, ) # Run the PSF subtraction _, im_res = pca_psf_subtraction( images=fake * mask, angles=-1.0 * parang + extra_rot, pca_number=pca_number ) # Stack the residuals im_res = combine_residuals(method=residuals, res_rot=im_res) flux_out_frame = im_res[0,] - noise[0,] # Measure the flux of the fake planet after PCA # the first element is the planet flux_out = compute_aperture_flux_elements( image=flux_out_frame, x_pos=yx_fake[1], y_pos=yx_fake[0], size=aperture, ignore=False, )[0] # Calculate the amount of self-subtraction attenuation = flux_out / flux_in # the throughput can not be negative. However, this can happen due to numerical inaccuracies if attenuation < 0: attenuation = 0 # Calculate the detection limit contrast = (sigma * t_noise + np.mean(noise_apertures)) / (attenuation * star) # The flux_out can be negative, for example if the aperture includes self-subtraction regions if contrast > 0.0: contrast = -2.5 * math.log10(contrast) else: contrast = np.nan # Separation [pix], position angle [deg], contrast [mag], FPF return position[0], position[1], contrast, fpf PynPoint-PynPoint-e5d0637/pynpoint/util/mcmc.py000066400000000000000000000116211512720201400215300ustar00rootroot00000000000000""" Functions for MCMC sampling. """ import math from typing import Optional, Tuple import numpy as np from typeguard import typechecked from pynpoint.util.analysis import fake_planet, merit_function from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.residuals import combine_residuals @typechecked def lnprob( param: np.ndarray, bounds: Tuple[Tuple[float, float], Tuple[float, float], Tuple[float, float]], images: np.ndarray, psf: np.ndarray, mask: np.ndarray, parang: np.ndarray, psf_scaling: float, pixscale: float, pca_number: int, extra_rot: float, aperture: Tuple[int, int, float], indices: np.ndarray, merit: str, residuals: str, var_noise: Optional[float], ) -> float: """ Function for the log posterior function. Should be placed at the highest level of the Python module to be pickable for the multiprocessing. Parameters ---------- param : numpy.ndarray The separation (arcsec), angle (deg), and contrast (mag). The angle is measured in counterclockwise direction with respect to the positive y-axis. bounds : tuple(tuple(float, float), tuple(float, float), tuple(float, float)) The boundaries of the separation (arcsec), angle (deg), and contrast (mag). Each set of boundaries is specified as a tuple. images : numpy.ndarray Stack with images. psf : numpy.ndarray PSF template, either a single image (2D) or a cube (3D) with the dimensions equal to *images*. mask : numpy.ndarray Array with the circular mask (zeros) of the central and outer regions. parang : numpy.ndarray Array with the angles for derotation. psf_scaling : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should be negative in order to inject negative fake planets. pixscale : float Additional scaling factor of the planet flux (e.g., to correct for a neutral density filter). Should be negative in order to inject negative fake planets. pca_number : int Number of principal components used for the PSF subtraction. extra_rot : float Additional rotation angle of the images (deg). aperture : tuple(int, int, float) Position (y, x) of the aperture center (pix) and aperture radius (pix). indices : numpy.ndarray Non-masked image indices. merit : str Figure of merit that is used for the likelihood function ('gaussian' or 'poisson'). Pixels are assumed to be independent measurements which are expected to be equal to zero in case the best-fit negative PSF template is injected. With 'gaussian', the variance is estimated from the pixel values within an annulus at the separation of the aperture (but excluding the pixels within the aperture). With 'poisson', a Poisson distribution is assumed for the variance of each pixel value. residuals : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). var_noise : float, None Variance of the noise which is required when `merit` is set to 'gaussian' or 'hessian'. Returns ------- float Log posterior probability. """ @typechecked def _lnprior() -> float: """ Internal function for the log prior function. Returns ------- float Log prior. """ if ( bounds[0][0] <= param[0] <= bounds[0][1] and bounds[1][0] <= param[1] <= bounds[1][1] and bounds[2][0] <= param[2] <= bounds[2][1] ): ln_prior = 0.0 else: ln_prior = -np.inf return ln_prior @typechecked def _lnlike() -> float: """ Internal function for the log likelihood function. Returns ------- float Log likelihood. """ sep, ang, mag = param fake = fake_planet( images=images, psf=psf, parang=parang - extra_rot, position=(sep / pixscale, ang), magnitude=mag, psf_scaling=psf_scaling, ) im_res_rot, im_res_derot = pca_psf_subtraction( images=fake * mask, angles=-1.0 * parang + extra_rot, pca_number=pca_number, indices=indices, ) res_stack = combine_residuals( method=residuals, res_rot=im_res_derot, residuals=im_res_rot, angles=parang ) chi_square = merit_function( residuals=res_stack[0,], merit=merit, aperture=aperture, sigma=0.0, var_noise=var_noise, ) return -0.5 * chi_square ln_prior = _lnprior() if math.isinf(ln_prior): ln_prob = -np.inf else: ln_prob = ln_prior + _lnlike() return ln_prob PynPoint-PynPoint-e5d0637/pynpoint/util/module.py000066400000000000000000000241021512720201400220740ustar00rootroot00000000000000""" Functions for Pypeline modules. """ import sys import time import math import cmath import warnings from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union import numpy as np from typeguard import typechecked # The following is needed to avoid circular dependencies: # The PypelineModule uses methods from module.py, but methods in this file also use PypelineModule # for their type hints. If we were to simply import the PypelineModule class here, this circular # dependency would lead to an ImportError at runtime. By using the TYPE_CHECKING flag, we can avoid # this: because TYPE_CHECKING is always False at runtime, there are no ImportErrors, while the # PypelineModule is still available for static type checkers (i.e., not for the typeguard library). # In Python 3.7, this problem can be circumvented more elegantly by using: # >>> from __future__ import annotations # This changes the behavior of type hints such that they are no longer evaluated at definition time, # see also PEP 563. In Python 4.0, this is supposed to become the new default behavior. if TYPE_CHECKING: from pynpoint.core.processing import PypelineModule @typechecked def progress( current: int, total: int, message: str, start_time: Optional[float] = None ) -> None: """ Function to show and update the progress as standard output. Parameters ---------- current : int Current index. total : int Total index number. message : str Message that is printed. start_time : float, None, optional Start time in seconds. Not used if set to None. Returns ------- NoneType None """ @typechecked def time_string(delta_time: float) -> str: """ Converts to input time in seconds to a string which displays as hh:mm:ss. Parameters ---------- delta_time : float Input time in seconds. Returns ------- str: String with the formatted time. """ hours = int(delta_time / 3600.0) minutes = int((delta_time % 3600.0) / 60.0) seconds = int(delta_time % 60.0) return f"{hours:>02}:{minutes:>02}:{seconds:>02}" fraction = float(current) / float(total) percentage = 100.0 * fraction if start_time is None: sys.stdout.write(f"\r{message} {percentage:4.1f}% \r") else: if fraction > 0.0 and current + 1 != total: time_taken = time.time() - start_time time_left = time_taken / fraction * (1.0 - fraction) sys.stdout.write( f"{message} {percentage:4.1f}% - ETA: {time_string(time_left)}\r" ) if current + 1 == total: sys.stdout.write((29 + len(message)) * " " + "\r") sys.stdout.write(message + " [DONE]\n") sys.stdout.flush() @typechecked def memory_frames(memory: Union[int, np.int64], nimages: int) -> np.ndarray: """ Function to subdivide the input images is in quantities of MEMORY. Parameters ---------- memory : int Number of images that is simultaneously loaded into the memory. nimages : int Number of images in the stack. Returns ------- numpy.ndarray Array with the indices where a stack of images is subdivided. """ if memory == 0 or memory >= nimages: frames = np.asarray([0, nimages]) else: frames = np.linspace( start=0, stop=nimages - nimages % memory, num=int(float(nimages) / float(memory)) + 1, endpoint=True, dtype=np.int64, ) if nimages % memory > 0: frames = np.append(frames, nimages) return frames @typechecked def angle_average(angles: np.ndarray) -> float: """ Function to calculate the average value of a list of angles. Parameters ---------- angles : numpy.ndarray Parallactic angles (deg). Returns ------- float Average angle (deg). """ cmath_rect = sum(cmath.rect(1, math.radians(ang)) for ang in angles) cmath_phase = cmath.phase(cmath_rect / len(angles)) return math.degrees(cmath_phase) @typechecked def angle_difference(angle_1: float, angle_2: float) -> float: """ Function to calculate the difference between two angles. Parameters ---------- angle_1 : float First angle (deg). angle_2 : float Second angle (deg). Returns ------- float Angle difference (deg). """ angle_diff = (angle_1 - angle_2) % 360.0 if angle_diff >= 180.0: angle_diff -= 360.0 return angle_diff @typechecked def stack_angles( memory: Union[int, np.int64], parang: np.ndarray, max_rotation: float ) -> np.ndarray: """ Function to subdivide the input images is in quantities of MEMORY with a restriction on the maximum field rotation across a subset of images. Parameters ---------- memory : int Number of images that is simultaneously loaded into the memory. parang : numpy.ndarray Parallactic angles (deg). max_rotation : float Maximum field rotation (deg). Returns ------- numpy.ndarray Array with the indices where a stack of images is subdivided. """ warnings.warn( "Testing of util.module.stack_angles has been limited, please use carefully." ) nimages = parang.size if memory == 0 or memory >= nimages: frames = [0, nimages] else: frames = [ 0, ] parang_start = parang[0] im_count = 0 for i in range(1, parang.size): abs_start_diff = abs(angle_difference(parang_start, parang[i - 1])) abs_current_diff = abs(angle_difference(parang[i], parang[i - 1])) if abs_start_diff > max_rotation or abs_current_diff > max_rotation: frames.append(i) parang_start = parang[i] im_count = 0 else: im_count += 1 if im_count == memory: frames.append(i) if i < parang.size - 1: parang_start = parang[i + 1] im_count = 0 if frames[-1] != nimages: frames.append(nimages) return np.asarray(frames) @typechecked def update_arguments( index: int, nimages: int, args_in: Optional[tuple] ) -> Optional[tuple]: """ Function to update the arguments of an input function. Specifically, arguments which contain an array with the first dimension equal in size to the total number of images will be substituted by the array element of the image index. Parameters ---------- index : int Image index in the stack. nimages : int Total number of images in the stack. args_in : tuple, None Function arguments that have to be updated. Returns ------- tuple, None Updated function arguments. """ if args_in is None: args_out = None else: args_out = [] for item in args_in: if isinstance(item, np.ndarray) and item.shape[0] == nimages: args_out.append(item[index]) else: args_out.append(item) args_out = tuple(args_out) return args_out # This function *cannot* be decorated with @typechecked, because the typeguard library checks type # hints at *runtime*, when PypelineModule is not available without causing circular dependencies. def module_info(pipeline_module: "PypelineModule") -> None: """ Function to print the module name. Parameters ---------- pipeline_module : PypelineModule Pipeline module. Returns ------- NoneType None """ module_name = type(pipeline_module).__name__ str_length = len(module_name) print("\n" + str_length * "-") print(module_name) print(str_length * "-" + "\n") print(f"Module name: {pipeline_module._m_name}") # This function *cannot* be decorated with @typechecked, because the typeguard library checks type # hints at *runtime*, when PypelineModule is not available without causing circular dependencies. def input_info(pipeline_module: "PypelineModule") -> None: """ Function to print information about the input data. Parameters ---------- pipeline_module : PypelineModule Pipeline module. Returns ------- NoneType None """ input_ports = list(pipeline_module._m_input_ports.keys()) if len(input_ports) == 1: input_shape = pipeline_module._m_input_ports[input_ports[0]].get_shape() print(f"Input port: {input_ports[0]} {input_shape}") else: print("Input ports:", end="") for i, item in enumerate(input_ports): input_shape = pipeline_module._m_input_ports[input_ports[i]].get_shape() if i < len(input_ports) - 1: print(f" {item} {input_shape},", end="") else: print(f" {item} {input_shape}") # This function *cannot* be decorated with @typechecked, because the typeguard library checks type # hints at *runtime*, when PypelineModule is not available without causing circular dependencies. def output_info( pipeline_module: "PypelineModule", output_shape: Dict[str, Tuple[int, ...]] ) -> None: """ Function to print information about the output data. Parameters ---------- pipeline_module : PypelineModule Pipeline module. output_shape : dict Dictionary with the output dataset names and shapes. Returns ------- NoneType None """ output_ports = list(pipeline_module._m_output_ports.keys()) if len(output_ports) == 1: if output_ports[0][:11] != "fits_header": print(f"Output port: {output_ports[0]} {output_shape[output_ports[0]]}") else: print("Output ports:", end="") for i, item in enumerate(output_ports): if i < len(output_ports) - 1: print(f" {item} {output_shape[output_ports[i]]},", end="") else: print(f" {item} {output_shape[output_ports[i]]}") PynPoint-PynPoint-e5d0637/pynpoint/util/multiline.py000066400000000000000000000161711512720201400226200ustar00rootroot00000000000000""" Utilities for multiprocessing of lines in time with the poison pill pattern. """ import multiprocessing from typing import Callable, List, Optional import numpy as np from typeguard import typechecked from pynpoint.core.dataio import InputPort, OutputPort from pynpoint.util.multiproc import ( TaskInput, TaskResult, TaskCreator, TaskProcessor, MultiprocessingCapsule, apply_function, ) class LineReader(TaskCreator): """ Reader of task inputs for :class:`~pynpoint.util.multiline.LineProcessingCapsule`. Continuously read all rows of a dataset and puts them into a task queue. """ @typechecked def __init__( self, data_port_in: InputPort, tasks_queue_in: multiprocessing.JoinableQueue, data_mutex_in: multiprocessing.Lock, num_proc: int, data_length: int, ) -> None: """ Parameters ---------- data_port_in : pynpoint.core.dataio.InputPort Input port. tasks_queue_in : multiprocessing.queues.JoinableQueue Tasks queue. data_mutex_in : multiprocessing.synchronize.Lock A mutex shared with the writer to ensure that no read and write operations happen at the same time. num_proc : int Number of processors. data_length : int Length of the processed data. Returns ------- NoneType None """ super(LineReader, self).__init__( data_port_in, tasks_queue_in, data_mutex_in, num_proc ) self.m_data_length = data_length @typechecked def run(self) -> None: """ Returns ------- NoneType None """ n_rows = self.m_data_in_port.get_shape()[1] row_length = int( np.ceil(self.m_data_in_port.get_shape()[1] / float(self.m_num_proc)) ) i = 0 while i < n_rows: j = min((i + row_length), n_rows) # lock mutex and read data with self.m_data_mutex: self.m_data_in_port._check_status_and_activate() tmp_data = self.m_data_in_port[:, i:j, :] # read rows from i to j self.m_data_in_port.close_port() param = ( self.m_data_length, ((None, None, None), (i, j, None), (None, None, None)), ) self.m_task_queue.put(TaskInput(tmp_data, param)) i = j self.create_poison_pills() class LineTaskProcessor(TaskProcessor): """ Processor of task inputs for :class:`~pynpoint.util.multiline.LineProcessingCapsule`. A processor applies a function on a row of lines in time. """ @typechecked def __init__( self, tasks_queue_in: multiprocessing.JoinableQueue, result_queue_in: multiprocessing.JoinableQueue, function: Callable, function_args: Optional[tuple], ) -> None: """ Parameters ---------- tasks_queue_in : multiprocessing.queues.JoinableQueue Tasks queue. result_queue_in : multiprocessing.queues.JoinableQueue Results queue. function : function Input function. function_args : tuple, None Optional function arguments. Returns ------- NoneType None """ super(LineTaskProcessor, self).__init__(tasks_queue_in, result_queue_in) self.m_function = function self.m_function_args = function_args @typechecked def run_job(self, tmp_task: TaskInput) -> TaskResult: """ Parameters ---------- tmp_task : pynpoint.util.multiproc.TaskInput Task input with the subsets of lines and the job parameters. Returns ------- pynpoint.util.multiproc.TaskResult Task result. """ result_arr = np.zeros( ( tmp_task.m_job_parameter[0], tmp_task.m_input_data.shape[1], tmp_task.m_input_data.shape[2], ) ) count = 0 for i in range(tmp_task.m_input_data.shape[1]): for j in range(tmp_task.m_input_data.shape[2]): result_arr[:, i, j] = apply_function( tmp_task.m_input_data[:, i, j], count, self.m_function, self.m_function_args, ) count += 1 return TaskResult(result_arr, tmp_task.m_job_parameter[1]) class LineProcessingCapsule(MultiprocessingCapsule): """ Capsule for parallel processing of lines in time with the poison pill pattern. A function is applied in parallel to each line in time, for example as in :class:`~pynpoint.processing.timedenoising.WaveletTimeDenoisingModule`. """ @typechecked def __init__( self, image_in_port: InputPort, image_out_port: OutputPort, num_proc: int, function: Callable, function_args: Optional[tuple], data_length: int, ) -> None: """ Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort Input port. image_out_port : pynpoint.core.dataio.OutputPort Output port. num_proc : int Number of processors. function : function Input function that is applied to the lines. function_args : tuple, None, optional Function arguments. data_length : int Length of the processed data. Returns ------- NoneType None """ self.m_function = function self.m_function_args = function_args self.m_data_length = data_length super(LineProcessingCapsule, self).__init__( image_in_port, image_out_port, num_proc ) @typechecked def create_processors(self) -> List[LineTaskProcessor]: """ Returns ------- list(pynpoint.util.multiproc.LineTaskProcessor, ) List with instances of :class:`~pynpoint.util.multiproc.LineTaskProcessor` """ processors = [] for _ in range(self.m_num_proc): processors.append( LineTaskProcessor( tasks_queue_in=self.m_tasks_queue, result_queue_in=self.m_result_queue, function=self.m_function, function_args=self.m_function_args, ) ) return processors @typechecked def init_creator(self, image_in_port: InputPort) -> LineReader: """ Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort Input port from where the subsets of lines are read. Returns ------- pynpoint.util.multiline.LineReader Line reader object. """ return LineReader( image_in_port, self.m_tasks_queue, self.m_data_mutex, self.m_num_proc, self.m_data_length, ) PynPoint-PynPoint-e5d0637/pynpoint/util/multipca.py000066400000000000000000000371661512720201400224430ustar00rootroot00000000000000""" Capsule for multiprocessing of the PSF subtraction with PCA. Residuals are created in parallel for a range of principal components for which the PCA basis is required as input. """ import sys import multiprocessing from typing import List, Optional, Tuple, Union import numpy as np from typeguard import typechecked from sklearn.decomposition import PCA from pynpoint.core.dataio import OutputPort from pynpoint.util.multiproc import ( TaskProcessor, TaskCreator, TaskWriter, TaskResult, TaskInput, MultiprocessingCapsule, to_slice, ) from pynpoint.util.postproc import postprocessor from pynpoint.util.residuals import combine_residuals class PcaTaskCreator(TaskCreator): """ The TaskCreator of the PCA multiprocessing. Creates one task for each principal component number. Does not require an input port since the data is directly given to the task processors. """ @typechecked def __init__( self, tasks_queue_in: multiprocessing.JoinableQueue, num_proc: int, pca_numbers: Union[np.ndarray, tuple], ) -> None: """ Parameters ---------- tasks_queue_in : multiprocessing.queues.JoinableQueue Input task queue. num_proc : int Number of processors. pca_numbers : np.ndarray, tuple Principal components for which the residuals are computed. Returns ------- NoneType None """ super(PcaTaskCreator, self).__init__(None, tasks_queue_in, None, num_proc) self.m_pca_numbers = pca_numbers @typechecked def run(self) -> None: """ Run method of PcaTaskCreator. Returns ------- NoneType None """ if isinstance(self.m_pca_numbers, tuple): for i, pca_first in enumerate(self.m_pca_numbers[0]): for j, pca_secon in enumerate(self.m_pca_numbers[1]): parameters = ( ((i, i + 1, None), (j, j + 1, None), (None, None, None)), ) self.m_task_queue.put( TaskInput(tuple((pca_first, pca_secon)), parameters) ) self.create_poison_pills() else: for i, pca_number in enumerate(self.m_pca_numbers): parameters = ( ((i, i + 1, None), (None, None, None), (None, None, None)), ) self.m_task_queue.put(TaskInput(pca_number, parameters)) self.create_poison_pills() class PcaTaskProcessor(TaskProcessor): """ The TaskProcessor of the PCA multiprocessing is the core of the parallelization. An instance of this class will calculate one forward and backward PCA transformation given the pre-trained scikit-learn PCA model. It does not get data from the TaskCreator but uses its own copy of the input data, which are the same and independent for each task. The following residuals can be created: * Mean residuals -- requirements[0] = True * Median residuals -- requirements[1] = True * Noise-weighted residuals -- requirements[2] = True * Clipped mean of the residuals -- requirements[3] = True """ @typechecked def __init__( self, tasks_queue_in: multiprocessing.JoinableQueue, result_queue_in: multiprocessing.JoinableQueue, star_reshape: np.ndarray, angles: np.ndarray, scales: Optional[np.ndarray], pca_model: Optional[PCA], im_shape: tuple, indices: Optional[np.ndarray], requirements: Tuple[bool, bool, bool, bool], processing_type: str, ) -> None: """ Parameters ---------- tasks_queue_in : multiprocessing.queues.JoinableQueue Input task queue. result_queue_in : multiprocessing.queues.JoinableQueue Input result queue. star_reshape : np.ndarray Reshaped (2D) stack of images. angles : np.ndarray Derotation angles (deg). scales : np.ndarray scaling factors pca_model : sklearn.decomposition.pca.PCA PCA object with the basis. im_shape : tuple(int, int, int) Original shape of the stack of images. indices : np.ndarray Non-masked image indices. requirements : tuple(bool, bool, bool, bool) Required output residuals. processing_type : str selected processing type. Returns ------- NoneType None """ super(PcaTaskProcessor, self).__init__(tasks_queue_in, result_queue_in) self.m_star_reshape = star_reshape self.m_pca_model = pca_model self.m_angles = angles self.m_scales = scales self.m_im_shape = im_shape self.m_indices = indices self.m_requirements = requirements self.m_processing_type = processing_type @typechecked def run_job(self, tmp_task: TaskInput) -> TaskResult: """ Run method of PcaTaskProcessor. Parameters ---------- tmp_task : pynpoint.util.multiproc.TaskInput Input task. Returns ------- pynpoint.util.multiproc.TaskResult Output residuals. """ # correct data type of pca_number if necessary if isinstance(tmp_task.m_input_data, tuple): pca_number = tmp_task.m_input_data else: pca_number = int(tmp_task.m_input_data) residuals, res_rot = postprocessor( images=self.m_star_reshape, angles=self.m_angles, scales=self.m_scales, pca_number=pca_number, pca_sklearn=self.m_pca_model, im_shape=self.m_im_shape, indices=self.m_indices, processing_type=self.m_processing_type, ) # differentiate between IFS data or Mono-Wavelength data if res_rot.ndim == 3: res_output = np.zeros((4, res_rot.shape[-2], res_rot.shape[-1])) else: res_output = np.zeros( (4, len(self.m_star_reshape), res_rot.shape[-2], res_rot.shape[-1]) ) if self.m_requirements[0]: res_output[0,] = combine_residuals(method="mean", res_rot=res_rot) if self.m_requirements[1]: res_output[1,] = combine_residuals(method="median", res_rot=res_rot) if self.m_requirements[2]: res_output[2,] = combine_residuals( method="weighted", res_rot=res_rot, residuals=residuals, angles=self.m_angles, ) if self.m_requirements[3]: res_output[3,] = combine_residuals(method="clipped", res_rot=res_rot) sys.stdout.write(".") sys.stdout.flush() return TaskResult(res_output, tmp_task.m_job_parameter[0]) class PcaTaskWriter(TaskWriter): """ The TaskWriter of the PCA parallelization. Four different ports are used to save the results of the task processors (mean, median, weighted, and clipped). """ @typechecked def __init__( self, result_queue_in: multiprocessing.JoinableQueue, mean_out_port: Optional[OutputPort], median_out_port: Optional[OutputPort], weighted_out_port: Optional[OutputPort], clip_out_port: Optional[OutputPort], data_mutex_in: multiprocessing.Lock, requirements: Tuple[bool, bool, bool, bool], ) -> None: """ Constructor of PcaTaskWriter. Parameters ---------- result_queue_in : multiprocessing.queues.JoinableQueue Input result queue. mean_out_port : pynpoint.core.dataio.OutputPort Output port with the mean residuals. Not used if set to None. median_out_port : pynpoint.core.dataio.OutputPort Output port with the median residuals. Not used if set to None. weighted_out_port : pynpoint.core.dataio.OutputPort Output port with the noise-weighted residuals. Not used if set to None. clip_out_port : pynpoint.core.dataio.OutputPort Output port with the clipped mean residuals. Not used if set to None. data_mutex_in : multiprocessing.synchronize.Lock A mutual exclusion variable which ensure that no read and write simultaneously occur. requirements : tuple(bool, bool, bool, bool) Required output residuals. Returns ------- NoneType None """ super(PcaTaskWriter, self).__init__(result_queue_in, None, data_mutex_in) self.m_mean_out_port = mean_out_port self.m_median_out_port = median_out_port self.m_weighted_out_port = weighted_out_port self.m_clip_out_port = clip_out_port self.m_requirements = requirements @typechecked def run(self) -> None: """ Run method of PcaTaskWriter. Writes the residuals to the output ports. Returns ------- NoneType None """ while True: next_result = self.m_result_queue.get() poison_pill_case = self.check_poison_pill(next_result) if poison_pill_case == 1: break if poison_pill_case == 2: continue with self.m_data_mutex: res_slice = to_slice(next_result.m_position) if next_result.m_position[1][0] is None: res_slice = next_result.m_position[0][0] else: res_slice = ( next_result.m_position[0][0], next_result.m_position[1][0], ) if self.m_requirements[0]: self.m_mean_out_port._check_status_and_activate() self.m_mean_out_port[res_slice] = next_result.m_data_array[0] self.m_mean_out_port.close_port() if self.m_requirements[1]: self.m_median_out_port._check_status_and_activate() self.m_median_out_port[res_slice] = next_result.m_data_array[1] self.m_median_out_port.close_port() if self.m_requirements[2]: self.m_weighted_out_port._check_status_and_activate() self.m_weighted_out_port[res_slice] = next_result.m_data_array[2] self.m_weighted_out_port.close_port() if self.m_requirements[3]: self.m_clip_out_port._check_status_and_activate() self.m_clip_out_port[res_slice] = next_result.m_data_array[3] self.m_clip_out_port.close_port() self.m_result_queue.task_done() class PcaMultiprocessingCapsule(MultiprocessingCapsule): """ Capsule for PCA multiprocessing with the poison pill pattern. """ @typechecked def __init__( self, mean_out_port: Optional[OutputPort], median_out_port: Optional[OutputPort], weighted_out_port: Optional[OutputPort], clip_out_port: Optional[OutputPort], num_proc: int, pca_numbers: Union[tuple, np.ndarray], pca_model: Optional[PCA], star_reshape: np.ndarray, angles: np.ndarray, scales: Optional[np.ndarray], im_shape: tuple, indices: Optional[np.ndarray], processing_type: str, ) -> None: """ Constructor of PcaMultiprocessingCapsule. Parameters ---------- mean_out_port : pynpoint.core.dataio.OutputPort Output port for the mean residuals. median_out_port : pynpoint.core.dataio.OutputPort Output port for the median residuals. weighted_out_port : pynpoint.core.dataio.OutputPort Output port for the noise-weighted residuals. clip_out_port : pynpoint.core.dataio.OutputPort Output port for the mean clipped residuals. num_proc : int Number of processors. pca_numbers : np.ndarray Number of principal components. pca_model : sklearn.decomposition.pca.PCA PCA object with the basis. star_reshape : np.ndarray Reshaped (2D) input images. angles : np.ndarray Derotation angles (deg). scales : np.ndarray scaling factors. im_shape : tuple(int, int, int) Original shape of the input images. indices : np.ndarray Non-masked pixel indices. processing_type : str selection of processing type Returns ------- NoneType None """ self.m_mean_out_port = mean_out_port self.m_median_out_port = median_out_port self.m_weighted_out_port = weighted_out_port self.m_clip_out_port = clip_out_port self.m_pca_numbers = pca_numbers self.m_pca_model = pca_model self.m_star_reshape = star_reshape self.m_angles = angles self.m_scales = scales self.m_im_shape = im_shape self.m_indices = indices self.m_processing_type = processing_type self.m_requirements = [False, False, False, False] if self.m_mean_out_port is not None: self.m_requirements[0] = True if self.m_median_out_port is not None: self.m_requirements[1] = True if self.m_weighted_out_port is not None: self.m_requirements[2] = True if self.m_clip_out_port is not None: self.m_requirements[3] = True self.m_requirements = tuple(self.m_requirements) super(PcaMultiprocessingCapsule, self).__init__(None, None, num_proc) @typechecked def create_writer(self, image_out_port: None) -> PcaTaskWriter: """ Method to create an instance of PcaTaskWriter. Parameters ---------- image_out_port : None Output port, not used. Returns ------- pynpoint.util.multipca.PcaTaskWriter PCA task writer. """ return PcaTaskWriter( self.m_result_queue, self.m_mean_out_port, self.m_median_out_port, self.m_weighted_out_port, self.m_clip_out_port, self.m_data_mutex, self.m_requirements, ) @typechecked def init_creator(self, image_in_port: None) -> PcaTaskCreator: """ Method to create an instance of PcaTaskCreator. Parameters ---------- image_in_port : None Input port, not used. Returns ------- pynpoint.util.multipca.PcaTaskCreator PCA task creator. """ return PcaTaskCreator(self.m_tasks_queue, self.m_num_proc, self.m_pca_numbers) @typechecked def create_processors(self) -> List[PcaTaskProcessor]: """ Method to create a list of instances of PcaTaskProcessor. Returns ------- list(pynpoint.util.multipca.PcaTaskProcessor, ) PCA task processors. """ processors = [] for _ in range(self.m_num_proc): processors.append( PcaTaskProcessor( self.m_tasks_queue, self.m_result_queue, self.m_star_reshape, self.m_angles, self.m_scales, self.m_pca_model, self.m_im_shape, self.m_indices, self.m_requirements, self.m_processing_type, ) ) return processors PynPoint-PynPoint-e5d0637/pynpoint/util/multiproc.py000066400000000000000000000401461512720201400226330ustar00rootroot00000000000000""" Abstract interfaces for multiprocessing applications with the poison pill pattern. """ import multiprocessing from sys import platform from typing import Callable, Optional, Union from abc import ABCMeta, abstractmethod import numpy as np from typeguard import typechecked from pynpoint.core.dataio import InputPort, OutputPort # On macOS, the spawn start method is the default since Python 3.8. # The fork start method should be considered unsafe as it can lead # to crashes of the subprocess according to the documentation. # if platform in ['darwin', 'linux']: # multiprocessing.set_start_method('fork') class TaskInput: """ Class for tasks that are processed by the :class:`~pynpoint.util.multiproc.TaskProcessor`. """ @typechecked def __init__( self, input_data: Union[np.ndarray, np.integer, tuple], job_parameter: tuple ) -> None: """ Parameters ---------- input_data : int, float, np.ndarray Input data for by the :class:`~pynpoint.util.multiproc.TaskProcessor`. job_parameter : tuple Additional data or parameters. Returns ------- NoneType None """ self.m_input_data = input_data self.m_job_parameter = job_parameter class TaskResult: """ Class for results that can be stored by the :class:`~pynpoint.util.multiproc.TaskWriter`. """ @typechecked def __init__(self, data_array: np.ndarray, position: tuple) -> None: """ Parameters ---------- data_array : np.ndarray Array with the results for a given position. position : tuple(tuple(int, int, int), tuple(int, int, int), tuple(int, int, int)) The position where the results will be stored. Returns ------- NoneType None """ self.m_data_array = data_array self.m_position = position class TaskCreator(multiprocessing.Process, metaclass=ABCMeta): """ Abstract interface for :class:`~pynpoint.util.multiproc.TaskCreator` classes. A :class:`~pynpoint.util.multiproc.TaskCreator` creates instances of :class:`~pynpoint.util.multiproc.TaskInput`, which will be processed by the :class:`~pynpoint.util.multiproc.TaskProcessor`, and appends them to the central task queue. In general there is only one :class:`~pynpoint.util.multiproc.TaskCreator` running for a poison pill multiprocessing application. A :class:`~pynpoint.util.multiproc.TaskCreator` communicates with to the :class:`~pynpoint.util.multiproc.TaskWriter` in order to avoid simultaneously access to the central database. """ @typechecked def __init__( self, data_port_in: Optional[InputPort], tasks_queue_in: multiprocessing.JoinableQueue, data_mutex_in: Optional[multiprocessing.Lock], num_proc: int, ) -> None: """ Parameters ---------- data_port_in : pynpoint.core.dataio.InputPort, None An input port which links to the data that has to be processed. tasks_queue_in : multiprocessing.queues.JoinableQueue The central task queue. data_mutex_in : multiprocessing.synchronize.Lock, None A mutex shared with the writer to ensure that no read and write operations happen at the same time. num_proc : int Maximum number of instances of :class:`~pynpoint.util.multiproc.TaskProcessor` that run simultaneously. Returns ------- NoneType None """ multiprocessing.Process.__init__(self) self.m_data_in_port = data_port_in self.m_task_queue = tasks_queue_in self.m_data_mutex = data_mutex_in self.m_num_proc = num_proc @typechecked def create_poison_pills(self) -> None: """ Creates poison pills for the :class:`~pynpoint.util.multiproc.TaskProcessor` and :class:`~pynpoint.util.multiproc.TaskWriter`. A process will shut down if it receives a poison pill as a new task. This method should be executed at the end of the :func:`~pynpoint.util.multiproc.TaskCreator.run` method. Returns ------- NoneType None """ for _ in range(self.m_num_proc - 1): # poison pills self.m_task_queue.put(1) # final poison pill self.m_task_queue.put(None) @abstractmethod @typechecked def run(self) -> None: """ Creates objects of the :class:`~pynpoint.util.multiproc.TaskInput` until all tasks are placed in the task queue. Returns ------- NoneType None """ class TaskProcessor(multiprocessing.Process, metaclass=ABCMeta): """ Abstract interface for :class:`~pynpoint.util.multiproc.TaskProcessor` classes. The number of instances of :class:`~pynpoint.util.multiproc.TaskProcessor` that run simultaneously in a poison pill multiprocessing application can be set with ``CPU`` parameter in the central configuration file. A :class:`~pynpoint.util.multiproc.TaskProcessor` takes tasks from a task queue, processes the task, and stores the results back into a result queue. The process will shut down if the next task is a poison pill. The order in which process finish is not fixed. """ @typechecked def __init__( self, tasks_queue_in: multiprocessing.JoinableQueue, result_queue_in: multiprocessing.JoinableQueue, ) -> None: """ Parameters ---------- tasks_queue_in : multiprocessing.queues.JoinableQueue The input task queue with instances of :class:`~pynpoint.util.multiproc.TaskInput`. result_queue_in : multiprocessing.queues.JoinableQueue The result task queue with instances of :class:`~pynpoint.util.multiproc.TaskResult`. Returns ------- NoneType None """ multiprocessing.Process.__init__(self) self.m_task_queue = tasks_queue_in self.m_result_queue = result_queue_in @typechecked def check_poison_pill(self, next_task: Union[TaskInput, int, None]) -> bool: """ Function to check if the next task is a poison pill. Parameters ---------- next_task : int, None, pynpoint.util.multiproc.TaskInput The next task. Returns ------- bool True if the next task is a poison pill, False otherwise. """ if next_task == 1: # poison pill poison_pill = True self.m_task_queue.task_done() elif next_task is None: # final poison pill poison_pill = True # shut down writer process self.m_result_queue.put(None) self.m_task_queue.task_done() else: # no poison pill poison_pill = False return poison_pill @typechecked def run(self) -> None: """ Run method to start the :class:`~pynpoint.util.multiproc.TaskProcessor`. The run method will continue to process tasks from the input task queue until it receives a poison pill. Returns ------- NoneType None """ while True: next_task = self.m_task_queue.get() if self.check_poison_pill(next_task): break result = self.run_job(next_task) self.m_task_queue.task_done() self.m_result_queue.put(result) @abstractmethod @typechecked def run_job(self, tmp_task: TaskInput) -> None: """ Abstract interface for the :func:`~pynpoint.util.multiproc.TaskProcessor.run_job` method which is called from the :func:`~pynpoint.util.multiproc.TaskProcessor.run` method for each task individually. Parameters ---------- tmp_task : pynpoint.util.multiproc.TaskInput Input task. Returns ------- NoneType None """ class TaskWriter(multiprocessing.Process): """ The :class:`~pynpoint.util.multiproc.TaskWriter` receives results from the result queue, which have been computed by a :class:`~pynpoint.util.multiproc.TaskProcessor`, and stores the results in the central database. The position parameter of the :class:`~pynpoint.util.multiproc.TaskResult` is used to slice the result to the correct position in the complete output dataset. """ @typechecked def __init__( self, result_queue_in: multiprocessing.JoinableQueue, data_out_port_in: Optional[OutputPort], data_mutex_in: multiprocessing.Lock, ) -> None: """ Parameters ---------- result_queue_in : multiprocessing.queues.JoinableQueue The result queue. data_out_port_in : pynpoint.core.dataio.OutputPort, None The output port where the results will be stored. data_mutex_in : multiprocessing.synchronize.Lock A mutex that is shared with the :class:`~pynpoint.util.multiproc.TaskWriter` which ensures that read and write operations to the database do not occur simultaneously. Returns ------- NoneType None """ multiprocessing.Process.__init__(self) self.m_result_queue = result_queue_in self.m_data_mutex = data_mutex_in self.m_data_out_port = data_out_port_in @typechecked def check_poison_pill(self, next_result: Union[TaskResult, None]) -> int: """ Function to check if the next result is a poison pill. Parameters ---------- next_result : None, pynpoint.util.multiproc.TaskResult The next result. Returns ------- int 0 -> no poison pill, 1 -> poison pill, 2 -> poison pill but still results in the queue (rare error case). """ if next_result is None: # check if there are results after the poison pill if self.m_result_queue.empty(): poison_pill = 1 # shut down the writer self.m_result_queue.task_done() else: poison_pill = 2 # put back the poison pill for the moment self.m_result_queue.put(None) self.m_result_queue.task_done() else: poison_pill = 0 return poison_pill @typechecked def run(self) -> None: """ Run method of the :class:`~pynpoint.util.multiproc.TaskWriter`. It is called once when it has to start storing the results until it receives a poison pill. Returns ------- NoneType None """ while True: next_result = self.m_result_queue.get() poison_pill_case = self.check_poison_pill(next_result) if poison_pill_case == 1: break if poison_pill_case == 2: continue with self.m_data_mutex: self.m_data_out_port._check_status_and_activate() self.m_data_out_port[to_slice(next_result.m_position)] = ( next_result.m_data_array ) self.m_data_out_port.close_port() self.m_result_queue.task_done() class MultiprocessingCapsule(metaclass=ABCMeta): """ Abstract interface for multiprocessing capsules based on the poison pill pattern. """ @typechecked def __init__( self, image_in_port: Optional[InputPort], image_out_port: Optional[OutputPort], num_proc: int, ) -> None: """ Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort, None Port to the input data. image_out_port : pynpoint.core.dataio.OutputPort, None Port to the place where the output data will be stored. num_proc : int Number of task processors. Returns ------- NoneType None """ # buffer twice the data as processes are available self.m_tasks_queue = multiprocessing.JoinableQueue(maxsize=num_proc) self.m_result_queue = multiprocessing.JoinableQueue(maxsize=num_proc) self.m_num_proc = num_proc # database mutex self.m_data_mutex = multiprocessing.Lock() # create reader self.m_creator = self.init_creator(image_in_port) # create processors self.m_task_processors = self.create_processors() # create writer self.m_writer = self.create_writer(image_out_port) @abstractmethod @typechecked def create_processors(self) -> None: """ Function that is called from the constructor to create a list of instances of :class:`~pynpoint.util.multiproc.TaskProcessor`. Returns ------- NoneType None """ @abstractmethod @typechecked def init_creator(self, image_in_port: Optional[InputPort]) -> None: """ Function that is called from the constructor to create a :class:`~pynpoint.util.multiproc.TaskCreator`. Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort, None Input port for the task creator. Returns ------- NoneType None """ @typechecked def create_writer(self, image_out_port: Optional[OutputPort]) -> TaskWriter: """ Function that is called from the constructor to create the :class:`~pynpoint.util.multiproc.TaskWriter`. Parameters ---------- image_out_port : pynpoint.core.dataio.OutputPort, None Output port for the creator. Returns ------- pynpoint.util.multiproc.TaskWriter Task writer. """ return TaskWriter(self.m_result_queue, image_out_port, self.m_data_mutex) @typechecked def run(self) -> None: """ Run method that starts the :class:`~pynpoint.util.multiproc.TaskCreator`, the instances of :class:`~pynpoint.util.multiproc.TaskProcessor`, and the :class:`~pynpoint.util.multiproc.TaskWriter`. They will be shut down when all tasks have finished. Returns ------- NoneType None """ # start all processes self.m_creator.start() for processor in self.m_task_processors: processor.start() self.m_writer.start() # wait for all tasks to have finished self.m_tasks_queue.join() self.m_result_queue.join() # clean up the processes for processor in self.m_task_processors: processor.join() self.m_writer.join() self.m_creator.join() @typechecked def apply_function( tmp_data: np.ndarray, data_index: int, func: Callable, func_args: Optional[tuple] ) -> np.ndarray: """ Apply a function with optional arguments to the input data. Parameters ---------- tmp_data : np.ndarray Input data. data_index : int Index of the data subset. When processing a stack of images, the argument of ``data_index`` is the image index in the full stack. func : function Function. func_args : tuple, None Function arguments. Returns ------- np.ndarray The results of the function. """ if func_args is None: result = np.array(func(tmp_data, data_index)) else: result = np.array(func(tmp_data, data_index, *func_args)) return result @typechecked def to_slice(tuple_slice: tuple) -> tuple: """ Function to convert tuples into slices for a multiprocessing queue. Parameters ---------- tuple_slice : tuple Tuple to be converted into a slice. Returns ------- tuple(slice, slice, slice) Tuple with three slices. """ slices = [] for item in tuple_slice: # slice(start, stop step) slices.append(slice(item[0], item[1], item[2])) return tuple(slices) PynPoint-PynPoint-e5d0637/pynpoint/util/multistack.py000066400000000000000000000202031512720201400227650ustar00rootroot00000000000000""" Utilities for multiprocessing of stacks of images. """ import sys import multiprocessing from typing import Callable, List, Optional import numpy as np from typeguard import typechecked from pynpoint.core.dataio import InputPort, OutputPort from pynpoint.util.module import update_arguments from pynpoint.util.multiproc import ( TaskInput, TaskResult, TaskCreator, TaskProcessor, MultiprocessingCapsule, apply_function, ) class StackReader(TaskCreator): """ Reader of task inputs for :class:`~pynpoint.util.multistack.StackProcessingCapsule`. Reads continuously stacks of images of a dataset and puts them into a task queue. """ @typechecked def __init__( self, data_port_in: InputPort, tasks_queue_in: multiprocessing.JoinableQueue, data_mutex_in: multiprocessing.Lock, num_proc: int, stack_size: int, result_shape: tuple, ) -> None: """ Parameters ---------- data_port_in : pynpoint.core.dataio.InputPort Input port. tasks_queue_in : multiprocessing.queues.JoinableQueue Tasks queue. data_mutex_in : multiprocessing.synchronize.Lock A mutex shared with the writer to ensure that no read and write operations happen at the same time. num_proc : int Number of processors. stack_size: int Number of images per stack. result_shape : tuple(int, ) Shape of the array with the output results (usually a stack of images). Returns ------- NoneType None """ super(StackReader, self).__init__( data_port_in, tasks_queue_in, data_mutex_in, num_proc ) self.m_stack_size = stack_size self.m_result_shape = result_shape @typechecked def run(self) -> None: """ Returns ------- NoneType None """ with self.m_data_mutex: self.m_data_in_port._check_status_and_activate() nimages = self.m_data_in_port.get_shape()[0] self.m_data_in_port.close_port() i = 0 while i < nimages: j = min((i + self.m_stack_size), nimages) # lock mutex and read data with self.m_data_mutex: self.m_data_in_port._check_status_and_activate() tmp_data = self.m_data_in_port[i:j,] # read images from i to j self.m_data_in_port.close_port() # first dimension (start, stop, step) stack_slice = [(i, j, None)] # additional dimensions for _ in self.m_result_shape: stack_slice.append((None, None, None)) param = (self.m_result_shape, tuple(stack_slice)) self.m_task_queue.put(TaskInput(tmp_data, param)) i = j self.create_poison_pills() class StackTaskProcessor(TaskProcessor): """ Processor of task inputs for :class:`~pynpoint.util.multistack.StackProcessingCapsule`. A processor applies a function on a stack of images. """ @typechecked def __init__( self, tasks_queue_in: multiprocessing.JoinableQueue, result_queue_in: multiprocessing.JoinableQueue, function: Callable, function_args: Optional[tuple], nimages: int, ) -> None: """ Parameters ---------- tasks_queue_in : multiprocessing.queues.JoinableQueue Tasks queue. result_queue_in : multiprocessing.queues.JoinableQueue Results queue. function : function Input function that is applied to the images. function_args : tuple, None Function arguments. nimages : int Total number of images. Returns ------- NoneType None """ super(StackTaskProcessor, self).__init__(tasks_queue_in, result_queue_in) self.m_function = function self.m_function_args = function_args self.m_nimages = nimages @typechecked def run_job(self, tmp_task: TaskInput) -> TaskResult: """ Parameters ---------- tmp_task : pynpoint.util.multiproc.TaskInput Task input with the subsets of images and the job parameters. Returns ------- pynpoint.util.multiproc.TaskResult Task result. """ result_nimages = tmp_task.m_input_data.shape[0] result_shape = tmp_task.m_job_parameter[0] # first dimension full_shape = [result_nimages] # additional dimensions for item in result_shape: full_shape.append(item) result_arr = np.zeros(full_shape) for i in range(result_nimages): # job parameter contains (result_shape, tuple(stack_slice)) index = tmp_task.m_job_parameter[1][0][0] + i args = update_arguments(index, self.m_nimages, self.m_function_args) result_arr[i,] = apply_function( tmp_task.m_input_data[i,], i, self.m_function, args ) sys.stdout.write(".") sys.stdout.flush() return TaskResult(result_arr, tmp_task.m_job_parameter[1]) class StackProcessingCapsule(MultiprocessingCapsule): """ Capsule for parallel processing of stacks of images with the poison pill pattern. A function is applied in parallel to each stack of images. """ @typechecked def __init__( self, image_in_port: InputPort, image_out_port: OutputPort, num_proc: int, function: Callable, function_args: Optional[tuple], stack_size: int, result_shape: tuple, nimages: int, ) -> None: """ Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort Input port. image_out_port : pynpoint.core.dataio.OutputPort Output port. num_proc : int Number of processors. function : function Input function. function_args : tuple, None Function arguments. stack_size: int Number of images per stack. result_shape : tuple(int, ) Shape of the array with output results (usually a stack of images). nimages : int Total number of images. Returns ------- NoneType None """ self.m_function = function self.m_function_args = function_args self.m_stack_size = stack_size self.m_result_shape = result_shape self.m_nimages = nimages super(StackProcessingCapsule, self).__init__( image_in_port, image_out_port, num_proc ) @typechecked def create_processors(self) -> List[StackTaskProcessor]: """ Returns ------- list(pynpoint.util.multiproc.StackTaskProcessor, ) List with instances of :class:`~pynpoint.util.multiproc.StackTaskProcessor`. """ processors = [] for _ in range(self.m_num_proc): processors.append( StackTaskProcessor( tasks_queue_in=self.m_tasks_queue, result_queue_in=self.m_result_queue, function=self.m_function, function_args=self.m_function_args, nimages=self.m_nimages, ) ) return processors @typechecked def init_creator(self, image_in_port: InputPort) -> StackReader: """ Parameters ---------- image_in_port : pynpoint.core.dataio.InputPort Input port from where the subsets of images are read. Returns ------- pynpoint.util.multistack.StackReader Reader of stacks of images. """ return StackReader( data_port_in=image_in_port, tasks_queue_in=self.m_tasks_queue, data_mutex_in=self.m_data_mutex, num_proc=self.m_num_proc, stack_size=self.m_stack_size, result_shape=self.m_result_shape, ) PynPoint-PynPoint-e5d0637/pynpoint/util/postproc.py000066400000000000000000000146031512720201400224650ustar00rootroot00000000000000""" Functions for post-processing. """ from typing import Union, Optional, Tuple import numpy as np from typeguard import typechecked from sklearn.decomposition import PCA from pynpoint.util.psf import pca_psf_subtraction from pynpoint.util.sdi import sdi_scaling @typechecked def postprocessor( images: np.ndarray, angles: np.ndarray, scales: Optional[np.ndarray], pca_number: Union[ int, Tuple[Union[int, np.int32, np.int64], Union[int, np.int32, np.int64]] ], pca_sklearn: Optional[PCA] = None, im_shape: Union[None, tuple] = None, indices: Optional[np.ndarray] = None, mask: Optional[np.ndarray] = None, processing_type: str = "ADI", ): """ Function to apply different kind of post processings. It is equivalent to :func:`~pynpoint.util.psf.pca_psf_subtraction` if ``processing_type='ADI'` and ``mask=None``. Parameters ---------- images : np.array Input images which should be reduced. angles : np.ndarray Derotation angles (deg). scales : np.array Scaling factors pca_number : tuple(int, int) Number of principal components used for the PSF subtraction. pca_sklearn : sklearn.decomposition.pca.PCA, None PCA object with the basis if not set to None. im_shape : tuple(int, int, int), None Original shape of the stack with images. Required if ``pca_sklearn`` is not set to None. indices : np.ndarray, None Non-masked image indices. All pixels are used if set to None. mask : np.ndarray Mask (2D). processing_type : str Post-processing type: - ADI: Angular differential imaging. - SDI: Spectral differential imaging. - SDI+ADI: Spectral and angular differential imaging. - ADI+SDI: Angular and spectral differential imaging. Returns ------- np.ndarray Residuals of the PSF subtraction. np.ndarray Derotated residuals of the PSF subtraction. """ if not isinstance(pca_number, tuple): pca_number = (pca_number, -1) if mask is None: mask = np.ones(images.shape[-2:]) res_raw = np.zeros(images.shape) res_rot = np.zeros(images.shape) if processing_type == "ADI": if images.ndim == 2: res_raw, res_rot = pca_psf_subtraction( images=images * mask, angles=angles, scales=None, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) elif images.ndim == 4: for i in range(images.shape[0]): res_raw[i,], res_rot[i,] = pca_psf_subtraction( images=images[i,] * mask, angles=angles, scales=None, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) elif processing_type == "SDI": for i in range(images.shape[1]): im_scaled = sdi_scaling(images[:, i, :, :], scales) res_raw[:, i], res_rot[:, i] = pca_psf_subtraction( images=im_scaled * mask, angles=np.full(scales.size, angles[i]), scales=scales, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) elif processing_type == "SDI+ADI": # SDI res_raw_int = np.zeros(res_raw.shape) for i in range(images.shape[1]): im_scaled = sdi_scaling(images[:, i], scales) res_raw_int[:, i], _ = pca_psf_subtraction( images=im_scaled * mask, angles=None, scales=scales, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) # ADI for i in range(images.shape[0]): res_raw[i], res_rot[i] = pca_psf_subtraction( images=res_raw_int[i] * mask, angles=angles, scales=None, pca_number=pca_number[1], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) elif processing_type == "ADI+SDI": # ADI res_raw_int = np.zeros(res_raw.shape) for i in range(images.shape[0]): res_raw_int[i], _ = pca_psf_subtraction( images=images[i,] * mask, angles=None, scales=None, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) # SDI for i in range(images.shape[1]): im_scaled = sdi_scaling(res_raw_int[:, i], scales) res_raw[:, i], res_rot[:, i] = pca_psf_subtraction( images=im_scaled * mask, angles=np.full(scales.size, angles[i]), scales=scales, pca_number=pca_number[1], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) elif processing_type == "CODI": # flatten images from 4D to 3D ims = images.shape im_scaled_flat = np.zeros((ims[0] * ims[1], ims[2], ims[3])) scales_flat = np.zeros((ims[0] * ims[1])) angles_flat = np.zeros((ims[0] * ims[1])) for i in range(ims[1]): im_scaled_flat[i * ims[0] : (i + 1) * ims[0]] = sdi_scaling( images[:, i], scales ) scales_flat[i * ims[0] : (i + 1) * ims[0]] = scales angles_flat[i * ims[0] : (i + 1) * ims[0]] = angles[i] # codi res_raw_flat, res_rot_flat = pca_psf_subtraction( images=im_scaled_flat * mask, angles=angles_flat, scales=scales_flat, pca_number=pca_number[0], pca_sklearn=pca_sklearn, im_shape=im_shape, indices=indices, ) # inflate images from 3D to 4D for i in range(ims[1]): res_raw[:, i] = res_raw_flat[i * ims[0] : (i + 1) * ims[0]] res_rot[:, i] = res_rot_flat[i * ims[0] : (i + 1) * ims[0]] return res_raw, res_rot PynPoint-PynPoint-e5d0637/pynpoint/util/psf.py000066400000000000000000000134261512720201400214060ustar00rootroot00000000000000""" Functions for PSF subtraction. """ from typing import Optional, Union, Tuple import numpy as np from scipy.ndimage import rotate from sklearn.decomposition import PCA from typeguard import typechecked from pynpoint.util.image import scale_image, shift_image @typechecked def pca_psf_subtraction( images: np.ndarray, angles: Optional[np.ndarray], pca_number: Union[int, np.int32, np.int64], scales: Optional[np.ndarray] = None, pca_sklearn: Optional[PCA] = None, im_shape: Optional[tuple] = None, indices: Optional[np.ndarray] = None, ) -> Tuple[np.ndarray, np.ndarray]: """ Function for PSF subtraction with PCA. Parameters ---------- images : np.ndarray Stack of images. Also used as reference images if ```pca_sklearn``` is set to None. The data should have the original 3D shape if ``pca_sklearn`` is set to None or it should be in a 2D reshaped format if ``pca_sklearn`` is not set to None. angles : np.ndarray Parallactic angles (deg). pca_number : int Number of principal components. scales : np.ndarray, None Scaling factors for SDI. Not used if set to None. pca_sklearn : sklearn.decomposition.pca.PCA, None PCA object with the principal components. im_shape : tuple(int, int, int), None The original 3D shape of the stack with images. Only required if ``pca_sklearn`` is not set to None. indices : np.ndarray, None Array with the indices of the pixels that are used for the PSF subtraction. All pixels are used if set to None. Returns ------- np.ndarray Residuals of the PSF subtraction. np.ndarray Derotated residuals of the PSF subtraction. """ if pca_sklearn is None: # Create a PCA object if not provided as argument pca_sklearn = PCA(n_components=pca_number, svd_solver="arpack") # The 3D shape of the array with images im_shape = images.shape if indices is None: # Select the first image and get the unmasked image indices im_star = images[0,].reshape(-1) indices = np.where(im_star != 0.0)[0] # Reshape the images and select the unmasked pixels im_reshape = images.reshape(im_shape[0], im_shape[1] * im_shape[2]) im_reshape = im_reshape[:, indices] # Subtract the mean image # This is also done by sklearn.decomposition.PCA.fit() im_reshape -= np.mean(im_reshape, axis=0) # Fit the principal components pca_sklearn.fit(im_reshape) else: # If the PCA object is already there then so are the reshaped data im_reshape = np.copy(images) # Project the data on the principal components # Note that this is the same as sklearn.decomposition.PCA.transform() # It is harcoded because the number of components has been adjusted pca_rep = np.matmul(pca_sklearn.components_[:pca_number], im_reshape.T) # The zeros are added with vstack to account for the components that have not been used for the # transformation to the lower-dimensional space, while they were initiated with the PCA object. # Since inverse_transform uses the number of initial components, the zeros are added for # components > pca_number. These components do not impact the inverse transformation. zeros = np.zeros((pca_sklearn.n_components - pca_number, im_reshape.shape[0])) pca_rep = np.vstack((pca_rep, zeros)).T # Transform the data back to the original space psf_model = pca_sklearn.inverse_transform(pca_rep) # Create an array with the original shape residuals = np.zeros((im_shape[0], im_shape[1] * im_shape[2])) # Select all pixel indices if set to None if indices is None: indices = np.arange(0, im_reshape.shape[1], 1) # Subtract the PSF model residuals[:, indices] = im_reshape - psf_model # Reshape the residuals to the original shape residuals = residuals.reshape(im_shape) # ----------- back scale images scal_cor = np.zeros(residuals.shape) if scales is not None: # check if the number of parang is equal to the number of images if residuals.shape[0] != scales.shape[0]: raise ValueError( f"The number of images ({residuals.shape[0]}) is not equal to the " f"number of wavelengths ({scales.shape[0]})." ) for i, _ in enumerate(scales): # rescaling the images swaps = scale_image(residuals[i,], 1 / scales[i], 1 / scales[i]) npix_del = scal_cor.shape[-1] - swaps.shape[-1] if npix_del == 0: scal_cor[i,] = swaps else: if npix_del % 2 == 0: npix_del_a = int(npix_del / 2) npix_del_b = int(npix_del / 2) else: npix_del_a = int((npix_del - 1) / 2) npix_del_b = int((npix_del + 1) / 2) scal_cor[i, npix_del_a:-npix_del_b, npix_del_a:-npix_del_b] = swaps if npix_del % 2 == 1: scal_cor[i,] = shift_image( scal_cor[i,], (0.5, 0.5), interpolation="spline" ) else: scal_cor = residuals res_rot = np.zeros(residuals.shape) if angles is not None: # Check if the number of parang is equal to the number of images if residuals.shape[0] != angles.shape[0]: raise ValueError( f"The number of images ({residuals.shape[0]}) is not equal to the " f"number of parallactic angles ({angles.shape[0]})." ) for j, item in enumerate(angles): res_rot[j,] = rotate(scal_cor[j,], item, reshape=False) else: res_rot = scal_cor return scal_cor, res_rot PynPoint-PynPoint-e5d0637/pynpoint/util/remove.py000066400000000000000000000116121512720201400221060ustar00rootroot00000000000000""" Functions to write selected data and attributes to the database. """ import time from typing import Optional, Union import numpy as np from typeguard import typechecked from pynpoint.core.dataio import InputPort, OutputPort from pynpoint.util.module import progress, memory_frames @typechecked def write_selected_data( memory: Union[int, np.int64], indices: np.ndarray, image_in_port: InputPort, selected_out_port: Optional[OutputPort], removed_out_port: Optional[OutputPort], ) -> None: """ Function to write the selected and removed data. Parameters ---------- memory : int Number of images that is simultaneously loaded into the memory. indices : numpy.ndarray Image indices that will be removed. image_in_port : pynpoint.core.dataio.InputPort Port to the input images. selected_out_port : pynpoint.core.dataio.OutputPort, None Port to store the selected images. No data is written if set to None. removed_out_port : pynpoint.core.dataio.OutputPort, None Port to store the removed images. No data is written if set to None. Returns ------- NoneType None """ nimages = image_in_port.get_shape()[0] frames = memory_frames(memory, nimages) if memory == 0 or memory >= nimages: memory = nimages start_time = time.time() for i, _ in enumerate(frames[:-1]): progress(i, len(frames[:-1]), "Writing selected data...", start_time) images = image_in_port[frames[i] : frames[i + 1],] subset_del = np.where( np.logical_and(indices >= frames[i], indices < frames[i + 1]) )[0] index_del = indices[subset_del] % memory index_sel = np.ones(images.shape[0], bool) index_sel[index_del] = False if selected_out_port is not None and index_sel.size > 0: selected_out_port.append(images[index_sel]) if removed_out_port is not None and index_del.size > 0: removed_out_port.append(images[index_del]) @typechecked def write_selected_attributes( indices: np.ndarray, image_in_port: InputPort, selected_out_port: Optional[OutputPort], removed_out_port: Optional[OutputPort], module_type: str, history: str, ) -> None: """ Function to write the attributes of the selected and removed data. Parameters ---------- indices : numpy.ndarray Image indices that will be removed. image_in_port : pynpoint.core.dataio.InputPort Port to the input data. selected_out_port : pynpoint.core.dataio.OutputPort, None Port to store the attributes of the selected images. Not written if set to None. removed_out_port : pynpoint.core.dataio.OutputPort, None Port to store the attributes of the removed images. Not written if set to None. module_type : str history : str Returns ------- NoneType None """ if selected_out_port is not None: # First copy the existing attributes to the selected_out_port selected_out_port.copy_attributes(image_in_port) selected_out_port.add_history(module_type, history) if removed_out_port is not None: # First copy the existing attributes to the removed_out_port removed_out_port.copy_attributes(image_in_port) removed_out_port.add_history(module_type, history) non_static = image_in_port.get_all_non_static_attributes() index_sel = np.ones(image_in_port.get_shape()[0], bool) index_sel[indices] = False for i, attr_item in enumerate(non_static): values = image_in_port.get_attribute(attr_item) if values.shape[0] == image_in_port.get_shape()[0]: if selected_out_port is not None and index_sel.size > 0: selected_out_port.add_attribute( attr_item, values[index_sel], static=False ) if removed_out_port is not None and indices.size > 0: removed_out_port.add_attribute(attr_item, values[indices], static=False) if "NFRAMES" in non_static: nframes = image_in_port.get_attribute("NFRAMES") nframes_sel = np.zeros(nframes.shape, dtype=int) nframes_del = np.zeros(nframes.shape, dtype=int) for i, frames in enumerate(nframes): if indices.size == 0: nframes_sel[i] = frames nframes_del[i] = 0 else: sum_n = np.sum(nframes[:i]) index_del = np.where( np.logical_and(indices >= sum_n, indices < sum_n + frames) )[0] nframes_sel[i] = frames - index_del.size nframes_del[i] = index_del.size if selected_out_port is not None: selected_out_port.add_attribute("NFRAMES", nframes_sel, static=False) if removed_out_port is not None: removed_out_port.add_attribute("NFRAMES", nframes_del, static=False) PynPoint-PynPoint-e5d0637/pynpoint/util/residuals.py000066400000000000000000000110751512720201400226070ustar00rootroot00000000000000""" Functions for combining the residuals of the PSF subtraction. """ from typing import Optional import numpy as np from typeguard import typechecked from scipy.ndimage import rotate @typechecked def combine_residuals( method: str, res_rot: np.ndarray, residuals: Optional[np.ndarray] = None, angles: Optional[np.ndarray] = None, ) -> np.ndarray: """ Wavelength wrapper for the combine_residual function. Produces an array with either 1 or number of wavelengths sized array. Parameters ---------- method : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). res_rot : np.ndarray Derotated residuals of the PSF subtraction (3D). residuals : np.ndarray, None Non-derotated residuals of the PSF subtraction (3D). Only required for the noise-weighted residuals. angles : np.ndarray, None Derotation angles (deg). Only required for the noise-weighted residuals. Returns ------- np.ndarray Collapsed residuals (3D). """ if res_rot.ndim == 3: output = _residuals( method=method, res_rot=np.asarray(res_rot), residuals=residuals, angles=angles, ) if res_rot.ndim == 4: output = np.zeros((res_rot.shape[0], res_rot.shape[2], res_rot.shape[3])) for i in range(res_rot.shape[0]): if residuals is None: output[i,] = _residuals( method=method, res_rot=res_rot[i,], residuals=residuals, angles=angles, )[0] else: output[i,] = _residuals( method=method, res_rot=res_rot[i,], residuals=residuals[i,], angles=angles, )[0] return output @typechecked def _residuals( method: str, res_rot: np.ndarray, residuals: Optional[np.ndarray] = None, angles: Optional[np.ndarray] = None, ) -> np.ndarray: """ Function for combining the derotated residuals of the PSF subtraction. Parameters ---------- method : str Method used for combining the residuals ('mean', 'median', 'weighted', or 'clipped'). res_rot : np.ndarray Derotated residuals of the PSF subtraction (3D). residuals : np.ndarray, None Non-derotated residuals of the PSF subtraction (3D). Only required for the noise-weighted residuals. angles : np.ndarray, None Derotation angles (deg). Only required for the noise-weighted residuals. Returns ------- np.ndarray Combined residuals (3D). """ if method == "mean": stack = np.mean(res_rot, axis=0) elif method == "median": stack = np.median(res_rot, axis=0) elif method == "weighted": tmp_res_var = np.var(residuals, axis=0) res_repeat = np.repeat( tmp_res_var[np.newaxis, :, :], repeats=residuals.shape[0], axis=0 ) res_var = np.zeros(res_repeat.shape) for j, angle in enumerate(angles): # scipy.ndimage.rotate rotates in clockwise direction for positive angles res_var[j,] = rotate(input=res_repeat[j,], angle=angle, reshape=False) weight1 = np.divide( res_rot, res_var, out=np.zeros_like(res_var), where=(np.abs(res_var) > 1e-100) & (res_var != np.nan), ) weight2 = np.divide( 1.0, res_var, out=np.zeros_like(res_var), where=(np.abs(res_var) > 1e-100) & (res_var != np.nan), ) sum1 = np.sum(weight1, axis=0) sum2 = np.sum(weight2, axis=0) stack = np.divide( sum1, sum2, out=np.zeros_like(sum2), where=(np.abs(sum2) > 1e-100) & (sum2 != np.nan), ) elif method == "clipped": stack = np.zeros(res_rot.shape[-2:]) for i in range(stack.shape[0]): for j in range(stack.shape[1]): pix_line = res_rot[:, i, j] if np.var(pix_line) > 0.0: no_mean = pix_line - np.mean(pix_line) part1 = no_mean.compress( (no_mean < 3.0 * np.sqrt(np.var(no_mean))).flat ) part2 = part1.compress( (part1 > -3.0 * np.sqrt(np.var(no_mean))).flat ) stack[i, j] = np.mean(pix_line) + np.mean(part2) return stack[np.newaxis, ...] PynPoint-PynPoint-e5d0637/pynpoint/util/sdi.py000066400000000000000000000037171512720201400213770ustar00rootroot00000000000000""" Functions for spectral differential imaging. """ import numpy as np from typeguard import typechecked from pynpoint.util.image import scale_image, shift_image @typechecked def sdi_scaling(image_in: np.ndarray, scaling: np.ndarray) -> np.ndarray: """ Function to rescale the images by their wavelength ratios. Parameters ---------- image_in : np.ndarray Data to rescale scaling : np.ndarray Scaling factors. Returns ------- np.ndarray Rescaled images with the same shape as ``image_in``. """ if image_in.shape[0] != scaling.shape[0]: raise ValueError( "The number of wavelengths is not equal to the number of available " "scaling factors." ) image_out = np.zeros(image_in.shape) for i in range(image_in.shape[0]): swaps = scale_image(image_in[i,], scaling[i], scaling[i]) npix_del = swaps.shape[-1] - image_out.shape[-1] if npix_del == 0: image_out[i,] = swaps else: if npix_del % 2 == 0: npix_del_a = int(npix_del / 2) npix_del_b = int(npix_del / 2) else: npix_del_a = int((npix_del - 1) / 2) npix_del_b = int((npix_del + 1) / 2) image_out[i,] = swaps[npix_del_a:-npix_del_b, npix_del_a:-npix_del_b] if npix_del % 2 == 1: image_out[i,] = shift_image( image_out[i,], (-0.5, -0.5), interpolation="spline" ) return image_out @typechecked def scaling_factors(wavelengths: np.ndarray) -> np.ndarray: """ Function to calculate the scaling factors for SDI. Parameters ---------- wavelengths : np.ndarray Array with the wavelength of each frame. Returns ------- np.ndarray Scaling factors. """ return max(wavelengths) / wavelengths PynPoint-PynPoint-e5d0637/pynpoint/util/star.py000066400000000000000000000071151512720201400215650ustar00rootroot00000000000000""" Functions for stellar extraction. """ import math import time from typing import Optional, Tuple, Union import cv2 import numpy as np from typeguard import typechecked from pynpoint.core.dataio import InputPort from pynpoint.util.image import crop_image, center_pixel from pynpoint.util.module import progress @typechecked def locate_star( image: np.ndarray, center: Optional[tuple], width: Optional[int], fwhm: Optional[int], ) -> np.ndarray: """ Function to locate the star by finding the brightest pixel. Parameters ---------- image : numpy.ndarray Input image (2D). center : tuple(int, int), None Pixel center (y, x) of the subframe. The full image is used if set to None. width : int, None The width (pix) of the subframe. The full image is used if set to None. fwhm : int, None Full width at half maximum (pix) of the Gaussian kernel. Not used if set to None. Returns ------- numpy.ndarray Position (y, x) of the brightest pixel. """ if width is not None: if center is None: center = center_pixel(image) image = crop_image(image, center, width) if fwhm is None: smooth = np.copy(image) else: sigma = fwhm / math.sqrt(8.0 * math.log(2.0)) kernel = (fwhm * 2 + 1, fwhm * 2 + 1) smooth = cv2.GaussianBlur(image, kernel, sigma) # argmax[0] is the y position and argmax[1] is the y position argmax = np.asarray(np.unravel_index(smooth.argmax(), smooth.shape)) if center is not None and width is not None: argmax[0] += center[0] - (image.shape[0] - 1) // 2 # y argmax[1] += center[1] - (image.shape[1] - 1) // 2 # x return argmax @typechecked def star_positions( input_port: InputPort, fwhm: Optional[int], position: Optional[ Union[Tuple[int, int, float], Tuple[None, None, float], Tuple[int, int, None]] ] = None, ) -> np.ndarray: """ Function to return the position of the star in each image. Parameters ---------- input_port : pynpoint.core.dataio.InputPort Input port where the images are stored. fwhm : int, None The FWHM (pix) of the Gaussian kernel that is used to smooth the images before the brightest pixel is located. No smoothing is applied if set to None. position : tuple(int, int, int), None Subframe that is selected to search for the star. The tuple contains the center (pix) and size (pix) (pos_x, pos_y, size). Setting `position` to None will use the full image to search for the star. If `position=(None, None, size)` then the center of the image will be used. If `position=(pos_x, pos_y, None)` then a fixed position is used for the aperture. Returns ------- numpy.ndarray Positions (y, x) of the brightest pixel. """ nimages = input_port.get_shape()[0] starpos = np.zeros((nimages, 2), dtype=np.int64) if position is not None and position[2] is None: # [y. x] position starpos[:, 0] = position[1] starpos[:, 1] = position[0] else: center = None width = None if position is not None: width = position[2] if position[0] is not None and position[1] is not None: center = position[0:2] start_time = time.time() for i in range(nimages): progress(i, nimages, "Locating stellar position...", start_time) # [y. x] position starpos[i, :] = locate_star(input_port[i,], center, width, fwhm) return starpos PynPoint-PynPoint-e5d0637/pynpoint/util/tests.py000066400000000000000000000275041512720201400217620ustar00rootroot00000000000000""" Functions for testing the pipeline and its modules. """ import os import math import shutil import subprocess from typing import List, Optional, Tuple, Union import h5py import numpy as np from typeguard import typechecked from astropy.io import fits from scipy.ndimage import shift @typechecked def create_config(filename: str) -> None: """ Create a configuration file. Parameters ---------- filename : str Configuration filename. Returns ------- NoneType None """ with open(filename, "w") as file_obj: file_obj.write("[header]\n\n") file_obj.write("INSTRUMENT: INSTRUME\n") file_obj.write("NFRAMES: NAXIS3\n") file_obj.write("EXP_NO: ESO DET EXP NO\n") file_obj.write("NDIT: ESO DET NDIT\n") file_obj.write("PARANG_START: ESO ADA POSANG\n") file_obj.write("PARANG_END: ESO ADA POSANG END\n") file_obj.write("DITHER_X: ESO SEQ CUMOFFSETX\n") file_obj.write("DITHER_Y: ESO SEQ CUMOFFSETY\n") file_obj.write("DIT: None\n") file_obj.write("LATITUDE: None\n") file_obj.write("LONGITUDE: None\n") file_obj.write("PUPIL: None\n") file_obj.write("DATE: None\n") file_obj.write("RA: None\n") file_obj.write("DEC: None\n\n") file_obj.write("[settings]\n\n") file_obj.write("PIXSCALE: 0.027\n") file_obj.write("MEMORY: 39\n") file_obj.write("CPU: 1\n") @typechecked def create_random(path: str, nimages: float = 5) -> None: """ Create a dataset of images with Gaussian distributed pixel values. Parameters ---------- path : str Working folder. nimages : int Number of images. Returns ------- NoneType None """ if not os.path.exists(path): os.makedirs(path) file_in = os.path.join(path, "PynPoint_database.hdf5") np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(nimages, 11, 11)) with h5py.File(file_in, "w") as h5_file: dset = h5_file.create_dataset("images", data=images) dset.attrs["PIXSCALE"] = 0.01 h5_file.create_dataset("header_images/PARANG", data=np.arange(float(nimages))) @typechecked def create_fits( path: str, filename: str, image: np.ndarray, ndit: int, exp_no: int, dither_x: float, dither_y: float, ) -> None: """ Create a FITS file with images and header information. Parameters ---------- path : str Working folder. filename : str FITS filename. image : np.ndarray Images. ndit : int Number of integrations. exp_no : int Exposure number. dither_x : float Horizontal dither position relative to the image center. dither_y : float Vertical dither position relative to the image center. Returns ------- NoneType None """ hdu = fits.PrimaryHDU() header = hdu.header header["INSTRUME"] = "IMAGER" header["HIERARCH ESO DET EXP NO"] = 1.0 header["HIERARCH ESO DET NDIT"] = ndit header["HIERARCH ESO DET EXP NO"] = exp_no header["HIERARCH ESO ADA POSANG"] = 0.0 header["HIERARCH ESO ADA POSANG END"] = 180.0 header["HIERARCH ESO SEQ CUMOFFSETX"] = dither_x header["HIERARCH ESO SEQ CUMOFFSETY"] = dither_y hdu.data = image hdu.writeto(os.path.join(path, filename)) @typechecked def create_fake_data(path: str) -> None: """ Create an ADI dataset with a star and planet. Parameters ---------- path : str Working folder. Returns ------- NoneType None """ if not os.path.exists(path): os.makedirs(path) ndit = 10 npix = 21 fwhm = 3.0 sep = 6.0 contrast = 1e-1 pos_star = 10.0 exp_no = 1 parang = np.linspace(0.0, 180.0, 10) np.random.seed(1) sigma = fwhm / (2.0 * math.sqrt(2.0 * math.log(2.0))) x = np.arange(0.0, 21.0, 1.0) y = np.arange(0.0, 21.0, 1.0) xx, yy = np.meshgrid(x, y) images = np.zeros((ndit, npix, npix)) for i, item in enumerate(parang): images[i,] = np.random.normal(loc=0, scale=2e-4, size=(npix, npix)) star = np.exp( -((xx - pos_star) ** 2 + (yy - pos_star) ** 2) / (2.0 * sigma**2) ) / (2.0 * np.pi * sigma**2) x_shift = sep * math.cos(math.radians(item)) y_shift = sep * math.sin(math.radians(item)) images[i,] += star + shift(contrast * star, (x_shift, y_shift), order=5) create_fits(path, "images.fits", images, ndit, exp_no, 0.0, 0.0) @typechecked def create_ifs_data(path: str) -> None: """ Create an IFS dataset with a star and planet. Parameters ---------- path : str Working folder. Returns ------- NoneType None """ ndit = 10 npix = 21 nwavel = 3 fwhm = 3.0 sep = 6.0 contrast = 1.0 pos_star = 10.0 exp_no = 1 parang = np.linspace(0.0, 180.0, 10) wavelength = [1.0, 1.1, 1.2] if not os.path.exists(path): os.makedirs(path) sigma = fwhm / (2.0 * math.sqrt(2.0 * math.log(2.0))) x = y = np.arange(0.0, 21.0, 1.0) xx, yy = np.meshgrid(x, y) np.random.seed(1) images = np.random.normal(loc=0, scale=0.05, size=(nwavel, ndit, npix, npix)) for i, par_item in enumerate(parang): for j, wav_item in enumerate(wavelength): sigma_scale = sigma * wav_item star = np.exp( -((xx - pos_star) ** 2 + (yy - pos_star) ** 2) / (2.0 * sigma_scale**2) ) x_shift = sep * math.cos(math.radians(par_item)) y_shift = sep * math.sin(math.radians(par_item)) images[ j, i, ] += star + shift(contrast * star, (x_shift, y_shift), order=5) create_fits(path, "images.fits", images, ndit, exp_no, 0.0, 0.0) @typechecked def create_star_data(path: str, npix: int = 11, pos_star: float = 5.0) -> None: """ Create a dataset with a PSF and Gaussian noise. Parameters ---------- path : str Working folder. npix : int Number of pixels in each dimension. Returns ------- NoneType None """ fwhm = 3.0 nimages = 5 exp_no = [1, 2] parang_start = [0.0, 90.0] parang_end = [90.0, 180.0] if not os.path.exists(path): os.makedirs(path) np.random.seed(1) for j, item in enumerate(exp_no): sigma = fwhm / (2.0 * math.sqrt(2.0 * math.log(2.0))) x = y = np.arange(0.0, float(npix), 1.0) xx, yy = np.meshgrid(x, y) images = np.random.normal(loc=0, scale=0.1, size=(nimages, npix, npix)) images += np.exp( -((xx - pos_star) ** 2 + (yy - pos_star) ** 2) / (2.0 * sigma**2) ) hdu = fits.PrimaryHDU() header = hdu.header header["INSTRUME"] = "IMAGER" header["HIERARCH ESO DET EXP NO"] = item header["HIERARCH ESO DET NDIT"] = nimages header["HIERARCH ESO ADA POSANG"] = parang_start[j] header["HIERARCH ESO ADA POSANG END"] = parang_end[j] header["HIERARCH ESO SEQ CUMOFFSETX"] = "None" header["HIERARCH ESO SEQ CUMOFFSETY"] = "None" hdu.data = images hdu.writeto(os.path.join(path, f"images_{j}.fits")) @typechecked def create_dither_data(path: str) -> None: """ Create a dithering dataset with a stellar PSF. Parameters ---------- path : str Working folder. Returns ------- NoneType None """ if not os.path.exists(path): os.makedirs(path) ndit = 5 npix = 21 fwhm = 3.0 exp_no = [1, 2, 3, 4] pos_star = [(5.0, 5.0), (5.0, 15.0), (15.0, 15.0), (15.0, 5.0)] parang = np.full(10, 0.0) np.random.seed(1) sigma = fwhm / (2.0 * math.sqrt(2.0 * math.log(2.0))) x = np.arange(0.0, 21.0, 1.0) y = np.arange(0.0, 21.0, 1.0) xx, yy = np.meshgrid(x, y) for i, item in enumerate(exp_no): images = np.random.normal(loc=0, scale=0.1, size=(ndit, npix, npix)) for j in range(ndit): images[j,] += np.exp( -((xx - pos_star[i][0]) ** 2 + (yy - pos_star[i][1]) ** 2) / (2.0 * sigma**2) ) create_fits( path, f"images_{i}.fits", images, ndit, item, pos_star[i][0] - 10.0, pos_star[i][1] - 10.0, ) @typechecked def create_waffle_data(path: str) -> None: """ Create data with satellite spots and Gaussian noise. Parameters ---------- path : str Working folder. Returns ------- NoneType None """ if not os.path.exists(path): os.makedirs(path) fwhm = 3 npix = 101 x_spot = [25.0, 25.0, 75.0, 75.0] y_spot = [25.0, 75.0, 75.0, 25.0] sigma = fwhm / (2.0 * math.sqrt(2.0 * math.log(2.0))) x = y = np.arange(0.0, npix, 1.0) xx, yy = np.meshgrid(x, y) image = np.zeros((npix, npix)) for j in range(4): image += np.exp( -((xx - x_spot[j]) ** 2 + (yy - y_spot[j]) ** 2) / (2.0 * sigma**2) ) / (2.0 * np.pi * sigma**2) hdu = fits.PrimaryHDU() header = hdu.header header["INSTRUME"] = "IMAGER" header["HIERARCH ESO DET EXP NO"] = "None" header["HIERARCH ESO DET NDIT"] = "None" header["HIERARCH ESO ADA POSANG"] = "None" header["HIERARCH ESO ADA POSANG END"] = "None" header["HIERARCH ESO SEQ CUMOFFSETX"] = "None" header["HIERARCH ESO SEQ CUMOFFSETY"] = "None" hdu.data = image hdu.writeto(os.path.join(path, "images.fits")) @typechecked def remove_test_data( path: str, folders: Optional[List[str]] = None, files: Optional[List[str]] = None ) -> None: """ Function to remove data created by the test cases. Parameters ---------- path : str Working folder. folders : list(str, ) Folders to remove. files : list(str, ) Files to removes. Returns ------- NoneType None """ os.remove(path + "PynPoint_database.hdf5") os.remove(path + "PynPoint_config.ini") if folders is not None: for item in folders: shutil.rmtree(path + item) if files is not None: for item in files: os.remove(path + item) @typechecked def create_near_data(path: str) -> None: """ Create a stack of images with Gaussian distributed pixel values. Parameters ---------- path : str Working folder. Returns ------- NoneType None """ if not os.path.exists(path): os.makedirs(path) np.random.seed(1) image = np.random.normal(loc=0.0, scale=1.0, size=(10, 10)) exp_no = [1, 2] for i, item in enumerate(exp_no): fits_file = os.path.join(path, f"images_{i}.fits") primary_header = fits.Header() primary_header["INSTRUME"] = "VISIR" primary_header["HIERARCH ESO DET CHOP NCYCLES"] = 5 primary_header["HIERARCH ESO DET SEQ1 DIT"] = 1.0 primary_header["HIERARCH ESO TPL EXPNO"] = item primary_header["HIERARCH ESO DET CHOP ST"] = "T" primary_header["HIERARCH ESO DET CHOP CYCSKIP"] = 0 primary_header["HIERARCH ESO DET CHOP CYCSUM"] = "F" chopa_header = fits.Header() chopa_header["HIERARCH ESO DET FRAM TYPE"] = "HCYCLE1" chopb_header = fits.Header() chopb_header["HIERARCH ESO DET FRAM TYPE"] = "HCYCLE2" hdu = [fits.PrimaryHDU(header=primary_header)] for _ in range(5): hdu.append(fits.ImageHDU(image, header=chopa_header)) hdu.append(fits.ImageHDU(image, header=chopb_header)) # last image is the average of all images hdu.append(fits.ImageHDU(image)) hdulist = fits.HDUList(hdu) hdulist.writeto(fits_file) subprocess.call("compress " + fits_file, shell=True) PynPoint-PynPoint-e5d0637/pynpoint/util/type_aliases.py000066400000000000000000000003341512720201400232720ustar00rootroot00000000000000""" Additional custom types to make type hints easier to read. """ from typing import Union import numpy as np StaticAttribute = Union[str, float, int, np.generic] NonStaticAttribute = Union[np.ndarray, tuple, list] PynPoint-PynPoint-e5d0637/pynpoint/util/wavelets.py000066400000000000000000000323621512720201400224500ustar00rootroot00000000000000""" Wrapper utils for the wavelet functions for the mlpy cwt implementation (see continous.py) """ import numpy as np from numba import jit from typeguard import typechecked from scipy.special import gamma, hermite from scipy.signal import medfilt from statsmodels.robust import mad from pynpoint.util.continuous import autoscales, cwt, icwt # from pynpoint.util.continuous import fourier_from_scales # This function cannot by @typechecked because of a compatibility issue with numba @jit(cache=True, nopython=True) def _fast_zeros(soft: bool, spectrum: np.ndarray, uthresh: float) -> np.ndarray: """ Fast numba method to modify values in the wavelet space by using a hard or soft threshold function. Parameters ---------- soft : bool If True soft the threshold function will be used, otherwise a hard threshold is applied. spectrum : numpy.ndarray The input 2D wavelet space. uthresh : float Threshold used by the threshold function. Returns ------- numpy.ndarray Modified spectrum. """ if soft: for i in range(0, spectrum.shape[0], 1): for j in range(0, spectrum.shape[1], 1): tmp_value = spectrum[i, j].real if abs(spectrum[i, j]) > uthresh: spectrum[i, j] = np.sign(tmp_value) * (abs(tmp_value) - uthresh) else: spectrum[i, j] = 0 else: for i in range(0, spectrum.shape[0], 1): for j in range(0, spectrum.shape[1], 1): if abs(spectrum[i, j]) < uthresh: spectrum[i, j] = 0 return spectrum class WaveletAnalysisCapsule: """ Capsule class to process one 1d time series using the CWT and wavelet de-nosing by wavelet shrinkage. """ @typechecked def __init__( self, signal_in: np.ndarray, wavelet_in: str = "dog", order: int = 2, padding: str = "none", frequency_resolution: float = 0.5, ) -> None: """ Parameters ---------- signal_in : numpy.ndarray 1D input signal. wavelet_in : str Wavelet function ('dog' or 'morlet'). order : int Order of the wavelet function. padding : str Padding method ('zero', 'mirror', or 'none'). frequency_resolution : float Wavelet space resolution in scale/frequency. Returns ------- NoneType None """ # save input data self.m_supported_wavelets = ["dog", "morlet"] # check supported wavelets if wavelet_in not in self.m_supported_wavelets: raise ValueError(f"Wavelet {wavelet_in} is not supported") if wavelet_in == "dog": self._m_c_reconstructions = { 2: 3.5987, 4: 2.4014, 6: 1.9212, 8: 1.6467, 12: 1.3307, 16: 1.1464, 20: 1.0222, 30: 0.8312, 40: 0.7183, 60: 0.5853, } elif wavelet_in == "morlet": self._m_c_reconstructions = { 5: 0.9484, 6: 0.7784, 7: 0.6616, 8: 0.5758, 10: 0.4579, 12: 0.3804, 14: 0.3254, 16: 0.2844, 20: 0.2272, } self.m_wavelet = wavelet_in if padding not in ["none", "zero", "mirror"]: raise ValueError("Padding can only be none, zero or mirror") self._m_data = signal_in - np.ones(len(signal_in)) * np.mean(signal_in) self.m_padding = padding self.__pad_signal() self._m_data_size = len(self._m_data) self._m_data_mean = np.mean(signal_in) if order not in self._m_c_reconstructions: raise ValueError( "Wavelet " + str(wavelet_in) + " does not support order " + str(order) + ". \n Only orders: " + str(sorted(self._m_c_reconstructions.keys())).strip("[]") + " are supported" ) self.m_order = order self._m_c_final_reconstruction = self._m_c_reconstructions[order] # create scales for wavelet transform self._m_scales = autoscales( N=self._m_data_size, dt=1, dj=frequency_resolution, wf=wavelet_in, p=order ) self._m_number_of_scales = len(self._m_scales) self._m_frequency_resolution = frequency_resolution self.m_spectrum = None # --- functions for reconstruction value @staticmethod @typechecked def _morlet_function(omega0: float, x_in: float) -> np.complex128: """ Returns ------- numpy.complex128 Morlet function. """ return np.pi ** (-0.25) * np.exp(1j * omega0 * x_in) * np.exp(-(x_in**2) / 2.0) @staticmethod @typechecked def _dog_function(order: int, x_in: float) -> float: """ Returns ------- float DOG function. """ p_hpoly = hermite(order)[int(x_in / np.power(2, 0.5))] herm = p_hpoly / (np.power(2, float(order) / 2)) return ((-1) ** (order + 1)) / np.sqrt(gamma(order + 0.5)) * herm @typechecked def __pad_signal(self) -> None: """ Returns ------- NoneType None """ padding_length = int(len(self._m_data) * 0.5) if self.m_padding == "zero": new_data = np.append( self._m_data, np.zeros(padding_length, dtype=np.float64) ) self._m_data = np.append( np.zeros(padding_length, dtype=np.float64), new_data ) elif self.m_padding == "mirror": left_half_signal = self._m_data[:padding_length] right_half_signal = self._m_data[padding_length:] new_data = np.append(self._m_data, right_half_signal[::-1]) self._m_data = np.append(left_half_signal[::-1], new_data) @typechecked def __compute_reconstruction_factor(self) -> float: """ Computes the reconstruction factor. Returns ------- float Reconstruction factor. """ freq_res = self._m_frequency_resolution wavelet = self.m_wavelet order = self.m_order if wavelet == "morlet": zero_function = self._morlet_function(order, 0) else: zero_function = self._dog_function(order, 0) c_delta = self._m_c_final_reconstruction reconstruction_factor = freq_res / (c_delta * zero_function) return reconstruction_factor.real @typechecked def compute_cwt(self) -> None: """ Compute the wavelet space of the given input signal. Returns ------- NoneType None """ self.m_spectrum = cwt( self._m_data, dt=1, scales=self._m_scales, wf=self.m_wavelet, p=self.m_order ) @typechecked def update_signal(self) -> None: """ Updates the internal signal by the reconstruction of the current wavelet space. Returns ------- NoneType None """ self._m_data = icwt(self.m_spectrum, scales=self._m_scales) reconstruction_factor = self.__compute_reconstruction_factor() self._m_data *= reconstruction_factor @typechecked def denoise_spectrum(self, soft: bool = False) -> None: """ Applies wavelet shrinkage on the current wavelet space (m_spectrum) by either a hard of soft threshold function. Parameters ---------- soft : bool If True a soft threshold is used, hard otherwise. Returns ------- NoneType None """ if self.m_padding != "none": noise_length_4 = len(self._m_data) // 4 noise_spectrum = self.m_spectrum[ 0, noise_length_4 : (noise_length_4 * 3) ].real else: noise_spectrum = self.m_spectrum[0, :].real sigma = mad(noise_spectrum) uthresh = sigma * np.sqrt(2.0 * np.log(len(noise_spectrum))) self.m_spectrum = _fast_zeros(soft, self.m_spectrum, uthresh) @typechecked def median_filter(self) -> None: """ Applies a median filter on the internal 1d signal. Can be useful for cosmic ray correction after temporal de-noising Returns ------- NoneType None """ self._m_data = medfilt(self._m_data, 19) @typechecked def get_signal(self) -> np.ndarray: """ Returns the current version of the 1d signal. Use update_signal() in advance in order to get the current reconstruction of the wavelet space. Removes padded values as well. Returns ------- numpy.ndarray Current version of the 1D signal. """ tmp_data = self._m_data + np.ones(len(self._m_data)) * self._m_data_mean if self.m_padding == "none": return tmp_data return tmp_data[len(self._m_data) // 4 : 3 * (len(self._m_data) // 4)] # def __transform_period(self, # period): # # tmp_y = fourier_from_scales(self._m_scales, # self.m_wavelet, # self.m_order) # # def __transformation(x): # return np.log2(x + 1) * tmp_y[-1] / np.log2(tmp_y[-1] + 1) # # cutoff_scaled = __transformation(period) # # scale_new = tmp_y[-1] - tmp_y[0] # scale_old = self.m_spectrum.shape[0] # # factor = scale_old / scale_new # cutoff_scaled *= factor # # return cutoff_scaled # ----- plotting functions -------- # def __plot_or_save_spectrum(self): # plt.close() # # plt.figure(figsize=(8, 6)) # plt.subplot(1, 1, 1) # # tmp_y = fourier_from_scales(self._m_scales, # self.m_wavelet, # self.m_order) # # tmp_x = np.arange(0, self._m_data_size + 1, 1) # # scaled_spec = copy.deepcopy(self.m_spectrum.real) # for i, _ in enumerate(scaled_spec): # scaled_spec[i] /= np.sqrt(self._m_scales[i]) # # plt.imshow(abs(scaled_spec), # aspect='auto', # extent=[tmp_x[0], # tmp_x[-1], # tmp_y[0], # tmp_y[-1]], # cmap=plt.get_cmap("gist_ncar"), # origin='lower') # # # COI first part (only for DOG) with padding # # inner_frequency = 2.*np.pi/np.sqrt(self.m_order + 0.5) # coi = np.append(np.zeros(len(tmp_x)/4), # tmp_x[0:len(tmp_x) / 4]) # coi = np.append(coi, # tmp_x[0:len(tmp_x) / 4][::-1]) # coi = np.append(coi, # np.zeros(len(tmp_x) / 4)) # # plt.plot(np.arange(0, len(coi), 1.0), # inner_frequency * coi / np.sqrt(2), # color="white") # # plt.ylim([tmp_y[0], # tmp_y[-1]]) # # plt.fill_between(np.arange(0, len(coi), 1.0), # inner_frequency * coi / np.sqrt(2), # np.ones(len(coi)) * tmp_y[-1], # facecolor="none", # edgecolor='white', # alpha=0.4, # hatch="x") # # plt.yscale('log', basey=2) # plt.ylabel("Period in [s]") # plt.xlabel("Time in [s]") # plt.title("Spectrum computed with CWT using '" + str(self.m_wavelet) + # "' wavelet of order " + str(self.m_order)) # # def plot_spectrum(self): # """ # Shows a plot of the current wavelet space. # :return: None # """ # # self.__plot_or_save_spectrum() # plt.show() # # def save_spectrum(self, # location): # """ # Saves a plot of the current wavelet space to a given location. # :param location: Save location # :type location: str # :return: None # """ # self.__plot_or_save_spectrum() # plt.savefig(location) # plt.close() # # def __plot_or_save_signal(self): # plt.close() # plt.plot(self._m_data) # plt.title("Signal") # plt.ylabel("Value of the function") # plt.xlim([0, self._m_data_size]) # plt.xlabel("Time in [s]") # # def plot_signal(self): # """ # Plot the current signal. # :return: None # """ # self.__plot_or_save_signal() # plt.show() # # def save_signal(self, # location): # """ # Saves a plot of the current signal to a given location. # :param location: Save location # :type location: str # :return: None # """ # self.__plot_or_save_signal() # plt.savefig(location) PynPoint-PynPoint-e5d0637/pyproject.toml000066400000000000000000000030501512720201400203130ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0", "setuptools-scm>=8.0"] build-backend = "setuptools.build_meta" [project] name = "pynpoint" authors = [ {name = "Tomas Stolker", email = "stolker@strw.leidenuniv.nl"}, {name = "Markus Bonse", email = "Markus.Bonse@eso.org"}, ] description = "Pipeline for processing and analysis of high-contrast imaging data" readme = "README.rst" requires-python = ">=3.11,<3.14" license = { text = "GPL-3.0" } classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Astronomy", ] dependencies = [ "astropy", "emcee", "h5py", "numba", "numpy", "opencv-python", "photutils", "PyWavelets", "scikit-image", "scikit-learn", "scipy", "statsmodels", "tqdm", "typeguard", ] dynamic = ["version"] [project.optional-dependencies] docs = [ "jupyter", "nbsphinx", "pandoc", "sphinx", "sphinx-automodapi", "sphinx_book_theme", ] test = [ "black", "flake8", "pooch", "pycodestyle", "pylint", "pytest", "pytest-cov", ] [project.urls] Documentation = "https://pynpoint.readthedocs.io" Repository = "https://github.com/PynPoint/PynPoint" Issues = "https://github.com/PynPoint/PynPoint/issues" [tool.setuptools.packages.find] where = ["."] include = ["pynpoint*"] [tool.setuptools_scm] write_to = "pynpoint/_version.py" local_scheme = "no-local-version" PynPoint-PynPoint-e5d0637/tests/000077500000000000000000000000001512720201400165435ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/__init__.py000066400000000000000000000000001512720201400206420ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_core/000077500000000000000000000000001512720201400205325ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_core/__init__.py000066400000000000000000000000001512720201400226310ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_core/test_configport.py000066400000000000000000000053361512720201400243240ustar00rootroot00000000000000import os import pytest from pynpoint.core.pypeline import Pypeline from pynpoint.core.dataio import ConfigPort, DataStorage from pynpoint.util.tests import create_config, remove_test_data class TestConfigPort: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_config(self.test_dir + "PynPoint_config.ini") def teardown_class(self) -> None: remove_test_data(self.test_dir) def test_create_config_port(self) -> None: storage = DataStorage(self.test_dir + "PynPoint_database.hdf5") with pytest.raises(ValueError) as error: ConfigPort("images", storage) assert ( str(error.value) == "The tag name of the central configuration should be " "'config'." ) port = ConfigPort("config", None) with pytest.warns(UserWarning) as warning: check_error = port._check_error_cases() assert len(warning) == 1 assert ( warning[0].message.args[0] == "ConfigPort can not load data unless a database is " "connected." ) assert not check_error port = ConfigPort("config", storage) assert isinstance(port, ConfigPort) with pytest.warns(UserWarning) as warning: port._check_error_cases() assert len(warning) == 1 assert ( warning[0].message.args[0] == "No data under the tag which is linked by the " "ConfigPort." ) def test_get_config_attribute(self) -> None: create_config(self.test_dir + "PynPoint_config.ini") Pypeline(self.test_dir, self.test_dir, self.test_dir) storage = DataStorage(self.test_dir + "PynPoint_database.hdf5") port = ConfigPort("config", None) with pytest.warns(UserWarning) as warning: attribute = port.get_attribute("CPU") assert len(warning) == 1 assert ( warning[0].message.args[0] == "ConfigPort can not load data unless a database is " "connected." ) assert attribute is None port = ConfigPort("config", storage) attribute = port.get_attribute("CPU") assert attribute == 1 attribute = port.get_attribute("NFRAMES") assert attribute == "NAXIS3" attribute = port.get_attribute("PIXSCALE") assert attribute == pytest.approx(0.027, rel=self.limit, abs=0.0) with pytest.warns(UserWarning) as warning: attribute = port.get_attribute("test") assert len(warning) == 1 assert warning[0].message.args[0] == "The attribute 'test' was not found." assert attribute is None PynPoint-PynPoint-e5d0637/tests/test_core/test_datastorage.py000066400000000000000000000036621512720201400244500ustar00rootroot00000000000000import os import pytest import h5py import numpy as np from pynpoint.core.dataio import DataStorage class TestDataStorage: def setup_class(self) -> None: self.limit = 1e-10 self.test_data = os.path.dirname(__file__) + "/PynPoint_database.hdf5" def test_create_storage_with_existing_database(self) -> None: np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(10, 100, 100)) with h5py.File(self.test_data, "w") as hdf_file: hdf_file.create_dataset("images", data=images) storage = DataStorage(self.test_data) storage.open_connection() data = storage.m_data_bank["images"] assert data[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) assert np.mean(data) == pytest.approx( 1.0506056979365338e-06, rel=self.limit, abs=0.0 ) os.remove(self.test_data) def test_create_storage_without_existing_database(self) -> None: storage = DataStorage(self.test_data) storage.open_connection() storage.m_data_bank["data"] = [0, 1, 2, 5, 7] assert storage.m_data_bank["data"][2] == 2 assert list(storage.m_data_bank.keys()) == [ "data", ] storage.close_connection() os.remove(self.test_data) def test_create_storage_with_wrong_location(self) -> None: file_in = "/test/test.hdf5" with pytest.raises(AssertionError): DataStorage(file_in) def test_open_close_connection(self) -> None: storage = DataStorage(self.test_data) storage.open_connection() assert storage.m_open is True storage.open_connection() assert storage.m_open is True storage.close_connection() assert storage.m_open is False storage.close_connection() assert storage.m_open is False os.remove(self.test_data) PynPoint-PynPoint-e5d0637/tests/test_core/test_inputport.py000066400000000000000000000076301512720201400242150ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.dataio import DataStorage, InputPort, OutputPort from pynpoint.util.tests import create_random, create_config, remove_test_data class TestInputPort: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir) create_config(self.test_dir + "PynPoint_config.ini") file_in = os.path.dirname(__file__) + "/PynPoint_database.hdf5" self.storage = DataStorage(file_in) def teardown_class(self) -> None: remove_test_data(self.test_dir) def test_create_instance_access_data(self) -> None: with pytest.raises(ValueError) as error: InputPort("config", self.storage) assert ( str(error.value) == "The tag name 'config' is reserved for the central " "configuration of PynPoint." ) with pytest.raises(ValueError) as error: InputPort("fits_header", self.storage) assert ( str(error.value) == "The tag name 'fits_header' is reserved for storage of the " "FITS headers." ) port = InputPort("images", self.storage) assert port[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) data = np.mean(port.get_all()) assert data == pytest.approx(1.1824138000882435e-05, rel=self.limit, abs=0.0) assert len(port[0:2, 0, 0]) == 2 assert port.get_shape() == (5, 11, 11) assert port.get_attribute("PIXSCALE") == 0.01 assert port.get_attribute("PARANG")[0] == 0.0 with pytest.warns(UserWarning): assert port.get_attribute("none") is None def test_create_instance_access_non_existing_data(self) -> None: port = InputPort("test", self.storage) with pytest.warns(UserWarning): assert port[0, 0, 0] is None with pytest.warns(UserWarning): assert port.get_all() is None with pytest.warns(UserWarning): assert port.get_shape() is None with pytest.warns(UserWarning): assert port.get_attribute("num_files") is None with pytest.warns(UserWarning): assert port.get_all_non_static_attributes() is None with pytest.warns(UserWarning): assert port.get_all_static_attributes() is None def test_create_instance_no_data_storage(self) -> None: port = InputPort("test") with pytest.warns(UserWarning): assert port[0, 0, 0] is None with pytest.warns(UserWarning): assert port.get_all() is None with pytest.warns(UserWarning): assert port.get_shape() is None with pytest.warns(UserWarning): assert port.get_all_non_static_attributes() is None with pytest.warns(UserWarning): assert port.get_all_static_attributes() is None # def test_get_all_attributes(self) -> None: # # port = InputPort('images', self.storage) # # assert port.get_all_static_attributes() == {'PIXSCALE': 0.01} # assert port.get_all_non_static_attributes() == ['PARANG', ] # # port = OutputPort('images', self.storage) # assert port.del_all_attributes() is None # # port = InputPort('images', self.storage) # assert port.get_all_non_static_attributes() is None # def test_get_ndim(self) -> None: # # with pytest.warns(UserWarning) as warning: # ndim = InputPort('images', None).get_ndim() # # assert len(warning) == 1 # # assert warning[0].message.args[0] == 'InputPort can not load data ' \ # 'unless a database is connected.' # # assert ndim is None # # port = InputPort('images', self.storage) # assert port.get_ndim() == 3 PynPoint-PynPoint-e5d0637/tests/test_core/test_outputport.py000066400000000000000000000554631512720201400244250ustar00rootroot00000000000000import os from typing import Tuple import pytest import numpy as np from pynpoint.core.dataio import OutputPort, DataStorage, InputPort from pynpoint.util.tests import create_random class TestOutputPort: def setup_class(self) -> None: self.limit = 1e-10 self.storage = DataStorage( os.path.dirname(__file__) + "/PynPoint_database.hdf5" ) create_random(os.path.dirname(__file__)) def teardown_class(self) -> None: os.remove(os.path.dirname(__file__) + "/PynPoint_database.hdf5") def create_input_port(self, tag_name: str) -> InputPort: inport = InputPort(tag_name, self.storage) inport.open_port() return inport def create_output_port(self, tag_name: str) -> OutputPort: outport = OutputPort(tag_name, self.storage) return outport def test_create_instance(self) -> None: with pytest.raises(ValueError) as error: OutputPort("config", self.storage) assert ( str(error.value) == "The tag name 'config' is reserved for the central " "configuration of PynPoint." ) with pytest.raises(ValueError) as error: OutputPort("fits_header", self.storage) assert ( str(error.value) == "The tag name 'fits_header' is reserved for storage of the " "FITS headers." ) active_port = OutputPort("test", self.storage, activate_init=True) deactive_port = OutputPort("test", self.storage, activate_init=False) control_port = InputPort("test", self.storage) deactive_port.open_port() deactive_port.set_all(np.asarray([0, 1, 2, 3])) deactive_port.flush() with pytest.warns(UserWarning) as warning: control_port.get_all() assert len(warning) == 1 assert ( warning[0].message.args[0] == "No data under the tag which is linked by the " "InputPort." ) active_port.set_all(np.asarray([0, 1, 2, 3])) active_port.flush() assert np.array_equal(np.asarray([0, 1, 2, 3]), control_port.get_all()) active_port.del_all_data() # def test_set_all_new_data(self) -> None: # # outport = self.create_output_port('new_data') # # # ----- 1D input ----- # # data = [1, 3] # outport.set_all(data, data_dim=1) # # inport = self.create_input_port('new_data') # # assert np.array_equal(inport.get_all(), [1., 3.]) # outport.del_all_data() # # data = [1, 3] # outport.set_all(data, data_dim=2) # assert np.array_equal(inport.get_all(), [[1, 3]]) # outport.del_all_data() # # # ----- 2D input ----- # # data = [[1, 3], [2, 4]] # outport.set_all(data, data_dim=2) # assert np.array_equal(inport.get_all(), [[1, 3], [2, 4]]) # outport.del_all_data() # # data = [[1, 3], [2, 4]] # outport.set_all(data, data_dim=3) # assert np.array_equal(inport.get_all(), [[[1, 3], [2, 4]]]) # outport.del_all_data() # # # ----- 3D input ----- # # data = [[[1, 3], [2, 4]], [[1, 3], [2, 4]]] # outport.set_all(data, data_dim=3) # assert np.array_equal(inport.get_all(), [[[1, 3], [2, 4]], [[1, 3], [2, 4]]]) # outport.del_all_data() # def test_set_all_error(self) -> None: # # # ---- Test database not set ----- # # data = [1, 2, 3, 4, 0] # # with pytest.warns(UserWarning) as record: # out_port = OutputPort('some_data') # out_port.set_all(data) # # assert len(record) == 1 # # assert record[0].message.args[0] == 'OutputPort can not store data unless a database is ' \ # 'connected.' # # # ---- Test data dim of actual data for new data entry is < 1 or > 5 # # out_port = self.create_output_port('new_data') # # data = [[[[[[2, 2], ], ], ], ]] # # with pytest.raises(ValueError) as error: # out_port.set_all(data, data_dim=2) # # assert str(error.value) == 'Output port can only save numpy arrays from 1D to 5D. Use ' \ # 'Port attributes to save as int, float, or string.' # # # ---- Test data dim of data_dim for new data entry is < 1 or > 5 # # out_port = self.create_output_port('new_data') # # data = [1, 2, 4] # # with pytest.raises(ValueError) as error: # out_port.set_all(data, data_dim=0) # # assert str(error.value) == 'The data dimensions should be 1D, 2D, 3D, 4D, or 5D.' # # # ---- Test data_dim for new data entry is smaller than actual data # # out_port = self.create_output_port('new_data') # # data = [[1], [2]] # # with pytest.raises(ValueError) as error: # out_port.set_all(data, data_dim=1) # # assert str(error.value) == 'The dimensions of the data should be equal to or larger ' \ # 'than the dimensions of the input data.' # # # ---- Test data_dim == 3 and actual size == 1 # # out_port = self.create_output_port('new_data') # # data = [1, 2] # # with pytest.raises(ValueError) as error: # out_port.set_all(data, data_dim=3) # # assert str(error.value) == 'Cannot initialize 1D data in 3D data container.' # # def test_set_all_keep_attributes(self) -> None: # # def init_out_port() -> Tuple[OutputPort, InputPort]: # out_port = self.create_output_port('new_data') # control = self.create_input_port('new_data') # # data = [2, 3, 4] # out_port.set_all(data) # out_port.add_attribute(name='test1', value=1) # out_port.add_attribute(name='test2', value=12) # # return out_port, control # # out_port, control = init_out_port() # # with pytest.warns(UserWarning) as warning: # out_port.set_all([[]], data_dim=2, keep_attributes=True) # # assert len(warning) == 1 # # assert warning[0].message.args[0] == 'The new dataset that is stored under the tag name ' \ # '\'new_data\' is empty.' # # assert control.get_attribute('test1') == 1 # assert control.get_attribute('test2') == 12 # # out_port.del_all_data() # # def test_append_new_data(self) -> None: # # # using append even if no data exists # # out_port = self.create_output_port('new_data') # # # ----- 1D input ----- # # data = [3, ] # out_port.append(data) # # control = self.create_input_port('new_data') # assert control.get_all() == data # out_port.del_all_data() # # # ----- 2D input ----- # # data = [[3, 3], [3, 2]] # out_port.append(data) # # assert np.array_equal(control.get_all(), data) # out_port.del_all_data() # # # ----- 3D input ----- # # data = [[[3, 3], [3, 2]], [[3, 1], [3, 1]]] # out_port.append(data) # # assert np.array_equal(control.get_all(), data) # out_port.del_all_data() # # def test_append_existing_data(self) -> None: # # out_port = self.create_output_port('new_data') # # # ----- 1D ----- # # out_port.append([2, 3, 5], data_dim=1) # out_port.append([3, 3, 5]) # # control = self.create_input_port('new_data') # # assert np.array_equal(control.get_all(), [2, 3, 5, 3, 3, 5]) # # out_port.del_all_data() # # # ----- 2D ----- # # 1D input append to 1D data # # out_port.append([1, 1], data_dim=2) # out_port.append([3, 3]) # # assert np.array_equal(control.get_all(), [[1, 1], [3, 3]]) # # out_port.del_all_data() # # # 1D input append to 2D data # # out_port.append([[2, 3], [1, 1]], data_dim=2) # out_port.append([3, 3]) # # assert np.array_equal(control.get_all(), [[2, 3], [1, 1], [3, 3]]) # # out_port.del_all_data() # # # 2D input append to 2D data # # out_port.append([[2, 3], [1, 1]], data_dim=2) # out_port.append([[3, 3], [8, 8]]) # # assert np.array_equal(control.get_all(), [[2, 3], [1, 1], [3, 3], [8, 8]]) # # out_port.del_all_data() # # # 2D input append to 3D data # # out_port.append([[[2, 3], [1, 1]], [[2, 4], [1, 1]]], data_dim=3) # # out_port.append([[3, 3], [8, 8]]) # # assert np.array_equal(control.get_all(), # [[[2, 3], [1, 1]], # [[2, 4], [1, 1]], # [[3, 3], [8, 8]]]) # # out_port.del_all_data() # # # 3D input append to 3D data # # out_port.append([[[2, 3], [1, 1]], [[2, 4], [1, 1]]], data_dim=3) # out_port.append([[[22, 7], [10, 221]], [[223, 46], [1, 15]]]) # # assert np.array_equal(control.get_all(), # [[[2, 3], [1, 1]], # [[2, 4], [1, 1]], # [[22, 7], [10, 221]], # [[223, 46], [1, 15]]]) # # out_port.del_all_data() # # def test_append_existing_data_force_overwriting(self) -> None: # # out_port = self.create_output_port('new_data') # # # Error case (no force) # # out_port.append([2, 3, 5], data_dim=1) # out_port.append([[[22, 7], [10, 221]], [[223, 46], [1, 15]]], force=True) # # control = self.create_input_port('new_data') # # assert np.array_equal(control.get_all(), [[[22, 7], [10, 221]], [[223, 46], [1, 15]]]) # # out_port.del_all_data() # # def test_append_existing_data_error(self) -> None: # # # ---- port not active ---- # out_port = self.create_output_port('new_data') # out_port.deactivate() # # data = [1, ] # # out_port.append(data) # out_port.del_all_data() # out_port.activate() # # # 1 Element input # # # 1D input # # 2D input # # 3D input # # # Error case (no force) # out_port.set_all([2, 3, 5], data_dim=1) # # with pytest.raises(ValueError) as error: # out_port.append([[[22, 7], [10, 221]], [[223, 46], [1, 15]]]) # # assert str(error.value) == 'The port tag \'new_data\' is already used with a different ' \ # 'data type. The \'force\' parameter can be used to replace ' \ # 'the tag.' # out_port.del_all_data() # # def test_set_data_using_slicing(self) -> None: # # out_port = self.create_output_port('new_data') # # out_port.set_all([2, 5, 6, 7, ]) # out_port[3] = 44 # # control = self.create_input_port('new_data') # # assert np.array_equal(control.get_all(), [2, 5, 6, 44, ]) # # out_port.deactivate() # out_port[2] = 0 # # assert np.array_equal(control.get_all(), [2, 5, 6, 44, ]) # # out_port.activate() # out_port.del_all_data() # # def test_del_all_data(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([0, 1]) # out_port.del_all_data() # # control = self.create_input_port('new_data') # # with pytest.warns(UserWarning) as warning: # control.get_all() # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'No data under the tag which is linked by the ' \ # 'InputPort.' # # def test_add_static_attribute(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([1]) # out_port.add_attribute('attr1', value=5) # out_port.add_attribute('attr2', value='no') # # control = self.create_input_port('new_data') # assert control.get_attribute('attr1') == 5 # # out_port.add_attribute('attr1', value=6) # assert control.get_attribute('attr1') == 6 # assert control.get_attribute('attr2') == 'no' # # out_port.deactivate() # out_port.add_attribute('attr3', value=33) # # with pytest.warns(UserWarning) as warning: # control.get_attribute('attr3') # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'The attribute \'attr3\' was not found.' # # out_port.activate() # out_port.del_all_attributes() # out_port.del_all_data() # # def test_add_static_attribute_error(self) -> None: # # out_port = self.create_output_port('new_data') # # # add attribute while no data is set # with pytest.warns(UserWarning) as warning: # out_port.add_attribute('attr1', value=6) # # # check that only one warning was raised # assert len(warning) == 1 # # # check that the message matches # assert warning[0].message.args[0] == 'Can not store the attribute \'attr1\' because ' \ # 'the dataset \'new_data\' does not exist.' # # out_port.del_all_attributes() # out_port.del_all_data() # # def test_add_non_static_attribute(self) -> None: # # # two different data types # # out_port = self.create_output_port('new_data') # out_port.set_all([1]) # out_port.add_attribute('attr1', value=[6, 3], static=False) # # control = self.create_input_port('new_data') # assert np.array_equal(control.get_attribute('attr1'), [6, 3]) # # out_port.del_all_attributes() # out_port.del_all_data() # # def test_append_attribute_data(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.del_all_data() # out_port.set_all([1]) # out_port.add_attribute('attr1', value=[2, 3], static=False) # # control = self.create_input_port('new_data') # assert np.array_equal(control.get_attribute('attr1'), [2, 3]) # # out_port.append_attribute_data('attr1', value=2) # assert np.array_equal(control.get_attribute('attr1'), [2, 3, 2]) # # out_port.deactivate() # out_port.append_attribute_data('attr1', value=2) # assert np.array_equal(control.get_attribute('attr1'), [2, 3, 2]) # # out_port.activate() # out_port.del_all_attributes() # out_port.del_all_data() # # def test_copy_attributes(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.del_all_attributes() # out_port.del_all_data() # out_port.set_all([0, ]) # # # some static attributes # # out_port.add_attribute('attr1', 33) # out_port.add_attribute('attr2', 'string') # out_port.add_attribute('attr3', 3.141) # # # non static attributes # # out_port.add_attribute('attr_non_static', [3, 4, 5, 6], static=False) # # copy_port = self.create_output_port('other_data') # copy_port.del_all_attributes() # copy_port.del_all_data() # # copy_port.set_all([1, ]) # # # for attribute overwriting # # copy_port.add_attribute('attr_non_static', [3, 4, 44, 6], static=False) # # control = self.create_input_port('new_data') # copy_port.copy_attributes(control) # # copy_control = self.create_input_port('other_data') # # assert copy_control.get_attribute('attr1') == 33 # assert copy_control.get_attribute('attr2') == 'string' # # assert np.array_equal(copy_control.get_attribute('attr3'), 3.141) # assert np.array_equal(copy_control.get_attribute('attr_non_static'), [3, 4, 5, 6]) # # copy_port.del_all_attributes() # copy_port.del_all_data() # # out_port.del_all_attributes() # out_port.del_all_data() # # port = self.create_output_port('test') # port.deactivate() # # assert port.copy_attributes(control) is None # # port = self.create_input_port('test') # # with pytest.warns(UserWarning) as warning: # port.get_all_non_static_attributes() # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'No data under the tag which is linked by the ' \ # 'InputPort.' # # def test_copy_attributes_same_tag(self) -> None: # # out_port1 = self.create_output_port('new_data') # out_port1.set_all([0, ]) # # out_port2 = self.create_output_port('new_data') # out_port2.set_all([2, ]) # # out_port1.add_attribute('attr1', 2) # # control1 = self.create_input_port('new_data') # out_port2.copy_attributes(control1) # # control2 = self.create_input_port('new_data') # assert control2.get_attribute('attr1') == 2 # # out_port1.del_all_data() # out_port1.del_all_attributes() # # def test_del_attribute(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([0, ]) # # # static # # out_port.add_attribute('attr1', 4) # out_port.add_attribute('attr2', 5) # # # non static # # out_port.add_attribute('attr_non_static_1', [1, 2, 3], static=False) # out_port.add_attribute('attr_non_static_2', [2, 4, 6, 8], static=False) # out_port.del_attribute('attr1') # out_port.del_attribute('attr_non_static_1') # # # check is only the chosen attributes are deleted and the rest is still there # # control = self.create_input_port('new_data') # # with pytest.warns(UserWarning) as warning: # control.get_attribute('attr1') # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'The attribute \'attr1\' was not found.' # # assert control.get_attribute('attr2') == 5 # # with pytest.warns(UserWarning) as warning: # control.get_attribute('attr_non_static_1') # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'The attribute \'attr_non_static_1\' was not found.' # # assert np.array_equal(control.get_attribute('attr_non_static_2'), [2, 4, 6, 8]) # # out_port.del_all_data() # out_port.del_all_attributes() # # def test_del_attribute_error_case(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([0, ]) # # # deactivated port # # out_port.add_attribute('attr_1', 5.554) # out_port.deactivate() # out_port.del_attribute('attr_1') # # control = self.create_input_port('new_data') # assert control.get_attribute('attr_1') == 5.554 # # out_port.activate() # # # not existing # with pytest.warns(UserWarning) as warning: # out_port.del_attribute('not_existing') # # assert len(warning) == 1 # # assert warning[0].message.args[0] == 'Attribute \'not_existing\' does not exist and ' \ # 'could not be deleted.' # # out_port.del_all_attributes() # out_port.del_all_data() # # def test_del_all_attributes(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([0, ]) # out_port.add_attribute('attr_1', 4) # out_port.add_attribute('attr_2', [1, 3], static=False) # out_port.del_all_attributes() # # control = self.create_input_port('new_data') # # with pytest.warns(UserWarning) as warning: # control.get_attribute('attr_1') # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'The attribute \'attr_1\' was not found.' # # with pytest.warns(UserWarning) as warning: # control.get_attribute('attr_2') # # assert len(warning) == 1 # assert warning[0].message.args[0] == 'The attribute \'attr_2\' was not found.' # # out_port.del_all_data() # # def test_add_history(self) -> None: # # out_port = self.create_output_port('new_data') # out_port.set_all([0, ]) # out_port.add_history('Test', 'history') # # control = self.create_input_port('new_data') # assert control.get_attribute('History: Test') == 'history' # # def test_check_attribute(self) -> None: # # out_port = self.create_output_port('new_data') # # out_port.set_all([0, ]) # out_port.add_attribute('static', 5, static=True) # out_port.add_attribute('non-static', np.arange(1, 11, 1), static=False) # # assert out_port.check_static_attribute('static', 5) == 0 # assert out_port.check_static_attribute('test', 3) == 1 # assert out_port.check_static_attribute('static', 33) == -1 # # assert out_port.check_non_static_attribute('non-static', np.arange(1, 11, 1)) == 0 # assert out_port.check_non_static_attribute('test', np.arange(1, 11, 1)) == 1 # assert out_port.check_non_static_attribute('non-static', np.arange(10, 21, 1)) == -1 # # out_port.deactivate() # # assert out_port.check_static_attribute('static', 5) is None # assert out_port.check_non_static_attribute('non-static', np.arange(1, 11, 1)) is None # # out_port.activate() # out_port.del_all_data() # out_port.del_all_attributes() # # def test_append_empty_data_new(self) -> None: # # out_port = self.create_output_port('empty_data') # # with pytest.warns(UserWarning) as warning: # out_port.append([]) # # assert len(warning) == 1 # # assert warning[0].message.args[0] == 'The new dataset that is stored under the tag name ' \ # '\'empty_data\' is empty.' # # def test_append_empty_data_add(self) -> None: # # out_port = self.create_output_port('empty_data') # # with pytest.warns(UserWarning) as warning: # out_port.append([]) # # assert len(warning) == 1 # # assert warning[0].message.args[0] == 'The dataset that is appended under the tag name ' \ # '\'empty_data\' is empty.' PynPoint-PynPoint-e5d0637/tests/test_core/test_processing.py000066400000000000000000000172771512720201400243350ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.background import LineSubtractionModule from pynpoint.processing.badpixel import BadPixelSigmaFilterModule from pynpoint.processing.extract import StarExtractionModule from pynpoint.processing.timedenoising import TimeNormalizationModule from pynpoint.util.tests import create_config, create_star_data, remove_test_data class TestProcessing: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) large_data = np.random.normal(loc=0, scale=2e-4, size=(10000, 5, 5)) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "w") as hdf_file: hdf_file.create_dataset("images", data=images) hdf_file.create_dataset("large_data", data=large_data) create_star_data(path=self.test_dir + "images") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) self.pipeline.set_attribute("images", "PIXSCALE", 0.1, static=True) self.pipeline.set_attribute("large_data", "PIXSCALE", 0.1, static=True) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["images"]) def test_output_port_name(self) -> None: module = FitsReadingModule( name_in="read", image_tag="images", input_dir=self.test_dir + "images" ) module.add_output_port("test") with pytest.warns(UserWarning) as warning: module.add_output_port("test") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Tag 'test' of ReadingModule 'read' is already " "used." ) module = BadPixelSigmaFilterModule( name_in="badpixel", image_in_tag="images", image_out_tag="im_out" ) module.add_output_port("test") with pytest.warns(UserWarning) as warning: module.add_output_port("test") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Tag 'test' of ProcessingModule 'badpixel' is " "already used." ) def test_output_port_set_connection(self) -> None: self.pipeline.m_data_storage.open_connection() module = BadPixelSigmaFilterModule( name_in="badpixel2", image_in_tag="images", image_out_tag="im_out" ) self.pipeline.add_module(module) port = module.add_output_port("test1") self.pipeline.m_data_storage.close_connection() def test_apply_function(self) -> None: self.pipeline.set_attribute("config", "MEMORY", 20, static=True) self.pipeline.set_attribute("config", "CPU", 4, static=True) module = LineSubtractionModule( name_in="subtract", image_in_tag="images", image_out_tag="im_subtract", combine="mean", mask=None, ) self.pipeline.add_module(module) self.pipeline.run_module("subtract") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.0071536034905338745, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) data = self.pipeline.get_data("im_subtract") assert np.sum(data) == pytest.approx(7.047314121155779e-19, rel=0.0, abs=1e-10) assert data.shape == (5, 11, 11) def test_apply_function_args_none(self) -> None: module = TimeNormalizationModule( name_in="norm", image_in_tag="images", image_out_tag="im_norm" ) self.pipeline.add_module(module) self.pipeline.run_module("norm") data = self.pipeline.get_data("im_norm") assert np.mean(data) == pytest.approx( 2.4012571778516812e-06, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_apply_function_args_none_memory_none(self) -> None: self.pipeline.set_attribute("config", "MEMORY", 0, static=True) module = TimeNormalizationModule( name_in="norm_none", image_in_tag="images", image_out_tag="im_norm" ) self.pipeline.add_module(module) self.pipeline.run_module("norm_none") data = self.pipeline.get_data("im_norm") assert np.mean(data) == pytest.approx( 2.4012571778516812e-06, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_apply_function_same_port(self) -> None: module = LineSubtractionModule( name_in="subtract_same", image_in_tag="im_subtract", image_out_tag="im_subtract", combine="mean", mask=None, ) self.pipeline.add_module(module) self.pipeline.run_module("subtract_same") data = self.pipeline.get_data("im_subtract") assert np.sum(data) == pytest.approx(0.0, rel=0.0, abs=1e-10) assert data.shape == (5, 11, 11) def test_apply_function_args_none_memory_none_same_port(self) -> None: self.pipeline.set_attribute("config", "MEMORY", 0, static=True) data = self.pipeline.get_data("images") assert np.mean(data) == pytest.approx( 1.1824138000882435e-05, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) module = TimeNormalizationModule( name_in="norm_none_same", image_in_tag="images", image_out_tag="images" ) self.pipeline.add_module(module) self.pipeline.run_module("norm_none_same") data = self.pipeline.get_data("images") assert np.mean(data) == pytest.approx( 2.4012571778516812e-06, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_apply_function_to_images_memory_none(self) -> None: module = StarExtractionModule( name_in="extract", image_in_tag="im_subtract", image_out_tag="extract", index_out_tag=None, image_size=0.5, fwhm_star=0.1, position=(None, None, 0.1), ) self.pipeline.add_module(module) self.pipeline.run_module("extract") data = self.pipeline.get_data("extract") assert np.mean(data) == pytest.approx( 1.8259937251367536e-05, rel=self.limit, abs=0.0 ) assert data.shape == (5, 5, 5) def test_multiproc_large_data(self) -> None: self.pipeline.set_attribute("config", "MEMORY", 1000, static=True) self.pipeline.set_attribute("config", "CPU", 1, static=True) module = LineSubtractionModule( name_in="subtract_single", image_in_tag="large_data", image_out_tag="im_sub_single", combine="mean", mask=None, ) self.pipeline.add_module(module) self.pipeline.run_module("subtract_single") self.pipeline.set_attribute("config", "CPU", 4, static=True) module = LineSubtractionModule( name_in="subtract_multi", image_in_tag="large_data", image_out_tag="im_sub_multi", combine="mean", mask=None, ) self.pipeline.add_module(module) self.pipeline.run_module("subtract_multi") data_single = self.pipeline.get_data("im_sub_single") data_multi = self.pipeline.get_data("im_sub_multi") assert data_single == pytest.approx(data_multi, rel=self.limit, abs=0.0) assert data_single.shape == data_multi.shape PynPoint-PynPoint-e5d0637/tests/test_core/test_pypeline.py000066400000000000000000000325661512720201400240040ustar00rootroot00000000000000import os import pytest import h5py import numpy as np from astropy.io import fits from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.readwrite.fitswriting import FitsWritingModule from pynpoint.processing.badpixel import BadPixelSigmaFilterModule from pynpoint.util.tests import create_config, remove_test_data class TestPypeline: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) hdu = fits.PrimaryHDU() header = hdu.header header["INSTRUME"] = "IMAGER" header["HIERARCH ESO DET EXP NO"] = 1 header["HIERARCH ESO DET NDIT"] = 5 header["HIERARCH ESO INS PIXSCALE"] = 0.01 header["HIERARCH ESO ADA POSANG"] = 10.0 header["HIERARCH ESO ADA POSANG END"] = 20.0 header["HIERARCH ESO SEQ CUMOFFSETX"] = 5.0 header["HIERARCH ESO SEQ CUMOFFSETY"] = 5.0 hdu.data = images hdu.writeto(self.test_dir + "images.fits") def teardown_class(self) -> None: remove_test_data(self.test_dir, files=["images.fits"]) def test_create_default_config(self) -> None: with pytest.warns(UserWarning) as warning: Pypeline(self.test_dir, self.test_dir, self.test_dir) assert len(warning) == 1 assert ( warning[0].message.args[0] == "Configuration file not found. Creating " "PynPoint_config.ini with default values " "in the working place." ) with open(self.test_dir + "PynPoint_config.ini") as f_obj: count = 0 for _ in f_obj: count += 1 assert count == 25 def test_create_none_config(self) -> None: file_obj = open(self.test_dir + "PynPoint_config.ini", "w") file_obj.write("[header]\n\n") file_obj.write("INSTRUMENT: None\n") file_obj.write("NFRAMES: None\n") file_obj.write("EXP_NO: None\n") file_obj.write("NDIT: None\n") file_obj.write("PARANG_START: ESO ADA POSANG\n") file_obj.write("PARANG_END: None\n") file_obj.write("DITHER_X: None\n") file_obj.write("DITHER_Y: None\n") file_obj.write("DIT: None\n") file_obj.write("LATITUDE: None\n") file_obj.write("LONGITUDE: None\n") file_obj.write("PUPIL: None\n") file_obj.write("DATE: None\n") file_obj.write("RA: None\n") file_obj.write("DEC: None\n\n") file_obj.write("[settings]\n\n") file_obj.write("PIXSCALE: None\n") file_obj.write("MEMORY: None\n") file_obj.write("CPU: None\n") file_obj.close() pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) attribute = pipeline.get_attribute("config", "MEMORY", static=True) assert attribute == 0 attribute = pipeline.get_attribute("config", "CPU", static=True) assert attribute == 0 attribute = pipeline.get_attribute("config", "PIXSCALE", static=True) assert attribute == pytest.approx(0.0, rel=self.limit, abs=0.0) attribute = pipeline.get_attribute("config", "INSTRUMENT", static=True) assert attribute == "None" create_config(self.test_dir + "PynPoint_config.ini") def test_create_pipeline_path_missing(self) -> None: dir_non_exists = self.test_dir + "none_dir/" dir_exists = self.test_dir with pytest.raises(AssertionError) as error: Pypeline(dir_non_exists, dir_exists, dir_exists) assert ( str(error.value) == "The folder that was chosen for the working place does not " "exist: " + self.test_dir + "none_dir/." ) with pytest.raises(AssertionError) as error: Pypeline(dir_exists, dir_non_exists, dir_exists) assert ( str(error.value) == "The folder that was chosen for the input place does not " "exist: " + self.test_dir + "none_dir/." ) with pytest.raises(AssertionError) as error: Pypeline(dir_exists, dir_exists, dir_non_exists) assert ( str(error.value) == "The folder that was chosen for the output place does not " "exist: " + self.test_dir + "none_dir/." ) def test_create_pipeline_existing_database(self) -> None: np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "w") as hdf_file: dset = hdf_file.create_dataset("images", data=images) dset.attrs["PIXSCALE"] = 0.01 pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) data = pipeline.get_data("images") assert np.mean(data) == pytest.approx( 1.1824138000882435e-05, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) assert pipeline.get_attribute("images", "PIXSCALE") == 0.01 os.remove(self.test_dir + "PynPoint_database.hdf5") def test_create_pipeline_new_database(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) pipeline.m_data_storage.open_connection() pipeline.m_data_storage.close_connection() del pipeline os.remove(self.test_dir + "PynPoint_database.hdf5") def test_add_module(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) module = FitsReadingModule(name_in="read1", input_dir=None, image_tag="im_arr1") assert pipeline.add_module(module) is None module = FitsReadingModule( name_in="read2", input_dir=self.test_dir, image_tag="im_arr2" ) assert pipeline.add_module(module) is None with pytest.warns(UserWarning) as warning: pipeline.add_module(module) assert len(warning) == 1 assert ( warning[0].message.args[0] == "Names of pipeline modules that are added to the " "Pypeline need to be unique. The current pipeline " "module, 'read2', does already exist in the " "Pypeline dictionary so the previous module with " "the same name will be overwritten." ) module = BadPixelSigmaFilterModule( name_in="badpixel", image_in_tag="im_arr1", image_out_tag="im_out" ) assert pipeline.add_module(module) is None module = FitsWritingModule( name_in="write1", file_name="result.fits", data_tag="im_arr1" ) assert pipeline.add_module(module) is None module = FitsWritingModule( name_in="write2", file_name="result.fits", data_tag="im_arr1", output_dir=self.test_dir, ) assert pipeline.add_module(module) is None assert pipeline.run() is None assert pipeline.get_module_names() == [ "read1", "read2", "badpixel", "write1", "write2", ] os.remove(self.test_dir + "result.fits") os.remove(self.test_dir + "PynPoint_database.hdf5") def test_run_module(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) module = FitsReadingModule(name_in="read", image_tag="im_arr") assert pipeline.add_module(module) is None assert pipeline.run_module("read") is None os.remove(self.test_dir + "PynPoint_database.hdf5") def test_run_module_wrong_tag(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) module = FitsReadingModule(name_in="read") pipeline.add_module(module) module = FitsWritingModule( name_in="write", file_name="result.fits", data_tag="im_list" ) pipeline.add_module(module) module = BadPixelSigmaFilterModule( name_in="badpixel", image_in_tag="im_list", image_out_tag="im_out" ) pipeline.add_module(module) with pytest.raises(AttributeError) as error: pipeline.run_module("badpixel") assert ( str(error.value) == "Pipeline module 'badpixel' is looking for data under a " "tag which does not exist in the database." ) with pytest.raises(AttributeError) as error: pipeline.run_module("write") assert ( str(error.value) == "Pipeline module 'write' is looking for data under a tag " "which does not exist in the database." ) with pytest.raises(AttributeError) as error: pipeline.run() assert ( str(error.value) == "Pipeline module 'write' is looking for data under a tag " "which is not created by a previous module or the data does " "not exist in the database." ) assert pipeline.validate_pipeline_module("test") == (False, "test") os.remove(self.test_dir + "PynPoint_database.hdf5") def test_run_module_non_existing(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) with pytest.warns(UserWarning) as warning: pipeline.run_module("test") assert len(warning) == 1 assert warning[0].message.args[0] == "Pipeline module 'test' not found." os.remove(self.test_dir + "PynPoint_database.hdf5") def test_remove_module(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) module = FitsReadingModule(name_in="read") pipeline.add_module(module) module = BadPixelSigmaFilterModule( name_in="badpixel", image_in_tag="im_arr1", image_out_tag="im_out" ) pipeline.add_module(module) assert pipeline.get_module_names() == ["read", "badpixel"] assert pipeline.remove_module("read") assert pipeline.get_module_names() == ["badpixel"] assert pipeline.remove_module("badpixel") with pytest.warns(UserWarning) as warning: pipeline.remove_module("test") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Pipeline module 'test' is not found in the " "Pypeline dictionary so it could not be removed. " "The dictionary contains the following modules: []." ) os.remove(self.test_dir + "PynPoint_database.hdf5") def test_get_shape(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) module = FitsReadingModule(name_in="read", image_tag="images") pipeline.add_module(module) pipeline.run_module("read") assert pipeline.get_shape("images") == (5, 11, 11) def test_get_tags(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) assert pipeline.get_tags() == ["images"] def test_list_attributes(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) attr_dict = pipeline.list_attributes("images") assert len(attr_dict) == 11 assert attr_dict["INSTRUMENT"] == "IMAGER" assert attr_dict["PIXSCALE"] == 0.027 assert attr_dict["NFRAMES"] == [5] assert attr_dict["PARANG_START"] == [10.0] def test_set_and_get_attribute(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) pipeline.set_attribute("images", "PIXSCALE", 0.1, static=True) pipeline.set_attribute( "images", "PARANG", np.arange(1.0, 11.0, 1.0), static=False ) attribute = pipeline.get_attribute("images", "PIXSCALE", static=True) assert attribute == pytest.approx(0.1, rel=self.limit, abs=0.0) attribute = pipeline.get_attribute("images", "PARANG", static=False) assert attribute == pytest.approx( np.arange(1.0, 11.0, 1.0), rel=self.limit, abs=0.0 ) pipeline.set_attribute( "images", "PARANG", np.arange(10.0, 21.0, 1.0), static=False ) attribute = pipeline.get_attribute("images", "PARANG", static=False) assert attribute == pytest.approx( np.arange(10.0, 21.0, 1.0), rel=self.limit, abs=0.0 ) def test_get_data_range(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) data = pipeline.get_data("images", data_range=(0, 2)) assert data.shape == (2, 11, 11) def test_delete_data(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) pipeline.delete_data("images") assert len(pipeline.get_tags()) == 0 def test_delete_not_found(self) -> None: pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) with pytest.warns(UserWarning) as warning: pipeline.delete_data("images") assert len(warning) == 2 assert ( warning[0].message.args[0] == "Dataset 'images' not found in the database." ) assert ( warning[1].message.args[0] == "Attributes of 'images' not found in the database." ) def test_omp_num_threads(self) -> None: os.environ["OMP_NUM_THREADS"] = "2" pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) pipeline.run() PynPoint-PynPoint-e5d0637/tests/test_processing/000077500000000000000000000000001512720201400217565ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_processing/__init__.py000066400000000000000000000000001512720201400240550ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_processing/test_background.py000066400000000000000000000221341512720201400255100ustar00rootroot00000000000000import os import numpy as np import pytest from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.background import ( LineSubtractionModule, MeanBackgroundSubtractionModule, NoddingBackgroundModule, SimpleBackgroundSubtractionModule, ) from pynpoint.processing.pcabackground import DitheringBackgroundModule from pynpoint.processing.stacksubset import StackCubesModule from pynpoint.util.tests import ( create_config, create_dither_data, create_star_data, remove_test_data, ) class TestBackground: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_dither_data(self.test_dir + "dither") create_star_data(self.test_dir + "science") create_star_data(self.test_dir + "sky") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["dither", "science", "sky"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="dither", input_dir=self.test_dir + "dither" ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("dither") assert np.sum(data) == pytest.approx( 211.20534661914408, rel=self.limit, abs=0.0 ) assert data.shape == (20, 21, 21) module = FitsReadingModule( name_in="read2", image_tag="science", input_dir=self.test_dir + "science" ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("science") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) module = FitsReadingModule( name_in="read3", image_tag="sky", input_dir=self.test_dir + "sky" ) self.pipeline.add_module(module) self.pipeline.run_module("read3") data = self.pipeline.get_data("sky") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_simple_background(self) -> None: module = SimpleBackgroundSubtractionModule( shift=5, name_in="simple", image_in_tag="dither", image_out_tag="simple" ) self.pipeline.add_module(module) self.pipeline.run_module("simple") data = self.pipeline.get_data("simple") assert np.sum(data) == pytest.approx(0.0, rel=0.0, abs=1e-10) assert data.shape == (20, 21, 21) def test_mean_background_shift(self) -> None: module = MeanBackgroundSubtractionModule( shift=5, cubes=1, name_in="mean2", image_in_tag="dither", image_out_tag="mean2", ) self.pipeline.add_module(module) self.pipeline.run_module("mean2") data = self.pipeline.get_data("mean2") assert np.sum(data) == pytest.approx(2.473864361018551, rel=self.limit, abs=0.0) assert data.shape == (20, 21, 21) def test_mean_background_nframes(self) -> None: module = MeanBackgroundSubtractionModule( shift=None, cubes=1, name_in="mean1", image_in_tag="dither", image_out_tag="mean1", ) self.pipeline.add_module(module) self.pipeline.run_module("mean1") data = self.pipeline.get_data("mean1") assert np.sum(data) == pytest.approx(2.473864361018551, rel=self.limit, abs=0.0) assert data.shape == (20, 21, 21) def test_dithering_attributes(self) -> None: module = DitheringBackgroundModule( name_in="pca_dither1", image_in_tag="dither", image_out_tag="pca_dither1", center=None, cubes=None, size=0.2, gaussian=0.05, subframe=0.1, pca_number=1, mask_star=0.05, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_dither1") data = self.pipeline.get_data("dither_dither_crop1") assert np.sum(data) == pytest.approx(54.62410860562912, rel=self.limit, abs=0.0) assert data.shape == (20, 9, 9) data = self.pipeline.get_data("dither_dither_star1") assert np.sum(data) == pytest.approx( 54.873885838788595, rel=self.limit, abs=0.0 ) assert data.shape == (5, 9, 9) data = self.pipeline.get_data("dither_dither_mean1") assert np.sum(data) == pytest.approx( 54.204960755115245, rel=self.limit, abs=0.0 ) assert data.shape == (5, 9, 9) data = self.pipeline.get_data("dither_dither_background1") assert np.sum(data) == pytest.approx( -0.24977723315947564, rel=self.limit, abs=0.0 ) assert data.shape == (15, 9, 9) data = self.pipeline.get_data("dither_dither_pca_fit1") assert np.sum(data) == pytest.approx(-0.6816458444287745, rel=1e-5, abs=0.0) assert data.shape == (5, 9, 9) data = self.pipeline.get_data("dither_dither_pca_res1") assert np.sum(data) == pytest.approx(55.63879076093719, rel=1e-6, abs=0.0) assert data.shape == (5, 9, 9) data = self.pipeline.get_data("dither_dither_pca_mask1") assert np.sum(data) == pytest.approx(360.0, rel=self.limit, abs=0.0) assert data.shape == (5, 9, 9) data = self.pipeline.get_data("pca_dither1") assert np.sum(data) == pytest.approx(208.24417329569593, rel=1e-6, abs=0.0) assert data.shape == (20, 9, 9) attr = self.pipeline.get_attribute( "dither_dither_pca_res1", "STAR_POSITION", static=False ) assert np.sum(attr) == pytest.approx(40.0, rel=self.limit, abs=0.0) assert attr.shape == (5, 2) def test_dithering_center(self) -> None: module = DitheringBackgroundModule( name_in="pca_dither2", image_in_tag="dither", image_out_tag="pca_dither2", center=[(5, 5), (5, 15), (15, 15), (15, 5)], cubes=1, size=0.2, gaussian=0.05, subframe=None, pca_number=1, mask_star=0.05, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_dither2") data = self.pipeline.get_data("pca_dither2") assert np.sum(data) == pytest.approx(208.24417332523367, rel=1e-6, abs=0.0) assert data.shape == (20, 9, 9) def test_nodding_background(self) -> None: module = StackCubesModule( name_in="mean", image_in_tag="sky", image_out_tag="mean", combine="mean" ) self.pipeline.add_module(module) self.pipeline.run_module("mean") data = self.pipeline.get_data("mean") assert np.sum(data) == pytest.approx( 21.108557759610548, rel=self.limit, abs=0.0 ) assert data.shape == (2, 11, 11) attr = self.pipeline.get_attribute("mean", "INDEX", static=False) assert np.sum(attr) == pytest.approx(1, rel=self.limit, abs=0.0) assert attr.shape == (2,) attr = self.pipeline.get_attribute("mean", "NFRAMES", static=False) assert np.sum(attr) == pytest.approx(2, rel=self.limit, abs=0.0) assert attr.shape == (2,) module = NoddingBackgroundModule( name_in="nodding", sky_in_tag="mean", science_in_tag="science", image_out_tag="nodding", mode="both", ) self.pipeline.add_module(module) self.pipeline.run_module("nodding") data = self.pipeline.get_data("nodding") assert np.sum(data) == pytest.approx(1.793466459074705, rel=self.limit, abs=0.0) assert data.shape == (10, 11, 11) def test_line_background_mean(self) -> None: module = LineSubtractionModule( name_in="line1", image_in_tag="science", image_out_tag="science_line1", combine="mean", mask=0.1, ) self.pipeline.add_module(module) self.pipeline.run_module("line1") data = self.pipeline.get_data("science_line1") assert np.sum(data) == pytest.approx( 104.55443019231085, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_line_background_median(self) -> None: module = LineSubtractionModule( name_in="line2", image_in_tag="science", image_out_tag="science_line2", combine="median", mask=0.1, ) self.pipeline.add_module(module) self.pipeline.run_module("line2") data = self.pipeline.get_data("science_line2") assert np.sum(data) == pytest.approx( 106.09825573198366, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) PynPoint-PynPoint-e5d0637/tests/test_processing/test_badpixel.py000066400000000000000000000207231512720201400251630ustar00rootroot00000000000000import os import math import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.processing.badpixel import ( BadPixelSigmaFilterModule, BadPixelMapModule, BadPixelInterpolationModule, BadPixelTimeFilterModule, ReplaceBadPixelsModule, ) from pynpoint.util.tests import create_config, remove_test_data class TestBadPixel: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) dark = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) flat = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) images[0, 5, 5] = 1.0 dark[:, 5, 5] = 1.0 flat[:, 8, 8] = -1.0 flat[:, 9, 9] = -1.0 flat[:, 10, 10] = -1.0 with h5py.File(self.test_dir + "PynPoint_database.hdf5", "w") as hdf_file: hdf_file.create_dataset("images", data=images) hdf_file.create_dataset("dark", data=dark) hdf_file.create_dataset("flat", data=flat) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir) def test_bad_pixel_sigma_filter(self) -> None: module = BadPixelSigmaFilterModule( name_in="sigma1", image_in_tag="images", image_out_tag="sigma1", map_out_tag="None", box=9, sigma=3.0, iterate=5, ) self.pipeline.add_module(module) self.pipeline.run_module("sigma1") data = self.pipeline.get_data("sigma1") assert np.sum(data) == pytest.approx( 0.006513475520308432, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_bad_pixel_map_out(self) -> None: module = BadPixelSigmaFilterModule( name_in="sigma2", image_in_tag="images", image_out_tag="sigma2", map_out_tag="bpmap", box=9, sigma=2.0, iterate=3, ) self.pipeline.add_module(module) self.pipeline.run_module("sigma2") data = self.pipeline.get_data("sigma2") assert data[0, 0, 0] == pytest.approx( -2.4570591355257687e-05, rel=self.limit, abs=0.0 ) assert data[0, 5, 5] == pytest.approx( 9.903775276151606e-06, rel=self.limit, abs=0.0 ) assert np.sum(data) == pytest.approx( 0.011777887008566097, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) data = self.pipeline.get_data("bpmap") assert data[0, 1, 1] == pytest.approx(1.0, rel=self.limit, abs=0.0) assert data[0, 5, 5] == pytest.approx(0.0, rel=self.limit, abs=0.0) assert np.sum(data) == pytest.approx(519.0, rel=self.limit, abs=0.0) assert data.shape == (5, 11, 11) def test_bad_pixel_map(self) -> None: module = BadPixelMapModule( name_in="bp_map1", dark_in_tag="dark", flat_in_tag="flat", bp_map_out_tag="bp_map1", dark_threshold=0.99, flat_threshold=-0.99, ) self.pipeline.add_module(module) self.pipeline.run_module("bp_map1") data = self.pipeline.get_data("bp_map1") assert data[0, 0, 0] == pytest.approx(1.0, rel=self.limit, abs=0.0) assert data[0, 5, 5] == pytest.approx(0.0, rel=self.limit, abs=0.0) assert np.sum(data) == pytest.approx(117.0, rel=self.limit, abs=0.0) assert data.shape == (1, 11, 11) def test_map_no_dark(self) -> None: module = BadPixelMapModule( name_in="bp_map2", dark_in_tag=None, flat_in_tag="flat", bp_map_out_tag="bp_map2", dark_threshold=0.99, flat_threshold=-0.99, ) self.pipeline.add_module(module) self.pipeline.run_module("bp_map2") data = self.pipeline.get_data("bp_map2") assert np.sum(data) == pytest.approx(118.0, rel=self.limit, abs=0.0) assert data.shape == (1, 11, 11) def test_map_no_flat(self) -> None: module = BadPixelMapModule( name_in="bp_map3", dark_in_tag="dark", flat_in_tag=None, bp_map_out_tag="bp_map3", dark_threshold=0.99, flat_threshold=-0.99, ) self.pipeline.add_module(module) self.pipeline.run_module("bp_map3") data = self.pipeline.get_data("bp_map3") assert np.sum(data) == pytest.approx(120.0, rel=self.limit, abs=0.0) assert data.shape == (1, 11, 11) def test_bad_pixel_interpolation(self) -> None: module = BadPixelInterpolationModule( name_in="interpolation", image_in_tag="images", bad_pixel_map_tag="bp_map1", image_out_tag="interpolation", iterations=10, ) self.pipeline.add_module(module) self.pipeline.run_module("interpolation") data = self.pipeline.get_data("interpolation") assert data[0, 0, 0] == pytest.approx(0.00032486907273264834, rel=1e-6, abs=0.0) assert data[0, 5, 5] == pytest.approx( -1.4292408645473845e-05, rel=1e-6, abs=0.0 ) assert np.sum(data) == pytest.approx(0.008683344127872174, rel=1e-6, abs=0.0) assert data.shape == (5, 11, 11) def test_bad_pixel_time(self) -> None: module = BadPixelTimeFilterModule( name_in="time", image_in_tag="images", image_out_tag="time", sigma=(2.0, 2.0), ) self.pipeline.add_module(module) self.pipeline.run_module("time") data = self.pipeline.get_data("time") assert data[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) assert data[0, 5, 5] == pytest.approx( -0.00017468532119886812, rel=self.limit, abs=0.0 ) assert np.sum(data) == pytest.approx( 0.004175672043832705, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_replace_bad_pixels(self) -> None: module = ReplaceBadPixelsModule( name_in="replace1", image_in_tag="images", map_in_tag="bp_map1", image_out_tag="replace", size=2, replace="mean", ) self.pipeline.add_module(module) self.pipeline.run_module("replace1") data = self.pipeline.get_data("replace") assert data[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) assert data[0, 5, 5] == pytest.approx( 4.260114004413933e-05, rel=self.limit, abs=0.0 ) assert np.sum(data) == pytest.approx( 0.00883357395370896, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) module = ReplaceBadPixelsModule( name_in="replace2", image_in_tag="images", map_in_tag="bp_map1", image_out_tag="replace", size=2, replace="median", ) self.pipeline.add_module(module) self.pipeline.run_module("replace2") data = self.pipeline.get_data("replace") assert data[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) assert data[0, 5, 5] == pytest.approx( 4.327154179438619e-05, rel=self.limit, abs=0.0 ) assert np.sum(data) == pytest.approx( 0.008489525337709688, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) module = ReplaceBadPixelsModule( name_in="replace3", image_in_tag="images", map_in_tag="bp_map1", image_out_tag="replace", size=2, replace="nan", ) self.pipeline.add_module(module) self.pipeline.run_module("replace3") data = self.pipeline.get_data("replace") assert data[0, 0, 0] == pytest.approx( 0.00032486907273264834, rel=self.limit, abs=0.0 ) assert math.isnan(data[0, 5, 5]) assert np.nansum(data) == pytest.approx( 0.009049653234723834, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) PynPoint-PynPoint-e5d0637/tests/test_processing/test_basic.py000066400000000000000000000106121512720201400244500ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.basic import ( SubtractImagesModule, AddImagesModule, RotateImagesModule, RepeatImagesModule, ) from pynpoint.util.tests import create_config, remove_test_data, create_star_data class TestBasic: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "data1") create_star_data(self.test_dir + "data2") create_star_data(self.test_dir + "data3") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["data1", "data2", "data3"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="data1", input_dir=self.test_dir + "data1", overwrite=True, check=True, ) self.pipeline.add_module(module) module = FitsReadingModule( name_in="read2", image_tag="data2", input_dir=self.test_dir + "data2", overwrite=True, check=True, ) self.pipeline.add_module(module) module = FitsReadingModule( name_in="read3", image_tag="data3", input_dir=self.test_dir + "data3", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") self.pipeline.run_module("read2") self.pipeline.run_module("read3") data = self.pipeline.get_data("data1") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) data = self.pipeline.get_data("data2") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) data = self.pipeline.get_data("data3") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_subtract_images(self) -> None: module = SubtractImagesModule( name_in="subtract", image_in_tags=("data1", "data2"), image_out_tag="subtract", scaling=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("subtract") data = self.pipeline.get_data("subtract") assert np.sum(data) == pytest.approx(0.0, rel=self.limit, abs=0.0) assert data.shape == (10, 11, 11) def test_add_images(self) -> None: module = AddImagesModule( name_in="add", image_in_tags=("data1", "data2"), image_out_tag="add", scaling=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("add") data = self.pipeline.get_data("add") assert np.sum(data) == pytest.approx( 211.08557759610554, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_rotate_images(self) -> None: module = RotateImagesModule( name_in="rotate", image_in_tag="data1", image_out_tag="rotate", angle=10.0 ) self.pipeline.add_module(module) self.pipeline.run_module("rotate") data = self.pipeline.get_data("rotate") assert np.sum(data) == pytest.approx( 105.86657256219851, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_repeat_images(self) -> None: module = RepeatImagesModule( name_in="repeat", image_in_tag="data1", image_out_tag="repeat", repeat=2 ) self.pipeline.add_module(module) self.pipeline.run_module("repeat") data1 = self.pipeline.get_data("data1") assert data1.shape == (10, 11, 11) data2 = self.pipeline.get_data("repeat") assert data2.shape == (20, 11, 11) assert data1 == pytest.approx(data2[0:10,], rel=self.limit, abs=0.0) assert data1 == pytest.approx(data2[10:20,], rel=self.limit, abs=0.0) PynPoint-PynPoint-e5d0637/tests/test_processing/test_centering.py000066400000000000000000000345401512720201400253530ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.centering import ( StarAlignmentModule, ShiftImagesModule, WaffleCenteringModule, FitCenterModule, ) from pynpoint.processing.extract import StarExtractionModule from pynpoint.processing.resizing import AddLinesModule from pynpoint.util.tests import ( create_config, create_star_data, create_waffle_data, remove_test_data, ) class TestCentering: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "star") create_waffle_data(self.test_dir + "waffle") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(path=self.test_dir, folders=["star", "waffle"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="star", input_dir=self.test_dir + "star", overwrite=True, check=True, ) self.pipeline.add_module(module) module = FitsReadingModule( name_in="read2", image_tag="waffle", input_dir=self.test_dir + "waffle", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") self.pipeline.run_module("read2") data = self.pipeline.get_data("star") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) data = self.pipeline.get_data("waffle") assert np.sum(data) == pytest.approx(4.000000000000196, rel=self.limit, abs=0.0) assert data.shape == (1, 101, 101) def test_star_extract(self) -> None: module = StarExtractionModule( name_in="extract1", image_in_tag="star", image_out_tag="extract1", index_out_tag="index", image_size=0.2, fwhm_star=0.1, position=None, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("extract1") assert len(warning) == 3 assert ( warning[0].message.args[0] == "Can not store the attribute 'INSTRUMENT' " "because the dataset 'index' does not exist." ) data = self.pipeline.get_data("extract1") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_star_align(self) -> None: module = StarAlignmentModule( name_in="align1", image_in_tag="extract1", ref_image_in_tag=None, image_out_tag="align1", accuracy=10, resize=2.0, num_references=10, subframe=None, ) self.pipeline.add_module(module) self.pipeline.run_module("align1") data = self.pipeline.get_data("align1") assert np.sum(data) == pytest.approx(104.7074742320535, rel=self.limit, abs=0.0) assert data.shape == (10, 18, 18) def test_star_align_subframe(self) -> None: module = StarAlignmentModule( name_in="align2", image_in_tag="extract1", ref_image_in_tag=None, image_out_tag="align2", accuracy=10, resize=None, num_references=10, subframe=0.1, ) self.pipeline.add_module(module) self.pipeline.run_module("align2") data = self.pipeline.get_data("align2") assert np.sum(data) == pytest.approx( 104.39031104541652, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_star_align_ref(self) -> None: module = StarAlignmentModule( name_in="align3", image_in_tag="extract1", ref_image_in_tag="align2", image_out_tag="align3", accuracy=10, resize=None, num_references=10, subframe=None, ) self.pipeline.add_module(module) self.pipeline.run_module("align3") data = self.pipeline.get_data("align3") assert np.sum(data) == pytest.approx( 104.46997194330757, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_star_align_number_ref(self) -> None: module = StarAlignmentModule( name_in="align4", image_in_tag="extract1", ref_image_in_tag="align2", image_out_tag="align4", accuracy=10, resize=None, num_references=20, subframe=None, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("align4") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Number of available images (10) is smaller than " "num_references (20). Using all available images " "instead." ) data = self.pipeline.get_data("align4") assert np.sum(data) == pytest.approx( 104.46997194330757, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_shift_images_spline(self) -> None: module = ShiftImagesModule( shift_xy=(1.0, 2.0), interpolation="spline", name_in="shift1", image_in_tag="align1", image_out_tag="shift", ) self.pipeline.add_module(module) self.pipeline.run_module("shift1") data = self.pipeline.get_data("shift") assert np.sum(data) == pytest.approx( 104.20425101355244, rel=self.limit, abs=0.0 ) assert data.shape == (10, 18, 18) def test_shift_images_fft(self) -> None: module = ShiftImagesModule( shift_xy=(1.0, 2.0), interpolation="fft", name_in="shift2", image_in_tag="align1", image_out_tag="shift_fft", ) self.pipeline.add_module(module) self.pipeline.run_module("shift2") data = self.pipeline.get_data("shift_fft") assert np.sum(data) == pytest.approx(104.7074742320535, rel=self.limit, abs=0.0) assert data.shape == (10, 18, 18) def test_waffle_center_odd(self) -> None: module = AddLinesModule( name_in="add", image_in_tag="star", image_out_tag="star_add", lines=(45, 45, 45, 45), ) self.pipeline.add_module(module) self.pipeline.run_module("add") data = self.pipeline.get_data("star_add") assert np.sum(data) == pytest.approx( 105.54278879805278, rel=self.limit, abs=0.0 ) assert data.shape == (10, 101, 101) module = WaffleCenteringModule( size=0.2, center=(50, 50), name_in="waffle", image_in_tag="star_add", center_in_tag="waffle", image_out_tag="center", radius=35.0, pattern="x", sigma=0.05, ) self.pipeline.add_module(module) with pytest.warns(DeprecationWarning) as warning: self.pipeline.run_module("waffle") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The 'pattern' parameter will be deprecated in a " "future release. Please Use the 'angle' " "parameter instead and set it to 45.0 degrees." ) data = self.pipeline.get_data("center") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) attr = self.pipeline.get_attribute("center", "History: WaffleCenteringModule") assert attr == "[x, y] = [50.0, 50.0]" def test_waffle_center_even(self) -> None: module = AddLinesModule( name_in="add1", image_in_tag="star_add", image_out_tag="star_even", lines=(0, 1, 0, 1), ) self.pipeline.add_module(module) self.pipeline.run_module("add1") data = self.pipeline.get_data("star_even") assert np.sum(data) == pytest.approx( 105.54278879805275, rel=self.limit, abs=0.0 ) assert data.shape == (10, 102, 102) module = AddLinesModule( name_in="add2", image_in_tag="waffle", image_out_tag="waffle_even", lines=(0, 1, 0, 1), ) self.pipeline.add_module(module) self.pipeline.run_module("add2") data = self.pipeline.get_data("waffle_even") assert np.sum(data) == pytest.approx(4.000000000000195, rel=self.limit, abs=0.0) assert data.shape == (1, 102, 102) module = ShiftImagesModule( shift_xy=(0.5, 0.5), interpolation="spline", name_in="shift3", image_in_tag="star_even", image_out_tag="star_shift", ) self.pipeline.add_module(module) self.pipeline.run_module("shift3") data = self.pipeline.get_data("star_shift") assert np.sum(data) == pytest.approx( 105.54278879805274, rel=self.limit, abs=0.0 ) assert data.shape == (10, 102, 102) module = ShiftImagesModule( shift_xy=(0.5, 0.5), interpolation="spline", name_in="shift4", image_in_tag="waffle_even", image_out_tag="waffle_shift", ) self.pipeline.add_module(module) self.pipeline.run_module("shift4") data = self.pipeline.get_data("waffle_shift") assert np.sum(data) == pytest.approx(4.000000000000194, rel=self.limit, abs=0.0) assert data.shape == (1, 102, 102) module = WaffleCenteringModule( size=0.2, center=(50, 50), name_in="waffle_even", image_in_tag="star_shift", center_in_tag="waffle_shift", image_out_tag="center_even", radius=35.0, pattern="x", sigma=0.05, ) self.pipeline.add_module(module) with pytest.warns(DeprecationWarning) as warning: self.pipeline.run_module("waffle_even") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The 'pattern' parameter will be deprecated in a " "future release. Please Use the 'angle' " "parameter instead and set it to 45.0 degrees." ) data = self.pipeline.get_data("center_even") assert np.sum(data) == pytest.approx( 105.22695036281449, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) attr = self.pipeline.get_attribute( "center_even", "History: WaffleCenteringModule" ) assert attr == "[x, y] = [50.5, 50.5]" def test_fit_center_full(self) -> None: module = FitCenterModule( name_in="fit1", image_in_tag="shift", fit_out_tag="fit_full", mask_out_tag="mask", method="full", mask_radii=(None, 0.1), sign="positive", model="gaussian", guess=(1.0, 2.0, 3.0, 3.0, 0.01, 0.0, 0.0), ) self.pipeline.add_module(module) self.pipeline.run_module("fit1") data = self.pipeline.get_data("fit_full") assert np.mean(data[:, 0]) == pytest.approx(0.94, rel=0.0, abs=0.05) assert np.mean(data[:, 2]) == pytest.approx(2.07, rel=0.0, abs=0.05) assert np.mean(data[:, 4]) == pytest.approx(0.0676, rel=0.0, abs=0.05) assert np.mean(data[:, 6]) == pytest.approx(0.0665, rel=0.0, abs=0.05) assert np.mean(data[:, 8]) == pytest.approx(0.24, rel=0.0, abs=0.05) assert data.shape == (10, 14) data = self.pipeline.get_data("mask") assert np.sum(data) == pytest.approx( 103.45599730750453, rel=self.limit, abs=0.0 ) assert data.shape == (10, 18, 18) def test_fit_center_mean(self) -> None: module = FitCenterModule( name_in="fit2", image_in_tag="shift", fit_out_tag="fit_mean", mask_out_tag=None, method="mean", mask_radii=(None, 0.1), sign="positive", model="moffat", guess=(1.0, 2.0, 3.0, 3.0, 0.01, 0.0, 0.0, 1.0), ) self.pipeline.add_module(module) self.pipeline.run_module("fit2") data = self.pipeline.get_data("fit_mean") assert np.mean(data[:, 0]) == pytest.approx(0.94, rel=0.0, abs=0.01) assert np.mean(data[:, 2]) == pytest.approx(2.059, rel=0.0, abs=0.01) assert np.mean(data[:, 4]) == pytest.approx(0.083, rel=0.0, abs=0.01) assert np.mean(data[:, 6]) == pytest.approx(0.08, rel=0.0, abs=0.01) assert np.mean(data[:, 8]) == pytest.approx(0.242, rel=0.0, abs=0.01) assert data.shape == (10, 16) def test_shift_images_tag(self) -> None: module = ShiftImagesModule( shift_xy="fit_full", interpolation="spline", name_in="shift5", image_in_tag="shift", image_out_tag="shift_tag_1", ) self.pipeline.add_module(module) self.pipeline.run_module("shift5") data = self.pipeline.get_data("shift_tag_1") assert np.sum(data) == pytest.approx(103.76410960085425, rel=1e-6, abs=0.9) assert data.shape == (10, 18, 18) def test_shift_images_tag_mean(self) -> None: module = ShiftImagesModule( shift_xy="fit_mean", interpolation="spline", name_in="shift6", image_in_tag="shift", image_out_tag="shift_tag_2", ) self.pipeline.add_module(module) self.pipeline.run_module("shift6") data = self.pipeline.get_data("shift_tag_2") assert np.sum(data) == pytest.approx(103.42285439876926, rel=1e-6, abs=0.0) assert data.shape == (10, 18, 18) PynPoint-PynPoint-e5d0637/tests/test_processing/test_darkflat.py000066400000000000000000000077071512720201400251720ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.processing.darkflat import DarkCalibrationModule, FlatCalibrationModule from pynpoint.util.tests import create_config, remove_test_data class TestDarkFlat: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" np.random.seed(1) images = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) dark = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) flat = np.random.normal(loc=0, scale=2e-4, size=(5, 11, 11)) crop = np.random.normal(loc=0, scale=2e-4, size=(5, 7, 7)) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "w") as hdf_file: hdf_file.create_dataset("images", data=images) hdf_file.create_dataset("dark", data=dark) hdf_file.create_dataset("flat", data=flat) hdf_file.create_dataset("crop", data=crop) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir) def test_input_data(self) -> None: data = self.pipeline.get_data("dark") assert np.sum(data) == pytest.approx( -2.0262345764957305e-05, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) data = self.pipeline.get_data("flat") assert np.sum(data) == pytest.approx( 0.0076413379497053, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_dark_calibration(self) -> None: module = DarkCalibrationModule( name_in="dark", image_in_tag="images", dark_in_tag="dark", image_out_tag="dark_cal", ) self.pipeline.add_module(module) self.pipeline.run_module("dark") data = self.pipeline.get_data("dark_cal") assert np.sum(data) == pytest.approx( 0.00717386583629883, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_flat_calibration(self) -> None: module = FlatCalibrationModule( name_in="flat", image_in_tag="dark_cal", flat_in_tag="flat", image_out_tag="flat_cal", ) self.pipeline.add_module(module) self.pipeline.run_module("flat") data = self.pipeline.get_data("flat_cal") assert np.sum(data) == pytest.approx( 0.00717439711853594, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_flat_crop(self) -> None: module = FlatCalibrationModule( name_in="flat_crop", image_in_tag="crop", flat_in_tag="flat", image_out_tag="flat_crop", ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("flat_crop") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The calibration images were cropped around their " "center to match the shape of the science images." ) data = self.pipeline.get_data("flat_crop") assert np.sum(data) == pytest.approx( -0.003242901413605404, rel=self.limit, abs=0.0 ) assert data.shape == (5, 7, 7) def test_flat_too_small(self) -> None: module = FlatCalibrationModule( name_in="flat_small", image_in_tag="flat", flat_in_tag="crop", image_out_tag="flat_small", ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("flat_small") assert ( str(error.value) == "Shape of the calibration images is smaller than the " "science images." ) PynPoint-PynPoint-e5d0637/tests/test_processing/test_extract.py000066400000000000000000000210131512720201400250360ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.extract import StarExtractionModule, ExtractBinaryModule from pynpoint.util.tests import ( create_config, create_star_data, create_fake_data, remove_test_data, ) class TestExtract: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "star") create_fake_data(self.test_dir + "binary") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(path=self.test_dir, folders=["star", "binary"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="star", input_dir=self.test_dir + "star", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("star") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) module = FitsReadingModule( name_in="read2", image_tag="binary", input_dir=self.test_dir + "binary", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("binary") assert np.sum(data) == pytest.approx( 11.012854046962481, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) self.pipeline.set_attribute( "binary", "PARANG", -1.0 * np.linspace(0.0, 180.0, 10), static=False ) def test_extract_position_none(self) -> None: module = StarExtractionModule( name_in="extract1", image_in_tag="star", image_out_tag="extract1", index_out_tag="index", image_size=0.2, fwhm_star=0.1, position=None, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("extract1") assert len(warning) == 3 assert ( warning[0].message.args[0] == "Can not store the attribute 'INSTRUMENT' because " "the dataset 'index' does not exist." ) assert ( warning[1].message.args[0] == "Can not store the attribute 'PIXSCALE' because " "the dataset 'index' does not exist." ) assert ( warning[2].message.args[0] == "Can not store the attribute 'History: " "StarExtractionModule' because the dataset " "'index' does not exist." ) data = self.pipeline.get_data("extract1") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_extract_center_none(self) -> None: module = StarExtractionModule( name_in="extract2", image_in_tag="star", image_out_tag="extract2", index_out_tag="index", image_size=0.2, fwhm_star=0.1, position=(None, None, 0.2), ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("extract2") assert len(warning) == 3 assert ( warning[0].message.args[0] == "Can not store the attribute 'INSTRUMENT' because " "the dataset 'index' does not exist." ) assert ( warning[1].message.args[0] == "Can not store the attribute 'PIXSCALE' because " "the dataset 'index' does not exist." ) assert ( warning[2].message.args[0] == "Can not store the attribute 'History: " "StarExtractionModule' because the dataset " "'index' does not exist." ) data = self.pipeline.get_data("extract2") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_extract_position(self) -> None: module = StarExtractionModule( name_in="extract7", image_in_tag="star", image_out_tag="extract7", index_out_tag=None, image_size=0.2, fwhm_star=0.1, position=(5, 5, 0.2), ) self.pipeline.add_module(module) self.pipeline.run_module("extract7") data = self.pipeline.get_data("extract7") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_extract_too_large(self) -> None: module = StarExtractionModule( name_in="extract3", image_in_tag="star", image_out_tag="extract3", index_out_tag=None, image_size=0.2, fwhm_star=0.1, position=(2, 2, 0.05), ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("extract3") assert len(warning) == 10 assert ( warning[0].message.args[0] == f"Chosen image size is too large to crop the image " f"around the brightest pixel (image index = 0, " f"pixel [x, y] = [2, 2]). Using the center of " f"the image instead." ) data = self.pipeline.get_data("extract3") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) def test_star_extract_cpu(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = StarExtractionModule( name_in="extract4", image_in_tag="star", image_out_tag="extract4", index_out_tag="index", image_size=0.2, fwhm_star=0.1, position=(2, 2, 0.05), ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("extract4") # assert len(warning) == 8 assert ( warning[0].message.args[0] == "The 'index_out_port' can only be used if " "CPU = 1. No data will be stored to this output port." ) assert ( warning[1].message.args[0] == "Chosen image size is too large to crop the image " "around the brightest pixel (image index = 0, " "pixel [x, y] = [2, 2]). Using the center of the " "image instead." ) def test_extract_binary(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 1 module = ExtractBinaryModule( pos_center=(10.0, 10.0), pos_binary=(10.0, 16.0), name_in="extract5", image_in_tag="binary", image_out_tag="extract5", image_size=0.15, search_size=0.07, filter_size=None, ) self.pipeline.add_module(module) self.pipeline.run_module("extract5") data = self.pipeline.get_data("extract5") assert np.sum(data) == pytest.approx( 1.3419098759577548, rel=self.limit, abs=0.0 ) assert data.shape == (10, 7, 7) def test_extract_binary_filter(self) -> None: module = ExtractBinaryModule( pos_center=(10.0, 10.0), pos_binary=(10.0, 16.0), name_in="extract6", image_in_tag="binary", image_out_tag="extract6", image_size=0.15, search_size=0.07, filter_size=0.05, ) self.pipeline.add_module(module) self.pipeline.run_module("extract6") data = self.pipeline.get_data("extract6") assert np.sum(data) == pytest.approx( 1.3789593661036972, rel=self.limit, abs=0.0 ) assert data.shape == (10, 7, 7) PynPoint-PynPoint-e5d0637/tests/test_processing/test_filter.py000066400000000000000000000032661512720201400246630ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.filter import GaussianFilterModule from pynpoint.util.tests import create_config, remove_test_data, create_star_data class TestFilter: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "data") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["data"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="data", input_dir=self.test_dir + "data", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("data") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_gaussian_filter(self) -> None: module = GaussianFilterModule( name_in="filter", image_in_tag="data", image_out_tag="filtered", fwhm=0.1 ) self.pipeline.add_module(module) self.pipeline.run_module("filter") data = self.pipeline.get_data("filtered") assert np.sum(data) == pytest.approx( 105.54278879805275, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) PynPoint-PynPoint-e5d0637/tests/test_processing/test_fluxposition.py000066400000000000000000000337741512720201400261500ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.fluxposition import ( FakePlanetModule, AperturePhotometryModule, FalsePositiveModule, SimplexMinimizationModule, MCMCsamplingModule, SystematicErrorModule, ) from pynpoint.processing.stacksubset import DerotateAndStackModule from pynpoint.processing.psfsubtraction import PcaPsfSubtractionModule from pynpoint.util.tests import ( create_config, create_star_data, create_fake_data, remove_test_data, ) class TestFluxPosition: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_fake_data(self.test_dir + "adi") create_star_data(self.test_dir + "psf", npix=21, pos_star=10.0) create_star_data(self.test_dir + "ref", npix=21, pos_star=10.0) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["adi", "psf", "ref"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="adi", input_dir=self.test_dir + "adi" ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("adi") assert np.sum(data) == pytest.approx( 11.012854046962481, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) self.pipeline.set_attribute( "adi", "PARANG", np.linspace(0.0, 180.0, 10), static=False ) module = FitsReadingModule( name_in="read2", image_tag="psf", input_dir=self.test_dir + "psf" ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("psf") assert np.sum(data) == pytest.approx( 108.43655133957289, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) module = FitsReadingModule( name_in="read3", image_tag="ref", input_dir=self.test_dir + "psf" ) self.pipeline.add_module(module) self.pipeline.run_module("read3") data = self.pipeline.get_data("ref") assert np.sum(data) == pytest.approx( 108.43655133957289, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) def test_aperture_photometry(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 1 module = AperturePhotometryModule( name_in="photometry1", image_in_tag="psf", phot_out_tag="photometry1", radius=0.1, position=None, ) self.pipeline.add_module(module) self.pipeline.run_module("photometry1") with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = AperturePhotometryModule( name_in="photometry2", image_in_tag="psf", phot_out_tag="photometry2", radius=0.1, position=None, ) self.pipeline.add_module(module) self.pipeline.run_module("photometry2") data = self.pipeline.get_data("photometry1") assert np.sum(data) == pytest.approx( 100.80648929590365, rel=self.limit, abs=0.0 ) assert data.shape == (10, 1) data_multi = self.pipeline.get_data("photometry2") assert data.shape == data_multi.shape assert data == pytest.approx(data_multi, rel=self.limit, abs=0.0) def test_aperture_photometry_position(self) -> None: module = AperturePhotometryModule( name_in="photometry3", image_in_tag="psf", phot_out_tag="photometry3", radius=0.1, position=(10.0, 10.0), ) self.pipeline.add_module(module) self.pipeline.run_module("photometry3") data = self.pipeline.get_data("photometry3") assert np.sum(data) == pytest.approx( 100.80648929590365, rel=self.limit, abs=0.0 ) assert data.shape == (10, 1) def test_fake_planet(self) -> None: module = FakePlanetModule( position=(0.2, 180.0), magnitude=2.5, psf_scaling=1.0, interpolation="spline", name_in="fake", image_in_tag="adi", psf_in_tag="psf", image_out_tag="fake", ) self.pipeline.add_module(module) self.pipeline.run_module("fake") data = self.pipeline.get_data("fake") assert np.sum(data) == pytest.approx( 21.273233520675586, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) def test_psf_subtraction(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=[ 1, ], name_in="pca", images_in_tag="fake", reference_in_tag="fake", res_mean_tag="res_mean", extra_rot=0.0, ) self.pipeline.add_module(module) self.pipeline.run_module("pca") data = self.pipeline.get_data("res_mean") assert np.sum(data) == pytest.approx( 0.013659056187572433, rel=self.limit, abs=0.0 ) assert data.shape == (1, 21, 21) def test_false_positive(self) -> None: module = FalsePositiveModule( position=(10.0, 2.0), aperture=0.06, ignore=True, name_in="false1", image_in_tag="res_mean", snr_out_tag="snr_fpf1", optimize=False, ) self.pipeline.add_module(module) self.pipeline.run_module("false1") data = self.pipeline.get_data("snr_fpf1") assert data[0, 1] == pytest.approx(2.0, rel=self.limit, abs=0.0) assert data[0, 2] == pytest.approx(0.216, rel=self.limit, abs=0.0) assert data[0, 3] == pytest.approx(180.0, rel=self.limit, abs=0.0) assert data[0, 4] == pytest.approx(26.661611583224417, rel=self.limit, abs=0.0) assert data[0, 5] == pytest.approx( 1.3375156927387672e-08, rel=self.limit, abs=0.0 ) assert data.shape == (1, 6) def test_false_positive_optimize(self) -> None: module = FalsePositiveModule( position=(10.0, 2.0), aperture=0.06, ignore=True, name_in="false2", image_in_tag="res_mean", snr_out_tag="snr_fpf2", optimize=True, offset=0.1, tolerance=0.01, ) self.pipeline.add_module(module) self.pipeline.run_module("false2") data = self.pipeline.get_data("snr_fpf2") assert data[0, 1] == pytest.approx(2.0747802734374985, rel=self.limit, abs=0.0) assert data[0, 2] == pytest.approx(0.2139883890923524, rel=self.limit, abs=0.0) assert data[0, 3] == pytest.approx(179.52168877335356, rel=self.limit, abs=0.0) assert data[0, 4] == pytest.approx(27.457328210661814, rel=self.limit, abs=0.0) assert data[0, 5] == pytest.approx( 1.0905578015907869e-08, rel=self.limit, abs=0.0 ) assert data.shape == (1, 6) def test_simplex_minimization_hessian(self) -> None: module = SimplexMinimizationModule( name_in="simplex1", image_in_tag="fake", psf_in_tag="psf", res_out_tag="simplex_res", flux_position_tag="flux_position", position=(10.0, 3.0), magnitude=2.5, psf_scaling=-1.0, merit="hessian", aperture=0.06, sigma=0.0, tolerance=0.1, pca_number=1, cent_size=0.06, edge_size=None, extra_rot=0.0, reference_in_tag=None, residuals="median", offset=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("simplex1") data = self.pipeline.get_data("simplex_res") assert np.sum(data) == pytest.approx( 0.2781582369128238, rel=self.limit, abs=0.0 ) assert data.shape == (35, 21, 21) data = self.pipeline.get_data("flux_position") assert data[24, 0] == pytest.approx(9.931627229080938, rel=self.limit, abs=0.0) assert data[24, 1] == pytest.approx(2.6575231481481456, rel=self.limit, abs=0.0) assert data[24, 2] == pytest.approx(0.1982554700445013, rel=self.limit, abs=0.0) assert data[24, 3] == pytest.approx(179.46648003649148, rel=self.limit, abs=0.0) assert data[24, 4] == pytest.approx(2.5256451474622708, rel=self.limit, abs=0.0) assert data.shape == (35, 6) def test_simplex_minimization_reference(self) -> None: module = SimplexMinimizationModule( name_in="simplex2", image_in_tag="fake", psf_in_tag="psf", res_out_tag="simplex_res_ref", flux_position_tag="flux_position_ref", position=(10.0, 3.0), magnitude=2.5, psf_scaling=-1.0, merit="poisson", aperture=0.06, sigma=0.0, tolerance=0.1, pca_number=1, cent_size=0.06, edge_size=None, extra_rot=0.0, reference_in_tag="ref", residuals="mean", ) self.pipeline.add_module(module) self.pipeline.run_module("simplex2") data = self.pipeline.get_data("simplex_res_ref") assert np.sum(data) == pytest.approx(9.734993454838076, rel=self.limit, abs=0.0) assert data.shape == (28, 21, 21) data = self.pipeline.get_data("flux_position_ref") assert data[27, 0] == pytest.approx(10.049019964116436, rel=self.limit, abs=0.0) assert data[27, 1] == pytest.approx(2.6444836362361936, rel=self.limit, abs=0.0) assert data[27, 2] == pytest.approx( 0.19860335205689572, rel=self.limit, abs=0.0 ) assert data[27, 3] == pytest.approx(180.38183525629643, rel=self.limit, abs=0.0) assert data[27, 4] == pytest.approx(2.5496922175196, rel=self.limit, abs=0.0) assert data.shape == (28, 6) def test_mcmc_sampling(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = DerotateAndStackModule( name_in="stack", image_in_tag="psf", image_out_tag="psf_stack", derotate=False, stack="mean", ) self.pipeline.add_module(module) self.pipeline.run_module("stack") data = self.pipeline.get_data("psf_stack") assert np.sum(data) == pytest.approx( 10.843655133957288, rel=self.limit, abs=0.0 ) assert data.shape == (1, 21, 21) module = MCMCsamplingModule( name_in="mcmc1", image_in_tag="adi", psf_in_tag="psf_stack", chain_out_tag="mcmc", param=(0.15, 0.0, 1.0), bounds=((0.1, 0.2), (-2.0, 2.0), (-1.0, 2.0)), nwalkers=6, nsteps=5, psf_scaling=-1.0, pca_number=1, aperture=(10, 16, 0.06), mask=None, extra_rot=0.0, merit="gaussian", residuals="median", resume=False, sigma=(1e-3, 1e-1, 1e-2), ) self.pipeline.add_module(module) self.pipeline.run_module("mcmc1") data = self.pipeline.get_data("mcmc") assert data.shape == (5, 6, 3) data = self.pipeline.get_data("mcmc_backend") assert data.shape == (3,) module = MCMCsamplingModule( name_in="mcmc2", image_in_tag="adi", psf_in_tag="psf_stack", chain_out_tag="mcmc", param=(0.15, 0.0, 1.0), bounds=((0.1, 0.2), (-2.0, 2.0), (-1.0, 2.0)), nwalkers=6, nsteps=5, psf_scaling=-1.0, pca_number=1, aperture=(10, 16, 0.06), mask=None, extra_rot=0.0, merit="gaussian", residuals="median", resume=True, sigma=(1e-3, 1e-1, 1e-2), ) self.pipeline.add_module(module) self.pipeline.run_module("mcmc2") data = self.pipeline.get_data("mcmc") assert data.shape == (10, 6, 3) data = self.pipeline.get_data("mcmc_backend") assert data.shape == (3,) def test_systematic_error(self) -> None: module = SystematicErrorModule( name_in="error", image_in_tag="adi", psf_in_tag="psf", offset_out_tag="offset", position=(0.162, 0.0), magnitude=5.0, angles=(0.0, 180.0, 2), psf_scaling=1.0, merit="gaussian", aperture=0.06, tolerance=0.1, pca_number=1, mask=(None, None), extra_rot=0.0, residuals="median", offset=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("error") data = self.pipeline.get_data("offset") assert data[0, 0] == pytest.approx( -0.001114020093541973, rel=self.limit, abs=0.0 ) assert data[0, 1] == pytest.approx( -0.012163271644183737, rel=self.limit, abs=0.0 ) assert data[0, 2] == pytest.approx( -0.017943854263249293, rel=self.limit, abs=0.0 ) assert data[0, 3] == pytest.approx( 0.001282493868968615, rel=self.limit, abs=0.0 ) assert data[0, 4] == pytest.approx( -0.04125986733475884, rel=self.limit, abs=0.0 ) assert data.shape == (2, 5) PynPoint-PynPoint-e5d0637/tests/test_processing/test_frameselection.py000066400000000000000000000340301512720201400263670ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.frameselection import ( RemoveFramesModule, FrameSelectionModule, RemoveLastFrameModule, RemoveStartFramesModule, ImageStatisticsModule, FrameSimilarityModule, SelectByAttributeModule, ResidualSelectionModule, ) from pynpoint.util.tests import create_config, remove_test_data, create_star_data class TestFrameSelection: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "images") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["images"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="read", input_dir=self.test_dir + "images", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("read") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) attr = self.pipeline.get_attribute("read", "NDIT", static=False) assert np.sum(attr) == pytest.approx(10, rel=self.limit, abs=0.0) assert attr.shape == (2,) attr = self.pipeline.get_attribute("read", "NFRAMES", static=False) assert np.sum(attr) == pytest.approx(10, rel=self.limit, abs=0.0) assert attr.shape == (2,) self.pipeline.set_attribute("read", "NDIT", [4, 4], static=False) def test_remove_last_frame(self) -> None: module = RemoveLastFrameModule( name_in="last", image_in_tag="read", image_out_tag="last" ) self.pipeline.add_module(module) self.pipeline.run_module("last") data = self.pipeline.get_data("last") assert np.sum(data) == pytest.approx(84.68885503527224, rel=self.limit, abs=0.0) assert data.shape == (8, 11, 11) self.pipeline.set_attribute("last", "PARANG", np.arange(8.0), static=False) self.pipeline.set_attribute( "last", "STAR_POSITION", np.full((8, 2), 5.0), static=False ) attr = self.pipeline.get_attribute("last", "PARANG", static=False) assert np.sum(attr) == pytest.approx(28.0, rel=self.limit, abs=0.0) assert attr.shape == (8,) attr = self.pipeline.get_attribute("last", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(80.0, rel=self.limit, abs=0.0) assert attr.shape == (8, 2) def test_remove_start_frame(self) -> None: module = RemoveStartFramesModule( frames=1, name_in="start", image_in_tag="last", image_out_tag="start" ) self.pipeline.add_module(module) self.pipeline.run_module("start") data = self.pipeline.get_data("start") assert np.sum(data) == pytest.approx(64.44307047549808, rel=self.limit, abs=0.0) assert data.shape == (6, 11, 11) attr = self.pipeline.get_attribute("start", "PARANG", static=False) assert np.sum(attr) == pytest.approx(24.0, rel=self.limit, abs=0.0) assert attr.shape == (6,) attr = self.pipeline.get_attribute("start", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(60.0, rel=self.limit, abs=0.0) assert attr.shape == (6, 2) def test_remove_frames(self) -> None: module = RemoveFramesModule( name_in="remove", image_in_tag="start", selected_out_tag="selected", removed_out_tag="removed", frames=[2, 5], ) self.pipeline.add_module(module) self.pipeline.run_module("remove") data = self.pipeline.get_data("selected") assert np.sum(data) == pytest.approx(43.68337741822863, rel=self.limit, abs=0.0) assert data.shape == (4, 11, 11) data = self.pipeline.get_data("removed") assert np.sum(data) == pytest.approx( 20.759693057269445, rel=self.limit, abs=0.0 ) assert data.shape == (2, 11, 11) attr = self.pipeline.get_attribute("selected", "PARANG", static=False) assert np.sum(attr) == pytest.approx(14.0, rel=self.limit, abs=0.0) assert attr.shape == (4,) attr = self.pipeline.get_attribute("selected", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(40.0, rel=self.limit, abs=0.0) assert attr.shape == (4, 2) attr = self.pipeline.get_attribute("removed", "PARANG", static=False) assert np.sum(attr) == pytest.approx(10.0, rel=self.limit, abs=0.0) assert attr.shape == (2,) attr = self.pipeline.get_attribute("removed", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(20.0, rel=self.limit, abs=0.0) assert attr.shape == (2, 2) def test_frame_selection(self) -> None: module = FrameSelectionModule( name_in="select1", image_in_tag="start", selected_out_tag="selected1", removed_out_tag="removed1", index_out_tag="index1", method="median", threshold=2.0, fwhm=0.1, aperture=("circular", 0.1), position=(None, None, 0.2), ) self.pipeline.add_module(module) self.pipeline.run_module("select1") data = self.pipeline.get_data("selected1") assert np.sum(data) == pytest.approx(54.58514780071149, rel=self.limit, abs=0.0) assert data.shape == (5, 11, 11) data = self.pipeline.get_data("removed1") assert np.sum(data) == pytest.approx(9.857922674786586, rel=self.limit, abs=0.0) assert data.shape == (1, 11, 11) data = self.pipeline.get_data("index1") assert np.sum(data) == pytest.approx(5, rel=self.limit, abs=0.0) assert data.shape == (1,) attr = self.pipeline.get_attribute("selected1", "PARANG", static=False) assert np.sum(attr) == pytest.approx(17.0, rel=self.limit, abs=0.0) assert attr.shape == (5,) attr = self.pipeline.get_attribute("selected1", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(50, rel=self.limit, abs=0.0) assert attr.shape == (5, 2) attr = self.pipeline.get_attribute("removed1", "PARANG", static=False) assert np.sum(attr) == pytest.approx(7.0, rel=self.limit, abs=0.0) assert attr.shape == (1,) attr = self.pipeline.get_attribute("removed1", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(10, rel=self.limit, abs=0.0) assert attr.shape == (1, 2) module = FrameSelectionModule( name_in="select2", image_in_tag="start", selected_out_tag="selected2", removed_out_tag="removed2", index_out_tag="index2", method="max", threshold=1.0, fwhm=0.1, aperture=("annulus", 0.05, 0.1), position=None, ) self.pipeline.add_module(module) self.pipeline.run_module("select2") data = self.pipeline.get_data("selected2") assert np.sum(data) == pytest.approx(21.42652724866543, rel=self.limit, abs=0.0) assert data.shape == (2, 11, 11) data = self.pipeline.get_data("removed2") assert np.sum(data) == pytest.approx( 43.016543226832646, rel=self.limit, abs=0.0 ) assert data.shape == (4, 11, 11) data = self.pipeline.get_data("index2") assert np.sum(data) == pytest.approx(10, rel=self.limit, abs=0.0) assert data.shape == (4,) attr = self.pipeline.get_attribute("selected2", "PARANG", static=False) assert np.sum(attr) == pytest.approx(8.0, rel=self.limit, abs=0.0) assert attr.shape == (2,) attr = self.pipeline.get_attribute("selected2", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(20, rel=self.limit, abs=0.0) assert attr.shape == (2, 2) attr = self.pipeline.get_attribute("removed2", "PARANG", static=False) assert np.sum(attr) == pytest.approx(16.0, rel=self.limit, abs=0.0) assert attr.shape == (4,) attr = self.pipeline.get_attribute("removed2", "STAR_POSITION", static=False) assert np.sum(attr) == pytest.approx(40, rel=self.limit, abs=0.0) assert attr.shape == (4, 2) module = FrameSelectionModule( name_in="select3", image_in_tag="start", selected_out_tag="selected3", removed_out_tag="removed3", index_out_tag="index3", method="range", threshold=(10.0, 10.7), fwhm=0.1, aperture=("circular", 0.1), position=None, ) self.pipeline.add_module(module) self.pipeline.run_module("select3") data = self.pipeline.get_data("selected3") assert np.sum(data) == pytest.approx(22.2568501695632, rel=self.limit, abs=0.0) assert data.shape == (2, 11, 11) data = self.pipeline.get_data("removed3") assert np.sum(data) == pytest.approx(42.18622030593487, rel=self.limit, abs=0.0) assert data.shape == (4, 11, 11) data = self.pipeline.get_data("index3") assert np.sum(data) == pytest.approx(12, rel=self.limit, abs=0.0) assert data.shape == (4,) def test_image_statistics_full(self) -> None: module = ImageStatisticsModule( name_in="stat1", image_in_tag="read", stat_out_tag="stat1", position=None ) self.pipeline.add_module(module) self.pipeline.run_module("stat1") data = self.pipeline.get_data("stat1") assert np.sum(data) == pytest.approx( 115.68591492205017, rel=self.limit, abs=0.0 ) assert data.shape == (10, 6) def test_image_statistics_position(self) -> None: module = ImageStatisticsModule( name_in="stat2", image_in_tag="read", stat_out_tag="stat2", position=(5, 5, 0.1), ) self.pipeline.add_module(module) self.pipeline.run_module("stat2") data = self.pipeline.get_data("stat2") assert np.sum(data) == pytest.approx(118.7138708968444, rel=self.limit, abs=0.0) assert data.shape == (10, 6) def test_frame_similarity_mse(self) -> None: module = FrameSimilarityModule( name_in="simi1", image_tag="read", method="MSE", mask_radius=(0.0, 0.1) ) self.pipeline.add_module(module) self.pipeline.run_module("simi1") attr = self.pipeline.get_attribute("read", "MSE", static=False) assert np.min(attr) > 0.0 assert np.sum(attr) == pytest.approx( 0.11739141370277852, rel=self.limit, abs=0.0 ) assert attr.shape == (10,) def test_frame_similarity_pcc(self) -> None: module = FrameSimilarityModule( name_in="simi2", image_tag="read", method="PCC", mask_radius=(0.0, 0.1) ) self.pipeline.add_module(module) self.pipeline.run_module("simi2") attr = self.pipeline.get_attribute("read", "PCC", static=False) assert np.min(attr) > 0.0 assert np.sum(attr) == pytest.approx(9.134820985662829, rel=self.limit, abs=0.0) assert attr.shape == (10,) def test_frame_similarity_ssim(self) -> None: module = FrameSimilarityModule( name_in="simi3", image_tag="read", method="SSIM", mask_radius=(0.0, 0.1), temporal_median="constant", ) self.pipeline.add_module(module) self.pipeline.run_module("simi3") attr = self.pipeline.get_attribute("read", "SSIM", static=False) assert np.min(attr) > 0.0 assert np.sum(attr) == pytest.approx(9.074290801266256, rel=self.limit, abs=0.0) assert attr.shape == (10,) def test_select_by_attribute(self) -> None: self.pipeline.set_attribute("read", "INDEX", np.arange(44), static=False) module = SelectByAttributeModule( name_in="frame_removal_1", image_in_tag="read", attribute_tag="SSIM", number_frames=6, order="descending", selected_out_tag="select_sim", removed_out_tag="remove_sim", ) self.pipeline.add_module(module) self.pipeline.run_module("frame_removal_1") attr = self.pipeline.get_attribute("select_sim", "INDEX", static=False) assert np.sum(attr) == pytest.approx(946, rel=self.limit, abs=0.0) assert attr.shape == (44,) attr = self.pipeline.get_attribute("select_sim", "SSIM", static=False) assert np.sum(attr) == pytest.approx(5.545578246610884, rel=self.limit, abs=0.0) assert attr.shape == (6,) attr = self.pipeline.get_attribute("remove_sim", "SSIM", static=False) assert np.sum(attr) == pytest.approx(3.528712554655373, rel=self.limit, abs=0.0) assert attr.shape == (4,) def test_residual_selection(self) -> None: module = ResidualSelectionModule( name_in="residual_select", image_in_tag="start", selected_out_tag="res_selected", removed_out_tag="res_removed", percentage=80.0, annulus_radii=(0.1, 0.2), ) self.pipeline.add_module(module) self.pipeline.run_module("residual_select") data = self.pipeline.get_data("res_selected") assert np.sum(data) == pytest.approx(41.77295229983322, rel=self.limit, abs=0.0) assert data.shape == (4, 11, 11) data = self.pipeline.get_data("res_removed") assert np.sum(data) == pytest.approx( 22.670118175664847, rel=self.limit, abs=0.0 ) assert data.shape == (2, 11, 11) PynPoint-PynPoint-e5d0637/tests/test_processing/test_limits.py000066400000000000000000000141131512720201400246700ustar00rootroot00000000000000import os import h5py import pooch import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.limits import ContrastCurveModule, MassLimitsModule from pynpoint.processing.psfpreparation import AngleInterpolationModule from pynpoint.util.tests import create_config, create_star_data, remove_test_data class TestLimits: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "self.limits", npix=21, pos_star=10.0) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data( path=self.test_dir, folders=["self.limits"], files=["model.AMES-Cond-2000.M-0.0.NaCo.Vega"], ) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="read", input_dir=self.test_dir + "self.limits" ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("read") assert np.sum(data) == pytest.approx( 108.43655133957289, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) def test_angle_interpolation(self) -> None: module = AngleInterpolationModule(name_in="angle", data_tag="read") self.pipeline.add_module(module) self.pipeline.run_module("angle") attr = self.pipeline.get_attribute("read", "PARANG", static=False) assert np.sum(attr) == pytest.approx(900.0, rel=self.limit, abs=0.0) assert attr.shape == (10,) def test_contrast_curve(self) -> None: proc = ["single", "multi"] for item in proc: if item == "multi": with h5py.File( self.test_dir + "PynPoint_database.hdf5", "a" ) as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = ContrastCurveModule( name_in="contrast_" + item, image_in_tag="read", psf_in_tag="read", contrast_out_tag="limits_" + item, separation=(0.2, 0.3, 0.2), angle=(0.0, 360.0, 180.0), threshold=("sigma", 5.0), psf_scaling=1.0, aperture=0.05, pca_number=2, cent_size=None, edge_size=1.0, extra_rot=0.0, ) self.pipeline.add_module(module) self.pipeline.run_module("contrast_" + item) data = self.pipeline.get_data("limits_" + item) assert data[0, 0] == pytest.approx(0.2, rel=self.limit, abs=0.0) assert data[0, 1] == pytest.approx( 2.580878183791224, rel=self.limit, abs=0.0 ) assert data[0, 2] == pytest.approx( 0.0007097688120261913, rel=self.limit, abs=0.0 ) assert data[0, 3] == pytest.approx( 0.00020126490906225968, rel=self.limit, abs=0.0 ) assert data.shape == (1, 4) def test_contrast_curve_fpf(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 1 module = ContrastCurveModule( name_in="contrast_fpf", image_in_tag="read", psf_in_tag="read", contrast_out_tag="limits_fpf", separation=(0.2, 0.3, 0.2), angle=(0.0, 360.0, 180.0), threshold=("fpf", 1e-6), psf_scaling=1.0, aperture=0.05, pca_number=2, cent_size=None, edge_size=1.0, extra_rot=0.0, ) self.pipeline.add_module(module) self.pipeline.run_module("contrast_fpf") data = self.pipeline.get_data("limits_fpf") assert data[0, 0] == pytest.approx(0.2, rel=self.limit, abs=0.0) assert data[0, 1] == pytest.approx(1.9339430843041776, rel=self.limit, abs=0.0) assert data[0, 2] == pytest.approx( 0.000709768812026221, rel=self.limit, abs=0.0 ) assert data[0, 3] == pytest.approx(1e-06, rel=self.limit, abs=0.0) assert data.shape == (1, 4) def test_mass_limits(self) -> None: separation = np.linspace(0.1, 1.0, 10) contrast = -2.5 * np.log10(1e-4 / separation) variance = 0.1 * contrast limits = np.zeros((10, 4)) limits[:, 0] = separation limits[:, 1] = contrast limits[:, 2] = variance with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["contrast_limits"] = limits url = ( "https://home.strw.leidenuniv.nl/~stolker/pynpoint/" "model.AMES-Cond-2000.M-0.0.NaCo.Vega" ) filename = pooch.retrieve( url=url, fname="model.AMES-Cond-2000.M-0.0.NaCo.Vega", path=self.test_dir, known_hash=None, ) module = MassLimitsModule( model_file=filename, star_prop={"magnitude": 10.0, "distance": 100.0, "age": 20.0}, name_in="mass", contrast_in_tag="contrast_limits", mass_out_tag="mass_limits", instr_filter="L'", ) self.pipeline.add_module(module) self.pipeline.run_module("mass") data = self.pipeline.get_data("mass_limits") assert np.mean(data[:, 0]) == pytest.approx(0.55, rel=self.limit, abs=0.0) assert np.mean(data[:, 1]) == pytest.approx( 0.001891690765603738, rel=self.limit, abs=0.0 ) assert np.mean(data[:, 2]) == pytest.approx( 0.000964309686441908, rel=self.limit, abs=0.0 ) assert np.mean(data[:, 3]) == pytest.approx( -0.000696402843279597, rel=self.limit, abs=0.0 ) assert data.shape == (10, 4) PynPoint-PynPoint-e5d0637/tests/test_processing/test_psfpreparation.py000066400000000000000000000304221512720201400264250ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.psfpreparation import ( PSFpreparationModule, AngleInterpolationModule, AngleCalculationModule, SDIpreparationModule, SortParangModule, ) from pynpoint.util.tests import ( create_config, create_star_data, create_ifs_data, remove_test_data, ) class TestPsfPreparation: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "prep") create_ifs_data(self.test_dir + "prep_ifs") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["prep", "prep_ifs"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="read", input_dir=self.test_dir + "prep" ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("read") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) module = FitsReadingModule( name_in="read_ifs", image_tag="read_ifs", input_dir=self.test_dir + "prep_ifs", ifs_data=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read_ifs") data = self.pipeline.get_data("read_ifs") assert np.sum(data) == pytest.approx(749.8396528807369, rel=self.limit, abs=0.0) assert data.shape == (3, 10, 21, 21) def test_angle_interpolation(self) -> None: module = AngleInterpolationModule(name_in="angle1", data_tag="read") self.pipeline.add_module(module) self.pipeline.run_module("angle1") data = self.pipeline.get_data("header_read/PARANG") assert np.sum(data) == pytest.approx(900.0, rel=self.limit, abs=0.0) assert data.shape == (10,) def test_angle_calculation(self) -> None: self.pipeline.set_attribute("read", "LATITUDE", -25.0) self.pipeline.set_attribute("read", "LONGITUDE", -70.0) self.pipeline.set_attribute("read", "DIT", 1.0) self.pipeline.set_attribute( "read", "RA", (90.0, 90.0, 90.0, 90.0), static=False ) self.pipeline.set_attribute( "read", "DEC", (-51.0, -51.0, -51.0, -51.0), static=False ) self.pipeline.set_attribute( "read", "PUPIL", (90.0, 90.0, 90.0, 90.0), static=False ) date = ( "2012-12-01T07:09:00.0000", "2012-12-01T07:09:01.0000", "2012-12-01T07:09:02.0000", "2012-12-01T07:09:03.0000", ) self.pipeline.set_attribute("read", "DATE", date, static=False) module = AngleCalculationModule( instrument="NACO", name_in="angle2", data_tag="read" ) self.pipeline.add_module(module) self.pipeline.run_module("angle2") data = self.pipeline.get_data("header_read/PARANG") assert np.sum(data) == pytest.approx( -550.2338293743467, rel=self.limit, abs=0.0 ) assert data.shape == (10,) self.pipeline.set_attribute( "read", "RA", (60000.0, 60000.0, 60000.0, 60000.0), static=False ) self.pipeline.set_attribute( "read", "DEC", (-510000.0, -510000.0, -510000.0, -510000.0), static=False ) module = AngleCalculationModule( instrument="SPHERE/IRDIS", name_in="angle3", data_tag="read" ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("angle3") warning_0 = ( "For SPHERE data it is recommended to use the header keyword 'ESO INS4 " "DROT2 RA' to specify the object's right ascension. The input will be " "parsed accordingly. Using the regular 'RA' keyword will lead to wrong " "parallactic angles." ) warning_1 = ( "For SPHERE data it is recommended to use the header keyword 'ESO INS4 " "DROT2 DEC' to specify the object's declination. The input will be parsed " "accordingly. Using the regular 'DEC' keyword will lead to wrong " "parallactic angles." ) if len(warning) == 2: assert warning[0].message.args[0] == warning_0 assert warning[1].message.args[0] == warning_1 data = self.pipeline.get_data("header_read/PARANG") assert np.sum(data) == pytest.approx( 1704.2202367435675, rel=self.limit, abs=0.0 ) assert data.shape == (10,) module = AngleCalculationModule( instrument="SPHERE/IFS", name_in="angle4", data_tag="read" ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("angle4") warning_0 = "AngleCalculationModule has not been tested for SPHERE/IFS data." warning_1 = ( "For SPHERE data it is recommended to use the header keyword 'ESO INS4 " "DROT2 RA' to specify the object's right ascension. The input will be " "parsed accordingly. Using the regular 'RA' keyword will lead to wrong " "parallactic angles." ) warning_2 = ( "For SPHERE data it is recommended to use the header keyword 'ESO INS4 " "DROT2 DEC' to specify the object's declination. The input will be parsed " "accordingly. Using the regular 'DEC' keyword will lead to wrong " "parallactic angles." ) if len(warning) == 3: assert warning[0].message.args[0] == warning_0 assert warning[1].message.args[0] == warning_1 assert warning[2].message.args[0] == warning_2 data = self.pipeline.get_data("header_read/PARANG") assert np.sum(data) == pytest.approx( -890.8506514377593, rel=self.limit, abs=0.0 ) assert data.shape == (10,) def test_angle_sort(self) -> None: index = self.pipeline.get_data("header_read/INDEX") self.pipeline.set_attribute("read", "INDEX", index[::-1], static=False) module = SortParangModule( name_in="sort1", image_in_tag="read", image_out_tag="read_sorted" ) self.pipeline.add_module(module) self.pipeline.run_module("sort1") self.pipeline.set_attribute("read", "INDEX", index, static=False) parang = self.pipeline.get_data("header_read/PARANG")[::-1] parang_sort = self.pipeline.get_data("header_read_sorted/PARANG") assert np.sum(parang) == pytest.approx( np.sum(parang_sort), rel=self.limit, abs=0.0 ) parang_set = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] self.pipeline.set_attribute("read_ifs", "PARANG", parang_set, static=False) data = self.pipeline.get_data("read_sorted") assert np.sum(data[0]) == pytest.approx( 9.71156815235485, rel=self.limit, abs=0.0 ) def test_angle_sort_ifs(self) -> None: index = self.pipeline.get_data("header_read_ifs/INDEX") self.pipeline.set_attribute("read_ifs", "INDEX", index[::-1], static=False) module = SortParangModule( name_in="sort2", image_in_tag="read_ifs", image_out_tag="read_ifs_sorted" ) self.pipeline.add_module(module) self.pipeline.run_module("sort2") self.pipeline.set_attribute("read_ifs", "INDEX", index, static=False) parang = self.pipeline.get_data("header_read_ifs/PARANG")[::-1] parang_sort = self.pipeline.get_data("header_read_ifs_sorted/PARANG") assert np.sum(parang) == pytest.approx( np.sum(parang_sort), rel=self.limit, abs=0.0 ) data = self.pipeline.get_data("read_ifs_sorted") assert np.sum(data[0, 0]) == pytest.approx( 21.185139976163477, rel=self.limit, abs=0.0 ) def test_angle_interpolation_mismatch(self) -> None: self.pipeline.set_attribute("read", "NDIT", [9, 9, 9, 9], static=False) module = AngleInterpolationModule(name_in="angle5", data_tag="read") self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("angle5") warning_0 = ( "There is a mismatch between the NDIT and NFRAMES values. The parallactic " "angles are calculated with a linear interpolation by using NFRAMES steps. " "A frame selection should be applied after the parallactic angles are " "calculated." ) if len(warning) == 1: assert warning[0].message.args[0] == warning_0 data = self.pipeline.get_data("header_read/PARANG") assert np.sum(data) == pytest.approx(900.0, rel=self.limit, abs=0.0) assert data.shape == (10,) def test_psf_preparation_norm_mask(self) -> None: module = PSFpreparationModule( name_in="prep1", image_in_tag="read", image_out_tag="prep1", mask_out_tag="mask1", norm=True, cent_size=0.1, edge_size=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("prep1") data = self.pipeline.get_data("prep1") assert np.sum(data) == pytest.approx( -1.5844830188044685, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) data = self.pipeline.get_data("mask1") assert np.sum(data) == pytest.approx(52, rel=self.limit, abs=0.0) assert data.shape == (11, 11) def test_psf_preparation_none(self) -> None: module = PSFpreparationModule( name_in="prep2", image_in_tag="read", image_out_tag="prep2", mask_out_tag="mask2", norm=False, cent_size=None, edge_size=None, ) self.pipeline.add_module(module) self.pipeline.run_module("prep2") data = self.pipeline.get_data("prep2") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_psf_preparation_no_mask_out(self) -> None: module = PSFpreparationModule( name_in="prep3", image_in_tag="read", image_out_tag="prep3", mask_out_tag=None, norm=False, cent_size=None, edge_size=None, ) self.pipeline.add_module(module) self.pipeline.run_module("prep3") data = self.pipeline.get_data("prep3") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_psf_preparation_sdi(self) -> None: module = PSFpreparationModule( name_in="prep4", image_in_tag="read_ifs", image_out_tag="prep4", mask_out_tag=None, norm=False, cent_size=None, edge_size=None, ) self.pipeline.add_module(module) self.pipeline.run_module("prep4") data = self.pipeline.get_data("prep4") assert np.sum(data) == pytest.approx(749.8396528807369, rel=self.limit, abs=0.0) assert data.shape == (3, 10, 21, 21) def test_sdi_preparation(self) -> None: module = SDIpreparationModule( name_in="sdi", wavelength=(0.65, 0.6), width=(0.1, 0.5), image_in_tag="read", image_out_tag="sdi", ) self.pipeline.add_module(module) self.pipeline.run_module("sdi") data = self.pipeline.get_data("sdi") assert np.sum(data) == pytest.approx( 21.084666133914183, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) attribute = self.pipeline.get_attribute("sdi", "History: SDIpreparationModule") assert attribute == "(line, continuum) = (0.65, 0.6)" PynPoint-PynPoint-e5d0637/tests/test_processing/test_psfsubtraction_adi.py000066400000000000000000000477361512720201400272730ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.psfpreparation import PSFpreparationModule from pynpoint.processing.psfsubtraction import ( PcaPsfSubtractionModule, ClassicalADIModule, ) from pynpoint.util.tests import create_config, create_fake_data, remove_test_data class TestPsfSubtractionAdi: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_fake_data(self.test_dir + "science") create_fake_data(self.test_dir + "reference") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["science", "reference"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="science", input_dir=self.test_dir + "science" ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("science") assert np.sum(data) == pytest.approx( 11.012854046962481, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) self.pipeline.set_attribute( "science", "PARANG", np.linspace(0.0, 180.0, 10), static=False ) module = FitsReadingModule( name_in="read2", image_tag="reference", input_dir=self.test_dir + "reference", ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("reference") assert np.sum(data) == pytest.approx( 11.012854046962481, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) def test_psf_preparation(self) -> None: module = PSFpreparationModule( name_in="prep1", image_in_tag="science", image_out_tag="science_prep", mask_out_tag=None, norm=False, resize=None, cent_size=0.05, edge_size=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("prep1") data = self.pipeline.get_data("science_prep") assert np.sum(data) == pytest.approx(5.029285028467547, rel=self.limit, abs=0.0) assert data.shape == (10, 21, 21) module = PSFpreparationModule( name_in="prep2", image_in_tag="reference", image_out_tag="reference_prep", mask_out_tag=None, norm=False, resize=None, cent_size=0.05, edge_size=1.0, ) self.pipeline.add_module(module) self.pipeline.run_module("prep2") data = self.pipeline.get_data("reference_prep") assert np.sum(data) == pytest.approx(5.029285028467547, rel=self.limit, abs=0.0) assert data.shape == (10, 21, 21) def test_classical_adi(self) -> None: module = ClassicalADIModule( threshold=None, nreference=None, residuals="mean", extra_rot=0.0, name_in="cadi1", image_in_tag="science_prep", res_out_tag="cadi_res", stack_out_tag="cadi_stack", ) self.pipeline.add_module(module) self.pipeline.run_module("cadi1") data = self.pipeline.get_data("cadi_res") assert np.sum(data) == pytest.approx( 0.8381625719865213, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) data = self.pipeline.get_data("cadi_stack") assert np.sum(data) == pytest.approx( 0.08395606034388256, rel=self.limit, abs=0.0 ) assert data.shape == (1, 21, 21) def test_classical_adi_threshold(self) -> None: module = ClassicalADIModule( threshold=(0.1, 0.03, 1.0), nreference=5, residuals="median", extra_rot=0.0, name_in="cadi2", image_in_tag="science_prep", res_out_tag="cadi_res", stack_out_tag="cadi_stack", ) self.pipeline.add_module(module) self.pipeline.run_module("cadi2") data = self.pipeline.get_data("cadi_res") assert np.sum(data) == pytest.approx(0.7158207863548083, rel=1e-2, abs=0.0) assert data.shape == (10, 21, 21) data = self.pipeline.get_data("cadi_stack") assert np.sum(data) == pytest.approx(0.07448334552227256, rel=1e-2, abs=0.0) assert data.shape == (1, 21, 21) def test_psf_subtraction_pca_single(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_single", images_in_tag="science", reference_in_tag="science", res_mean_tag="res_mean_single", res_median_tag="res_median_single", res_weighted_tag="res_weighted_single", res_rot_mean_clip_tag="res_clip_single", res_arr_out_tag="res_arr_single", basis_out_tag="basis_single", extra_rot=45.0, subtract_mean=True, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_single") data = self.pipeline.get_data("res_mean_single") assert np.sum(data) == pytest.approx( -0.00011857022709778602, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_median_single") assert np.sum(data) == pytest.approx( -0.002184868916566093, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_weighted_single") assert np.sum(data) == pytest.approx( 0.08102176735226937, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) # data = self.pipeline.get_data('res_clip_single') # assert np.sum(data) == pytest.approx(7.09495495339349e-05, rel=self.limit, abs=0.) # assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_arr_single1") assert np.sum(data) == pytest.approx( -0.0002751385418691618, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) data = self.pipeline.get_data("basis_single") assert np.sum(data) == pytest.approx( 0.09438697731322143, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) def test_psf_subtraction_no_mean(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_no_mean", images_in_tag="science", reference_in_tag="science", res_mean_tag="res_mean_no_mean", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_no_mean", extra_rot=0.0, subtract_mean=False, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_no_mean") data = self.pipeline.get_data("res_mean_no_mean") assert np.sum(data) == pytest.approx( 0.0006081272007585688, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_no_mean") assert np.sum(data) == pytest.approx(5.118005177367776, rel=self.limit, abs=0.0) assert data.shape == (2, 21, 21) def test_psf_subtraction_ref(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_ref", images_in_tag="science", reference_in_tag="reference", res_mean_tag="res_mean_ref", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_ref", extra_rot=0.0, subtract_mean=True, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_ref") data = self.pipeline.get_data("res_mean_ref") assert np.sum(data) == pytest.approx( 0.0006330226118859073, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_ref") assert np.sum(data) == pytest.approx( 0.0943869773132221, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) def test_psf_subtraction_ref_no_mean(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_ref_no_mean", images_in_tag="science", reference_in_tag="reference", res_mean_tag="res_mean_ref_no_mean", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_ref_no_mean", extra_rot=0.0, subtract_mean=False, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_ref_no_mean") data = self.pipeline.get_data("res_mean_ref_no_mean") assert np.sum(data) == pytest.approx( 0.0006081272007585764, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_ref_no_mean") assert np.sum(data) == pytest.approx(5.118005177367774, rel=self.limit, abs=0.0) assert data.shape == (2, 21, 21) def test_psf_subtraction_pca_single_mask(self) -> None: pca = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_single_mask", images_in_tag="science_prep", reference_in_tag="science_prep", res_mean_tag="res_mean_single_mask", res_median_tag="res_median_single_mask", res_weighted_tag="res_weighted_single_mask", res_rot_mean_clip_tag="res_clip_single_mask", res_arr_out_tag="res_arr_single_mask", basis_out_tag="basis_single_mask", extra_rot=45.0, subtract_mean=True, ) self.pipeline.add_module(pca) self.pipeline.run_module("pca_single_mask") data = self.pipeline.get_data("res_mean_single_mask") assert np.sum(data) == pytest.approx( 0.00010696166038626307, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_median_single_mask") assert np.sum(data) == pytest.approx( -0.0021005307611346156, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_weighted_single_mask") assert np.sum(data) == pytest.approx( 0.06014309988789256, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_clip_single_mask") # assert np.sum(data) == pytest.approx(9.35120662148806e-05, rel=self.limit, abs=0.) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("res_arr_single_mask1") assert np.sum(data) == pytest.approx( 0.0006170872862547557, rel=self.limit, abs=0.0 ) assert data.shape == (10, 21, 21) data = self.pipeline.get_data("basis_single_mask") assert np.sum(data) == pytest.approx( 0.08411251293842359, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) def test_psf_subtraction_no_mean_mask(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_no_mean_mask", images_in_tag="science_prep", reference_in_tag="science_prep", res_mean_tag="res_mean_no_mean_mask", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_no_mean_mask", extra_rot=0.0, subtract_mean=False, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_no_mean_mask") data = self.pipeline.get_data("res_mean_no_mean_mask") assert np.sum(data) == pytest.approx( 2.3542359949502915e-05, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_no_mean_mask") assert np.sum(data) == pytest.approx(5.655460951633232, rel=self.limit, abs=0.0) assert data.shape == (2, 21, 21) def test_psf_subtraction_ref_mask(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_ref_mask", images_in_tag="science_prep", reference_in_tag="reference_prep", res_mean_tag="res_mean_ref_mask", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_ref_mask", extra_rot=0.0, subtract_mean=True, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_ref_mask") data = self.pipeline.get_data("res_mean_ref_mask") assert np.sum(data) == pytest.approx( 9.400558926815758e-06, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_ref_mask") assert np.sum(data) == pytest.approx( 0.08411251293842326, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) def test_psf_subtraction_ref_no_mean_mask(self) -> None: module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_ref_no_mean_mask", images_in_tag="science_prep", reference_in_tag="reference_prep", res_mean_tag="res_mean_ref_no_mean_mask", res_median_tag=None, res_weighted_tag=None, res_rot_mean_clip_tag=None, res_arr_out_tag=None, basis_out_tag="basis_ref_no_mean_mask", extra_rot=0.0, subtract_mean=False, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_ref_no_mean_mask") data = self.pipeline.get_data("res_mean_ref_no_mean_mask") assert np.sum(data) == pytest.approx( 2.354235994950671e-05, rel=self.limit, abs=0.0 ) assert data.shape == (2, 21, 21) data = self.pipeline.get_data("basis_ref_no_mean_mask") assert np.sum(data) == pytest.approx(5.655460951633233, rel=self.limit, abs=0.0) assert data.shape == (2, 21, 21) def test_psf_subtraction_pca_multi(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_multi", images_in_tag="science", reference_in_tag="science", res_mean_tag="res_mean_multi", res_median_tag="res_median_multi", res_weighted_tag="res_weighted_multi", res_rot_mean_clip_tag="res_clip_multi", res_arr_out_tag=None, basis_out_tag="basis_multi", extra_rot=45.0, subtract_mean=True, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_multi") data_single = self.pipeline.get_data("res_mean_single") data_multi = self.pipeline.get_data("res_mean_multi") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("res_median_single") data_multi = self.pipeline.get_data("res_median_multi") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("res_weighted_single") data_multi = self.pipeline.get_data("res_weighted_multi") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("basis_single") data_multi = self.pipeline.get_data("basis_multi") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) def test_psf_subtraction_pca_multi_mask(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 module = PcaPsfSubtractionModule( pca_numbers=range(1, 3), name_in="pca_multi_mask", images_in_tag="science_prep", reference_in_tag="science_prep", res_mean_tag="res_mean_multi_mask", res_median_tag="res_median_multi_mask", res_weighted_tag="res_weighted_multi_mask", res_rot_mean_clip_tag="res_clip_multi_mask", res_arr_out_tag=None, basis_out_tag="basis_multi_mask", extra_rot=45.0, subtract_mean=True, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_multi_mask") data_single = self.pipeline.get_data("res_mean_single_mask") data_multi = self.pipeline.get_data("res_mean_multi_mask") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("res_median_single_mask") data_multi = self.pipeline.get_data("res_median_multi_mask") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("res_weighted_single_mask") data_multi = self.pipeline.get_data("res_weighted_multi_mask") assert data_single.shape == data_multi.shape assert data_single[data_single > 1e-12] == pytest.approx( data_multi[data_multi > 1e-12], rel=self.limit, abs=0.0 ) data_single = self.pipeline.get_data("basis_single_mask") data_multi = self.pipeline.get_data("basis_multi_mask") assert data_single.shape == data_multi.shape assert data_single == pytest.approx(data_multi, rel=self.limit, abs=0.0) def test_psf_subtraction_len_parang(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 1 parang = self.pipeline.get_data("header_science/PARANG") self.pipeline.set_attribute( "science_prep", "PARANG", np.append(parang, 0.0), static=False ) module = PcaPsfSubtractionModule( pca_numbers=[ 1, ], name_in="pca_len_parang", images_in_tag="science_prep", reference_in_tag="science_prep", res_mean_tag="res_mean_len_parang", extra_rot=0.0, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("pca_len_parang") assert ( str(error.value) == "The number of images (10) is not equal to the number of " "parallactic angles (11)." ) PynPoint-PynPoint-e5d0637/tests/test_processing/test_psfsubtraction_sdi.py000066400000000000000000000160341512720201400273000ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.psfsubtraction import PcaPsfSubtractionModule from pynpoint.util.tests import create_config, create_ifs_data, remove_test_data class TestPsfSubtractionSdi: def setup_class(self) -> None: self.limit = 1e-5 self.test_dir = os.path.dirname(__file__) + "/" create_ifs_data(self.test_dir + "science") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["science"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="science", input_dir=self.test_dir + "science", ifs_data=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("science") assert np.sum(data) == pytest.approx(749.8396528807368, rel=self.limit, abs=0.0) assert data.shape == (3, 10, 21, 21) self.pipeline.set_attribute( "science", "WAVELENGTH", [1.0, 1.1, 1.2], static=False ) self.pipeline.set_attribute( "science", "PARANG", np.linspace(0.0, 180.0, 10), static=False ) def test_psf_subtraction_sdi(self) -> None: processing_types = ["ADI", "SDI+ADI", "ADI+SDI", "CODI"] expected = [ [ -0.16718942968552664, -0.790697125718532, 19.507979777136892, -0.21617058715490922, ], [ -0.001347198747121658, -0.08621264803633322, 2.3073192270025333, -0.010269745733878437, ], [ 0.009450917836998779, -0.05776205365084376, -0.43506678222476264, 0.0058856438951644455, ], [-0.2428739554898396, -0.5069023645693083, 9.326414176548905, 0.00], ] shape_expc = [ (2, 3, 21, 21), (2, 2, 3, 21, 21), (1, 1, 3, 21, 21), (2, 3, 21, 21), ] pca_numbers = [range(1, 3), (range(1, 3), range(1, 3)), ([1], [1]), range(1, 3)] res_arr_tags = [None, None, "res_arr_single_sdi_ADI+SDI", None] for i, p_type in enumerate(processing_types): module = PcaPsfSubtractionModule( pca_numbers=pca_numbers[i], name_in="pca_single_sdi_" + p_type, images_in_tag="science", reference_in_tag="science", res_mean_tag="res_mean_single_sdi_" + p_type, res_median_tag="res_median_single_sdi_" + p_type, res_weighted_tag="res_weighted_single_sdi_" + p_type, res_rot_mean_clip_tag="res_clip_single_sdi_" + p_type, res_arr_out_tag=res_arr_tags[i], basis_out_tag="basis_single_sdi_" + p_type, extra_rot=0.0, subtract_mean=True, processing_type=p_type, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_single_sdi_" + p_type) data = self.pipeline.get_data("res_mean_single_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][0], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_median_single_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][1], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_weighted_single_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][2], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_clip_single_sdi_" + p_type) # assert np.sum(data) == pytest.approx(expected[i][3], rel=self.limit, abs=0.) assert data.shape == shape_expc[i] # data = self.pipeline.get_data('basis_single_sdi_'+p_type) # assert np.sum(data) == pytest.approx(-1.3886119555248766, rel=self.limit, abs=0.) # assert data.shape == (5, 30, 30) def test_psf_subtraction_sdi_multi(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 processing_types = ["SDI", "ADI+SDI"] pca_numbers = [range(1, 3), (range(1, 3), range(1, 3))] expected = [ [ -0.004159475403024583, 0.02613693149969979, -0.12940723035023394, -0.008432530081399985, ], [ -0.006580571531064533, -0.08171546066331437, 0.5700432018961117, -0.014527353460544753, ], ] shape_expc = [(2, 3, 21, 21), (2, 2, 3, 21, 21)] for i, p_type in enumerate(processing_types): module = PcaPsfSubtractionModule( pca_numbers=pca_numbers[i], name_in="pca_multi_sdi_" + p_type, images_in_tag="science", reference_in_tag="science", res_mean_tag="res_mean_multi_sdi_" + p_type, res_median_tag="res_median_multi_sdi_" + p_type, res_weighted_tag="res_weighted_multi_sdi_" + p_type, res_rot_mean_clip_tag="res_clip_multi_sdi_" + p_type, res_arr_out_tag=None, basis_out_tag=None, extra_rot=0.0, subtract_mean=True, processing_type=p_type, ) self.pipeline.add_module(module) self.pipeline.run_module("pca_multi_sdi_" + p_type) data = self.pipeline.get_data("res_mean_multi_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][0], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_median_multi_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][1], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_weighted_multi_sdi_" + p_type) assert np.sum(data) == pytest.approx( expected[i][2], rel=self.limit, abs=0.0 ) assert data.shape == shape_expc[i] data = self.pipeline.get_data("res_clip_multi_sdi_" + p_type) # assert np.sum(data) == pytest.approx(expected[i][3], rel=self.limit, abs=0.) assert data.shape == shape_expc[i] PynPoint-PynPoint-e5d0637/tests/test_processing/test_resizing.py000066400000000000000000000141121512720201400252200ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.resizing import ( CropImagesModule, ScaleImagesModule, AddLinesModule, RemoveLinesModule, ) from pynpoint.util.tests import ( create_config, create_star_data, create_ifs_data, remove_test_data, ) class TestResizing: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "resize") create_ifs_data(self.test_dir + "resize_ifs") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["resize", "resize_ifs"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="read", input_dir=self.test_dir + "resize", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("read") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) module = FitsReadingModule( name_in="read_ifs", image_tag="read_ifs", input_dir=self.test_dir + "resize_ifs", overwrite=True, check=True, ifs_data=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read_ifs") data = self.pipeline.get_data("read_ifs") assert np.sum(data) == pytest.approx(749.8396528807369, rel=self.limit, abs=0.0) assert data.shape == (3, 10, 21, 21) def test_crop_images(self) -> None: module = CropImagesModule( size=0.2, center=None, name_in="crop1", image_in_tag="read", image_out_tag="crop1", ) self.pipeline.add_module(module) self.pipeline.run_module("crop1") module = CropImagesModule( size=0.2, center=(4, 4), name_in="crop2", image_in_tag="read", image_out_tag="crop2", ) self.pipeline.add_module(module) self.pipeline.run_module("crop2") module = CropImagesModule( size=0.2, center=(4, 4), name_in="crop_ifs", image_in_tag="read_ifs", image_out_tag="crop_ifs", ) self.pipeline.add_module(module) self.pipeline.run_module("crop_ifs") data = self.pipeline.get_data("crop1") assert np.sum(data) == pytest.approx( 104.93318507061295, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) data = self.pipeline.get_data("crop2") assert np.sum(data) == pytest.approx( 105.64863165433025, rel=self.limit, abs=0.0 ) assert data.shape == (10, 9, 9) data = self.pipeline.get_data("crop_ifs") assert np.sum(data) == pytest.approx( 15.870936600122521, rel=self.limit, abs=0.0 ) assert data.shape == (3, 10, 9, 9) def test_scale_images(self) -> None: module = ScaleImagesModule( name_in="scale1", image_in_tag="read", image_out_tag="scale1", scaling=(2.0, 2.0, None), pixscale=True, ) self.pipeline.add_module(module) self.pipeline.run_module("scale1") module = ScaleImagesModule( name_in="scale2", image_in_tag="read", image_out_tag="scale2", scaling=(None, None, 2.0), pixscale=True, ) self.pipeline.add_module(module) self.pipeline.run_module("scale2") data = self.pipeline.get_data("scale1") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 22, 22) data = self.pipeline.get_data("scale2") assert np.sum(data) == pytest.approx( 211.08557759610554, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) attr = self.pipeline.get_attribute("read", "PIXSCALE", static=True) assert attr == pytest.approx(0.027, rel=self.limit, abs=0.0) attr = self.pipeline.get_attribute("scale1", "PIXSCALE", static=True) assert attr == pytest.approx(0.0135, rel=self.limit, abs=0.0) attr = self.pipeline.get_attribute("scale2", "PIXSCALE", static=True) assert attr == pytest.approx(0.027, rel=self.limit, abs=0.0) def test_add_lines(self) -> None: module = AddLinesModule( lines=(2, 5, 0, 3), name_in="add", image_in_tag="read", image_out_tag="add" ) self.pipeline.add_module(module) self.pipeline.run_module("add") data = self.pipeline.get_data("add") assert np.sum(data) == pytest.approx( 105.54278879805275, rel=self.limit, abs=0.0 ) assert data.shape == (10, 14, 18) def test_remove_lines(self) -> None: module = RemoveLinesModule( lines=(2, 5, 0, 3), name_in="remove", image_in_tag="read", image_out_tag="remove", ) self.pipeline.add_module(module) self.pipeline.run_module("remove") data = self.pipeline.get_data("remove") assert np.sum(data) == pytest.approx(67.49726677462391, rel=self.limit, abs=0.0) assert data.shape == (10, 8, 4) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 self.pipeline.run_module("remove") data_multi = self.pipeline.get_data("remove") assert data == pytest.approx(data_multi, rel=self.limit, abs=0.0) assert data.shape == data_multi.shape PynPoint-PynPoint-e5d0637/tests/test_processing/test_stacksubsample.py000066400000000000000000000246031512720201400264150ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.stacksubset import ( StackAndSubsetModule, StackCubesModule, DerotateAndStackModule, CombineTagsModule, ) from pynpoint.util.tests import ( create_config, create_star_data, create_ifs_data, remove_test_data, ) class TestStackSubset: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_ifs_data(self.test_dir + "data_ifs") create_star_data(self.test_dir + "data") create_star_data(self.test_dir + "extra") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["data_ifs", "extra", "data"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read1", image_tag="images", input_dir=self.test_dir + "data", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("images") assert np.mean(data) == pytest.approx( 0.08722544528764692, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) module = FitsReadingModule( name_in="read2", image_tag="extra", input_dir=self.test_dir + "extra", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read2") extra = self.pipeline.get_data("extra") assert data == pytest.approx(extra, rel=self.limit, abs=0.0) module = FitsReadingModule( name_in="read_ifs", image_tag="images_ifs", input_dir=self.test_dir + "data_ifs", overwrite=True, check=True, ifs_data=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read_ifs") self.pipeline.set_attribute( "images_ifs", "PARANG", np.linspace(0.0, 180.0, 10), static=False ) data = self.pipeline.get_data("images_ifs") assert np.sum(data) == pytest.approx(749.8396528807369, rel=self.limit, abs=0.0) assert data.shape == (3, 10, 21, 21) def test_stack_and_subset(self) -> None: self.pipeline.set_attribute("images", "PARANG", np.arange(10.0), static=False) module = StackAndSubsetModule( name_in="stack1", image_in_tag="images", image_out_tag="stack1", random=4, stacking=2, combine="mean", max_rotation=None, ) self.pipeline.add_module(module) self.pipeline.run_module("stack1") data = self.pipeline.get_data("stack1") assert np.mean(data) == pytest.approx( 0.08758276283743936, rel=self.limit, abs=0.0 ) assert data.shape == (4, 11, 11) data = self.pipeline.get_data("header_stack1/INDEX") assert data == pytest.approx(np.arange(4), rel=self.limit, abs=0.0) assert data.shape == (4,) data = self.pipeline.get_data("header_stack1/PARANG") assert data == pytest.approx([0.5, 2.5, 6.5, 8.5], rel=self.limit, abs=0.0) assert data.shape == (4,) def test_stack_max_rotation(self) -> None: angles = np.arange(10.0) angles[1:6] = 3.0 angles[9] = 50.0 self.pipeline.set_attribute("images", "PARANG", angles, static=False) module = StackAndSubsetModule( name_in="stack2", image_in_tag="images", image_out_tag="stack2", random=None, stacking=2, combine="median", max_rotation=1.0, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("stack2") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Testing of util.module.stack_angles has been " "limited, please use carefully." ) data = self.pipeline.get_data("stack2") assert np.mean(data) == pytest.approx( 0.08580759396987508, rel=self.limit, abs=0.0 ) assert data.shape == (7, 11, 11) data = self.pipeline.get_data("header_stack2/INDEX") assert data == pytest.approx(np.arange(7), rel=self.limit, abs=0.0) assert data.shape == (7,) data = self.pipeline.get_data("header_stack2/PARANG") assert data.shape == (7,) self.pipeline.set_attribute("images", "PARANG", np.arange(10.0), static=False) def test_stack_cube(self) -> None: module = StackCubesModule( name_in="stackcube", image_in_tag="images", image_out_tag="mean", combine="mean", ) self.pipeline.add_module(module) self.pipeline.run_module("stackcube") data = self.pipeline.get_data("mean") assert np.mean(data) == pytest.approx( 0.08722544528764689, rel=self.limit, abs=0.0 ) assert data.shape == (2, 11, 11) attribute = self.pipeline.get_attribute("mean", "INDEX", static=False) assert np.mean(attribute) == pytest.approx(0.5, rel=self.limit, abs=0.0) assert attribute.shape == (2,) attribute = self.pipeline.get_attribute("mean", "NFRAMES", static=False) assert np.mean(attribute) == pytest.approx(1, rel=self.limit, abs=0.0) assert attribute.shape == (2,) def test_derotate_and_stack(self) -> None: module = DerotateAndStackModule( name_in="derotate1", image_in_tag="images", image_out_tag="derotate1", derotate=True, stack="mean", extra_rot=10.0, ) self.pipeline.add_module(module) self.pipeline.run_module("derotate1") data = self.pipeline.get_data("derotate1") assert np.mean(data) == pytest.approx( 0.08709860116308817, rel=self.limit, abs=0.0 ) assert data.shape == (1, 11, 11) module = DerotateAndStackModule( name_in="derotate2", image_in_tag="images", image_out_tag="derotate2", derotate=False, stack="median", extra_rot=0.0, ) self.pipeline.add_module(module) self.pipeline.run_module("derotate2") data = self.pipeline.get_data("derotate2") assert np.mean(data) == pytest.approx( 0.0861160094566323, rel=self.limit, abs=0.0 ) assert data.shape == (1, 11, 11) data = self.pipeline.get_data("derotate2") assert np.mean(data) == pytest.approx( 0.0861160094566323, rel=self.limit, abs=0.0 ) assert data.shape == (1, 11, 11) module = DerotateAndStackModule( name_in="derotate_ifs1", image_in_tag="images_ifs", image_out_tag="derotate_ifs1", derotate=True, stack="mean", extra_rot=0.0, dimension="time", ) self.pipeline.add_module(module) self.pipeline.run_module("derotate_ifs1") data = self.pipeline.get_data("derotate_ifs1") assert np.mean(data) == pytest.approx( 0.1884438996655355, rel=self.limit, abs=0.0 ) assert data.shape == (3, 1, 21, 21) module = DerotateAndStackModule( name_in="derotate_ifs2", image_in_tag="images_ifs", image_out_tag="derotate_ifs2", derotate=False, stack="median", extra_rot=0.0, dimension="wavelength", ) self.pipeline.add_module(module) self.pipeline.run_module("derotate_ifs2") data = self.pipeline.get_data("derotate_ifs2") assert np.mean(data) == pytest.approx( 0.055939644983170146, rel=self.limit, abs=0.0 ) assert data.shape == (1, 10, 21, 21) module = DerotateAndStackModule( name_in="derotate_ifs3", image_in_tag="images_ifs", image_out_tag="derotate_ifs3", derotate=True, stack=None, extra_rot=0.0, dimension="wavelength", ) self.pipeline.add_module(module) self.pipeline.run_module("derotate_ifs3") data = self.pipeline.get_data("derotate_ifs3") assert np.mean(data) == pytest.approx( 0.05653316989966066, rel=self.limit, abs=0.0 ) assert data.shape == (3, 10, 21, 21) def test_combine_tags(self) -> None: module = CombineTagsModule( image_in_tags=["images", "extra"], check_attr=True, index_init=False, name_in="combine1", image_out_tag="combine1", ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("combine1") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The non-static keyword FILES is already used but " "with different values. It is advisable to only " "combine tags that descend from the same data set." ) data = self.pipeline.get_data("combine1") assert np.mean(data) == pytest.approx( 0.0872254452876469, rel=self.limit, abs=0.0 ) assert data.shape == (20, 11, 11) data = self.pipeline.get_data("header_combine1/INDEX") assert data[19] == 9 assert data.shape == (20,) module = CombineTagsModule( image_in_tags=["images", "extra"], check_attr=False, index_init=True, name_in="combine2", image_out_tag="combine2", ) self.pipeline.add_module(module) self.pipeline.run_module("combine2") data = self.pipeline.get_data("combine1") extra = self.pipeline.get_data("combine2") assert data == pytest.approx(extra, rel=self.limit, abs=0.0) data = self.pipeline.get_data("header_combine2/INDEX") assert data[19] == 19 assert data.shape == (20,) PynPoint-PynPoint-e5d0637/tests/test_processing/test_timedenoising.py000066400000000000000000000172341512720201400262340ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.processing.resizing import AddLinesModule from pynpoint.processing.timedenoising import ( CwtWaveletConfiguration, DwtWaveletConfiguration, WaveletTimeDenoisingModule, TimeNormalizationModule, ) from pynpoint.util.tests import create_config, remove_test_data, create_star_data class TestTimeDenoising: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "images") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["images"]) def test_read_data(self) -> None: module = FitsReadingModule( name_in="read", image_tag="images", input_dir=self.test_dir + "images", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_wavelet_denoising_cwt_dog(self) -> None: cwt_config = CwtWaveletConfiguration( wavelet="dog", wavelet_order=2, keep_mean=False, resolution=0.5 ) assert cwt_config.m_wavelet == "dog" assert cwt_config.m_wavelet_order == 2 assert not cwt_config.m_keep_mean assert cwt_config.m_resolution == 0.5 module = WaveletTimeDenoisingModule( wavelet_configuration=cwt_config, name_in="wavelet_cwt_dog", image_in_tag="images", image_out_tag="wavelet_cwt_dog", padding="zero", median_filter=True, threshold_function="soft", ) self.pipeline.add_module(module) self.pipeline.run_module("wavelet_cwt_dog") data = self.pipeline.get_data("wavelet_cwt_dog") assert np.sum(data) == pytest.approx(105.1035789572968, rel=self.limit, abs=0.0) assert data.shape == (10, 11, 11) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 4 self.pipeline.run_module("wavelet_cwt_dog") data_multi = self.pipeline.get_data("wavelet_cwt_dog") assert data == pytest.approx(data_multi, rel=self.limit, abs=0.0) assert data.shape == data_multi.shape def test_wavelet_denoising_cwt_morlet(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file["config"].attrs["CPU"] = 1 cwt_config = CwtWaveletConfiguration( wavelet="morlet", wavelet_order=5, keep_mean=False, resolution=0.5 ) assert cwt_config.m_wavelet == "morlet" assert cwt_config.m_wavelet_order == 5 assert not cwt_config.m_keep_mean assert cwt_config.m_resolution == 0.5 module = WaveletTimeDenoisingModule( wavelet_configuration=cwt_config, name_in="wavelet_cwt_morlet", image_in_tag="images", image_out_tag="wavelet_cwt_morlet", padding="mirror", median_filter=False, threshold_function="hard", ) self.pipeline.add_module(module) self.pipeline.run_module("wavelet_cwt_morlet") data = self.pipeline.get_data("wavelet_cwt_morlet") assert np.sum(data) == pytest.approx( 104.86262840716438, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) data = self.pipeline.get_attribute( "wavelet_cwt_morlet", "NFRAMES", static=False ) assert data[0] == data[1] == 5 def test_wavelet_denoising_dwt(self) -> None: dwt_config = DwtWaveletConfiguration(wavelet="db8") assert dwt_config.m_wavelet == "db8" module = WaveletTimeDenoisingModule( wavelet_configuration=dwt_config, name_in="wavelet_dwt", image_in_tag="images", image_out_tag="wavelet_dwt", padding="zero", median_filter=True, threshold_function="soft", ) self.pipeline.add_module(module) self.pipeline.run_module("wavelet_dwt") data = self.pipeline.get_data("wavelet_dwt") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_time_normalization(self) -> None: module = TimeNormalizationModule( name_in="timenorm", image_in_tag="images", image_out_tag="timenorm" ) self.pipeline.add_module(module) self.pipeline.run_module("timenorm") data = self.pipeline.get_data("timenorm") assert np.sum(data) == pytest.approx(56.443663773873, rel=self.limit, abs=0.0) assert data.shape == (10, 11, 11) def test_wavelet_denoising_even_size(self) -> None: module = AddLinesModule( name_in="add", image_in_tag="images", image_out_tag="images_even", lines=(1, 0, 1, 0), ) self.pipeline.add_module(module) self.pipeline.run_module("add") data = self.pipeline.get_data("images_even") assert np.sum(data) == pytest.approx( 105.54278879805275, rel=self.limit, abs=0.0 ) assert data.shape == (10, 12, 12) cwt_config = CwtWaveletConfiguration( wavelet="dog", wavelet_order=2, keep_mean=False, resolution=0.5 ) assert cwt_config.m_wavelet == "dog" assert cwt_config.m_wavelet_order == 2 assert not cwt_config.m_keep_mean assert cwt_config.m_resolution == 0.5 module = WaveletTimeDenoisingModule( wavelet_configuration=cwt_config, name_in="wavelet_even_1", image_in_tag="images_even", image_out_tag="wavelet_even_1", padding="zero", median_filter=True, threshold_function="soft", ) self.pipeline.add_module(module) self.pipeline.run_module("wavelet_even_1") data = self.pipeline.get_data("wavelet_even_1") assert np.sum(data) == pytest.approx(105.1035789572968, rel=self.limit, abs=0.0) assert data.shape == (10, 12, 12) module = WaveletTimeDenoisingModule( wavelet_configuration=cwt_config, name_in="wavelet_even_2", image_in_tag="images_even", image_out_tag="wavelet_even_2", padding="mirror", median_filter=True, threshold_function="soft", ) self.pipeline.add_module(module) self.pipeline.run_module("wavelet_even_2") data = self.pipeline.get_data("wavelet_even_2") assert np.sum(data) == pytest.approx( 105.06809820408587, rel=self.limit, abs=0.0 ) assert data.shape == (10, 12, 12) data = self.pipeline.get_attribute("images", "NFRAMES", static=False) assert data == pytest.approx([5, 5], rel=self.limit, abs=0.0) data = self.pipeline.get_attribute("wavelet_even_1", "NFRAMES", static=False) assert data == pytest.approx([5, 5], rel=self.limit, abs=0.0) data = self.pipeline.get_attribute("wavelet_even_2", "NFRAMES", static=False) assert data == pytest.approx([5, 5], rel=self.limit, abs=0.0) PynPoint-PynPoint-e5d0637/tests/test_readwrite/000077500000000000000000000000001512720201400215705ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_readwrite/__init__.py000066400000000000000000000000001512720201400236670ustar00rootroot00000000000000PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_attr_reading.py000066400000000000000000000252501512720201400256500ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.attr_reading import ( ParangReadingModule, AttributeReadingModule, WavelengthReadingModule, ) from pynpoint.util.tests import create_config, create_random, remove_test_data class TestAttributeReading: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir, nimages=10) create_config(self.test_dir + "PynPoint_config.ini") np.savetxt(self.test_dir + "parang.dat", np.arange(10.0, 20.0, 1.0)) np.savetxt(self.test_dir + "new.dat", np.arange(20.0, 30.0, 1.0)) np.savetxt(self.test_dir + "attribute.dat", np.arange(0, 10, 1), fmt="%i") np.savetxt(self.test_dir + "wavelength.dat", np.arange(0.0, 10.0, 1.0)) data2d = np.random.normal(loc=0, scale=2e-4, size=(10, 10)) np.savetxt(self.test_dir + "data_2d.dat", data2d) self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data( self.test_dir, files=[ "parang.dat", "new.dat", "attribute.dat", "data_2d.dat", "wavelength.dat", ], ) def test_input_data(self) -> None: data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.007133341144768919, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_parang_reading(self) -> None: module = ParangReadingModule( name_in="parang1", data_tag="images", file_name="parang.dat", input_dir=None, overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("parang1") data = self.pipeline.get_data("header_images/PARANG") assert data.dtype == "float64" assert data == pytest.approx( np.arange(10.0, 20.0, 1.0), rel=self.limit, abs=0.0 ) assert data.shape == (10,) def test_parang_reading_same(self) -> None: module = ParangReadingModule( name_in="parang2", data_tag="images", file_name="parang.dat", input_dir=None, overwrite=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("parang2") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The PARANG attribute is already present and " "contains the same values as are present in " "parang.dat." ) def test_parang_reading_present(self) -> None: module = ParangReadingModule( name_in="parang3", data_tag="images", file_name="new.dat", input_dir=None, overwrite=False, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("parang3") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The PARANG attribute is already present. Set the " "'overwrite' parameter to True in order to " "overwrite the values with new.dat." ) def test_parang_reading_overwrite(self) -> None: module = ParangReadingModule( file_name="new.dat", name_in="parang4", input_dir=None, data_tag="images", overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("parang4") def test_parang_reading_2d(self) -> None: module = ParangReadingModule( name_in="parang6", data_tag="images", file_name="data_2d.dat", input_dir=None, overwrite=False, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("parang6") assert ( str(error.value) == "The input file data_2d.dat should contain a 1D data set with " "the parallactic angles." ) def test_attribute_reading(self) -> None: module = AttributeReadingModule( file_name="attribute.dat", attribute="EXP_NO", name_in="attribute1", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) self.pipeline.run_module("attribute1") data = self.pipeline.get_data("header_images/EXP_NO") assert data.dtype == "int64" assert data == pytest.approx(np.arange(10), rel=self.limit, abs=0.0) assert data.shape == (10,) def test_attribute_reading_present(self) -> None: module = AttributeReadingModule( file_name="parang.dat", attribute="PARANG", name_in="attribute3", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("attribute3") assert ( warning[0].message.args[0] == "The attribute 'PARANG' is already present. Set " "the 'overwrite' parameter to True in order to " "overwrite the values with parang.dat." ) def test_attribute_reading_invalid(self) -> None: module = AttributeReadingModule( file_name="attribute.dat", attribute="test", name_in="attribute4", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("attribute4") assert str(error.value) == "'test' is not a valid attribute." def test_attribute_reading_2d(self) -> None: module = AttributeReadingModule( file_name="data_2d.dat", attribute="DITHER_X", name_in="attribute5", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("attribute5") assert ( str(error.value) == "The input file data_2d.dat should contain a 1D list with " "attributes." ) def test_attribute_reading_same(self) -> None: module = AttributeReadingModule( file_name="attribute.dat", attribute="EXP_NO", name_in="attribute6", input_dir=None, data_tag="images", overwrite=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("attribute6") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The 'EXP_NO' attribute is already present and " "contains the same values as are present in " "attribute.dat." ) def test_attribute_reading_overwrite(self) -> None: module = AttributeReadingModule( file_name="parang.dat", attribute="PARANG", name_in="attribute7", input_dir=None, data_tag="images", overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("attribute7") data = self.pipeline.get_attribute("images", "PARANG", static=False) assert data == pytest.approx( np.arange(10.0, 20.0, 1.0), rel=self.limit, abs=0.0 ) def test_wavelength_reading(self) -> None: module = WavelengthReadingModule( file_name="wavelength.dat", name_in="wavelength1", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) self.pipeline.run_module("wavelength1") data = self.pipeline.get_data("header_images/WAVELENGTH") assert data.dtype == "float64" assert data == pytest.approx(np.arange(10.0), rel=self.limit, abs=0.0) assert data.shape == (10,) def test_wavelength_reading_same(self) -> None: module = WavelengthReadingModule( file_name="wavelength.dat", name_in="wavelength2", input_dir=None, data_tag="images", overwrite=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("wavelength2") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The WAVELENGTH attribute is already present and " "contains the same values as are present in " "wavelength.dat." ) def test_wavelength_reading_present(self) -> None: module = WavelengthReadingModule( file_name="new.dat", name_in="wavelength3", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("wavelength3") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The WAVELENGTH attribute is already present. Set " "the 'overwrite' parameter to True in order to " "overwrite the values with new.dat." ) def test_wavelength_reading_overwrite(self) -> None: module = WavelengthReadingModule( file_name="new.dat", name_in="wavelength4", input_dir=None, data_tag="images", overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("wavelength4") def test_wavelength_reading_2d(self) -> None: module = WavelengthReadingModule( file_name="data_2d.dat", name_in="wavelength6", input_dir=None, data_tag="images", overwrite=False, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("wavelength6") assert ( str(error.value) == "The input file data_2d.dat should contain a 1D data set with " "the wavelengths." ) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_attr_writing.py000066400000000000000000000061441512720201400257230ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.attr_writing import ParangWritingModule, AttributeWritingModule from pynpoint.util.tests import create_config, create_random, remove_test_data class TestAttributeWriting: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, files=["parang.dat", "attribute.dat"]) def test_input_data(self) -> None: data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.007153603490533874, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_parang_writing(self) -> None: module = ParangWritingModule( file_name="parang.dat", name_in="parang_write1", output_dir=None, data_tag="images", header=None, ) self.pipeline.add_module(module) self.pipeline.run_module("parang_write1") data = np.loadtxt(self.test_dir + "parang.dat") assert np.sum(data) == pytest.approx(10.0, rel=self.limit, abs=0.0) assert data.shape == (5,) def test_attribute_writing(self) -> None: module = AttributeWritingModule( file_name="attribute.dat", name_in="attr_write1", output_dir=None, data_tag="images", attribute="PARANG", header=None, ) self.pipeline.add_module(module) self.pipeline.run_module("attr_write1") data = np.loadtxt(self.test_dir + "attribute.dat") assert np.sum(data) == pytest.approx(10.0, rel=self.limit, abs=0.0) assert data.shape == (5,) def test_attribute_not_present(self) -> None: module = AttributeWritingModule( file_name="attribute.dat", name_in="attr_write3", output_dir=None, data_tag="images", attribute="test", header=None, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("attr_write3") assert str(error.value) == "The 'test' attribute is not present in 'images'." def test_parang_writing_not_present(self) -> None: with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: del hdf_file["header_images/PARANG"] module = ParangWritingModule( file_name="parang.dat", name_in="parang_write3", output_dir=None, data_tag="images", header=None, ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("parang_write3") assert str(error.value) == "The PARANG attribute is not present in 'images'." PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_fitsreading.py000066400000000000000000000220271512720201400255030ustar00rootroot00000000000000import os import pytest import numpy as np from astropy.io import fits from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.util.tests import create_config, create_star_data, remove_test_data class TestFitsReading: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "fits") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["fits"], files=["filenames.dat"]) def test_fits_reading(self) -> None: module = FitsReadingModule( name_in="read1", input_dir=self.test_dir + "fits", image_tag="input", overwrite=False, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("input") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_fits_reading_overwrite(self) -> None: module = FitsReadingModule( name_in="read2", input_dir=self.test_dir + "fits", image_tag="input", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("input") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_static_not_found(self) -> None: self.pipeline.set_attribute("config", "DIT", "Test", static=True) module = FitsReadingModule( name_in="read3", input_dir=self.test_dir + "fits", image_tag="input", overwrite=True, check=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read3") assert len(warning) == 2 for item in warning: assert ( item.message.args[0] == "Static attribute DIT (=Test) not found in the FITS " "header." ) self.pipeline.set_attribute("config", "DIT", "ESO DET DIT", static=True) def test_static_changing(self) -> None: with fits.open(self.test_dir + "fits/images_0.fits") as hdu: header = hdu[0].header header["HIERARCH ESO DET DIT"] = 0.1 hdu.writeto(self.test_dir + "fits/images_0.fits", overwrite=True) with fits.open(self.test_dir + "fits/images_1.fits") as hdu: header = hdu[0].header header["HIERARCH ESO DET DIT"] = 0.2 hdu.writeto(self.test_dir + "fits/images_1.fits", overwrite=True) module = FitsReadingModule( name_in="read4", input_dir=self.test_dir + "fits", image_tag="input", overwrite=True, check=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read4") assert len(warning) == 1 assert ( warning[0].message.args[0] == f"Static attribute ESO DET DIT has changed. " f"Possibly the current file {self.test_dir}fits/" f"images_1.fits does not belong to the data set " f"'input'. Attribute value is updated." ) def test_header_attribute(self) -> None: with fits.open(self.test_dir + "fits/images_0.fits") as hdu: header = hdu[0].header header["PARANG"] = 1.0 hdu.writeto(self.test_dir + "fits/images_0.fits", overwrite=True) with fits.open(self.test_dir + "fits/images_1.fits") as hdu: header = hdu[0].header header["PARANG"] = 2.0 header["HIERARCH ESO DET DIT"] = 0.1 hdu.writeto(self.test_dir + "fits/images_1.fits", overwrite=True) module = FitsReadingModule( name_in="read5", input_dir=self.test_dir + "fits", image_tag="input", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read5") def test_non_static_not_found(self) -> None: self.pipeline.set_attribute("config", "DIT", "None", static=True) for i in range(2): with fits.open(f"{self.test_dir}/fits/images_{i}.fits") as hdu: header = hdu[0].header del header["HIERARCH ESO DET DIT"] del header["HIERARCH ESO DET EXP NO"] hdu.writeto(f"{self.test_dir}/fits/images_{i}.fits", overwrite=True) module = FitsReadingModule( name_in="read6", input_dir=self.test_dir + "fits", image_tag="input", overwrite=True, check=True, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read6") assert len(warning) == 2 for item in warning: assert ( item.message.args[0] == "Non-static attribute EXP_NO (=ESO DET EXP NO) not " "found in the FITS header." ) def test_fits_read_files(self) -> None: module = FitsReadingModule( name_in="read7", input_dir=None, image_tag="files", overwrite=False, check=True, filenames=[ self.test_dir + "fits/images_0.fits", self.test_dir + "fits/images_1.fits", ], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read7") assert len(warning) == 2 for item in warning: assert ( item.message.args[0] == "Non-static attribute EXP_NO (=ESO DET EXP NO) not " "found in the FITS header." ) data = self.pipeline.get_data("files") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_fits_read_textfile(self) -> None: with open(self.test_dir + "filenames.dat", "w") as file_obj: file_obj.write(self.test_dir + "fits/images_0.fits\n") file_obj.write(self.test_dir + "fits/images_1.fits\n") module = FitsReadingModule( name_in="read8", input_dir=None, image_tag="files", overwrite=True, check=True, filenames=self.test_dir + "filenames.dat", ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read8") assert len(warning) == 2 for item in warning: assert ( item.message.args[0] == "Non-static attribute EXP_NO (=ESO DET EXP NO) not " "found in the FITS header." ) data = self.pipeline.get_data("files") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_fits_read_files_exists(self) -> None: module = FitsReadingModule( name_in="read9", input_dir=None, image_tag="files", overwrite=True, check=True, filenames=[ f"{self.test_dir}fits/images_0.fits", f"{self.test_dir}fits/images_2.fits", ], ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("read9") assert ( str(error.value) == f"The file {self.test_dir}fits/images_2.fits does not exist. " f"Please check that the path is correct." ) def test_fits_read_textfile_exists(self) -> None: with open(self.test_dir + "filenames.dat", "w") as file_obj: file_obj.write(self.test_dir + "fits/images_0.fits\n") file_obj.write(self.test_dir + "fits/images_2.fits\n") module = FitsReadingModule( name_in="read10", input_dir=None, image_tag="files", overwrite=True, check=True, filenames=self.test_dir + "filenames.dat", ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("read10") assert ( str(error.value) == f"The file {self.test_dir}fits/images_2.fits does not exist. " f"Please check that the path is correct." ) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_fitswriting.py000066400000000000000000000121421512720201400255520ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.fitsreading import FitsReadingModule from pynpoint.readwrite.fitswriting import FitsWritingModule from pynpoint.util.tests import create_config, create_star_data, remove_test_data class TestFitsWriting: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_star_data(self.test_dir + "fits_data") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["fits_data"], files=["test.fits"]) def test_fits_reading(self) -> None: module = FitsReadingModule( name_in="read", input_dir=self.test_dir + "fits_data", image_tag="images", overwrite=True, check=True, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 105.54278879805277, rel=self.limit, abs=0.0 ) assert data.shape == (10, 11, 11) def test_fits_writing(self) -> None: module = FitsWritingModule( file_name="test.fits", name_in="write1", output_dir=None, data_tag="images", data_range=None, overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("write1") def test_filename_extension(self) -> None: with pytest.raises(ValueError) as error: FitsWritingModule( file_name="test.dat", name_in="write3", output_dir=None, data_tag="images", data_range=None, overwrite=True, subset_size=None, ) assert str(error.value) == "Output 'file_name' requires the FITS extension." def test_data_range(self) -> None: module = FitsWritingModule( file_name="test.fits", name_in="write4", output_dir=None, data_tag="images", data_range=(0, 10), overwrite=True, subset_size=None, ) self.pipeline.add_module(module) self.pipeline.run_module("write4") def test_not_overwritten(self) -> None: module = FitsWritingModule( file_name="test.fits", name_in="write5", output_dir=None, data_tag="images", data_range=None, overwrite=False, subset_size=None, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("write5") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Filename already present. Use overwrite=True " "to overwrite an existing FITS file." ) def test_subset_size(self) -> None: module = FitsWritingModule( file_name="test.fits", name_in="write6", output_dir=None, data_tag="images", data_range=None, overwrite=True, subset_size=10, ) self.pipeline.add_module(module) self.pipeline.run_module("write6") def test_subset_size_data_range(self) -> None: module = FitsWritingModule( file_name="test.fits", name_in="write7", output_dir=None, data_tag="images", data_range=(8, 18), overwrite=True, subset_size=10, ) self.pipeline.add_module(module) self.pipeline.run_module("write7") def test_attribute_length(self) -> None: text = "long_text_long_text_long_text_long_text_long_text_long_text_long_text_long_text" self.pipeline.set_attribute("images", "short", "value", static=True) self.pipeline.set_attribute( "images", "longer_than_eight1", "value", static=True ) self.pipeline.set_attribute("images", "longer_than_eight2", text, static=True) module = FitsWritingModule( file_name="test.fits", name_in="write8", output_dir=None, data_tag="images", data_range=None, overwrite=True, subset_size=None, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("write8") assert len(warning) == 1 assert ( warning[0].message.args[0] == "Key 'hierarch longer_than_eight2' with value " "'long_text_long_text_long_text_long_text_long_" "text_long_text_long_text_long_text' is too " "long for the FITS format. To avoid an error, " "the value was truncated to 'long_text_long_text" "_long_text_long_text_long_tex'." ) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_hdf5reading.py000066400000000000000000000065151512720201400253700ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.hdf5reading import Hdf5ReadingModule from pynpoint.util.tests import create_config, create_random, remove_test_data class TestHdf5Reading: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir + "data") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["data"]) def test_hdf5_reading(self) -> None: data = np.random.normal(loc=0, scale=2e-4, size=(4, 10, 10)) with h5py.File(self.test_dir + "data/PynPoint_database.hdf5", "a") as hdf_file: hdf_file.create_dataset("extra", data=data) hdf_file.create_dataset("header_extra/PARANG", data=[1.0, 2.0, 3.0, 4.0]) module = Hdf5ReadingModule( name_in="read1", input_filename="PynPoint_database.hdf5", input_dir=self.test_dir + "data", tag_dictionary={"images": "images"}, ) self.pipeline.add_module(module) self.pipeline.run_module("read1") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.007153603490533874, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_dictionary_none(self) -> None: module = Hdf5ReadingModule( name_in="read2", input_filename="PynPoint_database.hdf5", input_dir=self.test_dir + "data", tag_dictionary=None, ) self.pipeline.add_module(module) self.pipeline.run_module("read2") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.007153603490533874, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) def test_wrong_tag(self) -> None: module = Hdf5ReadingModule( name_in="read3", input_filename="PynPoint_database.hdf5", input_dir=self.test_dir + "data", tag_dictionary={"test": "test"}, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read3") assert len(warning) == 1 assert ( warning[0].message.args[0] == "The dataset with tag name 'test' is not found in " "the HDF5 file." ) with h5py.File(self.test_dir + "data/PynPoint_database.hdf5", "r") as hdf_file: assert set(hdf_file.keys()) == set( ["extra", "header_extra", "header_images", "images"] ) def test_no_input_filename(self) -> None: module = Hdf5ReadingModule( name_in="read4", input_filename=None, input_dir=self.test_dir + "data", tag_dictionary=None, ) self.pipeline.add_module(module) self.pipeline.run_module("read4") data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.007153603490533874, rel=self.limit, abs=0.0 ) assert data.shape == (5, 11, 11) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_hdf5writing.py000066400000000000000000000066461512720201400254470ustar00rootroot00000000000000import os import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.hdf5reading import Hdf5ReadingModule from pynpoint.readwrite.hdf5writing import Hdf5WritingModule from pynpoint.util.tests import create_config, create_random, remove_test_data class TestHdf5Writing: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, files=["test.hdf5"]) def test_hdf5_writing(self) -> None: module = Hdf5WritingModule( name_in="write1", file_name="test.hdf5", output_dir=None, tag_dictionary={"images": "data1"}, keep_attributes=True, overwrite=True, ) self.pipeline.add_module(module) self.pipeline.run_module("write1") def test_no_data_tag(self) -> None: module = Hdf5WritingModule( name_in="write2", file_name="test.hdf5", output_dir=None, tag_dictionary={"empty": "empty"}, keep_attributes=True, overwrite=False, ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("write2") assert len(warning) == 1 assert ( warning[0].message.args[0] == "No data under the tag which is linked by the " "InputPort." ) def test_overwrite_false(self) -> None: module = Hdf5WritingModule( name_in="write3", file_name="test.hdf5", output_dir=None, tag_dictionary={"images": "data2"}, keep_attributes=True, overwrite=False, ) self.pipeline.add_module(module) self.pipeline.run_module("write3") def test_dictionary_none(self) -> None: module = Hdf5WritingModule( name_in="write4", file_name="test.hdf5", output_dir=None, tag_dictionary=None, keep_attributes=True, overwrite=False, ) self.pipeline.add_module(module) self.pipeline.run_module("write4") def test_hdf5_reading(self) -> None: module = Hdf5ReadingModule( name_in="read", input_filename="test.hdf5", input_dir=self.test_dir, tag_dictionary={"data1": "data1", "data2": "data2"}, ) self.pipeline.add_module(module) self.pipeline.run_module("read") data1 = self.pipeline.get_data("data1") data2 = self.pipeline.get_data("data2") data3 = self.pipeline.get_data("images") assert data1 == pytest.approx(data2, rel=self.limit, abs=0.0) assert data2 == pytest.approx(data3, rel=self.limit, abs=0.0) attribute1 = self.pipeline.get_attribute("images", "PARANG", static=False) attribute2 = self.pipeline.get_attribute("data1", "PARANG", static=False) attribute3 = self.pipeline.get_attribute("data2", "PARANG", static=False) assert np.allclose(attribute1, attribute2, rtol=self.limit, atol=0.0) assert np.allclose(attribute2, attribute3, rtol=self.limit, atol=0.0) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_nearreading.py000066400000000000000000000237041512720201400254660ustar00rootroot00000000000000import os import pytest import numpy as np from astropy.io import fits from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.nearreading import NearReadingModule from pynpoint.util.tests import create_config, create_near_data, remove_test_data class TestNearReading: def setup_class(self) -> None: self.limit = 1e-8 self.test_dir = os.path.dirname(__file__) + "/" self.fitsfile = self.test_dir + "near/images_0.fits" create_near_data(path=self.test_dir + "near") create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) self.pipeline.set_attribute( "config", "NFRAMES", "ESO DET CHOP NCYCLES", static=True ) self.pipeline.set_attribute("config", "EXP_NO", "ESO TPL EXPNO", static=True) self.pipeline.set_attribute("config", "NDIT", "None", static=True) self.pipeline.set_attribute("config", "PARANG_START", "None", static=True) self.pipeline.set_attribute("config", "PARANG_END", "None", static=True) self.pipeline.set_attribute("config", "DITHER_X", "None", static=True) self.pipeline.set_attribute("config", "DITHER_Y", "None", static=True) self.pipeline.set_attribute("config", "PIXSCALE", 0.045, static=True) self.pipeline.set_attribute("config", "MEMORY", 100, static=True) self.positions = ("chopa", "chopb") def teardown_class(self) -> None: remove_test_data(self.test_dir, folders=["near"]) def test_near_read(self) -> None: module = NearReadingModule( name_in="read1a", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) self.pipeline.run_module("read1a") for item in self.positions: data = self.pipeline.get_data(item) assert np.mean(data) == pytest.approx(0.060582854, rel=self.limit, abs=0.0) assert data.shape == (10, 10, 10) def test_near_subtract_crop_mean(self) -> None: module = NearReadingModule( name_in="read1b", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], subtract=True, crop=(None, None, 0.3), combine="mean", ) self.pipeline.add_module(module) self.pipeline.run_module("read1b") data = self.pipeline.get_data(self.positions[0]) assert np.mean(data) == pytest.approx(0.0, rel=self.limit, abs=0.0) assert data.shape == (2, 7, 7) data = self.pipeline.get_data(self.positions[1]) assert np.mean(data) == pytest.approx(0.0, rel=self.limit, abs=0.0) assert data.shape == (2, 7, 7) def test_near_median(self) -> None: module = NearReadingModule( name_in="read1c", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], combine="median", ) self.pipeline.add_module(module) self.pipeline.run_module("read1c") data = self.pipeline.get_data(self.positions[0]) assert np.mean(data) == pytest.approx(0.060582854, rel=self.limit, abs=0.0) assert data.shape == (2, 10, 10) data = self.pipeline.get_data(self.positions[1]) assert np.mean(data) == pytest.approx(0.060582854, rel=self.limit, abs=0.0) assert data.shape == (2, 10, 10) def test_static_not_found(self) -> None: self.pipeline.set_attribute("config", "DIT", "Test", static=True) module = NearReadingModule( name_in="read2", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read2") assert len(warning) == 4 for item in warning: assert ( item.message.args[0] == "Static attribute DIT (=Test) not found in the FITS " "header." ) self.pipeline.set_attribute("config", "DIT", "ESO DET SEQ1 DIT", static=True) def test_nonstatic_not_found(self) -> None: self.pipeline.set_attribute("config", "NDIT", "Test", static=True) module = NearReadingModule( name_in="read3", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read3") assert len(warning) == 4 for item in warning: assert ( item.message.args[0] == "Non-static attribute NDIT (=Test) not found in the " "FITS header." ) self.pipeline.set_attribute("config", "NDIT", "None", static=True) def test_check_header(self) -> None: with fits.open(self.fitsfile) as hdulist: hdulist[0].header["ESO DET CHOP ST"] = "F" hdulist[0].header["ESO DET CHOP CYCSKIP"] = 1 hdulist[0].header["ESO DET CHOP CYCSUM"] = "T" hdulist.writeto(self.fitsfile, overwrite=True) module = NearReadingModule( name_in="read4", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read4") assert len(warning) == 3 assert warning[0].message.args[0] == "Dataset was obtained without chopping." assert warning[1].message.args[0] == "Chop cycles (1) have been skipped." assert warning[2].message.args[0] == "FITS file contains averaged images." with fits.open(self.fitsfile) as hdulist: hdulist[0].header["ESO DET CHOP ST"] = "T" hdulist[0].header["ESO DET CHOP CYCSKIP"] = 0 hdulist[0].header["ESO DET CHOP CYCSUM"] = "F" hdulist.writeto(self.fitsfile, overwrite=True) def test_frame_type_invalid(self) -> None: with fits.open(self.fitsfile) as hdulist: hdulist[10].header["ESO DET FRAM TYPE"] = "Test" hdulist.writeto(self.fitsfile, overwrite=True) module = NearReadingModule( name_in="read5", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("read5") assert ( str(error.value) == "Frame type (Test) not a valid value. Expecting HCYCLE1 or " "HCYCLE2 as value for ESO DET FRAM TYPE." ) def test_frame_type_missing(self) -> None: with fits.open(self.fitsfile) as hdulist: hdulist[10].header.remove("ESO DET FRAM TYPE") hdulist.writeto(self.fitsfile, overwrite=True) module = NearReadingModule( name_in="read6", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.raises(ValueError) as error: self.pipeline.run_module("read6") assert ( str(error.value) == "Frame type not found in the FITS header. Image number: 9." ) def test_same_cycle(self) -> None: with fits.open(self.fitsfile) as hdulist: with pytest.warns(UserWarning) as warning: hdulist[10].header["ESO DET FRAM TYPE"] = "HCYCLE1" assert len(warning) == 1 assert ( warning[0].message.args[0] == "Keyword name 'ESO DET FRAM TYPE' is greater " "than 8 characters or contains characters not " "allowed by the FITS standard; a HIERARCH card " "will be created." ) hdulist.writeto(self.fitsfile, overwrite=True) module = NearReadingModule( name_in="read7", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read7") assert len(warning) == 2 assert ( warning[0].message.args[0] == "Previous and current chop position (HCYCLE1) are " "the same. Skipping the current image." ) assert ( warning[1].message.args[0] == "The number of images is not equal for chop A and " "chop B." ) def test_odd_number_images(self) -> None: with fits.open(self.fitsfile) as hdulist: del hdulist[11] hdulist.writeto(self.fitsfile, overwrite=True) module = NearReadingModule( name_in="read8", input_dir=self.test_dir + "near", chopa_out_tag=self.positions[0], chopb_out_tag=self.positions[1], ) self.pipeline.add_module(module) with pytest.warns(UserWarning) as warning: self.pipeline.run_module("read8") assert len(warning) == 2 assert ( warning[0].message.args[0] == f"FITS file contains odd number of images: " f"{self.fitsfile}" ) assert ( warning[1].message.args[0] == "The number of chop cycles (5) is not equal to " "half the number of available HDU images (4)." ) PynPoint-PynPoint-e5d0637/tests/test_readwrite/test_textwriting.py000066400000000000000000000055001512720201400255710ustar00rootroot00000000000000import os import h5py import pytest import numpy as np from pynpoint.core.pypeline import Pypeline from pynpoint.readwrite.textwriting import TextWritingModule from pynpoint.util.tests import create_config, create_random, remove_test_data class TestTextWriting: def setup_class(self) -> None: self.limit = 1e-10 self.test_dir = os.path.dirname(__file__) + "/" create_random(self.test_dir, nimages=1) create_config(self.test_dir + "PynPoint_config.ini") self.pipeline = Pypeline(self.test_dir, self.test_dir, self.test_dir) def teardown_class(self) -> None: remove_test_data(self.test_dir, files=["image.dat", "data.dat"]) def test_input_data(self) -> None: data = self.pipeline.get_data("images") assert np.sum(data) == pytest.approx( 0.0008557279524431129, rel=self.limit, abs=0.0 ) assert data.shape == (1, 11, 11) def test_text_writing(self) -> None: text_write = TextWritingModule( name_in="text_write", data_tag="images", file_name="image.dat", output_dir=None, header=None, ) self.pipeline.add_module(text_write) self.pipeline.run_module("text_write") data = np.loadtxt(self.test_dir + "image.dat") assert np.sum(data) == pytest.approx( 0.0008557279524431129, rel=self.limit, abs=0.0 ) assert data.shape == (11, 11) def test_text_writing_ndim(self) -> None: data_4d = np.random.normal(loc=0, scale=2e-4, size=(5, 5, 5, 5)) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file.create_dataset("data_4d", data=data_4d) text_write = TextWritingModule( name_in="write_4d", data_tag="data_4d", file_name="data.dat", output_dir=None, header=None, ) self.pipeline.add_module(text_write) with pytest.raises(ValueError) as error: self.pipeline.run_module("write_4d") assert str(error.value) == "Only 1D or 2D arrays can be written to a text file." def test_text_writing_int(self) -> None: data_int = np.arange(1, 11, 1) with h5py.File(self.test_dir + "PynPoint_database.hdf5", "a") as hdf_file: hdf_file.create_dataset("data_int", data=data_int) text_write = TextWritingModule( name_in="write_int", data_tag="data_int", file_name="data.dat", output_dir=None, header=None, ) self.pipeline.add_module(text_write) self.pipeline.run_module("write_int") data = np.loadtxt(self.test_dir + "data.dat") assert data == pytest.approx(data_int, rel=self.limit, abs=0.0) assert data.shape == (10,)