pax_global_header00006660000000000000000000000064151053364700014516gustar00rootroot0000000000000052 comment=caecdf26509b683e52f11f0ef9e27cde5cfbf519 astropy-pyvo-b70558c/000077500000000000000000000000001510533647000145515ustar00rootroot00000000000000astropy-pyvo-b70558c/.github/000077500000000000000000000000001510533647000161115ustar00rootroot00000000000000astropy-pyvo-b70558c/.github/dependabot.yml000066400000000000000000000011001510533647000207310ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" groups: actions: patterns: - "*" labels: - "no-changelog-entry-needed" - "infrastructure" astropy-pyvo-b70558c/.github/release.yml000066400000000000000000000001141510533647000202500ustar00rootroot00000000000000changelog: exclude: authors: - dependabot - pre-commit-ci astropy-pyvo-b70558c/.github/workflows/000077500000000000000000000000001510533647000201465ustar00rootroot00000000000000astropy-pyvo-b70558c/.github/workflows/changelog.yml000066400000000000000000000010571510533647000226230ustar00rootroot00000000000000name: Changelog check on: pull_request: types: [labeled, unlabeled, opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: changelog: name: Check changelog entry runs-on: ubuntu-latest steps: - name: Check change log entry uses: scientific-python/action-check-changelogfile@1fc669db9618167166d5a16c10282044f51805c0 # 0.3 env: CHANGELOG_FILENAME: CHANGES.rst GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CHECK_MILESTONE: false astropy-pyvo-b70558c/.github/workflows/ci_devtests.yml000066400000000000000000000027761510533647000232210ustar00rootroot00000000000000# This test job is separated out into its own workflow to be able to trigger separately name: CI-devtest on: push: branches: - main - 'v*' tags: - '*' pull_request: schedule: - cron: "0 3 * * 6" workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: devdeps: runs-on: ubuntu-latest name: linux (3.13 py313-test-devdeps-alldeps-cov) steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python 3.13 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.13" - name: Install tox run: python -m pip install --upgrade tox - name: Run tests against dev dependencies run: tox -e py313-test-devdeps-alldeps-cov - name: Upload coverage to codecov uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: file: ./coverage.xml verbose: true py314: runs-on: ubuntu-latest name: linux (3.14 py314-test) steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python 3.14 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.14-dev" - name: Install tox run: python -m pip install --upgrade tox - name: Run tests run: tox -e py314-test astropy-pyvo-b70558c/.github/workflows/ci_tests.yml000066400000000000000000000055211510533647000225110ustar00rootroot00000000000000# Developer version testing is in separate workflow name: CI on: push: branches: - main - 'v*' tags: - '*' pull_request: schedule: - cron: "0 3 * * 6" workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: runs-on: ubuntu-latest name: linux strategy: fail-fast: false matrix: include: - python-version: '3.9' tox_env: py39-test-oldestdeps-alldeps - python-version: '3.10' tox_env: py310-test - python-version: '3.11' tox_env: py311-test-alldeps - python-version: '3.13' tox_env: py313-test-alldeps steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: ${{ matrix.python-version }} - name: Install tox run: python -m pip install --upgrade tox - name: Install library dependencies and run Tests run: tox -e ${{ matrix.tox_env }} mac_windows: runs-on: ${{ matrix.os }} name: ${{ matrix.os }} (3.12 py312-test-alldeps) strategy: fail-fast: false matrix: os: [macos-latest, windows-latest] steps: - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.12' - name: Install tox run: python -m pip install --upgrade tox - name: Python 3.12 with latest dependencies run: tox -e py312-test-alldeps stylecheck: runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python 3.12 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.12' - name: Install tox run: python -m pip install --upgrade tox - name: Check codestyle run: tox -e codestyle linkcheck: if: github.event_name == 'schedule' && github.repository == 'astropy/pyvo' runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python 3.10 uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.10' - name: Install tox run: python -m pip install --upgrade tox - name: Check docs links run: tox -e linkcheck astropy-pyvo-b70558c/.gitignore000066400000000000000000000013751510533647000165470ustar00rootroot00000000000000# Compiled files *.py[cod] *.a *.o *.so __pycache__ # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. *.c # Other generated files */version.py */cython_version.py htmlcov .coverage MANIFEST .ipynb_checkpoints .hypothesis *api_sample* # Sphinx docs/api docs/_build # Eclipse editor project files .project .pydevproject .settings # Pycharm editor project files .idea # Packages/installer info *.egg *.eggs *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz pip-wheel-metadata # Other .cache .tox .*.sw[op] *~ .project .pydevproject .settings coverage.xml # Mac OSX .DS_Store # ipython .ipynb_checkpoints /.pytest_cache/ astropy-pyvo-b70558c/.mailmap000066400000000000000000000024461510533647000162000ustar00rootroot00000000000000Adam Ginsburg Adrian Damian Adrian Damian Brigitta Sipőcz Brigitta Sipőcz Christine Banek Chuanming Mao Chuanming Mao <113032306+ChuanmingMao@users.noreply.github.com> Dustin Jenkins Hugo van Kemenade Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Manon Marchand Markus Demleitner Markus Demleitner Laurent Michel Pey Lian Lim <2090236+pllim@users.noreply.github.com> Ray Plante Ray Plante Stefan Becker Tess Jaffe Theresa Dower Tim Jenness Tom Donaldson astropy-pyvo-b70558c/.readthedocs.yml000066400000000000000000000007521510533647000176430ustar00rootroot00000000000000version: 2 build: image: latest sphinx: builder: html configuration: docs/conf.py fail_on_warning: true # Install regular dependencies. # Then, install special pinning for RTD. build: os: ubuntu-22.04 tools: python: "3.12" apt_packages: - graphviz jobs: post_checkout: - git fetch --unshallow || true python: install: - method: pip path: . extra_requirements: - docs - all # Don't build any extra formats formats: [] astropy-pyvo-b70558c/CHANGES.rst000066400000000000000000000355201510533647000163600ustar00rootroot000000000000001.8 (2025-11-13) ================ Enhancements and Fixes ---------------------- - No longer show a warning when an overflow status is encountered with a maxrec set by the user [#690] - Add support for the jobInfo element for UWS jobs [#679] - Fix DALOverflowWarning error message to only indicate the cause as limits from user or server. [#689] - Add retry option to AsyncTAPJob.fetch_result for transient failures [#696] - Improve the ``MivotViewer`` API: adding support of multiple mapped objects per row and a partial redesign of the public API. Also, improved the gateway between annotations and ``SkyCoord`` objects. [#698] - Adding "hats" and "hips" servicetype shorthands for registry searches for HATS and HIPS resources. [#706] Deprecations and Removals ------------------------- - Upgrade of the ``MivotViewer`` API including the removal of the ``xml_viewer`` module. [#698] 1.7.1 (2025-10-16) ================== Bug Fixes --------- - Fix job result handling to prioritize standard URL structure [#684] - Improving error messaging for invalid SIAv2 inputs. [#693] 1.7 (2025-06-01) ================ Enhancements and Fixes ---------------------- - Extend the MIVOT module with the ability to build annotations component by component and put them into a VOTable. [#627] - Add an API helping to map VOtable data in the Mango data model by using MIVOT [#664] - Make deletion of TAP jobs optional via a new ``delete`` kwarg. [#640] - Correctly delete jobs in ``TAPService.run_async`` even when the server returns an error. [#667] - Change AsyncTAPJob.result to return None if no result is found explicitly. [#644] - Add a UAT constraint to the registry interface for constraining subjects [#649] - Fixed AttributeError when a capability has None standardID in SIA2Service. [#669] Deprecations and Removals ------------------------- - Versions of Python <3.9 are no longer supported. [#639] 1.6.2 (2025-04-07) ================== Bug Fixes --------- - Fix performance issues with datalink results. [#654] - More careful NULL value handling in tapregext data limits. [#659] 1.6.1 (2025-02-12) ================== Bug Fixes --------- - Fix propagating some previously swallowed exceptions. [#614] - Fix string literal generation for SQL query when using numpy >=2.0. [#624] - Switch to do minimal VOSI tables downloads for TAP metadata. [#634] - Provide more informative exception message when requests to endpoints fail. [#641] 1.6 (2024-11-01) ================ Enhancements and Fixes ---------------------- - Add method ``list_interfaces`` to ``pyvo.registry.regtap.RegistryResource`` that returns the list of available interfaces linked to services. Add ``keyword`` parameter in ``get_service`` which should match ``capability_description``. [#505, #525] - Add list of access points to services in ``pyvo.registry.regtap.RegistryResource.describe`` when there is more than one service available [#525] - Add optional ``capability_description`` parameter and a ``__repr__`` to ``pyvo.dal.query.DALService`` abstract base class [#505] - Make ``lax`` parameter default to False in registry get_service method [#505] - Making optional parameters keyword only throughout the public API. [#507] - registry.Ivoid now accepts multiple ivoids and will then match any of them. [#517] - Introducing the new MIVOT module, enabling processed VOTable data mapped to any model serialized in VO-DML. This package dynamically generates python objects whose structure corresponds to the classes of the mapped models. [#497] - MIVOT module: the model references in the dictionaries that are used to build ``MivotInstance`` objects are made more consistent [#551] - RegTAP constraints involving tables other than rr.resource are now done via subqueries for less duplication of interfaces. [#562, #572] - MIVOT module: If the MIVOT annotation block contains a valid instance of the ``mango:EpochPosition`` class, the dynamic object describing the mapped data can generate a valid SkyCoord instance. [#591] - New sub-package discover for global dataset discovery. [#470] - Updated getdatalink to be consistent with iter_datalinks. [#613] Deprecations and Removals ------------------------- - SodaRecordMixin no longer will use a datalink#links endpoint for soda [#580] - Deprecating the use of "image" and "spectrum" in registry Servicetype constraints [#449] 1.5.3 (2024-10-14) ================== Bug Fixes --------- - ``cachedataset()`` and friends again produce reasonable file extensions. [#553] - Path separators are no longer taken over from image titles to file system paths. [#557] - Added `'sia1'` as servicetype for registry searches. [#583] - Adding ``session`` kwarg to allow to pass a session along when turning an Interface into a service via ``Interface.to_service``. [#590] - Include port number if it is present in endpoint access URL. [#582] - Where datalink records are made from table rows, the table row is now accessible as datalinks.original_row. [#559] - Tables returned by RegistryResource.get_tables() now have a utype attribute. [#576] - Registry Spatial constraint now supports Astropy Quantities for the radius argument. [#594] - ``iter_metadata()`` no longer crashes on tables with a datalink RESOURCE and without obscore attributes. [#599] - Avoid assuming that ``'access_url'`` always exits. [#570] Deprecations and Removals ------------------------- - Removed usage of the astropy TestRunner, therefore the unadvertised ``pyvo.test()`` functionality. [#606] 1.5.2 (2024-05-22) ================== Bug Fixes --------- - Avoid Astropy Time error for SIAResult.dateobs when VOX:Image_MJDateObs or ssa:DataID.Date is nan. [#550] - More robust handling of SIA1 FORMAT [#545] 1.5.1 (2024-02-21) ================== Bug Fixes --------- - Fix ``pyvo.registry.Author`` to allow registry searches with author constraints. [#515] - Backing out of having alt_identifier in RegistryResource throughout. Use get_alt_identifier() instead [#523] - Fix ``maxrec=0`` special case for SIA2 queries. [#520] 1.5 (2023-12-19) ================ Enhancements and Fixes ---------------------- - ``registry.search`` now allows programmatic selection of the registry TAP service endpoint with the ``choose_RegTAP_service`` function. [#386] - ``registry.search`` now introspects the TAP service's capabilities and only offers extended functionality or optimisations if the required features are present [#386] - Registry search now finds SIA v2 services. [#422, #428] - Made SIA2Service accept access urls without finding them in the service capabilities. [#500] - Fix session inheritance in SIA2. [#490] - Add intersect modes for the spatial constraint in the registry module ``pyvo.registry.Spatial``. [#495] - Added ``alt_identifier``, ``created``, ``updated`` and ``rights`` to the attributes of ``pyvo.registry.regtap.RegistryResource`` [#492] - Added the ``source_value`` and ``alt_identifier`` information to the verbose output of ``describe()`` in ``regtap``. [#492] - Added convenience method DALResults.to_qtable() that returns an astropy.table.QTable object. [#384] - TAP examples now support the continuation property. [#483] - Fix poor polling behavior when running an async query against a TAP v1.1 service with unsupported WAIT parameter. [#440] - Adding python version to User-Agent. [#452] - Output of ``repr`` for DALResults instance now clearly shows it is a DALResultsTable and not a generic astropy Table. [#478] - Adding support for the VODataService 1.2 nrows attribute on table elements. [#503] Deprecations and Removals ------------------------- - Classes ``SIAService``, ``SIAQuery``, ``SIAResults`` for SIA v2 have been renamed to ``SIA2Service``, ``SIA2Query``, ``SIA2Results`` respectively as well as the variable ``SIA_PARAMETERS_DESC`` to ``SIA2_PARAMETERS_DESC``. The old names now issue an ``AstropyDeprecationWarning``. [#419] - Class ``pyvo.vosi.vodataservice.Table`` has been renamed to ``VODataServiceTable`` to avoid sharing the name with a more generic ``astropy.table.Table`` while having different API. [#484] - Deprecate VOSI ``AvailabilityMixin``, this mean the deprecation of the inherited ``availability``, ``available``, and ``up_since`` properties of DAL service classes, too. [#413] - Deprecating ``ivoid2service`` because it is ill-defined. [#439] 1.4.2 (2023-08-16) ================== - Fixed TapResults to inherit session. [#447] - Fix handling of nan values for Time properties in SIA2 records. [#463] - Fix SIA2 search to accept SkyCoord position inputs. [#459] - Favouring ``VOX:Image_AccessReference`` for data url for SIA1 queries. [#445] 1.4.1 (2023-03-07) ================== - ``pyvo.registry.search`` now accepts an optional ``maxrec`` argument rather than automatically passing the service's hard limit. [#375] - Fixed the RegTAP fragment for the discovery of EPN-TAP tables. [#395] - Removed defaults for optional SIAv1 and SSA query parameters to avoid unnecessarily overriding the server-side defaults. [#367] - Error messages from uws jobs are now in job.errorsummary.message rather than job.message (where one wouldn't expect them given the UWS schema). [#432] - Avoid raising ``AttributeError`` for None responses. [#392] 1.4 (2022-09-26) ================ - Added the TAP Table Manipulation prototype (cadc-tb-upload). [#274] - More explicit exception messages where the payload is sometimes considered if it can be presented properly (simple body text or job error message). [#355] - we now ignore namespaces in xsi-type attributes; this is a lame fix for services like ESO's and MAST's TAP, which do not use canonical prefixes while astropy.utils.xml ignores namespaces. [#323] - Overhaul of the registry.regsearch as discussed in https://blog.g-vo.org/towards-data-discovery-in-pyvo.html. This should be backwards-compatible. [#289] - Versions of astropy <4.1 are no longer supported. [#289] - pyvo.dal warns on results with overflow status. [#329] - Allow session to be passed through in SSA, SCR, and DataLink. [#327] - pyvo.dal.tap.AsyncTAPJob treats parameter names as case-insensitive when retrieving the query from the job record. [#357] - Adding support for prototype features via the ``prototype_feature`` decorator . [#309] - No longer formatting microseconds into SSA time literals. [#351] - Adding operating system to User-Agent. [#344] 1.3 (2022-02-19) ================== - pyvo deals with non-core terms in datalink.bysemantics again. [#299] - Versions of Python <3.8 are no longer supported. [#290] 1.2.1 (2022-01-12) ================== - Get wraps decorator from functools instead of astropy. [#283] 1.2 (2021-12-17) ================ - Make .bysemantics expand its terms to the entire branch by default [#241] - Added optional includeaux flag for regTAP search() [#258] - Added VOResource 1.1 mirrorurl and testquerystring to vosi.Interface [#269] - Versions of Python <3.7 are no longer supported. [#255] 1.1 (2020-06-26) ================ - Added TAP examples function. [#220] - Add default for UWS version. [#199] - Handle description of None when describing a TAP service's tables. [#197] - Properly handle single string keywords value for regsearch(). [#201] - Add support for SIA2. [#206] - Add kwargs to sia2. [#222] - Fix handling relative result URLs. [#192] 1.0 (2019-09-20) ================ - Fix pedantic table parsing not throwing exception. [#140] - Drop support for legacy Python 2.7. [#153] - Sphinx 1.7 or higher is needed to build the documentation. [#160] - Add support for authenticated requests. [#157] - Add a get_job_list method to the TAPService class. [#169] - Replace example's usage of pyvo.object2pos() with SkyCoord.from_name() [#171] - Stop installing files from scripts to /usr/local/bin. Move them to examples/images instead. [#166] - Update ex_casA_image_cat example. [#172] - Fix waveband option in registry.regsearch [#175] - Fix to regtap.ivoid2service(), few decode()'s, para_format_desc was moved to utils. [#177] - Fix default result id for fetch_results of async TAP. [#148] 0.9.3 (2019-05-30) ================== - Fix parsing of SecurityMethod in capabilities. [#114] - Keep up to date with upstream astropy changes. - Move into astropy GitHub organization and README updates. [#133] - Replace mimetype functions with library-based ones. 0.9.2 (2018-10-05) ================== - Fix typo fornat -> format. [#106] 0.9.1 (2018-10-02) ================== - Don't use OR's in RegTAP queries. - Add a timeout to job wait. 0.9 (2018-09-18) ================ - Add a describe method to services to print a human-readable description. - Use a customized user agent in http requests. - Fix some python2/3 issues. - Add general datalink processing method. [#103] 0.8.1 (2018-06-27) ================== - Pass use_names_over_ids=True to astropy's to_table. 0.8 (2018-06-07) ================ - Make XML handling more generic. 0.7rc1 (2018-02-18) =================== - Rework VOSI parsing using astropy xml handling. [#88] - Describe service object bases on vosi capabilities. - Add SODA functionallity. - Fixes and Improvements. 0.6.1 (2017-06-29) ================== - Add Datalink interface. - Put some common functionallity in Mixins. - Minor fixes and improvements. 0.6 (2017-04-17) ================ - Using RegTAP as the only registry interface. - Added a datamodel keyword to registry search. - Using the six libray to address Python 2/3 compatibility issues. - AsyncTAPJob is now context aware. - Improvement upload handling; it is no longer necessary to specifiy the type of upload. - Allow astropy's SkyCoord and Quantity as input parameters. 0.5.2 (2017-02-09) ================== - Remove trailing ? from query urls. [#78] - VOTable fieldnames are now gathered from names only instead of ID and name. 0.5.1 (2017-02-02) ================== - Fix content decoding related error in async result handling. 0.5 (2017-01-13) ================ - Added a RegTAP interface. [#73] - Removed urllib in favor of the requests library. [#74] - Deprecated vao registry interface. - Minor improvements and fixes. 0.4.1 (2016-12-02) ================== - Fix a bug where maxrec wasn't send to the server. 0.4 (2016-12-02) ================ - Use astropy tables for table metadata. [#71] - Fix another content encoding error. [#72] 0.3.2 (2016-12-02) ================== - Adding table property to DALResults. This is a shortcut to access the astropy table. - Improved Error Handling. - Adding ``upload_methods`` to TAPService. [#69] 0.3.1 (2016-12-02) ================== - Fix an error where the content wasn't decoded properly. [#67] - Fix a bug where POST parameters are submitted as GET parameters. 0.3 (2016-12-02) ================ - Adding TAP API. [#58, #66] 0.1 (2016-12-02) ================ - This is the last release that supports Python 2.6. [#62] - This release only contains bug fixes beyond 0.0beta2. astropy-pyvo-b70558c/CONTRIBUTORS.rst000066400000000000000000000016671510533647000172520ustar00rootroot00000000000000PyVO Contributors ----------------- PyVO is an open source project developed through GitHub at https://github.com/astropy/pyvo; community contributions are welcome. This project began in 2012 as a product of the US Virtual Astronomical Observatory, funded through a cooperative agreement with the US National Science Foundation. PyVO was developed with contributions from the following developers: Contributor's Name | Affiliations | GitHub identity ---------------------|----------------|------------------- Stefan Becker | Heidelberg Univ. | funbaker Thomas Boch | CDS | tboch Carlos Brandt | Universita di Roma La Sapienza | chbrandt Markus Demleitner | Heidelberg Univ. | msdemlei Christoph Deil | MPI for Nuclear Physics | cdeil Mike Fitzpatrick | NOAO | Matthew Graham | Caltech | doccosmos Gus Muench | Harvard/CfA, AAS | augustfly Ray Plante | NCSA/UIUC, NIST | RayPlante Brigitta Sipocz | Cambridge, UK | bsipocz Doug Tody | NRAO | astropy-pyvo-b70558c/LICENSE.rst000066400000000000000000000027741510533647000163770ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2020, Astropy-pyvo Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. astropy-pyvo-b70558c/MANIFEST.in000066400000000000000000000003771510533647000163160ustar00rootroot00000000000000include README.rst include CHANGES.rst include LICENSE.rst include setup.cfg include pyproject.toml recursive-include pyvo *py recursive-include docs * recursive-include licenses * prune build prune docs/_build prune docs/api global-exclude *.pyc *.o astropy-pyvo-b70558c/README.rst000066400000000000000000000070251510533647000162440ustar00rootroot00000000000000PyVO =================================== .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat :target: https://www.astropy.org :alt: Powered by Astropy Badge .. image:: https://github.com/astropy/pyvo/workflows/CI/badge.svg?branch=main :target: https://github.com/astropy/pyvo/workflows/CI/badge.svg?branch=main :alt: CI Status .. image:: https://codecov.io/gh/astropy/pyvo/branch/main/graph/badge.svg?token=Mynyo9xoPZ :target: https://codecov.io/gh/astropy/pyvo :alt: Coverage Status .. image:: https://zenodo.org/badge/10865450.svg :target: https://zenodo.org/badge/latestdoi/10865450 PyVO is a package providing access to remote data and services of the Virtual observatory (VO) using Python. Its development was launched by the NSF/NASA-funded Virtual Astronomical Observatory (VAO, www.usvao.org) project (formerly under the name VAOpy) as part of its initiative to bring VO capabilities to desktop. Its goal is to allow astronomers and tool developers to access data and services from remote archives and other web resources. It takes advantage of VO standards to give access to thousands of catalogs, data archives, information services, and analysis tools. It also takes advantage of the general capabilities of Astopy (and numpy), and so a secondary goal is to provide a development platform for migrating more VO capabilities into Astropy. Source code can be found `on GitHub `_ Installation and Requirements ----------------------------- Releases of PyVO are available from `PyPI `_ thus, it and its prerequisites can be most easily installed using ``pip``: pip install pyvo Releases are also conda packaged and available on the ``conda-forge`` channel. PyVO requires Python 3.8 or later. The following packages are required for PyVO: * `astropy `__ (>=4.1) * `requests `_ The following packages are optional dependencies and are required for the full functionality: * pillow * defusedxml For running the tests, and building the documentation, the following infrastructure packages are required: * `pytest-astropy `__ * requests-mock * `sphinx-astropy `__ To install from source use ``pip``: pip install .[all] Using the developer version of PyVO in testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ We recommend and encourage testing against the development version of PyVO in CI, both for dependent libraries and notebook providers. As PyVO is a pure Python library, this can be done as easily as pip installing the developer version from GitHub: pip install git+https://github.com/astropy/pyvo.git#egg=pyvo An example for setting up development version testing for a library as a GitHub Actions Workflow can be found in `astroquery `__. Examples -------- Many instructive examples can be found in the `PyVO Documentation `_. Additional examples can be found in the examples directory. Unit Tests ---------- PyVO uses the Astropy framework for unit tests which is built into the setup script. To run the tests, type: pip install .[test] pytest This will run all unit tests that do not require a network connection. To run all tests, including those that access the network, add the --remote-data option: pytest --remote-data astropy-pyvo-b70558c/RELEASE.rst000066400000000000000000000015211510533647000163620ustar00rootroot00000000000000Release Procedure for pyvo ========================== These steps are intended to help guide a developer into making a new release. For these instructions, version is to be replaced by the version number of the release. 1. Edit setup.cfg and remove .dev from the version number 2. Edit CHANGES.rst to change unreleased to the date of the release. 3. Commit and push 4. git tag -a version -m "releasing new version version" (this makes a release tag) 5. git push origin version 6. python setup.py sdist (this makes a .tar.gz of the package in dist) 7. twine upload sdist/* (this uploads the output of the previous step to pypi) 8. Edit setup.cfg and set the version to the next release number and add .dev after the version number. Add a new section at the top for the next release number 9. Commit and push. This begins the new release astropy-pyvo-b70558c/conftest.py000066400000000000000000000042671510533647000167610ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # This file is the main file used when running tests with pytest directly, # in particular if running e.g. ``pytest docs/``. import os import tempfile import numpy as np from astropy.utils import minversion try: from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS ASTROPY_HEADER = True except ImportError: ASTROPY_HEADER = False # Make sure we use temporary directories for the config and cache # so that the tests are insensitive to local configuration. os.environ['XDG_CONFIG_HOME'] = tempfile.mkdtemp('astropy_config') os.environ['XDG_CACHE_HOME'] = tempfile.mkdtemp('astropy_cache') os.mkdir(os.path.join(os.environ['XDG_CONFIG_HOME'], 'astropy')) os.mkdir(os.path.join(os.environ['XDG_CACHE_HOME'], 'astropy')) # Note that we don't need to change the environment variables back or remove # them after testing, because they are only changed for the duration of the # Python process, and this configuration only matters if running pytest # directly, not from e.g. an IPython session. try: from pyvo import __version__ as version except ImportError: version = 'unknown' # Disable IERS auto download for testing (to support the local, non-remote-data scenario), # revisit this config when minimum supported astropy is 5.1. from astropy.utils.iers import conf as iers_conf iers_conf.auto_download = False def pytest_configure(config): """Configure Pytest with Astropy. Parameters ---------- config : pytest configuration """ if ASTROPY_HEADER: config.option.astropy_header = True # Customize the following lines to add/remove entries from the list of # packages for which version numbers are displayed when running the tests. PYTEST_HEADER_MODULES['Astropy'] = 'astropy' # noqa PYTEST_HEADER_MODULES['requests'] = 'requests' # noqa PYTEST_HEADER_MODULES['defusedxml'] = 'defusedxml' PYTEST_HEADER_MODULES.pop('Pandas', None) PYTEST_HEADER_MODULES.pop('h5py', None) PYTEST_HEADER_MODULES.pop('Scipy', None) PYTEST_HEADER_MODULES.pop('Matplotlib', None) TESTED_VERSIONS['pyvo'] = version astropy-pyvo-b70558c/docs/000077500000000000000000000000001510533647000155015ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/Makefile000066400000000000000000000111641510533647000171440ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest #This is needed with git because git doesn't create a dir if it's empty $(shell [ -d "_static" ] || mkdir -p _static) help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR) -rm -rf api html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." astropy-pyvo-b70558c/docs/_templates/000077500000000000000000000000001510533647000176365ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/_templates/autosummary/000077500000000000000000000000001510533647000222245ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/_templates/autosummary/base.rst000066400000000000000000000003721510533647000236720ustar00rootroot00000000000000{% extends "autosummary_core/base.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}astropy-pyvo-b70558c/docs/_templates/autosummary/class.rst000066400000000000000000000003731510533647000240660ustar00rootroot00000000000000{% extends "autosummary_core/class.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}astropy-pyvo-b70558c/docs/_templates/autosummary/module.rst000066400000000000000000000003741510533647000242470ustar00rootroot00000000000000{% extends "autosummary_core/module.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}astropy-pyvo-b70558c/docs/auth/000077500000000000000000000000001510533647000164425ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/auth/index.rst000066400000000000000000000004141510533647000203020ustar00rootroot00000000000000.. _pyvo-auth: ****************** Auth (`pyvo.auth`) ****************** This module contains submodules which help handle auth when communicating with virtual observatory services. Reference/API ============= .. automodapi:: pyvo.auth :no-inheritance-diagram: astropy-pyvo-b70558c/docs/conf.py000066400000000000000000000151061510533647000170030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this file. # # All configuration values have a default. Some values are defined in # the global Astropy configuration which is loaded here before anything else. # See astropy.sphinx.conf for which values are set there. # 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. # sys.path.insert(0, os.path.abspath('..')) # IMPORTANT: the above commented section was generated by sphinx-quickstart, but # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left # commented out with this explanation to make it clear why this should not be # done. If the sys.path entry above is added, when the astropy.sphinx.conf # import occurs, it will import the *source* version of astropy instead of the # version installed (if invoked as "make html" or directly with sphinx), or the # version in the build directory (if "python setup.py build_sphinx" is used). # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. import datetime import os import sys sys.path.insert(0, os.path.abspath('..')) try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: print('ERROR: the documentation requires the sphinx-astropy package to be installed') sys.exit(1) # Get configuration information from setup.cfg from configparser import ConfigParser conf = ConfigParser() conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) setup_cfg = dict(conf.items('metadata')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.7' # To perform a Sphinx version check that needs to be more specific than # major.minor, call `check_sphinx_version("x.y.z")` here. # check_sphinx_version("1.2.1") # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns.append('_templates') # Add any paths that contain templates here, relative to this directory. if 'templates_path' not in locals(): # in case parent conf.py defines it templates_path = [] templates_path.append('_templates') # This is added to the end of RST files - a good place to put substitutions to # be used globally. rst_epilog += """ """ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does project = setup_cfg['name'] author = setup_cfg['author'] copyright = '{}, {}'.format( datetime.datetime.now(tz=datetime.timezone.utc).year, setup_cfg['author']) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. __import__(project) package = sys.modules[project] # The short X.Y version. version = package.__version__.split('-', 1)[0] # The full version, including alpha/beta/rc tags. release = package.__version__ # -- Options for HTML output --------------------------------------------------- # A NOTE ON HTML THEMES # The global astropy configuration uses a custom theme, 'bootstrap-astropy', # which is installed along with astropy. A different theme can be used or # the options for this theme can be modified by overriding some of the # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. #html_theme_path = [] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. #html_theme = None # Customized theme options html_theme_options = { 'logotext1': 'Py', # white, semi-bold 'logotext2': 'VO', # orange, light 'logotext3': '' # white, light } # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = '' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = '{} v{}'.format(project, release) # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output -------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('index', project + '.tex', project + ' Documentation', author, 'manual')] # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [('index', project.lower(), project + ' Documentation', [author], 1)] # -- Options for the edit_on_github extension ---------------------------------------- if eval(setup_cfg.get('edit_on_github')): extensions += ['astropy.sphinx.ext.edit_on_github'] versionmod = __import__(project + '.version') edit_on_github_project = setup_cfg['github_project'] if versionmod.release: edit_on_github_branch = "v" + versionmod.version else: edit_on_github_branch = "main" edit_on_github_source_root = "" edit_on_github_doc_root = "docs" # -- Enable nitpicky mode - which ensures that all references in the docs resolve ---- nitpicky = True # See docs/nitpick-exceptions file for the actual listing. nitpick_ignore = [] for line in open("nitpick-exceptions"): if line.strip() == "" or line.startswith("#"): continue dtype, target = line.split(None, 1) nitpick_ignore.append((dtype, target.strip())) astropy-pyvo-b70558c/docs/dal/000077500000000000000000000000001510533647000162415ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/dal/index.rst000066400000000000000000001030631510533647000201050ustar00rootroot00000000000000.. _pyvo-data-access: ************************ Data Access (`pyvo.dal`) ************************ This subpackage provides access to the various data services in the VO. Getting started =============== Service objects are created with the service url and provide service-specific metadata. .. doctest-remote-data:: >>> import pyvo as vo >>> service = vo.dal.SIAService("http://dc.g-vo.org/lswscans/res/positions/siap/siap.xml") >>> print(service.description) Scans of plates kept at Landessternwarte Heidelberg-Königstuhl. They were obtained at location, at the German-Spanish Astronomical Center (Calar Alto Observatory), Spain, and at La Silla, Chile. The plates cover a time span between 1880 and 1999. Specifically, HDAP is essentially complete for the plates taken with the Bruce telescope, the Walz reflector, and Wolf's Doppelastrograph at both the original location in Heidelberg and its later home on Königstuhl. They provide a ``search`` method with varying standard parameters for submitting queries. .. doctest-skip:: >>> resultset = service.search(pos=pos, size=size) which returns a :ref:`resultset `. Individual services may define additional, custom parameters. You can pass these to the ``search`` method as (case-insensitive) keyword arguments. Call the method ``describe`` to print human-readable service metadata. You most likely want to use this in a notebook session or similar before actually querying the service. See :ref:`pyvo-services` for a explanation of the different interfaces. .. _pyvo-astro-params: Astrometric parameters ---------------------- Most services expose the astrometric parameters ``pos`` and ``size`` for which PyVO accept `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` objects as well as any other sequence containing right ascension and declination in degrees, which are converted to the standard coordinate frame (in the VO, that usually is ICRS) in the standard units (always degrees in the VO) before they are submitted to the service. Also, `~astropy.coordinates.SkyCoord` can be used to lookup names of astronomical objects you are searching for. .. doctest-remote-data:: >>> import pyvo as vo >>> from astropy.coordinates import SkyCoord >>> from astropy.units import Quantity >>> >>> pos = SkyCoord.from_name('NGC 4993') >>> size = Quantity(0.5, unit="deg") See :ref:`astropy-coordinates` and :ref:`astropy-units` for details. The `~astropy.units.Quantity` object is also suitable for any other astrometric parameter, such as waveband ranges. Some services also accept `~astropy.time.Time` as ``time`` parameter. .. doctest:: >>> from astropy.time import Time >>> time = Time(('2015-01-01T00:00:00', '2018-01-01T00:00:00'), ... format='isot', scale='utc') See :ref:`astropy-time` for explanation. .. _pyvo-verbosity: Verbosity --------- Several VO protocols have the notion of “verbosity”, where 1 means “minimal set of columns”, 2 means “columns most users can work with” and 3 ”everything including exotic items”. Query functions accept these values in the ``verbosity`` parameter. The exact semantics are service-specific. Capabilities ------------ VO services should offer some standard ”support” interfaces specified in VOSI. In pyVO, the information obtained from these endpoints can be obtained from some service attributes. Capabilities describe specific pieces of functionality (such as “this is a spectral search”) and further metadata (such as ”this service will never return more than 10000 rows”). This information is contained in the data structure :py:class:`~pyvo.io.vosi.endpoint.CapabilitiesFile` available through the ``pyvo.dal.vosi.CapabilityMixin.capabilities`` attribute. Exceptions ---------- See the ``pyvo.dal.exceptions`` module. .. _pyvo-services: Services ======== There are five types of services with different purposes but a mostly similar interface available. .. _pyvo_tap: Table Access Protocol --------------------- .. pull-quote:: This protocol defines a service protocol for accessing general table data, including astronomical catalogs as well as general database tables. Access is provided for both database and table metadata as well as for actual table data. This protocol supports the query language `Astronomical Data Query Language (ADQL) `_ within an integrated interface. It also includes support for both synchronous and asynchronous queries. Special support is provided for spatially indexed queries using the spatial extensions in ADQL. A multi-position query capability permits queries against an arbitrarily large list of astronomical targets, providing a simple spatial cross-matching capability. More sophisticated distributed cross-matching capabilities are possible by orchestrating a distributed query across multiple TAP services. -- `Table Access Protocol `_ Consider the following example for using TAP and ADQL, retrieving 5 objects from the GAIA DR3 database, showing their id, position and mean G-band magnitude between 19 - 20: .. doctest-remote-data:: >>> import pyvo as vo >>> tap_service = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> ex_query = """ ... SELECT TOP 5 ... source_id, ra, dec, phot_g_mean_mag ... FROM gaia.dr3lite ... WHERE phot_g_mean_mag BETWEEN 19 AND 20 ... ORDER BY phot_g_mean_mag ... """ >>> result = tap_service.search(ex_query) >>> print(result) source_id ra dec phot_g_mean_mag deg deg mag int64 float64 float64 float32 ------------------- ------------------ ------------------ --------------- 2162809607452221440 315.96596187101636 45.945474015208106 19.0 2000273643933171456 337.1829026565382 50.7218533537033 19.0 2171530448339798784 323.9151025188806 51.27690705826792 19.0 2171810342771336704 323.25913736080776 51.94305655940998 19.0 2180349528028140800 310.5233961869657 50.3486391034819 19.0 While DALResultsTable has some convenience functions, is is often convenient to directly obtain a proper astropy Table using the ``to_table`` method: .. doctest-remote-data:: >>> result.to_table().columns[:3] To explore more query examples, you can try either the ``description`` attribute for some services. For other services like this one, try the ``examples`` attribute. .. doctest-remote-data:: >>> print(tap_service.examples[1]['QUERY']) SELECT TOP 50 l.id, l.pmra as lpmra, l.pmde as lpmde, g.source_id, g.pmra as gpmra, g.pmdec as gpmde FROM lspm.main as l JOIN gaia.dr3lite AS g ON (DISTANCE(g.ra, g.dec, l.raj2000, l.dej2000)<0.01) -- rough pre-selection WHERE DISTANCE( ivo_epoch_prop_pos( g.ra, g.dec, g.parallax, g.pmra, g.pmdec, g.radial_velocity, 2016, 2000), POINT(l.raj2000, l.dej2000) )<0.0002 -- fine selection with PMs TAPServices let you do extensive metadata inspection. For instance, to see the tables available on the Simbad TAP service, say: .. doctest-remote-data:: >>> simbad = vo.dal.TAPService("http://simbad.cds.unistra.fr/simbad/sim-tap") >>> print([tab_name for tab_name in simbad.tables.keys()]) # doctest: +IGNORE_WARNINGS ['TAP_SCHEMA.schemas', 'TAP_SCHEMA.tables', ... 'otypedef', 'otypes', 'ref'] If you know a TAP service's access URL, you can directly pass it to :py:class:`~pyvo.dal.TAPService` to obtain a service object. Sometimes, such URLs are published in papers or passed around through other channels. Most commonly, you will discover them in the VO registry (cf. :ref:`pyvo.registry`). To perform a query using ADQL, the ``search()`` method is used. TAPService instances have several methods to inspect the metadata of the service - in particular, what tables with what columns are available - discussed below. To get an idea of how to write queries in ADQL, have a look at `GAVO's ADQL course`_; it is basically a standardised subset of SQL with some extensions to make it work better for astronomy. .. _GAVO's ADQL course: https://docs.g-vo.org/adql Synchronous vs. asynchronous query ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In synchronous (“sync”) mode, the client keeps a connection for the entire runtime of the query, and query processing generally starts when the request is submitted. This is convenient but becomes brittle as queries have runtimes of the order of minutes, when you may encounter query timeouts. Also, many data providers impose rather strict limits on the runtime allotted to sync queries. In asynchronous (“async”) mode, on the other hand, the client just submits a query and receives a URL that let us inspect the execution status (and retrieve its result) later. This means that no connection needs to be held, which makes this mode a lot more robust of long-running queries. It also supports queuing queries, which allows service operators to be a lot more generous with resource limits. To specify the query mode, you can use either ``run_sync()`` for synchronous query or ``run_async()`` for asynchronous query. .. doctest-remote-data:: >>> job = tap_service.submit_job(ex_query) To learn more details from the asynchronous query, let's look at the ``submit_job()`` method. This submits an asynchronous query without starting it, it creates a new object :py:class:`~pyvo.dal.AsyncTAPJob`. .. doctest-remote-data:: >>> job.url 'http://dc.g-vo.org/__system__/tap/run/async/...' The job URL mentioned before is available in the ``url`` attribute. Clicking on the URL leads you to the query itself, where you can check the status(phase) of the query and decide to run, modify or delete the job. You can also do it via various attributes: .. doctest-remote-data:: >>> job.phase 'PENDING' A newly created job is in the PENDING state. While it is pending, it can be configured, for instance, overriding the server's default time limit (after which the query will be canceled): .. doctest-remote-data:: >>> job.executionduration = 700 >>> job.executionduration 700 When you are ready, you can start the job: .. doctest-remote-data:: >>> job.run() This will put the job into the QUEUED state. Depending on how busy the server is, it will immediately go to the EXECUTING status: .. doctest-remote-data:: >>> job.phase # doctest: +IGNORE_OUTPUT 'EXECUTING' The job will eventually end up in one of the phases: * COMPLETED - if all went to plan, * ERROR - if the query failed for some reason; look at the error attribute of the job to find out details, * ABORTED - if you manually killed the query using the ``abort()`` method or the server killed your query, presumably because it hit the time limit. After the job ends up in COMPLETED, you can retrieve the result: .. doctest-remote-data:: >>> job.phase # doctest: +IGNORE_OUTPUT 'COMPLETED' >>> job.fetch_result() # doctest: +SKIP (result table as shown before) Eventually, it is friendly to clean up the job rather than relying on the server to clean it up once ``job.destruction`` (a datetime that you can change if you need to) is reached. .. doctest-remote-data:: >>> job.delete() For more attributes please read the description for the job object :py:class:`~pyvo.dal.AsyncTAPJob`. With ``run_async()`` you basically submit an asynchronous query and return its result. It is like running ``submit_job()`` first and then run the query manually. Query limit ^^^^^^^^^^^ As a sanity precaution, most services have some default limit of how many rows they will return before overflowing: .. doctest-remote-data:: >>> print(tap_service.maxrec) 20000 To retrieve more rows than that (often conservative) default limit, you must override maxrec in the call to ``search``. PyVO will only warn about truncation when it's unexpected. If you request 5 records and get 5 records, no warning is issued: .. doctest-remote-data:: >>> tap_results = tap_service.search("SELECT * FROM arihip.main", maxrec=5) >>> len(tap_results) 5 However, if results are truncated by server limits without you specifying maxrec, you'll receive a helpful warning: .. doctest-remote-data:: >>> tap_results = tap_service.search("SELECT * FROM arihip.main") # doctest: +SHOW_WARNINGS DALOverflowWarning: Results truncated due to server limits. Consider setting a maxrec value. Services will not let you raise maxrec beyond the hard match limit: .. doctest-remote-data:: >>> print(tap_service.hardlimit) 16000000 A list of the tables and the columns within them is available in the TAPService's :py:attr:`~pyvo.dal.TAPService.tables` attribute by using it as an iterator or calling it's ``describe()`` method for a human-readable summary. Uploads ^^^^^^^ Some TAP services allow you to upload your own tables to make them accessible in queries. For this the various query methods have a ``uploads`` keyword, which accepts a dictionary of table name and content. The mechanism behind this parameter is smart enough to distinct between various types of content, either a :py:class:`~str` pointing to a local file or a file-like object, a :py:class:`~astropy.table.Table` or :py:class:`~pyvo.dal.query.DALResults` for an inline upload, or a url :py:class:`~str` pointing to a remote resource. The uploaded tables will be available as ``TAP_UPLOAD.name``. .. note:: The supported upload methods are available under :py:meth:`~pyvo.dal.tap.TAPService.upload_methods`. .. _table manipulation: Table Manipulation ^^^^^^^^^^^^^^^^^^ .. note:: This is a prototype implementation and the interface might not be stable. More details about the feature at: :ref:`cadc-tb-upload` Some services allow users to create, modify and delete tables. Typically, these functionality is only available to authenticated (and authorized) users. .. Requires proper credentials and authorization .. doctest-skip:: >>> auth_session = vo.auth.AuthSession() >>> # authenticate. For ex: auth_session.credentials.set_client_certificate('') >>> tap_service = vo.dal.TAPService("https://ws-cadc.canfar.net/youcat", session=auth_session) >>> >>> table_definition = ''' ... ... my_table ... This is my very own table ... ... article ... some article ... char ... ... ... count ... how many ... long ... ... ''' >>> tap_service.create_table(name='test_schema.test_table', definition=StringIO(table_definition)) Table content can be loaded from a file or from memory. Supported data formats: tab-separated values (tsv), comma-separated values (cvs) or VOTable (VOTable): .. doctest-skip:: >>> tap_service.load_table(name='test_schema.test_table', ... source=StringIO('article,count\narticle1,10\narticle2,20\n'), format='csv') Users can also create indexes on single columns: .. doctest-skip:: >>> tap_service.create_index(table_name='test_schema.test_table', column_name='article', unique=True) Finally, tables and their content can be removed: .. doctest-skip:: >>> tap_service.remove_table(name='test_schema.test_table') For further information about the service's parameters, see :py:class:`~pyvo.dal.TAPService`. .. _pyvo-sia: Simple Image Access ------------------- .. pull-quote:: The Simple Image Access (SIA) protocol provides capabilities for the discovery, description, access, and retrieval of multi-dimensional image datasets, including 2-D images as well as datacubes of three or more dimensions. SIA data discovery is based on the `ObsCore Data Model `_, which primarily describes data products by the physical axes (spatial, spectral, time, and polarization). Image datasets with dimension greater than 2 are often referred to as datacubes, cube or image cube datasets and may be considered examples of hypercube or n-cube data. PyVO supports both versions of SIA. -- `Simple Image Access `_ Basic queries are done with the ``pos`` and ``size`` parameters described in :ref:`pyvo-astro-params`, with ``size`` being the rectangular region around ``pos``. .. doctest-remote-data:: >>> pos = SkyCoord.from_name('Eta Carina') >>> size = Quantity(0.5, unit="deg") >>> sia_service = vo.dal.SIAService("http://dc.g-vo.org/hppunion/q/im/siap.xml") >>> sia_results = sia_service.search(pos=pos, size=size) The dataset format, 'all' by default, can be specified: .. doctest-remote-data:: >>> sia_results = sia_service.search(pos=pos, size=size, format='graphics') This would return all graphical image formats (png, jpeg, gif) available. Other possible values are image/* mimetypes, or ``metadata``, which returns no image at all but instead a declaration of the additional parameters supported by the given service. The ``intersect`` argument (defaulting to ``OVERLAPS``) lets a program specify the desired relationship between the region of interest and the coverage of the images (case-insensitively): .. doctest-remote-data:: >>> sia_results = sia_service.search(pos=pos, size=size, intersect='covers') Available values: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps with the search region CENTER select images whose center is within the search region ========= ====================================================== This service exposes the :ref:`verbosity ` parameter For further information about the service's parameters, see :py:class:`~pyvo.dal.SIAService`. .. _pyvo-ssa: Simple Spectrum Access ---------------------- .. pull-quote:: The Simple Spectral Access (SSA) Protocol (SSAP) defines a uniform interface to remotely discover and access one dimensional spectra. -- `Simple Spectral Access Protocol `_ Access to (one-dimensional) spectra resembles image access, with some subtile differences: The size parameter is called ``diameter`` here, and hence the search region is always circular with ``pos`` as center: .. doctest-remote-data:: >>> ssa_service = vo.dal.SSAService("http://archive.stsci.edu/ssap/search2.php?id=BEFS&") >>> ssa_results = ssa_service.search(pos=pos, diameter=size) >>> ssa_results[0].getdataurl() 'http://archive.stsci.edu/pub/vospectra/...' SSA queries can be further constrained by the ``band`` and ``time`` parameters. .. doctest-remote-data:: >>> ssa_results = ssa_service.search( ... pos=pos, diameter=size, ... time=Time((53000, 54000), format='mjd'), band=Quantity((1e-13, 1e-12), unit="m")) For further information about the service's parameters, see :py:class:`~pyvo.dal.SSAService`. .. _pyvo-scs: Simple Cone Search ------------------ .. pull-quote:: The Simple Cone Search (SCS) API specification defines a simple query protocol for retrieving records from a catalog of astronomical sources. The query describes sky position and an angular distance, defining a cone on the sky. The response returns a list of astronomical sources from the catalog whose positions lie within the cone, formatted as a VOTable. -- `Simple Cone Search `_ The Simple Cone Search returns results – typically catalog entries – within a circular region on the sky defined by the parameters ``pos`` (again, ICRS) and ``radius``: .. doctest-remote-data:: >>> scs_srv = vo.dal.SCSService('http://dc.g-vo.org/arihip/q/cone/scs.xml') >>> scs_results = scs_srv.search(pos=pos, radius=size) This service exposes the :ref:`verbosity ` parameter. For further information about the service's parameters, see :py:class:`~pyvo.dal.SCSService`. .. _pyvo-slap: Simple Line Access ------------------ .. pull-quote:: The Simple Line Access Protocol (SLAP) is an IVOA data access protocol which defines a protocol for retrieving spectral lines coming from various Spectral Line Data Collections through a uniform interface within the VO framework. -- `Simple Line Access Protocol `_ This service let you query for spectral lines in a certain ``wavelength`` range. The unit of the values is meters, but any unit may be specified using `~astropy.units.Quantity`. For further information about the service's parameters, see :py:class:`~pyvo.dal.SLAService`. Jobs ==== Some services, most notably TAP ones, allow asynchronous operation (i.e., you submit a job, receive a URL where to check for updates, and then can go away) using a VO standard called UWS. These have a ``submit_job`` method, which has the same parameters as their ``search`` but start a server-side job instead of waiting for the result to return. This is particularly useful for longer-running queries or when you want to run several queries in parallel from one script. .. note:: It is good practice to test the query with a maxrec constraint first. When you invoke ``submit_job`` you will get a job object. .. doctest-remote-data:: >>> async_srv = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> job = async_srv.submit_job("SELECT * FROM ivoa.obscore") .. note:: Currently, only `pyvo.dal.tap.TAPService` supports server-side jobs. This job is not yet running yet. To start it invoke ``run`` .. doctest-remote-data:: >>> job.run() # doctest: +IGNORE_OUTPUT Get the current job phase: .. doctest-remote-data:: >>> print(job.phase) # doctest: +IGNORE_OUTPUT EXECUTING Maximum run time in seconds is available and can be changed with :py:attr:`~pyvo.dal.AsyncTAPJob.execution_duration` .. doctest-remote-data:: >>> print(job.execution_duration) 7200.0 >>> job.execution_duration = 3600 Obtaining the job url, which is needed to reconstruct the job at a later point: .. doctest-remote-data:: >>> job_url = job.url >>> job = vo.dal.tap.AsyncTAPJob(job_url) Besides ``run`` there are also several other job control methods: * :py:meth:`~pyvo.dal.AsyncTAPJob.abort` * :py:meth:`~pyvo.dal.AsyncTAPJob.delete` * :py:meth:`~pyvo.dal.AsyncTAPJob.wait` .. note:: Usually the service deletes the job after a certain time, but it is a good practice to delete it manually when done. The destruction time can be obtained and changed with :py:attr:`~pyvo.dal.AsyncTAPJob.destruction` Also, :py:class:`pyvo.dal.AsyncTAPJob` works as a context manager which takes care of this automatically: .. doctest-remote-data:: >>> with async_srv.submit_job("SELECT * FROM ivoa.obscore") as job1: ... job1.run() # doctest: +IGNORE_OUTPUT >>> print('job1 deleted!') job1 deleted! Check for errors in the job execution: .. doctest-remote-data:: >>> job.raise_if_error() If the execution was successful, the resultset can be obtained using :py:meth:`~pyvo.dal.AsyncTAPJob.fetch_result` The result url is available under :py:attr:`~pyvo.dal.AsyncTAPJob.result_uri` .. _pyvo-resultsets: Resultsets and Records ====================== Resultsets contain primarily tabular data and might also provide binary datasets and/or access to additional data services. To obtain the names of the columns in a service response, write: .. doctest-remote-data:: >>> tap_service = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> resultset = tap_service.search("SELECT * FROM ivoa.obscore" ... " WHERE obs_collection='CALIFA' AND" ... " 1=CONTAINS(s_region, CIRCLE(23, 42, 5))" ... " ORDER BY obs_publisher_did") >>> print(resultset.fieldnames) ('dataproduct_type', 'dataproduct_subtype', 'calib_level', 'obs_collection', 'obs_id', 'obs_title', 'obs_publisher_did', 'obs_creator_did', 'access_url', 'access_format', 'access_estsize', 'target_name', 'target_class', 's_ra', 's_dec', 's_fov', 's_region', 's_resolution', 't_min', 't_max', 't_exptime', 't_resolution', 'em_min', 'em_max', 'em_res_power', 'o_ucd', 'pol_states', 'facility_name', 'instrument_name', 's_xel1', 's_xel2', 't_xel', 'em_xel', 'pol_xel', 's_pixel_scale', 'em_ucd', 'preview', 'source_table') Rich metadata equivalent to what is found in VOTables (including unit, ucd, utype, and xtype) is available through resultset's :py:meth:`~pyvo.dal.query.DALResults.getdesc` method: .. doctest-remote-data:: >>> print(resultset.getdesc('s_fov').ucd) phys.angSize;instr.fov .. note:: Two convenience functions let you retrieve columns of a specific physics (by UCD) or with a particular legacy data model annotation (by utype), like this: .. doctest-remote-data:: >>> fieldname = resultset.fieldname_with_ucd('phys.angSize;instr.fov') >>> fieldname = resultset.fieldname_with_utype('obscore:access.reference') Iterating over a resultset gives the rows in the result: .. doctest-remote-data:: >>> for row in resultset: ... print(row['s_fov']) 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 0.01 The total number of rows in the answer is available as its ``len()``: .. doctest-remote-data:: >>> print(len(resultset)) 9 If the row contains datasets, they are exposed by several retrieval methods: .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> row.getdataurl() 'http://dc.g-vo.org/getproduct/califa/datadr3/V500/NGC0551.V500.rscube.fits' >>> type(row.getdataset()) Returning the access url or the a file-like object to further work on. As with general numpy arrays, accessing individual columns via names gives an array of all of their values: .. doctest-remote-data:: >>> column = resultset['obs_id'] whereas integers retrieve rows: .. doctest-remote-data:: >>> row = resultset[0] and both combined gives a single value: .. doctest-remote-data:: >>> value = resultset['obs_id', 0] Row objects may expose certain key columns as properties. See the corresponding API spec listed below for details. * :py:class:`pyvo.dal.sia.SIARecord` * :py:class:`pyvo.dal.ssa.SSARecord` * :py:class:`pyvo.dal.scs.SCSRecord` * :py:class:`pyvo.dal.sla.SLARecord` Convenience methods are available to transform the results into :py:class:`astropy.table.Table` or :py:class:`astropy.table.QTable` (values as quantities): .. doctest-remote-data:: >>> astropy_table = resultset.to_table() >>> astropy_qtable = resultset.to_qtable() Datalink -------- Datalink lets operators associate multiple artifacts with a dataset. Examples include linking raw data, applicable or applied calibration data, derived datasets such as extracted sources, extra documentation, and much more. Datalink can both be used on result rows of queries and from datalink-valued URLs. The typical use is to call ``iter_datalinks()`` on some DAL result; this will iterate over all datalinks pyVO finds in a document and yields :py:class:`pyvo.dal.adhoc.DatalinkResults` instances for them. In those, you can, for instance, pick out items by semantics, where the standard vocabulary datalink documents use is documented at http://www.ivoa.net/rdf/datalink/core. Here is how to find URLs for previews: .. doctest-remote-data:: >>> rows = vo.dal.TAPService("http://dc.g-vo.org/tap" ... ).run_sync("select top 5 * from califadr3.cubes order by califaid") >>> for dl in rows.iter_datalinks(preserve_order=True): # doctest: +IGNORE_WARNINGS ... print(next(dl.bysemantics("#preview"))["access_url"]) http://dc.g-vo.org/getproduct/califa/datadr3/V1200/IC5376.V1200.rscube.fits?preview=True http://dc.g-vo.org/getproduct/califa/datadr3/COMB/IC5376.COMB.rscube.fits?preview=True http://dc.g-vo.org/getproduct/califa/datadr3/V500/IC5376.V500.rscube.fits?preview=True http://dc.g-vo.org/getproduct/califa/datadr3/COMB/UGC00005.COMB.rscube.fits?preview=True http://dc.g-vo.org/getproduct/califa/datadr3/V1200/UGC00005.V1200.rscube.fits?preview=True The call to ``next`` in this example picks the first link marked *preview*. For previews, this may be enough, but in general there can be multiple links for a given semantics value for one dataset. It is sometimes useful to go back to the original row the datalink was generated from; use the ``original_row`` attribute for that (which may be None if pyvo does not know what row the datalink came from): .. doctest-remote-data:: >>> dl.original_row["obs_title"] 'CALIFA V1200 UGC00005' Consider ``original_row`` read only. We do not define what happens when you modify it. Rows from tables supporting datalink also have a ``getdatalink()`` method that returns a ``DatalinkResults`` instance. In general, this is less flexible than using ``iter_datalinks``, and it may also cause more network traffic because each such call will cause a network request. When one has a link to a Datalink document – for instance, from an obscore or SIAP service, where the media type is application/x-votable;content=datalink –, one can build a DatalinkResults using :py:meth:`~pyvo.dal.adhoc.DatalinkResults.from_result_url`: .. doctest-remote-data:: >>> from pyvo.dal.adhoc import DatalinkResults >>> # In this example you know the URL from somewhere >>> url = 'https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?ID=ivo%3A%2F%2Fcadc.nrc.ca%2FHSTHLA%3Fhst_12477_28_acs_wfc_f606w_01%2Fhst_12477_28_acs_wfc_f606w_01_drz' >>> datalink = DatalinkResults.from_result_url(url) >>> next(datalink.bysemantics("#this")).content_type 'application/fits' Server-side processing ---------------------- Some services support the server-side processing of record datasets. This includes spatial cutouts for 2d-images, reducing of spectra to a certain waveband range, and many more depending on the service. Generic Datalink Processing Service ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Generic access to processing services is provided through the datalink interface. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> datalink_proc = next(row.getdatalink().bysemantics('#proc')) .. note:: Most datalink documents only have one processing service per dataset, which is why there is the ``get_first_proc`` shortcut mentioned below. The returned object lets you access the available input parameters which you can pass as keywords to the ``process`` method. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> datalink_proc = row.getdatalink().get_first_proc() >>> print(datalink_proc.input_params) For more details about this have a look at :py:class:`astropy.io.votable.tree.Param`. Calling the method will return a file-like object on success. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> print(datalink_proc) >>> fobj = datalink.process(circle=(1, 1, 1)) SODA ^^^^ SODA is a service with predefined parameters, available on row-level through :py:meth:`pyvo.dal.adhoc.SodaRecordMixin.processed` which exposes a set of parameters which are dependent on the type of service. - ``circle`` -- a sequence (degrees) or :py:class:`astropy.units.Quantity` of longitude, latitude and radius - ``range`` -- a sequence (degrees) or :py:class:`astropy.units.Quantity` of two longitude values and two latitude values describing a rectangle. - ``polygon`` -- multiple pairs of longitude and latitude points - ``band`` -- a sequence of two values (meters) or :py:class:`astropy.units.Quantity` with two bandwidth values. The right sort order will be ensured if converting from frequency to wavelength. Interoperabillity over SAMP --------------------------- Tables and datasets can be send to other astronomical applications, providing they have support for SAMP (Simple Application Messaging Protocol). You can either broadcast whole tables by calling ``broadcast_samp`` on the resultset or a single product (image, spectrum) by calling this method on the SIA or SSA record. .. note:: Don't forget to start the application and make sure there is a running SAMP Hub. Underlying data structures -------------------------- PyVO also allows access to underlying data structures. The astropy data classes :py:class:`astropy.table.Table` and :py:class:`astropy.table.QTable` are accessible with the method :py:meth:`pyvo.dal.DALResults.to_table` and :py:meth:`pyvo.dal.DALResults.to_qtable`, following astropy naming conventions. If you want to work with the XML data structures :py:class:`astropy.io.votable.tree.VOTableFile` or :py:class:`astropy.io.votable.tree.TableElement`, they are accessible by the attributes :py:attr:`pyvo.dal.DALResults.resultstable` and :py:attr:`pyvo.dal.DALResults.votable`, respectively. Reference/API ============= .. automodapi:: pyvo.dal .. automodapi:: pyvo.dal.adhoc astropy-pyvo-b70558c/docs/discover/000077500000000000000000000000001510533647000173175ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/discover/index.rst000066400000000000000000000201731510533647000211630ustar00rootroot00000000000000.. _pyvo-discover: ****************************************** Global Dataset Discovery (`pyvo.discover`) ****************************************** One of the promises of the Virtual Observatory has always been that researchers can globally look for data sets (spectra, say). In the early days of the VO, this looked relatively simple: Just enumerate all image (in those days: SIAP) services, send the same query to them, and then somehow shoehorn together their responses. In reality, there are all kinds of small traps ranging from hanging services to the sheer number of data collections. Hitting hundreds of internet sites takes quite a bit of time even when everything works. In the meantime, the picture is further complicated by the emergence of additional protocols. Images, for instance, can be published through SIA version 1, SIA version 2, and ObsTAP in 2024 – and in particular any combination of that. To keep global discovery viable, several techniques can be applied: * pre-select the services by using the service footprints in space, time, and spectrum. * elide services serving the same data collections * filter duplicate responses before presenting them to the user. This is the topic of this sub-module. In early 2024, this is still in early development. Basic Usage =========== The basic API for dataset discovery is through functions accepting constraints on * space (currently, a cone, i.e., RA, Dec and a radius in degrees), * spectrum (currently, a point in the spectrum as a spectral quantity), and * time (an astropy.time.Time instance or a pair of them to denote an interval). For instance:: from pyvo import discover from astropy import units as u from astropy import time datasets, log = discover.images_globally( space=(273.5, -12.1, 0.1), spectrum=1*u.nm, time=(time.Time('1995-01-01'), time.Time('1995-12-31'))) print(datasets) The function returns a pair of lists. ``datasets`` is a list of `~pyvo.discover.ImageFound` instances. This is the (potentially long) list of datasets located. The second returned value, ``log``, is a sequence of strings noting which services failed and which returned how many records. In exploratory work, it is probably all right to discard the information, but for research purposes, these log lines are an important part of the provenance and must be retained – after all, you might have missed an important clue just because a service was down at the moment you ran your discovery; also, you might want to re-query the failing services at some later stage. All constraints are optional, but without a space constraint, no SIA1 services will be queried. With spectrum and time constraints, it is probably wise to pass ``inclusive=True`` for the time being, as far too many resources do not define their coverage. The discovery function accepts a few other parameters you should know about. These are discussed in the following sections. ``inclusive`` Searching ----------------------- Unfortunately, many resources in the VO do not yet declare their coverage. In its default configuration, pyVO discovery will not query services that do not explicitly say they cover the region of interest and hence always skip these services (unless you manually pass them in, see below). To change that behaviour and try services that do not state their coverage, pass ``inclusive=True``. At this time, this will usually dramatically increase the search time. Setting ``inclusive`` to True will also include datasets that do not declare their temporal of spectral coverage when coming for version 1 SIAP services [TODO: do that in obscore, too?]. This may dramatically increase the number of false positives. It is probably wise to only try ``inclusive=True`` when desperate or when there is a particular necessity to not miss any potentially applicable data. The Watcher ----------- Global discovery usually hits dozens of web services. To see what is going on, you can pass in a function accepting a single string as ``watcher``. The trivial implementation would be:: import datetime def watch(disco, msg): print(datetime.datetime.now(), msg) found, log = discover.images_globally( space=(3, 1, 0.2), watcher=watch) Here, ``disco`` is an ``ImageDiscoverer`` instance; this way, you can further inspect the state of things, e.g., by looking at the ``already_queried`` and ``failed_services`` attributes containing the number of total services tried and of services that gave errors, respectively. Also, although that clearly goes beyond watching, you can call the ``reset_services()`` method. This empties the query queues and thus in effect stops the discovery process. Setting Timeouts ---------------- There are always some services that are broken. A particularly insidious sort of brokenness occurs when data centres run reverse proxies (many do these days) that are up and try to connect to a backend server intended to run the actual service. In certain configurations, it might take the reverse proxy literally forever to notice when a backend server is unreachable, and meanwhile your global discovery will hang, too. Therefore, pyVO global discovery will give up after ``timeout`` seconds, defaulting to 20 seconds. Note that large data collections *may* take longer than that to produce their response; but given the simple constraints we support so far, we would probably consider them broken in that case. Reducing the timeout to just a few seconds will make pyVO continue earlier on broken services. But that of course increases the risk of cutting off working services. If in doubt, have a brief look at the log lines; if a service that sounds promising shows a timeout, perhaps try again with a longer timeout or use partial matching. Overriding service selection ---------------------------- You can also pass a `pyvo.registry.RegistryResults` instance to ``services`` to override the automatic selection of services to query. See the discussion of overriding the service selection in Discoverers_. Discoverers =========== For finer control of the discovery process, you can directly use the `pyvo.discover.image.ImageDiscoverer` class. It is constructed with essentially the same parameters as the search function. To run the discovery, first establish which services to query. There are two ways to do that: * Call the ``discover_services()`` method. This is what the search function does; it uses your constraints as above. * Pass a `pyvo.registry.RegistryResults` instance to ``set_services``. This lets you do your own searches. The image discoverer will only use resources that it knows how to handle. For instance, it is safe to call something like:: discoverer.set_services( registry.search(registry.UAT("galaxies", expand_down=3))) to query services that claim to deal with galaxies or perhaps more specific concepts (although this *will* pull a lot of extra services that the discoverer will just discard). More realistically, :: discoverer.set_services( registry.search(registry.Datamodel("obscore"))) will restrict the operation to obscore services. ``set_services`` will purge redundant services, which means that services that say they (or their data) is served by another service that will already be queried will not be queried. Outside of debugging, this is what you want, but if you really do not want this, you can pass ``purge_redundant=False``. Note, however, that you will still get only one match per access URL of the dataset. Once you have set the services, call ``query_services()`` to fill the ``results`` and ``log_messages`` attributes. It may be informative to watch these change from, say, a different thread. Changing their content has undefined results. A working example would look like this:: from pyvo import discover, registry from astropy.time import Time im_discoverer = discover.image.ImageDiscoverer( space=(274.6880, -13.7920, 0.1), time=(Time('1996-10-04'), Time('1996-10-10'))) im_discoverer.set_services( registry.search(keywords=["heasarc rass"])) im_discoverer.query_services() print(im_discoverer.log_messages) print(im_discoverer.results) Reference/API ============= .. automodapi:: pyvo.discover astropy-pyvo-b70558c/docs/index.rst000066400000000000000000000073101510533647000173430ustar00rootroot00000000000000PyVO ==== Introduction ------------ This is the documentation for PyVO, an affiliated package for the `astropy `__ package. PyVO lets you find and retrieve astronomical data available from archives that support standard `IVOA `__ virtual observatory service protocols. * :ref:`Table Access Protocol (TAP) ` -- accessing source catalogs using sql-ish queries. * :ref:`Simple Image Access (SIA) ` -- finding images in an archive. * :ref:`Simple Spectral Access (SSA) ` -- finding spectra in an archive. * :ref:`Simple Cone Search (SCS) ` -- for positional searching a source catalog or an observation log. * :ref:`Simple Line Access (SLAP) ` -- finding data about spectral lines, including their rest frequencies. .. note:: If you need to access data which is not available via the Virtual Observatory standards, try the astropy affiliated package `astroquery `__ (and, of course, ask the data providers to do the right thing and use the proper standards for their publication). Installation ------------ PyVO is installable via pip. .. code-block:: bash pip install pyvo Source Installation ^^^^^^^^^^^^^^^^^^^ .. code-block:: bash git clone http://github.com/astropy/pyvo cd pyvo python setup.py install Requirements ------------ * numpy * astropy * requests .. _getting-started: Getting started --------------- Data Access ^^^^^^^^^^^ Most of the interesting functionality of pyVO is through the various data access service interfaces (SCS for catalogs, SIA for images, SSAP for spectra, TAP for tables). All of these behave in a similar way. First, there is a class describing a specific type of service: .. doctest-remote-data:: >>> import pyvo as vo >>> service = vo.dal.TAPService("http://dc.g-vo.org/tap") Once you have a service object, you can run queries with parameters specific to the service type. In this example, a database query is enough: .. doctest-remote-data:: >>> resultset = service.search("SELECT TOP 1 * FROM ivoa.obscore") >>> resultset dataproduct_type dataproduct_subtype ... source_table ... object object ... object ---------------- ------------------- ... ------------ image ... ppakm31.maps What is returned by the search method is a to get a resultset object, which essentially works like a numpy record array. It can be processed either by columns: .. doctest-remote-data:: >>> row = resultset[0] >>> column = resultset["dataproduct_type"] or by rows. .. doctest-remote-data:: >>> for row in resultset: ... calib_level = row["calib_level"] For more details on how to use data access services see :ref:`pyvo-data-access` Registry search ^^^^^^^^^^^^^^^ PyVO also contains a component that lets your programs interrogate the IVOA Registry in a simple way. For instance, to iterate over all TAP services supporting the obscore data model (which lets people publish observational datasets through TAP tables), you can write: .. doctest-remote-data:: >>> for service in vo.regsearch(datamodel="obscore"): ... print(service['ivoid']) # doctest: +IGNORE_OUTPUT ivo://aip.gavo.org/tap ivo://archive.stsci.edu/caomtap ivo://astro.ucl.ac.uk/tap ivo://astron.nl/tap ivo://asu.cas.cz/tap ... ivo://xcatdb/3xmmdr7/tap ivo://xcatdb/4xmm/tap Using ``pyvo`` -------------- .. toctree:: :maxdepth: 1 dal/index registry/index discover/index io/index auth/index samp mivot/index utils/index utils/prototypes astropy-pyvo-b70558c/docs/io/000077500000000000000000000000001510533647000161105ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/io/index.rst000066400000000000000000000002771510533647000177570ustar00rootroot00000000000000**************** IO (``pyvo.io``) **************** This module contains submodules which handle datastructures related to the virtual observatory. .. toctree:: :maxdepth: 1 vosi uws astropy-pyvo-b70558c/docs/io/uws.rst000066400000000000000000000411051510533647000174610ustar00rootroot00000000000000****************************************** Universal Worker Service (``pyvo.io.uws``) ****************************************** .. currentmodule:: pyvo.io.uws Introduction ============ The Universal Worker Service (UWS) is an IVOA Recommendation that defines a protocol for managing asynchronous jobs in IVOA services through a RESTful API that can be used to submit, monitor and control asynchronous operations. Getting Started =============== UWS job documents can be parsed directly from URLs, files, or strings using the parsing functions provided by this module: .. doctest-remote-data:: >>> import pyvo as vo >>> from pyvo.io.uws import parse_job, parse_job_list >>> tap_service = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> ex_query = """ ... SELECT TOP 5 ... source_id, ra, dec, phot_g_mean_mag ... FROM gaia.dr3lite ... WHERE phot_g_mean_mag BETWEEN 19 AND 20 ... ORDER BY phot_g_mean_mag ... """ >>> async_job = tap_service.submit_job(ex_query) >>> async_job.run().wait() >>> >>> # Parse a single job from a UWS service >>> job = parse_job(async_job.url) >>> >>> print(f"Job {job.jobid} is {job.phase}") Job ... is COMPLETED >>> jobs = parse_job_list('http://dc.g-vo.org/tap/async') >>> # Show jobs >>> if len(jobs) >= 1: ... print(f"Example job: {jobs[0].jobid} is {jobs[0].phase}") Example job: ... is ... >>> >>> print(f"Successfully parsed {len(jobs)} jobs") # doctest: +SKIP UWS is most commonly encountered when working with :ref:`TAP services ` in asynchronous mode. The :class:`pyvo.dal.AsyncTAPJob` class provides a higher-level interface that uses the UWS parsing functions internally. UWS Job Lifecycle ================== A UWS job progresses through several phases. Most jobs follow the standard progression through the five common states, while the additional states handle special circumstances: **Common Job States**: * **PENDING**: The job is accepted by the service but not yet committed for execution by the client. In this state, the job quote can be read and evaluated. This is the state into which a job enters when it is first created. * **QUEUED**: The job is committed for execution by the client but the service has not yet assigned it to a processor. No results are produced in this phase. * **EXECUTING**: The job has been assigned to a processor. Results may be produced at any time during this phase. * **COMPLETED**: The execution of the job is over. The results may be collected. * **ERROR**: The job failed to complete. No further work will be done nor results produced. Results may be unavailable or available but invalid; either way the results should not be trusted. **Special States**: * **ABORTED**: The job has been manually aborted by the user, or the system has aborted the job due to lack of or overuse of resources. * **HELD**: The job is HELD pending execution and will not automatically be executed (cf. PENDING). * **SUSPENDED**: The job has been suspended by the system during execution. This might be because of temporary lack of resources. The UWS will automatically resume the job into the EXECUTING phase without any intervention when resource becomes available. * **UNKNOWN**: The job is in an unknown state. * **ARCHIVED**: At destruction time the results associated with a job have been deleted to free up resource, but the metadata associated with the job is retained. Core Components =============== Jobs and Job Lists ------------------ The fundamental UWS concepts are represented by these classes: :class:`~pyvo.io.uws.tree.JobSummary` Represents a single UWS job with all its metadata, parameters, and results. :class:`~pyvo.io.uws.tree.Jobs` Represents a list of jobs, typically returned when querying a job list endpoint. :class:`~pyvo.io.uws.endpoint.JobFile` Represents a complete UWS job XML document. Job Parameters and Results -------------------------- :class:`~pyvo.io.uws.tree.Parameters` Container for job input parameters. :class:`~pyvo.io.uws.tree.Parameter` Individual parameter with an ID and value, optionally referenced by URL. :class:`~pyvo.io.uws.tree.Results` Container for job output results. :class:`~pyvo.io.uws.tree.Result` Individual result with metadata like size and MIME type. What is JobInfo? ================ The :class:`~pyvo.io.uws.tree.JobInfo` element is an extensible container that allows UWS implementations to include arbitrary, service-specific information about a job. This for example can be used to provide: * **Implementation-specific metadata** about job execution like progress info * **Service-specific configuration** or diagnostic information * **Extended error details** or debugging information JobInfo exposes this information in a possibly hierarchical dictionary. Working with UWS Jobs ===================== Setting Up Examples ------------------- For the following examples, we'll use a sample UWS job document: >>> import pyvo.io.uws >>> from io import BytesIO >>> >>> # Load sample UWS job from Appendix A >>> sample_job_xml = b''' ... ... async-query-12345 ... query-run-id ... user-1 ... COMPLETED ... 2025-06-19T14:35:00.000Z ... 2025-06-19T14:30:00.000Z ... 2025-06-19T14:30:05.123Z ... 2025-06-19T14:32:18.456Z ... 600 ... 2025-06-26T14:30:00.000Z ... ... ADQL ... SELECT obj_id, ra, dec, magnitude FROM catalog.objects WHERE 1=CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 180.0, 45.0, 1.0)) ... votable ... 10000 ... ... ... ... ... ... 1234 ... 133.333 ... 98.765 ... 0 ... 120 ... compute-node-03 ... 1.1 ... ... ''' >>> >>> job = pyvo.io.uws.parse_job(BytesIO(sample_job_xml)) Parsing and Basic Access ------------------------ >>> # Access basic job information (job created in testsetup above) >>> print(f"Job ID: {job.jobid}") Job ID: async-query-12345 >>> print(f"Phase: {job.phase}") Phase: COMPLETED >>> print(f"Owner: {job.ownerid}") Owner: user-1 >>> print(f"Run ID: {job.runid}") Run ID: query-run-id Timing and Duration Analysis ---------------------------- UWS jobs include comprehensive timing information automatically parsed as :class:`astropy.time.Time` and :class:`astropy.time.TimeDelta` objects: >>> # Job times are automatically parsed as astropy Time objects >>> print(f"Created: {job.creationtime}") Created: 2025-06-19T14:30:00.000 >>> print(f"Started: {job.starttime}") Started: 2025-06-19T14:30:05.123 >>> print(f"Completed: {job.endtime}") Completed: 2025-06-19T14:32:18.456 >>> >>> # Calculate actual execution time >>> if job.starttime and job.endtime: ... duration = job.endtime - job.starttime ... print(f"Job took {duration.to('second').value:.1f} seconds") Job took 133.3 seconds >>> >>> # Check execution limits >>> if job.executionduration: ... print(f"Max allowed: {job.executionduration.to('minute').value:.1f} minutes") Max allowed: 10.0 minutes Parameter Inspection -------------------- Job parameters include both the query itself and configuration options: >>> # Iterate through all parameters >>> for param in job.parameters: ... if param.byreference: ... print(f"Parameter {param.id_}: Referenced from {param.content}") ... else: ... print(f"Parameter {param.id_}: {param.content}") Parameter LANG: ADQL Parameter QUERY: SELECT obj_id, ra, dec, magnitude FROM catalog.objects WHERE 1=CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 180.0, 45.0, 1.0)) Parameter FORMAT: votable Parameter MAXREC: 10000 Result Access and Download -------------------------- Results include the actual data products and associated metadata: >>> # Access all results >>> for result in job.results: ... print(f"Result '{result.id_}':") ... print(f" URL: {result.href}") Result 'result': URL: http://example.com/tap/async/async-query-12345/results/result Working with JobInfo ==================== JobInfo provides access to service-specific metadata about job execution. Basic JobInfo Access -------------------- >>> if job.jobinfo: ... # RECOMMENDED: Dict-like access for expected elements ... try: ... rows_returned = job.jobinfo['rowsReturned'] ... execution_time = job.jobinfo['executionTime'] ... print(f"Query returned {rows_returned.value:,} rows in {execution_time.value:.1f} seconds") ... except KeyError as e: ... print(f"Missing expected element: {e}") ... ... # Use .get() with defaults for optional elements ... queue_pos = job.jobinfo.get('queuePosition', None) ... node_id = job.jobinfo.get('nodeId', None) ... ... if queue_pos: ... print(f"Final queue position: {queue_pos.value}") ... ... if node_id: ... print(f"Executed on: {node_id.text}") Query returned 1,234 rows in 133.3 seconds Final queue position: 0 Executed on: compute-node-03 .. note:: **API Usage Recommendation**: When accessing jobInfo elements, prefer dict-like access (``job.jobinfo['element']``) for expected elements, as this provides clearer error handling with KeyError exceptions. Use ``.get()`` with default values for optional elements. See the jobInfo examples for detailed patterns. Understanding .value vs .text ----------------------------- JobInfo elements provide two ways to access their content: >>> if job.jobinfo: ... duration_elem = job.jobinfo['estimatedDuration'] ... ... # .text returns the raw string content ... print(f"Raw text: '{duration_elem.text}'") ... ... # .value attempts automatic type conversion (int, float, or string) ... print(f"Converted value: {duration_elem.value}") ... print(f"Type: {type(duration_elem.value)}") ... ... # For elements with string content that shouldn't be converted ... node_elem = job.jobinfo['nodeId'] ... print(f"Node ID text: {node_elem.text}") ... print(f"Node ID value: {node_elem.value}") Raw text: '120' Converted value: 120 Type: Node ID text: compute-node-03 Node ID value: compute-node-03 Best Practices for JobInfo Access --------------------------------- >>> def safe_jobinfo_access(job): ... """Demonstrates safe jobInfo access patterns""" ... if not job.jobinfo: ... print("No jobInfo available") ... return ... ... # Pattern 1: Expected elements with error handling ... try: ... rows = job.jobinfo['rowsReturned'] ... exec_time = job.jobinfo['executionTime'] ... print(f"Processed {rows.value:,} rows in {exec_time.value:.1f} seconds") ... except KeyError as e: ... print(f"Required statistics missing: {e}") ... ... # Pattern 2: Optional elements with defaults ... queue_pos = job.jobinfo.get('queuePosition', 'unknown') ... priority = job.jobinfo.get('priority', 'normal') ... ... # Pattern 3: Check existence before accessing ... if 'nodeId' in job.jobinfo: ... node = job.jobinfo['nodeId'] ... print(f"Executed on node: {node.text}") ... ... # Pattern 4: Iterate through all available elements ... print("All jobInfo elements:") ... for key in job.jobinfo.keys(): ... element = job.jobinfo[key] ... print(f" {key}: {element.text}") >>> safe_jobinfo_access(job) Processed 1,234 rows in 133.3 seconds Executed on node: compute-node-03 All jobInfo elements: rowsReturned: 1234 executionTime: 133.333 cpuTime: 98.765 queuePosition: 0 estimatedDuration: 120 nodeId: compute-node-03 serviceVersion: 1.1 Job Status Monitoring ===================== Checking Completion Status -------------------------- >>> # Check if job completed successfully >>> if job.phase == 'COMPLETED': ... print("Job completed successfully!") ... ... # Show summary information ... total_time = (job.endtime - job.creationtime).to('minute').value ... print(f"Total job runtime: {total_time:.1f} minutes") ... ... # Access jobInfo safely ... if job.jobinfo: ... try: ... rows = job.jobinfo['rowsReturned'] ... print(f"Total rows returned: {rows.value:,}") ... except KeyError: ... print("Row count not available") ... elif job.phase == 'ERROR' and job.errorsummary and job.errorsummary.message: ... print(f"Job failed: {job.errorsummary.message.content}") ... else: ... print(f"Job is currently: {job.phase}") Job completed successfully! Total job runtime: 2.3 minutes Total rows returned: 1,234 Working with Job Lists ====================== Parsing Job Lists ----------------- While the examples above focus on individual jobs, you can also parse job lists: .. doctest-remote-data:: >>> from pyvo.io.uws import parse_job_list >>> >>> # Parse a job list from a UWS service endpoint >>> jobs = parse_job_list('http://dc.g-vo.org/tap/async') >>> >>> # Or from a local file >>> # jobs = parse_job_list('job_list.xml') >>> >>> # Iterate through jobs (each is a JobSummary object) >>> for job in jobs: ... print(f"Job {job.jobid}: {job.phase}") # doctest: +IGNORE_OUTPUT Job tk7xsqux: PENDING Job swmua8pe: ERROR Job e58i7yoa: ERROR Job 84w2yz8q: COMPLETED Job 6r51ymds: COMPLETED Job undl67gs: COMPLETED Job _wyoqule: COMPLETED Job ltct6n8d: COMPLETED Job 71kg_stz: COMPLETED Job sc9vc_8h: ERROR Job psn4i8_s: ERROR Job kbrhcstw: COMPLETED Job lvzez3fa: COMPLETED Job l9pfluab: COMPLETED Job lkv6rlxx: COMPLETED Job yb77nhg3: COMPLETED Job vkf4h48y: PENDING Job xr3g9c4d: ERROR Job x9xryn2x: COMPLETED Job wba5foai: COMPLETED Job pni8axcg: ERROR Job j6ip1kn_: ERROR Error Handling ============== When working with failed jobs, check the error summary: >>> # Example of handling a job with errors >>> def check_job_status(job_file): ... job = parse_job(job_file) ... ... if job.phase == 'ERROR': ... print(f"Job {job.jobid} failed!") ... if job.errorsummary: ... print(f"Error type: {job.errorsummary.type_}") ... if job.errorsummary.message and job.errorsummary.message.content: ... print(f"Message: {job.errorsummary.message.content}") Reference/API ============= .. automodapi:: pyvo.io.uws.tree .. automodapi:: pyvo.io.uws.endpoint Appendix A: Example UWS Job Document ==================================== All examples in this documentation reference this sample UWS job XML document. .. code-block:: xml async-query-12345 query-run-id user-1 COMPLETED 2025-06-19T14:35:00.000Z 2025-06-19T14:30:00.000Z 2025-06-19T14:30:05.123Z 2025-06-19T14:32:18.456Z 600 2025-06-26T14:30:00.000Z ADQL SELECT obj_id, ra, dec, magnitude FROM catalog.objects WHERE 1=CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 180.0, 45.0, 1.0)) votable 10000 1234 133.333 98.765 0 120 compute-node-03 1.1 astropy-pyvo-b70558c/docs/io/vosi.rst000066400000000000000000000004641510533647000176260ustar00rootroot00000000000000*********************** VOSI (``pyvo.io.vosi``) *********************** Reference/API ============= .. automodapi:: pyvo.io.vosi.endpoint .. automodapi:: pyvo.io.vosi.voresource .. automodapi:: pyvo.io.vosi.vodataservice .. automodapi:: pyvo.io.vosi.tapregext .. automodapi:: pyvo.io.vosi.availability astropy-pyvo-b70558c/docs/make.bat000066400000000000000000000106411510533647000171100ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end astropy-pyvo-b70558c/docs/mivot/000077500000000000000000000000001510533647000166375ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/mivot/_images/000077500000000000000000000000001510533647000202435ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/mivot/_images/filterProfileService.png000066400000000000000000006053031510533647000251070ustar00rootroot00000000000000PNG  IHDR0!QgAMA a cHRMz&u0`:pQ<eXIfMM*JR(iZ0ASCIIScreenshot^ pHYs%%IR$iTXtXML:com.adobe.xmp 2096 1 Screenshot 908 2 144 144 1 P@IDATxրC9%(ADT0(fӽk 1bAAA(9K9ߧV3;avCuu۳CS9N:E(        @ }k@@@@@@@@@@@@@@@2\ ?t@@@@@@@`3       .@C:       0@@@@@@@@  !O@@@@@@@@        @ ᧀ         |@@@@@@@@ rgx@ <)3 g^ҽiv6mL&`) X     Es /<d.k敤}ر dժ]||{qv̟ٝ/[;'Zjf֯+7gkYgU X  H7_)& b2Ks!~;%u4pᡇ&4ڵk `{r@:vCC3    YY`Æ}[e썲lp4(#ZUnN3y^ܧ&z3Ֆn0r\uV@H0bї̟E4yӻHxxv4[E|֭gu7[|gXibOǒ1cI?uyUrm/.&IVeu+o⣏(m\pU-uС*ˮ]Zǝ/_pĩ駋]x9}sN#5ϫxG@@@@ ZvmvE5eN&xܵ밼L6l]z=m(DjaZɫ?6f뱗]VW5ֱW>|\tb}ʕSnLIJyŊ[u{䭷*T(lp}s+{&&N\\^o]52e iuKkڥKNܿ|;te2l|b7of3tHӦ坡F}x08Z,|\w3gBrYeՋ~>u{F]NIKk>[;I| ={{~Q+nh@f4Ǭ9wϿ'vcﶍ'ELY#?Z֮\#z]QK)ڴ. 6  @NI$w#=rodMn,]+%:o].7_Bߟ)׷oS'b;j G':3z&x28,"_(>;]Be7*:cS婧-~Ạ*U^4vGw2 d}f̽^f}?iԨYǽe`XVF/w}H [kz+jbvyO 3     p~hf`ao&>}/ZX&w]Mo.\xoz&ٽ{y?:މ#:vy>M{ѽ!ikmVȴi}P}?fnTuznׅGu77[͹H{Ok}߮G/vBI4X觟˽z#GvvWt]4ǬYG8m;=thy BW;_V7)6C7W`Nuݻ!b@k /mg=2fev8ހ/\8m1.xA;A W\qhV[Kqlz??]ݽ'?*V YG{'kM_CV={Ho8$U_mImw 2d >~R?LFpSr^õr@@@@Ьk ^к^LIzhJn$/hn*#m祗0M==Y 4j4<ɇreo%m'auo p!lHVL.l5G۶eӦRPzL:$&R_xѢE%z?a7pz]59$wKXA ۿR@2@\0DwUMZ2M+Bg"b(ĶmkE)z] [ϖ Ss2fL`Z(\mOThJH#r5JtvsY t󘶦O_ǭB/h]!%4 -&owR=)j@@@@#0kֆ5cЊ#ڟs]GmI3I։5kObxNt-z<ҢY"7u//֛@|I&CҢCsh:\q,E@Р4j<)EZ^MSʗ_^c|؉:,wƍ˙棏N 8@`JCth joatc[ u=־?LpVg&C, @fMq@S9a-O hc/Q"'eL1’*O?.L#9є5Mʯ rC+tL.yKŊ7S}xiY3=}z_7Jϒ?ϐ;2dH~ۆ=v;@@@@U`۶7M=Χ׬-}=4qhvEWЌހjA3+Ͽ;-[V6ECu)=f~27mzSOzوW\+~ǎvnۦͻuzgR_]uUC:w[ZvI'O}jczM<0i{&>Idt(Y)-F jVh>'aI0z`nDwl=58cIfuzA_=Yנ^D}8qUzAqآĐZ[vyG@ =*A&5FNj/ecvkӦA^e&RJbT_@ 72qÆ}v,Y7k帪< r['761_u}NuE#64m2b7EӘEZ4Ԩ\tQMgJ&M}1lIKxśb.Wr]-kohϫwL#    )ۋO|P(vSzh6xA9}?Tu{|>M̍f1/\w ηuj׃5S-v}MxO7!vhޫU+/vzYk,YN|53,FǘtC}߁C[ S2gNz!%ᅢH\r?_7X%ψfr.]YgX5 :&W,}e[@2B 4BỤQ{!"ͬ47#uY6l@ /2e E5_TxcV6%Ytif%[3lh o fKE`kI%iC@@@@ 9}_t#G$_SOH넚O롡2Ļw}HC0h0Gm#^YP)6, 'N tס/ңh`7AW nnÆ! Q3i~IR B}fupǜlQV:,/ثKf~RL3 @ 䌳펦:7/h( OVR3CrшǞ=Ǚ4VCexyt6r_Jf#XkZ![TKr/eO2    @J}0ҥI?u=ԿhO &%t{lv^oÇO ]j`<˲Iu =C܉w<ju4#K}6fC@wz@N |8/H4ԯ_D/-TrFr_ڮI[ɻޛ/_^߻(9BU7޸?H/mmO.Q"~ݲ@lZ";A;e    )hҤ|S̐!嫯zmС7]n,чzhEbӦSCMtx::uJf?~=w%5ߠX*YWGOdz^jj(a>}x~zͰvX*x'c)SG-F} 1\lo1_r@M Gh~1pb~oRmM_4E+t-[;Y 5_xUPc~\P|Cv~P/CƍJt9fQZwfv;q*G(A+@@@@Q@3_/zy'ߖaæ}:;?7pG_dNYCZɂzC0h yܬYⰼ#lڴ߹0`wgx]6`e23qM7gzϖ y֭0-:;݌Wkȥ~ 9Va<\fnw}? 4ѫ7o.ͧ1/_8`_zq7~w:,,Iiܸ뭫UG&}jy7ɐ :1ij{A5噃⚮liPwqϖʹ۶mcjnH` A9mKjxYSٳHv.g7n uZ:Ku}.0Ϝc}S6m*DP  gf+|aY~*Oni=oI_EM%_.ԇn:DBzi Hn}i @:%Ml{hh'{Z]wѣ狦套f*W.*z/…3    @rǐCu|`'Tz^Ij$ץ1NVȴi}pŊ哷,zsh0n7o[?&>t 7g W߿<-FznW4ţ^`sۅz[>WZ ʬYݦu]f"5'g͚%R"Ä hݺZ@M μk_ݿfcAxS_µ 낢O5k9=dR5:` B/28zݿ-    dfn1ȔI+wKx=4~뺏>.UKZq̚mV Ry ,P-a1Ι'O|_r`AF\}nRJy׿w4ǩS;R/8%5A : s)A+ @}1%g =[*j' .65;kMA7s:өS]FAü[p!]Wr1mjݩ3殻ZHI0袚7뺈MǕǭ˖!wiu2jǀ:w aP۱ @@@@ ɓ{˘1M&pÆqn@ A 5V뒻_>;-a:`{jsѢ䧟75,ǿȝ7;q 'Õ{=GV\@/`xE2}, 7--E5]{:eug믿 T l㏛B~.u7޸Lo2ۆ>CݣzvHPyyg7'矷HuG@ rtJO8_wʕ@ },Q#q4{+!TPy6Fx:TY5.XU9.PTAs 4ڱ㐹6z.)V,W_, RvU0>(77n'k17u(PvR:k:;swA *kzÿ~[) cLv2L=E|(1fV;9C6앃9m3bԽGmA)Q"ίF  :   g.!     &.!:0D)U@@ Y܉C@@@@@@@4 ! qi@@@@@@@"Y5j!     @ 4n\Nʖ-vrtNTV\<۽ڵKL  @q)ٛG@@@@@@@`>@@@@@@@>        0d)        @@@@@@@@p7)y<!U={0}xL |~>Ha>)~0yG@@@@@@@bq)4        @d`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@X`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@X`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@X`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@X`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@X`U@@@@@@@@ fb&@@@@@@@UX@@@@@@@b !fB@@@@@@@@Xr#E`ݲfwo)^T^]]:%j'vyrݬ 7CX   @v8y$$$H\nE@cbGL+ G?34i"{}bwku}b+I?)o]\l=m7+'n;#  d]CINĉA{RJwN̚5K~;+חÇ}7|S,X -2SlY\4mT:v(ڵs(I]b,^y'%Jp@@x2Hcޘg]2{^3 y}V.ע7Lb^k׮fuw?vF׎w;]o7v?:i{|Lg'5wL.@@@@$O}zqlݺU5w\ywGaÆKz.|A]Zl3  0d9 z_3/ 6mژ]Sw flmv?ۮﯧY E{ufR)鏶g>Iٱ@@@?0?~\lڳgπ8CE\z饢%H"fv߾}L#  $@C @ hf  śQ:xiez3fTzޛ@E .@'ݢvxn}~h?lߋy@@@R"йs4ۀ]Pvmw>ཙߪUڵke…fÇKZ3ΐ:u*@_ {5 eբCZܹcƍcٿVtҢC,)S&`Iqy4p~s9Gr!K/j N[Oz PʕYfϞmRJҸqcw:bÆ 6:l2)TYhޢZ|yi=?3v͚5%w2g7o6Cϱڲj*_رcfH:ˮ@0d#=frӑ^E38~m.Ӏo j@?nzIDZ?\in{rϬ?>@@@H@…O>ﺛgC|':-]vСC^oy駥Xbˀwꪫ?6#J U]~mY!&.]B f ^RbEKdĉ7o^9qrBoۢZז_=ޓ믿^4{7xV zWnMZh4,,Y"wyOA^xdհGa*{IWC4=Ǐww!J2f@@(@Cv<3Lfv\w;`cOַAޛ@u^3v;{r U_e}v~=AA@@@ 5ڀFBojk9r oo޽;{As_+o=zTySE;t - I9s9T`=jժzYlۻM|`1h w~ԨQRX/ 29r.Ԅ73Bepis=&Q*\hN7ꫯW\Ӯ@.9ˁr _f$YAo.ݗS裏h%S:OJ@@@ jpX`â7{!%Kt:7xA  zcm۶n=o^ .L4pwOʕKt_K3äI' :vX9~xP3{nb 3HN|&n뮻Ν `9馛`2mڴAС&~ာOС4ҥ$$$ 7`wmhΩfuHD*AϽ~>О4\CL  @ $s (`3h[̛[GϟVnA Z/%7aWlm]Vʀe   ";wn۷3n89r{nKݺu /RPB&ɓj gqFЍoRa?0 wrJJo jV\Y4 IaĈf(p4 [o^z #QJ3\Dn/0i0ܹsE4x[ofu o\xjhfqعsY Y5/r¶ [zi2HlٲEVjHl4aݺuRF [}`,C<]Kx 3/\Pu# Er9NplfQY|RuvhE o¶ۮn/_oq2u@@@Лޡ4֬p:-k׮ Πb jΜ9U4@eҥ2tPѡ"* 4h O=|a7c` A3>0F0L34[==A];wl2^֮][-i&ZQ1Z*dآ RX1wVE  ] F6 ƛ1.Kw/Ih@͖mh7/{cǎT+4(hѢn믿ޝ A㎀l'N4CPTPAx Yr=&Nd oٲewV6l0kxk C>p1 @7TiF@Hn׻wos?6T[F}6mL4H ?=|4@ATxk:r    @ŊcǎUׯ_1tPgyg![4Ӂf(Л-[47ǎnJ̙3`Ȇe˖?.7C>E$Ֆ-h׮][tx &OhPE]dzy,، 7x4mT *$ .V>[reUV-`ݝw)+V۷j@1n8SOMtbܹ&˄@@T !UiY>A76fz޸a˖Hk=m#\;=m7;5-]vZ"u`@@@ [ p !4KB7uhO?]Д@&܄7``ǭ]􆿾E/n)(A@4!T, S'oY~ Be:tfV9r̛7BE^|EfL#  iK _zYO%h{ii $Q E]τM}{zNRd>IZ|:Og   @ܹs@Vސכ:hț:f /X YhlpYg5jIy̓:r-f7t{܅oYJ,):faU/(͛7wWiFI&s=T'OSD`1b<X؆*W,zr/9wZxӺ.cGA@ Yrl `4'DZ#~Yݮm ;I})@d?Ii ?#9^ݿm?>{gӻ۾]nmi@@@kpI;ѻҥy>|XٸqhBٲeEoxiWN;4i߾Ǐ Iɛ7ꫢ/_nڬW+V>|[7 ?h :z] Ϡ7˗귿]j[x5hqٲel޼L)ShѢM|۶mE_:lnK6/A䮻2{իeΝRT)+T`w͚P { .v]X dΗ=g6՝Ze=^ߣo۳eaiZ"C!Z 9i'\_<㍶=pl?nHW϶;   )f͚%u5  E!Vjg]&xxeȐ!qO:  60+"&CSa)"ae    j6!nj#={LJ;pno>֭@A@yƲ1s @ЬE)    93֭[VKn6m?\ ݺu#x!NA@X i L     @z ?~:tHJ,)O]Ǽ'Nlɑ#ʕ  `'CD@@@@@@@ r{       d}Y9B@@@@@ }VJ7R*Pf|fS*q^d^,g*~ޑqD!-"       @&4       u@@@@@@@@ M`HVE@@@@@@@h`F        &0 +"       @40DE]@@@@@@@H҄F@@@@@@@@ Ѣ.        iJ        @h*S7v-k֬}  IxRzuw    @HH8)9s92WCv#okdx+#[ U5UMBo? m֖2e j۱6pBrJg`'O^)۶ /%e{ne~2eٗ3ދztGFY*į {n"/=ZψS @&MH޽E)  d'Sz6/ *Usn]&m\8n,K%7bJx0oC %>TWJq\{n5i׮x,\l޼_ i'vd6ᆱcAwYf?KŊEe̘Ԝ>w7o|Q `8yRo? Ȋ֯͡_{%tiڴRo sXGʕ;_A> HyǞ=SeSSL= =֩V=W_./4-4A_ww9 `/ ı^j)   ]YV꫟HBqg΍CɭvwT9qq"o -;֗3(o_~f~t;#G^)PƩ :B3 hÞR ㏯ysfԤweRP^y챋MB"ȑ㢁_|@ygy*KEVsx_`ȯp,=D`/~6ֱ5\?'  @LJYb@@@B\+Vj_׬YO<ժA ŋ{w'Wu…3J*+yuѴe d͚NN:3'˺Ķm#$gΜ|"'YK.ܘ>+776TiBL:4DѺwtr?`uFٰ5ӧ[7yV#Tѡ')"EIѢCUaY* |"ZumZVcznkf2(V,g29r~zy3D@cMۿ|v糼LuHE<5'w>,+Wn-[zkv>J*un'8eu2o05a׮C *T(ly/*`!&tfU>pVYvsVH7j.&xAw7:?ӧ~-Vy/o(?9~nٸqԠC h l)^[/҉_BIg}9ߙ[;7O8 ;ur&hǦ8>4S-:~mŽ0% Mewp\<8Z t;E- ;+LEAL!@C8Mt@@@@y]ҥ E62bij2|S2xNCyM< 2|ۉ{}BnAt_>DjscynGNڴLoƽP"o9{uo(zy P.ᇥr߉5f_x)K& x 9Nd챟aon)&༆,i_ >}ֱӑĉr>k0sjDyHH8!یC?~P4bxgWhf9sD-Z\ /Z3n]V~qw@%@C:_@@@@Çܣg ͼ; f٬~LQ^yRckyzscv f,Svn0z]ho$^;PN8!_~}w/ZTy}L\|ir8-u9֭;8O ʴPRuҥ 3-m`?L?4pskN#:u3 L9 SӒ}ە/Xhf5@!'t_P9 >;E~sq/ ;e&DɳM}ܳԿ3L[8C,toNR%&N\n\sMw( w&&MZ!3s}m2I̝K=Nt78qoٲ? 3fup+ĺu{=^}w&1q c~u'K:*rҥ[[oxqMhAsR~l8;TSBdѺu Tڼh?lqhN2&~N֭mg49\(7P4PA=sa94O48. &MZݎ 09     M-:h} ]eطo`S>]O=-ޱңG+ygkn2A6/:Aѳ&@`WL5jnЛܼc[MFmWsnܞʬ`OBT t/1Ce?XZjow=NxMᇉOEkDc&m4Ls#W38eײe=ر&>!bvٳj/?zXokٸqy}6 rbSà13w T).\`TxZ˷ 65?TF HM7lHPVcC3̞e&-]-6iR}`-/ٞTP$8p |g28\l''h]-Pj LZVؿdIR*Lh-z_WpPtpU3tEϛz.|?x3$͇fR^Y5tq%#_j{P~֮M|o[q:St>=]`~I `^監E@@@@8hذ<1B*m6lkħ~˗)uAI  6CKh@Un34Y 8q"Vk3pASk9mmX:À-̐ nkwGԉƹVP!1-S}4=$ێ[ o? %K)PbQe˖UW&ۊ݉7p +T~I)SVINEͺZ|uI:\CEs3pDzl>~8ᤌh?:;dL7[xbv|׮NS{0Gq4PD"]L@@@@@4Zرޝ$3[z:܄ N@ӓ'vhW˗#o^s?'Lx)">R?Q݇(Z4Y_|NO,қ6CA:Ͳ "~/$bf5oHnBNoaW#j`rMhAmfY={z~Eumny`SO:t,\s.OIe=rxP~*WN*2gΆ2!_Oe6P} sN|N{߾5"@f !3=    ^y֢OotǗ_~ sL7.u:VBo664\{@>(?m@;:Ą _ȠA,OnbqN }KYn4zuM]Or`}Fvpg˾}n-uC~yU7v< Grj>㉁5ҥր^MαhSٻ7W\q+3,^UnvjL4mZItx pulݺ_ ӧ1;'uѣg]8z4AF)%Ȁ!Cya!lW4SO?-mG8C$_W;C̜ (ظqn̄&;BƌyS/_(J?節T]wˏ}I‰iiթ,7IUJqW\#35jF*>?kV5jo<˭C ܔ~_壏ͫ@᫁Z#=z4?_鬗/YzhV3p*M͛K~\y2qrq9դh|6ˊõnxZoqG+yNgO)[̜?7Z˕+"Çw5]gP1aK(ڶmGN?K$?c'ω?gm6$%ر:n{j{{j{{*T4kV|^{@6h2sx3zl~ߎ>9NY}g֬_Mwwm I+AE "w֞=s{H}(@δ{@@@@@ ٜ9SfW&f\=PPy叜ĉO7ĉM眧G:O*O= 4Xp|(:{3Ob->_|q7'Aa"6oNmwmۖ=o3t475pA˺u |ޔ3s*=!~q3 ^FNPCoɟ?9nhfjذWK̴;[}%WWwg5sرMeq}mrȑó,g*W9?zyʔL ȠAa IBw &~/dwisЭ&(h=ihնreըQdl@3gNqxG%IIܔ:Oo4ʼnF}P 6L.X@@K\:  JVRC)hf˖ N65ΓRݺ5wnN274[ɓW*TQo}uÆ5/_R@4qCʦM̍(M%w';7:ʨQyW1 5Jك֭D(Yx B瞛-wGi(`?{i a3A"     @hMH TV;^b%yjc_e ~YoicGeĈgLwիDT;z7t(H+Mm}WcҧTI~ָq+A + qHő?KSJԬ8F8h>좯T4-Ijhr*WjG ɇ8fl;     @6MZ*Ujy]M8l@@ }5G    xX'3I߾q-|@@ D:     @hx.9snҷ0/w}@0X &     % 83K?myșSS/~!P9@HJ!$a        N@@@@@@@@ )a        N@@@@@@@@ )a       @t ;A@@@@@͚`9,!g6KƠ >?Q`QRM FIC        RR*v        j !jI7{nYfTzu)^x@@@@@@@l$@t:0zh kv       @ ƧH3/2ei]FA@@@@@@@S0H)2/ +"      d1Yx2hv z4KPxL @kW.6b      d <ͦ޽{KjdflHդIHS@ ؿ m{Lv.     D,S>)7S)3d+@ ; hٌ;قcG@@@@@-@8;6cC׮]#Њ$CDQ @@@@@@@  !fl>LJZ2@@@@@@@2CHdSEG@@@@@@@Ⱥd`HscY&`rA3-pNA@ 9{ߏX   @Yh̙Cmr\tNufݕWw Y/^?~\<(EͰ.'r劸G\?^*8qB$OП~65ʱc9ʙ3i:$ x͛W St~s~/.r~/t~/MIƍモU_nMnGY}ܭtRܯf+~+D$ň5 hѣKA@"@@@ qrF<Ӯ];ҥ2gΜaݺuR;8)IENPDQDL1+ATDń ;33bb΀xQ@Q1z W{fgfgfgwgfo?]]U]y!$)#SNueee̹y=sKc̚x⋮SA\f{ n„riӦB\JܹB<v|p)[h_V̙v8㌔̰a܎; '5k7n6h#w 78)-..$Z|W-}sz4>]pn뭷lV]u`g+p2hVC߳gOSOljڴi^Hyk:O%yܐ!C;!kRbүTG~O.sP͛7/qSuڪwytSL˥ѣýwiz9лK[Nr)a>c @m6g۾;EmiWq{ZNcOcm9ax?޲}ƕg}uNiU;~/[ʼq য়~\݄m^{<9n{?n޽Sq]w]+cr"|r[neW22bZn;>tsύ9uvuroԯ~Ll[oԖ7׵5Ovi̕G oX׶u7t\U+?c94܏bjqig_)/w}wRʧo(+?hР0Teɇ@*M(PXXC Twlez@&.@ תI^AU~}{%GW8T*畀^W&^QW>eWBFe|Lwo_N\^aVyjrU2s̸Ǩ2?˽2%W7 |}GuR&xEo\YW:*c„:7{Jz~,@_ψ'o:ѝLhtَռ իR]vYCRͫ)ZW_}uƗJu4ך*Z%ļAg ?<\G׽Qүhuyd;~ghѢ[=\O4bLN~oW\eRx|ƌ3O"P8^xg31yrB|„ =;\gP@*VV!%T`HhP+cEO}y@}.]!|WyyJ}yH'zӳ@ )f(y; {F[53J> ! @@*H#n?Q[r"*RܥK$## +ԭV=OgWoǮ!eD(SfuU?Iqme6ǹ~5jL-'&*:Pq !d%M] Pʵ^W>۶r-/c1r=΀!]P߯ԏ\JW;#7*~I'2R='2!0  ֦ad Y#w| @ @Ivp@wWWuTnwe\E{e5j *Cn뭷wWqsLpOvm۶ur['~bɪf %By]gVp^f~+R|X6tS7߸6mdtzC* rޠAa{u뮻n}J^xPd-|{Unx+qbo] g>ߏU2ݹ>;t+ꤛ(=SںL4H&6|ItYVv,* ZG+W5:*?DLZ-c>w+2.ͼweH&͉ѵ>0-vN[[&QXcf^%ضrs.*'90lw}7iiӦ+CT{9RX a,<$30}&z z;x饗 S\pAk}j0!VVa־%y41 PhOOZ\!V^~Ts ?"ٻCQ' 5FoT/yn[pwFه@Rx`O@m%`>ffl_#COͧQեK0q:@ @YԊD5jT ?yʼnkҤIyynFaug >D\Ww1:vwSO uJSb-Χ;JU^+wmVN[+OJ9 / ]y%G\VilT]B+u%3f,_]z-lgO|ѯt!xWx]׮WЪ헷9g/U-p:y6Ъ^[ UŚ+jɰaZkKѪzܗǍo1*V]V'K4l &SZV>;fǻ]y+USV4]o0Ӊ;:'//{nh:͛^}<5k,4 6ѪCiFl^Δd/7Q8jl޻ʹA~d":}Ι+.EW}DO=0f̘ЄIV\qEk.lwV5 i}yGvͪ>1O\z0k:*sqƮgϞÁƚNw '<%蝪#xCj?*U虱(ds=:(t8?Ϝw1ugy#C*G ? @]UiC@[$0h޽{ؗ;?ZUA!N |ʐAZl t  @r" )NQ]ɕ$!C$RBȕITcyښҩk׮/%2[(ҤS 3ܹGr;Qn}5H,q˖-C97Uץ"Xfι~6v7ݴwFBpخ] UyreWI桇 a?#1}ӵ>M}T씢S F{wtl_ zl3"9*K\ LѨnk23d^¼hN@l5 PzX=$rzAJUO?~캺mjۯw g el d{ GyZ=mll\[ zzObέ$3`0hß}Y88-H|}nU DdwN<ձq@9:-#Bd( aY K$ t]tEv2ރs YX/L,8Eώ 0k.Tg;`P!C(pR?eA]D2VL2%lKFS;5BI럢GI8Jp/@ @(zRxaO$[>2HAQK-eKnW.ϙgKݟJs^LHKxO3~_>"]x%+ӽg Z(zOHdrxoE$zIӾ|85|~&ӱ] |XJ+ts2;Nq I?20Gp%c-bF(ƍs>O<}3ɗa˶4 `Pʨ zD q) ?앧$*/ GOfPPmtqpQ@ @E@RoЧk6(cSuPſ7|KT._6$3\VIU+k6]<%XSpؔv.[)1-V ſn[tW4̦ze]yOZ~*+Ph&r&W^ <ȅVGqזbvO̎=2)̀RE!bWUL|~3ĐKL$3q \$kvTVa@I|lKHWİ+Om[9ȶfwy2'9SHH,o!L}*o͛/xm[+&OΝͮ})I ǏV:`O2Ѡ>IR yUѾл[DE>u^O9c[>E ߿ @FV}AVJW^b??V:\/8_yd%Gy@ @G@1ͽG8*;i f"Ff>QJ_56 ŷO7x#dI!%ybl-^δnF 2 0~!8dSF5kyHFF|!)j7Lۨnh 6/V6}k- ŬVG[{ߘH\}\H$r&[>ȢP6dL?ȮJR|A:6`RSO=ɃDE{,c&lx̕12y뭷/ۇ۷o;S}]7hРP>W^t$ ӰaeHWJL\sM8pQd֬Y3ʑ|'di)o #DFJVs֜ku  @ P\UhOJLVJ]3fLܠLr(]  YRO0!vZh?p,%~[^"2kI'e ^x!7t*iӖ0OwSs?(]عɓqX^&;7pCPbK5%2 jݷQSI)h37yJ\wuVUW]TٖxDܙ /0x^:~άlL/%ڹ\裏6N뮻B֮=Vrk^!* Q?lFx?$o.@IDATPySTr#\'˘; &$_iG\ kXy6mem?pz/Y$”a~C$)ǎ>(@-ȈeK<so=;#c{ne,*ԇB"o-p!◼?H *N@BH@q6! l%K.O gRHΛ kz@ @Z̯*LM7V@W).AZ+7گZX'^.屔Nr,kny^0%eb\")N{ ŵu:w"駟!-O"Ee^A)+~V+ԩSS*f}L_ytZv}N~, szeџ]vigmebݷ|m=-b '%sԘFJt"ݍ7M C"*{S:ؔvڗB;Q|V y0_)B@"Qsfmg;V=RjERK(Zy.EVk井aVj"#y^3! 5Z_Ɍ3"V嗠hV}yIT`ʽyW2zjoSO3d_ĝw=2Vx烂[7E]'~ؘHXHhhG23+Ȑ7ߌ=x݋/B155w]<@8-chVEe]AmZzݗ >឵MyH43{w6l Y]}ƚюػw嵐]|W3FDzoo~8< 32z萵.DO0`HL@KVpAl J # @ tk}wrF}Q_1eVh5l e;]w]Qdm<U΢gRK%e)Ԅ=)(8,\ugvJ>(xdMϦTe^ʟ~)Ҋ&M*QRH1jsMF6eW]û]~M*V%N9k/hZ٫(xCer}ՑA6ar6FQ؋LRȅV+ OUR~B2X;/O opb Uy;utF2>"i忌׵kЖ +$2FjyC^$6yEo;~w'SLsfҤo2hb49@ q!ĹB5YyXvm5wq?s?~8p`8'O _pA Rʰ=z8y3؄ B{mY94ijE8dJ :& 20PHs`0 jW Y"5KÌ@Xٟ%,W7S Y^ @ @ @ `PSD!@ @ @ @OqB(.Z0J JA<<螲+ @ @7EVZ)&P^^~G׼ys׸1dx 7}tװaCw&+O>Ľꫡ:BF PXx|@paȑNݻ.]E3h 25sL7`p]ڀ @ @3gg}wW_}ʁ|n޼y)Ę1c++3|w_|}nUWuno+ngرꫯv͚5s=\K pezʍ5m gߤ'+2?p7ppt1DŽ<ӕ׹+m6),dw=:,ɉL{뭷½7nܸX2dwG&M={r-q(n6˝}qyi~nnʔ)i;  ;7 lY [db;R>}dq%B @ ~'wy餬}wevmr-ݧ~Əҽ O'Rhp. yxWᇔпKR˳Ν&NND 2ƌi뤌WR;ͫVK/UL{nN qw}ftߜuYN Ce׏+́`Ȉsn#߲i@ahXݢW dB@a d"oR@e @ @$ FmܵY5O؉IHoj_Jо}G9Soq{g o-Zp=z({TO_)MMӦM ^6TiӦV%n+ rHx!dp˜<{ =tY=2Ψ$BF Cku2Q| JԠAҥK,m@B@~)ȥ~G TXAo @ @JQFnvp f]ǎ ~ԩaĈAWq7$w!qxbY (LGnƟIRk˖-c]wݔdyUX-I&rS~OdI{^N;}̝v)*Z.V,^ɀ@"oP I͚5+U[D$ h~޽{@CҙKF@ XٟW4@ @<L0!K.$cdvpZ\ 6tG}t@AmH9}W*CFj?pln?ႼH*'#D;#Xhu%ݨD$ٳC&lx^zUO5D2dHz%]w]sd@K;zy7y;d]t .jYY@ @@a+z-glH&Z/O*VѣG (2+ᩱ W_}ɰD+峑o1x̐ъX$?Orgu+rbim۶.wS s=\!o Y^x0yȆ<\H]eٲeqUuOvm!k9#AӧW:O P0`(y@ ȑ#b]ڀ!C !y2Rk5{ @ @(5sLds묳z뭝)>m[lE}w~GҪUp~ƌV%loV'Eo|A׸q.`wv;wv͚5sgq駟.P+Tvx`PN^Ys5+&#d{{2 {U!Ns%S.zh(_8ytO<Bln z_{Jɀ 9o(A恡Ԑyڰ# @ @ k^{?hKի{饗bCw$@uƎkGy*m63f;餓\֭?vmhڴi09C)%7p;Cb Rbذaw1!O^:uw>ۃ\kѢhs#Fl.fPZi bn)om9sCELS"<@O<1pA,iG @ @@1ЪD7o۷{CL)S"  ygRtN7x[U(⪑LUv`hrᇻq* C_wNGuTXu= *"#.]vmCa,%b֬Yw_|5jT}믱a\v _z)W_vڹNgdpAlVe%~m2k-[>qD~ E΃5O m @ @@aԩSbܛ8a̙wGydؠw_P \ǎCh/Px %0<N 5GAyxR^o[89VD1E ll+/vPF y裏Vu=o bT]ѳo:w ļ,^8U!"#"0 D@#G-¶>q&zU5{zغqе@½0  @ #`rz5׬rt/=ztp%1DŽ`+:Tep\} #!M6٤R:gSN9ŭ(W^IL#HakUޠ͠QF6riϔ?C莭7C/sI3lYa*aRko 8`D+/27y䐵n٩gqz80yy衇|j<}«?,A}_馛6ӦMcyc|{q׮]cy;3V^,LU9><0C 7)f3]]n+ӵUh;Ē9H v @ @@8qCO˰?oʘ3gNX$7RrnfN"ƍkW_헌9awa!]ٳ .viVm 4(W^!9]VM7T"1cBJѯ&MՕFmۯ%Ks9'*tD&7nۨAE!LmX9,Qyy睝麇t_{R,z7xgϞ/l3 ȢEb kR29ݹNN;픬X,O4G]v% P0`((*2^Z]ޥKo+DzU @ @r" CV[ފ+=;CXqԶZ=^a EmjջR|۲6h 17UVƎ31)AǗ^ze&M|iƏ;'7|;Cby3fa@Oza 3s@=՗Rk 8Ç˄B`#HA^¨QEyȣB="믿>$c!2\bȐ!<$֑ɮ:COŸ#z! ~O: aAAme?F%@ @ @ qN)SQ ͟??|M&mF+? M6nw5JZ6]f+9f͚1)ovw-s:P}9DzJ)e#Sy8쳝ȫH˖-acSqFᮺ*7lذc=6ѳ/ڵK[~;/ ҉-!+ Pd0`( S w<5` @ @(.Z]R~o!+ǹ' CP*uYy]NB*2>.x`H3[Q+ŔVKrY؎VJK{2 P<3<@ @ @ D`][Q}3:P]חWLV&[͞I}@.'NaB#`Wyh_cXS?4 @ @ @(i0J1$)q /VK" Eo&(S(r}> ~޿SW!j. @ @ @4LI $`HR[ r&+WWed{W C}@ @ @@!_$ڑbKlP~Rl*D3 ]EC=zeptv @ @ @' rw)d d2T@#P${N޽{x>d`}Nͷl!@ @ @ C,U"ŪLÂ<*DW 'cRD6Bs1%3礔XJgÓefef9ʗ.%F@ @ @r'C;)h:lxBJ혛u^ @J@zP!=ܘ2eJ̳Hq.@ @ @ ].%D+kCCDYD[;:gY==D߅2LqZؠD"#O\2 @ @ @!Ō~ySRIaWrs @ {<֘ } @ @ @(L0[Yt̓B_U;V- ={ l(D5{m/цƭ@ @ @  Ik@-@-®Ǘ 4dн{RH@ @ @ _ A=@d)@Jf${J1k |-@ @ @   @ @ @ 9BHXju[X/y' wy+bsOq"W*v6l!@ @ @@M >!)|Gݻw.FJv!BYY2eJ8VblDv9w@ @ @!$k-VVJ])pY^SS'ZJͽblRPH!@ @x㍯=Lq;5% <'?j]' @G cIK @ @$p'>r*pw6(+ݸqb6l5k6xcw!]Υڹ馛O?t-ZdURu1oB׳csܲGf-p_{Yn!UWtn֍mӦzŵ+TOv׿Ѯ⊮unvrz[uUs@r!C.Ԩ@($!J!OJ%HnB @D`ҤIު,enĉc=zkGFdؐJ>쳤m*_JϞ:kwI[ƆG{b2^ĉ_C4o|E7vsn#:mY+V>,\0}3j(7d+##čB =f5 @(02\9r+++sݻww  M@ @^xGG}4M>u-X{]*9s_ oO?=ݰa*ftݡC"nms'OV/Y:tǸ>(a!0qK,8ݏxףGPƹ?n^!cРҥ8YOfϞEvn7.n޼y Æ @ _/&ҥKXQ.e[i^g©VgP <$,yxLHdg @ %Юrey^p w {9jU*Z }NrO< *ձ5X)EEp&RJoT(%KVZ)|?O?7o,dѠA:_Sɏ?>C駟6mڸ6YRI_w'C{Mv۵x`7oٵmG8ypi[%^6qT!ͽXm6t_N:w97 jo 4{ΝC؉X~M:5iҤzý{C=)IT~PV *:Ըq΢E1ݛ_hsa_}3gaD&1  <0[ZjbV˵\OKXJ+uRq @ @Kॗ^rgqFc=b;{R.^8z N0;mb{EPƾI'sA_~r2Kd꫻v{챮W^A}y9)3_f>zNNV+NKCrJאuuo' LRRӘא)SĆw}Cw=#\~ޓX9Qhc9Ƈ8"xwr-V4l`"C=a#DE![to#t} !d>6|s=> @#P zQYU?ViADԕ]8$ʕBd3ɫD9Y@ @ @ϝy?_CUW]խ TMYwaw}z)waŕac8 *4jԨPVkbH9-%^6`(~O?yWVZ ;:n=$)"kwÙn~Tٳvӧwn*EK3[$2hNw\!/ 2T/X x /f馛<ɣѣG[^pjW"՝;w{WCHMn^{yo!Ý p {qfmOw/ۃ &@O3E[RW) Aob4n\׷_ny_wuP0b@ @Ȗr_|qVMCe]y Q zjP8|qFp7kr)Zy]u)W3Px/HhӒ:ʇlg7d'f—>lUZhw/p{&ҸqC׿'kʀA9,TAF_̙2&'[mUpg!=[dT; 80xAIg}~8D Bmy?xU2o $,דּ vv[<>>h잴znD#<*0`0*l!@%oR߶i gly`Cmx`0̶]FꃼO%6~nqm-kQsu@ @ȕeJrT Rr_tRJ&Uh%{kZJٳgP k;RTxw !Xm)ތB!K!AW_mU>Bz-*W!w_>{ X3e헷9g/)$;0ԼydE*OKF?+Ă<kG~`cCyQ{k&xAywŌYtK|={ Uܸq¾ !mnڝ{݇}q :"K !J@Vˀ)^mz@0 H($uя:@%!@ @ȑ_ \U+~)瞰<_6mڸ#8i%V}[o [yG"Yg` O_g>(2:thVH6$ePB2hg̘Cg:~)Ąy;6a[ovuWcXu닌ZkXSD!'2-[b/Τ8e @ȁ 9@  Pl ,c>}jqA##h,_Q]ls<ی_@ @ HU'xu1annvr R*oVgtB'xb0`+pcw HӦMEj@"s~_2vJ̀u땫*w~ذ4^PUW]![B2j*FԸo HLٟǾ©y慭e2yx'M#%JC=4x&·~db1̙t<=s @5KK@=&P! }}w]S@ @ ?y` ]{n≠a6?s2._݂r/%ԩS݃>f͚8ҥKY ;N9w '\hbw ҥ\d3I1 @5K l_i@A0 Z1 p.][nVN!._b P I\8NMճ<څ @ P~G>Ky3ΨtSO=5ԻKme?>W5X#w)lzlJZQμ/daCAރEf2gZkHډ$R!%9]rY⥗^r[M0!3xPW_}R~Re#@ 4= }Ѱڞ>}z؏ml!@Cx`C\( Rx+dݻw R h_ePVVJd^b[sllz!v(@ @E@/"(^A?C9ĝvi1 =zp;wNoB}Uwᇻ.ȍ7]zs 1{?1~U馭|rӦWM[;ҥkٲiڲ:9yp;^ehn 2$dx%qDr'Eݐ=%+ߎV[VFhRT+s.\gF 9wS~9w_F=}yi!T/+w6y5G|3h~[V ]6Z4l/uW.]D$_/\xƮi󦮁7\Q^?>kɾ'{}⾝[|bvF+.OQE,);@  ;7 EC@{)?aY#Zn+5e+!];*M2%˼0n]W5{u)@ @K믿vcǎ tp'~ =O[ozcr{{tQѣ? %v٥]24Q>-м{@IDATcqnG7L_q˕VNetlut!O2Pci56]wy/Tݙxb"# iy/ @V^ʮ ooD@3r^b՝iVa`:ˍ,^on}"=sR=2h1n'}Rkklʩ9D @ `"j y '/cd qbd vuݯ7C @ Do rmN.m\N kv,FPaΜ9n̘1"waCӦ!'||C( uXPXeUfWݙgˍ>|׼4ekAh_Eo-y]݆t*]t ߋɾlcGJyXq$?c-*=*`DǶUy@ E0ItN +׫;^[~ɰBҧOͪ M'j7ꩮ`5 @ @XguBh\F~}9|<hwYgs0 ۧn(K.uڵsZVU=v wЩݖڗ2YIFs:>쓼ZnZ~v|BdC0 jr='"le}~H *{@ʬ.p@uK@2tGt"O VO '[ePYwucU< @ @KӧOySqB&"#ykF䝡SNhU>@IvB Tc/ww^My'@ tzJȆR}BXX3@@5 `PMT y<0O 'NLL/ڑM]'jNeR3g+5qF)@ @ r-!kUVs?V|>͏lcs]TD䱡OzEA#[S*>! 0`Hl@ )Rz;P9yPB:CSB.bS]헚fdcQj< @ @X ;?. 4hʅրIOmI U/[( *?gZ$#mucma  P`PfB0Ue E|h\ XT*| =CҧO@EFw.YXE]C @@Xmܒ%K@"e C%-[4]2ey@d4"%&~g$9  Pr0`()e@@"Jy.ůT%ɴ~UsnH/ 2 Y2ꦷjgNj58@ @HCܟ&|\9I" X6 Y@Vd$HO k TlX*T #CM@(2NuA},C\NjA @@,EkB0PPdz|'@!<(%|:>imd {CC@(H0)RY^% @ @ȑ|=%a[H!GXMWH+6JRF:V@QE|4_R&&_ol{͋nOwUR[UFU^4ߎu^IσG ȧd[٘.R4gWn9iIiE @N `P8@\/Y(G=E@ @ @y$o f$>!C@|T[#[4oyѭf`URbmN f  R_V$+H[䏀泊>OOf# P+0`\@% cfBYY2eJQj3!@ @`H6ӧEE2C"o}rՊ$Cd UH% C 3<'yq 5!1dش'TM@Qwʰa֯H 4  yIc H aȑt>}<#y+DO @ H(#[-OȊrMf C8d 4B# 3ɥoz_d}6X~4IrvE2&z_SGd`xk0@!wvԄ "& TȐ{K.D!@ @ H(cܰ`5O-}Ҿ2Nо 8T,' Ey Y3 (2`OJ?$C+__ 4/+ҋ~k"aEO dD0QK A )av.e>HB  @ @,Wg>PCfTߤٮ^ #<@ uE Q!VV Ϋط\"3A;$ÆM}R( 4t@2^@5@@ 2@YY2e 5&!@ @@ e>iZo~[ʲɔF(0A@ ҍD^ ̓yoY+-DjJ*ʐOU+L@wq@uWZ]@ 22^z@(]|Iځ @ T&O^]Or$((8 % % ^D¾ޡ$j0ZȀq>m>ɨw@3fT FݻwF +u鉁7Y  @ dM@(xϧw|ħb7X a=o[BԣBS@ R+)B2gkyla<|]-67"=2hȧn>O ?w2@L2C@ DWKݥK|_Jyb}$CI>}j|yA!,oq\ @@Њ+$]\CBl]$c)W~_J4@]J"c0o fϛS17h WQ~O[O2j@D'aA@<}SHk|:>gc! @;BtN`f}6'@>viGO=(0L2@EF< D=#(kBty_9µ=\=@ @EB@ʮO|*iOPȢRl>XAF {@Bl@c?ALIj SQh~)Izݧ|@EL"<@ XA2*(++sSL&y\ @(ZO/MvY ZuO2Vh @@VUd_LIF $o  P0`(I+R^Z-_Wc亵GV2^zH+}mTo4طy#G}{ @N"E$H "QEp&Tt~ )f|wg<*z`!&DDI,9ǯ;;fwvyw鞞} P(Eh}J ]xuۗí++(HU:@% aϴǮH4(StO/ez;m1@ńbr&R p*Йf:t 7P| HPs꧋dt[O% @ DP.lS@,(Âҝc :$ S ni eh( Ou X  0 {H%.0`/@$AϡD ZƲxyxncQ  @@)%0^n+Fiڇ%Zhe`dV0@Őg2}#1dI׭+}AHQR-@@eX|l&a @ DzrYPAcBXWj1@($HPM~j%KM71$6춞l"#ӏK;  b0 ;J+!Aδ/Â!ӂ' @"ڵFJD +Bk[ϥ}oΝ;;V߹s)_e˖O?4h /~{Yfisiͳ{m.SRl^Çlla7aN:IԵ۷={+n'=S_wmʕ+(y5/%Kl>`s)d9ɮ]sT\9˾T- ]gٲeM2zwșgij׮]x'͙$\;:u>;D.q4 [zc !7݀ G@Y=Aӕddd(ڟD B,*A @(z֓=ʵ=DZ;Yh!kUZR%ӤIٳg(`ȑo4hP ,0Cv ~G}d~ϊӮ];s1Ǹ׏=<-| h~(6li֬+W|I~gUf<@>4lr9w9ȹƎ]dxb7nYh+[޽akc+FsW+[n5/yfѢEKt9묳]wݕ~^zn-e7Or:ud /b\~ >kz!>o5frmpVr|% ]=?Oϔs9N!ѣ4>W_6Ż{n=֏͛7_|ѝwڴifÆ F=CL֭;]ʂAI?>̝;oSN>dӧOӶm!qׯ_0bĈ,Ϛ#:usJ׿tW˲ܴi93^x=#Y*%m\B7eTzlmx`" *=aɰ&6 @ $?/7el*(6v/_܌3Vj< +{a{s_cUW]%P@XA^CuD Ԟ8q )xf̘p 梋.2 ~x#O?4ȗHCb /@.!:|T}e^+h~4`Ǜkyiݑ?oȶLb?ϑ]jՊ\w0sE?[shI+dyW!1F$dNH,L, |[Hz-CϚ(aV4e'`hE ';W&TeF|Yg].}<}YҺtFR`@S ?A]e) @RN=o^l ,e}Gz0^xQPAAT(T XS4eF\+F;+H2)*lʕu %(og}[j xD oDFxkygt;^m." ]ͼywիh3L\ x}_E8HөS=tcygVԾސXEϨ2vݒ(̶O>dzփ,2 4s[zd4IJ5kD DϬ6t%ZPyؔE}>}Yn k| n efiP o6(0cyTKd]vY, 6 .r6/(t~Jx!a>Sc&~Wv ]G>xA إWzc5R:l^f6mMɮL:uoW :h *8)9վ7UX To7۷w5RwFk Amv9ELJ͚5#[ha:(ޥh}ԾK8#L͞ƎD+B<.~5ʗ/kG72ryf„ӧ~\s'v|;EŨ6rgk+T7~Nmk 6>ÕꫯvY4G,ѣ Hwn@^< T׳t4Ł7/} WDrM0vX7ޟOAn?n:_Hh @Q$ˣ3:еkW7\23κ:NlM}vsbʰ"ӔCSsp;}}T6=(7˖{vOҲeK#הGenL29/PEW,agϏ:YQ 1$M O8T;׏z7n-=υl).$ZW&m#ӭ?h0@A @ k7@ [/;UR1>[\ @ @( (v/h@SfAֻ[/m%)MI,`q~\kuA`]+h *h r}}\=S}CI}/n\Jxϱ쬳r"kZizDL ?Hx$rDxƕW^.^1|Hyp JAJ"!!LT@& 2-"TJn:\= @ @)C`@#ei[?zM`yg̛o^}IJaFsM}Zz}%L5e|5X7M41=vNKSr).{( *.DOЯ_7maNqMbKۺrM%=egrۥ@Y=d{oAm}&|Y#Dȼmd &ԶH}y2229$kغ1%e@Aro52 |֬YNСfg_H"Ǐwөm֬YdS4J"(ÂIS>$v"uEGY8VԷXfhLo4)+D2zot_.>S6$3z>ȔB e"oA" 9wӬ,?R} P0"lN@ @ $֗}ʾܺz!} (}y':A@u5[5?o#_x`ΜnɍV}(yBRwnG6?A5:Z:KO>;*l)]/9sfB[|ewNh1Klɷ͔)+hlcb׻ay0hc Ju%\▱~( *3 hĺٙR86W9AG;8פ處@wSJ(#A,뮻\ܹsMnh{ y4er(HIH$SDLjAAx $P0\>gw>?k*]O6CI*r!nڊ^zO1HؤL%WM1x`7EK"@~gDYisL2&2Mq!}XA&aޛbƋIltu̓>)MQMQ$^A &jγKIs]LT}; PH0GkԹj緽 %B^q  @ @Jq{V?{׬k`wu`K.u{eHT>@ T/ Y3t^ TAAĠ){>}\ѨQC2[SP)5XaM)WZW.ɡjh*DlͮZvuLre=d=vs/@|"SzŘ5l7avuΘuƫR JkLs;RGu _>  PT(*H~׮]#k>:NwF?sFNSĚAgW?GXݻwʉt*ٙEHW]rLj*KyYP$Jسg/vxMO _}(C$-[t"A!a?)s:D$,Y$xX{!H0o\ӈ?K~_K"D@tMMa"6m[z,7nG6?N t>~T4ݑf4{6}./V}b 8Kԛr zv緽lON0*eZZZS甒@s @ @ glbmIѩíe=v,HM2$B< *hzXGGQ{lQݚw]3+th?~NUW1EFTT)&R|p5_=z$yW\ю>M{`_T b,1(TOiuLD?xP:h`WeY$ RHqʃ hľB?c~J*sm *d^6$ C&QMlն+=(ve2dH0'@I U1bرcF0scnZOU%Fb`7/gvm:5jdիpƌn[ 5EYԩzBP{A&Kt]hm-H;1zT PɊCq/`MX맏C!@ @Rsy Hc -Ԉq~ ib^x/nǩ@r"LV-~=ަM\~p @+ܤI_ZiL#͛wj5k& [DޫٶmWB Q(/"gVT_?T\9nۑ[kE b+6 D@$~J߯)SH>'Q+сؓ'O6^E)| ve~O_ y;(AΝܹfxƕk9|YM"ef [ŊRȽ)Í7ljj 7qE~HUPK}h.n3'Nt".Zz鎲/~> +{47+~pFuH,$7$֕Q!jy:@ 0K_6ՈsF1 n"n{?$ ϯ{Tc  @ P L>+ׯrqpVhd͙^ָqceb6o W y}.`Aa@GO ֙9sQe~tzN֪UUYp}NU5xL|$ښ@a= &j'|;NVվ+/bTW[S/(xDLN<㑬 0(!QDveI߿Q_O?eH9M9x>:_7LS=i3$`fTigYteݺus}vQM =c1hР |A eYm['bwnD*S@ 0D ʸ_@k=!tHp; {es2 qcƌ1xnݹO?sg @ @ \IA[OB.z bN矻Aa䠬 aZ \}P4\Ok]J'/A+L h jLgyfdu@{瑝vG4Lw4pտ.[]\fM[`OoVh,߅ !C:(mksƍj;w tf?y֝6 m M[zv29V',8gJzwWȡy0ʈ៥pcʦ SPZYe<{y 7\a t>C7J &iƼ{n(ij}AsO{챙EBT%_2MW^yŝBS2eTgz4=E0s3gXYAA5uNgF3?uQreF мHD @_|EWM.'_ӧvgYI#֥^E!ӵ^b:N[?͎Zƍs=zH`̼/lӦ}I];QߍLĢ/'#@ ?]B˔5A/t:t @ @rG`Qo%XWH'xMp՝;w6JGS18zNp mh5^5?fWuԞv[v?R^{6t*Uသ;q{槟~rW5q]wW/Mk$O>,(m+gٴL瘑#g~%K6{3SYspŨW_u֡] o+\PjOy,n҇-`Ϫ!lI&.-Qxa^ҥ|Mˆ/oYk΍o3+J,7(D,Eٌ3NS)5DA(sjD9MI2(,,~[u۠3il__noFS ~4 hs>W_mL`/˽*觠7/_"C(ӂL۠A'袋\ z?UHG߾8ða?e+`K5'9mPyo6޽Nٙ\OC#YlxDžuիWxwԶ5_Gֽ>|!@%|}}Fz-w&(Qx)Z$?40?6n,FڷrJ-rmg,1K.qx?=@%tOYU^jC$Rf@ :$ϫϼ>>F˃lס,A:#Y0tTM0o;Rb߃~HL1wxگ(b>$pXCVeؐAA"4G}D.zy㒺L;2ܤv!NU圯S\~ٕO&u,R6|V}!I|3*v$0P/=w<ѡ_D\EPbG $>bwUyފ @ (w|QKVŋe*U+S+(ẚ^J4bN:*n[G(65Z^Rl۶]6P9̊9V[yS{lb |4N+ [lQʕ+Q dW6h eDX 4#jfvrSF e^1d2(AnL!7MGqMkeдeycU_4퍞 = \;4؆}, ڝ?pѪU6 l=ö@~@ Gx/(Bj2*(?|< @ @ `AGmyKd 䉘F+u{"VV-#Ӕ%*V,gG]FkmNh}Ѣ N VO<ѣ$"+kR\wynLAsyvidi(;8`C"&ynLM(J&7z)KѺ/C|,!@EA *A(W @r$HDֻǡN #M1l+b ˎ.Fl͛wة=m2$t68p(G}짞zj᜔D;nʸq"Sh*8?Oz˹KwXwd"\ @ `@p{5/D ڗQ^ @ /['OY(/ms  @ 8ζzgXe]gf$x =g?fj3X԰̝EvJ:JfƊI P_>u;X{v=y(H0k,ꗦ*޽=b}k.v|.R@ `#PULyЂ|(~ߎp{Y 6A^t,T| @&<PoZzmINf?Vʔ)^{孍< |Pa̙f.~gj֬6z|[*gwm\Cu@y !4?U6wOplC @ @ ֻ-7[9A ʗ/:9C&PBSn|${&Е~% @gw$B~u^nL^.[b(Mc "06q'[c=V6):'qڄ 8 & @ @ P(a Sl\ i]ʟ),5+Zl0;6gwh m棟~.[5+W6?u3},坛41kR>nx,'v]jQӦ [f)۾P>kx͉U=UaS?ԟtްM_L]8\8g]æ6W߰96=7z~qTz?Mԝs7?Fig{1e*~*EqO~{ASVi 4\5@ @  \eZO- 4l]m⧊gB J--nu[aͮm$+BދQ] 2{ѹ9m3fy]K֭!@IDAT[JUiL յ{"|<+iHݾg @P%k0;Xoc] XJТ(S+{c؂$Wĺ0@)F9U@ @ $6ϰY/a^ P)`%_}je`6s@%[X 9WEanQF 0荡[ @ @@ (%? Ȓyv)13fz1@H}m>Ӻ,HzQZU{XoX#!1NԂ@]̛7ϵѴiSSFrU @ @ %U/l 3hlQ]2ݟ]vuL @(osϱ zX; ubt~@șQ& BzzkgCMtrڿ&k @ @I#OL_lYm]A4gG%rueh&֫X @lMͷl9,(l[IvZ  `H /ʰ0i$F߾},$gggHҩi @ ~Xo]#k3z*gS2=M74H%x @ >WX_h]]ABշf=aX cͣ(h> iii.S  @ @(jWڥFݎ>ֺ[f ǠյMK QA%P9. J=*WXWE%VhauPx]+q J  %Nr%όqLH9i @  إ1|rT5o4mD r~AײKb+)f}i/K$To}fzWQ  JC \V"3#{@ @ P (tF+ 6ֿi'DKuBY%a` $j$lren`Z {(gXgUX8l#&a֏zkz'cJ &sş@03B߾}q @ @P`IA&e[j}b8l7#怗Dz궰uj2y5 ;tUE *(:%$XhivXj!`@i$4u @ @H.dδ%j)sVQ>z,zQ26xASԴ.NLSa!`(%$8auM &Qa-/&ZXAUc `0@ @ @H6tk9KܰzI2 5߼֋49I1eHL?. @ !D%@ @ @I&WLRp_Kܠ<-PHny"+$x~uqRֲuG@7ZA mK(hYҧq/%Rвuo0@3 yFǁ @ @L@Oݮ̷>Ϻ ^hpRMWeu'jBb1 $n{[&A^)ә‚ gؒ"l#r/P(A"P/PH&+X @@ `(P4@ @ @ 0h_y\^0n#lRFj 8x_JPѺyу}_j|?ecO@4-DFr i4 ʂ /%RK@& LB֛Z@L'A( 3g @ @ \ ] R"  +Vph*&xA"!E:xCLQꕳҗ˕U"/Q/׶ֽ"RereZz&k)W/vfWRJ=Sڵkͼy̤Ikoe @ @9kf!\FouV[!T|u HmUl$Hh]BRz/TA@ ( /1c8!C 㴜 @ @A@LyAYb-ek|u rp/DҲzi;`0@@> `'@@~(낲/1@ @ @ Ff^i elX7 2}FlA^լKp@RZʨ4@ICa\ @ @ \  sq.w_\J9i] M@teֺD9.U{|?[A &E@@aQt6in!iӦF @ @DPWiy1au~]K/|}-Ml3R.с_/UC&  @Ci\# @ A+ 0 222ɓݶD  @ @P̦^nwI! ᥲFL*1|WfYxn[.1~]K՗23Phݮzm_嵔Uek~;-<2E<~{8E:Ɨv br ^>] @'h  8 鮬o߾Ё @ @(  @% @ @ @ @(R?'/4揗k @ @ @ 0B) yR˵A @ @ @@lcS `%|7m5uetж @ @ @ J]J,AB4wffC%ڹ0@ @ @ @t` ubFgpug`>  @ @ @@I!I @ @ @ P `(7C @ @ @J  %Nr @ @ @ bLC1yt @ @ @ PR `()w @ @ @ cq: @q]L4ԨQ4m-A @ @ T%!U  yLzzСIKKsK 0@ @ @ B"U  䃀 ! 2!@9 @ @ @tĜ @ @ @  `ȉ!@ @ @ @H: IG @ @ @ @ȉ @ @ @ @tĜ @ @ @ ϩBiݿvZ3oBn݌?HC @ (޼Y7n9㲔K_d)<欳2Ç7*U j*sg:+V?`^x?Kd||Iyq @JH@ m!--e[ @ @@2 1"fbpȑW_M !,^8 @@* C*tʴ0i$wH:nN@ @ ` l۶e3C ⦄?XpBsN`s5D}Per˂ck`AxhaԨQcuSY,YL2<裑L6m2sM}i&lٲ*o֬Zj>L0}^zn ۻh֬YfՑcO?E7onT޺uvf-Z>: O1[n]6mڸ~|FǷmuQv8|w:t)+ @I%!xi @ @(4%† b^?#>~_( (W^in&z衑VfΜAcqhذ={>TP ڵk9N s%D/Dryꩧ"LJW4 F׮]MN?7't%u]%Lj8S|8py#h{QEIаtRӯ_?'`WE .@ $L!$4|M!?:t`jԨU}, : @ @@~ KQM|Qۚ4'Ab7/r!}ڎQV]zФIsw:!#w٩)1ʦM4viFd7xcYx +bt`V-r ,u$Jt\ǎ~,{UW5ka @ I0$ ,ͦR䥥M?i]eڧ:c0@ @ ʕ+ȑ##MwqwQAS+d*U2Ġ8?me 8s:TaSv_ѴO?ޮ ꖚ;Wꫯv˱cǚ{.SNn_(2 2e?6lX{V ޺uؼy!hj?_|BXm*#M}C , ˓RR@ @ PJ[QWzEm oLx]AA)S$t:thuֵ^k$8Xp+х@S4l۶ f[ի+_n[1}t{n#C=dϟo?5kK/4kWwKyW? ~oM7|W,AY,駟_$t?s> @H> g @ @ RF_3cZϕ!'+SL*{; @MC@*mAӡC'raҤI?R @ @@jعs:th:3Ϙ;vxݻ̈́ 󟨺mڴڎ_>t zQ)CpJ:j2)x  40O=3g_DASv~pJkr˷zMILA_xp@ Q$3(K 0yd-Q@ @ D Kif1vN>4(诺aӾz`z=znf>~:u2-[4 KLn͔-o,2e^еMk<FSKt(sĴiӂ̑Giʕ+ʚ5ksut 7` m @ @@زeKdpD޽jRo7b׭[NdӧO…W]u׿%Q&exWMxJս Ç 1¦ zj8lʞЭ[H駟n}Yao\Gm.]j$g:#Y$TZ"9 s%uن @(8d`(8<f7oBZ-^=c @ @EKRJfϞ= uBS/īKY+>yvb ѶR@ P< `(^3k׮uJq2$U˂]&McLJ멭}jҕ~h÷2; 7zM4H @JǛ~!rهz9"۬@ @  `(!--(C &bx<>cu|;*S25dG@XaxdG} @J.{.nFSL26 @ _eCY ( (F3'ju1c\}!>Q(]|.=R?W @ @ Lg5;v^6 @MCR!:@ @ (_ :Cc!@` xd(/e@{F kqn2ˋi!)+b4A @ @ @ B%PqsdpAYOKKsS7HȀA @ @0 ̝[g\;3w>{l%}}S}oI)$RЫ<|: p @ @ @ 7.@3Poe(i @ @ @@ `HYڅ@twSjH0i$)5jԨQ @ @ @` lbO@b LFF !@ @ @ @  `H;B > -teYPg`P]  @ @ @@` T#y qBzz*Bh!Mq @ @ @ "!HsJ@ $)2 ꋦ @ @ @ P0Dq[ Lv E):@ @ @ @ C \BlҤ;q^21(sSdqi $g @ @ @ MC4 ayAMЭ[7'dH1*~Hj @ @ @ P `(nw e:X@ ~zcjOp, Z{FJݔZW@o @ @Hev1e˖1eʤr/̸q̂kvXӵkG?ŬZɜtRKSN|v6̌S.3Ӧ-7m7[/昕+7Ola֭Z@&~Zf~yi۶9ae@OC3Ő Fe:X@ 3ydedUw1@ @H5>;8C_4jNi%_3fL1֭1Z3ի׌Fj>}yĉ…k]'7a:ҥ9FTn-hdӽ{HgXiHY[wA4 V3#F\W+O?%Wa 楗& ߴiU9:3xKǿ67t| yf}WÆ ,*$]Tg͓O3s&8hذGgo9gKb0櫯Ft2w=$fD Gi?[~9 V P ߂K-@AԖA&Mr jTΧ  O  @ jrcbvfĈg\@/{Y>_w\rY%:xkc\ oo :uTi,`Nq㶨s(Wޮ]ӧ6_lٲ(ڱcWTTسǘ7ޘl/hJ u7ڼŦcdžJ_J|ӯwͺv,rc#忻ʖ_6 \hpRMC\|~ H< A L m7u G=@ @ 'йq'0v? #BsF}oAi&0nx8Ҏo)c̢ER?8/Rsϝ͚ie( ~\yBS;Br){M}vd(WlwyiҤR,_L<믫p[lC YNgbNA@1'@_#5Ġ쳼>UvV3g*W-kgc_Qjf۶][I]#5YŹ 48]V3+0h9s~s}TƉ-tվ-e~6mY`U17طܵԿge웮W;EMs {3̟-na<9]=R'e@BfJv}oOB/ʣR9SpΜ]ɾ'{ߪUݨk+6ԿŋGZB]5kOܴ(,;wf̕.Gԋ]ZV.c?ۺu.;w6٣v 4hl?[%KNPjJ ^sI$ 1@0P 'aAt[ҿβ@ @J13N3Cvz +r`eiP?_Vp;歷^4wqYxr-5W]uW^y{p}}- :O 9R|KQU`S?k_)^zwv@nI'6_|akOSOmiݺS~m\|qw Σ<~ ;\e"+W*CxsYxe;v^o>>] Op DU\s}ÕU`yޮk5'.M.;L3?Skק#Ǐ&\|k#2vadVWs-`{p  F4qԴ]4]Ƌ*/>xfR#~A!HT{=xO{W_m$.?X梧9Qu323#2>{vԾL.Oݶ+<۾/ ^yG{nߌ+2xQD7TlXGD7r]'uvm.yȐWZo M'mtM0ֽÏ<,޽ۜp>Z;aUNi@kT.'3H 2Z3 $ۂ՟}}]Ozzݷop5!@ @Fz gxE|=-_{m3\;.X, Æ=b ~߃/ny}(F60;ډ"#W;Bۯi.;щZjg.tzWl 榛-\}cO)7R#iڼpw=]jG ^5jԜqF?'1Y'60ywNs~pI"p!]ˣG2;})_lg9r+1uSNhGfoac;@ukGqW5G϶>ˉ!j֬ltڵ\p<,w3j\ۜDsJ()"7SkHn(!CkJ5Dy5e5dPL\R$4Q>u9{az޽VyO] bmF^Q>aW>oߍ^nu_k7h3>SwJ)Swů2a^ɭ=[k׹ɓ.~:ep`]D ;$&_ܿ|. ?&L8^z2к N h|xoGV[E2p9ԧëAޓ ɀOzЗzA^M'#|f%2ĀfÎ^^|fQ` 2(xn|ÑP&1 2Rw{,!C"Q<l2f(lIҭl<%郺 @ @O@F & 'jmmذ!P$v/z뭏^6ٳ;}7OF6:@tK,̃ׯ7=NA~!֦Nǁo=B#ۍ=ƇPU"NK*cn}y3 3dxQ^X씗D:ucqu <|=׾ګec oСCBn#Qo<,pgK7ة=>c eWNs {g曳}tҮ Ø1SBFuu9|.=,rWY;쯹"e ?qK@Q[`ӫNA.wqNի׺.׿?k!}h~b]+DsDw.0(9H8c< A}@6_,/sݡ?87=o\ˀ᪫F&}'s?o;3@J2\p2PȅdAr);NQI1 !@ @J@`Ѣǃ ƺ'0LCEkP|pI94|=nժUe[2jqP]w;U|Nݫ\|hA>(PgG4|Bu!"*;u?/HdJu2S?/o%s1nNwuO+K/N>]/\"Hzl3^P@_(: |/zc3^ >u=rǡC2^PV[s]W8 /\)S~q?F\ֿj? &˼=*È[U$[nYD20 1u LMA-XZv!9VNAH6m2k" 3^NFoM`ƠnYZ0e,EFk Bk{gNeϿ:fmmukB  t !]R @ @ E_~9ݝ|r]2 [@DEyd9>saYII&Dͯv%:Uտ2ИV->N;?Pa0Aw'5k \KU 7!i d;㍗4VZՉ}e^}w5SN9a)2# nٲߓ| NZ8|o})CA۶}1WvM}*|I[n蛘8/y<|\n]z ;?0=6%i3|Qㅤ D23o̎SO EKNY؏=C!D͠_>#٦aL!Qt[*/ [n' dZcy'dOڃ' }aTeL<(ȋByPƘ @ @}N!("bԨ s|O; "ViL_/vorvfb] r˖ȤI&,ںkC|9J^2'adM?5N"E;ᆳWH6&K{U5gW,#w2Hڵcy:⋅Sv˗ww;gXN8BKzѹs R\_x"Fڑݻ~zbJu>p~Z*BHDlOم WqN:D/+׸[ny+!=t曳OO/TL1.a/4Zz9,T7EZ5bYZ op=_3cvbF_$jժ?<_dYk5j_vj|[غXy_9gX}pw{晇7#P8N+vKlnܣGbᣏ޶նI~SHzڙ? ^)y12XJ=ڒ $w)MKtkVStRi+_x`m 'zn( HyoN`2ǚlNe^N6-|~ 5׃tou3GVO*bUAMr%BlDa[ǎ z>W%6%66Q?2Xiam2 [IW I]op(ԇ>D.#ߤ~=lX&JN() P0`(ut ( E>%< SDcԩVY/y^!@ @ Jמ ܳ/ ĭ=0 @@`%4Sd #27јARWx1@ @ <=f+z`b"ڔ.k;@  Pʖ%j$F*B@|͛ Ae`xt@ @ P yժ?8VVXŜי!CŜ @4 &H__%Q[;\b$E% @ @ wPEcaU S  d0`1 280 f|xLڋ K@ @ @ @@^!r;c us'MMh; Qzq @ @ @ ) "76!FK+R:eF#8qK֞yn11O dM @ @ @ r1+Vg %3HU|@@&}2!GY@ @ @2%C,o;V@A 5p Jgz% T^ϸ @ @ @80`({e֭)3tM7\vĚBB7 @ @@ [(@6l9!q= ~FEA@ `P`thyV7idhsɚ(Hu>vTEȇ@!l} @ @ @@ͣ $2H0`?rR'9rM<}lM @ @ @@Z0``3<+(D 2^8q%pٮ|+ӱcGAԖk:ǃG)A@ @ @ @ ) "xlg &Ms9\#+\ݺuKm Wv5j~\ժUUVƖ~wWvl5UV1cƸ 6vu1nK/ :5nܸ g͟?ߍ7m6G%K6/駟cg}܎;XqR!!!@ @ dgQ;Vٲ,t׻^{-6)7tS׮];wqǹ.]2=;w޽o-2&bO.]:t4hKdCr1Xչskzk7nPf ]z[6o9S7eB7mڢB q 1=)k 4(ehW^y%XǗ???S裏_f{/y䑮J*&M䮸u=GFY$~s{裏܇~_7p?|A9h\~g?lI|iŋ￟KynѢEN>͵~%nrÆ ;Ɋy޲eG:0"fɰ!,څ. +'f2Ȑa>?C=S;찘E֭ƭ[oE5kU b4~xפIE߾[cԩSCYuvZo$ :K$XK5^ё ~5sn@r?:왶!@ @ TB׿܌3~nV[mn E袋.JhW~aw5k. %^I/HI溇̵n]]y待repԩ5k.p˗{6_Z}cMO4UvA CImxGؗdi@ͭ:Xo!SQ޽ݻ@ >ו<3 :f=_AϏm͚5?{ュq]wuR%e=ddXhl۷+׿Yg]@gc=oIBFظn˖ƤS,w}EE,:0a!%4dJ^Ee"ygO?ϣ_իWwsO2dE@V !8i  @ @@rmHn6]/y| k]v)RNS}h'Қ6mj֬%EjOJAgmub+Ą]vY׻EG)[[(޻LsBEJV)Nwyg?+:"1= W^.pO뮃Epq| {Qh׮Q0罃]յhQ,RCG&]{y_ۤ׺?/6Ff&v+d*HN9唴ߞ6 , ^n=䓾ԩSYzδm6nV yw^2zH} yNh޼y8 RX yAP(MTr?*Ȩ8g%߮L&rۯR0jbӵkWTI55V?׉ [}~M>{#|V3ZiCz&kzpBs蝩5) k/L 2+I;S/C" A$ʡZzgj*G^u&6=~З3`EkUz'j.f|*OSQGyė-WO?ݧGUN er\C +0R o*!_QY!@>wz*}@ P1?z"6JX+(063P\#}øq6 X[o՗ O`,=^(^`o'P,)9hbC#P$o$@ӕxsb@6%  ? UL"PLTES E|.u`|{lra&ˆ 4 mPwx=& Q/֞A'9'cdEɃ@6 V@ @ @K@n]"Br4P&:y0xG}|yWw߽P뮻x- ֮@awShM4 3.0|pr^R] ^X8OhWvIo,F5]ڥ#G.m vk!R=zE}~^}|╈v]vk0Jp@K>oޯؗj[G~ӊ[/ K^qcϡta|WPh诿O?xy`ЮtwaێzqI4fykQ}EaQ86sK.ϣޗsqW… }:u*Ҷ<.'橠H gX~Iqk 2h/pСCb8y9ᇷI BY 8N {|#{ĭWzU׿@iI #"1Wq;!Rk+H%yf [="DS WΗ_~ه;gFcr/(䤓Nm~ލYgUH eK|~> I7E{ꕰ?uHN{Hc2v$zɰŌ.{cm*,b!]5j( N:|3b%DsлzNC?_~y,7I0$Zg1$  v4^{B! z•r6$OVJ#yȖ̚oUz)|%`޻رb7KXom qꩧ:}r-RM>H7Z;RJ)3sL#PZcfżZ''xb)㍧8iR Bd)i)!H)kϊ Bo]ڕޤI nQ0m4׬Yܧ~wݫ-w!l >!AxJgvf~&G`Jh]K_lY4+k3H^sPE!Cj_kMQIzW}Q= JLNd&焰hKѻ"Pž>LJWvmkNKD&*l)_"Dk nw,H߷uTk3?kXcR'?^~NtZj嫘1l!4i  @ @@d\ e /W/rܔUYwRnUuXђH"I.]zp J"C0HOXd]첋yۉ- &U[eFGۑ+WِUƚip"+9Ixcڹ;80p?_e"R,v6 33`5)mp+y0tL:lkOk[ReL ZQ.!@X'—żn:k6Z'k@~`F;%0`0nOpDoy ʆ(»m']Ѽ$f k e uq .##3 G{- 7e|50P~ 1HD"n)f|.gF"|Gr*'Cf @{A2 #  @ ,XtibN'ra ȳ82RfDJ%ˋBov{=&JÆ 4VʨjժJ y C+?_O_.-QL)؁ܶmfFvWJFT+Gv[Xk0)AvyY֧O5)9Ӑ2H$~7uذa.M7t<r-xů2snpU&V )pr͚5ھOSsR˰@jއ:t\!b Nڴi㯴?(gԧ ("0qIO:$?qDBavKܺ=OON:#-@;viJ:ӿdD(C+n $z?%B&b#L$2lR}2_O2)Fo5MC=\GCxN*;{*;@ G@W^y& f)N9|{駽x2~W)1ynFavIKxAyE[n=/Xxc\nVN! c +ꤰK)CqwWb%c)8{7fC['wg \qVs9Q{ů}oXy$V RKo`t5>JR$?2H}\K^ V^J^z鴈ԬYpUx \PN8A VsP1d*yL׼/GEmnI>BBڵ7dہ.ËLD,$E8\BH4S0Z%Çz^Sv¬YT$_KhG̙3]< bHwbmq_~ekO:l !c=G?g!ZG)|AB0\a^|E?ѺmZl3]g A^Jk7o" utuGF["&;wN8m03O? dNzh6Ȁ@I `PR/S2\IrI@rI?Cx3W쓶!@ UR D?bY2`@^ dd`* SxWij|d[TnKv-SR$SpC_6/;J)'ErRH!e"ճBph޽{vb!ePN f`uAZqJ$լi$,cO??=EmKN>O2W_}C#hbNy0ܔ22֗׀D6 $z5j(QX 3HG)@[M"I6.a}N!8׳v̘1=@ Aݺucih!S,M㏊ dB#Z1"y.< amӃBt\}xČ[G}]ƼY,' 98Cd2j}ݽG{O$6Th>VO4#7QpTLގd $1v.#yŐ%3=2%6dld($91їu Au!*bCk+wUsi9!o)GK@Y"s$JW2~Ԇ @ȝđGn+R̞=ﴔA[lQܦ2'ڽ+%vSehHTpA]ܣ#(ֽ5ה\y|`G}[Ço0(>Bc}Fo3l& =s_J=o!I / M4ɴe#=eCƣEƍs4U#eg) >?2$Qɓ'{t tqaCi GP*1X0BNLfrCy睗IUB #x`!@ @ =)вDxKc2HW8st^[E k9txD$\ h{˖-'sJR!uq0⪫r]vgGy$aW<S4i7?~̳A/(|R& <4p&cDl8THd @ qچ2#P~}\u@ @ @@Fn{sn`7m ~nE@aG#Vڑ"wt Zhes՞}Yڪ(i+V!!4p⎿4|ׁ^?D~3  R$ЪU+K1t@ @ @ 'BnĂ_8b ޽ r2WVڡ lP8QF&@r)ͯ)Sb" uRVYƷ0y|gʕnٲe3B @zܜ9sUV~H y}{ @ @ d@ժU>A`P\zԃrMChÅ\_#fȭTPb @ @ @  9K @ @ @ Ct(Q @ @ @ !xi @ @ @ t`%@ @ @ @ S0/C @ @ @  ҡD@ @ @ @rJq@ @ @ @!C:(S,[M:t@JҠL @ @ PY `PY|~F?:G @AOiP@ @ @W։3E@;^42o v^~}שS'9@%\C @ @ Hx+mǫFHZju e4 @ @ @*& *}QG=-D]=/XG@@i09%; @ @ @@>!jSӂMEJ!<.  P^ ,L4M6_+@ @ @@`Pq7! 6r  @ @  ҡDyA xZn PbzF˳&MrӦM@S<2$E @ @ @ ˠLwvyLnBH==z'OĞʗW&*ܙ+ @ @eG`ڵnʕn7x֭sԬY3i 6~խ[7i\gviw~z?5j]2\j3fm\ǎs6ܩ֭[8 g0 PTXeBvjnnTmnM6q;찃ҥK639i3k/w;C6馛[ne,cn/=-h ~ts)n3ԃ솖N\FչBys@d?4ɡx@ @@e!b wV[m͛Ǝ?ƍs={,TFn(_u .ى>#tևNFeQEoذa0eI&1$1qصȃBq_~nܹ.rg}{饗 <"<a* n1F@净?P~ # @ @|b+;e Ű g?P)ryŌF,Y~͚5'wժUsv9^}վ~+=$hC?*Ygdgv?7d/²hѢ]wA2(\pJy_Űe˖84l{P9lq_v>l@߼0ty+<}y^l+}cJJ2<}tnL۷L)6ηI? @ 7-X]~0 !iѢ+$%|7NF 0QFZvqGwuهP^ݺuu]+&#A8ۇ2>OGXSoC8{)`D'y# ]Nz+?x nO>9) [_B2dzre~wK!KFD4 P `Pgo;fer  ~n;ݒkHe@ @ SG{X"なU7z./ {?O1V5sG>+W &jݻw/X;6)?SKv?8rA~{W;Os-NeMLm۶ua/2Q,,ᄏ7^x!9 P l|SU3HT:u?x^Z P=?%L=d\H4Β?}@ @Ϙ1Í5{V,oB{v{'x;C:"sGv͛7Oj(DzԨQ+.EBiHvygKwiعSYE;CrG a6m{ŵ`W=S$fXz֏򬟲`\hoر~Hf`V @ @H~E,WR:J)3sL7nܸ"ԪU+.QeĿG 'xkܸqܼ()JA"#{z$lPfMZ!&$G>[=>=UZX#F^:wKyVt[s_Y CڴiSyˋ &OxZP3nftk׎[D@ `Tg(U6R,! P9$zBI)"GҤ> @ @ =<9rdc 絫?h絤4Hɝh<x`5`h֬[|cƌq o88,Ya֬Y.۷UW]CJ\|žz]w7`gʯm;Wo?ۨߒM7WG6L8X^/^XR}‡p Aܸ}C )\uZn×OEl!JDRٶST$6':}l:ͧ2@HL@3V^й/T8p;Ɲ- @ @(8B! l8y睝Q .2R'#|ۻ+9sx%Oׯ__DųQZlwq{׼1J$ŐyPS\HUVҚ4i;l'+VS63 F-2yW|4Q;[zu"C tmG +tJwc?tn wL P!X"{N @9Ɍ5*M` @ bDn߭tHO2)2uu^ziEdfmbskm6vp 3 (T r!;7`\yv={v.MA'$a׼]P&5Jw?5wH${キС;#㙹s2lhРAj>N6 )vD& P `c;6йĔR|5Ym>6kX/~X)v*l.!@ 5{{Al~[!YDVOX鴧2蠾bFǥk{chU:}<_4?z @ D ȃyRQ~oYĀa̙Vŵi&vD >gG!In rFTZ5Vtvƍs2XPɌ30fmc"*6 |)hӦM+Tez~=gʴڋ/@ @ #8},] /V8 / 8cF R$N:|/w'?]qˁyV&ӣ"'x{Gb>`]aѢE>l+Yv:t?W\F 7p{ᇝBc瞾… e]S)}<2#?8,bBKL6̇'t;wq~]>Iwveˎ{42 -L" 0`TwիW3݉)H)GtR]ѾHرcc@@罽}` {d-o-?ӣkqK,-]ZXhxՂEF6YM12xD @ @(9`Ĉ##=E/r_ZjO>{> z7Vw\rzȑf}d ѹsgM֎v!VG'̙o;ꨣ dK83ݿo׶m[{yojCaiܱz)^{9el馞vKaާOpJwnFZ  C"[oSYfyFW^ye!/N|e8s-$ydH%OLB4sҥ.rdp饗'|2dpw8uPc==)XQ2<0Xye8묳 CO?СaȐ!C,18ٳ_Æ ɕn̘1ޛ [lHEi2nB"o =;V[md,Dǚ5k\FTX}׺TB0ڀ!/w͛C +@Ky@pI>. Oe%>0CoT@ @% w ,n;r^!~ݺuޣAׯw )-R; 鎹?WqU^4i)wƌ;Ŏ;=1TI ȳ@dЬYe"ο_OKu?p⦛n59p^r T60gj`)Jp"|Q)pko!m=mdz"|0p"|.|(; @ @e]=fEq/p۷/^7n)DPe@@=7{!__K"N .Y;O<*ǫS|X/y\D#\ +  @ @b:t{G[nN+G~IV\r-/6 ߔ @ @ @ ڵkQF*t&LȤ/tXgyf't{w]g,@!k`!]VA @ @ @ Pn͚5~(UfAի'WC>䓾*Ujժ3@H(Cb+ItvGڕ@ @ @ @HexjO\ȇ 9AL6&}@ @ @ @ `PJ+<1;Qh6+l7lSk:J\qE[u6Y:f{|e^xX;%1To.A @ @@ `{ƕo֍=}SNq9rq]ĒO:5(9oJtYlSo6DqYy]gcgg똪_O%W"VO/oISqo啞^t%Nկ'͏'_׋\k1~t[OqU @ @ P:0`(Εۉ9qDv^ի3-'ň4%IxrE\_IQ -\l6D|,?<5:W^us]w/GI|Y7 /K:TK.O).H*G.6ݻ;}Jk=jE%:>&ҽ.Gi]%:D*:][|/:,bN{|HKw\lRʓ~/h,[\IicǎI_m]$oNJ^" @ @J ùb;.2%Fɧ[.^݊|S()t)T"+Ѻ|IYͷh)RQM\lqvuؼt񂉥=g,/v5xk:\9@ @ @0`wW:RB^ ]I&iӦUhO &B )yG}qq~7,P~'qI(1Kl%z|w'+ʺ^"  @ @@,@ 6'\G=\'),& 7(om|IeY7Csr։-M>N4x)ʲ^l}3ue=fʥʺ7 @**}WjwNc5Nqfٽ\קOo # @ {0`KZ@ wjG}eS`喊FeX7mWBt[.yo'7A@RfKݽ!LřQvF.ʶ^CV @ gbԨQnvezkUZm馮]vs]t%:;?(Hzz2*_2JXx;ЧܯF886skzk7nPf 5s^;͛oFM0|P;Bdf[V-״iS~}7+ @( C: _!:LjOJcrn6rp[R UD.h9׍)_&(Vq+/!w6%պQ_p1/qMҝolᢾj¥ekX;֋/+رcĢbVU|<=}%/%<_!@ Kr[m>}kР/*䢋.rs[z[p{ꩧm >]vei*tB*WzuצMBibҤƸ֭.NslqugɵoU^}O3{cJ|ᅯ]^ϸO>9٘[9dҶm[?yOg3k,aٮI&  @ '6-'he%of<ʅMH#VQ.XO <ՎGq2V$N.%N"C YAQb@ |3L0`E9 $IΒs|_k==3˲ٝ,O;? M$Z=7}.GکtlE\o%?lf_|>zv(}>K/  lu8d"(xAe hU+'@ |k7|35j~E*WQ/AcB--an4ZlB 6uT(P?LEcǎI&Y͚5mAڧ_n]v~W'&/uZ'zlӦUّ>AYak)c:5ޛ31\!j^;s]^7onsotr)SKgk?ST);{C[SKD˪U| r*ucRM]Ə1ݛ6lorO>:^(  lwɞÓzǥlj^Um>O^ ]wk_ø-u'zm. ?S%/ΥƐCjWםma\[p??.B?zMWB{+ t(H]W&p=vE}y 1|ﯨ]cAt=}_ok^.2}a8~yE@@`g裏OguV% TPhD4Ѫl˿/Kcǎ'{wk׮ eѢE51?O֫W/;l޼y6x! &7mdOCاA~_ΜyUtWۏagy63 W\a?+J@J`@@@@@`zdm0h 7O*B~ ΠW_}պw401 _%LG} 849Idx_W_x}O۾ n;c# [(6*k /|gZӾ7n*Uwz%֬Y6*V)ACzm矷/ܓ(8UV> CXD8 EXYfvg3͢ |z>`F߹>5_kntY}晓Rdqh6lVq!,UPD̕ &˞r!q?ϪOYڴic<]z֨Q#K2~M ܯA}(X"ZFa˗/OTʨO' rk'ÁM44PtUTxE@prB OzC@@@@B`~=ݭ ӗ_~0axI\M~sNÅY."ߝP=ztkMZ+24hxDu^>eГ*%K0C~p >+Ϙ1#T:qb~!C/@yJ]2נANs&- PB&;Um(wK|{T0l09 <# tYz()xAu鲉%EtZy:tС>‡~zv뭷}>o~'Uvi~>K  l20lWzE@@@@&  xmMOGvfֳ͚gOӓzg]>SU cSHPٳg2?dxX* ׫ڪ(!,1+ZfcJۙ6nu)ᬳ#SS__nNܩeEw)' GX!^K2賞8ql(SرcCUUV׭[k;v" @`(G"    $ Wy7qYպuk;MKRvppƻ}Un3cx Vp nێ@@@@@^`ɒ%ֹsgwx;5]wu~s=~[ )͵T@5voҶ-Q ,0x9."'#@N#3g.9"rԤMO #;2}_>>c_2-g_lYJ]PF-qe;ckWe(QQƍ_|M6V Qϩ@@@@@`Dv=lԩ~U2+VlG8s }Mm8ֲevv[s=ovچ 1zh;mҥI4kT,Kǎ]J,%KrȭAUjGu Jl#1c{キW?Ç:eQ}+guii/SrgO}rJ{}.]{C=  }`ؾ@@@@v!$.[h plx˶?M+AOׇ%02G sMLX%\x/[la\sh/ i -O:0͛M7}&Z'Z eҴʩ*](^< o1m׮U[>Fӵ)!CB#(H[nI{o,_|Yx衇}AmtDQ?>y䑉wH7O+V%J"5uuI@Z5VwG     E- zM&+ ^z7h 4K(;&ZIi=qF[ ݝ~71?awLVhuָ>[W_J̭9b:2m4Yկ_Zb䣏>?>UVP ! $5͒%K(Zҥ5@#>F@@@@vq%)\xY dɒ~R9ƿ4Rv͛琔<^} zhf͚UKi{ mڵM?e˖СC3ei_ 2@/^bZnq?;@@` ?F$tRͭ]Rڟx; ԫ#l!z0~5mٶdؙy ]!   0O>˥^jժm]@USO/`g #>1$lWVz9瞣C5,LzUa„ %VtI+!  ,ش| ^@^z%xkzyw~tMg{/5lu.=   ;?~8W^ye*V^sm wڶk-.]L_=Ϻv}[@BfYfȑ#}l  !@]s*%~5DlG/<Ѷ;v4@c/i![luvx;?W댏+z={@@@vt߰aȭ{ŷsny܆VrGߦd{=7VZƍm~^m@- N'mtÀ@_@\pիWg_Q(d(4ރ:hGW =X?^YKk .uKA   Ly.kQ^u,Xr2B*m}[gUT@vZvڏ#@^IzK7Q-zB_Dž'C;ևc2y nm:U4Y^ Džv> .d.vO%~oo<"S;A 'jHi     0ڟ?WoZ ? рM0f;D6      /@Î1BN|MǃR8^x`!a?d Z! 9(m۶IouEIM8>DoO2ih@@@@@@ Mv2 Lևk"[„zk?LW!S eD+m<**W΃     l0lEB:DžMzGN?j[ )@Bۅ]Ds;ghW%~}@      ; ;gˆ@4%4AǚI6%TQСCKGj2:2Mk_m Z?'~F-ԇ-\ou?/9 vka|      $@i1VJ@ l(XAQJhm&hIի}L&O=T,w8.wko3/7~G]$PT%3_ίt%Oyŗ׽tql#     9M +@ ־&Z2!9_.(8hb>] Mk>Sxl(H;1]挼+\_Rh     `}\@xr>< a\ `ք&ÓvTQ;ЯBڧḜa< v}Ak[lgWp,a<:?/y폷ϔ Ot\>8WxG+_~yE@@@@@NWkJ+@`)5Z=uU>>9UEsIׯUzSX8_3 h~ee2U% /~kJfʴ?S}8W@@@@@@`ΉV .'%t&4B~v90.@@@@@@``*>F~!i!dW6hmV?      @]rZ@l.尰?Ii@       C>h!^E d^      50l"    X`ȑ>;bVg_7n% /l޼y֩S'Yf[-Yj.o;6L[lݺMVlzW^gkl +YvRߑ*֯_o[l2eH8uVDm(=g|@IDAT{nݺvI'ti&wrʥQ+ ,^+VH/+ 9ӬZjEznN P8Kp@@@@@L-[իWOw!)(߶^zm0c 駟q1SDkf5ƍ盶hZnm\pqm'dp7ѣS4h~hST)PUR4O?jժ_O>ag?Kt1l,{olĈ6k __z9;vml}$ں^Sp㯘ط7֮]kg4fC{ュk׮vm|^O?i/cĈv}-[Gy$mɓ';K>}^Jvrܹc|`cƌիWw" =zꩧZ6mk5SOj&Mj_ܹ~y{п{7/'>˗/UٝwޙVD=|;?O2q>8C qwg} 'Zzcǎ+V۸qck޼Nצh2?GԩS.yoo]t8 zH˗)Zh#C:sCuUA_u=*}5j$G77O; <~?!l#     PDnbpw3I-?sb4nrطu ޸{9dzڴi[ڷo4n7!^&37s y5u.7Y4?~|xT?}Ϟ=Z*qL~6\ք-Mp3]0@cǎ݄~Ny47Y&TѱGٲf͚-ѱ]:Uu76lذ%|'fϞ؝8>D[mL4)i.C桇Jj+˖-Kj5u[|{)S2^\s5i 2$޽{mlnȟ P.     PzMr}6G}?9TTdV&7i/ieط~kʆp"2Im=GM6M-$Ox >t-?{~w^st~k6J-xu.DOgXڷ㎜'A7xlYebo~ط_@2\?}R7dq~ԐWnr7'ӕ%K YU}|z>EcuA0&M?.^, AVn0ai9h`C@t_&eKSV 7RB`k3?(ÆSP 3-^>s?cIB;v.:]0f͚2)1~)?oL0J%r뭷2bGY% :NYEw PA     “f/z_gѢE~w<~뭷 -n8E{7ť&궸-. iިMs^uU[q}{Eb\=햕?9q7iEQ lq[t|D_6!&[tv]}i}v~K3]JΛ]aܕ隤a Ms՟زlڔyU(Ӂ>?7WӬI-֭K+->?~7iEcwy~_|)ða2-ŐNq@|n?zm}/ܲ)u'H7wމW'޻ w}7QLYľoF [qI};ゝy qn??}~r[%K.Ǹ%u M21>^_W?oC?+WL?eIWr+ꐮ@/@@@@@Dcb?Cb[3fY駟xwuSI7l+W>?5޻[?ڻzOuiu=I}7zJ=d;G_>`sQ$ZkԨwq~n?_Z5ӓN^,ѿz=?bĈm/@kݳ>Zmժ9;zhM$jortWV]t:tUl(QJo߼ys_&u{}0b {myD<7;l(C(2h_no5L娣Na-+V,TVP!#SGCcۗ+W.ܿw}ʨ/2wAZP\黨,3gtt>l 8#Mqŋgr*YuYmQw{e>|=P;c~voN{@<2kǁF@@@@(\-JJ¦}g=}&g$U*^ LP {o=1)'|r"Ҽk0ԫWSj˥^6zU&tp=VyL״#Ν;tEٖùfժ S||K8 g?Wk)`/Nj;RwʹC=!!z %%)ZʹTwoy! (sРA+8sLwh)#G]28qr+a\ P@JQ/L˩DK oeߟCۦeYضo]v#Jp ?߿*K!*S  @ P    XW^cDlГ.}WO֝ubՓ7t?L?+ҝzT~OFg្ד9D)A(ŋMոݻwoMtw[*?cVϟ,m>NV̙N9޻B:q;7 `UϒN{B&Upn5T c\H0MVS8^zq-E)KYyַo_SFt-⫧Njm۶5eP SO=rh<@"@n ((SCq!d_Di6>֭Ou  {=kܸs1v7ڿos4͚ }}PQ>ݻ[zm=▀U0w\SSԩ5kߤL1ʼePP5ZlB V     D@j{wD2ʠn%u 01vvi~3#荂4QM"FҾwW}]M5m4cL;R gM klo-Z TZ 2~R-Zi%J͛'G[Ӧ/ؐ!D΢UX&qjLX֮ݘMJ;yLM ^ҧr}*SrߥL=V@R2Ó!*l{KEߟhQ 2D*4y晦 |M+A42n8{|}sO]> _[p}^7ol_~uh%E*drڵWA:P&eQZ&@A>!GԠjIes*ڧp駫*cdKXBO>1emȦwPVVZzƍ]֏g}f2 ef2eʘR'e~kgw%- 6EPPh *K} ܒ-c锑FB sUVly=kM&gS4Iꫮ7MѲO]&GW-6fL~CH !Մ@@@@@`P&>( &&drPքxc=O&+}{ehӧߥt*zjY Ekܹ 6"0LwJ ʸ׺fuuklt9k'Ã:߼yiWY&6]v~F:o}2d@򠢬!z{>ӯhРAdÆ ~f*%jժhT.D]|CСP/hIx4iME(!j9B>+ɓmРA~;ӯ $k̭a)V~O6@ҥKjH%-N -C1|/͚5 d)@CP4C@@@@vc9/I- N;.=as&;ƣ>ߪO=mwO$?㾺rU啎n nWw̙#aff2MBjSOVgS)MԖ,Y!knt,pnݤ\jر|&M%FSuw5D@똼:/_+uV0ʈx!忂*2購kc3` DRQvePw_~ib?,=_<@G:Y9#$>O?Կjm= Q@K+e˖nݺ]a ]PMA0{2g]߇c=f˖-K:}! 6E,ԇ~jLA" fx}3}v[_e L_^IS_mZ.D;e@:  @F2Ұ@@@@O>~wQGeʜйsg;|&OkYMpIOMj]xM[~NmZjeZ^xTM4k0Cnw^z~__LxU{IċDz^UІ) hci<ޗޗ+W:uv5I͙M?!BbgwڵIUi¹J|V & h<<9e2Kʚ]*@Tք MK.w~;8qҽ{Kרl%!BPeHW} 젃Jq923:QETBo9];ョGY-̿LFvk K?%hօ2\_Cԇh -1 ߼ Yf¾?|6ݒ%wx>%duժUCw" H     @~4M ڧS<Ԥ&q'D_tj{d&qx =ڿ&MW?jϐ_۷[h*SuM8Do~PUTZX>E~+hWOkr\ǎke6rꩍ}\Gǐa;q 9:w HFx:>dl<':zJOεy|O_}>'<1 Ki{Ʀ-rW$Wݫt*={K,~ SO}Ysƒn…[q֢**XAjٗP#L뻬`~!i^wqG"6d*+%jeԇ3$(Nj2ɄoއL0l*a֯[߮܁yrY  ,^ђ>;4ٯS~keӦM>Bʈlk_rV@+I2eM!}nZJ*^{% ʨe"@` a)     &0tLkUy%d/YΚx<폟6J7i\}q޽g.˥l3{2͛7-[7|yEd7r\     )puܲ-\6v x ֭[??GiGBa~Hܗ hy-EҾ}Bx뭷|/8ZV[gCJ}=vR?{R1eJJ}=uԏ3͝Rʵm莉9r|ӦVeShҊ+Tq&{of2e/Z>ǔ+W#6L`93~Zl5S꿞>~Z(}+4~-Y:XҥSelI/UvAIuzbZ`ؔ=˗M\ľ:5qvP:)ϞmK?~}_jJɓme)'췟U.W.~1fÆ3[lƍwߥn־y+VЉSVb7hR?ߥ]ۚWJӦٴN?qc۫B5kR껴ha*RDw)YN9h^nǸŋ~W۾5jXN3k8~JaαMd/WI͚YŲeSu|,wZwwMTrynċ_]}M$^t3g0^>t{/ϵ|E߂h}YwtvMċ.S}/u˷1}O⥵Z}OoK}-ᄋ;{E~ߑx]ߧxiPR^ƻ~p>'}:}ܿܿ2;/]}/CU VR%kӨQJԅ 3R꛹ek;/Gksx9w<^=6m =N:@@@@@@@(Rb[\)3r2@@@@@@@@ &@o@@@@@@@@`(zsΈ       1b E@@@@@@@ 9#       `@@@@@@@^7         ![@@@@@@@(zޜ3"       @Ld=oشi}6n8[fլY;<+QDќ9sm:uꔲW`Ӧ-qf+S&s-Q7,Xe]c5j)Y)^:ujg{       ;@-dѣGҡ˗7ԩSN8;N'1cؒ%KEVr,*X˗'lÇO`־}{={=v) t}/]̙˭mKV~ۆOn;$GϷobŋj*ח_α[o֯If^a>}\9~_fC.;Ȏ˸,FkÆ6mNg^ݽt        P O3^<`{キ=ֱcnjqu < d{\A=>xA }ď?hJ2.X@IXnMWj;yVW_Uybz0zl^/3w@R>}ƻ |aƍ[tܟtyg+]M(       (pCTf2%rXt-Zg!xLd0d!z̎{ki#ڿ[~5i$7Vʛ2/ ھv%-Z"A3v+hnO?=֮h-tw >ݷR̴4C^[ޣoew(m#o4$eaֺu-{œ,Y<=o@@@@@@@(ڵsR+cJ۶moO?hڵsiG!~'1c-\֭[gիWVZYժU6V<+O_n]+VmذgGi\v)yDGncƍ~yQFe&ε{Zr^zvZXb\׬YcǏwO}4mԚ5k+Vŋpmib ?y-w&gZΙL˦9B׼ٍg!Auwc^gZg[r5hPѷ-So:u*8{s9uڱ^U?o}9 ~:JvMt[Wo޼Ů#ݥ>[]-l~|D's[͚{^(PCrݬi{UFBw~mؠbН[Ң?G؟5~ʕ+WFK@@@@@@@`{ JC m7ߴ:?sObq^vxP^z ͛7 .Uĉ鍂"q֭[ʾx yk93O>6 痝6ҥ)KE5~ɍn)rIs{s}tmMWc}pA?l,as zU?6Q5eRg9+Q -p}5״2Mׯ߿nM~ yёvQvm:>`֬.a]}QWMe}A CtA4s}B}8&^w!tj|3}DV*gI'5HfРܽ6=F/!%#چm@@@@@@@`{ l]2\wu>AH̛7=f-P|y;ӬqƦlcƌ_~xpW +^yT k߾}R@SYz5l0_ OݲRf@.͹k{챇oޠAvѾ)n]bĈK.D٪7NkL8Ӓt;Xgs`W^c%N> ]znmڴFo}h ne>MT3/0|lhGZBSF>ɡ}]+n )OvZ__ #F)Pe3S@@@@@@Qi.240mڴDýkO={>y~4y0aFPvN8!/Zj%zvYg >Ct,I y%'{D#<o)⪫Mcsѣs=VZ5{zh+e\P J.ÐhƽLOL.pᵜ`g:9}4#˝״ęnaر˪q_ >| bkn[(0!;dhbO;=4in8 VmǎC~>FϞG%?Trzū>'>.       ;@m=h悟y ^ ^شi{|{[-Ӓ*W^Xvi)e|x׭TRtKdwA)¬Y|0ezԩ;6g/)S r>^W/IAO+YXho CS/]6eej}Fy㓂Tˑ, ֭kl 6塲CU=,|\qAGi|O>m=bl/8cߤUYTYx      lCD! A]G'׬Ycwu)-12sL7{ZHw%L4ɟN/ ,Uӧ>9?.kltÜh]!v_ MAen|OI#m9غn鎜'O\P|i GE`_|1^x;@Əw֬~I דּ&f=̑3fٲu&dbm~>\;r;7 Gdj+HCe3VQf        lh&zyUV1>lkѢiʕ+9ڴi!ʗ/oݻww 45kíwP*2MDwU+~{MiugZ/Gp9K ~+V,c}ͮ#b_b@ h޽Oiuu/?hR/g9>#ÛoϨZrSdtӧ/͛WK;c]~?\`lbq PQܖ(H@F\G@@@@@@6`ӧ&֭Tu}I\ɓ66oޜRs(Gm+&hIQµm^{m>geJ5BgZ9qΐE!Qs \bz7˞}v9׮z}VNΒ 3gXOki]oOw`x1?Z`;      P< 9[ZǎC\9';AlՉ3fM7ݔxO|j{?ТFazJi_Gq5kg袋lI;w]uUn}Q+YYtiҾT=hOtC˝'a㡇n2=wJ*aʕsm딕!,) e+xInɏ)dѸqc-[̢ur,z֭y-lK%7tt=vёv͟)i뮸`G}(7 ]Ez}.讔#jᢋwZr>ӫi)R      (% c uqm)=Cv'wnF~wۛ&njY /owK<=랦n_~?{~7xeFmfd%H߶Sn(wߵ[vRJ 6n8?:$ EBnL;L8nδ3]J?nժ v_Oo͛W~ZOzk{핳|'M}&p}+V}kL{s=XbIexTP^O.QDR[)SƮq)ᥗ^ +젃J:&:vR4o Dg?6mvK:>Mfww]jW`=E7.4izܽgtwb;>>~v [ 0ٞzji 䌣I*UsFgU+yL}~+1bMw۰a%_|=e;㋏!gXn?իϗouۼ]t/_;̙?׬LVz97u붟}@]߲+} =]DG{Ǒ#ڬY+|MVy5}ٺ?on?`H,[ξvv[W6^}8@@@@@@ 8a駟=c߾};_|.´m裏 W\q{6w\79V/.vvZv&/ƍan?&ḫ: ܪ㯢Y< 2$I%Q"333eI$Dh y\w}Ϲ۽)zs_ZE|R孀!(1 Yg p>A*ˈsώw;׶o=u׷iؔۅI (Q0hB_ ;SnqvA,曹a-$?  (qݛ?"S}Ks}ިwotDٵh:'iV0 C0 C0 C0 C0 C0F 0=<rN\Æ ]zG<#b!}[Zup cN݀QXv7ovx01 ֭kn:RXO=hׯw߸.U+Gn( cS(.~;NvG4>`[|o8۰a6-FDCXrv@^ڵkӦ'#fZƍ[ɓ{OK75j/.gj޼+̪Uܯ.q }闻rĝWޓc$%0L5m[ZoYEWО3?[߿w{+6jS3fddNWޚŶ^B8&WՃ $H\[nx|ZOXrwe2uq1\ÆC9SdAw1Uݐ!mLjL %HXp?p yU<x`&-gsAm)+ ZHLL|-7B( BaQ+6oަaI azT  ^2=|QKs>`^שSZ{jӦ˟?<kHb #(\8^N8o9HHRb䣵k7K;+))|h~qM*c @<-Ef~y]C0 C0 C0 C0 C0 C߅(s$VZiȔgmwP߷# "<({g*Ck&(j[ hKۯ}vcǍZNB[loӦM:MG/^jLcAtT$D_^FpO>dvt~5-[ܞv{Sj/KRNA4_r ;s<O<7j/yqYm;Hlz@VƜs\1hy~ QwР?rY̛dz:֟(NJ /.O!.[ްaOMo==oq0z#eis{ R&,ϩps(\}Wc<: CJ$k&dtMcvusϷYi;h͛ϋ~?lpԢEku Lgg_#Sok/\x<[sO=]`G?`Gt3#;0 C0 C0 C0 C0 C0Edrv1DqBpN<1 0gqp j%>j('QO>X0wv_z饮wbJ<MĬi+|nXͮ$QxnڝuYjꫯJݩ煷KzH ,*!D/:Q;Q 6|/_.ݟiNj [ANtNf~=7pD,7ni-ZpB&̝3̩T-Te}⟛>=7B4ć|ѱc+E8{9F3\E&ޅvt{X=Vx Ȍ25~ƱZ~nhaNFc$sO= Ga< %ϔD\;O/8k oH{GWUxc:2 J{hNgq5 ňs^<kTR9h;6 C0 C0 kS#gϯX) _C0 C0 ]1y`p~a8tj5qX'y;vl'DM߿Nm-~0`~_ۤ- .о(x O=T߱*x˶mj[]vYP^/Hg={4=ݱ59xt,a4/ao&"k>}|IG}Ӧ51(`'h*|4m^̍;ax.Lsw ?|vz-`$YtmayG4}C_a/"hK\`޼;A;}{߾}KMO)$~$Oޡmǎȹ<*X&7sO?-rGkŰ<06} (5-1`ܦe}]r.n*GBbԖMZ!`!`9fenvŷXfy^D=2eʨ?Xg';PD,nw"E$Vzxqck ұcG'd7 ӧO҅Bpv[]t;4F9z~]w%q3K0F YF9 !M\4w9%$xL~/wBgiWˤś[o_d{_ܥ~*^2:*fiOO՞`PBQ:x{6-vLjcO4V$:ֻj7'oH?vn9Q~8WZ5J^k;zxS9ׇpx2j|yXx-luB' =[;V^ᨦ$Us|zR ܟCPoCȴibhF__}-"\뎐gm}:hvl!`!`;<-ϔx{˖>@=[!NSi}.]퉉;c,W>xל0a~]חylx#(N1ρ9.k0 C0 CC WBH vq3fP>oyaFu쓒nwEc paQcC@;A=m4oڴi2C;=Pq@ +¾D:¾F9gF t{J<-kJr#?Ou8Ĭϟ?_4РAL~t(3 E0>p޸qcP}U?%]\|ܢ']Ӧهi֭mֻc]BY?Je۷Wu.b)d9s PIрװ:uʸka FE@]ڠc _]\ɒ5-/O>9Vn 3(/ dDtwL\x /с [4\C,\O~MSR7kOJ--Y'\+WC~ᇅa/}&ۇ$ŋ֭TA9_ A߁t̞Or[\ߴ),e3v'v_D׋hRZDŽ@…tBLJhYmk!`!`; .Dˋc15aa p!`!`$YkRrX^ϺG}4,^mZ\^ InE_ Ɣ?g56XөH/\BpcI؉$J*[l"޺=<wDsn̳8tsI')&ի~  $IBIBJ'@fH$+ T$<D_:y>E+Sשw i>bc9;w|_mBʏ6j;9<,6_vΜ?EoQ%~T+w_Ë/P3T+?:ƚ5Ke 7'曏n_nc8q=Hkqxxrh&l[7<̘!!Zly!ZNB͂$Dyx_@yLH>k.^j*:z!)!v[\7d7h q=~lBqӣxr* %+T,#d{ .9S^ 7|/BۇFdyܭ[#qkߡDĐ >;PgUOܺu~4епYi۷I%F?oY@g3-o"- _>^VZI^d"j՘ܷh'mb?+e֭۝1dHLs-ƌC@i}tUˆa\JpA%;AaUOd0yrfo(>Bԯ COn+HZ>#C;PZ{\P̭o쳏>C+xX@/Wmt=7Y"$* +e3LX /xI s;(y¯W+=sJpپ!`!`@# !HĉuduU@N2#TVa҃VLm?-c=^7oP'kvX/nnɭ_ʚkֽ{w=-ծ&Bpyܞ=Nw<ڵk.ל9sXK.zFv:Gu[h,v/xk;>iNy|G&w..~'T#aGY9п"K/`ߋe!#v_DSOOzW!e M\uUfp17<3`#:[. ݿムsQ׮Y~15d@&/}gN_WKY~ѩӐLb\y0yqp+a)\8c۶b-4QANV˅۵MgmҨQe"\ C0 C0 C߁@Ŋ3y`=?k39-P*E/Q!'sO;4X9 ;[.Ґ~e !' BxH"ヒ qa@ 5:]w%k21H@XHH\tEAUW]XJ&gyF6 ۣ7ֱcC0 C0 C WBHDb+ J,[lƎpQk:V iz/,Nϭ|_'|l\/ps[znyg-ZЗy^܉F7yYĀX|Aec;D^DToogmW^QǤI1scG+ݹJHA(`~@駟7(R 6K?z{=:nA)aMAMH 2}uQ׆ȟs$]\0DOBA!$,Z@~y_I a %k.-+$Wv~#<@,뗓!%#c)ȹ\NY=z4tm8/2vqo @[MQ|J:u<ӎSꐆbw bW6]҅D|ZjI{y(Wh=prUWEqM܃ku9 r/p&-2fOGju(jDu|W<c*6s5#FPK;0X7lSC&%#r培7U3fAazs[{@/=?7SdAD!8 I0!h4iR=PS+?+xTR齓KToMN:! ҽ{3y&mi!`!`!# avY{a/@"xGX'=4}XC15!=zQ nZUXemR1a!W^NVAu`֥FSxv!TZ -^XmiӏYWKsyaꡇoF=Yf]!`!`$U?ŋwC QV-/0zab=}t}楒={*y-^=4#=. 7)gyq[nn`آ^>`ǔ}f4w7n8D⯁dtǢ| ?'/J^^~n+}_ou|ߒcZAY8g^r{nvm6Qt=%KD<~꽣O>#1dϓ`3 .WO"9F wxz G> (>K)*tpɶѲxH7oM7 ^|kRHǎijB(I3(Z,a-_~Y _ i.Weo7Z>:*0c*Q~Uܢl4l)PrC^"Wӂ1!qcI)D | OK$HLa@wdڴy@g /}zWhb!`!`-ѣGKhQB PX? tQM֘1cotDjXlyS ì6mC‗$\ϯ}YPիa j!&'NN0 C0 C0 C0 @h;L :<=#BErᇻ5kJ2AXQ/_n޼csqI*+cCN|-&MWP>L*aPr֭[kx |~wK/$tR7a„  J*n C0 C0v3]0!FPW~?|7 C0 C0 C0 C Bp uu۩D'*m۶uB^ve -[t 2$<"ށrh؋W_}~y޽b[C0 C0 tى .j mh!`!`!`kի;3›oD *$-^ i_~駟v5JZ ۷3E ~$wWdJóB-2<+|'[j^~e ^{xT8qѣ+_ώVZՅ/D4>gkn`b!`E`";K0 C0 C0 C0 C0 ?۶ms ,p .t[lqT *O~nΜ9nٲe\XbBBxp H&lڴ-^-Zȱ_\9G_ŋOT(K',oI}Iמsύt=z| 2=z{oĉ]]‰*Ą3V(IaMqyPM_;E_mذš漶lW~W>ؾݹ=p%--Z@ϑk;뷐o.3羻ȦMߋ[&%0\%2oWЩ͘1UYϠ)S~ɼێ!`y Yk0 C0 C0 C0 C0 ,hhs﹭[rV-%׽W^yXEm2vF ;7+:vQnڴe;oPi*a}3ACһw;WH(?͞<~P0n>ģn_͜­_"꫃>ݷ.;F!`0ÿ!`!`!`!`.@Ѣ\ 1 ʕk;]rɩjq d2?WU-a+gnM+SkQ'YŝbQ^>;K.r7ox\|LvXٖ-[AGdaѢymT 0&-~XjX,Y͟?MǴdB=&F"!{]/^2QKEtR-Q2 r>&dPDAգĉć' ^Q2+eN+*jF7}2USfͲ27 T)6m*s&k>+7臄Jd[,@ӗ8Ϡ&MZY)YQw!"%vxKm䢋gj \$D~kRgwٳWv-t:@IDAT"zRrʤ \R +-Ϲu"ya)< Sǐ]\Wȳf<'7שS>~ɒ??û@bغu_2%寿 l:uz[Bvi&M`O;:lExF6lX(ׂe"`Ъ!`!`!`!`![sL󠩩S':νq7u?~ziw<-No[o$wn!w巈vrp.g5[NG}D<o Eg˸4Y}ҋ[\ֿ,ԩ|YFTO<47|Aٛn8g&-iGwA'"_ui|K]֯ߢ '̃$?>wur_ye1uzB^YnV\ 7+sIrϞ4 B\a9FNe8^z1Z\}4+gvr_UwLr3LP7mbP̘СC?װaUׯ_ oncU3zç(A!>tTw-C:scw.\!$eȐ5 q[Dm۶5C]~'Z$)mCʎ1C| ʼqyуŋ׸&MMc+ݻ&W2ks=XS,q(ީU*ʿMiРH3@!;_~9u&]rICUjO0O">Ti&oh_[ݘ1sAAkwhJ"8x_),V]qP r/t_g @Xa@￟-Z 9ʲQՕ+WT}|o>p_|q^ʫ ̫|;И׬+'זV- %/ī.IB(N= -_ZI!]Ֆ#~Ni.էK:C=J==\'/*Q24 Z왛AT; "Ln@NHoUzXb;sk׮VcQs?^GKx0/NiӦR1_^yх/C`Μ95S jԨy' vܹ]n{'ܶ!-yfw饗~M]mt+7{x#nn)σիWx; ߶`^oժXܔRWoذ!x֠tFKNE>#q9~%0 <ؽ0Z]{݉'(V-dzF|b9;S7ߜQFBwkbv?Ā֭[˂ElA_~H… ł:tXWEʗ//-s7vgy; >c /BӏFq֫], x'JR˖-m}kGt"gyկ__e[MN:)Z$8)?,>鸦3f,wf͚>8'?SQLVXDڱ!`!`"N", \|22YZc~yOjTk\צb\$ Sv:Ba] гլYW=y/m TscMa{Mih ]p"7 q7ɻw'g/nQ! }aLv?D<|<>ׯ?z~{VڿfGY(qyΎH.k]tza'%/)SDA+׭/FL[Ne3="Sh7gVb3Ϭ^4xV/ W%!|0+]yF8\@1Ѿ=&¹#Jf ЦM}A>d2y‼ЫW[AH"/(n:!1ƎFBڥQ^V ڵ&x@ڷ?L<͜|]}V K@y≘Gc!Ě{o{A;ig}k7qtu׵~B,Ǔ /.\T kD( C0r 7nt_uZC@_,y)SNtrM Tx]wݥ?*yVfĈ(Y'^h"/^h+c0< ;@a|<-s( kd=ztvPlz%s뮓ŀ؂T2W9;]ڷo," 2A;qG /^bHr,_>ˊ878H^/V.Wøa:">cƌQb(y>eӠ-# >7;wA݋oquY'q2g}=#>+B&yWcUwH+^ʔ)~7 De>ÇWʺuR܁?mB621 C0 C`wA`ɒ`Ӧ l>|\C3bԓx4D5jԗ%U8?*r(JwdH@y,ltYrALAG=AI%-[ c')aڴenJ2d6wyok.4ދ sU:J`^.kzܠBn'ƑHή$1N>,%0̞=}W0 E mVlSLߑG,F*V!jժ"(++rб31vW<=#ts+T>YY&l`MċW^yEܳ(sW EU{9j*> -CO^ _|7}ؙ"Tpa,pBcM5\sz a@\fwޚxID`Xre@^ȵ^ـWɓ'gO>$G}4 cc^PO6Mx1Jf2c=9; ,(n''!ox"CTr%qD_fΌyJɶjӬY0V K|c1gED׏_[KdwZ }byϨUQsܟ^E8?B}'XBY`Z%֨QC5\%ɺp`9x 12_~^.ƣX_wQG%l?S7o#uM a0~Wa.y=**9I&Ge! 6Թnܰ98~x2x1INƛΜO,א9Aes@9¼w%.|SAPNpݽed FTm! h1.=_GSz{s%V;c<we93>rHUa9EǓq5g0wSE<x. Y훲^eqO0WUd2!y/oΕ2\[<Is!ge*%Kvi=w}]0,e˖Uwy'!{+zo!27gd~G~-%IB淘zEA; /Cz*ٽ;o$>d}hXsEv ?Q%{sw ?L@Ɋ5׵,NSo]`\q&< Ԁot:9o߾ a9 .|=\`07dX!`!`x>8U1_bUQaJ,.Yo^Tf*M,A*=?N ֩B(T(|7; !^Ҿ5[o=FAE9/ zQ|-kc$qrR6mr^1\fY#ZoDBn k礯Eh3X""x|f-y;Yݜx`y%0̘ݻ EUԻER%,`>t#OA%$`킰xQb?$%5$Ӓ%k䬮+$ R|fo#_ elܸA0=CImE=vlk@f v3ΐt1O?*"gϞAhz x cZXU Ng<L0~aa9Yh uñQPx t@9 QavQTB#0p`AsvHŘ6XDKްbxxA/XsE!CTӲۢԩSU/ua,2(7X4 尦< ٦uCiI(8^p=RĹ~|nYhGAKNǛΜEم9{9-J*\ /0(;xJi[_([(Jw9fe92F\sD:9IaOكb*ѳ,"v6H+}~r/Z {x8_4]-[b%queGئb$q_~9Ma%)bc>ZLWP(_0!%K[r ^ !L C)? aY1} X]ZTς0Vs,"CUE,()XFQl>X{e^zI(~QYxQe1 Kb [X%DEq%D XQzc$ +l: D"7(B|~Q( Hp} L!;пUtYALPr 0&H6XHG \W>x2@ S[zJ>ύyA0<dU(wu?sDȽγ<̩${E= [GBOpxPskm$dbagdX Ⱦ Go\?',e? ڜWRBH$XdAW^sRM cI dkܸ惇w8)U+}AQ܈ N\ FXnB~À|E!GXCQO&@E#MuBhEY"1y:?aPԠ c+r8(58_Ay5C)cٿpCȩDD=J~vRy04ܧ%ф=ev}) 1+\mpT'n#~y, 2ܱ ˧eΣI(CQ1oOcGB*t0J]z饪Pĺ+jp-M,'9G ~Kw;e\f+#F MZjpM3gO-#exc\8Ek51ed$}+bqF]-!GѶJ7yb bc84gGEqeLY._\ڎvX!yU2DBnk&zY~1kxBO@[e{Y7_rx@g~m/^VHCdW &//oo>=SЬ.;m}׎ O)qsog>૸ +Xmv1^(:AiӖ#^_k׏e=qۍ9Kc"Ok߈bkPM߾?ǹ y;&~cz_^zcBO_0mdndU CH =S+;XX>S<Ö -(Y_, n"A),PĢ/>t ظ%Ƴ&Mݸ-#<2H8 |`QBB"+c  E]\#@9lM k J (QEMX=iY1MXcՓFnq*أ\ ցժUS e!°OE( ĕ Q ~7eȎ79pxb%9soaH+A8g%zbg1f< ̥+*\fG˥s Lej.^Q(!0o’Y.{ Q*>ć/;e~{ϷE8$5^r2|d[^aϵpZ:yp牷I*\gexlz@o V`mY0{ltێ[gCPJ_`XZrQ{rJy~$_rBWC\Q%tw=OxJ>XCx[DQιB {KB zK&7Ar@&z\c>^w<'*T'2OVz+:s-k;)~ B32ԼB2;H/`O A(g(/$Vŋ~<5 C0 Cb{sZB#^gWY; XWW-uVT+_ is(b_O? /4!,Y 5ܗc%@LL~~s~W 3mKnx/viesJR?ժtPN֓ſoi?7AH6``za8묞BWL$ U@~!·oýĘ%u4nd;ht? \Cb7\O_OfGpK侟.JGYPQR<^\3WM!W}G:ڹ&BdγNruxZxn6߼y-y^}}6k\x(!X ?:*^|>8dX?hfb7 xh1 ،81*P#Šܪ~_?Pgw{7͚ r¹Ep"M1(!Zc/2]@#g=qo9:+DB_>BEݰҍ{);+L*AP.d>(J -K_(4rT \DL8w dϲp>Dn}9L&;$˹!2yw&'e!9 6x ?KaRPg9n7Rhb.~qP%!4Ev/x6Dchpe{T=7;x71(Ke>ǻ7Mn!{~LT{X3\o)Xt¡ 6 yR#~fF/I UcTiYJ嗅2AC)kwN Q&Ir:1p<[AB^Zx[7'8\y\ܵr*.L9`!Ic~BD׌2`{睟e#ѵxıQ `ʇ ̘\~N勹3g!@-˵Cl2lإ>ĉf!6Zx6@=_}ôߞk)ᶿ߫!uxfҺu!IYvD絯3tОy`F]l![ĿViYlt׮]ODYx}3 XpWV‰M6f,@Ģ E}߱*XyW%;/ɽ*epќ !;G[o5(;ܐD$+#GҲƙ mo1yB2$,|>Y.s^{ZeRQh2Vẻ˾wamt(usCB&R VОH}WDsdw)]ܞ dsP[?NsoiN~r7DPAZV>uzΠMUr{pWAp#݅^aOYB'h]j@@ y?~oY4 鄏#<9 7\uW a 6gp~}/KE|h ?8/>[M$}ge!JJrde(hW\QYk+*s̗"l0}zbF'̓H~mP*/'E3)Qtqζ,5&Xd$ҁ͛ZKn q2̙+\҅u|*:^Q5DҪՁb w[{?{woոqiD$ kU"HDƫno! Ȭ) D d蒡R7]sTukg^k}:oA N9εHiӖ!=dktԹiAfS h6hP=s}hG{u[TѮ_Q\"fEpf?v޹Rp_l)|=*㯾bBĎyUpvWw X/8抑HqFޣGz/-eؙ8qN`0[Y @Aec~a_ y=l^;Aנ3Q$\>[wY&ձ뿗wZ^gwq$>׶|L|?? ~&^EmұO81xֽ砃t}m@ D0kZUO?q N{KX e0auTE"4 dZ/S0[UIU|P_?5CT}Ӷj>%I%@@I@nQ^{nuWA'bDe?$ Lذa}ay]6 [@O׫ueghhkV++hV1)!,kdacp:O+2UUd֋DJl[ MJ lOeu$[Gu4ԇ~r+2V`Da2A6ݯz)V' L yc52<_idUN=Ը oƚӓ,~\j?v6M&B4h`pu_gJŜ4k5:R)|WQz0([Gb?mޗe˖ 6˳=+u_4xR8J?j[5'ZdWA )CQCwD[R?JN\Jrܮ`ժUIDf| Wy~-kF2=EߙڗTL﫿ޯtEwÆ m nTe]_WYh,۽^ T!܆>BQװAEC8h,wy'6?䌂FT6l`H;ٽ|f޽{'}MA3/3&;_EW 8nCپtOg@׊4 4Q_WY?Q̗p1?W@@+0aAAq`L6@s@@<+߿Z[%S(SQtm#z]dhlpQ'%Kl:Z)?)>p_b m$:Z A'QYOh> А*:?*ʆ_|bVQi ͲbŊ4c}>}l[vӨ0۩@/'|rkEISڹ;mĎӺӯرcG$N/+TǨtM<+wbUO>Eu<ݼ\i6.|^F?}g,$z?0e54OVufߣ>[~;_߿Quk=͞ߢl= KHCG~io" \(H'|FbWBFF TA%:z9eXrQ> >8TwIGïbF{1[kg?uyE@H- my^^}-cڶ[N}mYg @"V=m?\CN ;[8'ě?k(/*&#z^`56:Ru2Qrpg{zQnBuql;<Z͛[:dW'625y>hh=1i( UR%|Ԣ^Dmke7j[% :'*ھ/JEiy;h b㎳@uj̟?vG->:u}E5D7Pӏb $# ktZ R~-d¨R>!:"lNndmg~^{(|}w@=UDT̙3'߃-~馛l_;0}ذOԽ^sYe*.@Ib`oW?(HGǬ./r#Fuz랥SNPN;4{Pdw+_W`&_Ͽ*(ggMɢڔqcZ'<=]_U(EYtTɫѱw>(D('%\ʎ!^4ܕoo~xE@1[3*\~.]f[M-@߾>ӓ0@r dӨNf%Lɭ' GMV`zjQO,?O^[JWv:)GaÆ~^}}yz~z}` !XEǪc1+deUW6l _UO =>h0N]U^_WܿEM׮]:ĕIC_zX5 ǵSwJl zىXOu 61ݿBiõEfp}~;z*T#ҵN߮?zCշ OW& _cjC~ܸq6m86Ӻ[?-_ǿjyөQ:T'Q|gzГ^wj/Yѓ:pduWMxչpo/j^^ kg嫒j~ <-LշW 톗i^Fx[Q(~vyRz8}F_O`+K:}'la߮??׫_5/eyW*UyިZ=Kxe{뭷Uy8$ߏ|?3(@IDATg{2&}SMi3fݪQ[>(KT>7H\'܉}Sm龭ҳgO{M(} ɏΕ!O~7Kk>a}oz#  zⲟ~y(]Z>,{;@(jRANh>BO` 4u"'EOkYcwdz[tVAOsR! f^zROkwm0T `_ه'zP~~㭣WxzX܆(=]~܎-jSO,ܨ3'|n9kT&йK (JgE]'1}Stp׉:5eHekQA@Y1dZȄQQ$f:dg~^W&#eQQ?x;>?^GkS $}AJ\6iiOE/:'⧟~Eg)(JeP)pg/(I8$[&nW]:oʚs=  Px͢y:       %Er&9@@@@@@@        Jʙ8@@@@@@@(e     f^J.z?rWop*쫯)Sٲ3lU+YW`ƍnծJ*m"zk׮u*TH/;ʗ/^͛ʕ+ey{S:f+jG,Xx Wn]צM*E2oӦMnݺuR믻EN;ըQ#O&VΔͤI_mݻ'n[20Sǎ#    @q+\˖-ꫯ!~qs ';}:y湆 ڏ:} Rf̘&O/_f,Yڶ}=> kg޼UKr͚tJvxx{b4Q>6|/noȏ:ݟywYggEUV RY[ou7|k߾DTX^W\צ:R;0[^reXns hȭqGV-+ǎ9WL hܸ4hdeyltɒ%mpSCxGl_OZ,{ܹtN˖-ܟwkTyb סCׯ_<>D5>KnݺE5avuWۧ=zѣG'@-A)n;"    U@sSNMk?w!U(+=SIwAGiO 'd:r5jdKՑZ~$5s!=:qDw'BD=L7`nذo;c+W.~u}supg4~ծyNSp{lU`ժU^o=pϏڵOgbܸqu{vp-_lyc:`&_j֬֬Y[UVNEeoX~#.R[.2ڶt>X'w^ط3{ ={ϰ N:m駟M3| '?ٱ?c(D:6v7xcyte8裝>osq5H d     P^x7Gfz:rq*\suV)࣏>r}Wې!Cu(r2eu',xAjO*]>q_|q۰a[op/t-ZԵz_<'Y{챭]4>HY9= W(Sw}甑AKMph:m3Ʋ.O>￷v,{X0a˶?[nzw{gQVf͚Yf8}]nfr_~Gejߕ9F 9Tj߶e-(QYC}4gdÆ 6ǣ>jTw^rgs=tGueaP2P( d`( gc@@@@@!0|p|q\TǚVgV㞂 Jwz:ZORfxlw=;sLkI&֞k zZA VGQ8:iٱ3I~Rk,u_Qu>3vZ~qzݙgBmծR*m gЙ,׽s${Tc}w O%wo?&x*7| m?9N1Q"V^^[c޴iSZP+;t,f0 ,\uzS% \Ѱ \馛ry?>"1D.flu{REC(Bڵk۽PA%*o˻իǪ+3w% jղaxUi.]_|ѽ6P?&th$et)c/o DP.bUZ懁8c{|[zMvm52/kW}ѱ{6;/W!ի{o8kn6xu٦]/HR\27E n      @ h_ :~rM|%u'4OgСC^[zs Roꪫb_k/7SNjulAp-:bOjmk?':b2[X%\%Am從^.]k7o|q=x h7YO_liU_s„ocyu] :W]zۙ@cI0BG3:LFiUQ^ bsJ S@gŽӦM_Xotlѽ=|_ӺWAgw}Wc:[tv{-qA'z#FѦnѢŖ z9=zKidKcY$?AoU c_[UneZ7o[ijC!Um . U5!P,CuO@@@@@p駟nnO\[liO)РR?nԩsα&tlvmIxz4q[O*MرcOj:ڎ2?I?RklyRv,M6'Yp:jzC*R?CR+R?qI'+ּͭUj|ĈO͡VR״iu@^*KW_m+AEOtՆPPe1i8\RF=M>R{a:T&4z]Ch ( J޽fQ曻7pe_;v{WjԨǾ#t9SwH38UPѯZ&IEChot=z;6'MdC3FA_>CTV_t-r!?']:uJ[oInŦ_}g!\#j0s -DY2t*.lTOߗRԭ[7ۇ ȝx≶,;\C^.]:("̂D@@@@J :ɠ}M,Ag-W==.AǕ-;Sbӫ_ =yᣏ>u]tEe`~:r/nU=oG-~(jqykl2\mhig3=~VZ#G #b?z*TvZO%dN)l}[UTR]sA'r> K@vBe-a: 0oھ|qAپluMXm߾}# ^$cy0ă-Sp?eΜ96=W|饗M%y:L<*  fb'@LA@@@@B Htϖ5 HqʨPLyAC AP@l2dJac U68 z֠cӞ~ݷn\}TO*ӄE4Ȟӣ<_5Vp9,"T;w5U^\|,oO. zFvΧ{}Coms^{;3f،\o߾OƼO+_=yY't֢Sű(HizeIOiܪr0Ԍy :]0Te9zr>ꞕzR]?9֎$T'tADB+{ L0qR 3peP*UNaнX^zר$YFľAcX=^}1* ql֝w d%fþ7!1ݻN8! ][AK<=YHʠ>se˖um۶ :TE߽_|eJgk!*X"#9AĒ? ׏9j[!Y=YE|&|yS"    @IPp:uԁ<)VJ-CWwLyzbQЕZ[y `Pg~E?k޼W%-W:Ģ1|]Z7*F z_S7;շ:Zv7;?hm e Q`>QwC2E-fF ';    =1۱cG˩C0YQj= %vl$[/|"WZyub],=z5$6{ǢmL :y|f҇T5ūƍNyY=׺mͼPZ|\W, ڵsIUҩk70賥LJz ( y(DĢ{p1-^r%ioN.J{[drwH~qXxx/ϯJ!,?zYpeI70_Y)[H,> {eѰ/ω'@1ҐQEWSeѴ+s!-TrQ?\|߾}{7P eCv@@@@(L#i >xVLd4nؚQebYllv:w`O2%G?橳&Y+a?՜eʔ Hߚ}!a&N\dؚ<̛5ݿFnruTh_A׏p*oVxMoذ\_/G_~iԨQ’0褧U $jL/0ԮՉ*+D-YK%1%=}cQ1uÇ'|ŊELog#G'uo eSQ | CJvOZO>}l駟v&\nv{^|Ew H裏ڬVZy:BYYk׮֑j櫿Ϝ93rs*$\4|'=B"ܹuβL@5Nqv#L\l8 {wXbg(s5eN/M4^?\۞x STYv;oc^J:6.VԩK6ԇ΅hĢ 3f̰ 6L\{CH"    @qPzgu(tn${ϷѣG[7(AO"+o4S juxNKJN_*Zn7T<܃>hi޼S:PǎkCZS40yVbݓN::R82k,c>:|7 :~ RmgGvrp,믟N9%uGN˖u#K63|x:Y}\ 8_>.^pzߗ5r=կwaݜz_EAEOGt?R2/NU/(/Y!d4R/ з;` 0uuX'Ud{({:5< /A i ePIG[djQ ow䷩s gy+X&<߯W5d˭j :Q^6 :3gjSYA`L/C(tDߡy(xgU(~;W}>h=/W΋~?UVx;)c@@@@@ $v쨳!UQ/S:EA \z;)KN8hh^z%fE p_g˿ZA P(vI\u-<~wtcLvzUY,yTjTݮ]׿YnĈV/Wnk'qҥ FcZA@턷 Z<?Oȉ%/uW xGv1bĈS%殸 04\4̂Cu# eP@3xʸRuώGQyٳS0Jgj+?6i}&]dkv} +V 7.W]<;w 2PAJF>D2Rsϟt^4MTpSب    lgnAչ'+Uou|7_~|rSbz"VO7n:} yXQͳN2uU^F 9.[j:רѣ :kGWd.i (IO/ZȮo)# EO{D-[K-[f+Wtk׶L ;CU,SBeнHI&Zjާlw4L؆ r*Ev}?]&ec BOkN8^@h nHDkuԱL<鮛zCA I'Oᇍwc'Hsbڴi"\ljl0l@@@@@~e_ (.+w~?{j@ (L#o)'E''Ov-[ ,ӓzyjCcԨQC]&4|aP°QPG~mG+]c@@@@@vWJoc;Թn޹cg 5kVrvXJ4t9,A+2*h=va6 A~͍7ΆАŽv_|ql4,N9 |h`[ ör&@@@@@m^@2j4Gw{}6mQ)[oܙg6sۗFoժUp{=g @Ðq6Bx#Gq[܉'1&RT jm}W^Q E 0d@@@@@J[~Tʖ{cAlڴ9ۊ۝͛{wkxUV 65kRJe}l }M69-!=& `~       {hh6J        @X        P$0 ;E@@@@@@@ a @@@@@@@@H`(v6       aL#       @P$l@@@@@@@05F@@@@@@@" H(       `k0       E"@CQ@@@@@@@ `@@@@@@@D"ag         !4        EF@@@@@@@@ ,@CXi@@@@@@@("       @X        P$edl@@@@@@`Xj+WPBinƍm$W^ʗ/ʔ)Kͭl/_Tʖ-n?͞=ە.]+OM2V=3]j  CX @@@@@6.\>`ׯ_;? /:V*Vjժh)WcoZlڷoرc1cA hܸ4hS0Ab9=?O⪑e1d۾vqG D> >ܭY&rSmZ@;5k 6D͙K,ݻw|0հa;kv=Ӷt-P@(Dm) -@@@@@@\]uU'tr{#7_:믶SNn>M6uį\r9}twƖGu9]veN*{챇K(>>sWR%{X0wd:._ns]tQfn)#gq=zuj֬N$6?Ĉ#lT i~~|r[bŊ*;v,3fp6(  Cm      YP6wy~rKe˖u9.\iĢs9}g+t}M{s1y~m۶l ~h,U ʈrw USN}-+];ښ5k|uW^ش2>$ePw}ek("K1t΢reH,:*CT$= )2%I;      @1֭A5\˂_zʖvi9f^ vj+ @TiҤ?a>h׾}eyvZ+تO e8#콆x',fLPre[)Ǐ{ۦk׮m'#[`k׮9+)pc93ȴ =@@@@@@`PW_m{9l0.s=)@@EA >Cy :erHU:([C6٧ON;ٱWc3dK 7KJ      P,4,3rkFbO§S端roFvʗ/.R_u~wݨңGWFE%f:5kִO?5kw B˝<g&LFvqGwy*UyW_}M4ɚTRʦ bO95Eʕ#~Ϸ%j{wUjU׸qc7qD ^Pȑ#ܹsmS!EfM6 7yd n5kĚT}Eb0TtP!:tp{w֭k~uXdJLI     %P@O04i;t~[n'*T']aSQCvM(TB*\pԩ޽{7m0^ږ럎;Ʀ5~=Ӧ?3뮻t^x P pgc=ίk(4qu;ePfӧ6m6O=T F(SL\~/(ӄ.J*`q7>m}v<})h9۷o {+2e>ƺuRc! 2H     Pv9*ѐ$Yuvi'=q{4HIeP;`CG^8`A}'{2SC?L6 9(ZL+ e?(Hy,&z{1a4˿//OeP0+ >dN!7V]~=XFk: `VZEh"VZl @rޙ%E@@@@@(r rJi&?q:6ll ɆP6 KVټyꪫlΠA\8 ewW[eАCEǖx0j(wgrymY=XlY’oM8p#bf(dK,-+W.\tE!_fΜiuc?fh LdI^@[.f      @I8蠃p`ذaqxiw9ny^ߨpQg|׮]| ЧOju4#[0[v M 74_*|pM7n؝tI6=w\{֭[z34$JbzG2!@C&i@@@@@(BI&N;~j{apBڵ]weuzױcGV_~N|d(ai8#\s56[CGnU+[/iCPhZSYh;]qGq=zpUTqWP aO{.Vy 0iӦ24(@DCq9ke]߷o_;، kْ-[F` YȬ'!     .0|ᢌ~^ËvuWuPNjUj׮8VLxkC+$,v (PpPaVX?(6 gTհ:㓕u"`x|nĈqh8 B(0EO?mޮ2NO81+ {f&_[/لMTZjd2ȏ@ϰ:      R`ƍ~psʕs{キYfƎeŊnΜ9^&M\j2v }[p^$ \xnȑN2 ).]Ԇ[Xb7RC x7ǽ_j ?zqydCl&      _\-¬Yl4~z{0 ^PC@<(Ľޛ*@ XK4      Pxܹ[|y-xAxi&ecǎe3@@2'@C,i @@@@U+p_~eTv7~0aBlҥKw5iu|e&^7vds̯Zjhf,Yڵm[b޼UnЩYpT\.GN+VvC}>g7}vb nݺm]͚5ݱv8V[@@ ?%?+     @^N?|زi믿v5JzȺND   \     @! nՑ[fwS{f̘vygwWuֹٽnVgnmk/_sW]5#FTQuteQK:ulڬY3wqu裏\ǎcAå؛7onN*$L_ނv;;0tmTViHp߭ކSs=mxX֮]5kSW 帆1P@@ {seo     o~\eE8j${'BelPfLwy)@C6,]4cuԁ2l0{嗻6mڸ Ν;3<)[nqoڴ2H.cuzrڵsꌾ:-;wYPٵms:Y;5jT-R|lO\y6yYi>f"Ku藴`C=kS6sz'Z0СCƍcĴi,8@m{n} @eڵС;#򄋆lu|A=z5NkK.92kXN:,;w~;s,K/45  P0ak@@@@@($Unyib͚5Vrʮ\ryY5iSN9Ş_x{WgW[ܠO?-S19NAِQ|w7puJ;ֶym5j;-a RbF^= #XUlٳMk$Vٮ+Tq\`7AFtWZeCBѴiSwg8e5kj;h"7eQ&ͨh#<ҦO: )H2b~ᄈ.?l?<8ݻz! Q@2/@CMi@@@@H[@JWutMiPp뭷:sLƆ(P |}0(k?W^i*ʺ')} *@ *hkls lڷo9etH'0clYvǶfx6-[u,S ʔ)~q`?!bCg ܺOAG};\޽- :Sԩ{睆De=zM Kˇ~h~ހlO>}>` /B)xAJ*eAhZǺ|rM1UOWѐ(?knhzV5x ;A 4ˮgvK^[\uhH eO*Uy\2rHwZ6XP߂qt=@Gu轮!eQ{*wyg\I,>Eǟmm۶0aM+"V͚5ݵ^k|@Dx;={ ^вN;ͪ\R@@ ;d`Ȏ+"     ::Q믻Usx'~Eŏ?=Q_ڵkݻ;=I'ݕ>_WeG-*L?SfXQC ީ*Yo*ʠBl:ĵNv xܹ+vJV׭5;STjĢoAE6$ˠAzܹzk{gaÆ6?bB<|MrDvkժUilu/ V}c2hȉtJխںuҩN@ȇ @c@@@@@S*cƌq5J#8{NCM+!/eĉV]CԪU˦5kfJ^[L)/3ĉ]+ [3D69o*{ZrR6mKB? o֯_y3 2eʔW\c⢋.rcǎu=-zUJTٳm:^&M³F@" 4    l_֭oU2*UjGҥK0Beˠ-Z͛'o {_kwUW뮻vsMO0r-nÆ e'VX7?ٛM0sa YY%˷g}5%qˇd(k?G\qcVng|`IDewt}Ν;; W_0%gulY>=9ѪNwnwW5_:??6 @(Z(Z    ۑO?d:>n){&:Wn_!ΫcXMznժU'gX {ュZCH 4=N d裏m HoMc$l׶֧שA'jgSNiW^ڦ[np Gx:^Iy_BwN:oѽ[v|7nC3n?Pf>w޶Gk/_~CݭjCKtsI'~=VC6V @(Z֟#    v$ac6U TeYX zl; TXpaZ.{bŊq]qn4P% a6q+Fi׮A,7bDkWJZ[g+W&J=snV{wͨatTݒ2O}]wZuA RڵkܡO-eԩn͚5lz; Q~D 6uQ0}t믿tM(S_9X.s^ʼA@ ">Uh@@@@@$ (hAOЫ3Y:o67tP7`h"h_ Nx:ƍ]ݺu`ËӚ7ȑ3 9.uRUZjkQY%jβe]ڵ]z\^ACNkw6B@ ^PVW7˗/)QqƮ\rQ՘ E(@"g     @q={v,BϞ=eZrZ ͚5*9qG s \nM]Ӧ5rˌ?+v5kVrvX12uzUܞ{;0իKU" @ DF@@@@Ȏ?%\r#J5e,.޽kѢ={|miڴESm]DTZ>_Rʰ3#̟?}'6,M8)p ۇ@-A>D@@@@ȏƍm5 l޼tnm޼%ؗ.[6/6m>ؾUV 6Y/j) ۺ b@@@@@@@vp        P07[C@@@@@@@"P       +@Cz5@@@@@@@ !Y        P07[C@@@@@@@"P       +@Cz5@@@@@@@ !Y        P07[C@@@@@@@"P       +@Cz5@@@@ۻ0)"7! x G( j(QQ#AQ})T>o_"ٝȷgOwyRo   B        @@@@@@@@ F@@@@@@@@p`(\o       10ĠP       +@Czs4@@@@@@@(SG     @8qoL˖-M͋Hvc׮]x35,_tԨQHk~nՊ}qyva&++POtT\9r>;@/r(v     0vX׿5TR&'' ܷk.!(`СG53УG3eʔpK/dԩ㖵wy'hSD SlYSbE~wԬY3X/ ~t:m:a•.>tyIf%f⍮jlsMϞ㎫c{yn/¶m̫jFi/^tGΝ;{'zۛ;34Əo^Ewޱ̙c}u^m+-[fz)oSUix~}?4h`qcǎ/桇rϸ`9ry뭷_|af͚YLrիWs +µӱ>S֭3\r PqRi|'n>sr?[l1/;w}g6nhƍT. z'>̛7ϭ~Sڴic.BӤI&)7lmk%klo*w-w6YYr4x/#GIkʔ[3{c}wV~2~>3m0@z .m}u>S_`yR_aVچ^~w&[n E:l]عsg&,YO}QV gN萰yqe lC\3W$"z}^N9o`x;wueOe `@@@@@o+AMkUV zCzȐ!v7GwڴvJ\;k;M7ݔ?J޵S^ h;kcA\WF0krv? oo[]t30؁*=M;{vq6pwove65{Ovu>/uU>~U(Ӂ i۷om 78{e wynT.W^y[32.!X8{Lmu<;>\² ;mJP|xPTYk ꓝµ 3<?rm 'O_th?ӳ虶S߸mA]tA}o@>FۇJ3m4&z*7_@/@_      P@cЍo6X… ]'ߥK7ǻ36~ȦB 3poxkjժ;{ ܷ'7^ПVכwuk}}yNGTz뭦^zv}ʕ+?6.e9r;?~|G~饩mLޮrRmJ{?|r sI5[̈s«Zkܵk׮]ZiTX1s{FD7nUmڴ)}W7myD<71 ʐb]eP;cnwr;'^T|Ugٲeѕ<[>;;;}?zeT+#[_lѳrwEX+;wltG&) 8S~K=Txq_`W_}m&/ "&rx     4/˗3s;}gͨQ*OKA_&(ʽ {TJ@c[jJbZ ~Ѐz!^*=9ұcG78yo޼<mkzM%Qԋ4p՚ yj8pk{饗MpW>}?>(@ڷHl5Tޡ7p((UGtƍ W,~_Mߡ0&NV|֬Y. 4ʫ~)x@M*E?7N%\ԩ|]t}2ে:t5Y"-W$-=.{]b@p`(g    +裏m@Л6/3*l{KEO(@ qvj4Aye =N*ӧO7/vJ*.'nʵ?S.ٳ\.8HT*:(Á*weT2e뗶5gAQPWxMO>l'@rǺ F*= dtu\(bҥزcw?5@ O³H     @ 0uT7 wnz j 6|hժU[ݚBE))~x4OhTz,$UErz*joڴfgKv<|ůMNN`}tAuwիWwZhL}߰awu)Pf6X=g7 2X$>@O}v:FQѱEgzД ѡC K|?>aSYpWeY~+"Z BEA  wS-?c-]{^Ǩ%?>ÂQʆ &$l-[[ʮlx;L;a;S(@I3 (PV Рi޼Yn]veɒ%F\Կ>;vwz]cwF% A(~Џ@@@@@4h@17 g?soz'5NQ M)VQ{ \-z=͛77'O5_Z7Uy:e˖Y 2ڶjl; juΰ}zM 'NtSU|i߳ʸ*{γM. (P :j7p D@x =aVVQ:5E7i*ХKU,:lS|FY)z^jвe<7۵kkρƶW2DKɒ%e~[ݵ uYk((B!T> )u=Pc?  C@@@@o9ު jr/_O7M'ԯ_?Ϙ1c>3e={y7 pn̹瞛慺~̙FUoׁJJ&mȯ>={.4Hʢ!-Z qQôim{"l~j)7ߌkTnMpM7uM;OM*1b PPD^=ʒr嗻][߂1.,)'H">#l=/ňw[(pAE^z[[&W{տJ+-y]ׂ Ngɞ@@@@@ ({;Eև|&eM#< ^h+CàA*Wj6(-[T(}>aS-[Ҧ̘=ۺuu;FA2РN:nΝ;ݴLUS0ԬY3h)_Tڠ.kZ(ۊ4FAc=f4hNE(!=b2g3n8p0s̫~* V₧|p] 8󌿖J?5@ Wh     A83ݔJ׮AMiSN*;+“O>jz O (]a\>} *O j;׌|-2{6'|YxiԨ9r)^xZ}Uk7o:v<ٶmvGV+nkӠAE;Hb6:m?M믁cǚr~0ʈMAEAAErU5eQTAN83|pݭ?*YfnY:Yiժ)]t->#JԿY0`wMWJ*e.RO PI8Fό{9CgEk6O=Y~}t9 |Q& CvS_QD8/ŕ}q@_b     zSgqS!˽QݲeK}np =iT4ܤI7@7yӡC7h8dsUW6ڟm7nڇбcGӣG7}ҥMU~3m4W{xAXzo߾n5EB(fg6sС~K7{3S]{)_d_:wn]w 8?/n[U: v!U1ʚejk? zz+/ W9k,{6m0a}C8,>ˁ]"3̉'̜uYiOPZSդ* BPpСCP _eaߵ/eQm虺_ݺu2a8ŗpօaeTt8~CWxgih]_,unw@ Czs4@@@@@ vXzlbRO jYՀ4ا ڽnz-h޿|57^`?l@e4P[ *xAp?Oi*`ڨ/PZL Z[3fx |P/Ҵ<7 !8wΝ{l;$XYv|6I4|=F}Yx[_ߧJl_ڭo|.GBT'{>xAjM6rnWݫt*ݺusS,*@_~n]^Aj[rWA [~[=*W_}L(O?>h_,+(o5*UM\ AϞ=lU!;xg^(4Mmo> e_P/5n?ŭ@ d٨Uc7@@@@@BXdQ5j4Ah JyMy5P 1]JwM7OQJپ}i%;ȺҜ~>>}& /<֌q~Pށ˗evo\֬Ych MY4دS~je.Bvʈloڴy+ C dɼ3ײ~t4EŊMՃ PP+˄otk=  !3@@@@@' NjL֯ٷ76ܼxFw2vиO@y ]v4h\R('i94}M-̤I  @PQV(]      o uV-;FSbwx˖KGm,@Sm۶ٵ!C'Y!G@@@@?+zw8uZwص03xL;%EWW%xl 0_BN@@@@@ vq]2Ŋe;}  @ ^       &@Cs       AEK       j0jWE@@@@@@@ P/ ]B@@@@@@@P P/       EP"xQ        |@@@@@@@(ŋ`     ?-֤Iŋ3O<1\6ljޙ>=FrcMrpaR}2 WO5k>8S#GOfؑT-iFMT_tisNÆIl?>lGS-Y'ZY3Z:Y?\vcLre8´m8~ ٳVlZ$t~ٲ19+&4kYqcR}&MLOr#G(a.l4Rk͛̚3f$]iUnR+7'7U[jRgs皥%՟w|vvRo1;vJ?,+\ҢEBlܶͼwIʖ5g֯TêU ׬iըT?q|340U˔IfI]m߳9v{#FKkr%pժ>:ZmhVrjz7klIY3SxQnnIm,Y\p I?{c{GKJIkGtl|gh}g*Z>{+3-Z>2}=p9ܞ\e5yDKMkx9?h,Z65j`UL7,^6ZmȊ2ZFm5\tgu lhb7˘q}Fe}g*Zΰ^u F;Y`hҼ)vX{;w6þ:Ԕ+UʜߨQRgs$׫RŴ8昤K˗'՟bk`#WhhSKhmg7;ZVmd>93ZmO̰f-:Oo|j]YhikZhjwYGL $rd'@@@@@@@Y,QvRp        )20dJ"       @ @C@       dJLs\@@@@@@@`(X@@@@@@@@L )y                 )2%q@@@@@@@@  !`@@@@@@@2%@C9.       0,      eIDAT  @`Ȕg"S#^^- 4quNm-cMLt{DޝGɊ/;NOPYJv8$$ǠWjN0`裣oY>.s:?7.8oogE^o_թ1ГH)(( l|yxzKBܪ,qc?vtAcX p€F}άC]#GT+,,_/ugGf'Lї3&ͻZ~]ldFw '*OE)0`GG߀s{Z䍳=J}l} %!!Ao.Gn\<ȉxꕋї^C0ng|tI^d>=1d  }t 8;8w&ƆW!Зq's3cN8{+Np/P'  pֿ/++MqK9q6GU֯"H!nk'iTU887~*NkQq0`}'WOb=3*Jpmla*V~ޑY[BMΖU|&mv חp€w< |N8z%M 0e_?FG<׋{eOHćXz8a[Wzh"nؗMng' 0`t88a[W &L0a~L0a„-& &L0ava0a„ f& &L0ava 8wf=>4{Y0a~bX* ai$,`GXNÎaGűX$!eJcX&;&Dz,E,KVX*cX`uzq}!v;Nc'M|S,;eN++ B{v;3;`EX `vnv;{aCay_+]..`Xi0V6+]]]CKKK˓S+Ӱ+ӱ+0 Z8vmvm6V181iBEHzb%KeXr JF jkkحح 6bۄu kV ڎm'.z7Vއޏ9Ijb5IXM2Va`ұ{GLV`ry|)Q{|{|,9I1<{z{Zq{v {v{v*gGO؋_7W^^Wconco`ojjb:!kkx{ {'9+؇7؇ǷwX{oGScǙ-EKBٿ-;(O% X#F'8N8Np>wA+8K8;#p'8o]q8N>>?h]l~X&&wlUNW M"X *ɐ]U^8373QdFz|\p緃3s4/,g!eycAv*-yo5'™E ;1?o:4ӕFߕy&}3+Z fT pM3x)}gI膁b@&/sd$X>I` 'ɫ&:2΅}Ȭ) 'Qqi+t8a{g:,H¶7 8G8Lj gݩ0^^ENBpvr+Dʼns' p 8_huvb;Xi7!D/?@a97?2k v=j\|sTOMVvâyĩqLwIq%wf p8?Uk)^0Ǘ8U7P8U+-&<~b"l8^DqfsdZEJ$\~ZnN$H}v\O2ozrv03Tmp{Myә=Ny8{F.UNrsKƍqנ8n8P%┿1zT?'m($!A~CM ^E8_[`S'L8Ll(I̐DˋRv_x'*N8 'TPq' p' p' p' pgOO8N8N8X0L;٫d 'G(CI8"ɑ'di ŇI~,*/p}n~8\Qsp z翅q(>ZqiK$,f|*8te 8NM8q?ϛvD{V w"ߎY#rIuf)\ 秡t{;iʬeܚ {gnI_Y 1)(hg?p9UఅW}fX4 *I,[bt ?TmW἟?e ~V!~U<\G  i>U{g>zc"s:E#dl(Td}Gjߟ!O" 2թY+ ;XE~}="H]MBcg`Ŋ+K'`i(Q{KG0$x٧j 3 iPtT&\8^qɽ^xFӄsgr3y$\+>ybq > pVZޯKg_?b!==8{|=gچ-^_YqlM k8gBBSQV$<'A8FBSQV4|8N.p mzR-\r8NoL8N8N8ۄñ.Tw'P!~P8{οKFuWN;hˣG2V_; pՎ їG~O'X#>v꿇SE-S_{thB!E?Ł2I|7KrBx8o٫\>V/? pGp*9C8N3G "()^?IwWf8N&C0_@*̛!&]-|(ӂzBso : ۛcdeE)|bwM1(QʎFbT_b7))R=9cӢh:Hpm:ɐQ ((8|,E*it7;8dwѪ/dSK&j26֯=8qSWl gb?$hg^%*Ü U rdP©qaKL]Ý D$-5CK =dTں/=θIb ~PFy7GTR/JF)ݯNI\ޞXĆ&o\&2GNYF-Kn%'S5mθp\?vUg*㙁/v(gD t睜qKA4+j~ yU~j-5\:7GY+ˉR=^5s=ՕPăoQiu#8Qaގ,]*E8uLΥZ#:|aKWp>Hrw6o ֘P<73:SANzyDoo1'ᤋ}ߨZS(5^њ+a.ܘK Gk#n!Z|u|p9 JcA3t -& QҜ|-%kٽ:kexoKE_"=4v[ gW+ΤR%k5wr+8KkqyJ]Τ%MS?8Kv $I1Cz3wd7;3}Vsڃ3iL2RCu^U|t5"󨽔pٽKQRn&f!.[ڒ5RD4O g*uI<3kD)|os%|*Τq殱t1 P~Y͑p.=x/¹hIf!ʏOM ) 仕13=v \'+LZ=$q_gǸNqfgf2zL4=8"l}~⌵T Y{9pBL }*<Ro{{ =L^Cy5/ϳ,Ƕɵ⼗w %MGs™$Ѩ?oApZ1ZqJᄊoq81KU{Rp393'L$2G;tKql{eAE_uaxSB⼗94'gv 08:@]rtkyi OOGﳳ6.Dž#\Xl8!W0r!jڝaJ%Hɛwί8aWÉXQ,(UU3lfXkc-oTcqeĻ֭8J{YIUwN].{3oɕU8q9ŵpɻ׎{s}3JќU{N.L8F8 IE)Rbϛ#*()ޥRT&봅e92h(7cFqbW0ϳVIY*rfawgP^6r8ݭqz+ 6`|W:$O|89{1=Sdgκ^Ug_[~%-EO1,L9uHUnH _%x΄~up7M'Ƿ]mmMO"?qp>+FIJm+Nc&t59!a:m+N뼁*jr$hvi' S>J2d TSEWSY\;8䌷7Q Kt81TCY$,Q9x+Z6_Gm9UU8_90d ~VGCqIWUY=cE5I<(Y2D_FX8;Zkʼn>|aV_qٖ#ddg4= JSSzѬV9C\DńF18ҽ^:*bxetb)H>]?r"Cҍqe%)|VGYK#۩)JPH,#mc:yͭ}5T+x\.>UU8_MD_ix8煎% swd=8㷮j@ U]DJqQ*|p V-8O$ƒqv%NoÙs~UGnØ> ' pZwߎ]?)C{76+Iq^:}g7zۣrR8Y{ȎK(/i-y[y8Kᄊ' p' p'+' p' pg_eb8 pgKz,SM .ߵDdPNf|tOٍ8=Rqi$l ggTZ~ϓrpԔIc7SQVپowߏ3pJ',sp6]vT' p~'p+&&wnU qs616|a7pg3Q3dBvԊm:DҞ;HL+N)uiurQ̘"QTRW'=‡1d1oտN) ,K x(="lp AzsaFx2&JW{r:vֶ-n-tgȋLU*.>)]i*-AJ,'SG1:J["*[8-kȶ3n $dgW)۞a8JS_-jHGk&BJݳs=7LJRW.FwW;*EWKmkl$~;e51ы#t?ZZh"C\ƌ `ifD\jۖ5\URs8׿Oٹݖ^fQPܤk`?}tED#|ȚsUMCVX?So;)s4wNh;#kxAL q&T@ʁOGV'xg 0#8N --N LWsG3EDuiI`wCiqeɟeùf~6p- ligY%u;ڔvZY\mjgZ*?;[3{gb#몽Z<47iE %t4{x~SRp%ʶ|?3^H倫K'Y8[(I}4mUk&>olcBN?-ʛwMpmihds˝Xl8%.g.3GFRSYU~h^.b/׊I(aQ=NucHI|zv ǖe7iuNZ!8?6dWj2$8;PWEک89sqS&Jgmeq.B߻|34~n >p~F֜pz[Kg"n&NRëT{tmN ΤQ'K,CEg FsG:{(s8M=lUr?cS^۳#o[ќahNj_gUኦv n,ψYs8Yexp=Φvҗ6WY0FFJTtlig~AKF5)¼;9j$oQ{X3Qxsz~îKuza7xa$޶:yh{K၆OWTDRVh՝įE,4V̧Q*'pH,S mpНn=M[ 87F'jWġmRmMN Y掗j1BBpn\ND"G_}-O]u1}eRRhzz TP|LVAbO˖hKw}?"z Ғ0Ӎ+N7QfF֜pKCzЂsHãRm!M :8K ڞ,Θ)&R lހ+KZ5?5S'Fpl#kN8o4CZ=Β-\yp/4gi#$Đ3igNw۔8%85I 8+v8LbvPqĻΞKS)CFkO3 s?жb? ku8kiTk+V.Zq~5{Y(K4r}uXuξ^qrV{Վbb6~d(@7pr}Wu{wlhK:SqB8+{vxlNBpFZq&/8es8-dZUNL|y`8هۛq(Mg g{ cT#da&Uys;e`({jFq[4q;HIȶ{T+(c^6F\_;y^F*ի(ʾ!>JΏTmxSGSy0sw>սժ&_u%e[YgL$U,Mw@GV{z=/uВ0 ;9iC"8Lpcmaw85U-Y {$%%n^zx@if<_8ݺTAnRb$iqh? |yaNh&!Jrو!Rdiqh/?., 8OCEogkşY% q2ongt <+qv%Nq9u]ѓ+u8K,{ՕzjOq g7xQ].;6\8w HV73mwdF|ٍ߲?8 L߶yjp>] MEyi^pviV@;*bdWxA%\B:Dg7z;6 /p8{' pI}b' p' p' p' p'Ɇe|过SEArL]t5@!wq68Wg_'ˀpΞi)0_g4ӕMX>[W8TwqVgyvL.X8k'/qzqi&,d{8t8@ p΄uÆ)ހ༖r οg'L &qz8NR!/FF+] * h@ƻ[/P|-,-(J=h@$Rl<<B+* ( Kb$1ؙ.-&(#&8vŸ15hQkq.e̠S|vFRst3upͳcȉ+RQk~ r(WM-mֱ͔~KLmzԁNj"!-TAD疸;;ueFWl^{]3Y:t#\ٯ*NTVD&q-YN&C ʟ/DI'Jhs ks}4=-"$^]%&H 8$NIĹ.:f9M+GNYFoU͒" bId!>;C=@'9sTuj[8f3xf2CG_)_JH]ƹTԒdd,BR(@p:SWMj!Y8NoK/3J-#ޮg|ߥZEj0'cNn<-|֏;4-Zq:2=XRdK 'zbߪCn;q$U7k(^1ƔkšRvpǟZq"8.N"&|/kQlMA&OV9mi8v$6iE?3/mK ']lcxkE[XU~6GDM1^<ܖ$E>msMpI2c3_Dxڱ* ohgސRbΪxfw5*eDp%e>^kljT.'8;&^FGN[cŠ!zO91@8gYۋޥj3FXީKByK,60*wZۉ U tj [퉥2pL%jk WեښQ@ejPKM-BpnNyR>OŕDþgºÆ.^?1jJ/g~T gW= 8cFKu&An[XocޫE,՞Y1uI j [ԤXS*Y˥ښax؛ԷTnps܁Rxf ª9RmѾ1'8;_qEz4W$Vmsk/0QPy;mim,dnl(O.#LZl]kfI7T=܍nf2!zjΤƟUÏw N"ZU[3'=gW+ΤY%?йDuBeRg,٨yιnfZ! mz3w@7d$LZ1 ͝Z,=p*ij˓TuŧS֛tD8-[WVpk4/KZUN}ѕ->brj 8-2Bl8% WP0t0~ԲΒfq,d9F:Y,~7gZupF+|%a(ZގsF}:D6V'j8K8l˂\KJ>Vwǐ:zv׶"{ar7jBвc#Œ:`Vv[wƑ$w\ Opzȳ{[#T;?*qٳs >\upb#`/|yX)]DI_;T-'3#M}q)KՉ')Ɋ XgYXGC}|1ș.%*(-&8zك7v"$ZcA˦δP% 36x*=Ð߶*FOɬ_U+f8_D6^,O}pPݜ˩; Ts V59aUH }Tq?50v$TZiB,ՖnQ6Sjdžh(aהȊu&Jz騈m9UU81?U*~ <Nkp•s'L;sfw8E grPW?ybG88δ3ƽz|Mvp=pdlS 4dX'@wcU.> py4=n6 u'ΠN<|v~N8MXQ>' pBw>l\Mt}g3l g;\r8Olhkg~u=8N8Bw&8N8TyC TnR-ɆqnS˄']Ipe '1 dnj8)ҏL0G?L["aT}9 gg4ӑNX4WHX' pg' p~prmd^ #(lo2debwmd!^;ъfm\3YK]θp#ܿz+3E՗Bp25ģ9Щ$UNKLM N "dA-eDOqU[ڪJn]x6 p~ӹ8rgGD_5f(F^,ˠmko3!G]5S9g i |s~ZxS."JS{|R[8ׄTWo g"YXT"}x7RF1gGMuuРR7\e(Fz8YiPTe Ipq@#tc=gk#k=V9&1y&SVUMۖo5I8R\;vZq"8钤/Dp*ө/L [^e<- 7@8{v8{s#k6LN6בlUq~,c0 EڡN\+N߁6*5!·mf-xmng +MpmyiVNHY꬏77Dp*K\Ό#f$[3͠VOs~%SpFY7zbH$1pۨ)vIWjkߘR-3f)A΍mgPbhO pYN/$χX=u6nUZ[8N_ODblE2}\gjkgSHftTpsc"+a.n> ۉڢCQg7nΒumBE]f.nI q_e=5nxjAԾ=8Wp6\ĻfVg/;J`q֞pdFR{>l8W;hIPHldY׏=ml5C|ͮglΤQa􆛻0g^D% 8k/l88-4?yQ{ 't8/y%pnk8uN8^;a#Vg|?:o]UɗfeaSE ,@8]ڡP(dA8YQv'+~}\:!*|cVa["fLuQjL */u {v,#c Qvj=xyyjz9>B[Y"JQO`O[kwooj-drԒdhB% x~8EF2ϲ%Drs9ؙRH겗ްp(*<9m\1\]Y-q2$!; (KCmٯeS t6RH"9js8ٚPZJ雈=jRݺv>?B+cw:ڱS'|R[rf$Zzۧ4]6Ȼ$U u h,R&4 d i(F4 o|rќah,<p2į?K?/p)]7pV݀7X[Jy)\+N']F,gǺ8 OW9YFJr[QUw*-M;gV.iF=#}i'4ܚ B_ךn.Rs@Ud}vK7pWb!Ѷ gU3ޚ~}]#Vͭ,j5I1-e$Hp2k===NK}: pX#kN8kM*)q/4cbmZ]κY ;D[gMJU%(]udžOG,6Q*S|ngt(b̎]3f@bpsY87-J$P~ZG,ՎjhN.֔l*Oxp,A΍+B翝DAeNo]U@D({Lx~s\-rw^ gY)!QX=1uZxu&x]z1Ka.w'm%i;T[7NpxoM?Ř 5,+byɐcM2g.prrnd3 W[UۃxeS6ɄdQ۴0 8" ™F&Q3x?Β\jk. Y3p&&,>0P=P`"Qi($ADY:Ǚ/́iftL;p6WX}%8g'BbGTp8n8WIQy{{;$VKq"8櫱o,,,*y]r1 P~=8ڶgr'Ά)cc[]7 8{ ęr42o}vg*΢X={)xL붇xGAc5zaj{ֵ'[jUqrř0Ί^\gwIjMK2F%WBY`j3ykDq}VgGũ!'-OYAy/A Oh~A꜇D7LQէ- TA>M}xk™ pB pR8 WEWQy8w' u#w-Z8}l$$,O_ac+spb}#Gk;K.uaqPUJ?|e򻒱إ F#\TsXWÉU.s0Srzty^U8q&/ V]]uJ0RPY̙"dma=NaC~TZM7ڵ-TڵɞR a0#Cd.Dٷ(Yh˄a&I$e fJݚBu~>麝syW`1^n7]_ݬJcTUV eAnnjS[疩͵5l Xq̈sgÁk8]ۥ雿<i$2Kc8_0 mBPUohk2UA[:5.?֑AֿT}88lH?D(OEl֔$b+-;8[f8 m!Pb+b&63QR4JY5{-eAh/''+U[]qݹ,47nք^NSF{4)+nZ8tWѴ=|ܬ*J͚X?g5>|AoVS|iXKNMEfxG[;M2wQUk9+N[i!Z>-y|% =fT^'G,xwMi+h&8zw70QR wo`R8猰6S)/΅u"auv?gp˶XkQqښ72w-8*ĩ#;k*ժ/[DwOx'NC-Ŝ%NOD6RsC߹/3= :TQv=}suGFUΨs 6Dw[qvokA/k8^:wzUę/Yz㸒?eo9؊SGmS_'4lVoseH Nr8 _VJ `Ũ&iӴ8m Z5|Z*wqi(gӝ-jɽ[ajsK̚*m havi߻}VLJݪS86weV^֩$Ovod_k'N哫 +3IiIԸўZo1tlu/3 }MS⬞8ur*\\gՆxwڅ6m"ΫۺO7U{hQ] #;Zou…aU骽vpIwRqFO!vrgܴ:P^b+\X^HqB7V2Oo_[`s2bj/.6f.$sVqCuٮڇ[[0w.eF^762?k]쪽=@EQB]Tab'QqEx]32ŁQ#+(sZgrܰg jcXq:Yf暚)|au]I$A%`V>ڏɪ88QqB'Nbe|{1N_cbIڻYTe8 7y7l<ZaTEqS[j5O4Yq|'GZP/giD_'c|,v|/tk+|~fZU83V>?c qkcΝGv-#{O'㬇|1NGEO+ˋum4X~ZIwc*1N pU2)Rd\SN-5R`Iwlv9Qʉ8ZkE<ԥc FXܙ 4mO'u[K=qB$;!*e_o,JvV@1v!=A8W6vP[ HJ0>YY~Ynd][c'g֚ ;IքPfj*J\ZZ\Y/.Dv53PRld+KY2)l|YgQmZyJB3o01o')|Yq>t/@CV'3ӻYwx]>7W.ά8!UΪf-h$/Ւ?Hv{ 7TT7ѕUKřUhcofվ eoҵ %ƇZ'4Rc.j+΢;7 MNh\P N⬅Gj.YygzLAcAȽŽq'qn2>IJܥ66Ja'sү䏭AĹ9z<9ua6fZa_9B'`K`/uZhkȇ86>-4U8?;=89$N,+Q Nk# N88!N8!N8!N8!NSyn% @}xR/dްT"ĉ'*N e)*NT8!N8!N8!N8!N8!N8!N8!N8!N8!N8!N88!N8!N8!N8!N8!N΁8!N8!N8!N΋NqSă\p:āH!κbewu< @ >.u< @ Hqp .q2 AsA N$hn8ɀM8  'Cշ6bXh^qpft$:qp .q2 AsA N$hn8ɀM8  '47A\d(FE4(&}-at8ɀ'pA N$hn8ɀM8  '47A\d@& @ Hqp .q2 AsA N$hn8ɀM8  '47A\d@& @ Hqp .q2 AsA N$hn8ɀM8  '47A\Didhp:`UމɇFA\DĩTtB4>w߼G?ą~Y=6 iWVVjC?w;/ktj:R]q=X26Erk>WX@79\6@V pN,&ƆMf}i[DŊ/GTUʦwsd5f5LY?;'衃vSt]<#iUaC$"AWWbpE4vY˞?{Ȁ9.ٮWr١{KsE+;?P]i222 6xygUTNe9dP_JU&tcQFbpEB6kf耬[ӭQPRq9'<)}YbW݊~Λ*9 e94?~ps\]r4%%A[8YyxgwߌC\ Wy6p&y~5vLŊӡ횄xQbF]9[f4FyV\x2?L9$%.[іGRtu)Wt`Ŋ֗sg{ſ]?|0 pE7V9| MbŹck^S݆ U[|MsLJϪۻeȠ4MYb+Vߜ2y<-1iD\YCKSVVXZ8P~&bgov#JfAF\ROy]e_4ݲ11_^JJڥ(ANZ7A\ 2(A-t{[}qp+W~}>8N'>Rב [|2Z30h_k ..SI4)Mו? kq݀4A@\xBhB  wq:-pM8!Υ!uoڜ~ݨ}j$C\$:OLpl% d٢BdloBvh:dgSOR .cI5#$MHX=6$%kG"~grd&9ԆjK'G:.$+9Hu'{A$+B?9ٗONA!$gA~ #G3cəql97:FSoɅb4ri.#}c#-&W$%!֒kIp=\Lnl%E͝.RJn[AaQrw;Ig9䯳Ey;yG ȿ &yv!%CGOɫgsͫL+G6ɓ͊d!V'5Q%)z$ŀ2"L.,.hKǚ څp#$pw G:'ȱnxrtmSH@=㸏"ǐ3C?N!科F;rqm6m'|B-XM ɵuZǧ6RL!7w4rkOng||2'S oOkiyZL~{V%}|b<%JZ>7D 9>Y8ggj$8%Nvn @WO瞧 8qڹ$q~8eed4hp)+ Y')+ӐTZ? N&9HG]y病kTI 8 NFۉlвq:XjUq*@8v5B~5~@mzZWE3mn>/#΢zwuk-PSUmcwt!q8 uUQAϲQq kTW0Kˊ=ئ98gb+Z; Ģ˝'hO#;̋e~#+8E,9-lE_/m Z;8K.0ёi<9hѥ$k,.40(3l[A%?GX3[;/uu&QvHۉ#,&+pf{wgMZ9Jq:I N@uV74yiqN V$Δ􅅱fE];/Z2gBL 8&ΔL( JwƄV l̈́ofHg%2ekksOJ)X]i0@R8y5ߔs}ݘ'`8oTqΛO_w3H4(׾ qrM32klPZ& `8o1#s:r,0WΙ7 @R|7PLDmN{t̊˹9&q\T^'U$NvrsƴntfL4brJ}Hkq#i$V[-:&~4rvpkDzq^N7D"NT8Qq@"8!8& NƁ8!N q Ni8k@⼓¬u?\S[A'_3ŪI$gӥe¼!"%78]l֨8]lN8M$Lwde\ l8ͣТYbhT"qYķ*NS8^q:4O\ *N q8' 4!'!Bc9?\Lz{&_Y҈x|]E+Xkzޝ5JZ::͞ZS{;7TZs"6)/΅6PVp8M3fۇgoAike2%<ۭ%OEiijmͧM)[رg<f?d~-N[)-x<%MQj7:e- U +i,|5h- ⼨8 & ƺ|eE9;ݳASOpE?V7}M?m;ex;o'S¦Њj|djU^ jluv<gpleEikm6e|wk{$I]֦Gx8,qr '#Zv-]n{ۛ^l!}7Սm,Sqzj־E} xĩ;nЫe*N˘HN6kkT ;G[qRqh NU|'NOOFO0. wZNLgGz"NAq_;tV Ѻó`qiqj+,~=ZKDي3oKFr2Y+JN ^#ݧSGrcKqqg^"$9`GvO#{88uܛnv1N*NC}ݜ#[͍6BKxb埝!N>ghԯdU:KUYBdwnJ[м)΂{ S]1g6U諾v% {3zRATqߏc^;F1El *6 &VtO_;q7ښ}2⼶#HEIƞU骭D:k'(+^8_yW-gb'Qq}Xt5ʿv޷3 qV<+,Sg̜砢(+,?ř,@NVF4Y=~8fTk3uN^66ƃzy"q&-Hf,C$\zfZh@ gu+ΤH%OIV4Yѣ8Ec%3Go_1ԟ:trwj*է3inOUfJ'o{f\\8LH+bXq5/ܿSqq36qXRaL*Sq.VNe*i+?ar뫉FH3iq&]uqJMY]q&GU+N'&'];U'FV$N3CVggŗ(59~*_in*N'eętFʼn/&n.5OZؾ}mh&uQ js/}vΖ 4~yr;ewc8+1ΌFv?2o$'ϥ54ctppۧ91Ό ӣ?kD4'?3[Ĉ^/}8ɿ\0 D $!n3]EEY!6l RץYTY}FdeYwt4PS(byȨOKcef(oki"͘Y;f MdEj+΢G;TފhKg'fVP%vs4;T"άeFr ]5_3L_UIAּIJo%]=[*+61Z:狂-1S2'IoٮڬB3C$y?,s6|>O,v ܻqH8JL߱FXKc/7³jǙA P}|dssOncce6}橖gX?I]bc 0887[}'w q9@-=BS_O'sY: Ю-$Nn$ =7!N>S)eòbXqB5 q M@gqBHz'sʾ痥ʼn!N ''bo;┲+iܡ'KYt1Jq*A~8> NƁ8!N )B j<~mu4t+͗]]Z7+74nxHd 15y_O\^sƵ55P//΅S++{8MI3It#)͘FSFwv(nZ2tWUi۽]8VoM7ZZLfv mjV^ ʊqsg!<4o際:"q&]8OfYRt; gK<%Q,A//L U)>OҸ8WC]qJBb'8'9&s8ZgrЪ'EI *G)+#ލqzi~?nӽF== :[i8y{mĈ_yvxU83V .?c ϬY}~!g@;_4qnN-0Y YX90jwF!9Tmm"Inus'D hè |fVG|Sͪ-[~V퓣#6SUQjb+kgվ8ofVtUeTWNVV4,:ڹ,47~fY Ys3c&t23lDnʢlWmqf̢gVWEg;0jfG2N>:Gĉgղ2}blj+3}\q Nkc8{|Yؐ`S޼<qBu.x u5Çxv# @#=,+G} NƁ8!N q Ni8k@8-_A}x}-j@8QqDʼn8k@8!8& NƁ8!N q Ni8k@8!8& NƁ8!N q Ni8k@8!8& NƁ8!N q Ni8k@8!8& NƁ8!N q Ni8k@8!8& NƁ8!N q Ni8k@8AS @%pB"+|!D&8 p'P N@@58jq T'P N@@5)qʗ'Zm'Z5ĉVqUAhhhhhhh'Z5ĉVqUAhhhhhhh'Z5Λz"eIENDB`astropy-pyvo-b70558c/docs/mivot/_images/mangoEpochPosition.png000066400000000000000000002070721510533647000245660ustar00rootroot00000000000000PNG  IHDR77fIDATx׹{t{s&a```````]&@@@@@@@@@հ`5 0^ q``z& OB*"gESQJE*1+6WŁYheTJ$[|RJU)r~V*Z=F<҉+ѻ2 zUžzK"ӻrKzUҟgUS^UgUSezT9q+zWemUSՄz;!z|mثރ>Sj]zUՈVKO53RՊo]zU+/=ՆV{O}!Wډwu|U@uNwBu!ET00H^āāāāāHHHIr+888c89efQ-$&Մ *cG8=HJ8=ϦC@8Y?-QeU5UQ*$JPeDUNT?g}yGT5DU:zOT~zHT~VGQ5U5U Q}YDFT_~vJTw}#N]D5!q8<z'pR)zaQ880āāHa!ߋַr `{?{A TIv|*H.88C!83BAi5\%3MyX.6v_IF.s7U [Vaaf޳asqi,j%!cZ6zL? Nyl2.vf,ܼ(qMxA;2NV7Cř=C;ұ$8I88͏CC&7A@qBq^` 33k qF8tJLÆo2fSPP:FLym'4e?@-+VJ},7l_9r|`@N$:@9EPx'Ԫ5m鯃7XU@2PEދC N[I fz$L6<@-k~/RW@)sHH&ڗ] o!q q@ \ډ+mNV6@wathVplYӦ9P2_\y|~c݆<: A !5MF`w,\yҬ(:Dedi`;8)A`)&7māāˁāāH) Sdu$P_CE?6 0уuӍ&O]N7:iBQ%΅CGS,RXLxTޢ,Ҭ J GO jhS "2X/ψ){}0I8}+/͆ˁQ W/A׋2~𑼔 yȔ8bCŁāāq_{^n-DީkV-Ky5\affW !q@8 w⴦N#CCMd.- C#NknttϏ844YoB>?kOnXԗ2uko=OBͅ`rO'CCC3FG)~t+( ZZЃv qo9Nbf~<0q$/ MLd 3Kq5my芪Zڜll`)GUfaf޽a"K9ؙ dd=5%w_߶%$ x!(0 U ).dggbʅ!^Q380 858CT67/\mM7l0X8?f_-Nܔ_9iƢܬC܋C--?NB0,[\$g\Zy98#HqϿq880'8E)(5} 8_+!S(ًX9k JJ@g7 IޗqUSU2 l+.+WVZmn[ߴ5S9+4\ qF488880#8= TтM%tϓA s YqRbw4$ݝ;&q'&ʃ{yii'rbɉcu2[)/đO]q"0޿%S>h 3āād qoY_=Ksm-!.ZWs x6=GZJGiO7]⠽8J p $$$$UU[ݛ8kHVT9ܽʾv, 8ћ/PyA#8u\n-uɉQ@@@@n5O(q(((Zik*߸~##xl%NH =kG^>EWT_ /'#xa$jz}nҎ:4BhKu=uaE7qCa ϲ qxyxy'C3~ ^^ 1~SrwJg/[[}/;|{^_MfoGC'xH(hXuQ*Tw|z->__ _Ҁ/kW6᫛5-V/v|W|k7nB〈8^Q $$$$Έgt3HU āāHHHH $ _&N*HH+8888CHbb26m[ZZ$DMT9֜KTy88P q q q q~7lllΝkllr劳[qorʬFAEM7wM!΍RYL٨1I *āās^ u8zzL&8z„ q%_āq.]tĉFsppL,ĉ~ꩾ@ J>7q=fg/88555;E<==m///aaaN6 `%55o&==}ҤI@;wlٲݑn:666۷ף޸ql]| .q9~FC?c#~zn_H3Ceǟfl۸U{q^uqN}a8ظ³ݷE}aEAIi|ŋ|fVэ>Svͯ]g_P\x BK!EL(((+잇!ْP<4}骍oG8Iv~'Xc5l!qMM᳋ZSn?MIIvD&qIW3c@@|,Z~rrr-,,Z[[INNFMKK^9̙3'NAaXXI`'x `mUUU UTTlKHH ---ɷW!iOհ"&eK޵xI>PE=.nfF²aĹdb6oU[iI2ڸiVV) BPT c. /Oݰx rix, gcT61ȧKVYT^ C]=ݾ#|r|y,qִ遮Oo.CIT/%~BDǁwR 3C/iiiYrѣG$$$|< &"ϳgYo߾퀀ɷVT-:S38k̷`1qV+Yw}v7NՇ8hh hEe4CZ8MR芪Ro?&0J:й8M[!na0:}'#o:);OuG4e!QA8r2v. t7l%dāā8[4u+rb)~) @]| .qT5NQ2Q[ĩg*pFsGbâ=48$sq6Yt- ϧ(-7<0>QDNmZȩ [VT.oCWT= ݼЋ܄/+7_ 3^;TT8_"P⸞KT_n0'p wpĉtzD~w.ĕ5eD: "]Ѧ ݩ )=^W ](G2ZB%FqChi\o`c(kBqA&-- gqԜĵYh(if [U LBnu΢M4aqU8+vVThnC8]Tn'oIm Xihhg7:2;8g<і';Dii9ˬ 3!⠽8^!' {qdE#J u%%e}DN7:y !QH)$ӍklSvX;d3ȹ845q)8/9Iy61 i欼JSg$pGF\? OnP5Nru_)((<*uq q qF3#7GIBJGAo0:Gk\r\eJ*7KDJM}ZCxB ?(HH^>47HVTl8z*j:J*/[?SUkOtcPMqq[Xgj~@pkZv"H=3?'88|&И7bi͌^=r]KJsruM8a2]ўW܇YXp7@%2,[a}*qSC@nZZZ/0D3Έs]FQ^&=;1E99{ q#NoZ5MI&l*b`T Ҝfito '!N#,5e"NNTpTk)(jI9`T^ =\qC[Q%)In dG;r, 3Oge[Gy/`dL^$~Ӗ\8,ZQF^tls UNr!-$M8ў>B3IVTDx)$ntUq*, q~qfJQQQ yĔĩ(7đĉ9޻KHI;9^)I= 8Uֶ,LLWzztѽC{IIC@ ;qm8AC@@ q|~zsx|s3w"(?q*kNh OGlу2b=nM߷{3/73v¢p8bs1ۥ0_ˈ@=Gi嚪Ź1(n-Oe`WI @>om|</*΋G#6WQX,㬙_>Yde$oh̏v~Af09)+XX7m\%.T>kĹx̌ȉsRa5~Y@q1++)222>~BW7W![KŚJoC?K352ڔespǚ3aߕ%` yTs2 MQj0.!.RFCCcaLN8z45U sb?0)!.%pO\sg|Czth},8cڣv}GYIAKsqeYvYqBeEM ԸwgOu~m qrhhh"ZcCًg88||qџ[-,β vwE28c8#tqHxqH.w.(+)$ |صWFv=cbu+?%Φ?;s :PU-Fne8*قv.9w *͏C^^Qu'fGc0 `xFEq2:PpĄMO FcyP\8uV׬8y)4t*#ZUy.JkWϣUue+@Uq[ 0@ Ź3}N7Ĺv*:Pl MSTA@@@@t>9߿J{mɉw8j/΃{WQ>&=J{,T9qtJV(qxcgk9S]MY7MkR_yq(qo܀{KNn8OkLРԄ8M5Uq(q\ڣimsS9CC] fMicqqy ?7<,0藍+g뗀!%CK89ᔔӍ97wًu8Ź1+RnRx !$?\WI>ݸ?HKIS]> r2(qdqeً3d7_]gb~n##-EBgdx3w''NCh!%HHHՕ}d*oY\oFWGM[S%'MS]f\(q&L ySm G\q@.URIk~*|y]n]\_寻Td,0ٿ(/"$E[Set?/8Ku4Mv}\C>GIQ^{FUy:GSC0s.N+cGe~= Msc βڦ&;ڛF 8:qD:?3\đ9\yl"7cܗLSI86q>դ06&a|jG9|`<)F p؛UAfo>axZPt1t/O uf&;{8p/NdKUW.`g8fD ͞\(lEUuESX7_SW]ioxbs_x>E"f#NJ%NEyzoj38RBNYًT+VXƐ88āsq q qF8O=)=-UTTd]8#A~J\;v!Wye#U5>%*Xt%Ne@(|ue,fδ6GNbn'3#V[Q, 8?qY3(_iI0ڰiSY,(%t{qGEE낢|E YTdi+Nh%ڋCMEU|rI`xS|9߳ 8Y|nWI<݋89ih{TG34+A~7q$xf7;> '!C܋ӒSĈ~%َ] Ua/$_Ł 7p"q~I-: 9`U7Mi q_(t* %N0QT}Of0fzݹ8 @!~Y!QHOU7L^$U7&!N8|%qкyꬾRqF>e#cO|y-[7X |^T>#$$X$$$$Έdzq<[f a{$&1, IS'ѕa/o\+kȈufE:?;q<v{q\6qzfsqhkbq^uD6.iΡ9#92f@ȈGq _;i -sd>8g'Ner5qI_H.#DR PX>.q).4]88˔TLog" 5ƕ|&;0GAZM}pt@Y8z*t{jKK|'da1>״EuUZ_i9o‘O==7sp$9qy%w'И3r:3{1tE*%k4$gŪ/@Si{NQ3/>n_goEYXpǷ qm٧SFBΝ;WEOmmmy`0$$ί#+seGNV枽 $e,rL'*f@{BӨHVTvp~{EUkE茙GOSH9*myx1rsżm %!iNݦhGSXO'3ټOJ#\ER7l)Te5y@-KHl5%qy &YQ*yZ']U~\4޽{G8 . cdd̫~Ð8c8G'xis'x'cGJR/} ޣܣ}\ GE'Mzbc3ttt`0AwBHH_gֶ-@$ $_ %%e˓"NW;Sgx89;J* q qUԄ>}yBHqH% 3nYv|4 Nӧw ND(FJH暚_ 1gP\lK<CT׮8rpMYQ 8EEI:DZĄMO @UANqЁ/qҚ+ȉS^ lUU7iq$յ@UhkZ>CT֯=z ME92 )#=@@@@ 2 p.θ%{qB\1 8Eab& Yiq8RbFSRRV ~ :{ӍIzqP⼫3ׯ]yGFZ@kJɧHK qJ A޿Ӎ8rb'Ϣđp 3t ۑ#Gr]5ksqq_? C{%?Orrw%q2Rb(q(((]?gGxrT[x߶/\ݥZ9!Ms"|8tjj/Y$>xX1ȹ8 eWԭ/KDS\>]@>ީ,21U2mm씖ڄp=]/?,[x_Ǘܷ[c/m-8gGNB5HqFqH8eyWT]9uD9 [ ĉ=k55SmӦ=O o/f&O> _QUQFl'N''m78ᯅfBBֽc/'h/NTDᰡA3ӧd^2P'=-ܙ]D?y@J>b5#U_8O݀āsā!'-֤[NQRRfy!qh'N4gR[|Ŋ=3B^ :`8ˀ%vwr$.n{z#>s %0]u$y,u!ɭ`<8V.L&!%wr8gSQR=j |Ӓiq 7ЉJYP$*xZaqrKzI4PjiMEEU|r %NeTh!-jOSZ8ԅr}*YӦ,#Lw输ρ*DO$.^-D NA$2;,.g3"v`l7\c̄FIKuqv28SXc\:?zq:3R] Y\L/NgRVcO h/N]',2ǔ)58$ڋCMEU|rI`xSB9p: R_ Y !N Ë8ih.\8hupM 7O_kvySX^8Ľ8-EL/QحP@b q q q qF$$Mu '׋C#0)is~>n_9kwbphsq@Blo +Ca(&FLOk')xmeDtc>nfIt|K ı:q K}xUp@WWv̜904//{T&;\\\ $5$$$N) |9qf GNLEH#')`e ',, ݩ{tzƍ`#==}ҤIwΝ;[lww[ 8fFnܸ6݅FFF@MMBUG/ q q qze̥f/2eMHPZv#ڋ#%"•a!2:v@OG7w#BX #-܇V78RbnGJ\MHCE=MNNG@V\ 6|||@TTo?%VUU%nPEE}ZZZ^Nggg%%%yyy Vo!HHHHϿK[wi)~ 3ܽ!oن/(o̻u,'$?vDsؤl7L@K^^^ޡ'55x4<Ϟ=߾}444ć6=JrHAh_HHH^^⠷TI13<9wY[^_Ŏ]@N[NspbܖǔXmŅeBɉSNKe2)}JBja7$,ӎk?'eTkiY- "!Υg@|aRַkZ2slabUEơ=?uUZڻ?*A˥15P(JHTQ{^/=Gd8 򹝿J>sgE@jZ J%~AMf℄&Eਊ7*6Je*HJg{jM7sd.q/NKr#c#8f[ 5A;d8$ڋCME]\]SKY$<)=pNX{a9< df1q:K*' 8h/Ngl㙚c쬬]{qⴔT0a~P٫ q<==yxxbWT L| 6w@ǀ> Y6@WÇöǏsss333/_L-4BġdJH^tERKl!9ŠO߱ρG4o<ˁoЁ`_N7ivL_]8կé❟8k+Iq\<|AGn;L@+էp|Ujj6ߴWLag_8-- gC00ڡtsٯTj , 5v̹_ā?ɰCyK,! == &nz8p|8U UQۿv#>57˛v wt6"Ix<%UWfHGK[z:֎!yRCKFL34>='v8Q CDJf88v8ѹ?[%-J/b8Q'.[ǻ:HM͛7%ҋU7UBǝ"[-&).t+fLg؉CGU_8usHlWl!98!wuZ˛ ]Y$N-{ǞY/qB)))n┿B/tcek)HJ15򆔬̓-.G!?9dqmBYy]ŕ' r. 5M hFP`K'g8)A(qjӲ?h+>c!82<{µ3 rG8---DXXd9SC73Gi68ǽ*p1rc' k{\^E=nL@j7P.[=e Ĝ^XB洵Sʻ.H#Ng`GC{dR JP bՋ߿(~ +"JNR@vN1Nn|\{[~.iR9O>g"[-S. 8n a>nz5tTf'zťqͽ=$88j_G44&fq3r\o1c?ZDgV$5E%8qIH%>|<<$2 MU֐yjo?ʆ|!q7qIn::F;WOxR;8C΢u͉g@9̬\፷?Sv7ů?ה/(qN q|:&_7"]Z/Y7fr))_yb& ĩ ֚`U{^;Wdݦcó iN>yzzwWTՄĬf2y8q){n̼s *Pя܅͠@ӑʦ:o~IVw-.5g49CmvX=ʊLp& $ŌERwS(+@O/:[e@llH˒R&;~IB3IVT ntTq**E8}og 7xvAӻ'V q6]|ʣURkÍ'}6Z骫:{Wq^J/_8LJY%Sx%%% dv. '1?L~g/&-749 Wġde ww8χ=9qܵtC6t.˞r($jtVH}l*-G ++n< ]܈/ S,nB@⌃;e8@il|*GL%'Fb]Q%Q@Tni'ۄowb*hMH@CK?s;sY 9vm׉(qB:XHKf뾄80058888-__>3X^oRQQSPPEW@@@@tuu%'F1ٶJ{['9i=a00c(8$NMx=:iY(~g|米܌q_j3N%ę!"EEE-$19 f 8ű. ¹f {q׋o33ChS0^/H <8wiT@ HOG7wУk78ϼF:ZZIѹn ;qRYymHH_fŁā1޼ _P֚{YJJ,7H'Wډ͍&֧uɌe:m|gdWTA!q``H27p8`8ˀ%٦k7z9ݮ3a8u4Lf|HG˼tU֠9il"ed5}Fcez?hRUˉzMTh8If@g⬽_Av8,X0MA13a0_N~%,n@vݕD厬|:%atSx<_: އXt*ᐉS6oЁ.(q*CF58E #cw _P^Dt i @gu'sʸd|<%qкy2]qϢUr ljXwUkF{),=8C@ HHɰ8=M m;P⸚_DwCOOcln0K,/tS8WP|Oo\sqJCLn140%Nrsw⼏O]%3W3$ɳ>ڼq',a`@w*H  Hsٲ,8R]<᷈KGK[WzǎT88f!Ga'-} 8h/NoP8d/\b}%NW8!(qʃ#)))cRp1̓-.G!m&L7O{939 P$ ttF%Nmj&͹E]5pGFqzO7VWv朂ܑp0GWW%Ν;Gb477300pssaHH3MeQg886X3234RI(*֠sq sqȉO͝/&˹8n[jJU5/Z\:Lmn9Ez5tT oO| RPP>!'(_G44*>g\GVT]=v]Kjr $a2@DewXo?>_g]*jȉLCtoUsdge(LH^{q .\XSS9W !q q``~#蕵vk)~, /^ݴMALAbH;'ŠAd&WZXX&o\Cpĩx)`[bIDhtj**tEUq@4,ۓgĩ B.6C91yy1 rb6C[QӒ2;!mG;r,`e4j'qj ̄ER6w )Sc-d{2aiV6eI)$NLjjjUcNbZ8D;KIeMU-r-qq I].;7k(Je$I]{^*Rɒ}fǼ3b:>̙3}&6(i{LXPP[&'ⴵqrrFGGc/Pҕq8>8#\ݾg?I݈p2DXP0nψU W~-2}ӗJ@_L[[GOŁ֕Jzw,BaM3LMM]]]鷱t_<+GZ' zzbFu :PO9tqqaҿ)lgg.""B\7[Cq>6"<'<&G]^¾@ q ÐqOu7QQWTvā(@0Hvnq'^ޱ*z8*CN;1'.7'!C&MNh(]>z*ηթ8@H03@q=h=f{Ĺ9mlj/Ҳ<޶ ā@!@8Jc,іWT;>|?$=qZj;:cXR",,,bWLs|$9ꎪ{k<"y!s0T=&`pSku}-q$=Y3o(/~yh=q;m8_ J? 4 D5 %DEN, #0Nlnw8+%Gtѓe1 7įbĩJH%|KCg){H<OwZM-n.R}oX|)*QwTͱҙnOCKFwR@z=@%pnX}NI[""-21f0?|LJ)#.PU84U2ش.*~$8ӍL̵JC"3-Q0S8F"Ljj_)-yRʼn J*ǂ2$!Ntc󱨂m`lUq>VHJz'XR`p+lM)*X*NKQ97wl}8434*!@8/gٴuᒎ8x/\b9먊t`aayuT3܄Nյ711gqx*'FQuT}* ݱ8(H!Ag=o|R}/%3(s,hs}*.fؾ ~Hر{ N#[w`U 9)]'ΗK9]E5<q8 al\ŹiNKMukqz?8n-yE'ʃr┅Dbĉ\S:\ukEe99~;eWq.: q;(;ۙUTq\=~# Vn'7:v!/ vhq }!@08[na4hۉ)9ֹGP*;#N&D3qҮn~ #NEh_W۶<¦S+tDUEzS4oߩj 7@H8a2qFFʉX#NYC3g_cg4ot0q Z!5#ά 6>N.aRPmbVX6Ick8uAaPhkjV-[~b񗂲}6HG46f`T053?]],2|Hޅlo_T2= ^nC\U-7>/M2SD>wE$D~HkS?Wi?*kp(Ϗ.Q@H8a2q6_%Ρ0f\̙K.7'7q>;^cڹ p^6^Ru U^eFUI#%C'Ɍq߲'G1ں33/[j8Ou9;'܌*D\ۜ~2)q Nvuy;Un.BovђvZZ:\$)SgD먪sO;}~mV!~.YCJ3 ػA_RRˆVY#,(hƁ8 alE<D\(kTէf"lǡwpxWQ=bgq8 aTXRZ9po"#;v Uq8 af8#N/9taA`o_x1ҧ!@8LY+q y8q8@H03@  @f9ġ4q803@>Bpd8qF0`%y).N w!@8 c6ddܒ{*tCcs-ҠT'~Ji{\%@C1~람(CڭLjj_ӔlTq'đ92|YvܜdټL& >gAiL|Sv劲>VuBOзH`gs?@G8!qMkw%4)q/uQe_^ ë8xGIs~Ξs*nGFQubÖ9&ĩ DWFcU7UY7Qt?|h:Nl6ܒ8UIz>pqe :qn:"}YXX^gbUlc TUŧ/uT}* 8qI#y18qD/:@G8a`'EV(Q_I%\ؼ'˿$f>e<~4k7;"WqU7=qb<ћ,ę&V) E?zJqDNv7LOk~]'#1$ܼKķ8Kp_wK\U  03 $Vũie4U|̫;prƤt4g$e,γИA>8sj1M7"a|#NŃJcqF )z~<Ӝ9ئ#o(Q,""|]n]T&/ݭ0 @fcqti"`cq̩rhw[>$: .qm{D%Q-!fІP%d_cg4oD"X[iwBZ2oKX gTFow>7%DDqX;-\[ci!y7I $j&f #bZG/ߑʺ86퉹7Kkfɟm Bq 3Xal?lZ>‰ib-9.vb^qUN2#%؇ A:A\!ll"qs\/4m`k.{xI)dlFUslFsOsnͨ~bb4q[I|kĉ1^J|Ui/dPW~UԚ /GJBx<y?|Çjcb?#\p;J<āq 3kT38 \āq 3 8q G8o2@ af8@ ?!@8 @G8q8@H03@fG]^ž?Gt']$ySNuE q8 af8Eރ@ ¨q 3G:q.Xc2d̘Ħu8ڎst8']}8@  @fO3C؜-,857`~q8@ @fđymyEnNűc^ppc>>AR*g1AN3?c?'fI9ݎZ !8bKnNl")*1tRϭNc‚|R8_@}|Gı5YW;}F;)đ7aJ*ܜdco`OٯL?Q6FU-O,40,N|Sji`5AF=gңFKUe' dSt>>A~^f`ĩӖjn!=qZ,Y*!6xjyJ:F#.{Ӗߠ#/;u:\\cq = afNBޟ qF6e9/YTT6̢.982Ve\w37fSnM<\98zS, S2* 46F~'ęnnanhTD*ugɟG2G{9GbȤ֊=]>׿q = ax>w>?$Biڋ%Uq^ Ϲlňsq=Q$j&t3i+='NɝVY`(KGPPNv,;:::Nl:ܒwTM9s/^{w8U1T%cU7DYC#7XGU祥 3X⓱(O2II\yB*QMޟEML}fdc]Tx"bh:N?f:N#;wc]T RY@ !@8 LyifzПhWq.lچRSgeqn߇'U|,su^ʼn:wu | x$//iy3mq'yAԍocqޖV&NL@`;XCqdN?5u8W`y[U*JNq = |959vͲ9z5Ǣ)m=h?uQٲv8?$6aUq*h8pO9Wjh8 pts\̂cGP:iry@3O>sqUljev8[b"*a/R2iTq¯v8[ViSn")*NEJ:>s i?4hPeϵϏޫ @ kgi g!~ Aѻt ި)Yn6ssrcq6mBX۩ C#[ oC/#h)̶VY&ݱ8# ՜GrsɅUטf4&5cn`!7Z174ckf>mIa\bkieP[3w5e)ϭ;*kHq  jltl蛅:%Φ9ƌ9s Q`űGNžsp:806\`,"O|rpc9wTd߼Qm` 9-ĉ;~Ąr[IYy 쩉i?ZJ\ '\gTmZO@;Q՜eRw^ 9O ~zFզTT89&ܸOզ=ږ>fz$#NmF6Ϝ<;8ͤ+F@s Qm`6 eq  4R7)c\eӍqn<#AĹz(Q@ 4rQOWq8@ җ!@8SI?ch !D83`__@z.@08@  @Wn q8BCq āq 3%Mz >½݁8CqlZ;:8PŁ*Tq "@888q8@ Cq ā @q C8q8@ al8@ ;Cq ā @q C8Qquu^4Ņ āq 3q8 af8@ ?!@8 @G8q8@H03@  @fq #@q8@ Cq āq 3q8 af8@ ?!@8 @G8q8@H03@  @fq #@q8@ Cq āq 3% Zl@Hw!@8?РA;U,@@ Hw!@8t7@@ Hw!@8t7@@ Hw!@8t7@@ Hw!@8t7@@ Hw!@8t7@r<Р!u8A4ʢl֔Crky4Cs>]+k]+kŴuI{ѴtVITѵtҵg4ӻZ=]{N޿hk j5ѴZ ]{NJеӻ{FӾ|@>>Ѷ/׿GCq ā=@q8@w.C~q qB !@8LA 0szvtBۊnTeYq ӗ#&*4∉ ೱ2ˌĉ 0:b iq [8Ĺr08q<=k*ٹm=ږP+UJr!{o"C(jmy׼?Iy@>@}28q6m myXGժK޼~mnGUJ\s;墨sr Yj7.zzH~~,N'ĹOr NG5]7d>ھp:tp㛾[ je-ʼnRQ!wQB/5aSsfϞ褣jСhscmĹM+9rf r?8qRS>lݲy^t1"Ν2e'NH UH @*NQNFbz ژjgv2M,mhu2gۖh{9Í`Ío%_X#)dQ9ݨA9F;7O;'NEIbͶh񢹝WqE9F=\)g3ÉSQMC8Gˆi{rQC )K8 Fqގ^Z[Yĩ,'!lFqⅈ8o^7L5SUT ā@!@8qè*N':wTW 𚢡vnr!OWq艃dÍ^^*N'8: 7B!(*זm@8cq.Ei=Zz,!4cq|Nܻsi~^ K|]!W5X^͍)IQK/ &цJ]MJm')r=q^7I*+3ℇ=ذ)/'놿za B a~>qs;͌*nE f7?fTFϚn!,$kMQ Ϩ?wHxnY3/å7UoO+7yСCDE󪜆8anHKb{QU\vk˚*RqX lh8@ @gb,ៅ<\5@*>R,=5iET==Tq@ = a~z8%̲!(7Oۺ$ 7roٸB_Wsd71f&hqݶ]RDQe7r7bB!i h8̀r*Gwi]b(/aI~ qh@DےF֑ܶ 2>#Ε 0g7W89!8Ź?MNtocqsc0DC΍ÍG_G /`oiNQsq7(O:Lsz=q?acݱ88È~k޽p㡨0:Tq8 a~z8X$/*=qXQQ&ԨqcۭƈS7pg 7+7ei=XuőSpxz|S]A>bc]֪#v8O_j'}xS}m曶zm,D.=]x]vLjSVf}M!p㚪|t7^{l`,?!@8OύR4ӯ$`cq̦cq艃58qB83mlLkb 8ʳ[ԔTU86V&yMy wR)΋caa {P !..q(6FzX).H"rUyl=_} 4( xIjTA$?;*1z.zmFH.qUՕUZk/04ö/02@ L0?=GsfY 1oumYB)͋2JQ5n=.h˚tD?7Vu6.;qp┓LgT5wTCmFZ|ı u 8"CqCgIv3-)&jł, 4ā@ a~qzhq  @q D0?@ !z8a,qzzqzT'P愧28y0|GC~ āq !? }CmaZ8GuD%Y3ۭP:pKPqqrh+q0ǫ8mo;;`*8+W,~ *N|l8af8Mz=2psUqhk )1O+̌R |@z;@q,L>XXXFIױ ɋ0PuTѾB8E6e3?G]Uq I8&4qsXXX]y#EpXY::,zېGO+ #Uo1̴52A)΍22hj(Ϛn^SLʎTUXM47 kIcceeA)}n/Rlm qvSG9̷86"@B\s'Pom, z<8]?aorss=IX8X[ZLNhiH}^izZR.0MSultQU]Y@Qf|CC=4@ C qvvvvsss?)$$t!YYY>>>Ϟ=={#$$$=}'2U=d554g̙xOĩSnnn'ND̙;l[˗/ ͷ빎{6 VW_r!NPqcXY̨B-ZHpmMvىcOۉ0D~FUóuKG:n먊[ɷjqA>o)9Z9qP;_!Ao/ňbMVWܾq8ĝ#Ϋ%N+FB_S]U)V9lj*w8mo[7}\sIR + }8>}RQQYx1JIIڵkΏ?N0aÆ ^z''gzz 2LR^^}ϟUnj􅮊.##.񥃎Nn``PYYLsE355UDDD"ϟ?C/kTkT 8q &CA =,Æ kkk^;;; ?eff&o…_:=ztAA###[l4IIڎ w&X $uuu#޽+++KΝ;&M_޽?BAFVV%:ʣ‚ P}̘1lll؝`Xi8\=;;߹|rlSWWWSSlmm.@ߖ8 'o!@AGKtQQϟ?ӜSKAA:aRR۷oNIIG qqnUq%3gnڴ8@ Bq>H|,Nii)>g7n|UHH6@|ICCɩOM-|||"nnn |%FX^'ݻw8P]]8cƌ5k|A ā=@ӧ#F rqq7 qPfT͛7O%K |rvvdee1ܺuKLLfF͛7[lE'>}:vQq8@  sqz=@.iiڀUf28jJXNr) q GMU@g( ԭɌ%e' @懱E^ U%gNc=lBrÛ@8A*·wM9qppX>-8+c?Urb8 !a~8%%hY?<<5O84ٵsȌbs3}=Tq@ CqGn8u˴(ssq*Oƈ#7y"}8C{]Dx,{*T&1{AsUb%Gܾ7Mܼq..No3T|xS9q,̧ھ[yUYew8j!ξ=[FG^nfu#|y45<]jH^^ lL6hۥ%vW"ㇱl2s38ȯCqg0/StF)È3JB,)Vcm¿f  U$g _< qtM+Hq14h'h|ԭ]!Q*:sfM)O%eGqfؚהgf?VVÉckmffbP,2Im+;"VyRPFZp > |)^A۩dG_ޕ*N[kmjb8:ڕ3kB|d]8-$(P]G_8[ i{S[433bRfuN>kK8mo_&?uyc#Q8>^@W!@83 cQ6_Q?N̉q߄uQB_=X3j##URe&G/+1ۂuT?MGIYV$wNifǠt*u8$=b]Tm+xx2Sh:V\Q]IQunY8qvpi=vlQu^ٶxԶ7BW{Э*D;]\5**xG͞uf94q NEo14 8W.ˆ_0k'7+ | Ns/[q\8Eڏ|Ɂ)΍iDqrrcq1DޤߝS:W|X{휂${Ks*N+'qz:'EEZSpTW䲱jOXDKg0DEͻH6~ׯ(*}Tq8 P^56x^<9rũɱH08ۯq*NI^tUzt,GMi;7.bInQ/F҂=d1>i<ny4~h}zm'" NK0TWdX_M]:$NMe:uۍ|MTcD.;]x]>$}5U$ozzWGG8@HCqz4mmܻdjh17<ث& \X#bb 6DCOñ8VƎk*ƈ3s鋚x#m|,Y3,k+2H9* X+SKs'/HvSFkcYldX).H&rUElTз:$NIa*:>7MOc%Gٱe/+"Bpss=Iܭ88qP4MiyU^i+)"_:ڎyDt7Ϩ*1Gsjc3,^hdmā@ aB>v,)Ow:ci'Ȑk.g$3~Uq<" ?p-O8Suƍ>w368a7л،*RNqSQdfO?.yrXm8ܷUHPm㪥};LqղΉڡ;(dziW/Lj0& ];67eR%D| r-$$H㚕%·wMNkFXOßqΟsS B 4y] 5c1q~z1"ΕӍo]>֨5h8 ;W8ޞ'kxp_v:qHOCqz48(yVf7r0ޑb«V,j~āƁ8 0dFїq8 08@  @q D0?@=-M `2qTιpm<'I !@8O!78'( qʄI% S@G @v[iOK:'ÊeOEq Cq:_[[ӕO988qE5Fó|pTdqhk翔+IfStR {t8K(II\-Ӛũ ?!:_a>~t\ߡ.bB<\Kٽ q,L>XXXFILVLKqj Uto ܻ0^mW$[[ϰ 'Ϋ+%Eyy% 4Ai~ABt=Q;m;r'n޸FWg }o8o SUDPqjk!ξJKIG^Nv%^VvX&!>ܤ v~*ʦKBYe3h8H$ۢ {y) |>'};`e8Pbn5֦/kRM]Er~Fu?{U{WZjv8cqh8G[KmyMyZavN[kS3؆g,Umk)8X}KEjB˧q >]J>]]HFz+gMh nn++r8|KQzԸ0yź r޾8mo_&GHK]u<V~X_O ң @N~yO򒕕8 h//見O'}N8oº{!zfFGQuV !Nav@:B{c)'%d A-39jňQu.Y8qvmwiúQu6xն׏BW խ*D;`SkVbU7<<ܙi4UǏ=))"67;'s8HCq: Q8gO‰ca겢[+I#45B_NN|,Nq^,FspclG7(OB{U8W.q{adx)j8q+jOXDKqƒ>7~>_ZS8@Ht6ۢ~$]u̶fґj*qT:"đ#Nrv8}0$ 8*D t{.6|h:rhNOo3)y4֓>>i ĐX_MQJo˩m-E.;]x]> 5E4Ík ѥo߼e GC8@HtdvgToca2qF jI=g7'>S/rKı0r\e8VƎ}Ug)bMq^>Gsʬ餜Uy|,Q^fdĘ@]' {XyWB\]'R<6ΈS\B>>Vب tcj:'ÊeO.*N|laT8qN];\G>(03ݕ*N@G!@8O$Y#i[WFn-Wssq D?NVKʓ;fZǖq LSVGY!mW'XPN%sN8uV,03,ɏ8AQ#wn[5:vyUvj1QAnʋR k2,̍&s>8v[B\PRJO}])3G^NvZ=M..NM/W;,I9Ң 7hۗصcw/E1Qb9Yd;qq  a~z8ڪ&ѥy4&z8q$FGhܵu-P[4geMY");\UY8U:Ƭ,0;JYi2N[kS3Nk*˶v^y\@FZVqTq:" [˚ʬxe%y8fE uEW(Onk:qGX5>/[vo*NۛԤHT`ĉ ڝ;"UwyYt*H)* !ND-tN|,NqA2F;Mqџmo?E9KiiC @槇UqJ"ڭUe9jHK ?'96*N}o8%q|Vޕei=Xuők(QQ8Ji;7`Ii 8Tt{uy?7;F tB5UKݾqՋ^=q @X-K3JR 6l.>8h[CMcq,Wٿm,Ĉ38/@ GsVU٤UE|,Լe!6V歯*>'j6&9I-Uq75%tC %>V!( [@PPAh:XnmVTDŽٲ!;B{Q}Or6&p}n ?9q P u=n[ {T 7p@Pġ@@:Ĺx T?1bȒgqq@ġ@@:)y[9ueI@N؆Q" _!N'4q/> @TC8q8P qIN/8yݕf~^<'>4 C}G~19yJUUޅ]KKK |az@ !O$Մn0gN h4q#CݠGDV.j'qtuΜQ޷ˆs-B6ͯxst/.k8.˗,;ā8C~@% 褪tDQn"Éc6eЭǙg@K C8ױ9tb14qm.bheXaC#.emlpd{ YM f*ضy u'9- J\þpw̨, 1lxH qj+\(d08$a޵M]m0q4Gm\̔Fpx2eT lv*&-Mؾ埦"oGቪԔT̴$ 5ġ@@p-pϳ0fv$"N&ғksdSOA^omz\.+ EAqT#CՖ16ҝ>ui8&Smre$hqӭgEV0W.Ԑ6qPF<)Ϝ7g:RMYQ39BA^4=/>¹-i8cMLlW\b&0Nf%TW乯vQPo~YosdG;UVfJMe^%Fn_D RWu)/-D/521GLLP 8g7`߆5KZ!槆 U?vL{){^wMIfLg܇LVrYEo'fΘn[;'<͡Cbrd 2D8tS7 휨Bٹc f*e)TϢ>0٩>mj38N<ā8C~>C4u0qxsxY\fnFEE!ԗsG\9w&N}Ew4q1b-;5𲸸+ g  ^ywLwOv-΂y3yi8>OdA'6zNb-N+'$uRy$&-=Ě˗ΏBx󦌍`>% _gGˣ8Gjq'MLlvzweY)L4T%ˍ 87{䟦_C8)q(:8}H*ݷ' 8㈃X3Dߖ>š[kq΍ Gw-턕 _ֲ1q&3+M8cN-}J &8YLHK|\Wn>tq1"""7?Dn}Պ[Ř5NSl+K8,-̉8&ƆSJ 22 u8Ԙ@tkqa$[zzڳ'Q!拆 ;h TQQPWTЭb,\0b >oA;3Z_[~/8\#|j i1^ mebwp"#88C~_ qܖG,N^d0;f qy,qc3sU }b@ !#Ú+jqvbo?Q%z۞X6a/CˌdX T&.(%)g ei%]95G _X_\\lA!7٬ n۷U~Kᆻ˘59x%珽wU}NlԃAΟ=(ZQֈcjb8qRQ^JFjAGڒS]v_lƒGA^.,~Sc?*l9\Fkk6 pc>vTSV^jdx GL9 qę=ӡ[KmXUorvbtqk zt ';#fF*I ڳ'&\C)`ܻOQ>A-cb̜cy;'<վCʝwCU~bf=C'ىxXRR"!q;'qvn߈s9ܧIųT/ʹO.0Qu׽Ӧ8lwN-59 b'@ q(:oamb0t:8]N v؍+6w x>|7 N籦U\85=ͻ! ۇZvz &NH-ww1|xdshmMM(D@ !N"N;hUQ,x+oqtGG]Mƕ8On8w<&N3{%yqm,78>89лvRfCVpqt3H}M_OL(GqbdgƣwWVqˍqDSe ,7.@o5y_Uzp7 @C8Ia=PpI?8v .^T ǎauj&&# b9ޘXcl;uMQSVrnEZB@mYJvV A63 WgTQV"cgkm˺ae}&I65Y1fZ1S秲Ҟ&82&2#r_Ca˰f<.pm+b%7w (PWTg'+Οci19:2 _MTtkX> c?CDE }V=hj'&KcĐɈ[ _QUUrxBωI ط{ t NhaC \QoŽejb}wÎ Nn3U5Y TEP_U85]> x gr=z02[bهEj7i龽zBl">58݆FqxXˆsu7/kָst+.MK_ʼnx ā " WBbKHJ c8g- 6tqc3s5 qȯð{ ""}ʦZ.tħ l VvGˌdX4u\eE)I }<;-Lxڲ48r[hb/..6|ȐlVRT@qMm( gYi9Yٳ`hiq!Ƹeѭlp.K R':;<(){t &Nn3:/IbL=Ր?%S[HE$/ݻ & Lh4qV.yT +; TUv"ާk8>π8q(:{euJLuu5[_#0qL8 9O2tuFq[gT'^񣶖ƫ:viy!3!ٔ$2Ce}k8fcl-bYOh9Aeлu!5?џ>͡8?7 (Nkkb4e4VS=]m8tkKvz\u}ڣ_8 ra!MGIt6+"}hmUMӋا.x~┗䢗D܈x{q P 8g7߆5K?Qux&g'F Gw0q3"Inf$B={@`u= _\<5\\&N'7zNb-;5'Ga7_nw wCw NУB*6zл8i⠯#=ĉ8'M $dea~_l?q< SA|hmMM`wā>c@ !N"N;hUQ,x+oqtGG]Mƕ8On8w<&N3{%yqm,78>8x(0q9OAozaD8(Nvf':jG`&16 ;~4{龲2ҳR6qY1KHІ o FWTҞ?Rc؟#I[[ _QUSEm*z#7z}UĩpwS8Tz7]o)//ףG#C+}(q_x^!-ݷW^i8'O/+)@p@ !?أO7s|R_U 8qCq_>Y]Ɗ1l_ qq(8 y}R,| ām8!8C~@J4@8C~@ Q= @TC8ױiN$'?Yyq_$qN@"' _'ssq<UDN@ !icGqxXpXhF:A/qxmDuQ= |22bbbvGFK |1q,㜪Ts%%hE9@'Caۆn5|l{Fq`zo@ !#NaV2}dgXFs԰k33479t+3-Ñ-sOT9;f+ri3[]>Cy(}<v8ե(+JIJ0Y ԕ\| kGc}qqCEڿg$3r n~.z۷m~ETw&:wG9I 8V丹,RQ/%%aֹh*ps]Vv6 aMyW5 '}ƀ8C~G3S}+܌ǜ`+s8*E_-MرePS-wPHgTۢhVr0qGqZ#zavLFr(8tq)ɫW,hM߫)eΛ,Aʦ  /s{Gq^F=>٣-i8cMLTJ{MnmN.㸯Z>Z{TGA^.,aSc90Nf%TW乯vizQ4tO\R#Ã111o/ }z@ !gF۰fIa%S0wy0LᑛOhd$r[>_&^sp<92OmY:9c5.휨:|pT!+7'\J@'xTRR"!.U8;o³TW~.OQ5>U|,Ց_:O~der659 b'q P u(N}/Qo8e> &ιS1q˒{q'?O#S?8S]N " W_nۢ|Kxo+#87y[Y` kZ%υZ#C3swD'dea~w_l?q< SA|hmMM(}ƀ8C~D<(0q9Ou5WNb< (߽8ŸݻŷXr&Na3guҌ䄉qx8ĉoq'Qxʊ2>n1"ٿ0qIܧ*Xn\BoŚʂׯ=8g C8ykqƚBZD3b-0qйűcXt:ɑhgXN oB16ҝ:QJ"8YX%ՖE=n>buq(WWgVQV"c7jyıdV&I +ŒXc2`}I3Nqg2Uy~fc]QA535(:2l/LBo*:SQ!*ϲ[b,?b9qȯ󈓟6}*CFLY틳&'-z"CZ^/@>C~b%닉1qrXtqWTU\4p oBJcFquc"/'ۣǷ:+] ^U VSCFj }q8 tkK+jʳ=V TEP_U85njp@Ƶx;zadrŲ%N+~8ce%@C85{Oo)أ{T 7p@@ !Cg_GU#,q& N7:? _!NIg'FR\ͩ+KP8 'qȯvq8qq ġ@@q8@q(:8G=;)K]UyýW:% _'|19y/8*o.P]rā8C~1#GUGأF72 zxm8;jGWG̩߾0ܿ{i~Uc]qaNqYq?QȈP ġ@@4t('C>qvl!>8sܾ}}q(:8Y3cibk33479t+3ޮ Glޣɖ C;^xTeT$%t,fu"""8rX_\\lA!٬ )Iq̦6S8QVFZNVfe8#6_5DB6dڃ;>X6Ym9 z%iUoL/MnVo6MAN f~G2er))I'3A8wm D-͑{hNV.}T k;ǖa~cfz>8}:x> @zo@ !#Ync 9A"֖&ز ;clm8FqZ#zavLFr(8tq)ɫW,hM߫)eΛ,Aʦ  /sx1;"/+>3QQV WWu@͍ܱ1ӧ9称Ҟ&(Nkkb4e|&Mn=_]efGAA>48:L'YIݸOAq1a.xn?qKK&∉\:āġ@@{/qfo kAV;%s'! 9{)ˋA_IMO{k]&NFr0:AU^ΈB'xU]-C*%hq8nS/#Ԗ㉪3&[qXܷόSf>&΁},Uy1DL'~4qXi?Rtq=%4TqГG<&NCu6D]ĉ8&N-:8YxLo'ٿ0qBT7|q._^@ q(:8x'+-QasGOW8j7y~Q{1qݻw/ɋocMC~b%닉1qrXtqWTU\4p oBJcFqfmc M #WVFzN%m';3j }{Wf '4ΰEEEU̘&F q֖WTTxvQ8C>*~Tx}*׼@}zadj%N:}{D|.Nk9y|Yi! P u=n[ݣ΍ {T 7p@_U@ !Cg_GU#,qWE]wuYf@ aKJ 6pOC8uSىѧT%esʒ*9Ou~n.?W| ām8!CP utNT!N'4@_C@ !? āqq ġ@@I+?8o$q qȯc8]8'|qTUy:XXZ{s:4 _~_{Fq"1,իH!+MQFw| 4pw`o[j8.~C8A@8C~$S}cG wy,>wӕMc rŹ}  C8y8޿QUEIWO1Oî#42W}UY3M+'+={ci^ "oji'|ܽ{wvj Ún"""oqozkQԐ&.voU C<堇lZ6lM|`cT-?@o)E f֖#Ԕ2ݖ/PQV`-ңˎ j-gOn&4mA0kg8O=˹((/EĩvsY_JJa3ÊǾAPUoţ9rq5 {0kRMKqqȯoE]/Kq_&]1 > 8CQ;oBxzRp 8eYo-"̟X#&,u8C~%ZjÚ%m'3_\׳[Uϣr҂J @b'\ eF Lp]64ձTU<>$wCeFX56mm,MM *S:f-̈́u谻ۧwߵ2־=[$$hx-XS#[nm#8tkK86 +frT}U^t=0+#@uE(WTep23Y09 >@g C8Iy^gu5^zj}sq&ٌG .Ξ?ό>VFLY3sa&#h|߮r2=z|kh0z!{/ĩ,NYxrE g+K5 x }ր8C~`Fq`Fq ġ@@q8@q(8@ z@ !? āqq ġ@@q8@q(8@ z@ !? āqlp|q(3VQo4pG }q( >4 @AЇġ@@ Ѐ8AAq  CP A}h@ ā  C8Aq( >4 @AЇġ@@ Ѐ8AAq ~|FP _M5I#6#UH?G=#݃ѐ%tp=s=rwwwBEѻGG RݣUQQTQ#t6׽{Կ{4{4{xݣ߿9Ǜ??"q(8@ Q= yA C8|I ġ@@j7t-Dnֳ @ }b@ !.K%Eny()*?TNVՈO'.&A_g@ !.Ns?yu9۷TWgDz/tD&ٿ`A@ !?'%)` &M3iR?9bf>3kbRϩ ի'nYU Ƃ|{7S)KII55zxg&yt>qgA-wn|<?1bH򜶉tDwX6٩_?ޏ3 þyJ ۶lTV߫W/}=W<@rq((J\V /:_z M]9Sm[^a:=z|P1l:ۯU\p7ADdF|Ϯm2K]#ܹɌF"G׸Os}UvYOe=!P6=ûWq, Μ3*JLCGIexW*i}^W6LFA?.9)AɎU7A͹sf=xìlV `@ !.NO \A^qt9OT-n.w$:2Xʼn|Z7th'^:x~e!G,_p9t2bP3G= lz^$ E'Z#Db."ϫ Vs_s[\wIT01Q+!xޏU+\n.K <ST7_F75F ޹n<;y: u8C~%ZUkjT_1\݋G 6^CocY<%!:gY豇 :_S_X}N"NCmkqЏKJx"L@?yJKyPy[G@P C8ʼnß勵͛3< _QN : /nb2qENWT-]4":S&)IJJ]{E]Ghjjb?e k +o\Q??Qm+ }/86q)ӦNVP1"4GSQҗ@q(,qZtck8;#6pO7&@_y@ !? ATC8q q((Giā 8C~@ q@P _1ǀ8Wq(@P q8@z@ !? ATC8q q(8@ Q= %c}n@ ġ@@q8D8C~'N7|jy7^4n#_2u%Dnڀ8C~@apYtDQnb$ ץ0C7 ā8C~KQy,6+Aa=[n"""*J7lMDzA_fM\|w}ꦺ̶àsUtzur3d=Oƾ?|ѫ:q~ٱ^]M ⠣"=+gk4Gذv14EEyI3eedefϚVVľX cC ֨Рۘ57E*JRR V<Ɔu14= ⤧Ơ?NfBa z6 4]*ܽkDIA"nmN.cZ>Z{Tsc &iC7=/ũE ʲQdǏ^TT_ tZ܌\Nqf +8n/k8Dŀ8C~%ZjÚ%UÆmDqvl]D[l?Qu6gID֍'ܖ/$`ضOTҢП; J F7Qbjy07xD%!1&Ο76Q|Y3!4IKZ"6&훉)7SnYx~*)!,caܱ& q(:|; 0mZ\M[kX/H ٓ \ݻMKjkiANLߎ55i G߿Ysy5QQQb-NCuT}LaLؼ=$o];yֈѳweE^gɾl(y(ٿû%NP=8e5DGcx= ā ġ@@p_D`gUQ|Sm ;u +{KǪ2Ļ[)nT*!AkqmɑU9aMZ8>A4#-S&tdž>Xā Jġ@@pu_dlCAܑ[C}Ht۵o8ˆ77e&}oĚX?|EÆז1;89 ӧ9HSIA*Auk܌hH!MMyjU un\jq'4ްEEE t1q^6+ˍ79%tg'iYYfM/+@Зqȯc?ݘG}_Uܵ5G;Ӎ}|1Z C8yJ.4q 8C~@L=Sw2Qā6P QbN @q(8@ Q= ā ġ@@gGt֭8i;);>> 8&Ξ={6mJJJMַo__Q^^~7333'L !!1lذ˗/m۶]dIccR1:q.Hq kġ@_3q1cƸVUU1߽{<|駚 '66_~, ɓ=dN1klNy!h41A$Kk 3.T@ 5'!!wH-9wB܍ӧOC{xx'11qŭ\3z,/L L ~@!p]XS]qYDq> g Ł}ıePnnq7F޽;=-,,JRRq77o9r$;wΘ1C!fffϟ? >M\™C q荠}ʦ7coIć Uж'>-MoCU[EYmqW.22A'5nJIJ0YHa< +#-'+={Բd웪T{KI.Z0?Q<՞K40fe gMj :ЛnBKScúf4S3;azyqƍkWIHFkk<ĩ*ts]_JJS0n7073EOz<2RcA f"z#69jㆵfci4E/-MMMsMogLf99(/Bw[onnā CH= |y%99śm?W__~zw8qeB_VC8<&솺郘8zS 9QI:#X&#&6g yC1qWde:w[L *Iq_H[K>[8fcl-1̍9&DcGݺvN`-(NMyDhmLK |qB7567OIJgn ;NuEޞ_WRR0Nf%VW仯vEi~Y n U=Xn=QD7j D͐YaC/?QPP{A}V^]ZZZ^^~GM~zذak׮y𡘘X\\}nݺw)SXB羗8g:B6Y"qvlYD$#=OT=Ƈluv&Nnf7k.z[J,-;# =a jHJ>'+%݇'c|N'#'X`jwBy^+ݷU7QHBVsF{T=EuO7_/*}|1Q+ Xx`J &?MuVRR$q *q(8H=S{"{Tu,qz쩠 uأ (q;6D8Iy{uhϦN8@ Q= tR5U>v̺U boq q(SkjzՅ3@'8@ Q? п4|ݪSmx͓8y)8@ Q# ZjRdG\ı`֭[GIx6qtF:}WFqh41zA$ۭCO{tA]3 qQJgaGY.HއUe,[())QYFq qO\ y,6+R`c?oLk.0+rƴI2}dgt,͋=|7S]l>e9jr8,Gwz| 4w)ʉ +#NeI7S툁 UE 3y=kjYa*&MW37 0q~ܷA8訯q FKScúUf4S3O1q4G_ښ!1nj \(d09dLn=LHo2 8q4Gmܰl,F詥wӫ8eeed̞UQ^D۰q:ss3t7 ux@Cj'q YY{ /3Y<a>gf -pBeQ?qފneRYzŏH?ؘ8 򲡁/kxgZ[=rB9zd$Ƅ[ ☍E?k|)wM3ckGq{E^V|6+f9!ov2kB>8 ra!KyUcCwߴO$ӧO {~&))}a>Jw_ PSW)B:kVCHH*鶖V Qڂ۱@`kZmE O+RBoPQbwfO8z7\q3=x0}f8Y':o6EqZՓY)Tq-]$jl.$usU$^^FSTg~%*eaU_Se2+EuUIԧ18DhtUDqjyM5^~g8 Y-=EUEgSHZgDq%9)si~(/U;*Ƙ4QuL{?Z6_@L*N䔲YSȧ|Cє2Q&Q5b+bԨKH#7DnPʼnUaVH0=45TQʼnZs޸ctԧ4KU(%*ThH+hDGD (,<}K48~ޫ#Pʼn߱*Nj yE~*s8i~>#?LKP폣o=!A9C2Qx<S~UI9qĈȓh+ioNю#WE.p#Fe\n┕%BYhDqs t'vWWwٽY"Nys4jv/ωd+sV'ʮlO,\ʪ(Mt!oJ NHv|Eqts[<]QvχQ_x:pQk#i q˒Fב'WUy^Q&ܨ\n(Όך×|Qw:hMOѠi!3ӎR _;x8]VFrͫh*.B$F"֔i*J}=%aٙ'ܺ@q`Gř77"B0Æn(N!/i0WҭRR^[Z/Hqw8]$& K֮Y~*/"s̟HT-\O_[Y@'jm$R)Jr]vf[.d8Lj~Q*PDTw:ꉻePʼntUw2w6h9Zƺr8џaS:%sP)DUqa.V)DG}D &l(I{xg*b~͡|x[3rz{jqڮu3Uٮ/_=8㏖wizJܘ(NTqx<S~MMƟJ=v8)kV6Pő;9_x٦:EbV{;ѭ8ms"?y3SD!7'*,~N+ʈP(4M/n)Kd%4)qF8٬QQ&Q8Qٮt8QU+Eq}6ՕIeiz^nc\VD,X{vUv]Ź׽QJX8 qP8 L86RŬ[![Zw'اJMkqS=;,(15)4ɋ?|ua-T2<}Z~jqS<&JV&186OT'L5Wf'nGKL{Cdo40ﻝv"٥ҼQ<=ܥRqő28PL8+绹[Zvv[ΩV %Z6!AչDqn\-"z9Q&2R~P7UgfMfzM#S B[6d̆Fq"#Mp-aCq8(8:=gTAq0c888c P((ׁ@q`|==v:X(xB,8 ǁ@q`Łu8Ł@q:PX@q8p(,@q8P8P(\ P(Ǿuڃ7(xʁ`+ { q8@/^(z!PB8@q @/t溇IENDB`astropy-pyvo-b70558c/docs/mivot/_images/mangoProperties.png000066400000000000000000003155171510533647000241430ustar00rootroot00000000000000PNG  IHDRUIIDATx \M}TZ$KȮҠ3-؇uhB6P6}:*W}?{^}sAA7k @AAF.  *  .  *  .  *  .  *  .  *  m.C[BAATtH%QTB J%QTJ&JϢ29Tv.OPTAUTLReTY9U^AUV6ewwwIoVUUDCCCvWQQ)//EoooX7|`ffƼ>\;wBAAϸPfff|||\vѢEJJJt)ٽ]vLy,:::rǏ°$')) N 9o޼aۘp1Ŝb 3g΄[yxx| gϖ255}aؿ!q19s`ѣ AAZ2<㲻v}mذwflII H丹IKKCٳgĉL}6q>Z޺uЀmcʂWHӍdEvv6,[?|066C.w=w {phŇtYY 3n  Ғ҂4a՘.{uX$h"O>URR|gϞښkǎ̋>|8d\"Jɢ ,UV 7n܀!e֬Yx΃S.[UU 9k OAAK.{il.knnVVV'[-))aNܔ`]AAA.{n$N ix%;ȪDÇaqd?I&sm dvp L}ɒ%5GAi\uԩ{wLС]RSSk׮,^x鲍/[O,n522p8]vl. kPrrrAA J(3鲧Nȑ#ߧNDsq .\z'##,((>1 %̟?"9nذ^z1 Csx_<.wѢELLL 9`jjJ@Aii%}X8@%ͫ+++_TOZZZ@@s:::K.:;;ѣp΋d' !!pBP*nM>]VV6lZ=IHH"&&6h {{Z;0nnn$Ԝd`>`  H\'t:HcAAZ  .  *  .  *-C`trrرcɨ˗/WXA4tX[[oڴ[%!Ҍ˒ANryZ"lA.cs]vf2l0111!!! N\ˮYҫV, -&vo߾֭[ݺu#}]׆ H+\v…$‚e={Fκe(urrҥ 8knnnLL  jhh|̕@_kp.rܾAF.{eeeefԩS׭[W!9leee233A\vѢEPgѵٽ]vLy,:::r߿?h1/_dW^ icݺu#k!1exk$v#Fp~tlBBٳ奤LMM#""bA~߿uz6竞%qPLN;v 9?^EEe$ݽ{wA"552;u6=KHHHBByZr?NʂڠVLII!P'oܹg_kiQ|V'WTVUWdTfV~,ͪ,ˮ,˩,˭*ϫ*ϯ*/(((,,,ʩ []v׮]o߾ 6@bL-))777iiiHГigΜ 'N`۷r֭[dmqq19;sBPmǏ `sY؆pYBYY3>'OcWH&}X,|999p% A;[[[UUՊ e!o߾PiCut}QQQ??+++8lPqAfUUՐ!CVXb թݻw8Pm߾ׯCFע-"SN|juZuidd$\Ç g\MKK `Æ ca @222POvHCӧPA eϞ= i!!!5N5Of^ /wʕ̛7oBf~ܹs9]q.KˠoeoܸiCCCrYf3g?k| tYBϞ=)Cd @5KU7^re'NU O?۷otBgѵ(Ԅ@i9VLxeO>Mٙea:'bZRRܝ)eh]v+ѣGu5eܰaTp:@dd$.KAzro^\AZcƌL ĹZ6B-Eg!aiiyK^xSN8:tVѠo׮Zo޼KjWf hqLL`jjWV`dd O HK\~yw ~З3]6++\v;>%bl. s̡]ɉoff&x-°F5]*\O]AFPlxx۷oɯtMcvvv'Nͅݻgφ#نuڵvY`ݦv\h\\ZKԩS7o͟# ܇\tYM#Gk/ș4i,q .\v+##,((>1 %̟? ee E`ҩΝ# tj\,uqqwh"OOO ]OhQOeܹsDDH'kuYOuu7>>zzzP}I{0¥̓ ٖ81E8 =a]m7܇/ |Y x&^Vd[AWuxׄOHMDD8O" ȚbE0D4B" acED<+"!޳":j"&&b?""$ՑR5!I$ҩdT L*:>D:D+2 L9Oˊl^~i:BkaQTҐL|+ҦusyS>$.nweiw@E Nрv)'ksgMz6qPn];U*5eOڰF"뭦BK gO&.[VVm9#Fi ȁ|X ExtYifpr}ً9j AduBu:dB[>Tro$] "j17-_I\v BBzGWѢ˶U\695Tbǎ2?x1֓Yؽ\̧)bqtlo/oL&܌pU˭ z9q[{t{1̔Xx~"[^&/' Ww]-'FlVF" ?' Dve¿qqqQg͐251 &.s=z  ?wesO8Fo(m6 #aKj?2"H]]0++il]e&Xn0vYe #\:k3f%?y6w貭ˎZkoN6N&Resv*?ݟ/[WlQvjAeFӇ!3>=-*̫$m$Sȷ^ \}>֨ɤ] Y_aCrق\v-*[{aArrYnn 0ܹ1Ql.{)_U˭5}m˶P222Hխ[7ɉ{2xe]+0z}Ԑ>B;nءQlq{6ui>N6OW&cVONv̆lZ7'g[Q Ӑ>BB\dEkXSqyVVLv5dw?[Bjm]Ks_B\VGG|sa$s̘1;..\$(((..o>]񋃑Txߍ[~]ޗ|ef4V߰xr;tU#\8.q=˶J߹f5g$HoZ1i/Z}H!5ͬV- ^XI{ǎ dX7lAvܸ#>Hoer$!IV*+)v!Gm>'7X75(+GGEbYlK.+))KU4($$mMHH߽{O<SRR7lSSu]i.Kɵn"(?C\v1O.toX ~/( Reh0]674|ceOcH~|C߶]@ߴf4!gfQϘ\kV %d$qY}=-^8suu4R|Kr#Y21~/\6+5Ȝpp3e!ܼ8d0@nݲ4Z- *~=Wd8? j'Ge Ԇy1]6c9W^v*3l5>gݽmh8ض1 6 <0-qYyyyx=pi2ClVV֚5kTUUEDD{ Gwއ~QOٳ%K_U666ť\ͫ ?~j*85GMM 8wHȑ#Ο? oG\\\[[L:0e;w̪׶n522DSSp1RRRVVVCk=z|zzl\8{H.0zԗ9>Aӌ'@;&F#ae` ] #vJ\d(֖t8cBJլE\ڍ ˖xUa%޻]fSȻ1P忴f$e4(ķS͍>xvgl:)}J $qYqcvt*ׯ }*%)e<~`x%.]!o޸4ime@ \Zo߾ k_PP@}xxxԳ9ΣGZ85,.[uGX:t(f^tѣG"|Pt,o.~6 /ˋ~5-tYޅ\6;;[\\222Lᫍ ~ A`ٳgӿ>}k׮󧧧S_k% dK.ճxN9c"""d1))vz N%+WUw8&wt,o..7c.. w^VکSҋVZr%$X#w8p/YKlSRRHի㧦N>SNG!;w!d믤5 lK. ikkkHݻL%-eeelU P-]GAo  2dـ/;|˫Y*i:mxlO<,,R? _]\vBwxڊ YYYԠ]`ŊYYYaaajjjw񑓓;Ç.eU&ך5k:ungnnN,K. 6 O BBB EDD;I0U=~x|IwUTTAFG%&&Ǡ{\>=###2t.z#> EZHAm.[? p߶m[VMMç-~uuu-a???G:+(ípϞ=lgN:sL~N>Bk\Mx[s_EӁeeeF.eӅ%%%CCCꙏ޾}[\\ ʏ?.+**Gv$󓗗&Nʜщ.{Yvvvurݻڵk*tٶ fN+FO/ayyy3g;Wiɩfk䨧رcuuuɢoΝ#""@@<b6UeM..ˤLTTdѪ$tYtfqqY䫠"H]]Idd$=̉'ܐZC]ؘnf. mtYtY&ddkGGGMM͆j$sY5us\q6jِBrj:!˒{---%k'fff|_ζJE@@@ZZZ__׮]yb AG2)h^|FqAzg.q @^kǠMLh?VacM9D\Ґ՚85k֐?e,eeeAAAdd^z%%%17૞=.55UHH~m^ZˮY2*K m˦~Ҟ.W?hvY~k.7c aaa999ulkZx~,㪪*cҐ՚y. ,Yh"q͛7?^BBGvvvdǏWZ چ jjj2/sΜg)//] gː@TmtNCeɤP?e}$$$\tzR۷oYb\ yth"tر LgOKKիq놻^zz:СCp]tŋ{›z와٫gϞAAA8]]KXz0N 3Ǜ?ii˚ v)+Y ?bϛUqD7O~,v(+t?mNoIEQ>:}5X=jX:]9qk"jbEIq 1h:zX_gE&ZbfmCDEe-@opsوZB:++܇^zumr,((U d0=z(`֭QRx|a!@. ;v`o>8zhX\zuoŜ]*?e#]d1sã]կC*P-҃cO31cc}Ƽ^\veGⲲ2^X {~ <!!>}ٳgi '.]D}G,lvv8eddeIʕ+|]ofegd/{{{8{lXMqvFEW]vDb6@TX81{֙ ;=u'c ֏eA~ocP"!.yԫ,y } ^I\6]kǠ2-en݆} Mdow.z|.{2-I].[W&y&~eUUU8ݴ޽{\ o%oe9&.%~lۇOWcD .G;T] 0#}MSߺhu:zlqD,<+6}ibu_6ۏ ׻l qYǓe}GY5Tf^V벥}kI҅ ¢8+Y '88Ç 8˒ϟ?Mq,]e-| QBO_pY -eS{oMO_ [Xc]@eUn8M\+$*C<"!&V]@Gvo|{W]XGDq>.t4,ԃ-k'g'<ŗR0rrS0g`UŌlVL]i,YOiͽ/b <g1S`0'r:3b0<;Ɨsd͔i/`N>} zϳi|9sf/4ƧڴbFFuvv6 l;v)FOIIIرs|٦t߸Qs7鲜owA?e({%/.CP@ 7$Qk-痃}ܮ];7 %o^9\U]vk-(F\vx1_?z>vPⲏ:lei NҸqkRH˚4v\ċW^oPkYs ?7x]>S f%ɩZ2]% q.%~˖zrjEu<7cyr/8lֹsAtXΧ]zͪ16!a-޶w- zzߛ6W.y㶺 /,zeDED 疽# dhq11uU;Xז.~Yr@eerٴ  eK]tY.py4KW%p|YNeerٯT-tYNel#\3Ϟϋ\Kҟ"w.]}`ce.%eᲙ!3'KKJu_藢e?$4e9D!Lv$ ^JRBKS="y0A ;m]#LODe^]K~g7myyeS&FM_LtY=]-~~~Gιo=nϝ=qz]eitqh˞9VS]vJ2vY,r tYtY&Vs&lZqH]6/kʤSQ|7vB!a14& l*_n%35s3AaWX +غGwyaa!ANfJd`O@dˋX:_&ZyIV.akb񱡳fN45. \\]]{̆=K7irZ)TRTX|mtٺes^< Tk,yc\,gO ,I<.}|߅5Dani-)H=d6.Ifs¼jﻂłfg&";+@E_behI~&,Ď0[U{t ~ؐ>BBs3"qق86y}۽(נ";+v6.~,JKK |0ZN,,CSb]홙!._VڇxAAX ~ \fO.A1'# \v)|qwo]2l[%qY- *=UGt zғ#ˊa1$ e3ȹr>Ξ5]hP| AQ]-, m]639`띙8b?w5j@=vh7q3'Y 4KM 8bek<Ǡk֜=LJRv'\֯]Y`zeߢ[O7o\SYqߐ4P?-)dVewz"SҦK\ 7|1?'c"h Uy.]/i y&G*)* uTSkXO_UUQ&xcQvSD'.-]/p>ԣ]v_y'ք._jUlEqbwq;Op乖37].)+)~q.8sfPP 8Y۲ltYm*oee 8AK-9Z_8-DBU,ˢ2e[&mρ...rBU,ˢ˲~@B...rBU,ˢJ򇄦,H ]]]i崅Y@EEZ>mDEEEZ9m*o}~f tY^-TH-TKfj<m-! -T,ON6< tY7h U9 H e]eM[Aj-TH+-T *@tYtYtYfٻCa4c4wy/jf/-.iH]K\mxL.}?LLb.t\X ]]]AXr tYtYѠ˶h{f,,ptY..ۖ]6-) o.@mHIk.;@AtYtYM.%e۲ l]!%.} i}otYtYՂ.%ڲ퐏/O]ftYj*)uekzWl沛V~AEEE,hM.MnNˢ6 ?edUӮ]NGM|%ŞɻqvFGN0I>d%EזEq;Ne"^I9i!+-P!/)!>dtL+N*cu#VuU&Aas3cW._CARRbH? &c`߾>OKr/=Wo?>6*֖F EE]Kˢ"懻Ӿ~6Cs1>NGnD ^;vE\vF f˖{{t:;p5dl2~dTGVjUuu4`=}}f]ʋ9e'OgNYfn":d£}zi5lfb<6:/;5tˋ3x<XV]AtY.\[.SM=0] ).h֜x ofO&+%!i:jL+tYn,At'v>={1_T2e6TGxe}ݞ秏\vޜ|e>ӧX?ٲp%E8kf tYH~KJ{=lij_IdaǼcsYaiHX q!^5ZÛd}lk,'q=ai~eD[pY.[|$l0`eM޸t9,m^*pSt߈/'-'< ]a.[YvYf.[<_UuˉelaBLv߸[ZJ7{lNRo_K$.{񸮎0` tYGΜ}ڹƹ pWl.+\] |/G\ӻl2qYeAmeIuD.]+eʀްgΚlgAEFYBA'g4;t`sg>>-XQtpD? RDΘ.[ ˙/EDC+۬*n:!.ylLK=e.KOg{vٸ8up]AtY.]""n%ݻ=G{.oȪ#;vspl\]a7XKoDGᅮ8˺9\VUR&994|)s}?cMvK]3׮Ɉ~wɯ ee]K&9le]]6=9btsii +R]] e,,heeaK@ExtY...4tYtYtYtYi(\]]i4Pe,,heye_ĿEEE]Kˢ"]]5p?~4PrA؝>. ]K@5t^1T~z]i#D̕rei;}yeڟvYiNe-b,dÇQO|3DEEEZ9\]]AZ?e"='UrNeSV,byI ƣC=zpRwCxy&"1%pҟ!_JRbٹ`nٰh`11KN0hؼaPCqq1]OoΘ6~6fc4kE.evg9#=9-kG ꭦ3ۻ]2;_YI˰>1} } ڲz0x/3hQ#O?WⲉLMCf߾; JOMDEo]Kˢ"HKǺ0S&'żx_vYMƏ vn*+]w?q tnbƂ,cqٸx1{^T3QQwoc1 1}Z"tj'me#C_)ýHg)s7c  c׳>{vo/pC>?.)>22ITD/N\eNlW Ӛ?o{nΒeOE1۶ee O\?e s!?"m˺޸<~hן[xO岎-τSȺ=F"7.%ee%]]]G1S{Wi^ԅ%rCiu8w.ݲq%g}zec#}jmm˒L$$se gsY v wYlEW]v͒pK mk.;HẂH5Ir't?33QnZg7` 3M뗆| ;w@vZpyAaA3OOe;IK=mbDkV..{_vf%&ƣ_uZ>eƵ>Ey)#((\vOkV/+-l˂<"wUmˍ oM=?.q~F=eˢ"<ďu&81J_zQO"\=U]1lM*@U_9Ef&,Y|9ݼKN]t=-Cwlq~ʁ}TUz{aq"c>q eEAWWן ?'+]AtY... -U=,\_\maۻwnJKKmr3Ǚ|PK\6Yse$͆\6sNI\vԑV i<:l.z.֝ E޸ .;s۲# ?>jգ ݫËW~~g:jX_5{Ce"@m.{5Kijij,]]A tY.vYQcM ~z=>_S⢢799Эs'/.[FmeeeV,.k2eyϟ%_! 2f\vA8h q:xTBL0 \֠:.ˢˢ"tY.c]vѨsW:k>rOGDܸW|Ԙ7಻S;⢢A4ry ߅5#{-,z$(Â!ಓƌ[`QyxL\JH?"HS...reďuY3=ڷ'Z?oA/s vk;d=WUpҬ6AiPğ7** cyE@{zouل~&#Y27<]AH''O/٢"tY.as~m|{%p|Y*..nel.{g¢iQ_eˢˢ"tY..[f͜8YZRbޣ"WAEEEZ9\]E,,,A@EEEZ9\]]AZH+]Kˢ"HK]]]ir &N4 M A [tYtYtYu.%l]e?0Zl"tY.a.. -x(̨̠",@EEie]Kˢ"4o.%eeAxtY7@ < ,r tYtYA] e,, O.\]]AAExtY... Ӡ",@EEie]Kˢ"4o.%eeAxtY7@ 4m6ei",LhkA{AExtYAA8AExtYAA8AExtYAA8AExtYAA8AExtYAA8AExtYAA8AExtYAA8AExtYAA8AExtYAxoݺ ucǎ}XYY8PXX477l:}N:)**9rdu%;w–kQQo>%%]z5}1_ٹsN ,"C4eGZݻÇkUUUC Ybdݽ{VUUGNN.222''ÇT. )W@ExtYAx;IOo. Hg̘1| 4sKpʠ 9s'HɓSN+ J߮];H=mmm:t@NpϞ=T.HuAx tY7@EhHl.'//_UU+<<\JJ۷Űczm~~>yfHő|KKKs", ߚ O:Q/x9@[A#fO ?axGG8ψYA$j"D4+BHİ"X*q#"&"IgE T4#b?G,ć#Ċx:Y*DZM$HgE2T #R3?GǚH'Ŋ ٬Ȥ#ȥ9r k"D+(QP9 !kD +(JQZEr QɊJ:*F| !e)͝Ã:I::-u‹Ǜ:CRg(w?H] S!PFN݉EQG1ԓX-zOy^&P?P^IO2BRAiTH:AE~l*>J̥|*,**,˩B媉AExtY....4tYt,r tYt6iI-%eXtY7@meC4@mbjJe]KAmߞ/wly[]f>-eq*6ee[ o.%Z*tG|Z*~]̍M"=e[Fem2B ^!ݴfe;)or؟^w#.4tY7@e,۳b Uz(16:=^z#:Keutw.]m.[UUꗙ#^! 9Fisf6dӚll *@EtY7@eȏNuŅvYوQobOc[$X< q;W!'ӱw voO( -)Eݺt!gcx7,?:o.K6#(ʁž;sd9nFxܸM\Ģ9s?Mm1E^VVJRt؈Wo~qp9>9Wo9}99Ӧ>?b-++fGVj-[Dۼ՜  Gl{AvۋH1]gDŽ>#gu(&s/"ˊJES7S5YS[wڟyM*0o5-m".(]de]^'( I1O^0]88R yr+tlYD󉓰vFivplQT2\KWc_yv v{Aiӳ"UK+Adh{RoAguˉ]bY6,. "[>V9YL7a6rƄڰ%l{5׷) ՛,f6,~j:47JڜݴzXJwvS?Zh:+Iv.˭lΰ}OmY]?Y s߉]wa].;t-l8qDq^G="@gtAe]Kz+/K!YU! a1ó۟ s=úv:x4ʰX7m ѻgzuG6,jj>.ŕ .LNWHȀްgٹ@`9?g]ӺTed)+*> W`}pεS5$o~CRQKB1inC6Vy0$} G͂ gt}O 9wWN{WhS\&} f!c0~2;Ae]Kn%t4yZڹl ewW[EKfYBm9ec 1]""n%ݻwЁ7ͣg3a}6R#ٗ]%nڷk߲d*T)=dir:NS3g4yzޞyF݈{*7I]vn\YY9J\v@ֺ(3@V_vtCRZ,/u{m9߃wtvg`7+./ep*.q(=FR.^֑7iպU+ey|`ݨJm̝0ݽٓFIW\DnT$% _dc@ޚ>b>FVQR7='\6zN.]]ŵjlL\ r,4 !""*z~V;tRKQ ֜".;eGTilFN~e&L.ܸ& "&.G`þ >e,\O? a!>ym&$>',,\Q`p,M3=x\.%U\a~>rkn03o>Jey,\.겺}kjeu5%e \.˃epنml<öyf [#Fgnl4j]e<\pY.ekʎ7+]e<\pY.ek,;pY,\K߹WETy',?tqx6ώ#]z. ,p¼,e1/[pYv0/yY. ,e5e\,\.%pYlMe. Aಀ?r ,\.[Sveفey,\. ˲Ś5kZbŊOF65\.˃e}:{n]@Ky.,o, ,\.%xe+}57lm.k{\.[e<\'GǠZl V?y{Z/<]V}0/ m0pY. ~wY,\. pY,\Kԯ˾pr[ Y#G/(X -Zg/('^UfjHk~_e5\=c؅{lL+wy f޹bYiHxk[9iyv۽]VGsY.6im$%mL"{VL  "6*fyaќ]v]:쁍[w5gp˟߾YmbW;.20PB\5k/1d()!ѽKKWh}t`W$z. j. Aಀ?ruF<__{!s]۫ߴ.+-%tMh?/ vYv.n1e?'OxkwW;/&zMF 3ן,3]VUI9{[.o׮<-&biel&ޒ3{}Ҫe$["YìLrB"ߤ/qKvA]]6#$BDX$W(/sU;/K\VQ^>GOUUisۯ4іUkee˟>'.;8{OG. 8 pYe]vmX3š{&.{s18J!j3/ˬ1թh2a5ٷ57ˢHe,_MN"չlaHyqX ]cAD _FVXk[GÂ**9@D0">21@JB2DƊq1 .eŪj".g&eF6=޽O, u, \.˃e8/tlPZ2[[wkw).*JK MڍL14Y7s.6d?{[-.|漰^,>TEe]w],)2rߠj]6;{?E\֩JHϾsBIw2ˆ^fg~^9K3]%e]]c⃯yI3esegpY. ,Gv^ )ϝKʣ[@]vY˺nt'w…<.H(OJ߷lU[eCE >IbKFǑG>{ޯhjK~RYye!ڻ,eu\1Dm,\~emi]|qb޻ze; OȥCp5#ಀ?ru~FwPP$2:k] ӮH>}T]9aV']} .Kb *vWsф)L ?{gBB} "YJKIL,<.[< oI3fu*.*f}əٓ+?lޭ;kO3gwVU#zc!pu]:nM ]c~ͻgn>uuقa=u۵aY&. #ಀ?rڻl?" [Fc*x>ܥ۶EY\Ke^j .نw`. xGW,^[d=ygw`\(>}1&ϻz'^JہoK0אe7!f\4{w#QK^yheO:>;oC,ue-?7!f \+M1;3i,-$*#|*X% EG"cF#5,Q[<"[}RepY,lZJUepY_˪((|v#[fe@..kߧ-!]T;.۷/9˿pY,eey/ X/bBBB)[({ߔHJ3sJ[,-?2rh-?bB[))+Sb۹R.]Z}՚1[hny6X`KMe{x{]T;  $FRDo e'ٍz6n4Y^>gYb;ubFP8Y%y3.k7yYݼ%RqُJ!.OVdOa-0%kʾڤ^K}˻'3ݾ˸-<=<eOyu/a1=jHϿϬgV՛s9U넪MϦSǟO"t{e1u3 zp8rNSNthOo2F{ ˊrdz$YG㆏ /wfloM5叞u@֤^p' ~Pce.o1x(/Z7 buuP#cz|ݺd9y9|PF5'4ۧ{mh.[ġ^vRKfϛ3y Yػa\. 6ˢC3M,,]cP'WyYc-լⲅIJ IpY,\.ۈ.'he˖ftZ/+",z;zeQ݃l^?Lu7!W.;Ԝ9y+k+s z6.e?粕j.]Dnpk[E99) C= HIHN;X,(*Jh޵s@wj@zpY,,k MҵpY,s*GʨzV‡G_ pYtY⼬nj.\1Х|Νehe\qkQ7Y p٦ֶJr .\j KWI}H}YaetUi\e^]֭m,&գKz\. @. ),,_G7/6. m2..AeӮ9AhV7ﰴp[ g$y"GN:0W2k )c_e HwpY\Yl<öyY<.\I-Iy>F՛5Ө U N'Ze8l$!,.wvrmYY'ekngtA{̟1K epZY14sm_C*[8Ӊ.ER,',vOL-IಅI$Wz pY676). eᲵrٟӨO\.Lve]CDw˖Ml^jo-o!.;Ĕe͞.;̬pYl. ,e1/yYlm֪%:Ѩά.Ӊx޽kG.VcP9#]6gN_{<]e0pY\. e__pYe;pY,\. eepY,\. \epنsYn<.s.[RRboo/&&d1b]<{OEP;ಀ,\;uڻ=pY,\.w.`jjZ\\$!!Z?>\vrY M [᲼;hBSC~]̌V\\. ~jHHrFIl_}~fvn792(+jPS#z{\m.yY6yDΝ;^6.pYNq))nmrJE9FBfq۴jvɆZ:⢢:u>{R9y)qvQb@14!&)lXN׊U{ϵ3Hw{P-Uj D&:NLVRjqK!UTڋ5K|ejKZ.tƬ:dp}8vL;v&ۍzBoLSk߾ԬqAX3}K٧ սG[U$%gN]7-$%ep&처Sಀ s) rv]:a =ڙ[ 0̹zM@ S{BW,sNNRL|/"2(u߱u@ѵ0]qҨ-˴+'+ۼ,70ѽ֫Ȅ7{9qÏ:gKe ܮ,PK%/=ý,93;vդ_kSQfXӃp!z}_%?u9+?v׎jft6Ac) Skt|.(JUG.(/u'o %s 3i^bpNBқ%hkB UW|񚸬j|`¢i&JJH}r>+$NT`st. 6a#tpY+jk(Lq9W|EES\/^~d7KTDUe.ZFA/4P'Xٮ>X~eP_Ē< d,a57:gOqhP52^wT0rz|M(j do;u^YcЫk56m>[Vf͘8j h8R&ߣ~i ")nY9ݻu=yP?Oqwر+esR'2^e%%NpY,\ ,_c`iifel֩u{f;2;JCa`݇huݸMN'NPgp$z@u߹WG/.AZ:LuݲPbReCΜ*Oɨ2z>a9Hwivzש!G%yNJ/ 3ײ{ԟbG\Lg]L1qq-]vots0![zzj]6˧ ˞;ze](;IΙyZd"\.\dرJJJekrYhŊ\J,`,_"]o/ ɿv[PPo(_q'~D@=* +oJ>}9FeꗔL{$$JRk7ψ%zRs7|JDf^Il0c'KRg[@K鲫;vUu,=&cw'I}t).d߆-˺rbd7 gbcM~'P?.qYףeQ/G.kpY,lv?j,w.6 "Hs[C w3n{ڹlgG%GqSUT75_*9'eeYQqjWZP@^/z e1(~c5t=Θ2Ԍze33,\ڍwY*/A_-Mxeg\. ؁v~9oQD["s=F.0J{bv,]*ȴk)"_Cs.n=;ubc:2a޸ dڴ=c!)m_˻vmZG/σGk=nYeZ?vL#&Ӄ&LL$ut]*Qߝ΢.rAzZC/{mvXQ?1Q>=z:mQm*hl.XQ^ޜ(#Ǟ\%K/Q詁5\dc;QQ5*?n.[DUsfƝ{f>eA]ve_| ۯ3F/[u]E2m/sn~l=>. evpنvlؤDg6-=xŷNg/68ўӳ/}z6DGOBL¶]5lݝ8r:Ye I:co;sq9{073Uixxdd&#Df^kiUZiWD۾teՎvMj;KMvs!Eթ/Xh2Qk.68HRB{.nzp܂խ-۩?πeS.Kke,`.[7u䲕fye uؾ= zu;&._%FKsUuzȾo yYz-s .ojZB,Z&MelzvBfcP37Yii9G>JEsD[&yeWUXXǠ.[4\RBgnm deM,`\ϰyYmyY>gv,q[nyEK̓9I}.; ۴EYls. ؁ee7/[AY9?()tzeaL4&|J*sGx%9w5w4,\94-c3HRLZKV1]mPW讥NoMTSVi+)91 'Cf2էڹoZcٽ u%ŵ{wq#C.Uj Di7y݋xzdkfWvbwvawيGvdwk׫:'\!oAST'ypkyk킅fJKtٗi@(:Ԝյ[K|˪(*ܼ1H 'y ż,\.^mڶWRv1]n!\.vi2f+7z3]j>07Mڣf\Au/w>Wu҉StQ^߉vYS :yMDK% beuY~F# s66erū,n9kkArJYjf.q3%o*\)M{e vd{OEydt.de"kQc&.*r6_˫g29:ttBVpu ѣllNd&-( λeL,\. ,`w]GN7eWo.zL5Mw"מCQKv Ci rr1˞۴&rL12]7k >suX-KOubA=ZD eZz;iidLuڨ޷krs䲮ClكPؿt3K4|Y,\. ,`)lyـCδ_M5i ޯ/)vHKsuݼ&]=c6y|jee˃\]$$JSUfus.󲨗mj`- Fee tt>. i .KbP_1C}ŧ_>z&ޥ!gܨ&a ~N>f#^ʴi|ʵ$"atpSœ~"۳ۚeL?tjeN=/?,{n_[ YÖ\r1'\VW]ܘK=fG&׮][6LC=漬c4pYи_ /K\u?>.ml=>. G]8q/6v6Q1%.Y[e᲼\4.pYl.{+ǜK2oӣIS []V[&jjR}n%*rR3GB"l3E@tEm M?t|xZ˾ Op?QI@vݾpIt٧a1SISCc튕f&ݻu;7XppY\.[>Oop6m+)9N6|4/pY~rY5%3EMItQ/2Pl'mz&:zփ ,qLYH[waх.kg6jq 7qK&M _pY~Ʀ"  *:=W>4 (`YeIj{MVIIHTFKFΏ˖uy尨k7泦0k+3ȸ7.=Oe#n~&iX]u㵬ݸrN_ͷE̜ebHXΝ;v^eM --g>,H036&LU!&$ً-7-/-6 0/ l?öilma m.NU]v¥t9_P }٬2]'5//t=^M5$ SӴ " }Cpj}e=vBg\u|BwBtKևT벫g&Z.]oQFwFΉ5bOU]=H%QW ._b$%SBhݲb:eHڸˑ:v,N`18GqvTO)5j;]0v=wЩq1uzl. ,\. yY:J5~}3~"ɻcgF_,"hMً'\^nE<ⲡ\Mџ vqNk,}e%s_ӭ.29j]yNf٘[3,#]oҳo.((~5z5WŕvsTΧ543^ 'L+3g]6maaaf6={A]Pe]OS+ އ6 ಠqepuYz^6˟l@ųN EE׽_ljee54 UԪeq1 H}~qYCN.$5]HH\Iv~6o%oqv٤09Y𛷙i$#ډ/۶H(}]i˂_. ؁epuYں&EzYFt 3;a^5#,lL3*J})R/kokb^ER+eٚEVƦtp!2mTkIouuC.v)أ$[!w.L}:-9v҇y>{5ln\@k "}nyI)*JJW\αUnr*7?/jLnexނO/^2fUMZ ʬ˂_. iLM$7A "*Ss[AZɶhe?vG7ײ1( _:yzF};} CYWtzgŌrϢi pk>+g'!&K=•=e=UXX@[/#6m$$u5%bɃsuV(.&FvyYި 'Re]Epv}[UaڴYANe˖͝t=wNAqVθcd۵8nܳB,Gಀb^󲘗~Uz|@oۢEVZ/!!^cvۛ^ɓ1cƐ}ݻIOO'%F[v9r$y.\XYepY,\. mv+))eff~ĉ % 7of1K'']~˗/zzzӧO'򚝝h"Ayy,XKݺuqŋVgJMχ6[pYlvY&ϟ䑣 M5AٳgyRF&%%1uֲeKz4((UvHVwȲ9YYYfffDakii++۷tVe;pY,\. h}vԨQ+Wd0/"111eee䥚d2k!;/_&G?}Do޼)""RXXXYepY,q;v<~իW#euttrssƍʄveddܵkc"zٜ^˗/_xӧO={vԩÇM8166Ç666Xtsڳb. ؁epY@ݻwWVVVnӦ}qm۶ѣm۶i%K͔̙N\|M/zK&L`ƌdDp܈㊉)**wYa9vqqpVe;pY,\. eSSSIIIqk,`. e]ϓ'OvppGಀ,\. tٷo߶hѢwUb¹_.Kָ;F֬Yӂ+VpҘ.@ jpY@eծL%k;)C42AWKF?;z܌K 蚎@4Xp)m3Ǐ.KO r?} Mׯ^Z34X)zT`eڇ|gw3.ϰRs:|W}[yOM=)?.kUO5o>cl\ás[N K1g*v~Ib^XpUW%dMcxek*uar%X4}k{͸\. OlTqdP#_2Y'xl ?&~3&ؼ/{Gen|~7pYlq٧6|zÈA|%˜.p& 'r%4oKތx/%,df\. m>.˽s`P5m࿰ػs^gw3/jѢEax \Iպl廉wp& j.4AiQMONgw3n|\VE^$yx7˪(*2*NvaAapY,+ T \i[>ӧOՖE_ln47uݱeiuwüK.;ݾd?gE\!iܮj ؍|mnhSe hWng.'gw.K;(MIIeE[*ɓ..(,fhUee6mM҈nmcfnze>zt&8߆++26qtPV&:gya1l.]Y'^[e^6?!~B))ü|hzC)˂C.c3zavbhehch`93d?xata0;˰8/cy vc/3y0ʘɘr1:cc7c Ƽ o3213Va` bf95#Ocw$c_4@ P,HXq>Jj:w'q!/q'c3" QEbFFS/_2r^1^3 002=FGF'Ƈpo\^LLLYY{1b>H\A\Q`IrAj nMM^kݬa. \ֻI^$dye%wQ( {\N@(1ږ""|R3zvB6ۿv{se>e=dy՜]݃.ըC^ &FKNp%jWcƾK Ș*5L}Zn/^ΉM FRN23d $]vc. 7.`jjZ\\$!!Z7CG߿߽@^6i6>2_fܠ,V-[lEFzNdYQVǡdYWoq4IS=Nj1H~]zޟdyָ k <[1p@bڌ\Ȃz BeӇ1p9ڭL& br<. j\.p ǏCBB3*~˲RJ;;;khhͼ\\y`9!~g0t[/#/[ ٻjY<АCzdyq~ɴ1Ēk!Mhݺl%u!eYyd%#j;<ٰڭ[Wvk9鲅ˮ_: ಠek֐fff߹oT}Сf?pYv=y$'NrpY~qtkOe٢>g'+.\\e4~z^. ~,\MLL$su9uu.[J=zt߾}tY@? e]6cqTYeESoVtYstIzYaTW͙^0aTqafdm.[SعcGzYԐp,95ٟ{K)9!amT5?loR2rBBm;v8.żOS$5{؁2˲yX>RGyF1 3ZQV7vc0yJTLbcP'enjMQN^JBP_UFAǜ#[kVoݪ₩?VqWu:Jܸ1P44. ~sY)5ACmϖnS5d'a\VsKw]31w \.G111n,9lMԻ<. ~Og$EZY~^c ?-t}vY1K+0ϋy.xc5&=m$j v-S(uhƼRc`d55o 2rц}eg-YܡUk>Z[N.2*>k֬a_e9eepYP~e-NVQَ] Xqc*.lD&eYe8r;Y׈:29՞o o}=y幇+W. (Dg* }2Sh;yњc7.-]v/YU];0T[kGQ;;%&ݕFOφ8|%%%cǎURRb/[˒?&XaΜ׀r.ˀeᲠ.\AQXGdv,^ɲ"{pշ7~'YН`N}5,ǑvvMaݡe:J\fVB{5ݮuʭ{o=t7>|\géc`-ۈUm:jNj8eG!.;do~LJAMHD7sk fuYE{[a3u9`DzTv<]/;yFr%#&! %.Sd(s'*|eylWpY\. u{ruҡky^r+Y諾 ς"sejs5|*uh}3[e" e+ xF0K.:|GK ~_k<\+,. ˂.k~u;us%ׇ{b}JD^vqOe <[9J\J͗ǡsK0sX~<򵺁CםCy=:}>L=k8y޲a1e&P-Wo;qM\Ie#S!hp zQ8e~'i%*QHk n7qRM1CϺzYL`- .K16|>s} Mh+JLsNNt*rVDZVM^_.GeºF\HwuVZ+*1cApY.\yYb^ԅpY]vFyBBQ/˿e82pY, Me7FU ųlWpY\. u)l>BB½GelWpY\. u]ϰCG5wN]0 ,< fJIH\YzY""7N!"[MtEumlY?Șvܨxjϗ}uSU]vų~zeZ+e82> mVD+g]'`@CI1jv-ZFֿ gT,,$D1Ⱦo'ZOnNk60]ѝ0+Cc>%1wNuܾA_k\odemVavb׎+ew\ +KT΢պ,#(`C# q .9g͎1g &*ڧGOm;5֭Wk)Ҳ7{v&,,\qp[o.1zrǏmߙ+Yn<\+,UY<&-%U/ϰ{YmبP;|%?\́6qp%9e>j%-%^^qwe\8/ۺU+Ey._7!:u%W;|%dMch|M\Ie䲵y-\5x-euLޠO 4>p&$ ,\. ,^L>0O 42<+Q݈_Esh&eepY'޶ه;!BII6bi#g9G].˨oe0.[|'j#.k,\;g>;q;w,888IHH֪vp&\4_ pY}욍prY}}pY,kbb"),}K#VB***,W\PR,]DH,IH$Kh={Z#TNcL%xμsΙӨ;3|p''>gǏbbbyG X˵=j+8Oήp3JCǏyG XRXveX~YsFmΌ9Ҳ}XYoKz{(N[{,XWf۶mfggg111AAAwޡ%''' r>{jhh766qƥ5559r[?D"9rch^‚xf͚wzULOOG''暽x񢢢bO,~Ҳf:2.ֵ#&XN5%Jc)k ,穴<)Q޸eFqMF}dGغlSB%$ I>n3Qo8RԍeQk-abb 8 ۔kd)IR1lAEcܲhW'5ޱm/:t_V5~Й%"J?[:BDX3g*qs;ڧ* O=z dY˙_4u Y):u𐒜[655UBB۷gϞ :,ZVVV^^6\\\(Ammmmkk\|9kiiֱ ֭[kDQFi (8|1vګ B(oXߏ˶g_ J,e,ոlWUלk4.qɭ JegL5Wv7!>jB[VZefb*B.(=TorYDlC+ ʈqւwd9FeHtaC)-ZR֍eZ4Y= ˒5TټUTXi,e,lSY/O\ܲk֚¸,={fee<388 QPP=pH鼼THÓ'OZYYaC .A`]b˗/f͚uQVWEzYbۀe!,Z,۬lz^caT^lɭp <<[~ ڨxersg_zYͰ s>Jk GUcc *F\EV'SEڷq7-xc9ca;w5 OӲznY.^lB>`8?ڴ{M8ťljj*lFF)9j(VVVlQQQ7oޤm>D212200mLK쯪6LMM*~f"v:1Aym<`#`Y,l^7r\?{:*jbs9z;ec}i=~{eΝf<5$RP'<}A6SMAWqY7n,t꬚3k#,w$neUIeGll/ KgR:ս_iLLL!g3ڋ*ܷ9#^yrL9_ܲiq֦gӘW^yzRUʲ{DCzNҽeo޼ZSS͛9sbUWW///@vBiiiBBBȗnnn;Y-++eƏooo_WW oo'N-^899̌ GGGbӫL?~fB` X3˖_y\ˣx/?aٙzS\)!ֲJ-;h)Fob*nGiO euTjR_`X^\JYaKZھ Ir,O1( lOv=lyRT]i- !IJZ[^>,,7T=N~>[̛n6MvAE|$]ܲ3_RR#$%MΜfbOUi-;~O5ueEEK'e$[ݻwNNN|||gBݷoߘ1cmllaW,:>})((hh"юX[[" rrrg)atѷ *ʀe!B7`Y/k &(07^i݂ńe. gfbY6קJCu48ٕdGynIXix$=y Z*rcZSaӻ6=q^1Bz m61aaWfԺSˢs嚑.7'א O"-4Fs~mN1a5lV~Ӳq7dG133ScZ\OuXcM&Ȳ999322q{zXB,~z{ݯw}5l Yv`EnĢ_~էf!nYװmyLP@s`YH7Ŗmmm]dMB(oXWY0o_FFȀeڗwHIH|De!]WZiȐ! Tw}u,2`.76b`_hY|N~egcs9 e!,;lذYPZVQQCXX?|^kBuuXғea [o,2eOZmTTThii\O. |oX, €e!5~s6f !C7.-- tvv\jջw:)..622ᑓ ?ȑ#oݺw#5w\bJǏc3H$ґ#G3CxyyMMM/ZˢK93F[x;>a„uֽ~:??6z;v444\vq+V`D'A=Xm׉ZVVV^^6\\\zub]tz,St,6H,Kk nYm^;]eU;&˂e!},2ڲx^PP%##C9[2mOrr2??Ǐ˖-spptNz0y1۝`Yȏ, /lZZPaa!–ې!C/o޼ZSS͛9s Ymڴ۷DB#eʨe;}-ㆰ lkk?~}]]݋/O8^KVVٹzYPҳQWW///@v1R^0uz, t,ۖU5fI*j<\j5qt{];rmeNVkrsr^f6InR.mqvmY3__ufDĉ s-|ZڭTo߱mז J@ad٦\%Hf *qȢ0tc;eKeJk)e1t$dY)qeɋ>!/fkfh.7W權S&N=bd Y˙_3AYc`9k6S+-JJEWR e!u|||7!w$ѿ ||gF2A"pss3& ?ӧO0BRRRbEv-DI--1 BENo>occC%wT'`y }|3`Y,+uһa\bqքA$eŅ{_jE;|mMN&`Oe[3CdIeŕwރbBϑ;ieiY|\~Ʀz"5?qXBMAm-,O+;L1*G"D&^Ei*V/CXmI@VfSeIRRI5TټUTX<@K=.KY/K9.T^vÚu&0. \B(CGK~֐ XeLikQYt5.Kˌ8e'U˩&ec4Ӟxlfi<1p^Ϩa2H.X, fioQ^cVP͝NUcc *F_O$)ooZoXΜEX.u~izv-տ^f-5υ}.e!+`Ye}@XqY΄etv,_,9.܈:*je/OX6yԻlQ59}ITOCeN3RۚWD3A<88?_fe}wc٠S^jJ\_ά5߱eߗW㓿˲^y, \B(ۀerXXʆj\N:0ƨlwMȦev▭rXclr{jn][61yUp˶eL8i4 ̲XA,A>3UF=fdd|AuWmBSWcV^{q6gm˲03?OD -v>nl4旴W=wwzRUʲ{DC\tio{ .`Ye X E{z7FĻ^Ly=Hs-]"KtjNg|ޭk˖߉ ے IB5#F/9+%.?P/O.oF'EF̧ޏjO02n-.~L+-y%WCHRʞ:wyW18u;>泍MTħuT8 ˖'RZ#$%M46_紖%?j:²^EEK'e!.`Ye XnY\g*71/nB4n_ZKu[y7e?toY7 u?Vnns_ vc^ndesNۘhr4^,y0TYwjYbrsrS8M/[(Aw6'9ϽDb"3k٬eBoɎbffY6Q_ǚ:qQQ]= 2P, ߀e^lS}>,Yx[_sY>- uumϞBcʀe!,k¼/222F^_m{ux) ,2`Y,eò\8.&.*zJB(oXo[ְza B(oX, €e!B7`Y , Baʀe!,6,^!C^ʲƭ]Oe5}\e&;%bٸ&OfgcSW{^Ne5T|NB4`Ye X̖} GdnWͺvkYvrYsA]{sDeo_츩2-e~ﱓ|;6eSʀe!,k:Yirsp^v9`/*#!j]{b6ngw"S<\GشIrYc۞Geٶ<ı :ZYOPQCmJ̴]$!c7"<ֲuN!"((*(dW ibRmZ*#5wȥ1n>KUNޫ4z a٢1LLL TݿiHҰn,Z[StlqOئe6$))>^3CTܲfS?'.#ײSkYS<\,+%!Zn'rK*eͦN|fi[;BmeU;n=ZV6<圹_MM˹Oi:ꡤ@@(+e!?,2`Y,eŅmE_e[3/%>[VWU}Ժؒ˶J^c.\YvbՔ۪㲭U ?qʲ1qLNig,^GvcY|PezTg˒Mz\SgQZqYzYq٦:^elכLڿЏV_,~3,Z~6+N-gպNk <6lj,[r:.<1xy~yltfJkٚLLetmNά~NXe"nfQZ4< S~?.ȹy=|XI`qxT7~Q]d9-y%B'@, + KluR:S2ꂶg<j I!1~WQg-{!;,Gń?^>=gjkk7:~HAԔ89?'Zz>.qy)zfeN- lnJ;;˪~DE?mB XBb}`Y?}ekDCmLhOrߤ8v\6ӛֲ112>(=-q+zS9qy7=0!'8 Y23gO7S\^e[<=|-j' M4nfݧoh-;su4 '*"B~soX_ZKrg+ xv }z;t1pXe?teYCgJX1!q\ȯ7yL‚"gXx[mr֚%$WYI5lܕkr#F231k_&."b0abWsrвMy%‚l+v\zi'' <n۝DD݊UZ[Fz\\geKStOPzž 7us }- \ @ },~?m[wH`E-eFF7˲;UJR,鋀e!,i˲u~e9q\]\LvݯYf7ZA ^XBb`ި15le ۵kׂe!A,~`YlG XBb`Y,X#`Yd, ߀e1l-q ybYm5u/#`Y,B7`Y,yrYl/Y`YB7Dz햡=SJIOY_0.@XXo2~<;'zŲq7oOӟƦ<Rfo[VCUNe5TKڂe!H, czw/lO,~ VQӜ_r=9#Ҳ.:;lLxW{$/N`Y,BB7`Yְݺz'heQ¿&sqpˌH<h(/ ym][aB3AQA%^E'U-})Z<\\Ʉ-#IzS~c[wLYc`ij6KǪ"J=+:*kr&RGIf ȧ){bn²MyK$%xx ݿq yڮ-Z[S&&c .!IIU$534—vٸuײ5u詵ˬ)k NBg.OIGxp:nWW\,k6uڗ3\nkGvS&ps) k9{7MSr\NURP ["`Zv۶mC(y_S@~*`Y,exxNz7aYСI"ٶCTD[qYzYq٦u<ܲ 88zYYs/Xܕe2312p=[}r6/u?7''Q/[ÖI˖Gᖍ }[/[=LKE:jH'Ḓ :奦ɉwhn,<뙵 :[:]MNeggYY-&*MX] X:Yt:.ce+Dv:.Kkj),iXɳeӂC; -[29'BMv ?+Ṛ6#)OcAI{u.=eaay,+;|x?nٴQeks gk,ngfRWʲZT~O=w 7 {  XBb}`YA^hOƈx6pr?fY5u'?˚|,]8AE3/Y yò6ZhQ/҇1ƪFS G5g$ZL5n+{g6m%IJ}(0617Z,]a3oHȠlܥ@#y Jãu589FBXiLdy 3W&qqpj8u1@m-<@c^#Hø89So+eݶaeaagSb^\EN15Asi'prp({t#j -&":^SߴlܽrF333ScZ\SZe?~+.&f?Xdg7XBb٬κ_=xze [܊*Ⱥ_XM=ѬU u?w ۖ.\bRRRÆ |INN>|SSSjA~e!,;Z׎oLȼ$;zteY.322F/˶74޷_JR`^ e2---"""w~muu|}}l, ߀eѲ/c-M ]]jYe uB߿m۶;Ǐ(ev)//imm=n8SSӆ?CPPD"9rS!Rp͚5ׯGVZ;g7XBbmٟ\ök Z,5n ہ-u]ci``ٳB!!!99ׯ_a„u֡NDQF! ;"}Bwaaaܭ s˖-TS}R.iݼ:,oodYme/=`޲c`Y<Pߴ ܲmmmuuu/^>qD^^ŋKJJ ̈rtt!oo޼ZSS͛9sR]g3".ŋ=y_d, ߀e^+'-_]YVCAgNe5|\\Ųƺ ;KzŲq!'ps+ۖPQ9~qY UU`پ7-,%%@;A:1XhhGhpq999ѳH… w999IJJ͞=320. ǀe!,ˮ_jV7?):漛1^OZ?+ʤ9xxw;e3ddd |zYt8B~M X5l.ɥ6Z./¿&sqpˌH<h(/ ym][֬cAFRem;mFw(PXP<5VAVJT쁗/n8"ȲϢ!{beҞ$!c6٠"*ֲ6ڲW211x@mzRht9IR|NÊIe >uC]7#ײUSk.1;v<1 AVJ\A5ܲY䯮h3K\8㨭}I<ܣG  F=뛦IYc45OqWW ,[I.XwJkk%Klllz~gkϔx/B7`Y,+uһabpHH6E'-aŅlk$}nRRveY] LK£4 626՛\0!5a 5Ŷ,>.\Yv)e 9+V)*U㲭ew ?vʲoݡlk3VV/#Xo5ud?u,iФ{j8miM;.KY/K9.9/Oܽe51 㲿Sw]_|7m\gb^cVZ͓}/_,}5[AuIj-۾iYc~Y ޵1x]\sˢfbKKٖBCΟ:ԃ ,~`\k3aY3Wwnل\D9,ƲA!8uT{Ų88z1ec/=ԹﭗOɜk2]kJkqel`p'JmOswܪ<rWRR,iX3eӂnv:.s led##Ĵ 7gK"3=ޯ,&&+jwFa, 3'Ȳ2C}/M[6'|f%/]jktoY-5,K~~L:g_ɦKo_Ǿ|֡me!,nwg'Q/c-!̅Ĭ IegN6_SZ.egN6k٧|ܲ6&!~^VGMq)UzY i E6g%_ia4%Bm$IL #cJoF[_ݕ-!ОE햏+ ǪWO /z=`٦fo J*Rҍq˖|Y{$ߴLSk>վL~zlQYݲIrBQO_oXXv˒:cU98UG=}ǠE|NM7bBBsU-!,w!@nf&&y e||~cһJF{EXid><9W&qqpj0, ߀e1 ,>'W?˲ݹ;?> _,~z۲?-XvPX\àom4qg_}, ߀e1,X, q1L86&wSĔOa*1ɚ^ظsxolyLӿM /b.a&lellv 67[y[YĬCð5wuw0p>!mœ]1K,|1$x2v2;MΥc30L/t|'eq X˂eIWU;e,~~e)˂e7`Z(`Y,ee86TzȐ!*JkYC\`Yz Xo_,~z{^,b;+p\Mj(( O|llÇJ/aMj()8܍e,LLL}7.,{r1.q:aA!Nfy[v͊qل`Y:LY }u, ހe!,6P-K^6-CbVێK131]?|WZveXA+pOZr\v_Pc@eY-NRRQrM &Lɸˁ`Y, '_ë-Va8G@4Jo,~z۲uwN5\2}ƫnɥ6F>"nY2RT.],Qc|\)Qvo*#)v};mVtEEM$$y&W܏~#IX1X>wx;.0l;#WYzjks l$xx̦VY;Xv, ߀eXԼ nl0_~.^c> eyg:ȾUa٢ЩSxlfi<ֲb5qxA[V!/7wvH-Eרj ͌^},sIpz9KXLGo*,IH f l;AtTi-{!ߡ'eqق{A'88?_&ecwr|znY'p&ܼM&zYY,{=`>ͯ_cjdhڟgG܀,׾h[Fy"^c1mzx4l).ĥA kA='ò^ VA}Bd9&>Z_ìmPc0oXZqqYĽ_mI~]yҺ*&ޞW0)ȉic;,LX2<䟼^vD{Aym|*R]ƌ m;UpjU!,[(|i={-[N~ enWZv56ڞ|Yvڞ0o=?Gh;7m&|/[o%Q z?E>ٟۏDc ߏӛFDRZ&4,;oXoj=˚ND EiJtukt:1lW8F5,K8ux.s vߦi-l6zyļ2=zXDCXmv'13/6ȅ6Dǔ zjXv, ߀e_7XWaKe^\,K81P7^'"-xyϤO') I cedac6j݁9me\#e#J>np9)."&9ٺ`A,~t`!-Xa3\/{, ߀e1nYr#e{u\{Dn`Y:o`7'M~e!,˂e!ߓnّLLr}c*M X˂e`,a[, ߀e1,X, e0`Y,eeUռ\~̲3rSbeв*~'Ne54]7iP>^^3c㊜\hC}G;PMp~3,/4lذ_BO, thYި ]׮GT[VFB27):i $]DW]-.$0lnP(PO@К>I~Lݨ)Z'#޼{}rv_7e[ ӯʒ<㲭y7n?|_ԥ<׍[y3p˒]79rԧfucƦ VVQ,;N{t'%5Ǫ~el(pݾ>YVt5yU9S&mܲTeNlFS2sk٪Ui{@˶iHXveIIѱ/ti_3X']k״988͛WPPձ`Y` X,?)="`Y25YYt5.Kˌ8e'n#vS,21߲mDN[u;ֲ59Wo>*#ڭ1غj <;[NA9.Kȏ=Ǖ%j eG{+V'/!)7~dI\"Gk ^5[mQ:RB~L '1|RPAOk rȾ'_g~!^c8;>㠛ܹeۏW~ ,˞9sۻ֭X,ddX6X^j ee?*`Ye:5۱|nYM8JZϚ~e.231ОÎPS'i((.t>&^F-qRMAꚄeRXluqz;HXl캲l*I˖'~e5:mDd0rzj")-{^'y%A6X.Neq˾=Uųm۶u2+GNNN@@`ʕ>| xxx,--[ZZb###ԉ D=s%?;~~~///@7.-- BKe?ԆaT, ɀe>Mߴ,^/v®e+DR~esnNԲ1 LL!ڟoަ=V-[8)rw*jeVt\i?=LHadd|KuոlD`Zi}㲕ٝVft3.[G {jKg M5LO,ZVVV^^6\\\)STUU ў;wuoܸ۷wJOO;.#W+"zzzʢa`Y X, cв|ўލnl"eiY|vVVE!w? uZy=uMsb>ze-)9\\Y3:lyx4CK-yWIegN1Z ܅e#x%(>:IRҲ|AYyn[p}ژu;zY cséQ ˒oE[J*aua>>34>i D6V9e&L4f,'554e?~55J[/kafnnlR.92=lYK~x04T,ۓIJvHHb+o;99١d~~?˖-sppk7n ~†тe!%ղ#^>jsY }|We*˲},ݲdXUnNcbO_ 1eegMF =`/ӅD|,!%瀝,iYe\IƀW9!7 WQ[lܥ #mpDZjvKSZv˪5:ܜ\򊱗l{_A160y WAɩ~>Ӕ )?<RURcIsD1Ea ny~!!a-_Z?#ddשy# ^,GLTu85kАTC[[vOdT|;##Q'e]\\VZu8UII8/X񸬧Q ~=j,BBeu&hN &D=,,uexi?7QJr`Y"y,w(Ȳ~=hh5?6,sYs~m0lgei-ո4Q^ILAAOJJz=z(## , <->ej.llHKv~^Q ˮ_0;˾k  DX, IJhc׮]D?e[[[6m{qrrpihh?@9}4bO}Gy ОBBBMMMnnn_ ,dpeZh{8e?>GO3XYYЏnr-ba%ϲfHNҎyC8y?HR|WZ5*A6fϚ^ZLiY<||M%"U9"h{?ϙ-ˊ3qRYJRb\ ~GԲehII ~~>SiyesN)IXʡ\666ajgN8GZMЭTrҡx*_'=]:YT$$gn#[Iͳefggfw̼(N&<<<˖Ue#h,^>a, tŲ/^7nm ڵ, ¨&:?U,,,o<}t\\\h;+WĺKJJڵKNN, _Ed\t:;WOH-+$;j8:=_ڥ'!"E}I?Vīg'9[xveU:=@RU&u+ʊRO4}!ek+r[bg4Sm e,RsRqöߵv`YlC{A =`,g{sIJY V魛wŲXl5eB>M+G AsbC^T1>OLeդ\KeÂ>Z1?IwN%l}m˂e3`Yf}IhA 6|I4Y̑A-hs Mhi[{m჻G 1`ܲaȲ'VCm~-=S#pf'eϜŗ{v u5z]˚^DG-9'e?f"*++iGdYC}{}  , t1`YRmDj5&.[o&'|Ά^&Pi,X],vv-k^Kρ?J &ƅklH⒒X*j~e{}]Ʒ,E߇ ;B0e4?TUQݧr[(_S}NMY2eP?Ek_WV9ӕ]>mܲe60z0.:P@]>0Cӧ^=m#ˢ7O[fRGהbe8VDFoۺ)0,#B HAgo [nWܾSyʻ*ݯʢ²ҪATU6U6zTqcj[j;TOW;T;8V;:8:898Ը!!{"IߓS~OE=-{zFcFfc&ƬT|#UE?74657ge*T?k~EUgߚjhnGT-lHk4M-K~5f {PBFRЊez|ՙJ~^.8\1lkd|^n9Cǻ`mmEfO> q觰je*}b-u0hLܬ?׭B[Vd~cC5X/B f X,ˀ23lՕ _@ n,-)Ƞ e*˾p%фgv%`Y}K9Yoe߲pyqc٩S&PVHv%`YLUeV;%2<l?Xu0AҜhlY^pv0Xa ˾ݭv`v@ u P 0!huuZZm?;JTuX1(%Uu¯J$$~$U Ye= X@92<؁-+T?uRƱ=<6MK|u,;^q=`Y,XB >@,<,K,X,ۇe!HO/K,w2`Y,, `YGӋ!X,ۿ2,;}ŝ}jJW`,X,XsZ]vס],A}a(^)zItX, ˂ep~aYC*TXE*b0^Đ_Sk}*TaXœ*bįLP%V4Vɤ*KVR+Uzk% U"^$JRTT9J*Tx}&fUf^keol HU!r*"~UyV>*TXȪ*oh2*xUUEUYoU%U5VuXKVu_[շUaT x5FT?Zjg+t&+x, baJK{?0hР(X??[}t7;cpQ#SZ3#RAvՊE0ӕaѽj3} '?0[C0v(O' 1t ǘ]6UR}D,X,oC/ywPȲNm/d٫Oc ʊRз͌IImk2,Ke`Y,XN)4X, ¸vهW{~h666|Z6;=Zw&//a1W.F+K3X|t Dv(S&n0 ,.NN=MdqdұȲmҮNN,9ZeO&%VhsV3mqGyv3Z^ ڤXa^PPƗKDM ;W#oŔTG2"M QQ4icOҲWV$j(@ӗw.ۺU)3TH Y[fQ.!bU)zOZ5;/#?woaUQ`?@ Xuxh]U7Int X8-g;'䖍p}I_%4vֻ%K)-ғp/ޱv=uF&~.:w?i?7 >`gNSY^Z]Mȍow%Ģ2O~ͼ}Ϣ2c0p6%ɫV.*-p{]g,]%x]31ŝh@([6C(e6A*31ڲҲX魫Rl^X4郇I Y,{A9=߲j6,#4[nsfdEeH&`Y:1g1`Y"X,*Y=vnY]V~*ZvB-˪+{WaL'32'*it̃F`?@ X, ubmNe9 ) nY, SGe۵< R3;׌M]zv׻m{ Bos=Z䢟ۑI [ /eH&`Y,Xe,~^^SX>$/ۮe鹑/^)N2As|:zlT,ce)6~6ܽxYa'Ğ-۾.h+_3n_ a B0eIm>OݲLxdeuNWlq2X,;2fZWae FG`YڇO/meX޻.3 ]P'e4I+vײ~d_B3, }&]yVZv;nE} omVath077E-k?6!._lݵlo"Mm>A\DDWM#X,K0MLhsgN*:l ˾tub>ma 2z@h&],,,e}& 1]*x <&~q [Yg&aR| if&SseG kF ͥ:szD[U+wLW,+//j*yy9֘K;F t ǚm en\$GU`Yl ^ ڄ -UCUJD;Buܲ>_>]YA6MVWat`wȲb= E644mmg3'DEZfUg*^8?5=% S˖ee}N`Yl ^ ڄq-aʇߠudWܸzxF߻u!;5m37#{M&2nlWw'pFRc,{vBv,ֻ(}I-xMzuԲi2 qe$--ne)%]DF?iڛ3)a B0e{.hk6Ya"7fߙ3▵sys}Lez_٣ڳ^7m\ӑeYȲ1W壯 %Lv^ X. 6z@hfl~fZyEQlcM?-ܲn.Z9j{l}K&촩X͝[_`Yl ^ ڄ,kۺ@_aRr(|8//d\I~եIp8~doJ|p}eMvvtdE ػ6g\eR˒cP9m< Y8[7oҜYvfil߮,X,O6a6ˢ|Ȑ!Jn"CfΘJV Ǡ8#Gp#>w8_/7-;S.6X?Ftײ9tjƏs埳h~eYn:WXXkb=||َ,kiqM/,2, B[RŠonY'VOVFG|$7a- XHo*O:y-hu2X,Pe*nUܺ]qNTw+W߫~}J *K*+TXWYT}E Z7ZwT/k_zz}57uo<<ֽE]O*z_zQ}!k@A| GTa¿G|@-2[T蘆T >57ģJhHH= U)SӾJoLhljBݘӘۘy?(@UGQ⒦TMeMeM_hYY Uꚟ5?k~֡Yo 7olnUAB:6 X--L[rPi; (-RU}.Z`ٞ eX]˒, b2e [l%vDzE/ fe_UUúsqr1vܓ˦b sg898Mztz[vf˒޺y, ˂e;lIA7,X,ۇen72o8}mcȐG~z|?/墳t{8u`:1oYF2`Y,XPmt|qe{";fy!ꩪ4eɮ=ȵglޛ!;>!E[AFB6;ӏҲbYbb;\[obƜ@̲z5[PFlsZzpy;S3Ȳ1慒Fw պLmFmD̙K % Yvz85e&[6-4}؁iYzL"oY,lXhòÐeHl, ʲށCbl]0˞ںKstv٦\F7u.=_U=ջHO]blE]=wFuDm~&fM/^>Y&z/;er[]BŲXlSf^#Fߺ[VFZ:ó>-b""yEeek3sxy^,kpրm4 E H 团7R%涬x-_G&l=[Ebe]EyF16F`}!6zre3uuѲyXX>Bӗ/܊>MR,٦ =yamxX1Yh1nYӿ` *R1],"WD y‚B6yw,bURhZl؃=,Zӓ}m pc˚>2ǀܲS'`B,L`na./=0%?CfY&pqaTUPeP2ٽ- _3Oi|S\ZS;DMvyWqw2\bYrxmynYw0~Eov˲ivw6e!}ZֺvmiܲhoYnݙg JrqOTxyY1Ƴe&=T_.$!3Iu;S̲KsK 0éDݗwe=Y#")B'誸Ĺ ;|&{dɿsqeY^vem+KY'/DiR##1bkaR, Ͳ?cE\~Υ+{=ߣ_\1wQDXJˎ&]̲Q0cee-{,\ $m3$ͽ_Φ/XD%4d8qJYar˲}F=|Ke<<1GƒliS֜SpYR/XURܑe&+KCeT+ۻ Ei@,N"b,R;ڳkemKlGߍD6,܌]ֵUr$Z&-ia[v>y+U= <e*N7=n:r-m]K4-^Y&O@Cw-K._kDR⬄ϸe/.R, ͲڸUCq7'gfl7/t3n ~"#!ٮeWh]:G?,畏ʠ_eh-TYnTvCx\BuaIRtL}S⦯5SJo {>vhK qr,*K!KdWcTw^elRNGȲAI[v|ڐCFJS.{p];]?p:[>A1, V'Onsܤ,{!4=B~`%IiٿB/Gɏ,MȒ]KpZYV:_b 4zjdًN3!bY`Aܷ8vb(7Vhb1[}z9]e:`e-[eAG˂e}l:ʍ%>#q!aDЮe3ߪ)NE$;Sek#o2j0..?n8j#D9_wxQ6'ݱ1z􄸰{NV<`]ˢT[ugPηe3CԔ&gnrqЯ>WO-."Jڲ[wj٠Gacck3l׭oMY1HBLLsӀH,WWqb~-;N썳e7\܀40ˮ;TZDi٭S`58m^uLsDD'geb Ȳ{/D 6Pw7_oC4gͫwyqehYk&jeSsS, dپxe_Ĵ\RQ<4ösXM,ilΝ4?3=c@eL.rRi=ete[k.y,>?evYVQG-v̲JIOYv6p.Z/_E2ǡo^lg,2Ի*MhHPm EAYq :9h ej>ί߸uWX+˂e}.X-EvYN 17Nܯ~Sӝ;*KUxƫ?6&׹$GIb+y~ƣ:zYܼr*rWc@ݲ8s"p)ɝ8R,{0aqӑe͜Ͱ:~x-ݳ#9Nq V[))-C<`ٮ, `Y, 3l4K-ϰ6`ϰee`>\, ˂e)`YdHjv`Y, `Y,X,LN2q0`et,, `Y,X,Lv [,COB b# ,K2eFZnRZVklYn9X,xe?tX˒uvY ֱr* ii٩xxz՘fDw.}dYOxlSIaý"\\ SҨ[p Ia{Ee!݊9B,, {۲K54z7h?J?kInd?3MM%-С)*Ml,+!"l҄KPR^>?9*h {[#JsrW>:&hvܟȸ- ~ olmb*7fY;i̡.*!*є[H/! vՄ((EZ]]9*v,4[ǛR*婙^\\n, ee!7`Yb,k0ѠbleܽϐX>B+f} H/wdՓc^Y1F^1˞wc֟>`} _z>1#.1 ;b^@(i±?Sq^{c=HG} 7nZY!-[XP˖}J,{q屿>!1@α e?iXJRHnY) n, e8PLU%{]6CJv홋䖵={ #6D_eƐhYrRWBt c'/-6/0Z[Vo߆\/K̲nvdDo?NED3r;myܲfw0?sccc~'}7o16Բ. $Օh#/!fی2Keƚm;l#v8lׇhYcRߑhFKe<d`ڮMK}ڎ8UKuZkێYz=/! ,{f$#ue\7/VVr~!ڝDnYsi]|-1  YSv\veO4l.BگX{eLB{4U8:MnNBxCx%ݭݹו{,cyܣw`=/M˂e{opb{BUTa6 ->eB5B/|U5lGJَ,>mٚB 9D'\=z/pVpח: I]sp9eaaaIˢRPgnaL_v_1(JJcuF- ̲[7`Y㧪BWM0,`Y,SY 뿄`Ybo[7_+f"fAQA e>YPDPPTHhb(ܲwVm`a.851I5L[YAރve>/;m0dE̲b3U/Y-MXd+Sk٦r\2>lGumެ\,AbgsOz^ 2eu=__Yʍ^|WCjmsz=ܲ [,ظBOO4M a=e`YlOvj2v)?>SI?[aee}Y[~ݪȀc@n1:E], BQr񻣰0vO0>b<ץ ,RN|6D$;X639pr=I 1~>^5\0f&Zu=E7fysa1aA oݴ",[Rme\0_$ AvXYOhb\R@z@ǐL>9֬^&%)! ?OW;9!?]MX(+#JgmZD~%ȲK3SdǍMlNvU+%%%>,?.=U`> X, \x ?9h>Xd vعs'ݾb7|{D0e@->Bj}7h*lt_[,qH7> )mMYҨ[ۦ'`"m~„떣#v[Vd>ʊRT 'm-<\f;=^WcEcȲ},9! qVRB4_J;cP_T5+} aG}E*g'v.[JVvz&))9dȐXܲ+jh\QZm?q koW>|xDDDu&2}'?qq^\{ab6-Wy¸/1YM+Oٽ 4ep|Rی^wX65K4-!.Jnٰ7Ŧ#But?޾u}G} NA/1-Nlq_hzǎ`>9B,Bh߱,y::ŲTWL=b0ez>TUt7v٫N:ZjeMNF/i_dk>ɲ&gסb`̚ 5ٸes#X6'#Y޲i]GP rGY Y 1ڇ[67+Ҳ1\_+%)esDI6 Gݏ5G1b8FR[ڵ+hb6fϡ--9YShz-`> X, 8Li^Y,۳0eY_=h:E=) ]svYu5{XP,_Z ښёe֖a< }lݲ>oذرcW=aP(~0O5 š6iϿfZez˲ե #G#/U]8mkY4S[dY]h;7_]iYݹ d+JVs{f[9$cEW#Uae9?o PK֕O+)4zkRb[fװel=1x`ue#JşM-Yv\iJiٺQFEq`Y,wE655M0ȑ#UUU<<<љ?,,,Bˣhf˲ssAAѣϜ9S]]FnvW=a˖=: vx~hVoNwcrBv4D^ #Ê $Ex *|q 2?SiL]ǀ`uߴhّo^:U.3cQgO`R֖חP췺A˓Lb+olZaZ@>Ѳ{q 2VX.!!:k{?q X, nhp$ƖP6olddDlirttܾ}Rrr͢E(C/˾>qāw*,,LTT6e]<`ٞQ,j%iZ<aۏ˂e!--AQVV\cۤI&&&k֬Aݻ#{SǏG!bYZ, N`Y,wǏ?zhUU77wTTz755@ ʒαՂߴ?7˺/vY0eVy8YE"B~GM2X/a u>=TXqq [555\Ǎ/ !!i&lzԩkw; hY4;m4lUVW$\YhU@V$ZJ^`Y,XwS^o:ѱG,X,ۓ@<߿?00Ç\\\]_1˂e{8@ }Ø9syyyoon˘{˂e= q a0mBOFܣPLS`Y0a0m. = q a0m˂e{8@ }|a#, $Lsơ{(^)zI:ixoh,X,ۓ0b~H!Ls|{De`ٞi8`Y,XO4a<̷G X, I2eK 2}0Ah9a=MײRJ>hGUa$Y 'Ŀ-FYqA A/0˾xpmqCTEOn^]9'wVNS9K\tho,Xga3X,g0ym +)"txy~g޿[vxٛA6=($W̲OLO+󽴹^MG]_ab}y-iKř%^gwn˂e{9eic4(/4 , V<6aP˦Fx44Ú2XgfڒXY>%f"֖ơ\>`Y<`Y"X p|7GSH@MBTTgZCr&X, e(K,Ԓ;w+fohlMqÍiW%kۛg)|P!%Y6=pUa4yٓWӟHz(, %e{dYk7YYYE_?xT))e+S`Y-yаʏ6aP˶4;:bL!eA~c!}vU|\m՚&[;\m2k5N椷/,YDl-[ˋ`jgzAnPꅋ%2ӕlnhEhq~^>ubeHKHea>1&kYwAfF4-[S**iǣeü@,X뿄`Y"X}iaq:6e& gaay>+ v;{[?r~M͒>SVp-ie[P`YHa>1&^P&쏚6,W/[_9,-,XWDl-$NN{\Y&O۴kܲZico62▍xe!] ɏ6aP˦,>'Y=ިȏLڦxҸxZ3ֳK`,,l/˚Bi`pzX{.eMO&-YettqEĀe|{D0e*ܭRƂ1Z0-+Qjqy?:a,7_S6UD/˂e{ I#:E,X, bO~̷G Z&`cuqOڸO7-'cP6JF,;[[_r֧d/Bm˂e{@,1{{s i͜/rB QQ>iQcRoi.1˂e!] yxY;F/0&`Y,X'p3lixoh,X,ۓ0, Bi<6˂e= Ӝq`Y,ixoh,X,ۓ0, Bi<6a\Fw^?Ki*GkPSU%1 VWZ9u}Q^m,~֭]]˂e`Y,Xo4a<̷G Z4zm_>%Y=zy !@w]4^ %Y^wXOn;'TDZu ˂e`Y,Xo4a<̷G Z65cAu11-[[|ۄJ(/Dc%Xmwkoa0#ڄA-[S*&|pcZU T, rjUa֖Ʃ\س9=ڻ& , %B,B,߆ixohlK쫃+R76XQҮe:Qޯ(C+nmH,XwlF3A^ ˂e,to,M3Pw=Au ϸ{dxnT[O[7`w5|O>gOO, ], eRRl N괃?`0N.ǝ^)4S\)/FwrbMy%Myﵻw6N^ۻc̝ˢzm{CULJY;>&k۪b"`Y,X˂e1`Y,XnٔW/~\SeCWq nE}vRIVHFzY$ߵ$.+@Ce,X,K˂e}ÇGDDƳM6x6`YflIAA-Pn}uR됱FtdYt85[9U-*&6~NqJidnY⢇W/.˂eDz[VyɳoYeE `[4h YsACCCe`YflG7Z&`~aYe)V׮eeDaaȑ#W˖-<"'#MO:}#,X, RX˲jZqO.eϝC"7n٩ .KzmlZҥKT(((Xj63==][[wLܲ+..~DXaaa"eİbZ!!!ׯ9~˶{9`;;z}ev[VZJK2l!>^7orh˂e`-&bBF}T <Ӳq rz܀O=XvXϟӦM۲e eff̦ &9rӓ'::˲,meQƌcjj܌۳gOEEErr2+%K]}lm"/l+KR"B<7_Y]G}h8R@ ˴ZX, ˂e^mnj-]9ݼeTձ^#$LviF;d޼GW~C ڷv$?WOmve/7&CŲČip&"lmd2Rzsr|1lCM5#ײi9-} LЖ?"JKHs,AڋOzZ[:fD*L;Qo흐eW/^)+N!czoMzI'M @$'*,nYP>>h/Zyf###/v.,-Jjhh>s'lSZvvWXkm̋~=z[wqHKyx֧e?z\LD9],ylmf./oטevԂvGKKkE,fۤI&&&k֬!lG")-;zׯOD Mkjj]3oEKOCzG!Cޓ["[v2Nῖ[Vɂ/imek8죃>.;F%},9! qm4]5C,X, $Z`"ʫ;6>[A94vԓ(_3$Mx ➾nYWV̼m`} b8w6} ̌O^eѺyamxX1Yh1nYӿ` *R1],"WD y‚B6Zildd$D))6,EZ_E''_O[ rEyVr[u2-彆Zي[6,-e;=suy&fL.ӫW-C/-2e&ܥ",xh^YN(vhx8y!'7*QhVWil7vʂ!@rFydn.eA?U:Oߺ-kIoة2?`YHa./=0%?CfY&pqatU.Z6 %_n:&7ť9Mܔlw'pq~)Ӻ.u*uk(7ז)ᖵy췌\VzPh,Qfwhc~1XCݲȔx٬,=ZUUE$/Yk3j&d;8W!}Ɋ kYTj"p j9>l27פM.} [\DeũnԲA/<Əfd٠[ߚ2lc^,5 ,鼹s#",9g6lFJΜJ9AFJe~-|Jˢ3pa+/ ~_s2?[-C (Ojmy{} X-!$쐷]fnPޮea1vPu4G2I:{x_ͱdvbe?L!䧆e>T /bZ.(U#."r|!}aPH@ , a tv}V,:_>veuF ˆy;u.&޲x,Xˢzeޙ^m.*3=sNZR/,, &=,,_;ene_,,/eYO U{0X,4e1.!!& E˂eiXգ+JW䇧EަxҸO],6t~K! Oa2_;VE᧴wt4O_WLnYtޤ,(2Mݲ ["X"`Y,XeQ޿HnCg0XQ/غƗ]=]q27i N/|H"WkƵnyQe"к`Y,, |s, "/,X2es/11 DӴ=6M/S`Y,X˂e,X)-ogE;49i*+?BO؁-mv DC?uڛe`Y*LmCV$7w-5{6,7cZ.5kΐE9y9h>z-;A{tS. .Ke`YFH-g%ײ3հGUҲS'N-19e=M%  ssq/+NInY;7x6~ h٪ 담̑oCXZ0[-Gk(`lY'e`>X, e0e7 ƧW28.*R""Jc aڱ݁,XX(J Hw "yg@D}={A>\LPi ]9oA2[vצee#}ƾ2>HW17ne]o1]r>Ox, ²6Xeײ^ASt5HFYVO_9 59ۛxr ʲ}m/6ТeXhm%y4׽geU*,ZbMPN-)..չSUUR޴hZ?)VDYϚ+׵IOXU~6K ^ϖ Ӷ{+eX6۟0= ˪SYpAߴ^i8d(MqMYJBbd7eUUTlV9_uYVK{Q3qA!~ yf,}Y²lKXmr?]ZvԻ<N\ղ2%.8~ Լy'"Z93yk |SZMd՛aq)Z:F9>:CeeńE]QXP5anbfFek/$Lղ2?7/[ ,(eٵ 24nx_UFR*만\ZvM)>r^iG.3,K?ѧ %=a9]ܖ,K=1/Kü,, ˶pE^ba5jc5²FͳԲl}r}Sjs``;em\1Ě1HNnAܼOu,kleg g ,kk1Hq'ԌA33e3ʽzݵ;/uR҈qzP]e7X:1؝3ꛗ-,k/ƌ͍FxsKʲ|w 5,$.0aٮ22o{y ~e)/-Vc?_?>mϲZge?+;gJ?%Xַ,B,Kk}YӗYreY-ʲ>yVE[88QuصiyсzÞe}Fe"ə]{02ٺ:s(:X-5Ps u٦qɲ>ՑɲO1f<s_<68oL%ײ7.5cBf _<%eY\Sʻm7W6eG0~ԩcOӧM |]nY?{ht4ej林g`bkpӥ'$ҵq&^4lYBXuMK}\7j^LW1/[e3/[e a 4-Lr}yY3yYqQ/le 74]*˸qɬ3|y ?&3,tTg ..$,YX񒚗53Ƙme1ȋK0>PW:Rl,ie.pTW:vf⸨gEQ/Z3nYȅܸ൫ܹoÖ]dᢅsaڲ$!Wk=0Z2Xܖ- \7cl7? wee}4i>3W[6/x1 kݯ'XM<q/R.'ǯv:eoU՛GgeI2R&Cu>ǾkټȗSǎyɅ ?jOEk.,.שX3se'jfl]Z63PHP@o2,[Yrݺ %z 6l6,32L>T=ݳ5c0e%VZ39i/N#)!.%_gGR~8@yFCȇU eW/ 2KG?Ϊs 73z$%1un,Oݝɧ&7b5 S}YyYbYߠϊҼ]o%$ּl}Yf虛fg$e262 h~X ~'Xm`Y,+-%522_װm}e˖%R5E}ײLFҦukY6!>N KvwLY6-y3xa! [ng0rFZ61ƗOzNWY))سQ^CWI1rMNK|S>1~WOl8yJ GTfPݵcbC~ޏ2Rw8+=QPdÖ=rQAyfz|#-Htɱ$9Y)eKqMa.^e$ò -[Z Ҳ:vztٱ~ek/ؗ*aV'-}YG|/=)z]a3 [!gtEa,9UW#-vr exQM6/qmI:={lY/gr Geic({TebB|<1޿{a:؟.W#iYoO7rx yp/eY/Rx\*NonYN>eil-Aa ²?_Y־=eٔ8秇>g,1}Wu6ͲԼsۗMM|}ٴ:e.5`ٹsft(-91Mb}XVKS}:eae[=X²,{b%k`IG$F?Șk9bq\{YAdmۢ?iW.WU:/ma6,+%5҈1/]9/KǠS.//J޴:` įԼ(SyY!hj^v0Ƽl~nrcnan2$;8swy?i1W\u^@_,'3551/ p̖uɫ8e,KeaYX=kI˦5€<Źak.ϧz>W7y b&,.)!>c,u~YKbe:lvzq_?]&gDzZ6'#j I9,5<@({:liQz hk8߾|=*1 7+e$ Β3gL} 6ZRՄ广sZ|KӷߓB[L 7K, 廖afZByoF ,ۘ`Y, ²YsYG_eq~ٺɴ-.mCqz{0eL~呑nMB[+Wn]L他]b Ɋ1,߉'Z|XXM^AW`f²l d~{3p}.w.DŽ̚1 Y7Ί޿ذe3!:q3j#ܿҰ/}6/yf槇oܸſÚ|Q,, ˶D,EJ#H x^~w5oDìL"#E}rղ !S&v`ߧԃ:Lͧ'ov*!ȩVv!e:e]w_шaw®ɛ-F0[mcR];6ҏɰlzrx}e=>,֍lfZ6jDg1#]q&XHK rsb=Ar|Ф ctj{f>e :6i☐ƃ8=9rIAAmN,hz>}ܻw/G:AjJIedt26, ²-Q_x [X!=Xe["Xm`YP eaٖeJ aYX6, ²-e+ [/U& "jV daYX)6, ²-e V;"oX`YP eaٖe[=X!&eaYX%eaVE`YXm`YXՃeBm2Xe[";+iGb]|ɸ6,j,,a_,j,,l"d,, ˶D,,6, ²-, ˶z,BM²lK²,jџ]}՟IOFo#5,wkm/X!&þ,e/5ƲN88ӤbY eEG`YXE`YXmkkvh :O/H pzE3`YXeBm2Xe[-[%"tNH9C5\=Z˲Qsuq{CWJ_}ղ,yݛ-h 4]q2O9HSXhH'1,[rh1QMʩڢ"1AO8?=zD`gX-eBm2Xe[-Ԯ]IZ.5k/^Pǰlu髯]X|YM?&zF3}YYCr޴O+GQ8iUi~kT)~|csWL{+xݽB7N4ۧ8}㽫`_]eBmVV3.vĩ%"\&XcX6ůce m1 rs~;~?F,"mѨaecCmٕ{6ΜBr*Jc+We,KIJ}zcje%$)9ayz@HeT|:Y QZ˲yq{$lE eaY [672e;}q7ّ+5ޗ~:?jٓ %mlhooݎ}Y8²{_', i~M,{Z3:v$׿~#~ȲanOi9mWˋfbl߽1@r1,rШ>=CZԮYC:40;vɰ\ /Xeimڲ} R/X,/ۭc'~nsY6E$f0[$*""P6e/\]1UGf[,KkӖ=oj^Cl#Z?ЈO<A*J a@ubu$nP3X\X6dMUM!#Oϰ,9?6J}4VM|WDY -| 1Qm,{|])Ir3߾I!=ym5 x[lYn9DuUNNR "]1~4Dqy_6->l0# !Aò-,Kk- uw* ?i+?JYv||Nʲ9H0<5G6e3ãfLD.hM˂eB'cKRQWУ4ZF/luiiGZ^誝 77eg NM!i@t+)ΉI&kH_F*%YI\tӗN (Oqꠟ˦DXikXll4j6ΜdXiPO5u˒ʒ =cldBj`{We[ Xֶ,װeBome1Wx)A>r,ccXeC)˞ܶvIJOn^ G_\\l#O?:COg`͓6-JT{±z:}7}Yr!b.)^=ߴ€;~}}rheƼ[VO˶@, eaY~M {%"}&r\)Ƈq ,,b~_5la_,KeaYX!,,ё';wW˰l77w`YU~TS>eيd deE- Ozj=g1t=&7GCXVU5CuTaz&,`YĹm\5pגh1Fzj7xݴIbYCuMs]G^=5eed88UF.[)%Kx!)"״ j#X!9qt8c{29 dY<܌y٪@::C(:,[IwG-]hg):=DY*<~s7Y6ͳSǎEaюIKH| |0z-:%F˺8\556PU;mzo}u;SeϞ,[_H#|>`˦! EkF6/}:-2a 7>>j ]j T/C7mRsemYڗzNKQq?ܯyO&RSeYgoY"89X!yeg?H=~W9yY5MK}ԼP}Ƽlcdz朏qgǙ 44Np|z8 Xvճ}$ޭ߲cX=wW :,ŕٲ⢢>NcleǙ6"'2!58Sz8d*)yuVSiIJcFsǼM,beB,G׼- \:ɪ Li&!*&)&>|tS-oM77uus+t.ǧ3P _Ћ~zc@,cxtT(~HH4|N.bٿ,O3uu(QSwѾ1xo={uDlH?xԧ<?j̸WTRT<`\_"8!X!EqQNղ_?z~ه! Eeqfr${Zo;^-͏ ߯ߒ aY"ѵa˦y>c=Y-۩cGIGjY(-jD1QnK{]"8$X! [f Z5lwa`Y8:XeBl,G²,Be`Yf R;g?, "(X!eaY["5k%MlnOlYiIGS55F߀~*W]`XtK²!NEe²+\B+.?@caYeײڵuן˿~UlO]eEM?@/z̚+'NyN,[n=9."BFndmBħvecvKyr e,`r'KbeU,Yn=X_SYvMzȳZdxۭdSUec]=!AAE^7nȞw2òAϹ#oY>)%v,BT,BݯؗxwwQc`7H=KwYGe~г[v=vXv3=ԇOΚsW^U/^]nbلOW^q v{܅MdMO]gYj_SzN'{^=yaY9n!*S2l7HIH|}].,$%F k¾,OX!`Y8ZvhW"m&lpڿF"p}BOy 'n3Yn>ʜ'~Ԍxa;R{ E}]vD4-p3'M&,&~ҿ?4/KС[E!F,B]}P!&/]U5bYs_Bc-{ށ=Xf]Šfghщ俦71+5crJsOt~4:G%89(,(X"ܯ1{ѯ Ye{w9}lCf:jeã/\.OwdN5-K/22:Wd6MZѯ/֝Լ8F *C"Cg2K7-XIuX>^(س:"~/b$S#LK^D{ s^6-0:ݩN @NV1u:wAJB0)òN/ w6#'31cU>Y,K+)߽m\n_!Dewixyj ˖z/4 S,2~IVy ͞Э{Dщb^[_N}^&߷fNJeg1Hyꧯ_f,Y6o,je?dHIzTU w:~BFd jY";Os3G²!De_痥Ԭ_z~\qQguBYe`ٖ염C;vv!;Ef EkKM{z&>Աԣk7j,BMEkKe{ [X!,G²,Be`YXEu,B,3QS?e?e͛:]_o_X!~i,B,\_>p}K_%r_?ceuΟeBm,X!y_+Y,O=MJ˻UkL3.>}YG"²_{n4wj y3ݗ5br}6򠿔W?5c9*AXP0eClpͽc~pjFZ18cMlVxk|I|+}FYvFG7Wi>ߵSgT>Rw@WS Zxé?4/rtp׹3gFgY곬˔eJz>O 2eӒaY,G׌m`_6IuZVKeòn۷/ e}W}0e=o0,ۻKe\S|Ryl#&lme3^DпD~:ڝis>9;/,$TXVK]e/\le/b`Y8杗3t+Mͼacoj^l>c^Nˎ12Y=s8L['zT> ~gɈ,;d>?/9uHD e'Y7U,x>e4`...뷪]6Ͳd3312! 2=+)y:;a9/q5k£ O- cnz%eM,!c^!^qtkRKˈ ,0:Aԑfbb3G>oزהz*psS1(X7gB||:U?[J 7<!fF1H nnƌxyiɖ-ON_tB:}:-h;{+(uS?uj㩒"<?j̔Q#M=wM1,e_yAa e:/KyM_-~~} ۇ]p~Yo,G²̖qNo#_WQYx1,̓eڰe<7`l-ͳ(f_VYI#!v Eu5T4yੱe1zcoݲiƾ짰w{w`c, /]HYvpS3ԧ~e/b]Os|*eYwTD͝8t7/HJOiI,Be~e} ?'u>|?KDa_c o߾}QPscP3_udAj [VKuPeWx괰PeF=Rdz7+ RרeYoY"=X!iX>e͆Sjqirqqyt!'ӥ8oiI=#wx:nHK#7ȸ[ YMn8˦՛ wuenj0]plyY#u&;xM֫Wg?[՘yY6^Y_how~Ü|b1櫗-HieiU Ƽ,B}e~e{Oi&!*&)&6̲3ʲ>K'O.-#.,xby ^/ݹs^UW͘H˾߷fCo|:Uv>qzWu*t#ZQt\eiY6m5U~ײw]za=Asu*+.*oS3gۺwϞ_F>}~VPħkԌ%EEgvlfrʨ#=w/ ,b`Y8}/ZEA7>l[z ۇ!6 EeaWn_Q^&**K/eBl,GMsf=5`flQnbbbde-WZ"(X!-,}1k".X!eaY[",,b`Y8:XeBl,GGx i+,j`Y8:;b_!vEeaYX!qt,, ":X!eaY[",,b`Y8:XeBl,G²,Be`YXEu,B]im,X!Z!zE#XXM^Fqh,B!b`YB!Į!B]eB!"B!v E!B,B!5X!Bk,B!b`YB!Įp[MF5IENDB`astropy-pyvo-b70558c/docs/mivot/_images/xtapdbSED.png000066400000000000000000003671461510533647000226100ustar00rootroot00000000000000PNG  IHDRJfsRGBeXIfMM*>F(iNJASCIIScreenshotN pHYs%%IR$iTXtXML:com.adobe.xmp 1098 1722 Screenshot -iDOT%(%%$E@IDATx ^=!Jjߩ="ZU*򳤵־jRU$kl3,'&:Y3~      |Gq@ lZg2/fϞe;v5\3NjժU_YiŢ2 t_`3f ײo֮};uD@@@@@@QuP/5r'6k,emбup x_oŁ.-PŋۼylYa]zk[k /^ &ƛlZuZ龛cʾ3޷a߲wg~df &ؠ}ۼZ}m;Y.ۖ}T  @sꫯgiK,CN:9mG@@2|<"2uٹm]|]G|h^zjl]ڵv[6mZ^ބŋmɢEVQwkA+pZOZ[[vu[׾V+ڷd-^.[>ݿ[u[F51}=>e6q~Wok"9֖YdGۥA-N3"R@@CओOy:sOvѯ~l;E2y΃4dz>kz$ο{N;OY"  MF 3'ºݥ,k ͔!h|&x.tq5|țlވw \vYR Omӿ ju[cҞJn}];ֳO/ڧ;)<]j[[hWś.y  @oKˆ {zastllF'#?ɹ]9Yx@@@?&LhlM<|aCۤLwfzvCr-$fGk|VVPFqWWUY,{f2k뽑k_K>ԨRvԔq0ɽtUN]U.er]ն}uK7 riZ'-_/]l5cu߹̮31  @tuڵu7) v`H<7 2&LXo-ۜ]eKq ora +/j2mk+1  @t]uɦ=s G'뭹fgh㍓QFL۬c}ll׶iӦل ɪn믿~|VS]=b:flFpeΜ96f֚ga?|Yvud@3fkt7 r+֭q[:~Tˇz؞ydݍld3f|e3gJ^#ЕP@@@s]?p-7Zw߶]Z!&S*{w> g̴瞟V}<<kf\>87}\v @M6+?Y;ԺڷtE7,;]s2c3fΰCi^6@@hV t}vW'Ǻw̜9Ӯz=뀘^o2Ǝ?fx@/?owߊw<<׃v>g,f|v'mͶnmn˖o 9@W-XA6:./@@@Y˿nįVrkҏuN.\ѶRgNİs}nҜ=~OSN.QlEtKgK;ȸ@לyslec;ܾ>y@@"u]k׮9&'sZPC::^z.Ispin{׆;~*j׮rKܹsD@@ d .;o ]ܟ=2C֭햛ou]7~Ϸn^,_*l6KW^y}ч{n/>C?OO}7yR  -ƌ];=_T9]?VN$_[r>{-7ޠy^5>% tTY*[؉*fnenǵ.|~0y>jd5cCFVk#  Є*zWtMMwqiE.֭3mʔ)[ZA9 rGq1TL  M@ S]]gt.Sȏ;]L@@VS@Zc5rvmk{l^'xž_:LE駟GzJftm6K. w<>a7Ţyl]VW@Wb  %qƁSbB.l" ف믽6t^(5tPMy]nR]/{g;3Ç뮿>yomK.U<|0pЉV[ yIwj*y_*~Tps180~\tź   ИqKC*Ր2Kk8kձ9TY}3nd\{#t i~mu])>MMsst-;Ǧkw{A;~OSN+5nŰ<3omls{l>6F@@1*誩w^rXm|Ão4|/ ._|/K.Ζ߱/Mdg7uؤɓo+PtU*@@@"];ԯA;Z,p)@Ӑ@׊)Scl4}#o65wXnݗѵly&&nӦآ ϰ5z4w/-]1\Lbg{iÕ)k"  PYJ.]j't)k׮6g?;^]|-[ڴm/6ndn{דzp?yfͲ &$oᆶkY']iX@@@esEǏ]wŖ.YR6-ua]E|/#IZ.[f5'ٚ`erd;6cW<صmigsCtWV= [k'Us7ox#޻̴hي-ոA.mpe6j==֯˶iz  49Jtw=s+o׵8 tM89:ݪxu.g])PV@;cwɾZ;ce˖q릛o67s.mzA+!  4qxѣGۮ|7믾mwˍݷmIE;U4iEfጙ6{~ Z}<<kfWu\j $Κa;hwAѥڡe-׾0K+ vM*q[]6׌3v{W."  L*RVW_c(fmn}6*y? t-s7 ::YaɊy7Х{ܮ]{رM6=i7q)J*   $Zȸ_weW{גR[nV{o$z箾* ^Bhl  ">scLw36>7{m֫woV\u.2g|hE&~mUU3G5˲^ޯF}-]rrHTZĪUYv:5}E_mnZtC7X:O^ r-^jƎs֧as8@@r sf:wu&y}ʯG8غv饿ι_~7|[guO>kw{Gm9;唓zsٳM:5y}.k]ΝcwuWovZw߳k6yo/(y%~"={vUuЁؑG鲼:y'Wl츱&^p/y@@@d\k]eoevŁ_YK-.[c)F7ټ7X,R\{S?)UU5:luynk7î{٧SrQRɵhM_mo3`3*,@@@ K`E_/ڵk^{޻k'd~x<|0}t+"  TV ǣEw/7}ɧآ?FڒttVXͷ6[ocd.L{My͖|--]0qyV]6VkogmfJwmݙ٨٣m‚[awԶ_u'e[ K@@H!^|E{G膳rJ]?:|_[dIvD      S gEcƌӢhY9.@@@@@@*&cw*#*F@@@@@@#GFն]*'.u!     TN p2*L      e]GvstQ檩@@@@@@ d>䓨kO[Ѕf@@@@@@ dgQ6?wb*C@@@@@@m>+Z]r7]n@@@@@@ d>x}ދ@WYi @@@@@@#GFcǎ5(侨@@@@@@ dFUWWǁe˖b*B@@@@@@qFѣmeJJS7      @Y2sE4ktmҥe@@@@@@@G?<=swǵdtUJz@@@@@@.+sY+^9"      P)&\ƽ|hѢR^@@@@@@*㏣*;Z1!      PI|?J@@@@@@(@f.2LY+2@@@@@@*%{GN*]2^@@@@@@ dx#8S%      |رcM.. 2"     _ Dƍ#@@@@@@(Y5&؀Jޘ @@@@@@h,+C^jklРA      @n—ChѢ @@@@@@ :$0pBc}"      P/+ ]FCˏ@@@@@@I 3kmAd@@@@@@Ѕâ6`@]ٱ6      @# CN0et lfk@@@@@@J :$A.Ѕ6      @ W jVg@@@@@@J sst?HD;VG@@@@@hD/9? v      P@f萡QuM HFWIr      Шn.誮N02`      %d M0?@W n      _y%t 8@@@@@@H/.rCdn      a^{];s)@@@@@@m㏧ߊ5@@@@@@hdP7t\`oV#7#      ^ kE)Sߊ5@@@@@@hd+CDG~͚5@@@@@@H/yע3}bM@@@@@@Y nz2L#7#      N 3th+C.Kk!      2Æ k' | 4@@@@@@ @f萡QM0t[      M@ 3th„ .k@hM@@@@@@@ @fثFUU6`td      \Fhĉv7@@@@@@R d^{hܸq6pT      MA 3lذx;2LShm@@@@@@@@57GẄ́ @@@@@@@"@WM]3      N ڎw-Z      @ȼQum       H  9@@@@@@@W_sj;,4\@@@@@@o ]]ԳF@@@@@@Ѕ5v7d@@@@@@o@u5~ܸ8ЅӀF@ ,Y~{'죏>3fŋ N:${Wƚ    @ ǹ@ 'Ѐj@!Comzy֬YֲeKԩ;no}a/r+޽{WVSN9eB-sζkZke[lEܾwveYY#pB;蠃LEB+ ?izhusϼ??{ :O?qu^+ .o*oLӐ^]?ۤIcǎ;kwRMnPPeܹ[ZuuuREGkf:?Ga[{6o9;CU̙3{_'W_}gm}t]k9˲eLˑ#GڷnzϞ=mvK+].9<[oşmu׵_?޶>~a{7̙c۷vm#>ڶm[*90mڴ|}wLtW^1]STsV7R?[UHsŷnݺ>U{N&N6lcs@_" 4Mmx:QL]O>]mz]StMwYoS{񪫮s>:S3<3:ZiO3p@[+V?dݻGS&ѢE"hH;sn::h# s\/d?Z|E򨣎ʷJNԣGб4׸|p9rKK}7M KTw:kqwOiwDtmp7uD>rATn֑˴-Xg݄Q6mDW_}u@ z*mvەt\i FfT_Rpu6dGWsn6@W޶zgYgs U{_~e~7  7C 3Sifu7Ņ eiq]޺KWwO>>c=zzt' &ua߿ =¹l݉~'֩Fw&+mf#_k7\Tܴ|plmMM_@Y#ʢ.9u|qv~U!BE?>IY$u ׉qLZywA'v].grtҥN=Phc~/W,KFk|]/P&/\皞Zftk_mޥ2nּzCY8)',iYQNm` l50e5*D .,P& FaR䯌܍3Ŕ';襗^3s<wU{X>}*3- Ooe )[E5X=2eJ]L_sGP.ʖsAUm.g)I}]CY.eʠUt]R|>qu.L:5>GƎCǦLRs@2 dt>RǷƌcÆ K~_r]ۥ Ke)S;[mU|(N^uWe 7~3z% 4+e^Jr׮zu2<1\G]:NruW.W("o9(%uE9;u2;nWK:n?JΚySO4  H.0fn(HߟJxpÍ&{6]wݕln_ft;vCܧkܠAvڑ ٽNF>N=/O y{eڸCr,i_E=P:uG_R\G5Y:!"nݺ%u*uS/;VZ%= .XOS//~a"2ȴ?Je,~pȑV2g)k(Wq}#e+C&eBg:67djl:[Rs@y\0:ߡ\(й]5edR pE.Wg} [߇w@'(8s3?K4  {-/G Z@siQ6.#osNBy+F)NShXܝI;-M (8;]M2sL?i駟lse %Sk^'~DNmR:\N+2i_kׂqkbeJ%;4b)rV 7R;+ThYⲫ7I9}ȿUg̊nH7H+ ؄A;3&n8`qy\y+- 7o]0%T!n}UG; ѯea'+u :6r&Ǥ`qo]^xᅐfk:@c'G_A˃D@#vj ְ[(i;s+ SNɵZ J:ԑGA>I=U2mQ`KY~[a"7lX*u uw}wu(yzyki+P{uoF t?MڱjrǧMvG_OJ7,]9TϤ.@~{ࢌCoua X'o[ l?_&6^*`⏫(߾[n%W? - 8vj,Xlu=-~7ӜѲ@2lb/QmiMnf(vL  Iџ\F@hjnc㗿eٚv`t.e ti?my"I4 5Oz@(:\*lhKÛ?7pNVo-RאXPO^CMB<0R'izO:[瀶Ur5:6hd- ә.x<9"7_]1oS4G^oPS'ܜUS(R\Uk,甆dSoKۆi0 i苂DaAJxP/H: 0+'wKF![ӰkѥHӟnir{oze%2̔95ܢ U*Rƕ?.hA_gu6SOMuzU٨\sMvf J٩ZynM)P{i΁b @+97W\LAAy~_ʖw.'?Iֻ5@@(`Vu.ع t@@ r!IFyqb2@IDAT4;n*.͗;{TH9:5FXw&м1JsV@:̕\uWIuv=zD=\M^[.u*h?tin[8_7{X?04B2tWo}+:!.GY8XTQ:/^'r&_FѥmtէOsٖuS%9б4A|?ZN4n',\z= R@gw٠:U?'V5 }~r/8UPӴA|L Ж()XPicڙ(VwѲ@_9fi좛A:w[l>y  8Z@@ l mVv`7@+d >ԑ#GF+ N$44Х,?#4D~.Ҕ:]?xR_p>38#^.m?f;+v)~:iTҔf˻ 7Uv+$_6HJW ZeEn tiDOpŎq}] jUUŸE}Weu1*g6ڮ{}_~E3ɲs@hoDMn  @sx嗓 unͳ3a„Ni֔]~"2R ['~ݻwr)ZֹFz庣?رc,+e]uUy;TS@Kٙ;p%u\J] n :s6n:vuU&]lU447ұ+hiG_z' .v< )m >е`$Ns*oBS% W76*{Ϳo8?:Ft[\ 4UvNF7(Lccy5bvǤ:g(iρb ZJK7m ۠(9s*е[&+Y H7zT{@VS8\c\F 4N ;1PǮ2Q;#crrn:4fʵTCib%̆FJ%]W_}uroefm =pm\bǦo :sSU#|TZK/MS06+ХDmWfWu" tIa`Ic%)З%o3 oR6oghr-PhrjFeh?/c9gr ֭@I\AJ4G]8lgخ},%Х/0@~衇&0־n'$Kf+N7XP@@`0?(  @SV&ᑽT&A9<m=d+Q;;D:N\sUmsuS@WMQl.\IZ:u?\rti1eJ* 6jYSSShpI]it ? _#FDHq^9W/W뷿mrڟ2 e5jLxG4? [UUURmC]aNEYzC)6FK0:~KqhHR|0o>d!bsI" (g fe)ZuQɓKvuu~[k4 ݿxtp}e([)kR0:oƢ71&̄Ǥ$eCs*襡e*[i:} +`pzA} VJ+ j4%"l8oWl1v亩"MX@st5ϋ6s5?˼:)]iKѸSw0;_̪F:C !Et'*kcu18@k݁x'C _jh|C,G)GFM7T+_WYAMM^C3\áeJyWYQi;t|3Qǝ6&WC],ViА v|r2)_>P9FY:TOOzXY+i)7Nd|V>khKFo Kc*[w}wb"||#=ܻk]}(+NG_U7`ui[ UhR0zOAO?]i ~_ ejao=U!T_\jͣ߮ڜ:uSVWlRv#Ǧ`h}JC΁|mR崥] ~hs}9MA@_ tY98B*+ε,ve[Uu7a и~BKM^']/P4ui(1eLڡyr])v)pSSO%ǧд%"/ל-tm] TFA:ݭ?a̘1#9^mKҔO?=.WR]XpYޣ!uSe;틵߿˙hհ}{ic\Co:!ھ}22Җ1;f!tovA9]{oR}k Rpr[:}.@Au)*{pJTyG&2.K-ׯ_RnS!6s]'&kC )e7 ZW\ԾT0+{ӧO|ǚ&p>!];uegiRJ9΁\]+X~u^{G@7yP@@`0u Eq?tU/?!V@W NM74 'zVQ>DXН_4ֿHTmQKvSYVOe!u_` ;u7x}:w;쐺>GYi r4[WOngeB2װy͠ R@W~uBc;裳wYߟgmfKQjؾb0+R x 7$u*5zTՆ׉\khx8Uve~ѐY@ڗ>^O]G ;6i^G7Jvʔ+s,pϞ={Y?{yfy睩aFJgXiN$o̱U]|d)&]O}-]w^΅4%K3peڠU{KڛS*},ŋ.( i3I%tYf[ }+0NntSV\矟Y}g[\hQoe]RR%\BaRκw:\&)* bT+DUQ:e7ePsv w'ְQEC(;z 6WPUch; 7j^z:* X?&]wJgp)$+Е=iovx껟,;DQO?nF<o.]bA:+Хh5_믿~oD]ѣG}k[ctV֞7MIŊ~}eEf.M)f nm Lt_Rگs\TY<~Zckl <3 R޵^lBeU5"HC$cU?| uRڦ4wZ?#@=u~%n . 篓ZviM.u?/:0UUR{4ocdZX:}]2bU9r$Uf~|a@4:I+ *=OJcJ 4Ekfb!s}FaP؜gu"_0)FՐJ];̬>e4FK;XFGɭ:khQ3 74{?NdVlH0Ȣ|r> $kHʅ ڮ+罂*ᰦկ V$-ܲguV򾆥LS,0Rs4bx=.;~_vXCTn)  o[#D i;4޸Awixe+ ><,Z~hurꏍ.aUC:tw?]D6 fN&1Λ7/jNeڑ]t=W=DlH6?pH&ewP;u[ tM:72;?Oy[cn4_Uxw2rz+cǎQ*e9a4Lٰar5knJYw]AK;uz޺ $(@לRJx蒉tn* ti~Z\+GYTGkn|sUJ; +vە*[B).U({$߽{ oOa?FM+ݰ+QC%oUk 'WVh8OUuj Uymu%5c8pXψ#"~Y}LJCk.|Eau>_ O ;:򨝚_i1@aC,0uvmvi%٧;0VtvC?r7V<#M,@`Z̓qmTDZ_ԜIJ؁.up*d^AS;=@{WZD }SO$rN>HÌ)/HpX@uF80Q8فN0st\6dN}^_}UUձ;¡}'Х9O!goOO<1ʵjs;z1A+V?sul*h['?IQvFTGx*/~9]bK%~D?{ʕu뫮u&?ի#CʷYY ֆEjG\yC t rոNDsY3יi. hnX{u&r7q)yx2_27_sY6fsP:6unau&g?pÞ)yuJھ.8O#il\rIn<ɷN7/`@H]wܜM:QJiln6S;ծ\uڕW^%ww B$Y\+~?yT +^\ǩm1iI&/쮻o':߫R Csx3L8sCrvZ-k>e$OϤ ܘ#0ލ |UngU}_㟯-0xK{9乎5+YqŃ(~9~>,vC躨k չ|{37 ^<\Mk_`W;'wsDZ97d}qulbo&w\w7k/:6LXuru/+y#L %@w>,n.2ӹ v衇MZݠafsAX9}Sn~{4_}zdSz}ݼb:g|> @/D@Y` v872JGh4}w?ݵ+⡱]jm^ih l9u7=bT,SY .0Uu`=rAقmn0_5$PF_R'2 7ԦBEsv+WbuZx +PQ_h4M>#YKy[pbLrR2.‚Ǜv WoX𚑽}n+vC.3+W: }Ozi.T0/W[JyMsuk 䥺*;;W^9W*yԷ|q1(XÇ:v~2jԨp^խ!L CYp' R ttYlVR@C<8:Q 9Y9bBsw{:_Q]ڗN+>u S睆DrYwP{=:|rYXq'R@sVr1O+rY'pr;ꨣo9RtUw,+>l;U.C*ߪy_ԡ`sx sB룮cigb:tQDs[C63q@eE.&>_54Zy|F@2easc*tS_wk/eY(ХVh2o"tյR9 ){קy@vo6|ú.+x똾/T4\WΥnp.[)>_5젿?M54jէ_7"Up^kڰJ;w\n=0:}/.tA?Ŏ=>\7'HkL@VboM)Xu 5 ,\s'ۅC$/ほ *u*Gѐ\zHEÈ]s5娖:@@*sdů~1k3"  q/';^Q+Pjˏ15Kt3w'iƅ<1;s=9_ϝ9UzU_>|?q~ @LM`ٲeO<1nMy @%G~'k>Fq߭aÆx|wMp ,;qk֬w1e' :::‹_hȾlᮻ /xg<#n裏\ah @ @\]*oy+`>c=6dTO]WW_}uW_2y56h1, ,Y$>yuօ *2Wx+Vg>nyk&<o`Bo @ @ @(]}CwwwCvPZ_VAוW^>_z8#c;;̙S @ @#m կ~_GGa뭷N3!PT*c]uv jtew='?ε}kժUa}MN @ @ \2:W oi3H,hQC.&0]Z]CCCpe}z{>{'>9 [nt]%r @ @ . jHB+ pZ]٣ \ve oxC8׾#o6w^}츲}n  @ @FV=/|aѷ+VjB{lXlY5Ox.]ԧ>>>Sv ]]^zi8ꨣ6>4; Oz @ @hlswλbm>}LwtMX]jtviᢋ.}Qc=Y}{K^<+Bax NyIL\H @ @, fpFх,Wu'DA '/o#/!nƑC/r: @ @t5SHz{{lwtMI5Eu){;gΜy&>|0w ͬ~}(?~뭷:0T\ @ @Ef|1|GWtV! a @ @uYgp@kxx8tuuͨ!@=>tMw\m]x^N:)l#ɠ0J  @ @ PGkuv $K7Gd @ @hFk<{{^XlO;Z5Y0"[ @ @(@@p?~ PѠ+{ta$V D( C7d @ @t Z[t0Iooo%E?aÚ5A׬ѻ1 @ @B]B I#5HӡaGWM5+Ox  @ @ZM@%jn;gLfM@5knL @ @ABHM<Y@o @ @ tBGV! @ @B]B Jc5wte 򝝝5F * 7n @ @Vt Z[yITJ׮]zzzZyF! @ @B]B Jc5$M4J FAW1 @ @KJܪcIlA>  P8K- @ @ Ak6=wt t6 &q @ @@ ] _:kpp0ڐ P H @ @%AkJmHJRjoo2 9A'@ @ .AWcWe]H@U#X @ @KURs$}}}ȣ $AS.%@`k  @ @4K|U_N> 芯vj @ @@=]zԙ{L t%\ꪙj qxA @ @]+;:3aGW`ȵtV @ @! tգcfyuGx`f-qq^ @ @h:A6'R)C<utՕ @ @. t^T,\ ' t @ @- t5wх+(( #[ @ @KUdlA5G nAWܟ @ @4KU#HJRv5Butۭ @ @@@%AYi`]!Ix]~FN @ .AWkTrk"K,C@q,D @ @`6]٨;N 5<<L* 蚔A @ @ / t5|`ȃ  P8K- @ @ Ak6=wt t6 &q @ @@ ] _:kpp0ڐ P H @ @%AkJmHJRjoo2 9A'@ @ .AWcWe]H@U#X @ @KURs$}}}ȣ $AS.%@`k  @ @4K|U_< k' 誝  @ @C@%Gb]zj @ @@]zלU/.{ꩧ* *8@ @ @)](;]{g{]+ *Sk @ @- tջܯzdO!tuuU+( H @ @t P#d{]+ *Sk @ @- tջܯz<;wnv]i @ @ .AWSjL^?BS>b]zj @ @@]zלU/|[~ӟ ˗/jW PQ@U @ @M! t5EFɤT*֭ ]]]S>b]zj @ @@]zלU/X"]~}jW PQ@U @ @M! t5EF|G@ + *Sk @ @- tջܯzѠ+{ta$շ &tMM @ @M# t5MFѤ7]F@UW @ @KUZs $P谣k$ko @ @h*A 6;wtEZ]3AWh5L @ @..AW] Mf$х3s16 @ @hA4>.]W. *T @ @* tյlZlAsZ ]x @ @@]R13)JڵkCOOONpAW$@ @ PWAf$V#."@`SAצ&!@ @ L.AW3k}MlA>  P8K- @ @ Ak6=wt t6 &q @ @@ ] _:kpp0ڐ P H @ @%AkJmHJRjoo2 9A'@ @ .AWcWe]H@U#X @ @KURs$}}}ȣ $AS.%@`k  @ @4K|U_< k' 誝  @ @C@%GOSO=Վ YI @ @i])ֈ;h]}á]q!zMH @ @.AW=ͽ',|ׂP@5!7  @ @4K4qG;=3+I'!@AW @ @ 0{.AU;OU YX,]SyLA@5$ @ @h`ASW {ayGי.AhAWѢ#@ @ P_An(?pazg\**x @ @@]MRQw33<3% @A5G @ @.AWK!>g:;;qK$ $} @ @! t5GˤT*;n 'P`P @ @KUsi]]]]!Ii4&tM= @ @# t5OӤ7]րH@U#X @ @KURs$P谣k.%kc  @ @4K\go]Y;,vZ&@ @ PAu3™z<@XregժU!-{aٲehuK~puׅ[o5u]aݺuG s /áw7zfn @ @) tfGy9$I*RTu7}{e焫*x7騹 @ @@]SzRI ѕ-wvvV: t.a=rK~+2}ѡ-|C x+[YK_ _W{n6]ua*tB95F @ @.AW݋ HJRvS. 0%K}'3o޼ ,ț**Bn-Y6g?pꩧ懲]˗/i'6  @ @4K cwLz Ⱦ;k;3uvan馛.Y$,]^PͿAWռ. @ @ 0.A׬lV σΪ7۲LQAI'1]wuMyt(_ @ @KՒb@q8 >pL{7ׄ- vym;jBQ @ @5t jZ`/D ѕ ]]]vtBjt} {U v[~tMʉ @ @R@%jԩqyuwSO=u/KA%\N>|H/}KÊ+;P! jʫq @ @5t j^dn0cT*R8=g͸1 @+"{!MӰ뮻=o:]AWU\N&@ @ p.AWm"w+W gyGnz2C{{{x'/|a=]uav @ @5t jV\.L myp֨T#0kٲeodɒtMny-;,c'k&@___Xz6l/_~N=rqǍ{\uw:(<#a-?MFkܹcߚ @ @ ]^:Y:g!,Ȃ.lMe߭悮l矿e8a6EtM @ @&t Lb[V:3z t] @ @K5 ru=RKrЕ$I=+  @ @ZX@%jn%i9 y{ZfPB]) @ @ ]ӯWK U]wh]|J @ Т.AWvK ++=,^lGWK}3Yt @ @]"?U*Gtg% @ @! tFݹguɊ+OJg 0kR  @ @4KE!500q P@LM @ @Ak- B$U^t* *x @ @@s]Qq2MCt!@8AWqZ"@ @ 0.Alԝ{V'CCCÎM`RAפ< @ @hxATC+ |Gj PXO @ @KUs6:OR @ @4@k^m6yF]k@MN @ @H 56teI___gJMN> @ @4@k^m6=|GW®%@MN @ @H 56teɊ+ׇi\* 6:WO @ yŶ|GWܟP I%i @ @Yu+Y.3hЕ=0I6rFb bG>o @ @@k^m^57]q}F[&X':[ @ @̒@k^mRymAI:44:::*U32&X'}N @ кyŶٺ]YLj@b bTNnC @ 0 yŶ9  xta"0V Igw @ @u+֪F?pppwtqb bk\F'@ @EX׼b[ۜs]YvvvМ&m2uM @ zyŶz׈R]65r%Pc&X'  @ @fQ 56gܺ]!I@lAj'@ @h]X׼b[l cdI___mt!@8&X'*FK @ @&WlkVwS@kxx8tuuUw T I"p @ @b]mmTCt AW[[ m2u/\4C @ ЀyŶـKU$R)vtWqS ؜@lAq @ @u+P=֭ ,Sdе|pAvak [k}gÆ a…ahh(̝;7]6<9xMΙMNӭ @ @4@k^m6~%dITJ [H.r@~߆v!T<E5GCxߘGXG [nes9d_MM8 @ @b]mmRo7ȿ+}{vtmu]jt]|~w컹8∊c9B9͏{a=xn5b b  @ @4@k^m6WUgM=pgn|kuEЕG?ժUa8O|uM7lT~ o~6v,I>'%@ @@k^׈3g_ 򗿜AN>p%?{Qs\x׻ޕ7Uh8# 7ܰI:= wMxAo6:WQN%@ @h2X׼b[lݍ< t@=\uU.ᡇ {)-Z4rZ6:Z4#@ @h(X׼b[lәNl$d뤿% @ @@ ĺf lCIJR:00]5 V _|qx;9~;=nSIb b^ @ @@k^mR8dŊiV [g~8?~MiY׽.dnذaJ8i@lAO+ @ @Vu+VǒCCCwtØjO+ҐWaΜ95N/d?S @ @b]mmj6ƱAp谣E+`޼yn! 3~??|3Ã>8K@lAF @ @-$Wlk-TQ%/Gf ~{8Sç>) O]tQ{ʕ+t 6:Խ"@ @h%X׼b[lq,ɟٟ˗/i8(aɒ%Oz;K_I}}77}z{{@/{ekZ`d?  @ @uu+:H N9~Fl\q|g+6m6|s GuTt)dߔũ @ @Su+)V ^{a;c ]tQ|$_<va! >=pMN3- @ @4@k^m6j+;u$ej{~_Cyֳ̙ j16:7L @ @@yŶYxhIOwO$+ dC-# @ @ VX׼b[ی[eITJŋ[eLA!b bt @ @&yŶYhk`` tuu$ɞd觕~/<~!G}4|avv}mCCCam ~N Ip6 @ @@3 ĺf3դn*yеaﴊSO=o&|3 #ɖw}ws=Gz 7C=4ή,}?z/Sm2uҟzE8 @ @b]mmR $#iBGG]mZ__K/4jѢEFoteы^֭[.piEd_zr @ @ ĺf+HRaFֿۿ>8>p-"?A},va[V zd @ @u+iD Rtaڵ %(Z ۭ=o^{h]W_}u8#îV^=z_.d?p& @ @@ ĺfեHnww6?U+.af8#F4Yеr׼&l6!? 6:W_ @ @hX׼b[lzω~ҥKL|w^`뭷Ovm$dWS)c{D83Zm2uүkQ @ @@]b]mmEf ;CWWWk1MvA#Tc뮻姿\:\pAƿ~ ITJ]Y駵N:)<~[r5pe R?Vci @ @D;FּzzzUW]߸i oޤَ ;c4 7v[o3rHh ~u 7kSͤ;FΝJtPX|;O/=P__}sGv=y 7|~)o/}KkUw~O~2[.?mUǭ=5xo6wy|SyH;wn?3\{z*>zƿ|~aÆ[槌Sۯm|&d駱8Š+^{7wtȊ:/_iY0j]RAgLu>?<S"2^[E)c^Bh\$JQP7i09$[V>*A\w}n?xyo}>g5Z߽z="      &tɢE~aIyXwAvf޼yFٲe=7Q4uPmL1MɌ}Ӯf>-[< Ad/2a|HB[L$t?\HC,c.J(7EQMͧZ>Tr.cfQgYT_~UWn&IKK]r5ىHBD x@( ,T(@ᏨYaI&:|͛oi@xR^=gU 6@`BTfvO rU Wkh˰%+*ģ͂anUa}f: OB^7uULkN!cܹsGpV=2`(. | Py1 @"GRgBE՛7x2L+O?t@$v_|a$ ^bk׮H1? ]p)W:m#\Ư-*Z BS0Q c0n+'|R)55կY>U.c4ɓ]-(QB ף an&Ѕ K/@K֙F bŊ::x饗x! ]"2N$@$@$@$@$@$@$@$c3gZ?AL^۞9Lx <iԨQS0]Ύԣ ±_fMk<(Lu}xO.pE ;`B{gO>6 =`NH7&s7a)'.ywq1cX W^Vŀ΋ 7ģ,L˜RGOLH`ɒ%ҽ{wQ*R@ao^֭['j>Y I(fͲxVPFJ%$Ff jȸ( &lΧZKɢͧoHHHHHHHHH Q:m6k4*L"Ifͬ<'|"-[J?2eQ"[ W\T2g#B(StEf[ p=_å6?%Gޛ?زkau ۭ~cB_%0%"V1=L[><쬜8/J a~No:3+Y= ϼ\Md-'eܸqc>p3ۘ?QZ5_0Ob!ɦM\֘ު-Z7@LFZk=}q76pe. ]"       ]G4iRaSB#3}ZnmٍF Ǿ}o7 gfaL;N! 6z>73̜.*9ǣׯa'{P; ssG8 j眩^:u?8qq%7 b~?Ρ;|LХ&tՊ:ǔ@CI.uֵF{LEWT#Xeۼys)\(O+P_2&Ieo*2O%%99I^Ybk5¹]8hsN)Jхrɖ)SF%>GRHQ"Dh SMGR`СZJӤD/d%(A8p< ;ʠAt3o,XP;1cQp¥KJ.]D"ɗ/_`}$#\?Ԯ][N<5%J޼y-˘_*\> }g|*7VsSE^/^Ѕ9%-sQ}<_^[+W :P}A1#<_=У+$@$@$@$@$@$@$@1L`ƻG]R2EC5 _*T`9sF!밷}`b}ǂ?\[·^f`{ L˗/R .8{s?&;+EK(aOb P ]N{52k:V%[}5j0'@ ]N;UV5[?>fr-_vLR$      'OHD.]EjF~Q 2v}O)thrY6ʗ/#]6ͽ=}Ѝ33=XÇ[_xQ}i[,}싁.Bn.DCx~΍!m_Rq!k7mp`d}?f .XHBx>6nj`!S1{;>s؂@ ݹsgԝwi@{wq%l\'xbrfۍE~5.ߜfOcM41P.Wl$      #pհ\}_pTS%D:<>5kmq ]ΛP}rt c3"ԇeW_}5t~Æ u]h ǏZ7; -|m>%KPopߵ^k6gwwA sr;bu X0~!vϘӧA/d?"lNܺvj}A-bm>rfO,HvkX-[zu )b.]PM]CxBPB׃>:}l'nah_By_hCBWl      1w,p*uxw\QEK}7C322|Fnq|*dVr1{jO'{u81~vVMn_to?vyժUKyaOeC8{=T{wr^{5]6mV%BSi̱A9JN4'\جӧ]034bu %c. _(w9Nl޼y:'껉bqDDE۾'|"t(8nymKE; f- ޽):cS p u xO+  @ ]) UJbN1͞8&t{5 p3쬂yg^bԶm[W^1>c 6vB&vЅ?{_u|{N#c{n#F<пcǺO>VqĉM6v2y{/?Kdq6lx9  ]+ eO5/P"`ͺ½!?_|?O{_Eիgq^ԯ__? lś} \m%آEdEٞ Wlnšd:\G)O+ @8|Qy*K2C,KvKJi;gOgF(3qM2Əo'৹c QŞ쬂y8#_\zxoAH2K/ 7`f).3tP?{0,l831{@BxGgCFN~ T3l$p}i-2. ]BׁtM|1x# yO?tf_Ȱ#TxX#ھ9P &;`~k1rwf_`aϞ0 [(J{H.֧H.c4P`ϣHHHHHHH N;#V_|U{Iy k&}YGYۗ/_ 8ؿ4E}ަ?b#B3hc 3.ŋZիQfM2o޼F% }q4α  a D*tᘢŋkkϬS063RF }f9v*^nD2KJOO7 +>LN@MXQjS.ҡC{DM^yTxXb܍!ȸ( &liRheǢJQ矹6]tstrGЪ68K r-2%իv0)'ޫ=듽^pu\?Hdƍ]a7|s(ӺsS]M]<&       %5e'iYI-RGk^n g:IPw˥KdݺuzR^eKĄ@' ATN=Bd腿;CyH=8} Qԯ$55Umfه@BJfͬ<]1)Sh1"!sCPuT٨o“+Ձfi0*^(wg+?EҥdgHRnz .޽{[fJYԯXt]W&L`DUj˷9{Tb-Tp{Nđ+Zx|qcܲEJTl-gԖA$@$@$@$@$@$@$֤*oʲviJ C6\rJ߿̙3'7T!sΒ"Ǐ^|$pBy5zt{*+VA2E… K֭:Gʔ)c҃B-"\ ㆇU:u,]V^~e.ޘB<*W,ϔVZ C_Dѕ15h@;zglnBs*hzHxE%G+o̙\sTbW+.\ƍ7JQ~끳(tf      E?4\+Iud)_*_ rP{ ,xqHzzKq[Ȼl!f,T!f+BI4k~'Cpoa D[CXBx ! l:/ >lA83CXBRaFN! vVf@حS.*9'SO=GiM۶m͙}rgVP tVF8(t9HHHHHHb@;T_2Y3m' xmo3y#.gtedFX 6hO/8D/8[b&tBj]_$utY+PB˫ZjV.؅./#+&Kp>c.؅n4nmyvV2^Sp~'B]Tr ]e] q3듳n`ȑ#G9i[ ?1YB$@$@$@$@$@$@$7^]z\,ը T2vrۀ6s79ZZhހ͜)Gq ^0A2=^ qbp!s>8!}_=ɸ$%Η-Ȁ?T{ < !B ]7* ѣ)RD DV*_`,vq06IvV4hn ԇa/^<`1c<2.]*]tѱO8!S$1~Pvm9ywI޼yC>BWHD@$@$@$@$@$@$@1G{\o2[nmz,j#1+{1$p322 q/?̩xa1xo|t,̘GٳL\v⍳^^]wݥիW )Z >B,YR I̳r mh LT믭P~ڵl2!x{ɵAH/("#+JXPo1 >u=j$qgKydM\\j\Bfz[ ]sAzG]RF h8mǎr7[8KgϞVѣصe+yB] !6TaPXN$@$@$@$@$@$@I{`}xR|)8JPV"/gr9Bm: 6 cH $b2egaz7ʙ#\d)zjDv*i9rtMr)؃s )e Y+]3?}@ď +Wl/B:Ϻ>}sBBl޼ߘ yQٽ{-kזoFyy'|EN ]dm֬\xQϯ1PBW(B,'      !p"4ëZoS 7 p<^{5Go䣏>^ɣE;pB m7rSNeY'7.\ K"E%24?PxfhgmV=k'Oʾ}gSa ֭k ]I8-P29 r7P)c W^kh;vX0a݌ȐKϛ7OtԩS[o1 7kSd1({{Q`' .y61ov/zJze  6q([nB6~"'O-Md#, +3RyU V#eMWd6RTTVZiQ ۥ~avyFe E0+4N 4x?Gr)¸۵kxbKv ]| ÇVӧ 1_~Y Ů~!hspС2c [H$!/>1n0fSsG>l1HHHHHHH PbŊO ( $5s"J[l# "rKظgաF%D֤*o qP񃒔3K]﵎˝MS|ԩSG~7WeoIBB0`)ƴBq%ٴzj_bŊ ԤIܹhG0~z-&gr~Ѕ|HժUg=#>"ODxO$@$@$@$@$@$@EBĚщ9?JsW_}7!r!\3<#-[g@(FC4%K 6N͙(t9D~B?֜1'$[D[Lj*=]_@90`TPA0'~'! S֭['mڴW$@$@$@$@$@$@$@$(tQ芁i. eZ肧S|xdRD  ^ t9r>{\Xlףeu" ]xtCͣ+vT{+WJeΜ9l+JǏK| DBBH 4-tu'TtO,gydرͽ;裏jO ʒ%KCV} ]/Q`+ꌮgteSL/u֍adKť3zly @fPЕö9C ss=x⒑!7of͚}.BvQ/_>YptU]X(taSE AJ%22        U(t"=8ЅaJ ˗/ˎ;Y]zz۷'NH DQN%Cb1  ]ro=zۆb@rѣ|r)Dm?WΘ1CZj%sۮ\H+l^zIVZ%?@V< .Iqqfن ᅲsnFi׮ 0@J..+2UzՐ7bQcJ>"ͺMy.`ِHHHHHHHH PЕ퓌4$u>~Еi1ae˖i&5jL4)>Ajݺ@|ճgOyMZbKΞ=5jhzd.ZH/.\XDxbiӦM:$m<#O*I#AKydMţEv$@$@$@$@$@$@$@$@$#(tQʑƇd@E lwMƹN`رɿyk߾}z.-B׮]$55U PEѣG :RΝ SRR"*.W^U^CCʗ// 6B _-UVYDhc2}Έ.\7Jef!        l#@BWM.2I[l1߽{,3JCG`Ҽys-P]V Wرc:_~eGBPg޼yYӦM}8uT9r{geܸq>tA{+" k6|p>}GYfLF ]`ϮQ+!S]zrefR- @E+G'-t|]ң+*^XUTm%dF3g]w%[tY|FBBX9s^Xkז{5\˗/_`Ix))Y?~ܵ* Hח;w֋6 B̮><)s>A9˸$%/-HKL.:!       u(teDЅ xBgEҥKZ,CJcƌѡao۶m?ydLk۶J{DuK*0k_}UzxE2ٸ-Z1HHHHHHHH⊀۞ΧZKɢjvk{a"Bbz13la"E:|XM֬Y3]3ݪkР|A=Ξ=+ŋ6:u$K. h/-^]lC$@$@$@$@$@$@$@$嵽e IЅ0uL$Luݻ> a~K.dɒu Ν+EgϖU1bL6M_> :2xuwvޒ $yymo3'f8p@zI:hէOYzTV&D;S|MIMMs~B G}ݲrʠ56iDʕ+z .TB%{Wʕ+'EG}[;V&L_?,7U5Yb`̫~ܿ8HHHHHHHH yQ 8%X,. ]1v2%97|o^{=R׮]eϞ=RR%+?'.;&K֏B?/^eʔGJfB8I&ɮ]ղeKa[+ '#H ]e        -D[@]={-8SʓO>)jڴה)S' r{x[K6HUu-ۻw5J{]|ٯN +TW*B/!.xG$@$@$@$@$@$@$@$HEDʼn?хЅݻwOzlW^=IIIR5k&ϟ/Bi9ѵyf{̙3RJM6ܯ#G駟'OJeݺuRV0t/..xG$@$@$@$@$@$@$@$HE+fq%iӦMÇ[n?Zp۶m,. \d? :ŋ lٲ:l!>o  ՠAٱcJxu$ac        &=/m$dBHj۶ P)!-BLhժ_?o..\… KF|~oƍ>yysuɉ'nݺ{:uB.]h.`eGetHBdO8QbW͛+.Y5_hHHHHHHHH xuk{7أH$FəCk֭:" y?~k=ɓ'ѣuի]vntޜ9sdzϷJ^[ g|        #=/mc"!>GҎug}#xE-]4WC٘1cV1PEXvZQa6m1B_bt!`ۗ_~Y SNFZ~IHHHHHHH⇀W?3=u#ԤIcСړ րyCʕ+B Izzp~gtY9RzsN_~xm1`}        !=/mόdO$} Gn̋c/@ٴijiذa623|!oM6ԩSuʸq|?iٲ{衇䭷)GH *%%%E{r->upRkW}         !=/m&̄@yIƏ?(ݻw(6DZjFk֬={HJrevۅ hѢ:!+㜬_]F b=Po> x?.mڴkV9"˖-s˗QE-Z$zY-^]j @ꞗ6coGHJKK3~%K}g" xm1ꢟ3IHHHHHHHbW+)"Y`$}7t*UJvaQFrqٳg*TEbE?g- yymo3V*U861%+VHǎeһwoyu.t!bE?D$@$@$@$@$@$@$@$E嵽,.4K6oެݻKrrr.u.ӟdΝ_J:uaÆdhFkW}+% ]xIDAT @ꞗ6lzn8IƏ?s=0Ç*U|9ݻWʗ/xu dyymo3  ]LO^yII8z-/_2kEDkW&+ yymo3&%;G i˖-VH?K̈ ƍ^xȐ6l_?}Y3"XBW=b_K& פy2|p2eem2uTO%55EbE?g- yymo3VtcB+:СC2n81c+V毿*CKJ|^d=-^]~" @ꞗ6ceHڴiIۭ[7a  bE3HHHHHHHH 1xuk{1[;e]k4@IDAT \U s!Y;EvdSEa\4(L8n; "#QC[|[H#6iYdHIz/ޫWη>{9|ΏsoY {g@5kVÖ}p_>0g˴)CA @ @ /yŶxIJᛛC$K9J@MNe  @ @u+]i655It$j?xCGGGx'q&?_8YZ Iw @ @b]mmb51$IlGG6v\s5aaÆ P(zi+S@lA~ @ @@^b]mm3/񨟥BOOO..,O=2ǤIqΝfΜ.\"Ηm2u/q"@ @hX׼b[lpvIWWW7EO[ { x;}u1&X'6 @ @@k^mV%x4Z3.Bo|/n}׏MNƷq @ @bu+bǘtvvf;ϟzlKxêUE-d_˘r- @ @ ĺfm*-J]Z鶵W'G}tXbE~wIMNFH @W 56y^XfMhiiiV_}'>.կ~uTG I:ѣU @ @zu+z5}wt.75== =Pַ>yhlA~cT  @ @Fu+͑z(&G&IRտ1 :sòeBsssgaԩlqfm2u;B @ (yŶ(8o}wauAG? mmmgh믿> @lAq @ @M 56-ncO>/}cvt5g>S|?WPHD׆ F]^m2u; @ @@ ĺfmlIUX;wnXzulcf~{8sN>=|C =9sf4i6G/d_~dA @ X׼b[KU@ ˗/˖- vZZhb bG  @ @S 56zI (A${4-.pg}k6OlA~ @ @[ 56(P䖛o)EMhG\gqFkK.Ŵ 6: w @ @u+ƍ8F6+|=W;~^{-_EXbE8#CKKK={v:u6G~o[ 6:o}!@ @hX׼b[lxu;{7L4)Vwz_Xʴ믿>\lA~G @ @/Wlkiz0s\ᝇӘI` 64&LGd_0< @ @ ĺKW@ ( 4zO~2np k&X'X۸  @ @1ĺf cL:;; }}}Ykkd c#0V&X'Ƈz @ @/Wlkz8@Օ%ZZZ.Xb bco&@ @ Wlk1r#ѣ ۄ 6:Oh8 @ @@Ub]mmA d;z{{=w^x!,^8\򖷌ؙk׆n)+s饗viNm2u/}%@ @hX׼b[lXy DW[[[ =\rIx~_msB!'7xc8YGb b @ @4@k^m6J:$7>{7\q᪫@.\=3wQQh@lAл @ @@# ĺf#lcI iж$Ib4h1__=jw_xk>ҧ|&X'#C  @ @ĺf^Q?K $]8w)S_=Z*|իáҺ֭U 6:~ @ @4@k^m6R8,500j1cFx饗BGGG8cF5GZ;.L:5;J m2ur @ @h(X׼b[lp0Y+ ڦ& ?׿_ç>Q+_Jg>gOBCb bu @ @I 56)fcK2˧jmmqQ .rK={vxGɓGwYrz,-d_;J @ yŶGzzzDW$1[47=0׾쑄+s9'|v?s8JulMN  @ @r,Wlk9Q]H H wɒ%Yj֬Y!w+-oFvnG,| 7Q@lA~ @ @@^b]mm3/񨟥;$J5ׇ38#<#P(dC>Swݰ;6 Ad_rA @ @ ĺfʅ"tvvf;ϟ_ h~$Wpuwmmٻ=3|s _| -@lAw @ @Fu+FXǑJ]AtN^W?pxgC sΕPD6:W(\4C @ PyŶYKe$e*Y&QMQ%d뤿8p @ @ yŶT;,9]\`pb bzK @ P@k^m֟@1ѕ>0I%0iҤ}c=~0r)aԩeb b%j @ @Գ@k^ms ۶wtm*%{ +V7n,&LN:餐J_fx&X'<Ǩ @ @,Wlk#G.tuuCsss1)Rֿm xnx.D 8{~8^/8,6:J @ @I 56@2iҤƍCP#Q l_җL3&X'Ĉ @ @-Wlkz-d=, HtmK*q}/? ,DH1!6:)8T"@ @ȅ@k^m"urXхtWW?}l-3τ{.mo#%b bK|  @ @B 56"X#D:c=1O@___W-[]SL ohjj _|q:ujźo=tvvk׆>᠃ ~gMV 6:W4h4F @ PWyŶYWA3e $٣ [[[ˮB>how 3f;CUaҥᬳ /bɢg`_=B;w<L'c bˍ  @ @#WlkH=-%tͅޞ/})L4Tr.vۍkf GuT8sqVNJ{lXn]/'tR{ɒ%ᦛnʮ&V\O>k nUVe?~ٮ$E$u]!MItX)I  @ @@ ĺ%pܸ?!ӘH`nz)7iX|I=>=}Cںke][pa+s9'|_xw38d47lؐd1m2ucxF @ X׼b[A(;N;bNYk_Z믿>G |;n햝{駳SxWG,{//ݵ&655e#LwZ;wl ᄏozlN;a|,X-Z4kkY+MΥnxS7ɯ&X'Q @ @u+HߕTvt 9G~%Kyj\ir(ݱu'xX' <qHOs Ws=vy|[?ri4A|&KpLkgvi _B8cGlA~F{ @ @#Wlkqz2lGWooGE/'uKu!鮭;,{~btձzWG%a7!+z駇{gjӦM /r8ꨣBz~҄>… C_%G)~/y~G]pKfx: @ @yIt Nԡ@1VӥJL:5u vi[%]Xzh>}zx^lntXl{w͙3'<##r_r?؎;[:/}K᠃ /b뮻o8gyvc[ܼD @ @ȇDWȞ>Q/H ===Ak8MsNj@{n׿fϞ_XgSl} O]+o{﮻}0wܐks=WLnu{ ~zv<ɕsg.pc bG @ @-WlkR]i655m3+/B+׾ϗ̌3B.ܛesavۗlnc-X ,ZdR[AG<)v׆~-xlA~ف @ @@nb]mm37%C!Z'k+W_!9~ҤVXwaʔ)Ut,9~zpG5\.\SO-Yn{wH71K [1}٧>o| It @ @ yŶH{Iwww'[.7QGme?aK+} _ե7n̙0s['OުH<|_Ί? y{J?餓ƒ>Kc{grc9d?P @ @ yŶhBooohkk94iRX;#{DDg >zHG^veٱ fQܼ@JTs zk}I4~w#tvvf6/s>;;{6:;P4@ @ PyŶYc(W. %|r!>~8{aݺuaڴi!}aJ/Y$xYgϞyCO,]4uYYҩT$ײe~&Ҋ{P(lNz`wwUVpc b  @ @r$Wlk9 I]-!J]ZC pUWﭪqŋg ?;Cؚ7o^KԩSKvDW˳[aڵaw i"3'Ì3J^gc b' @ @ԯ@k^mohw$֬YZZZFS^ ;wnIwI(&X'E @ @V 56Gէwt}xxя~4< ;UJ`MNb @ @qb]mmq#8] %<ovŁ*>e_b}_F/d?P @ @ oyŶߡ{ 5ԥ~M4iI]6ls+6:N @htX׼b[l8n%]]]\NFi-Zh\YpZ9 I?6n @ @@ yŶC,7]i+}gm2uү\h @ @b]mmNH iЦ!@rMN- @ @ԛ@k^m[OyԩS o}G+ҹo~/_-+Du]s;zxlA~Ɵ~ @ @_ 56)ZHdҤI7B0pg? g{ن\G oۋ>1cFxɓ|@lA#BI @ @ ĺfR $|'p CPsO7o^x׆$5$ [&???v{}[ ЇʢVm2uүU< @ @@b]mm劕H:::wt5774xk׮ g/r8u];0}oJϟ,Y?p76J Fd_Pr  @ @ u+ /@YLtUMԙe]%{3gf=|l尉n)\xC V;MNF$@ @@k^m%6ԩlG@hmm^IE:裏ŋK.H~NȒbu|@lA#BI @ @ ĺfR d,ѕ&>|O`ƌᥗ^ ˗/GqDq#%~g~^JZ I  @ @@b]mm3wCBi ?G`v ׯ]]]ᨣ*lDW{{{8*j% IP @ @ /yŶx]Xڥᄀ7|_mmműBnW.eMN%  @ @&WlkyK*tvvdI$I!>n-y.iDk`8䓳{}SN9%ZDW:sw黺ҤOMNG @ @yu+ͼģ~Hpw8KK8fa§?|${_W\ti.^7xc+?ww 1DlADĖk @ @F 56kMR-䮻*_>; vm7ib bsM @ 0 X׼b[E((R.BooohkknZnWW7M&g͚pE<b bGJ @ @M 56;T .Ht ui_=XXrexꩧ† .9p衇a*8&X'  @ @L 56,tLmKKGxI Ip @ @ yŶ(ߟ m2uү\h @ @b]mmN]َ&0`D'  @ @ԽDWi`֬Yut0^,ѕ.755ItF^*j @ @@ $$jn.5Ffd[[[؄j* @ @HtIt'Zi]]I+a*, UaP @ @DDWC tuuz{{C[[Bp]8N @ @ ]]Ը{Y%w }$*oE @ @elGւZ*  @ @HtIt&X#h+}takkk N]7" @ @Z HtIt2\klI{{{a͚5el-E@I, @ @ȍDDWn5zGW7Ы+ U]_ @ @DDWcL(&G&I2@@& % @ @[@K+Gwtq]5v) @ @UBXiIWWW?477Ua\- 7z @ @ HtIt?َ45B5!K @ @DDWJ j&M&@ @ O.|Fn\]]qwDW ] @ @@$$^@+]oiiP!@  @ @- %ѕ?¡O4% Hte @ @TI@KJ $m>,$IӇJHtUBQ @ @&N@KkϕG+L4qP(F[G9F! 5 $E @ @ԱDDW_[$ HtUTs @ @j, %Us1$ gqFذaC5&T!@DW) @ @G@K+?oOi^xx@$I @ @5a™g}16$J8F @ @ ?]]x{| ѵqƐ$IFN] @ @ HtIt8\n _^%ƀ $Fq @ @@ HtIta;֯_  *( UALM @ @. ;,S }Vaa ]X&FI9 @ @/ %UQiԧ>E HtUSS @ @&@@Kk%H ===̪ 0DH: @ @.R=L |KKG  @ @LDDK)tvvCkkkU'@`$t#@ @ P]]z%Bss]@$*) @ @ %5ae dtAIL< $ 5s @ @_@KTBˣ  HtUSk @ @j- %UsBOOOJ @DWI  @ @F@K+7qGBooohkk T^@Z$@ @ PK.ZƛkMKkljN@k8  @ @C@K+w/lG0z0 @ @X@K!rcvt.lmmCuUN@k8  @ @C@K+w/š5kBKKKFO] @ @ HtIt8\n 54UF@k4J @ @_.N=$PLt.Ldq@___W-[”)S¾_NZ>+aΜ9'k{;誆6  @ @N@KvJcH::: i;JX.]:/>{,~!]ke][pa+ˏ 6#<2Z*|K_ rKH(5Mu @ @! %G{]ޯX"K8 _6nܘG3g O=Tkep饗?? /B[L @E  @ @D( %an٣ ݽepWg}衇I-s5d4LSOݲȨ}_~9?'xbx[*5jA  @ @) %gkَ%_=\ lzlN;|p1d,X-ZTب~{ g}v۲:]S @ @@]]Q '݅'|2̟??']< ng s Wv(=\y睳 wyeG:dɒl_*O?]#9G @ @@* %߄(jnnI0ׯox2mڴqGuTHwxIep@~ngg&*JwBYvm8#"a֬Y#w @ @:誳ԝIggg!]O]>)Ow=Don>{Gxꩧœ9s#<2RђO4r-裏]]]CHt*y$@ @ P]]u: d;EV$*^{]ce wѴlZg} OrPk,j @ @.F=N ,lzta$Õs@E?ёzo_˗c9&;ZhQr5O8jrL$Ħ @ @誛`ԑa,ƚ u'l!pW:;C#}I';sí:h~HtFI @ @q HtIto@>F_Ltz{~8{aݺuaڴi!}aJ/Y$xgϞV\O>d]C8 @ @ @J]]U -VP . jjK. guVxKM\˖- V%"q @ @*HtItU!4Yadqţ +ŋV;Cؚ7o^KԩSK6 UA @ @*, %U\B*I ɻ=3@5kVN @ @ ]]y%Ef;b[Uj @ @@$$d.0n,ѕ.755ItSC@?,|#@ @ G.0I@$Y @ @5Q8wtQU$J8F @ @ ?]]x{tuuCss]ƁWA@ $@ @ PC.KQ ѕ&؄j* @ @HtIt'ZG{]U< @ @* HtItU94_х}}}ULM\@ks  @ @O@K+Q_]|KKK|7bU"  @ @@@KaH O>d?8R$6 @ @@$$9$IDW`5K @ @F]]5 5@YHD誜 @ @LDDDĝk'T$Fq @ @@ HtIt}`]}}}YkҤIHDW 5C @ @`$$&(\ jjj*lK@k[Bݕ@IDAT @ @o.PK$$q;F_E*j @ @@ $$jf.1Nb+}ta$lNu6 Htm7 @ @| HtIt3ruQ@@.A @ @]]U /MWH *f;*D8 @ @ o.|Gpvt8Flj$ U#h!@ @ P%.*f+(х$6 @ @@$$8{ta__wtw]U< @ @* HtItU94_lGW R4A&M&@ @ O.|Fn\N O>d?~\#7ZU2  @ @TY@K! wt577$I*Ф&H$ @ @| HtIt;}YHD誜 @ @LDDDĝk'T$Fq @ @@ HtIt}`]}}}YkҤIHDW 5C @ @`$$&(\ jjj*lK@k[B @ @o.PK"Q/FH ===Y+I:G"?pZn]6mZI'K, 7tSv4ٕƚ>}zY|\x׻ޕI^;ΰ.~:|ήqưvۅ4kDh!@ @ P]]zI *MM|I~>!׻ke][pa+֏mXxqHo/YnЇ> B_tDWIz  @ @F@K+7qG;$"}Ŋ#̮x믿~;̙0sSOɓ'oUn>я+kfժUCoCKt  @ @@$$rv8|[kߐk-pW:C=TLzmُk&{azzE%\swy捻\w @ @_lGWFo5v)< KubcN-X ,ZTqp饗fm;>-+Ktm)7 @ @| HtIt+bm2š5kBKKKF]Sv-<3aܹa^ ;sv>i3 f{pT] @ @ HtIt<\l*L _> oȪ~{iӦ_~9uQ!Upa 6wEͧ>k׮ GqDVd`` ̚5k @ @ Pg]]uSBJ]$I"O?v}dɒc=SO=̙y˖sW_ w\XreV-~&"U6  @ @LUMˎI (5&?Hlj}nvZ6'xbIJ圼 7ߜU9síZNbY"/ @ @4DWtuuCæl ꫯW\qE6?zHk6\veٱ +r|0tIٱs=7zCΧ?e#ILw{[A5uM @ @@mVKVH:;; iЦ.xᇳӺu´iB84)^dIn̞=;\2L>}Ho~+ ~/=C? *- @ @j%`mVҮS lG@hmmD{ 0*K:+%˧Ie˖oJt;;Mtαʏ`4J @ @ Po6HY/KtM4i@w/Z8wa,5o޼p%SDWI  @ @ P0jFIwww!TK 8Lqg$@ @ h66x &%  @ @qjIgggk5Kh|Ac#$@ @ Ј66] [[[wFF`L @ @[ 5PCf͚R˺04=6B @ @(`mj; &< @ @c9&6&HJ]$ue 4ɠ @ @Af wqƘtttqԉɠNnn @ @ PͲ`%`2hi4 @ @b˝nqf;DW[[[c(ԉɠNnn @ @ PͲ`.+`2h{kd @ @Yf#[¾>j{kD,`2 @ @IT lGW---%И&ƼFE @ @m6n%݅'|2̟?Ff4&Xd07  @ @14A]!I h<AS#"@ @ r1,A&|ɠrZ"@ @ @v6kgJvt &"/ @ @Hfn,ח%&M * @ @j*`m.6NjjjgS `s  @ @ȋͼ)LJt3G<4s.n8_kۿ۬_+wy|0tIźӦM ԩSJ}Yn]x^|O,NT!f͚0k֬~p!dַ-{ƌr~˱l~ oxCv555e~͋d߯ʰhѢ{)-+kᮻ Ê+O?{G>򑏄O>9L4i[>SO}*,^8;y0́;xv;s3gNV/b^sOxBz{{{+fΜ| ! @͊rj鎮%K6nX!@r ࿿ٟrޟgȟزeˊq:b=Pa0 ?SL!eJ;oy믿>`kȩV4R&OK?vڭXpa60* &击Vz|مշ>, &]ikl~n0UK9O__6U[2e6=h-?~ @ @YYOUW (;Lײ|RqSjGת/'2mJ?~7_N?AvX/IO?mKiw ׯvZXėZ NZd0q3⎮Me+]Kw &E]xzn?? p_np 9?O|x^ +W >JOCvIo{צꪰ`M?`2qo{}կ~.]Tܹsիuo}+̛7/o t[VwlCdL{M7wfw裏֐vnp}IQTN ""p"DA 1ED@0a8{/ )'/ 6S Qpeʕv׮]mO0஀r=) +:t ok'P[4]>tP3d} SNzӻW@@@pYg.R\T&ճP8I(A^t*Х9sQ=e|Cyzd(7=i)(=3חɣyo)puw} e 8aſ-]z?3)xM *y%ekմzg5ޤV`I{/R@9Jƞ^h>q!J u֦S$@@@p_gRbۣ+zdSn@3yoYjFvvuW_}e?xYZlY0o34yn1QR!Cxl :oU@xx+ *vmc= [2)(TT=oƞ3}m, \odɒH%/   )0e{ňP ʒejƾ-֭[gרQ%;a<O{v$##CW. y|ř,Il:tx^P Ⱥu gu]'_9LͫT"jՒ:*dQay]suw<pvΚ5K' x+:,T e!\~I7o. *(]G\k`nǑ@@@/qd,VA& =d(i[%_UPݳ2K?k3xiӦmo E/=z0>@t̙3}7ХQ޽{c};wW7p0\ EBk?a ']tWwW|MB #1[Ӄ͔nݺ)=qf3h#,1D@@@ l䆔;tC؝3!|$Evړy" rڻ[6?w]8] ޽[5k&s]A}Ԯ]PM7]}\xᅾ6aDy|S@ϙ3G.bV鱤=R$AuU4]k3l۶ۼys9ڣc[`V.y4M`S^ɑ:xrfIZ)FkrM7L8QFT5{pРAG'13@@@@umNJQ0=MKKi( yqZmFt/ecAj_:ZFi']vw70L-NB@w9nN$=ׯ/`W__D tmڴI7nlFЖ-[&M4 hӊҹJ]YYYJ#RQXagϞ-)))?:@@@@ v<ی5g*淽vڀd%/@pZs8YMd 7ȡH*5丫5&G'_N?W/":ێ;D:P@7_ג| iF, իW{XWmZ>eݺu*Cz=뮼JCj.ߊ!l"+Vo޼|7[u/˟)*U2Iވ79pz@@@@ <ی2pMףK I 8R6o= gO/V9;f {&1݇Iӻe;ߒE#.jWz;Pxޡ4ЅB, DE|ouֲtt?0d^F_V t~޿zznK.1A˚5kʯ*Wy^O-Z4װaC=7u`_x< ׻]AS(c]?cy'L5=#/.]<+VN?<WǗ@@@@]mIi<zj aeY-?92nޟVϯʉKݴ >sɹrG j/_^,Y"gW/MZj%;hܸqA!_AME.._\8 _>h:tmV4 d=& N6M f][;Tto/1Ȋ~FcrMqS{7H،   ~<`1L ,E*Wqr'vUWV?'    ,MgƧ t%R$pGwr)*ʢ`'gyFZ_{tX@a:t .N@@@@xYK]ɱGWJJJW P0(KW3:m)]jb52p`yy{޽eܹ2tPy)2նmۤRJ@@@@ 9x׹ ʕq'A]P5~.qݎSO[%., x?>}H-⸦|y5hРW3"   @fI+++]?GRFwSNy*\r)w 7 ժUs~:͌3<7[nڵkK6mkݻrB0pRa[A'gmGi']vLq    xYPxM+--Mt*[u3g\~k.dž5n5j?&~W0?|[ݘ)Gb]Gȏ;zc+ɍ    6P ]VlҡCٻwԨQCF-;w6믿<쳦Zl|3IZĉ͡-[#Gck֬LIM0,njd>oXEP{yl娈@@@@x¶x0=֭['׫Tu… B )g}v@ 'MdRq̘12v؀ᬬ^Z5k&֭[TZwsy@モE Ly}<䋈]޶ܜVɀ    @< W| teddC}CXduYC~Pu͛S͚5e˖-RbBBm婧2Y-Z$ڵ+}ž ' )> JjϮQokCZГ+9oZ   DUgQp3fիW].Rw)>٧&oЫ`frPJn f n۶yfiҤ ˬ{_ګ`l%sv=􂵲!K{ | s4sr谂    6ݒXX*UG3DXWǢ#jp~3:B{ao{eܸqN]ť;=^5lлį|0 ؾOi5yvf'U     n lmQʋ)ܮ^}O4Cڵk˶mۤEbŊ*;v쐣>ׯA1w\ݻc-R0o]>ܬϛ7OzW> J*;Srrz@@@@ l3ĜEOׯ_/:/ei܋@t'UV5S4 *ըQC~w3 7_]woJ߾}:k,@&=N{x}O?I۶mMQS$!pȇ    .E)/UW]e?LF`֭RN*zHuʖ-[yrʐywN4IFi6}rEX^\'On-`HBII@ @@@@b!@+ʜ-sضV{N8o/;nԼz)"?ws^>9駟J.]~=]EXwS@    H[=bztH] ]7m)6@W)sj@@@@ t% `ͷ:駞GW]DnYkAQB%@+@@@@)hRVf@۲(PV-پ}hBVXG7رC>h_~o[pܹswfc=&rK,u?|p>o<|] %Xàx* @@@@JKg%y#`y+T (@D{,\PW.-Z$۷7tq9esܶvZ3:t<ӎg̘aq 6 7|D*F~],#   Rg\%<ɾdڴi%-(R;|[xuYL8QFm}ҭ[7|Nmۖ?^6o,M4)viɪU~'Cм R1 `@@@@ <ی6*uM5)d_p+Xo|i޼ PլYSl"+V,l _<{Nk׮n>,k'xPlà$zK{@@@@xYZ8VNN+9cX;|[ 6y 4i9Ҭ3FƎe^,X ;w6˃ _|1`^Z6m*֭[TZ՗o޽Xl>on,abA+y=-G@@@J[g}8$@WzzöER &˥C5jg+];gVƍM # 'ХЇ:-[ʨQ]k֬zH4߄ ̲afE+9-F@@@Egr%G8… mH]$b%0ge׮] ׼yQF! "/B2*X+Wwk|FJN@@@@f\\*moذARSS&X~L2gJL`_~r7JjOn{oYK.m۶IZM6suݛW> \'@@@@@6c)\0=4]RFn@@@@HDm&UK:3ta^{Ze> L     mFB$`.!䘣+J|$    $6%]M.iҒ4h aM]F@@@@h l3Z +''^v?S&I+A^z    @B l3/_Uף+55U,J:@0,"    @4xM]v[ʲ@ =KJB@>f@IDAT@@@ l3v֜GW^^4J@> @@@@@ x@ t_ʕ+ $K    1fL9Y [{t(G> 5XF@@@@Df\) ]}@0,"    @TxU^ wYʲCZr@ aמ#     @@@@@ xW-ylعB+yoZ> J     ]mFח233mi][JKr>     $6%i,{Æ f$5DER(    DYgQxWL+//ORSS*-%~~@@@@Sgyݒ&Х7mJJ d hwT0 +"    @xe`wUZp=]-Hv>     $6%k;77,JVڍ|NJ     mS&`egg֭ À@@@@QgxՒξ]& :    ft})]+++߿%SI.A4@@@@f^$ѥC')F :|DǕR@@@@@ <ی/+`effڛ6m4wK4\$h>     *pIZmJ O/A9    /MM)1z@]hYVD$Ivi.    eDgeB&I3 yyyѕ$fNYs&@@@@pOgYRRl{Æ J{s$Xn4lдxɒ%R^$j=ME@@@@D駟m۶rI'%jSw]HDb'tR߇Aʙ@@@@@Kmڴq@JBe.t J    *@+Q\ ]~zJkNKc$o>Yr9[ڵB 1:3A ~3g^'jQཔ׉ZƿF01x/%u/{ɝktAٺu)TRŝ)(]:\ZZZH@ 0a (勒q,x/` ^*"pXa (勒q,)`k׮'f 5 @f\**Qཔ0ƹ8@T/ax/%̥q.{)/C ]bYVNA %/ #{GJA^rǑR=;qIʲͯ. hRv2 ^JM[){)Ljh ^.e'dڴL @B}E4 /pIEcJJKQ? .ȦתUK^y KXbiF}M/'ߌ[ tG鈴c&'F%O>DvjJӿ{93ܻSბ0d؆@ X!vff&ĽH] sh ̝;Wzmj}M7ԩST: йP5k&{1ipkܸq@]E#yt%ﵧ hb~0=u> *{JCV@XAX:{͚5֌#PK.NMIaҲxui gP2!#nڢz2.@48 1cyHS6eA@[w}|r3짷]"ZzrK.]I&f~ȭ[.O?rMK%رc>W[^JٷoԫWO&M$:n[t5J/^l#gN>,Z@<:n7IʰMC(S| C]ԩ#+W}%!@hnmƍ&޽{e͚5 0rgXǥW^ a/I,~iѢZJFa 2(XJ{dMSLaÆ/=$NנA_U&_~zꩾmiof:묀< @ Xoa6lm5GH] qd|7ұcGo0?C9sT9s 4|7sgM+ dp fN⟼ug}&:u2)s iOHF~H!<۶mڵka ;%e[oU}QllC0<.LG@D ЕhWƫ@nns9yf3dWx%ku %#]h.}p_<Th h/.ͥ}]3Zs*(:ٳM6qW82(/y6w\Sa udt^ +;;^~=q=i Po:Txפ*#_%1:tOxfy=HӦMM޽{ AlDGtfߒ%KGQ.÷vthO?=(? $(GeV6ZD+@V@W^* :,NFߚO>]n=D 1ϟ/ݺu30a=:1*N-W\!/RΦ=O:bA$ȑ#eҤI.E tY,X`\-[ z7(֬Y3믃c$c7ZLS[@D ЕHWƛΝ;7+k&N(FjR^@{I<شcԩrM7%|hn rS(Z@y4ׯ_A@ ƌ#wi*v%5k۶tc _.^!! -]ђܲ.?oذR|ف]؊9s{RB;_~_|#>|X@@=>rʙtۧ :/W6mLJ*9!@  -`ztIzzzB7#ğ:t2eM:t:oVld$a7fϞmtB rꅯ5n8h>t^ݻwl9oרQl[r5#&k׮ b )@H"2 `;p\:zժUEfZkg1J[T\9"zj9묳~3_l[D_|Az!H PvLk&Хo n h*ul jkO=IB@ t=E?^^x ~"@+ sIsϙyg˶}o+^-$!@rrrlѕRZFk@J]@W_*PtHB7זq@on[N93W^jժE*@+Q.oѢEf5ɵk.۸AҾ}{4hho//M6Myyt(zyWo&iٲeF RQQ@@@@@@ӥ]7 b@@@@@@X X;L+---汬B@@@@@@(.,J      q)`zt[NJ!     8 ]Nن      @\ X999vnnC@@@@@@Xƍ%-- @@@@@@ ,{Æ f5B@@@@@@Y$55]FlE@@@@@Cҡ SRRtJ      … mѕ      ġbִ{ D@@@@@@,fbŊ~lE@@@@@@ s9۶zT @@@@@@g큃ɦMs@@@@@@8rrrG}TffV*!      ,`effo< v@@@;v7̰R"  $5{l4iR*r@@@ :t̘1ôO>??~GrÆ )S},@@@X @ԩS^˲byn΅  $@is1^x!l+b/[LZjdD@@b)`-X2d^:\   @FKq5j$k֬#@>h_;*V(зv!:<7|#:t0̓*oټj*9S f1. ti8pK@@7_.DzxA@@^)Oʍ7(kK.;TfD 4@֣G{8#W^f]w%ǏmuB9tD @@w3)P]$@@@ 4t=̯Urk͛m-v_}A2퉥=;_~1s_tϗ ͛M>ǹ\rE  ĥѕ'qYA*  $[n4i/_P5`uW^ԆzHFY(3333pLϗnݺQFĉxE@@Zz{q}  $/a&Mr Xy:fL+V-[C 3fh3guIִiS>@@@xrrrlѕn   @ .2y뭷L[tiӦ9K{U=3gr1cuN?tYrԬYS~guúuVZɲe˜e    ]J!  @Yt߿|w1Mԡ C Y4k̦ 9㿻X˓'O#Fc|M۷+2`mʔ)2lذbA   @iXYYYwBɈK2@@(.۶E]ڳkܹ~PI{Vil]:wVIO?$ 4C%\"l 壏>+ʦMv%= #  1M+--Mt̝!  @  ti5էOy4Mr'ƍO;J*~袋?4/R8 ݻ{.@@@`P   TN.?M* ɢyth7o!V4EE+_ >\vmݺu3=%u:S4כ;ay nX!uxHnx7n,/i/^+;a?M^{]p/.'߉X@@@@@,C7;//OzXv"upz+H\^{^+۷e_˱ vCԩS&G`RJDDŽM~I Ϝ;<+ZO< -wAٲe Ve]&#FXp@׮]eϞ=N|}zuW@@@@\E92rm];vDЇ1X8o;mբ+on9s&חKC w>*m74ګcݻKQPQqk% tiN \ZfiϸYfVr͚5QF (<ȊGlF@@@@0]Ѕ:ܝw){ 2ydiڴ]we?Ҿ}{3Gߤ/*ЅڣK\G6ݯhKcB[?D v8p t'Jڳ- rx㍾ !:*t}I945}]%6\`W(۴iP9ӯ_?}n,GI|nxոq〡 >"0`|&_79y{,@@@@@ ,˪gp; ;.4iD}Q`׫*ӦM2-W'IDAT7t_njZjUW5U bŊrQGlfQk}[~wz0[ckxСYnG2v,ȥu,@8zk:x0m3}#0D@a>AلAƈk\B\rŰ$(&h@b *a7)D a`Pepo潑]UV^uuOwlӥu>\NwR}5_ 4>6˓ =13 ع޽{GքiӦ+R~Ʉ -졄e'dؿ73i0t+#JXjS ;I \m &)4z`]Q{ryr)xa=UO@~20fwy~Y#G$OtϮ(<Ў={>eAke hċҪ2B$DBM$Qx#۷ϷFQ+,x" ?Ѐ-[dl@G_G_Wqp 7=AƣˏG!        '`.~YC x8D1*L5J6nܨIzN`[n`-[ʬYd޼y~bR ]0΁Am0r)0ѴGٰuIɴ !cɓ5D1#G\augK>̞]Qx}/ ޜ?}&w+0F(<")XF<cO = ]>*k<GL$@$@$@$@$@$@$@$@$@ܣkх%4Yk.M2Ǽ{gٕ)vaƏ+k|[nH<@;`{^Ѵ{tuˏG$        FF ]rLSEz;a~iؘպ!n:֊uEe0~)g}VΝV]Nq0*dCľ{~a>c̰`l_XduY~YӢ"a'WX!7oN) COˉ9SgϞTeĈuִ!Cd„ HVWeeeӰ`zǤQFiD'D/Pc-.,`T]-`:<g5]TlGWmأm#0L T#`股k`w_0UVC<&L&1]EfR{.-[0***<\YХmL̺Ó .?o7\6a>QX1hFTxA|A֘\X.KeHg7o1j8›/kƀTeÇ cBQyx[8.alJM6;O]58jS Kb,ų!1-[&&'6%>l~krX#iBd`swb9G أK ]#G DG         kΝR8rO0.r)BVUΝ7ɓѣ&kР3F֭[᭤Ĩիi8 &"ӑ 5g}_Ұt#&.)l޽{e̙ؕ]w„F.# o<0﷬ڇ09*@W_X#t#~Pz kLgя~$Uti6x6R"8w/WN%8,8m43W_}5p{2:tKIH$@$@$@$@$@$@$@$@'`eB\&ovsEӧOCe 7oկ~p*lL̺L]b8q\qZM֏5#ʅ,]]z,Wr=re=6lܴxs=->x[-ݞa#LKN|&VoPT7jݻwg}V6x2tS{dhڴgoIj6&        pb֭O]0hz fώT˟T"<L&sjGkʥ mƮO֯_o0~w–-[s q  erai%/Kƞ\ɕ.<0I?vX'WQQDLԙ҅ ].D}3fKCIBy=cC?OȠAk׮rƽ\~hSLW[g֬YұcǴAl{B[ ا 駟K.D"ap{M0tp{}4Zr$@$@$@$@$@$@$@$@$@ ۷!<^6 ;4tU)yRXXd71km<<ƍB R^+eΚOxt 6 ]Fgvɸ q-<6]yj!4H$@$@$@$@$@$@$@$@!s1]CãD6 a&5kf3 p`Rԕ%ؘ ?Scף>* ŠLxjlm1r-CW*Al8n=5 Kճ6TmFwٳgkyyyHl|SA$@$@$@$@$@$@$@$@_O1goJxt]1P*+J=_mؓ(HG m-Z! [ƮlF.!OΝ6x`8qb1w\Eu뭷^Ϝ9s$҅3H)=̞=#4+"j)~ /i_|<3xOl {>"|[2Knuzꩧ.]ĨeK~&oȑX ~< @u5kT޽[z&/>lã  ꪡK(xb=b;xv?lTY#Hj m%eV ]V/=|za:t8^I=PyTcW6 1K*UuDj4\gc|.jr        D 6vv~Min5Cf2L5>%{߿?jOkZv>Oi"͛77{gpB'4h guV~+uhݺu+))19k Ụ&, CHHHHHHHHH <]={۶M7  RM$>E3jԨ Ie&"mH0IF*c&^ K/L<9>!CĉktF>=Ʈl$Waf{`/QTo" Sd]-Ka\sM<&OrL4c²>`֍\KA]2r嚇~UPP oV u}ꩧR+ؚL<;fS{,N5\p`Y PCx$        O8 s̑z"nLeٲeر#^o&jJ.RlL0R'۵k']tI%(o"@ Zb^,˅0Px%KHyy\ve5\c*XF~{f† e[O5{ncPtpwȝwNJzmk֬b`\F-Ǐ7ԕfiuhB]zʺxyN$@$@$@$@$@$@$@$@$@1 Tb?,祠 _EkN˰+7I<#<mB8,_2U&Mk[n%Fn<|.1TyxqJ0F$@$@$@$@$@$@$@$@$@Y ѣ2~xf`Ȉ`&ٿҷOuCy?r?tCR#WFN}QY8L%c$@$@$@$@$@$@$@$@$@A qb<${ٲeB+WEv\_GNO&s?ٵ`o4K[fNmkG^*䑞W1         bYTCd̙2ҦMkt!=N̕!T rwƉj/{ /o<1`HHHHHHHHH (#G*NytUTTȾ}̼D<(\#6o}Ĭk%?KcH+ @:؛k_ڵ|Xk:t,U['f(y7A^Gz^ HHHHHHHHH- Kq8p@Zn]t! \h/eϞ=ryIL2 וl2ۧTuu2| /o<1`HHHHHHHHH??iF%uJd`£GGr)..vIǎM:BXtku]#yxyx xc%c$@$@$@$@$@$@$@$@$@`'vJV@4h ?fB#8:tٳ MtMTrwr(FeQNL_ՋZӲG󑇲C[pD>Z/t >( ʩk8t-_?~zk>?~P5syOY8rɞ?F1?bqڗrl>_ԃ>*G^ӦMͽϷD3mn8O}#_EM<9͛gR@1b9׿>|Q曲xb~һwo3^޽[f̘aҿo7l&>wE\R,Yb9`֭`Ggs|g6|C/>2pjΖ>kޡ/Ϳz/c\w<}"~C~kPGCiw~-#ک(y%q *ct q}Z'5zG(a qC:pDкpwoʢNȹ,e!eQN NOA@֡qmti_eƵ,(nMí_LkQ?W}@???W.xp#sK!xEC>Se eUuBCZ-L\epgk`S#My-*5zo7}/ܿo?l_<#Ү];W˗Mq]pkLhl2Y`.d̘1&K.2jԨZ9Pb .̙3TW07:??|;O팿V;=l:߿AyeMY-*#ґW[GzHCѭj2ze$I  4Gh<9ps|P'd!mv@ 9(C‚u&zPH:Q'[~ԉs~$9NWHsw>Ρ}ߪg38|鬍Cs htPg9?sxr.A_˖- փsxt!`R_yHr?>_M7jK ԩSeb2ei߾}h׆ ̽1{ls/pٹ_{5;w'7p\uU'|b[ȸ eܸqF-|_?|M"]aiӦ~zGpN;_51wj~W''~}; ޣ 4w>;g~ZVG@rzT/Z/ε}8D!)֥uhȢ" ԃ2Fv۶m"Tb Bz!(-OW҃?? uy---ѥ?8##Lt}U.?oFc‹/h)x%:vX< ;v=zȑ#2v,[e.`6lo.Ikf3 uXڡC4iRcwC:A29?>^l<?8ߟ#ނj^%8~ߩ$>P_NE߯ YmG?~Ƞȇ,^ۧu!s6:NGC?5ep!֏t@P;C-KIENDB`astropy-pyvo-b70558c/docs/mivot/_images/xtapdbXML.png000066400000000000000000023504361510533647000226310ustar00rootroot00000000000000PNG  IHDR: t0VsRGBeXIfMM*>F(iN:ASCIIScreenshot̢; pHYs%%IR$iTXtXML:com.adobe.xmp 1186 1594 Screenshot yiDOTQ(QQPJq%@IDATx]E-rN'AId QDD" JR'gDP J AH ᎜7};{9owz:ϛ~]UIΝ"A@A@!jJW%$y+(A@A@A@H$#!  AC'!Bt$›*$  I\, $ %j˷ˈ?:~N2DS[n ʚ(wINA^D0ޣ3܋T,   !:}H$LI{b֝'ʘ%\\     T+@w!ʕhaE0HA@A@A@A@A@+_r     @"D@DxSĊ@^ui=zVy),     !:= D     a|tA^C@jf[^G+    #~.,B]YRI,-ѕP)<:]!JR\ ھh*5V?%,-O$     /qJtܹ{^6p4%yd@,ӣspŋs_3ӨVɿ7X})'\!!/[6S4=Gor}ԥKVT)R#9sRUX<Ŧ[G~#$[Q_LBZNs˥:P$z9j&C&2H+͞yZ>G՛7ԩAx}/SQ4n2W#\y\GϝF$4թ=1cNѠMkDEX( Ctl\AԮ.+mѻ}> ϕϳ캥D]g_3xș     .Ftdϔ^z V`p+W诣GiǑ#}Z5j\&)HBru2IC ʕsGDG"E, {IM3gy|UzP!Eh:kh@`9!!2D~ݴɊG~ԉҦd@,&QT)KStmNr³dcB۶m)*M_ފ7AGV~\Std*/e 􈋏 mJtY&ݸN%+ѫaQi- #JCi?rBjƏֵ*sv'+ sZ]ٜ)Z}?#DFt [3eRZ$:\w]̿غctEBxv*TeëY{Nū,/\RJQβ9D(橍rm: YsBҵ (^,;6?e)`JyE: HB%)j'(yTil.sBiBP)|{ ۗhX_ oDÁU=zUnڏpL±ho|&ѯߩưI+%:YC [\%k#rI    @"D-^gcc1&` oxrÌdoSyxс_2h|'PT<43@r0yzqRC%~j%GEtt4Tyf":1$:Sݮvp2>dnJ=gtuҍ@@h.:"np0e+Mg/O!oȃC(YdQY)C 8B^%*J1+䖓4XrqQ@@넴} QZjgnɒ% &xJmy]l2Exܾ*\e +"cT|w>;q* U_'ڧ"猙:v*/tuP-X@#7nHf4a R}x wD!/813s-iVi˔)~^IGyOW>~yqm` ) MFM^OZpmFX;`--H+ZP@&J9tǿ7|>'pͽed)p*~HƙjgsScp̝Lq3x[)"Q*Dx8 lL z5y?G?_dUShwAVQG(SX%,-'     ]gZ{\V*ֆ#zLИDG)H\qd$gHesN6vCbx8w3n>u옻ө[cҞ'48Ft9"-N$:5dIEpȾ/¥a /SǸ :J/NY5׾D(Y]H5_Leuk@یѱaKt:)9㭃'OUX}">|8biJ[ukeQGu ײ5fTO^jHÚ1_Y1CӒLf-#FX=z~ &$    ܓLtfQn/LΜF);qŊDS4UoIwfllSCy;aթNj3f =y:w:Fיz;wZ[ǾMP& :Ї?v̐=QyT遒kFD0v ֓tx2 O-W>ft ; j>M墤ɕS|+ѥʏ[DәLhicoGoh|!.#* Wi Xkk|hB7ШQA@A@A@A@&:v9CFp' ^ ގ&ёɊg}cǑ#t5: 0YUKx:F&:rdL?g~H\I2%=% f +Q?@Z9ԩC&kGe(˚-/9#Et7ɰat vr_D|vY}MF{;7ٻ4Kѵl-:s1Pa#rfMQi":/dUgg#m ;-N)ҥny߇ӴݴmtEdș:v}t#]QQMul*7D [&4R0)GVɍ xl/V1}}oIC>%F+Ӓ?93dg1 %    $&:ali4 é 2ʕF`mL X~k8w (-9rAoHܰF,PjNuyߏ㭨MKЋ?h&×Lh7q':7/ߤ@/X.Yhjg2 MԿgZwԻލ`kiQξPڿh>DbDGE'XMS*m*yΜ$Jo|LA>s?9vQجKm\S(iԦM4I+%$}TKNŃ-ە     $\Mtv L߲&{`oxTkںu4z$;_8p Cpeҥ4e ayF z|@"@:Ƭ rho֕[4rpxVc5뎋CwoߥJG9_DG mO2 KS_b!ڒ༃f̀Vʞ+z0{g٪KtU޼DDCʤ'V0Y'CqMtѳTޗZYn=+Y*"=3=سFӧ[5˞SF nW     pQC_&L,Z?fX_A^fաM;i#mTus]}4tF4sA"ժQV6rND5Zs;3?MDEtl KuXaW\])P7Kc=FiBX݀ 5_9!U驏9;N dSRr,^ljYJ:9:U_9e䖓 m e}H M8i%[lڢ(ct5t&_2<vʼ]V`2[O䚓-2O.2`+ q*#Z1[5Dݽ";_Q%fC~chUFh|@#chXQ)_( \tQ     @#+DӬEeKe'\i}6MFiQɴ?2Y٢Cta#}=9{k؅'Z!:F[>;$tt=8rH)CD$SL6Z28n5njr[ފ<}rGG4)QXv:i+=2#6Qٜ6sh8e)I$ Ss8R0Ex#:~n3mO4)@kYq5}݇;S,Lg)X~ Tqg#$Neqyu4ժDmYR>LSA|8ureH!iONyp;u>9     @&DwG?'KA͛p(#/Vum]Pjvj''/\p,sǎ)YmVf7lHUh483s&ylH UxG)&Q3eKKLakzh4"(@y*C# 'ygNyw|rA@A@A@A@{8%:uno'OB9sZ&k20'M0-~I1@Ўcꀹ.d%rʙ+se,W|)K,4yRY#l@aI ,alĀ\     #a>A !,alD\     h#Z0A !,alć\     #S"A ",alD \     #LA # `Gr     @# DD@H     _G@A@A@A@Ȇ͗KA@H4ёhn\    "&A@ HA@A@A@A DG>-G% )i\X^FyfdIn\lѕP)< ڳ(Gn Z*&I׶5t%%I<%K#>6Nv4};CԨMlJb<_.rR{ O4gi1>Ÿ\$}ֳnw}dW~ *G׉>Yn'ڻ=bz͉*TD:o Zk3HKX[.fz- @/kO?MUKp#pYq˖ʹuX6G.T)R#9sRUX^4$uj9z4/‹OOy{}T@eTFnx:u5#sfժ;mȼyvnn. }۶-eHƝ&Sو.%Jh Ͽ6U +# |굍.>Nu:B\B3`ۨ4[G4žJDgOYSeO y(cfd=qħD@4vx;HqSu> z{|b/c8,&8Su̍fRD7O D6n6<)H$nXN4JsDvv;&ڸhnSDFe{ΫC]w-sVGfD!-:5/>bK9mD1CE-ZK}rj'ξujBtⰊ9s=vF>ԫz5{oz8ɉBLDW%hZ5D:"(\t+A@A "qFtlڿ:>G`ʕGGSyϯ޼IݦNy%ɋTݩ/_t; "j.MmW)S:lhhxgD֌iFh6wg~ݗ!;M `͞ca,׉AԮzOBF`=tY}Nj*qk8ba*20i< nعD][w#Q_ XЊ㋹,V(,|y/xxF4-tZ̝к|жѪDY'zA0N*oY˂@rUj;Cm&k[vLٹq(cxhO9BsU  Z/*BBy}~G-8;&%z Դck=0>O!h}{OLT5|{I5/1Ʃ袲rP<-cD:wZ!:˨A@bx#:?=YXBk#GQ$:F-ZD׭a3P7iɎ;ur&-=F'.\c_GНwrHɓ%[GMt&Laj ݅׈;#]gŘɕpᝑ]t=m4u”P][^*`DN@E/H>wSvbo‰{KYX}g?zwV:yߌKvWc⹾*kC!JTgfWۘ`1!аxE^BOaMg Tr\5umicy.X%#.A@A сP=$:n3!ҠAtJ:5}-ʙ%;?b-OsݺTZ4 p{59cŘy4T?{,{+ Die# IVV qho&/x-[p..Fu%]["H$ʒ5.1\ a 2koH(~3]ݨs-|_ڡ \sbLLVyȌfHZD#y.\=?b#W>9EY߈\@6j0h 8$` v@l bd.&:X&qO~ 㼇f 6OH0OMM+(SJLzD3;X䵳?6'`$G*s/sBt܋wZ,  JtflvtџQoa]إKiʚ5i g;VF%ΐL 'JcdȈ?t4`.XZ%ct5Q",,9@VS"٨+E܋ܾ~.?HGct5 y(NOY ؤ.sTCo;7qSV:VS㛎[vn^iW9}uŢݐJ@4: gl[i-ihPJ jB$/YL>`-GӪz1W`U.yl*Ѵ%M#'(yTidǔt#=PU.]_ߪ7ELN*ٲw/C?uk [떨ݻpN"X0OݽZxOro/s,AA vk*F%GkSw̑'Cym\ڭNة`?rW"Zvv{B]_g{"~¢EKouGTQئ+_xɋՍk zw8{,~G&xq泰h.uvdE%1?w!qnϙ e0BW[D{d8xKF4oJy4 /:b;j~wԂT=(UQ=sCT ,s7Y2S]ҪW '.`p16i<; /VUcћDMK7)^|dDx0c&A Cb 3gyl]TcmCrX4]ͻ[$&Np7\RU}kM ϡ]';ɓU6whO@ dd<8B`lT~Ϩ[g𝄹 'f_1"}07@KK0/U&}8:[* w-0s mTϟQs*sLAWQ"5 -oDo~-W;Xι z>F8_/YH|_?D0 ^u=D&k#rQGA@A pbosqLp,>pj?Nmukբa[M lRCN#oIt[&^mŃa ЋH槖|сLڢEX^":FAmJS0;o әf(C~lH]fAQ؉0Qʘ;#-p*rQjS˲ܳo?b2Jqcx&q fjq̬S{߄no(B,0<3ϵ;Oι۫)VaSJlcEm8BܛU7iSC*Y5rw>0O\ZA2 `B_C>ٓTS[+H|t? IA 0d#|b`9M[AnE.lsuX>EdY}I|l0y+|tai3cTy{Y6g#w{yMWo5 WÙ}{}pE9`ۢ2y~70$wH%rQKd>$kĴ`.YqY79IԌHc[kus/;bC͌yxeɒԊ-> K%,-/ #cD~y |)&L¾-^d ѷIv#40~ J&E.ItWuUoIs2O@stL\CtLZ/_n~}z-Ȕ6- |alҔ9]:wj|DDgwgV!(I$:]:r<8LF=e-2@ @On$IWx{e^e-֤A#:}Qt|j iBM9\ԙB{4y#4yRv.OQ~k&:uPӦMюXA>vT]M? W\w)Hd;J*DƢ4CwH;Mj ڇY`xӻU2/‹e3 ?,k (>*.&s[cxh# '/ӡQZ/{?DG|=sq R + @nc  i)wMq]ZxW(\x}wD\ae3v sw5Hz<`-Ƃv~8vb'/!;5S_7S _v*g  vZp/|>99Wӊ*kp@CڼYM,Qё/W@ڂX$IR3(4ɴ1sL akXL\ A!& *s#^aSS|ߝ2?;ASE^~@0l_H%]J% 4( &HD91W[6)&툺sK 1ͿyRD*CdHf=s _{ZFU_v a^Y{x?3?? Ѡ%l@ [|fx9~ȊȂ[wq̣yS7o@;33=˜[ACm/ Dqg-uN~x@ŜrEz쫯(Mw:& ` `˹;&A@A@9\ѐoz\Izty`@W%K\ΜZظoRgݮ[ǍsX i*,YǓ'㏜=7]< s6uy;ĕ+44ȝP:pWa}Vŝӎ~ģ /^wuex\t58`'y+8̊gGV3WmL{yǦ\=-.w^wmfܺN{kZiVz]b;vS@?]3pu/UgJr2>i;_f03fvAi& LwC\6 ʸ Dn=0~J+t;st14Pk4.bnqɟ.Ϧ$w8>~k%R\sw /K7zTiF>Q;?D2o`3m!O:L%?G7c.@]rKʏy^gj&\.Wg:3>-=݌l.-luh+b(o_@;?~rE͈=Y:s.3+\ʤuh[O}ɕ7]ozvDk]-s8/ғ2 T-h;_A@F_߰',0ukov]8oí#V/XaplڿCL"k|F,ﲨկUxkX!:v,O)}fΤe;x0ˈ-x޼FZ6$U&szV_Z#иbEzZ5wj|jtϑ>ǞXavE<`"Rm64dz|dg0]vMt |2hR_~$)y Nt(޵-zrz3!?@eJ63]O瑅{6V03S^oS9َN͐+l,J9%W'1w MT|e96qLXqL`T=؞/o*,9}ǎU\b>i6C?}#*&o 9v}|0 vTGs6=14]6wǟ4SU8ڬn`vcsi f/&((+N=c; ky/P*xmˁ-pP9$^u zS7)ZkQٶ/E5 ؀|t$}j]ʼUƊ뛱/ Idt68 }*ޤ85*Pv"0P9!|ʊlS"qO-ڦ7>aʹ|#ʡS`jV2g0"LyĕVT}$ёXvzg;_?DG`8sNؔ9 )s,X8Ă/J?C infYǻYfjQb 2$miTZ嚷7<¿aX,t>ׁ! Lp|}ZLNd9G?d _9 $u1iP9շ&0wk'z![+1ϠT?p7bU9} | ];qyOEl>EAlǟmBM:d5@*TT2)TBddDE~Z 6-.߇\ݓ@ 2#|!EHfWC?'^C%Z6+@@5 0߉O>6cT^N\J6=-~G!:4"rA@Auǻ~?z4*PM杘eӤIΝ^)Syr?!e[zy i IϤʄk#$?{ my)*-t [ukx!e>kכq_=U!문Qc3ߺJ(Q7y*SP]oC65tmc`'4𲝮Ǟoy4]SßLWUq jQY&UT韶rٮq=^Teͱ+/a쾠_`L?NG=0A%[7_R¬iB(p TԘ/"ϭM~ALSKVmK V1տYT\6N;fPOd.C*.:mb;KHõm>n灩 -0>u]#L>ńlnk+U=n5Ȏzmϵ% k =Y&?}a.3:|j+ĕNSTVUu)@N!MR.אOTp eg&9J^yȯ0uJi8R|u߫f`ujnw(lum0af{ߓ!ͱ*:69L{q0S82MAœVTr~D;|ω>ž.}`*ϛ&ݴhSG iVno۵|t8⻍{:ͫ:s劫   @ F٫WYkl+t}cQsfNq&Rglʍ^5: -bSYmP!"q'ҶC٬01]5yjt[j2-Z#GS{ħrQ͑5%"]ez/UTTQQˑs̩iJSkXWJӱh?nō,{R1utsL1V8i]|PB0+G"sgc) !!!% oaѻq6YMPv;iuLvRaGSR;khr`7a*v?WǔfQmț:%cRCky4:kaŮ>sŅY$"Y)S@s3wynis--ިSX/cPZ!pco.˘hG`4,ޯL^~(&|~ c9n` ;x>Z.Z8J e~1GN 62rpe 7r8*yin06¡;k# Ʋ< T߈y;?BS4!6`0^ٝYԙ/3LNN1Z8^I<$#S涠IO4wDծU8`E =,\qqOI6bx1 w>-0) M@Z7s]>zGп_p:GOVesK}Й; }y>\c;.߇1?yW` :0W:2׌bs8~WUV7pS D4š} cqO9Ў/p0A SN*US|]6y^[ }EkZ^"9   %/^t}b+;!owo:<}Wꡜ/;2ypO M?pׇ1[qQxa1a.ou[vÍ[c1Ucpc_Vyq2;(OK$_i̇zדٲ3/,cvIaNcob[ Pw6A.c6nu::.qFEAu{3yS"^Kcnî`=V|Cb[s7NDZQU'4"8Th4i 8[D"4plʩYxv|49x6Ε'_ Io+^$ƞi~X_sO0qa.:x;_O&-/LTE̵]W}"]n\_Գ4˥? ,i4 U9k. .FdP&XE&y-KQX7E5§zxEa 46[;ןͲ=_-CiPҢL䋾fos$ΧzvA}X.^4R.1ZץǠ`Vc`bluYN!{ 0O8a#ϐ7=I#6-@?GWmcٛ<'LecO?9R*Nۮ2aIٹ\._`:nl;Zc1o[A$l{ U7~^b m}zAoC$yu?GoGDexN{ dl@6$hA lu9ܓoTA0/:{7R4!\}_u5x:W ^0'.8ledXG񞜆Ol@ۑ  5Zsr<4UCzP/&:޸!`Eiʘ%K4FKdD &9}l$vֵ[.1iʂ靧 Dblц/sW9.;<|СC~.]CO^b&쓣|޻Hb.`qYi!No]/l'9q7S]"NtdaP{0_cOq§k ua#ݗe sx̅;V6}+4.c3Ϗk\.#|{D%XD^,CVvnۚ8HF~4seR>!@AϷH {ڳ*ł?}׋N i^ 4%Ю lFcla')1E&ciψs1` l| vq  Ju1EkD7-7@kxy ׂu7P_*NcN;vPˎ;Oř ɸ~v9_#ry t>@=}`!bU_O [HqbΗ@$w]uDJ%",Kv:|z:۵sE[Ԫ2}n18qew<  ,EkA_ dun/#a#0e$uGES4u?ӫ:ƿcf7\.w ou3: ]?nκ%,-l_A@A kT  Ms`- :1a"v}Ǒ#.Yʨ$It 5a W3 yr0QD 1wfyv6錜},:8E8a<`U:mAKZ1Et\3[=cR#3-J ԶzuoIō(4;G:#6ijn{L5-Hxj?{␘pFnU_[BsߙKwo)Gp2Ly2YQ([/l :8R0Ѵ? ;n/y1NYd̢*™%NO95Чڊ3$[7c;TZ7vt*s̡6[H, e?1BRPݼv ;F>@m##u&6n\D>]9m?{=[zY9 y<͈Ng1iR~y,HiǙvLjn2cU9NAwo$kG&oa8S>v[H)GЙBؑn,)V~ 8FkY;ٹ<9`k+AJ`8^zX9͆MA9*n4CF$w+DM]_^g8/|VTUr(8t Q֩k,v* gǸΪyv-}IÒDSkJ3SG}Nƺ):};<rU>94P{@kt[οe{kݟYku6`*?U쨙'&VW<ֿCTrҭb=O]4z9!.e!p6)7/>b:Kg_5jc`.Cp=)v(Cf/'\ǎ3yw4iPG]eA?;UYy>o&<bϴwK4TGeb "Z}9̐vySPة"U_#-6E뇮H[WoYE /LgRY =D iH'Z|ψVL]WX3lB'/`\# عM S4j6! l >(\o[A33vJAZ` 5dFS$I51?o$cko->;$K/b ~* ٔ贩1.a) Nlw+XtԂhI=[WzWd`aE$[JTqu9jQXqK9SEj¾ _~3VdU K}Q>n=U!j>b/@Dݿ(/ Қq-;}ʠ?7$Z3og`?9 Wxt6/Tq -յ|XۢM4Tu3 bN߉<4P Nlr㷟ǟ0C P0bLw*Z{fcMfm圊pE{s=1j/ sT:W7ܯ2ml>~}`f]8/po7cp]Ma@ 1b7jaAXAGFoߠ6JraPe~;7[]K l&?xj#_!~[g ~"X?"!\1}$,=g\瓣  =qFt({$2Q(gNH`>t3xhq1SJNVo`b /Жx5ɲUo9"x^2s}.\_=ϒ BeLq7"Iw`އSճi]!JC X"rwҲ  xg{KA@1ؑO(Wdw7n Zɛ< e% p/ % >H/A%,-$  "%A@"MΔ~U^$Hԅ;a_{25gS1"@bF0ޣ3${&R %kA@ BtW\ @E6S:9AZA@A@w%6U]|"B)CB %IKȬ$CP$ d*< #̔Ȭ2fȐyxo߳y{ywg{=Zc+k+3舃11110 ñΣօKƀƀƀƀƀƀƀƀ@dXxqv&444444"u6IёƀƀƀƀƀaLj~1{7GMa@3:tG 07GMI170 ږ®nƀ@Bb I3:Й ̙)E G]vDLEA$؀Dc«'UF&+^A9mAVdɨ|"u _6FT3(Lj+UGtC 'mH%;Q[t}n I4}zQqUe3JDn'D_u rᜨ_[pO2\&:{;3;)FRx2Q'Dmu˗^!J`<@{ ^l|݈o۴j|(Q.R ^^%1141p8@_Kuy*c߱c??(VǕkר9ێt4vRJEyfeR9|~/Ej8h!x)]:̑2e"<'Lg#S;w75VnJ d~ۤ 5S?'/ )6!I; ,aA woNtM *8l9 mN4w0&a@N{Ѕ-翪"ޔD co=F#@pG,BNj!l}3=&͈^x%r~[„I7V}#TLx'jvOss "D_rmDv''0;@ϕ}e"U axTV5|;qr峦;zhHͫnV~f#wswv]sht&v1`{ ['r~xW"-`|7Nez`q4 ^@;+ '&jӇ(#[ؿD$+-"Fx-xwqJ'DSGMN_~v?2B=C5=,l0:be^m"ƿxs o)>^jׄ;^h!}9'.F_þFh|a5ݍ=u44cΝX)+o=aZZɒ>|?2}:~<'9ãCZCg=b /0"*-JMʕ /EΜ}͜!Mnx0)S0HMCh]!L,Oʹ5,9 FQF&flǙϰo>Z? @YqW0T˪Dicupύ&W)#&\Xq|'#N^Hn;fܵXIH D2zG2Nd&Z1EnutN?m)EGm@4¯LTo`DBvfy ? ؓ&!fIiq QN;7b|ojP [~fgګy)qff'c,%uO(n'T?QAT|uLq=s~=ۖ-j:w*QY/A됔󋏾&+J)?Œ"|O+k-u54ё `ɷ+$0[쌎Ѥ_yuU}22a5gH̎Z*8*<(SX+jDg=\%6yʒ۟]@}#PHGCƼKq.|dIkHqٚ%K.ktAHBgY&Fq:%y'jt}{E)X%fD nٺѬq"ǟ%ݔ(gq `,d3BHJ\uLs_ZDI+nMH߱:b*B%+y;G&ÿ& FTۛQqarc̶nj&_X8~zA0i{ω[s=Z9re& Q&Q*[-=Qwf|Ce{{<,8LٶhL_&\2^ıF3X`TO7KpY T@v~_7l9ƻD=%D{ }Qnȿ܃_Ojw| $x/C~_PQ Q|/!8T] 0&x+k:h/x@1:Кf+;bQWygrϞt9J& }۔;HZ6[WJUFG|ӫl?xLF{$~hpJmO!$צ!65Uz%lp`*277I yņD`^iDD -jDiL6,zm $ sw_1HƉzA֣G)4LAmuGM9~;^PB3:N0 *2GC)tw0j$ݠ+v:/tg+-Ɔw1n⃮w @2L$/uCW7T_ ?H.G z1 f%> 3$h%{8cһ`VEY̆4/DoY@움<`@?" ꫾95|W9_AԏLj-ើpQK^F)3:Oؽ \o=xh,1.s~5h[1LĴ9x P}/P{y5s})n[*|N:-A]}+2?\O;Ƚ18nbb%k?t >HО 2, U111- DqW埶makgcQ*Q|eԢE}ja0 P.˗C_^ޝNT|_zC1:Ѵ.G |_bKJyǩhs .{L^2嫖s(=؏J B^/d ENPȸ/ S9|D`ry赟^Q|O0C淞O6+17/N底CVDtiM7AE;W\˦z5 vTuՓ.KŻy I?6?D"`"o\U@=`SM&Ŕ:S|ҎKi)mӳ*!gs0J|X2d@s[0k姚?0;*)} y0?TTػUX5^ zSZ(I3 cHwOO{!o0.TwAW :SP^A=U fƾM*GJ΂y *jê!q @ܱE0@X']bL_-JD2@Jw#,/ZN&Џ<2dN`dK];C s̟*HY1ϪD<ܤ |1cs"n8ZǧoOHXfm@>ݎA_7)w0tTL`=<7@tCX#|u 'Ujx0vc #SfsC={; j+vL17LAht[ b[ .7e1yK1]5 ^~#Ǡ '&'=*kHC" :1%m g'ls(opo=x88z>2@:e7Na [}t4!`u?` ;!bܪcAUzCo6{Fv8:zu7g8c_ Hb9J `Z…Mpw ʰ.P=ƀƀ@15FNy1X}af6Іm[ȷ7䍎.kӸ+}62/OdpJ2%sUFGil4jDeS?96slƧ c̲e4b"_OW̘ MK=i7ŋMU!+\nџSyg̝>%KΧX847%=XAʜ/3Ϛ4f`xEbT72qD/>LkQ֌sqU}oij^J2A!mJRٯn4zUUT:u8&ƎAP6R?0m6W ֒4`6_ fJk:@z & oI#t7cJ3bSZ8P/$` %!H~ +A=tC\iVO&0Z !O4ٟ n܂:{,YTC&rFG4Al뢩 \ƁsJ f~F.?2D@tz&IT^wHIm:G*YС ,+.n&q."HW6AZҥBJKZ!x*O} 5AJ.F K@eB}$pu6g^L{p*ϓo3E$pp/K;9w0>P}Hb!ni) 82UɅhH>-|8in&~ ;l hJG~n5b=`cq 7Us&vyv`)?i Uo.)mgV v3 2Dˍ8 'r3WqK3PPn({1UPcŢ;Oj7 RR"_ebZ  >0ܰ3t3BsrIZ&ҫ}k&z0Oaܻk@IDATm j@| 7<`kbܔ8@ > wc>UHi U7p/?^-k%,Y3g͖SP?n` RkްC`21b&m%a|)zt`|Y4q;1ãr< xeXxM#x1~fGڷ7Hۣz㯣G}%ޱ(ծrVcʪU>;ÆY5qE kر~}ǎUG5Nlcb>?^w\gԒ%~8^&<%g&;s_/gvߒ/j?l/,Uxꢱ2\<}蕣/,}sQeeL5Ǫ)wO}vg׮\w?cb͉t0/l~-Zkw\`C{6G_q5ޅsѧa~nTO]3ldkt2yLiƕ2012 [5D m_B2]\zqʸö}7vi&CÒK|ndßG\} f8,~ˆYwG_*:+":пe_8La a#tqBk?V-QGYQٖ[asyah07,j>a恼l O(\ >-=ya9 T5sI=L6t[OiMRsga`A&  s;~Č7rxf~y !3Extt[}}A2̔)"~o8zN(|Hu Y-M*0|@}|'?)#D]X{a1WfXjWaדowPnd3C~堼7gFp`Of>ơ:/T8 qS A_nPK>axTn ʔ[̽Oߋ0vo5 P_$9wK_rEMGIa0V5POutvp:41]4ݞKHqR_{VCEk>UHi U7p/?A.jeJB-O׶5DNY[~v0:WxP|kO^*aNGb/ f LPf_5X3imb3ǵHbY*yhrP~"V) GӒk:"Aqע.frNUn0+6=j1WD﹩e.󟜃i?|Ko9 $I̼"b*87*$Z''O_o>},:';yeXxM:ƀƀ@)=< R=7իdDkf`(ߩqЁnt42ٳc&qu6.ӦY򖌎v@ȲKc+/`|9fFuV >^`i6oiUdt_djW_|} 1OޝwnhxkG_ɵ'J(AbRhn҉@|$f^ %, ;MT%c RBÕ,[bD1@Otq.%n7í~ihIE< $GT|(⨳K[Hzb HSrc-v"j$N@/磧22$NJCjMB66@@HyI&ٌ1x3 $% gDhwHp#h?`~CÀ6-Ё8D<s0S%W/ED?2Uc[G zot3*8_W֭q`\HDsL~b 㦠 4dx~jݑ7OT.EDo7yS3k1=-+~=#~GOz{θ!*WaAg A^YO9G ^pyHK13Ν-4< ­111<[LOaTc5~CI|1:Z㏻qǢڎ珳 p蘵n?-z&$}rBJ!べn~d؟PE%A2:֏\/~y,1|%3!Q%&Ij)lWpNB$v?d)*Q TIU] mCx{~&㇗q 13:V/}ROv6 {x߶ɌK8 tRRAi?6z^D@mSGcѩ '  V~KUひ%'WtH+O*A2st8e H =7/}TgW *ү2@O pƵچ@uTZH7{/$͡. &0$Lmڅ){ HfY3vAY~13>Jd"o%I;1K){|@)5@؏[. }W &l_|:$ʹP| o2}ln]>0 A)JmP6K6]nd]*ܜ]77$n>4FLT`Ć|nJ*w 3 7[ 2fHw^#{*.]d co0 m0d r|k:<]#ឿ[Sn,VOcu} îX$_rV.L4`{jhixTOyBwR̓Ab|cf^~&ߦanėakW٨,/7.c$^Q*8kJgad%H1I=o"zLeau;3 C0 /b#07)D:\DtKj:OT/OAa K::1x"Qcq1ˉS7]ߺlԚڡm"&0~lELYDLh`m2C? wmnƏ)E_ U ^H {k@9 ?0P8чz:߯|tCO޸DN{n_y\̆TUc' {apOއ>(MJgM S=651^+/QRv5Qo^3þOǏƻ<ȼ0_a޲CB #Uhܾ.oy3kNzzjcФ#i h $$ :e|xW/ g8_}iHnt0F6-R s\f͍O ﲊ%rBFT-q慟.zZbcy^hƐC|华g@y2qjB43-TIXNp /%j q֯1SFT U5YQtWꪇdN(L4[ 6a+F:{lF nCŚps#4 NPiW**'$j}0ИQSNJ*QƑΐ 0FDܾ7&'=Wäǒw *jX07ԆɺScN>DVU5|I}ҿCz(A@TT~XH6RƲl ,8S+!]>P`og(?HήLQA[^gP-b&fA.U@7TcCmK:ȵ&q-DS^ ԅH`Tf?Pg~/y꩐FvrإA8~!k;ezJ '\* j7F 楝`p #@NA`XT0h.t @*cb٫]2'TL[`t9Q_/*;TF:/{N^hw¦db}39bpD^DNq_8러e,O/{pٍ{jzAB-2|@ K1 a}QdBcH aP(M P(tJrHƵX A0//ጓ̷ZZqSo*l6踑(!b00`Wf@Gm'aC`/ TiMcbX | 0jE_u; 9pe7T,h N?debIx󠴡cNn?)Sx`MsރX >}di)#)7yk0ap?󁽮^$'Ҿgw?iOO=}%A2{ݎE<9Ù.~žKKz:v;th6poa5c%ƀƀ@<`'LY'G4eWfHUM}C'OWxS *Mpݰxqd?D2O.($V$1sڀYvIP eq==$ *VT4]?2*ÃkR=-Fǩ}Ni;}+WmO ?[ht=(#DMn)!  IPsag*PkpX* @tLJu㼦uRպp'ʗ>NX9OA=,-WDWldp8a@zX}E}}z#0EƘ cLm]3uM68c1.f q+1k. qcwpEn *DIxt*~RR} T0MUc d03RHJMdswj)qݪѽiRYjF^GY'CBZo{]C0B+[Z561/Já}f\ Ap'5ZĜz<c ˼0c̨ h|`o[E57X_d\c`-P 臘AhT^_2:^  kɼ#`. ֓cE1ȸ2z- cWP?'x^ Sq/6$=j8Xt x CƤ#|H?+Ra`ބZ4'fo]Q3~[56TƲLVPyh4>gg n--$? 5]B]#vZzK {A9a,l(2|zK! Tөszʹw vC9 96.ī gSǽ@- n{pi߳ͻ|;uy! ȹ<حj6E^Np X7qC3?s-rB~9UBBdyJFGbx(ʰ.T}tƀƀ@0p.?8r96v}u]` #GܣgЇ}G))MFUBc1rDT!FD7ʰ%p$<5]0 \No%Aԣ }$ʖdL8v°vZ2Rx^iz:Oyg5y]yLe[r?{x\p׽Dķ)YAOH7|NyxA`p #78j5y]2W&Ny{oW>]CULv5 GcwH}6`"%њ{p?/nIԴi#{j=9ɴր1ˉ^qZgi1>{}V9GuXω= n]-k52boQ%L`~iyNebP~^ӓٲˈv ]{7=ש؃HF'ߪ_} AJe(Lovz'}~,sg|5QJQۙW͈qUj$*CS>t27L?3DQ^!>m}%`Jp&3SUm&jg&9&w  D;Ĝ/ lwVM}}*m_oJZE-[Bm02W^tfz|bbY*#y> 0䫞^KsMbulJ4aQ쉛mJDf[ qc񁇉h .aуfGS o\Ax`bHBj D/p?2+@3 7,dZuO04VF%R D遇@r5gp0 63q'EH~L{)h_>oO:'$<C@}TyH0`!Lo"\ p]1gJ7S V7P&:T+4.L`qkO!X: xl &&u諾`l/stb !ł)Dmꉽ s\eza>0 $_T5KxNq.pg%@ᖉ<@& v߆ K?P {%juvl})Eܘ(SM wCp<ރI.!\ ]$}O3<ϔ:0oM`Z7[Hf>ñG=))9DN`!șG0f| Z[s~ U FxeW&Vx}3,p]OĂvYMm?t0##O֬"ӡIG3Facwn6};D R Bjr2Ʀ7@z GP-gftf@Kl\! fؿ[0;N&ԞStxa__R( %K;t Id6qfï<.=\k nN f #?U7Xp$4 mNJ%H~6LdԐx0b~͙? ;CT`$V9DU}/g7H>F h򉴝`&h~Qf \?}Ӽi;#i xy{7";PH D䊕+P9vU!G8]䂁f[L/na. | `,~[ys&t[+(uFZn dl&RK2 Skƀƀ@h $YFG7*z}̪fXۭd]B̗:%D >7;M h AHuLĉHIlst;}oL ,zU722,qۦ110{h h h h R^HQ|3)X??׉km֠1pb qpۙkY{x54472,i h ܼЌk h h $Y Nu74&غё(?ƀƀƀƀƀƀƀƀƀƀƀƀƀƀƀ@Rft$/11pb@3:n/ۥ1111111111111111p]0 ڰ4J1110cb̅ t_̔"y$v]ĀΝ!JNF72~V|NsڂdɒQ)Ex۹vl&'QQ+@b`dmd'vsy1o'qmD> ߒ+ew3sx]=_ Q;K^D pDI}+C)wZ DA&vk ǀ׾羄+vn!>E޶eF;~! KY0p8@_uy*c߱cB'ŊWʵkm:|OI SYRe@*s"547s013St#eDEx NQ71Fr47zϚE+nѷMP[0پ]8]!^^_|L<"Q)(>ֿkN/g1-ʰmt<׍ѱfNj=cH\ԫu?jOk%K~neyѡV-z'Ξ{`DT.Z+GioL9˽zѱ3,:2g@[f_ާ|!eJI-72~F珟!h}~z;2: MO!x6mOnDV ORN%L/=ib^A x#?8Az|>})I/&-×.~!a1q;$=qsHB "s!3|nA9obVh6o9ٝf 4~l6c8&v5k`fL8 o}h _q<6U2&G5Kc42U->bt^_' ˷)z@KR&rk !`QeI4Dwdr =Ph|6uMWe%]4@|wKܸyXp2'ƍk^⍓!싰?f?m^"Qv9+kUc@c@c b ə|BB!8oMOA PWUg)#3'M_矆S[3-TFnqTxQ:x$?qG €|"%N HF͌@olO4#|npO+f6ҴLy`H3ӕ%ٟs[oYC& vOgQ|fkLz#c)A%ʷ0nY\ hߏKTČp!|P&K4s?0sC3z~0kޟ:U:Dy72.'1hd&V!_r~3.7%Dq 2ӡsD$pd3802#`LL?~f ϦE`h%JȑhN'38= 3yf1^We.T&1}uOǞ"~ct/5KE}5ww"T?<EMw1kzp˩y|O䅚S0iu=Mkߋv=j~ኛx(g>^\ :octY*V3v/5$vIqwn/IΝti7oMYӏsŚauժT QW_~ u6 ZI2:w Q ->78 ج}')0:+ l0U}x{DiS8=nodX6D-rڶ ij!$Sc 2 Dc\'CM \A@g!uCz5P?KTAVWxA/_Sao5M=%|** z "x1B=q][ĭ>Q~f3' MY0oV߯Z&r s]R- 5kbLŞ+Xsh@Ufd!̜(uWKHDj)g3Wz?Z\ܴ1C]m_:+n]Ks+3f^#y܀5K`j*4@@20.#jT^6x{ѮGROe/=Df4!/uϼ3>Ϲ^^'~uI64F[G|cBz,gNڰg~>}4n aTX ;񭌶5jX%o2J`c@`dj6<vf|6?|L.@Y eEҮhϒ=tb 둻(+GwQW/^݋vӾ3t`FݔL.$qD.(vAV{KE.J.]M}eݒ<怯s}sI//lp~*$ Vq9O`պD6t+EZH!6ttC758T \HO{ 8҇@7t/Z%:@y?^g˦PB zkTF1`$f1{؝ nY3 i$qs6dl8u'3=l@IDATѕWfg83Ljт`ߢv߾tUJ0a }`r١//vN'G*V?s>2bt Nѣi]ݏ> -$%FG³j9O@  #?S0֔63SXq)-b1-}zS%t֓bPKPhn T9@R`t)AWq8/8MQd+ٮ7^ow75zqw!/&ںA0@\u+Cސ2 Pui@%&M5Cat{J^FXg[^q yP@F qvK]ۅ1!{iRKZvu@S 2J2 ?n]b[1/DuLc`dPC _J0!D `E2תyu"6)Y00> 'v7c4k;#2BwusxCGݘzԗUM{ c!X  TeSXRr?\B8rq3 ޶n#?r ƃx -y3ήyJ y Qdh̵M?ez I&l+ϯ0gHp; Zk@ᎤvڌxV󆄪" e`o3r>a0{:QL0:\# F Q ݨa *102H@:z)uPM(03jҫnӧ#`Ҩ˖V>96٦OŕFǘeh">2|R:gƌ=mZQO.fqmЄP]%I9O sgOɒ })-)R|)}Ȧ1|O+}72qD/>Lk!֌UGgnIުy)y> nJ)Ie*sBoʲpJ*T@b1m#d|8cs {ٿؐu l9SFM&nB7)@\BHࠉnF19L wDy$Zw4nfL( 3cs H#m[U9'|c`>)?iӠ(}OyS5%/+&tq^*~uhډPI]{X)*Xߥn-ėm?:=1g{FPB8n@u06._5s nt@B[%!- "Aӷq#)(mx.;`AHK=X3MNGS f#{)X;x9> uo~ִM68pKk^=8-g/ᅇĹsoцH׺[hkb\pм Du`Ked,pD0Y20'Ꙕy7̡W[Ѐf"t|`p 444nn $gx1~fGڷ7Hۣz㯣G}ޱ(ծrVcʪU>;ÆY5qE ر~}ǎUG5Nlcb>?^w\gԒ%~8^&<%g&;s_/gvߒ/j?l/,Uxꢱ2\<}蕣/,}s!geL5Ǫ)wO}vg׮\w?cb͉t0/lְ~-Zkw\`C #,׆"lpo<7+u5}¿@rX<73mU]0PK4xn̸ᵋZOpvض0oZǚ*Mc7tX t['{GG"HZ;"N鬪ohͲ'}:󏣬i~ ߱6 N{;`.j>a恼l OEŌQm; ɸSĩ9\[&a\bO&eY|m082DPsoV-4ÿg ߽~Cf="Ldy-gmfyN(|H9È9e_jM*0@}1qm \nL3o2BȱJ18#c>cEuJ|U|'J=s=4ƒ^a1LadwY˕ {m?ckJd\mA_ߨAk#bc~^/~a8N)?khMo0~z529" ƣuE65gGdz-Sha`>Dx~a|0Ck؁nn( mQFϏ  Gu@̤̙oΜ91 v𾢲+w$wqvUom ԂJ~A],C}TV"xćAn^gReP-ZKB.4бӍ/-#.F >mٓ1V C_wwWHi9pmrnߞecIYSj]%R* х J@#ԑo898BFpZoUyEH?oSv̫t|NkGu@ =;0ܔsmq/?C.gUP/c,|׉8x^JFb^S6yiS-TۥjJ #׋+c POqʠ,BA۔ >J~m൧T;h Bhk/>>ZZ</EK s  4;gPu]fywZH^[y1gxzKmV5]w.'=5ϟ7eF2k=d`yvBk^ X-9600s_7.7trwj{['Nt921#G<[ k бdr}g [: ߶-? +jM |wCKr e//|v;wXV%X:|ib}j,y# -:.l|}2`-k;?+S/;ό3˥Nj1 m7GUiʼntcѪ3>^%÷=کrqyb1Ʋ%1E-]-J:10~eApv.RJofKTq c{.@p"/ުn&!Ir NVʹw9Sza!úIM^HTCoMCXU+5C$3ߢ5>w  GϨxT5oΪ_L`8Rjp[fYF9^$q[!й֫t94ݤk@PD:@׈/(xˬWҷ*m=Ehr'Mغ/$ɍ;ve>Ѕ.T5f1.F_t|+.Gc_,+d&!g)\d3icK1a 1c)EqIAxPu U_a$2h$V(dXZ²`?7Mع}Ľ. ֿ Y+΅|{>wGZ;I#dbp=y,s9Kf ߶MtY,`1}]_\:ׁUV B/uV^g-k8uBɵkA,~tW ^%($YcU.d@AvxT5̯X!`9%9nZoa)BSkޢwI7,HGOZb׃2{6}́AomK'}Vol'҅pXOkG:dRRBDkJ^ʑ $}G%.i +:Z;^*Ӗc2*=U:Vڽf7 #*2]Cļ(%_;P9sdG={vʖ)=;8sP{{ʩȷ'f QIft=kVQA YDh 'S9rzDW_r݄:I4i 䬟(CtUzjd0[Bb Fc_".0վq ?_{ߒHLx3?'z;X 2 BRX/%%嶿Ht?O_5~.;=]#P'.4rYG\yUmf^80s 3:AgqZT}nWz_ꅈVv)}FU f+FB?A7^{E0F0o"(C&|MTڗ/TYu ǙkbC`מT׶Pqq5w#8׻z;"j>Qj8gfB o7y~ƻ^AX=Кcً$w*@9y^4tJ9<ic!?AׇUW}E-oxO{'w`FtUoUkw%*R(%*;-kAG5lxڠO|餞=hˊ=S_ վ̑>>;u8՚eD\f kTz;+Gi79.话D}%c/g}<7Dw}g';y6;;bXPV/S^8j PwPun۷򻝟 4_%?-D7:-s9 kX pu%, ߊe/r]x:?~ϧks|_'N Z ӄzW_ӻwx><.<1pxhK_|a80 F`{Ə&u]Q yYt9}ɢc$乛E% Gy穋wFR Bg'oJxױZdQ?H6kdvXt:CʺnbK hB $wzK>UC_}WB19Ba@`ɕM";Gvkj å΂P,i1p-z0]9p\\ȸҊE~MS?-XE?I~I/'~Ezm;s"Z~_ڇXsW>I[MB;9Ŋ5R.nrx/ɋmÛյ#6c+!h>tIJBE$huըb頯:AhI\ U˂7hP߹68@$ q?A^C]\!?}z`5W*5\riS,$ -T?0OGn#z(X N\z7HX?K{FgVsp'|.$kg7n:}>'I..Ƅ ?wպ ލF{#QU6 Hbق77]s,;bx~a!?#=9o?'OZcW*u-8dKk *h9il    x!j՝2J ̔o_՜9֊;x"@s&x%KJضo_bPî8#vIȄ\grb:P:7Ek)nZ ?BM|A($JjkH{~ Aй@%qlyO.Mk0>@ǚik}{`n/u&!.ƘwAȣ xyY/ɑ N݄R8+"xĂXX;^ϩ :[C&`*|KruuWx |T}Oz==6Uʋ,U-FTz=;%k2JeM,aO' A=:p)p_is-u܌H\x"N ܤ"S~0gȋ-3/G]+{ >w.?\ L,O̵AJݕ'I[rx  c^$VwLC%1N'uscr)V2M)/ ?ߤ/ba]H{A9ȋ~йGdAiSP 2~Mnw:c~uŞLK 7 oYgViP"h9ik8`8`8`8WX_}e`d4qI/p(F-\tL_$T4c;njI,7x7ہO[b1)K!\{DR l-\Fh#9R[l6?X<(mŗ;e ?BǟX5vl>0GHpPBX mv~=O|~ؖ2C[>5m˜>,z_b}}S#7Jqbt+?bx$-RrƯ' R~+ִryVx&p 2O=9rnf2 ^[u'(Z.JW)Á$5r v:0?voMNeVlUܞ@G+h*k_XfHN<9Q=>\<<>woG# ˡ_oN*TzY?h7 /o=X}qV{BA/~Ϻ}f?*I}|$c1*FT.`6\hCw7d$ >߽G TX8ʹh{ kXa?0ul1kB HjpZ`itRжCO}h-34op?u͝OAU D!~9 D%\ vj<&B[sC %(Wsk^ lgTuOLz1C]^}A@aɃ -FPN:4%]xڔ:U,m6p+d+cE֠LxjݶH@9h-))uAxv\ZPZ7X3(|2G-<;r !}7"=uCH)|pA]0xE"y  ''kƜ"yJ­H /x~р`ҭ̂ (%h؏w;0NCbmA>Ĩ *??h;h(+Vzh;t$]x^dƾ^6DPX-w INB֨Dt+s܇x7c\JP^_cO6,sy֣s >lukS#|}5`,?郟{r>:l!ثdLM]'WGP"hc8ǙzQB& Fj`Ȼ!zzxb7U^E9hi:{/҂= qm8ڵr !s1e̒^n!Ƙ@v+qTֳL8mUy vZY93ߧFpMñx^T@q.FcGt=Xݧ|+9 npIz epo?L.OU_}?{ZxҨEs{{6+r`SmxrW0~eQc:`n˜Zqċ~P}=YDpp!<d%:ʷ cT_;~ !hqj<-j#d΁%Q׆3rMvujx6DH@>3GUHyt=8󃉚=~dp4K=@pJ<۟spVM~OT)ϼN?> :%G_iS*|Hm@,t-o〞Ltp]H6 ̛ Jg&3&V;KIAa"={0uIwTr9k_\N0w|{x xo|~![ۦs#BrޡsZ˜4}/@D (u6?bN ?c\yN?۟v,ʞ5y'S;Xǒl    q ՁΊMq~͚QB*E2*+ ٢*Q;βA^3g*$_BVNKL׿]ȑ93]#]uT@| r*ɶV#Iҽܙ___L/ϘA4+[+R#NVt/[ƐIj뙡'UPn{궰 >s:uG)Ein6Bg+ף,&q3n]N{[!Tcp [4.{maNEJ*QƬYۮaƂ<Ҿa, n}C} J'1Dg&M-(ɷj, ._f1'ps W!s{#zAV7(œpA;>fz ug aՇh u *\PD˿pU~,5%ݏ>@Pc'w ]U}@ qf7(g;*0opڏ>$_SM2N:MT*چIzwxWM™|_۲dS}(x͙\*m4 &,@ kk@qG=wp5Aw8^%j|zN^?9oo0{ΧRCб'僴ey>l4Ei+@bzdgQwP{ş8BKPXHϙWi#8A QX+-R|תׇvuvЮv=W\ MS un*(aj ѕX˸x AmC|Pn kJ>Osq>gMnS9 SXʙJ6S|@P"h7g-q6c0000HpX-us|i"k7{{m+M+RI$_hr/xsΥl e9͌}5We8`8 X-W`j40089`9LE_ywv. ?JV(Xby2d8oa3~mq_|1n,so   0@}700[5+Y)n8`8`8`8`8`8`8`8`8`8`8p^q (`yYÁt&&   @W]:aϐXݻ-j0 a@0@G3E   RRS9ȁ{իOWySppppppp 9`氩ppppppppppppp 9`4dipppppppgͦÁ~-3K49p^#ǏQS ./Gs80&J勩^#Bv/6(_>E&ʁȶC)ߧ˘.<=vD Qμn 2U#1I7{/Gy9(֫;ѸIً.!zjplA։/(`\,}2y       Rg m6`vbpMٶ/0gjbZՒ7SRsGrA\FT275xsUX'N3G?Bf²K3Sl9lƫ $)cgLyq۴'=ݽEԫq?G6 B!׼=]zQsf@!~D KykEljV宼^Mxv :K`N?Y/RRg7o nEkt{w:EtiV̗OεZ.YV͎-D}Z~} 2躒ɗ19R)1ރԻ"FB{s,{}Zt[ 9}:x2D"[s/= -!ZU~ZB;+n-qWŻ]sVDy Xtσ\8'7DҪ2Xs{XF|8D{beyչhko]BQrsЖD;*%/WX.^ _W¢y-?rM3_sjO !doeLkg RsD1K,{l˔Oj_0ĻX(:1zA'ppppppp %9pր՛7Rߙ3E4c &PrgI[Shퟛ%)l : dBCw/M*UL^>MF }GX0ɈvO%0w*-}<Ӕ:8Z^9kw;,ۊ+_j~hHg:OVO,I<篧be*{\:N3P+O]>J :PkC:mDKBֆ5C!EM"eBNoe]6 I_EUaΧ Rb)x  C0AdR~~]';xG" ;¿EW9^-B,?[N<˯wNg-z ' {+=Bg}^iۆ*DuV4b`Ro>%uGx g5EϦ=Ig,RD/7mIU7e;Wk6y-[1(޾oR<,: fmIZ,@tbM'5<71~>Ր@IDAT4owv V!1?3dQ;':Б'{T$:vKIgp>]:Poo&bC>1@Gs uаdmc'Hr#zo]mM*~jK5UcjvХ{ꃎۈ]2ܢ}BLzC뚶obHu"j3mB(ekUI;[Htkv!"/ԟcRGC4dU0@x !2xo.>[UojٖeGgƬ_Ҧ勈Jz nSTmD[T>9b!Dww5sןNb"A_@ PG/^,s`gĢ3Cv)[||Zkxp Io"Xj hTluhiዜ,:9(EujnL5xP"hӜ7000000HiЁ iywM@wg'G:HgˌC㒌+\YIVZv}ܮjRt\(u`bw7̢{iz$pM5M'INvkdYR#TC,y3?ڔ4!(lG ɜ,KA:@gkL_YVZ}@o-ֲ,97e} V5`qa?빼wJx?ߗzoLVJWJc5US'jU8xZ(p4vYRIPIIkd G{T]JRբq&?D/`wX?7]sV Y@v U\kyIWp)E_Q uݛ̿2(F-^zm%xYH$/@RF/鱬wk>o(Aw/hDžA"äR8_l& %EVlgJdIB| P{jytcbBg@:᳣݈ౣOM>ЁL5lwo s>K QeC 2*P"y&B?:3eJCjSy26P:^q>,N9ꅩ4htZBiRS;JD0Bۏd uA>g-"LyICңY(QॗG|V g+0`z4'r+3 ]F5ZW fgW팳;@:0x64o湏 {.K`<|:Dq^ƅ_z=&XTtdڼ>'ha'  M$7bDy FXBY:kɕ~s $DU3zlh*]%nFu sɎDo N?={aM8e"xBvpyF,y)ɷxY5ɠx81@8v _UAZTO,ש2~cetz"mǖ`!EiJ[~޳Y9~eDU N",5rn:ceƈ!>5`ǯ *i:- tuzEY=_!-Hw3Sm/͊>_^ه8pJ]#w>x~ԋwʓ5!i²Ɏ_Z{zw_U|øN6^z9' ]@T1~/sgي .n;>Ve *dʓ9 5HZ.R?L@jq ŀM/4GaA8x|.59mR& ӧׁϥ/:Էi*xe{_>u,%eL<*₩YHk Б9Irm^Tfr]B-rA3Qq^h!GJcTGq[ aV.> +W8 xh6u!0@ t?BQ<~#uݦ$Б:u*Eұi7[jQ = БcOpna uY+RrAf*LPuAC*V9yNRn$)0ˑb/'ΌDMnd cqշuֆ!t@brek +@%ڵ=U;d[ @qI+ OTSu5%p) y,Oy q*V6-\g.xB\W)K5hXe k#h>SV,{_<]M@ ȀAT#d nXs)l̪ьG;@G\lۭ]0WOҬ^'htKccW^x*=w?#/gy8:T`xg}:u"Juc=Zz~֍4DtŸ~*&D.PBlu  vb>@aT XĽ\ ]̗q͸/ xl$Z壔;pqkp^3QBXϹ>.נ|ezJ>~HLS*g踹ke*sX5SL>mE:u$o^2]sN>ISo`!us>3%EFG'gEV"cb[|z)]}O!<\ ͫ`Rq@M =-3:daW\_-}vc6*U^>]'Wx\#=()u_B h B[PH4fqܡE 3=BRkDz5MOrY&'4:Wϛ@reT+J'БmhײT>X{b+a\XA mP/ : nC,?{7O< YARNAI~^-dț Їƻh[-h7/5 #4@!rќJX0MA`7Gd素s ܙ` Z [ԅ};\(g`խJp4++Kh}]xt'J | غPU_ĄB4C\<פceLsUIbNA,k5WKqzyD[&bY@+ꪽ=-:C/BZ?b+Jx/q-[ kO*7f0AUO P.'Q'Xڼl?~树/hP ݝUoVÝqtMKrRml 3,1^3UWZ<>P,$`vKK'c9j=yrK9wV+vS%@zۯnY DxfQeۇ=nFYW]:[a^;d+sj *(+鮽l^; i88>gV8XE9ɷxKV$ĵ X-L@q бa_l[MO܅2Z"6QIa@nG d;c{Pk zOMnVΔpQ>D=uѳ%_*Tnd%a޻!;*iHI 3ղ5<|C +;]0BCqoPErpuU~M C/SwnsWˉH˱k>< `vv A҅D@zH 9JƽjF9Ԟ;},ps~ +DHn} % !0iCφw~ځA76E"X+ j ;A]FXTSKJ%Nj*+qץåo̗(߹A#AkZ \~ǻ^68E4A#sN DW^(ν3D^'(0g%|zvWo^p넕V+zχjec@ oዜaHeœڪV9QuQ&1O/ z=Dkg9Vaq̋]U9b5B m>+U[1K kx G|(2N%~4Zw2tք[9xa>/'+N[]BAūfKgzINǴ5@GLl2      :~cBBn\y#^_@:,L=}0F? 9'ķhӮv^Dى_هr 耥,F@p[UBtuU$9t%--:ধ&a}D-a{w.0; @kHX ,*YQ{0ԅqi /!(#t [,Z6U`ALGg]@X\RGvRl})~u@^Rv٨ Xu_bef .$O%Ň>ƺC84H\vvIծ #$zc|zEϚ۴yNuX۲U!~ ?T^5|՞? Gu{BJ1e褻ҟsa>?x_ ί,3P'l;Oj<$z?yeY+OL &qmkUut G500000q7С>3q|*U *D#/,c*ʟ;yxe$Z7;Vѕ%U3@qEL@b wP#@YҰ6ӓIK* NkyYO:fkCN4r)-sTϺ/3t :ܷuРծG l9ݩu᚟l/_r~ǻ8O=$riZk(XH }. c ).`r$v0 #ޓ Ƽ ˃H8zG,!\0\^ ~a0/Y`AX`Wϥ`> -p&r/cG}p%1B( [kXt* ]MܲI՟luvr!G=vJG{5 tT_urƷ'Fx`LzVD:sOd E%>G_}'+,;|JDð wm&rLGBan$9O]@*ˏuMl}P #fc8`8`8`8`8`8oG?M8?jLAѐ!\_u\WSl!Q+n"ù/׮}Y[6R]wik<҅2g14egvvU]7s>7*^3d'Q\%#w#c֋[DgSSK.@|M)@:h^бonz5 U ~.͛qbі,AٲeeQ"iVEhqB=bM*u.G>! > ڏp M#`:f͇ݰ#O@JR %n_DZi50B-\/@ ;=EԹ)q Brohs"•$" l.pѲ[tw %6= h~z"8%$[/qڞp`퀱 @AKt./(cจ ='`-vV"XG?ԤIl ?%-c}ݱD'^.?]7q O% [݊aW>eهVBhy☲ɍWq[&h:S 0]Uv!LP:P R' ඩVt+\lı@< P!ڳMR@eRϘԮ19H\{ k .Ұ`wx sܨM%)'g .7_m( Um\/cR<7ʢrş&+@"́Jɣ@@@B4eCvׅw۹01w=Xcfًñ*Fԫ3U߳obE]/g vh4?unϫ٦ 4rJ ͜>hCb6:x44 BUA=j^W2%8:tLS;+byZ^8zu cIOHD%9y큇k!^6~Qp5>gkmq( V@ X(B&@ ]NHAPYq*1+c9i&0E SE@V^$C\%vyp.o33g(coIjȷ fgWK_d Z;L%aiOH ._D˴HqX@`QeATa gX-uF:Vxi!{mDҜ*W;mf;bNw {g.ݥ@ ![{ts=M_i@DZΗ(+Z^`ԝx^6[?Q ~2~ WJz`HndA>xN~5\=5J <Ф/RHrAl}F#X MvUȿr?bth}j1Ț zrJ}u;_zխ?H $*fn 8a9OФ؂1oXDЂX Ծ+ĶYwKe=Ъ#urxTeKuuw%k" [?Ht uHBw8޼WZ,y蓇YR@hvty+ݴ{[" wn`z~1\Щ~qK~X4x}C4UDZqUüq΃PE4t ?ް=w@'<~ $ WF$W^ݡ ߇J)E^GйD@>{c&u r^떲==AhK}+aqNzw'8mxk:/aZb-p؏Vmbh_SD)Fa yPH )u)Ɏ]gEOTgC9<1vC{qڞT}]]o#h0DB֤IYluPB vUȿA 襃[RgIP<#;-]һU_?>cIk-|Us dɑy& 1!@`=SK8 tPqr@;\=OvlV+6X^]vNb9a^-3`-m[OB *e'OYkQLmO7[M]Vq~forc)jtCIyɥZ.ac8`8`8`8`8`8`8p87Сs[xL\jn?б8tv`z]t-;Ё㣯kMtϘL!(̢_B@:[Qz?CgY=*+5u >#MC<%theN:>ь6 Y d}]bį:~uAO4W zH:jϪ4㎷櫌't47iѱgvQgLp}XnJ'p YܚtƲv/;ω^׾n֋ŮF.Ah\C:!9 V6gPN\;! @IZ}ElkvNXi%͋ ևG{c@0 h{l~CB|q"i :_XAz7I ő4wty Xx۠Z +O<cTw#i߈|s^:Q\, x~Nƌ;N ;5,.ά8@yЊE $>b-#(۳J*I&P JlenF?ݮJdB{DoE{-!ic 8^u;3|ՂX- Ѩϼ'ml5ai^$ zOvlvb`݉H>V)@ZAҿzBtpeJ'9CvN5L7B4v 4hu H9oR"i-s'}w QBEm W VA˅n       R) tHwj4oP}/Ё0AE^EMB/LK֯DD:缒j1~y`z8A}Oz2fȀD#Ӆy媉IwBTXq"#oR;^H2*@9@4 E FB0 7sIz f,'O[O|݃:Jmb7ľtITVCffIrZA`wSs껕Y :y](aB]}N0*p--ۿ;ꄋ"݅o ṩHnjrPG~n+A3xhFVAR$(?$_TJh`1 @KtAм?aaM&\{FýP{tDß ь.ɢ㕯fyxGA\sNሩ8.Mbnzlѳu7+!u:~>AR&uJY=yi9X8KXnQJD?u"LXHlQj3 F.0D+T ,$cr * .)/nnDsQX s4,Sli1{I9t+VmPh ;~P~J1:gJOGӲI J)u8_{4WV~ƘױcrKyսZ@ J\ypE~m-P딲1Cp pp2ў7{³ `%gɫog! xiNth;Fnrc=>ɓݻt!7`>cgR ^&5}}|T2!9k }?Y;w?̠ɐy3iퟛ JCsvYRmζB6/yH>6f0U,4Ӷep}~m,[*P wǔ7Z&̸]9_O(4*{/+}RGo >’q k1@kXzTQq;ߤC/&&uH~iw.{rVSt}aea!^OeAf$/R,lu!,4~kwA aޏ}G^7 hǓІv=CP+VUJ8mcG {hc(HKV*%8ZHEGĒŷߓxza&Ip}o3? n6jW?mH#Im|;Tz$HZ`(҉%%&Sx5ݽޏyڠ{8Ґ` ᨎo$L.fX%/nwӫ"("( 1:2]hӖ-tt iLsWOac)GZ>h앴qzDFY@ٛNY46p &N[Ou$rƩ}l@'qJyW Y+^JYGuԮZ]\h'Z4h3qMC+K:d 7ٸ8aV'ؓVf#_ ӗ|"jD`t"0gB )%7#:rz]N0{Q}DɄ ˰a̓^%x^M'/ 7*~5 ~Bž"a6^EJs("(B(k o>dsc4[E o(LLʻ#mEc~Q)IӶŊv%vz3u3>(y)@XExzZE@PE@( (0|%c! b'ŪU>壌J" NpJ|_N'4GV覇Jݥ.v-Nv^}"k!a6ޮFPE@P ( _Io4AJ2#9}"P@vR@>fCPE@PeX ("(@!;daa% mziv9TSϸYj=rQjԸzuyt;zCSΥ;n{+FǗ+G-ԡ*GnEn[^{H؃J?̑ KSգNJNNe<^֨KGӤ=}ojE%ё> N}EdjƐW  S_GJCb΁jIt0^}ѯӉ.lH#܊fc8ԭZn—֐=6D^$='9ѾyWo6gz:W&:0 |!.LݻgL%:/?L m,UWfQ_I}D];x^3:ib`dcQt KL"z<-s}M`q!P껿d7E^Xa-#J˰~}Sxhuuk?0vmy!lNB3+O6iB5Yi] q0".? jU.[ܵbWuF6lx|4{~`|o%JD` y݉M~z볿?Bс͜jں#ѝYtsoZ3޸oT.L^KT@q kH,,q\U1sˎ7ћ'X@HM۔{+ Su+l{R-QHuSieVӯQJ m,m:HUl@4[?ͳO== 1ov{kkM8y;Kq禮1ܒZ\Gɻ부7ԅzpAmgZNmQ~&*]v\pSП C9|nY:D+d/Qy|^dNbcta}P:m:'T{M4u9@,|^5:%J|tp g2[qIO~ rxq"()/ $: q+f\-GG0wnHGˈ>'e a6Wl|ct 0b f={"r3:^;} uU7{.b_QS:{32-lF8.9TZv--^~Yű&cɋE8'{3^,aK(#R]y-͛\K_XFo3Y[Ք(   So5'Ԧ|OfџD+[$Onh?2/Ai XQӻSZ2QR*>0o l:(F L۶O23*}\'XSR6>}TD8r)ɓר4;\yDG͋bq D<ѭɓg9D*_CX|Sk> _ԐX<&5nMFqpObokOXDw ,]C80~P^Խ/?$:t"'K3h6g~ ~xA !$H)U?`Am-6AǞyfirN+8vպ!mG7PO)L^[>D58:7 @a~˒:a>k핷ޣ ˰ϫqn)+ܹ:t?eJs-C^GYeU;a?ldɚ5Q?<;rd4~;KDq3{ɒH=v~$^|oKػFe_q4Pv8HDަ9hv߻mpO+}]ݏ@"cڌJAb;o[,[._ی`it77=sHdJX0Ch3͐AA&^1w._'hK˻˒NUV/uVisX7F"5)Ō bݵ5ӳ#%8懻M<>l嗢nHdx<=46޽O+^HQھ-9b9Mq!iг033dD\xcd7y }P:m:H}TۓʎȌo#[L+yNwE"+?lJTcQ _n'_~hIp4_ z}[wUUӮd ,-/;F JazOJt,`}OR,p6KL*VϧS<^~h7ߐcKt|jȱQ2i8aӭoq2&c/Ö]2=uqXA7 P).Ҧ AHt{mۊsk~Ht6QSTj9s4|Z:CkNCO=3;y_̣, 7Ж[ԱL2tGAG+ެ>wYGsiK谪An~x3ygQsCK.q޻|rھyBm>DƵqR3&:k4hpk+6r 13O臆:7ms.%*S}߿&/z9ewǧmp2|mQ91f/]8 /Q|R>dbyԿ2'f=^p:C\D符y_sT*_Zs tAT*tx f'.[)Y ߩφۢΡ>؄ !jW(ߚ%,)s.;mUw.2$$Nߎf& Nǧg'A$`3 k>dN.[h<y!bsA1W b_c\'@FG<K+#͉F 4ƸyoH'LڲܑnW# 0uP7Q,bN1|KغGx20wu2jvfpZHc/8mWSxtS/{oA/rMw,r=HۜӀ HtxTW;ό^a5bP|LJDh|jtC 8W8Vo$q2q yʐɌ! +ÎR Ɩ Zr_¼]6*0^H;h{џBU"Nڄor;g@}dJ[3Ky}y&ZH`އIDm,AiDZEԓT4sQwr"3&'gރ  W. T=$e"$wvV,0=س};-bs`wQF'~:@R~$пb^l3S&3]H\\[5?^k-'02[Ax56ϝks#3 <_3!9Uܐ8%H%a*Q.D_HA-hÓM9R3H}"<^s|]sno d3}xq6 zuZE`M`A盤;7 O2DEsc 61&{{ ob<·=Wx~3d#2.3 ѦEkx XUj:cVgZ_3"{C;蟱 Fف()L_XW:GSEa6>:>;\`0'P?Ȅ|'o[-bgzmc( ]Fs;pg?c6m H\Ժ[{CT+ܶژǟj=xNzC`8?܄|÷MIC$Z%6# e`1tD? ex1v>`>a.ϓr6~z)0ғ_|LJAy.qkwa(~>(b3*[@&q<nɠ7?%:;[xC&(|HaNv z $XMl(QՆ )zT33Znw eE0?z=+Skn;]'kk҇Vecf|8jy?:zA17pX7IgnNtCm3q!3uŎͭurhç qؘt;q}Ը q$w_9Ajw o6}?Yvt52c Gwa4MCqQ g+QXEx1:`}i93lj϶-d xhI9A0ftJ+0@~z'`T$0GFWz|DƊY+'\y_y߃+}J89is8iϢ{: HjMu5w=K~펻߿ PӦJ 1Irǃ'1@#Qq-<~Γ`䕯h6:/%& g"z1) HL@4:;0 ۝$͉Cr qg${qxa(&p^+!nF<.a QM{z$fB_a.<p̉S!8)1&6MD}7&fHK m 8$:qBsg,B<郳=ʳ0FcN ކ5Y6_q/! -3ny|ta 5x$I^sQ~HJɁ@NC@js̳Zu= !R&MW)kaRTWHR`ݏ{855m̋AS"6#3ծw\0C~3z~}\쀄ǭUґ%yPXExYwJ*֝;#C~9r=XY^x!VE嶍I1yc޽#=ƌqY5Ud֭Q /\3[09MfCmz&sqqF+ǽ ̀ 1qQ+ߺnkglZ[y[#ݎ]w\_>e}d敛dƪ"ttaE949Ѱ0j+"C?YK8~/p k׮]4] tB_>Gt[{ɘ#q;ӿDtt&|{51/5%ϋB׵cxҰ!~ЗY 1/Ƨ ߷ lwpfY֣=wNhD{ȿcA"? )iEv! qmԓW]`Ǭ܉.`߳q{Xw,aoy|gC]WS1WNƿI5:ivny?c7+Hʞ&K]SPzAOU;{#>ůZH1m[cjs݋͑HG"u0c uώ~wݾ'4Vw7k/8<8Ic1tJ cؗLozAA?ƾʂz&'o=Xך4` s_]">tthi>$+i<6:%w ?|1QcaSa"i (;Zȳ0;4ov$H% Z1;8)>o4aG\6) %{̞K>C,A DNĽ=2BuQ®Q郲-t0"E@#>=D\M{yaV9nxx?{7zi3FlѲWh5׋><%; ~0E.eFF\9VY;~q1y"~ޝ|ao$w) ;hn ! f,qo˖-sH^~tdX a R{=m`/>t^x6܆ׯ1~' 6~iȎ9?0۴x^ 2&nZ2? $o]~N::GľWSZrWVzg8>5҃-0my=3&a/0@0f:Ӯts7h=[`o{·0ALB\ͧF "K /c35K}m&"uϫǰĘ0>114F}"ħd+ޠԌk& nkauj{!?`4H[؝{LV\_ˤy350ܢ^4,L /d2&51 2ـa#\cﲱAEL!!)Y}7ɘaƅy!GkWI_;xOc06%~ y3]69Ykg;`ڠLյ]<ç"̛{P|dޏwɆ{G"Ǯ䗲H_JFI&8zH}3u>`}01ν԰\)~w=~$+:^[]ݾ?3ރM;1cI"3I܂8R c=nRjy`(3ԁs$rM297u%Y})[u&{=Of ܗƸ$O#.PA~0 .0gcZքe]czar,hF^ywߍ{ɸyhիD2,KU(ABAa|ȫY{ft X zO=ٺ}{BFG7ވ]٭ԁ,°hٳ9q3uf/Y*XyNqϑte3T ot0rNc2S8D"8lrLea\ۘs{SH ߒWYMX#&R^ %TV6gBcpޭA~!H.Ӷ^&w72IXEnXÉ}/N E]mHƩLZޘuSmb QE+$LdⵘpӮtp#[.|փu^yȗ|`sFd҇WlFŇwB=j~O|KaLEcU,qR kF0,S*#q\,: }(c0 #* ?c}m?}-6]o\7]`Ř{&5{>_p[es [(E7#@3͘ /cC q,l:KriNv^pE+Hė!ȸrJBAhvise AZ>3Cˉ :/2zwơ|/DD.s|pA ~IH2nR/锠ktlb+#ٰSOC](9e-1}^so 邌'kKytobٸPA%4 SyI_6H !E?staۉ=A"4X]ġ }8H?t>RH6_0wAI&A Rgu. 2巵m` |I$rjq7cSpHcN&(L6k}~M{t$2ծ$zI+6otgLz/~wXExΟ#bE\7mS!YJ^QSF s'R]`qGat>=mu%+&/I*(/J̏<ehS}**!at7C"T2l맽+̏ K7|^0Z4XD^.`:3{b 50tn^ĤO:DWl ٓg@t~6<='GHfǤKޏ& K$ҏIl ;lgѱydFjӯ9|ouĞeAAhOzg^'lw=ʤ<^̡ccU}#dbN73)Ht!0 WlހitY#}߅ozx/$ 7%bɂLkA(l_Xd/Hݳɽnvx_ВfHv),>H 'l.J}T"6=cS}m?}-D%ٹ :17z j9NhFA$&-' DTF]"7, A@#F1ga0g$}?HD@z M6gD:B(O@ ɮJDPc&qڛRln;}[\yHEIS]=vb0}P65JX7d.0%d]UA^d΀!i;%ڞha=`CV 0RCz@z̿†ʼn6Xl-ҙm'2&Ë/Ias"$O6䛤37 !r#: Eiw;6aphmPEv _&(L6k}O;_NAO(Sf;?NtY|Xn]ct9D-On9'qA+.MO=nlMS#YO9G n;B7_pU;ڼmY'm>lf˖T[sQi>Hq?}5-d1=xtYceZ߃Ǐ1իWӺu|' cGV t0 <@tXӁIt]kswbi؆aD ,h?ƨy20V yi0j :vm2`ܼ4Q:s~bLDaB+U1`?0Kd܎S(W`f0ËBQk};R]IUca`dV)Dw>vX| ;cA 1 CN} jrזqgf8J z=MJN}j1DԃGHtX6[1D>oq1s=@'6oƯg/^hm{vEt|G1sPӻy1s*D>v ~&pNcZ#BAGП)pD˗s-9e}C }*B}aڏ!nNƮ(/Sy7f̉?e.̘v֬27ݾ- ⃼!A}TO6Fv>([p6ьonc*V2OiD..QAgpw_΍lg9#9`ݒ;|"̑1>D?l6uFa<'m7FcJ2X0Ļxب97ȎE:asYiAOx6gu{u I|D[:0QglAI:st \Z&ZΚsqmmM2ΟZ1.u*֫ *ɬe|[?ݱ}s07Ƌ2ծ G?J`l۸-qQS5״h7NÉ/7=::nP[2m{Ee*pTD8rB@N<"8L6t #>mj̸5=Z1"Q)wz֟m ~?R>\ITmCh#&սL#OA~$-ug薔0wHDt㔗PkEP&y)d =; IL/N4-'1n =~Nm^y85փtRہSRܧE]]Njћ}2NoEl;/{"k{v\!U[DxQY 9c7E"i;:$L^AA0K^dKPħb<vn; _K$&a.'&ۨ96=Т~–~";vQgr!ć0g BᦰҖ|7  1z*([ȗ̷ǭy˔, H B2o"i0qѠ~9MT:5J& ;bѮdn mPt |i_ISiPE2A#d>C4~)<ҷlz|E cW~o{)бZ6ؾT0V!Hi-XgiK䛤37M©y{AֹFcvJֶ֪P ۭD]c70A*ys-e žEH٪z%,j>7im~3fDgsPOeKpT=yDCXTa*?7F?3'pȐHQ<uԨe"2:>e‡,VEoaaBK֬EegdWgt`W6zښ ||h{C8,IXE'!XoJ~Q`/ d!Ɉů}BkfcuoĊaE&[{q5W pD [ubrw-%O:%~za fX@+fټdt<D[5D6N FR'2qM=Z*Ld6а(C<"ăd K 'hS^kcsWՉTq]qR>h=[^li@X}tJM9|k/$R.af@K8\ZDjz0^at"G'q*joPvfIEu_lKVOGY1j%oƝ.72؛euߒ^`@l`3h OQs @x hBx g߆o‡썌d})MxCv0 +؈lW޼eS|:ROlyvaipI7_ʕhΗ}%>·}ʷtl4bLBu'6 C~# jxq& $mKac{l@ F1@ c;~yNlbO~_% FRCAI:st Q7y ׹8'j?*l{c';̌oc|:x?D^|/]CXoCh[gu&۵0rNY8"=tsȯbc#f`XExǟ#sS_}99}2WM:cǖ1)RaXc # bw$wCqZ`Ad>[~DuBZ6UH&\P ɦ 6}tDDl aTNcvt12=x8-i K^0?σ.!H$bRHyTRGv<]w}EFOD׸mhN i|o,^:GH*%!7XnΘKXn^czmm8Y=72ٚ[\ô xy=?F_7"W^SxAI:stk {Lq݁]1&ڄ5|Hm8 KM<ٌ#ƽ} yu#saR&$[᠉LWKpdכ?^q76%}]XExI3Þ)3O*8?`t ]a"R F&|f8j}6ftʒcߗ_F:edjKPFǥ,?$f;c3L9IiYT^5qfQyΑ??3NcL1:-\yjߧGwwk~_k"جZkRF'/8fop)iD'Yaa$!ڛ>?2NܡbMإLx$@|Ks>?l H{`K @IDAT"XbAYNc!gsN;?{3n~"͊-%美aص"K-l\iG&cǷwN uڛoe,l8>'зO3#O` =NB%3;hݞQlvlAng[bs'ٔ`/m{߅ozٌ0vOk}┦,@81O$W*i-[FQ5ē<€Hiku/>SSIIIb50QxVu?#o3,jLHFqi"X~(nlGyyżmK4FeȫHW \+H1Oz1'81  iic'y?T=7}P8喎 Fa TsS -cX5ka6_B!ko""m+ψ }}"A)7ꭸ':0`v;A"wl- 1r&2F+7l$VS A7h@-c1rHX wtu1F(>];*Sd#x_sYԪn]/@na3|{&qd]/ۧ_Ė՘T)}&#>Ӊ+;^E%(wCgCNmaFf0Lu֥gKm.QˏW^%}ܱy*B^t۝|ݳ|Nc?sA~EzߌQWnu /g"DNttSŊ|sZ}c#@sڗ E >ؔh`i^N#tBID4(c;8*SH_eʱ^n0h¶'`P3 ] =7Ε= > 9wM xXjc]gB{8q(Ӎ?Ctqn XqY9_/fqtU, Exg7c0&H+mTlz<ŮBg,OAA<əD0~x=)(+DeDw=6@'nlܽ0K&]OM}+ߋ_<_v+O#=W1Ш+0le5.ζ` 5b̳ ؎voi%vw=:-cnrgCAq)Uox# &S1?  0>u ic~ Cgڔrv}3L "D:o e"BAo訬16 @TMaIgޏ9'C +bn/]a[^ Av$zsK +޸穘 mR'Yݓ0Aq_N,|&"qTt$l-wNk_潆xy#HX+U;[ ot6ٲDx#k);:. ݹi:}P:k '-fV'N4|z|ydq ƇUg]Ødç>ϛ{Nt?D^%{aRx^[11{o\fI\G{Ufe]KZ&?4c.|9}RX>uw?KnwKb|Ƙ27GmPQ%}"WHk018.^⋑ fy$/iӯ_䅏>$2l. ]j$^+G:;ޛ}&Һd'w14Ts0kP|$>ꉨB[r,!I뚉>mcH K)+%LQ{KuWM;Eo&V1ٖpZT@ϋq0nxNAuH!gcpr;d}q`R%e{mTXBG9_Z9B"7l : #W~s̡=rZ8HHܰ;:2)U "]ìQ$v>(?u8!oc{&eD-Nc~+"5/Ȟ`Ϧoyy|>b+5gu.NcLŒE49p;sv"c-uWƩD3G ݃2It Mҝat;.!D>ڸAzCUK rNdJ{Eą2?dM?L'~w;6.=Q0Pk(Z[0'd4`c'%3NY¥{)\Bڼ$jhβeE*+GGJi!L ZJڸt#QX>”%tNWf?*ydIG#KNn@tJMs-O_8N$g˧&&N)NPϧ"q2 ?#jA }.e򆓈8))~1"h|R ''SNm:>iw0S4Ϝ'k2anO|r7쉋N7$N\hT1ǎ'qrUq#4NE'l{ʒ~ahky ۓO95eˁ{{8}xX7}aKɼFc|:8u5+ mF!QtƅDiuf}r}hgh.eʛi")& ug%υO~yNG|,c|'Y탐GPaQdq) i&H9aLj*Ǣ0stZv;oN YX27,xwc܅D!NpԒ;^~gDžVT9[)TP>~I۵_4=ԭp" [q!r7). :/;}nDuwUl6|ۨGD '*@2,K7etBHB DfuJ">HCMAPE@PE@PE@PE@PE@PЊ("("("("("(etOWE@PE@PE@PE@PE@PpJڻxq:iOXPCJ?~yMzՆ ff:p}tTd=7CvE@PE@PE@PE@PE@PE #Рoo~o/[Pf*ZU;jvtlI٢FI~fժg7,[Fwt"; :Uݺ/YBn-J,IK+8*/+0pDp4ǩxb4uk*%kPKO?nB` ݖ_OƏogϦ;vDÁQB:R%jRaSoE@PE@PE@PE@PE@PB@0:6mJOAM  {׿<>GJAR]Wz߹3_5ĽoƼynw\ q_š:$ܥiS?/[_J;E^&_G?0^WE@PE@PE@PE@PE@PE"/z~Y,fM:\9 _x~Ĕ)ev裴K@etߋ/FhO@֮u^ .ב8#ʕY"Bm+V$` jh\Yz5У<:+<87yDr2Kob*V$ҥ4JnD("("("("("P:cڟoVoC8 ǥxxOS&*+ >y2@W֨A\z$IVoIXSYVGn۶QN69P.ᆪƎu I 7q3ʹfaK^H2:3^{ #ɺR8_XF 4I-Ks4b?ta4pJ"("("("("(@AG c?֬>㏴%ljόgYoKP޽KѾQ#;X0,U䥗Fa.Ĺqg;ٖeRPExtI'97vLzvHIVu'ݺ9q)[j%AװOf̠Gr9ti9C&{qސ쀄ǭ\#YEIPE@PE@PE@PE@PE@(#Y_2 bfHsTf[ ؇ HM@z"at7lp.b^9ô sa*UW9alFGH9Ѫn]:nCDؓ#>|!?O/{lm f={HeFGc+VG.Ϭ֫33TRWb{)IUPE@PE@PE@PE@PE@(btj#17Wo-^E874@]sV.VzU, 5 !HH`&+?ãNaHw#Wy&{fԬG nl Q|) 8ǽf]ѥ mپqVf(:с4`ߧuB.N>.xѢwOu7!*~tKRǖ*%zUE@PE@PE@PE@PE@Pߙq«erɍXb ZHD}x7{~) <9.j]yq[FǷl/!C4`+ܴiP5z νǛԪCuf*o--2  m *'@`r|8mM`I1.i Y=os,2%:صYߙi}j@O]{-f #.}䄿A]jK8qG`d-؟m|++F~wh*+02O~zuTL;v CXVCơGIPE@PE@PE@PE@PE@(h tFwtXl =CO_wU󌿼`t0ԅ87[Ё0B`;сѻw 9ØoAM 0`>>e@n1Lăj˘1 Ɂrn޶ͱ#2𮻜 WHrHJ8a<E@PE@PE@PE@PE@P@FRP=쯿MvŶ!n`#]f=zDT\wrh3/hYw_ye+$EˑY;/ڦ{.:x{O1gR'յjQziٌGuh֭ ,4@e8w;t&9 z뭎_~NÇ;W֨ 6/cuQK!2A")}k֤8eNV5 EW;Iٳ'HoW"F"]PE@PE@PE@PE@PE!'+X= 1`!C*}uK_<{oy>_sYԪnݨ{~K}v߀1ҵY8pe/T S+c87>EYҰKǠlݶm`6+m#>"LnFw9S~fI)>("("("("("xёˣGwsVeRgm I?)]t Ò Y]k91y2}4֮8kX5]Qѕ+:("("("("("(|ctXac/,(RG-3}˖Q)6J~IbW6~`@r#ef"("("("("("+!P ZE@PE@PE@PE@PE@PE@&E@PE@PE@PE@PE@PE@00"("("("("("detdk}"("("("("("detdPMNPE@PE@PE@PE@PE@P2:IPE@PE@PE@PN:rҊE@PE PFGaoVE@PE@PE@P ! ("(etYVE@PE@PE@PxT2#l"("'6l57ӁKK"{7g5;\I/N,IŊ~&Bq'siwh2QkH_iKi7/+T6i#)W>nXKI]!2=?~!:U-r=lZOT|oK )٬v考ֺDow7jЌbMN2e6ڰy 羡ñh=z/֣"{q:l=i gžsQgv疝_S$*V2'қ5@Av]h+& U8`@OɿGsN-a6^z("(n2w.uÔXtԩI'oI[wp=@ҴgU7RQ}'o-_zOΞM ~`rTPΪTԪEhXaۗx5,}]/=t s8q"}8m^X1кu.ç?HC&Mƽ⋩M;sǹqԨZ5w4`X[yF 5ʕ&+3&K/N<1Yn=6JW*MwϾ;#iz%214*uL){tF^kBvעD?2_`2~\8Q]~ռYS6WF4{&Wu6{hXs֯A;!*]jmտ޸̿KoovzgDg]` Zѳ'E.ܺEG*yUt:VyjgnB2%ON?e K?/[f6.V,r-U7;g*<4[ط+ yUJr!MC%^PX tړXMZCT;)fDݼj}PXX +EY ;}5Jŵ+F7dx8[~{: NT# ˠ!x `~7`bZoZ>$[30iWy_*rj>ga+lx~uzv7yrv]@u}Υǎ p 3\Ѭ}`J'ORBUwZO RˆqX|kCԣ_e?:.Jj>'5mMx: +~v˅ճhhhhhhρ78DZ.[xlљ5ܑ@Gw5\Prb0[<z88]y(uԔz&F1:;Bo?SҩҮK4wR&|NK^$&_1"j]x^-V% 9>py8!ݪo=?73!^;Μϗ/ sY_}ֆĸp-:,a27|m>G8MF) D7Oܤ3Wكg cJ')eMÑ;s=!Иnm=\Lkpz"n$(+Хa$'޻Yw-Yd3yL:q @rR2 ٠[ӵgƼ)M4TnrL?bSanqd'Oñj9yD0-h4D <8yUip#;5 8AqwV㹐|["isV,TC*Bǣӱǂ#~!tz'"A/y렔ROHp`w2^M*\0r;}UЛŇld\/뉳U,[*xεz,CnѦB }3A}J6_R;5paD?uuM:ACJz?}X_Z܎T8Ibu!^DgtS}gzbg \+1=~#gseSrQK^؊vYCkhhhhh=^k4٘ƴV髏>5ǾSYp#89E|Cbu:V7 Z"$)ǧ1bT͹@j?2`b֥bYPC U')}ج t/Wsle;|:v>MfxBuxK*Ё;Ȓδ~K4FJ=G1Y+: O)PZЮ&#Ctye^<+5&$ c7awٚM6JV@ 9P;!JSL?Yi[UTf5j"v@9PBZn1Y 0^|3Bw z5#`Ys%W E%~\#g X!H5,%ٙ[ȲrJ, ɟ,rVtSe?Dki3 %ՕThRE"~ XYuC!a" ` 0 I떄YX@p/υaA ?Ky}[ؒ#94+!@3wx޹CrSRúS<*g`xTװ X-544444—%976pV0 1bPc6k5G&mE+{XS\H0Q;+$`yB+Mʝ6Ǹ ΛG5zA1x7hRZ8q>@GLȑ5Hl٨OF+c)"C̘M2TH؛ tu%4M6ц>Zjάi DK[/}MsRiG4( hֺY:s!j/޲9jGN_/]D)|t֥K@;V/P ++b~As&FN͵CxYHv\_8 S^bb&g< ,@ˤ8C3Aq nLwu6 7 uQ nS$F,npХlZ",bAKi&X~,#, lE"AXZ4(  6ƌbQ.bvGn"QOrA8R-Ah2b?x>8#E@IDATg>nOw$fQZD)`#+~E>+>Yp#XQa'#zg⴨.0A @1Ia 0K gK#h7Cc,sr7H淳E;g\P ׇvjth!J[&r@[?af3 '&k7nsr!!|SsJ#q`pc5!c%B_QxEr# 6g44Pb.-olNc4 lg&ak e+~pKuV}YZ \ I'Syu Wu1,EZj$Y`S .<7w7-t$-:pZk!*7DVUmy`r)fUd)OO >q(|/[)L#7(gk sQTV^̴y DDYr;sf?E>C cqXB UewВ" mҭF< iW[VA\*(X+_U ,]'< K S|]֡Y%{Yc҂B 0}̂1+bUgh֢/g\P(h?V . *ޥ{qcXzԚem8 ZWRVo3I >U{\F'* pi% w@G葨F@vE+;4@>0˖S%-1X>?|:d?XyccD jH$yz+$va#/hf8ݽppa.43&455H;w{֬Yyf5[#YQ' z6_yh9? y=;g~6˧cYhF LWo&fBbnzpu5k53ٝ?-֓EmXjk} ,t=o"2y JZ\`E4T-A$3?nngY%rˎZ{ }[x`kIɤRL+c L~vo9ihhhhx- 2dfA'gnLռc;@XfwhXFXVkd"`DȑMW;O8DAL'O ]kMx@`lѭjUKCEһfM*g^ t 'U_/j?CN@b 4, jV[4:h#@kZO_*kmZhbaY0:A|HeyEi+XhWbZ)HYl> ${5kpVyߋ V+.PÍ4>I`E泻GBH[+註.em"@:<-$ĉ2ϡՎVˁ窀_/*;_,6%f,,AP-?}з`+P)RHƱܨ@;]cIpNO#ô$}yK#M tn SO5]^gteQՍݲ6;d^?u=] x={`~H]cw.вgnva}+ wAp_%U.AFrXn[(jKu%ے{;?./ǙsL}giN]&x WU śߧm`edž"h.60fɐ](瘯ۙ:瓒Tj9_>$Y=>w<Z![}H8@khhhh=x-5MV̚|t#]ԕ+$1!8{:d{dmq̦rW=eOV 7RV@?sr^58)tÇvih]VgS'ʐeˌJr>YkXH{K*[ {hfBjjϣ ZZt@QGѭH5U*=lcQ(sq?*C"3WTA/+%sZ^Ї'sd;!A b&I_\B&{ tHu ,`k.lu'{W:1-@pSBPPt\}&dTWc@KZ. HJҢC(SϜ9\iVx{оVgNbw$*n$/Wf;,r涠Y 3۟k~IK fXѫHU/ܝalDvU1Kc:]7KUiM~s /s_P,~Fyx04^\7o=ɮҺ{qx{8Z4۫$e@MٍTpW3)Ļhǭ&$moek@Iٗ[\Aq*d\/|,Kā%!bpX8g"vF<w@ǎ'F6mkڡk4.8y.Jf,VљzVP9 'Е t+]k^2elj)me =񆰰eKBK+Y: "f(ʩے)5Z^q,p%̖k݅\Saq84].4 |.?ttQ9c R }BL+ j9ބ-Xxe~g'u ˃+$A絕`F䲷U-MjHZV#ޣ;1qƀBgr+2yg @ 1F@Cu{%/uT{wV@ wOD!E/?dU!Z zVB3I; } ̙[C'?8T#!(GOȖ׻iN+Y?Hw tpl78^ RXzT@8vZ`zday}~8":GeZK^J"P9!'"֭[tGqI a-( E)@IɶxY5g1-7!+I ;!֙J-Ժ94;|Ոswº})%Ž)揠tY8Pu4E DN"≳Dye+Pon}WDws~Wz.zRŎM-8fH+z99999p:8ϓ)l .:g%U@ -Οbt p< _C'Æu,kz¨Դ8q28T 4ntT3h ͣ2/+FZ2Eq( [׌mB)7a t<htTRTg UiZy*܁} Ad-oh{=Varh}=ZĨ0b_;o\[\1e C|pP48k@ ~wͷ9όc"иWpau?܀ XBcqX VmB{ZR~qJ^wM磖{-zXnӛ^ r5bѿ‰V zXk?ވUHWnwvw ȝo VD ]Wk.d LXi74~@BVgZvqdDZSLWv fUz?DY("ghUUsN%"}WW'^%CAW;HH/2zf_J?&@GOQx6џ=]Jw ^Dx7=oX1[ 5Ucd0rw[IVJVIɾkςV=,au3Dž`d3빯s Y͟TZ긶a)&Aݑ y:epd+1HR]~t -[Q\d$F@ da?iKԇPX^5bs>tn"w̷J iA[%KU@n3gn4!7-ˆ 4c&yRJv1[6 1M~ޝ~۶-h_!}gRB)nF9\S`kڷ4 0X kb8tV ۢn_KԫWXqb"j*/g CN-7U;RPv*(aJǟ%pu.Ɨa= WNA{EPK2_3PZr8vY@}iX d)Ao@q lhJиd~!ZRg>9c XLBN .J3t<~Nj&Gzo x.f4wRj*$Б$gj7K@M4&[,>frKա)ҽK7^@R@X93ޒ9? 7 JX~?+4deٺi0A7b}S2[vnz9gPyˑIuKN@ǢZ_t`HP8m*Ȯ r- !fx8$`U 똏 PV1SWZi˾2:{x%1i1ĊbkI_)Eap]u,{۪X>}sW,-oOx3AEmJKƯVɔt C(4~@sXqf\{,`9ؼ0ˇ;We' ;I@#qc+NPfp$y m5+8 7\^H|ApMOnT/ 0`DR3'g|_k'C*]n@+A2H3~(V"!v ,l`cjCP 1u# HqUy  7\k|j|7VdbU_h֢Oǥ@ǽhXa3_R߅=j0_?u\s&V凗 qL1F9堚 f>@pwhXdOXl i%ME{Яe31RB/j?K+Iž.bӗsr퉚$ЁcsL'6X5#lfI*|pn:\&ѩRrsŷ$+(|Cf ,;'Qv_ASgBֽ:hme\AyPj`)z;@=%CUfg$ݐu[GY? Kij^sxRG yL-X`\CXб =gK]fS=n|NM?2&KFw٤ό͛)!B tg3Mvԓwp/\qfWmTMڳkgoWWyd<5B0x0Ljח) !V%q3kŒK0C " q; AUܩ?{xq(7`W{t,(r:p KoH#[ b$./lk ̸(;#aP0"_ f T,a6 ,swGp[Z Tľ'VCs.@p@T1G>̳o5m@s}Sp1_8a\!4#hE7䲱!XC$Xq@!hrn 'BIL *y!Ԑn*IJ˫Y#?Ϻ?lӞ?~&ܞ!/zHqTNYU]an[@b#7ݠYgbBR3 RhXa##_E)SLF]E2dU=Ө*ķBP"̚3k.z/{n]z=#GG'Xց[i]o9(4T==F 08RA QDu[T:eKDW7b搀#q$nڸTebJ[*l}ߤ}`V@H`PE2X9Ш@H۽h$nWe ~2|pvG ,i:ڱ1"-,bL\fYӰ)?OTHK,觬!,("mT%\3K2FpX |}U7`V>$V ˗I;/ րΚ+3X13t8ֆiJ)Dŏf{'xLf'wR>2X.uRyuihVY5ӻO `(Ҳ6ˌ6̛[RB)Ɂ2['ypc!3@ҌF6e~'myN((<S x2;VI΃пXHGH !^FI G2}I T\uXk&O|x_(Hx`&N(`qMCcnɁy  +ܘ1P\ra wnT%Apu#KdRIH8y͸"3($== 5 $O9r XeVs\{ׇ́o$aff%@ sê`xfƥ?HO=p< 3A dpgLycm`-Hma˒+yudK壆l_xӚ, v5Dx! 'SnAgSŊT㚘i-[)pD|w#!p;bHZ4B+{5l<&CꤪUn#mb^{*@:ittxO-:Ӊ/VvjQm$\Mhp 2up}%((wGZ-#`d\µ| #'Xjw{{,hVIEtA{0p 'BPvZ8*Rs9.Vt^^>ŽGX >!0B+  BqǂϤiWWdq5tp=s->no;{T"ZWVV#_Y c|,[5ΐZYBN] Ν6 Ȩ}eηqo ǿh԰@3mŋq6 *P9dRCs_뇯4Ax4R[-жѳiv( +`AEFB:HX^}zuZl@f qS%]/rD2۰o#-Ȇxmzxa{бG1lG#J_4 @?1퟾ppYuccwt5Zz]u)2_t(SLT%6KÑw 0GQ '\;'g$q{KP(ϯ݂Ic,R- e[Q;DO0`A yjG w7ȧn w2W Dc keX1a꺇+_̉TKb(-u cHc}!#,P䵐ZV ob{ Z 0˖sO544444^@Gx>~8HHM$^o@cb&X<":E^yx A8\,"Jr/U <\APH6҂k+h-@3 `Pa~7pE6Dn`0U6@Xp˫mb~n{$BwGhm}/X] !PJ~ WO>^o-xMkBg&.KBͅ7 KX)cQiBw^=\`^?r<  E*vlgJê耻K..uѰM'(j=Qt:aanx8;Vv'gNbw?]ƩCS>a\\˹Ӫ_2KAXA$k\7b`W YX8x B(;=Su*ApWl44lߖ}KV3aOk7`wlPa'+ rGx߯?-mKXqx%ev˅544444@Hs@s@s&e \HxNux)vnZS*ӳ G3;&́7c۳Mo2X.# Ez tx`Jq@D_ 4p%kjXep' x r^tIg#h#Kfjq?~|\ Ro^Ooެ۫-Uc""Kt]ghx8xˡRWGC" .4w"B,6M(Vǒ6fo2벷 X-ESs@s@s@s@sm:ކQs@sN75QZ"hkuY>^R{^ F)}9uIs@sց[i]u1rDJ?9lrՍI>\}@}Wet&́k-hiDU'Dx nQF9M+Yg֑ͯ,o[Tgh#n^s@s@s@s@s@s@s@s@s@s@s@s@s@sh44444^Whu}rߚa u~q@~Ox97x87){%Hv˅mj544444`h#{߇)Nh?fL!B<߸AEL cǦHnЩŽ =Qt*С@8`c]sދj]Psg¢~$zxHD0{b&z_{Kt0QTD?u]Q%%N$LE6ߑ;~.:T٦L<\{Zx),Uxcy*_9hLߠ=|/:Q A_;.wQ"/wd[.d{kx8V;OK{qLiPHL6=JV u8!DtWɪh稝/]s-!+C/CDߵdoVy7.#4@łÜ[}DjE>n'5EoDט'^S@TQ]x-DUuK%z=}P?Zr-c ӷf4&MA8*A:'Tv=yDtaifL6bs$SY_ºPw;/&\)j4]MˠMǷM9c~\; X-׵ihhhhh x+ <11 @a= lЀ jbȳO@K#.PdI+zQЅ[Q.TɓR%c9,Rh-4` 9S4Op޽2v15.V,r-U7sHD1-@1Š}[ժT)R&TGiM:M։[Ѥ5DX]}ӟ>|L7/2.&׎Wܽ@:W^)sҸoy&# fDyM##QDGXR73oDC:ql&I}%́ٲah}i}ɖUDm+:co+9piyg}bηV-ǡ9hû'*ew\0sV& ˵WC<-G?/f5oDla=gI4tAMMZ2LW-W.{j.!ZnI,9` J_p*@ZlH昢ҫx7;q̕ڹYQyx`3sޣB؏L\O}<ׄ89b#$b 'A(ca rux萚Or^Ώ!@dLJ7,6*蛱A2+?ϙfD(rLl ~9?A^AP' 0" DJBV ⻠Opevx0}&ݰr>]< <‰w]=癨;>IW[ETڀeOOekwIIM 7jp7JpqX}nǾl;w k]{A|pE c'K^@ >*4{`.+;uwbw;<]?,On?=cɔenJ/K73LF7nEgǕ)s NO$ҍeseINz=F:H$<ܾ1s J2)'o[.[pnv]P-'{o9Fy~Obc͙3KÚ/X tD6%D ` Ё2wgp[n+q]cU<Za&ŋS?lR:>)\ظF 7[8&$+0TC [xQcAL?=/,iuPB)$h;w`܁K .*}e\/&Է!\)WKC~mZAԥS^͓0/jY#<,u $@IDATkݳ&W9H-!vB?È~Ꚇ3uZh!prn[*ZlOy /Tw>=I#j0ms wed* @hbg>"ȃ Bn$AAy&naBPꢣNkv6-^r]ǻq>}C <4R9D :лDf jxզ5lpՊ 3UI4|z5tUL|[k.˹12C ڂR J+Iž?Vbl XcQs]'L?lι;.㤎C V4DY k]v*~ v.He {P(f[8|wJ+/3V <76rɣF\@)uZ,= ߼ k0V)Sd:"s<ϸ\2k>7[G}&[Yi_cL-PfN/# d!JV}?`)vy.5剠o:_:}@ԣ!϶˔{y tԙ?=JVS==]n9O}4444444q:Ns nLۿq@l# бiВ%Fwu1* p+б XhuX,TgP}Uĉig˕c!򃬀ݧOS,bSoI:|uGYҙw}IV4齧P$G1Y+:`U< zx~6CVIs4DK^w"Cot'/5_1Lqn'N_5S6, p x.3iq;#k:}?nEV@'j@JW3N@ OBCԤ /cV dp= Bh'rTӈ$P\+HC Ry!vYV XCm%lؽG^WZ5G]߿ֱੇT`E" wGy 2kMԿŇ]}OAp]g8ƶWS3fs׿Oآl Pi |qȟg"X 3h/Tw_2*YE^> ?T$L2!-K? +iřP~7 yqr|!íײcX-sXY Z!!XH9Jmp-&㕸닝_ޞSPآcta{saMfIhM(v&lX\ZަF`D^Eԏr:9KY$%8z9l8ulXy|Ϯ2N.?i$B3cՌtrIÒ>#*=L5z#Aж$]lqǃfDE pdzpF\ `UQ,1@B!-` !W r@y">2B޿̙ܟV!@AVx^(Z5@x<$WHğ6.?Zι/߷` e!Zs TKIIK]r_86B~^ؙ/Z.#2[̻`K*Xrƥ9c~h/ӏD{V]<+XxdE,s9999999%976p܇)w4F jq `͑b[@ʞ='.V1O\HP,8ޘ-`]!)=Ο>=JrM1.,@Ƿ&Љ\ڣň-]J+_##·()59Ұ)-](ct,ePdF6eP"jT#"{(X'm귉68S͙5 @A-mM<:r6IէUG7ѠBnV3=oX|4ZՈ긊vK.Q ݤu҅2掠U-t`UaEK"@1 AY>DgB,56s$aW@ x2i)ULjm\! m@@-c@XD3M@GơH] @i0 >^>h|?>#y*~uűj"]%AF_TAV_ EKVK^nwn!-&mMgx|)J̑ ~GU `~"W?vAY񤮸s̟̖QݵR<\KZF =xJ~ЁNjgi1i:WL=9x91"&(I!P JuUe(7( zt5Fwߥsjn3co;}A߻u6qٲeV-$3@ j ,![1y.RͮT̀#X{$EJp4%"@CuqaGn `A4:0.DMG?\}Í ub&7H5V5'`E}n[QNy"},=*I9wev'RH,?ƏSx,F2 ~kpeDm/c;D֐}3#]A !]SAзXJ.^yR pj%J6.괎 zՙb~g;#)K+*,XE#,: R V?]h5-=5tS t3 t ~gQf`0FnOM7Ej I1;]FQbA5ŃBv\0@Ϗ}nW_Ie"݊Px#f+]-5F0H9?o0HE։2 "ݷԹq*b^mlXi9y"*[6` 9"eNYwod%))3.8kGx !P3Q3U$JS~?s t6_[ [V(kh; ;qp+&?ԕNNJ' .%%[y5]n9qA 2dr~|!;@XfwhXFXV֚1\Cb0"ZK;yc,,@1%&ڤ@`b|tZPcY`l/I:ܓtl / f+Jc+Q$Q~XyṇTy/rBGHb\s_ja@3y#!+nH0Cp[+ i1dP n&q ]]$I}Af+- F [U,`+4]UBY҄X6X4} !=p_sȧ3HßW1>"EDݮv1< /emHW65^83!RѨa `%v؆Hr^~$BU=g.k %NǕ2 0Hݴ?i1Ωbfw3ɰ#A~ҡKx 6q' j]t7{21aU 'ìCkp_!9ξ4 X6fBiY\R< _,nWT5f{hA,ot7tӗSYU#1}V1q\Uj6Fї9J-QJE qȂEQz^zsM88< XmvK/?\C WT]ݫpx"GI]~XC c"ð(oG5 30 C)¬;Iʤ5Y@kCޏQϘ"?'@oڤjY>c{QEפ(^xlHQ,yWw{RIqR+d:aW'"k[tix N3{IcH070"ěI\ъK /[؟-vȵo 5ڂDXOz45c2 &A}ҳћ3q$߼3r2 YK0ٴ {ldȐgP+诿?8 ?]m! A_ozu3FPf_@ @+q{[rR᳜>k:sJDdv:!@ $ԙSra e)†f֠ ϋB Qő"Baov#(\J! ]>pFme LS$c<*\%91<!3@#ƆBYqIq"gg3U`:@ FP>soй~%%xo]1Oqğv7.1He~Ǘ6QUy*1Ab:YKd쾏~[dLv@sA}|V=SK?K I%$vKq+vH>&tget; w/Mߩ< BWʖ~?hjCjG5 D C*͛4]i'\A@VW5P;jgRI.|{%Y tM2UzF@ ͸qֻh@.Ӧn6c]6*Q&0D@8.d-[,"> L!> (CŻfeǣp&F 9Kw^6Y]Ƕ:"0k'{k3<Yc\iĞ:+VZKMN"7W8&ƽlI*<7`1\!w\Ep "Zfn%v+H`ve.ÚcѻLEuw($1&C_6=ڃ ߊTJ:/j\!DW~~=%- 67"9<@ iR' ARktp['"%h_|Ƕ1mG%б}vZa;ҹh~~m(]>vm0:rjIV}6^ Gwр O[Жjݨs%gRkfa b#!yR`>( S+AʹSo kTs5ɿmh ҥ ³M@kq܎*\_D9|;WKf"cFz$WbuM`A8@3f_h!A$¨Ej,:ߛmIj 㩬 z6ވ"ÖZuA`σgZ3)_!5d(v6Iwt?ݯ5iǫS@RS% ԚS.:jeH7-mBs@w lZ " $IjI}lqA+T˩T}'!ğQX"t\W Vn(|_[ Z][M0皨uҦ:Tkೀ[o-ࡀpپN *ᥟӉ U''AF<,dd'TP{Ԏ4A͈i54؊y2#pZNm[ok h h h h h h xh t'vFM޳&MV 'U&w]@Y$q=GEH0ь9G>x@Si|b*d[O?IRpp$ hp!`]A@H~?r@'La3DT*OGYzFX?"t)QtLyo  I䦱ǒݡ-A4],N0Agrrn6*a߬7 A1[x!]YvMԟ냀|Rz$'.8faA A |"fZuwn00 ʘifRG9ȴlT!, ^D|nH}X ԇpR+Y964“ L Ȏ!$159fc &HԋgbtŨ0JDإk*zProyۉ4X®K)Sjxڭ؃oDzuX45"D\-ض ۥz[`0c; K} Gɘ> =G E/ݯ4Ŗ`/38G8,RP~;y<0Fꀀ!!A,U )x j{ؖ Ub#&}iSġ^U`~˕cImӼ  s?D*O㞕u`- @%ز;Ⱥ svI19|߫믆',:m[4)p/Vx2x`dğ:4  SgԸ-_<!6>=)⍨aĆ59vk c2)P Fx8I|LBVk I>@O0cm%2&*Ыe^mBv@HHڐIjHhwn.a除}HsBކgC.g>'߻D7˭J-8}^_vAr0k?d3v, <'{F:M|{ D^ĵ>ǰ&LlOW*&SSW֋I+>n=0AoFG 1[?qvqwR} n9x/Wrp8Xjixo  z=Si9Lxn.sx&  alllOɲea_]*p @ 0rcWUc:aQDVF|0"տ>!J %~y#0"ӝ"7 20v'z 뉟G#9d^ aE c B z h ` dY5"C6$ 2V%hcX1ӧUh)ԅwzYyhI@Ҏ)Rm$=0^2S}Z: )tYly-LE3YC#1~qoyHFd 飧F8pҤ'|)%mОi{4 1bǠ 4UTOB,ǯQi5or+PU)Fڸх]° Tc#IW$$fөNQ'}6RAPS 5zwaeP1[[Q^ Os}؄cl8n&He{0`T5)yD ޽C`A=G̾Z'\hx r%bfa36{ SRvhdEd'h,؎>F.qu =ۡ1;wD, f5㬹D+K@G 1"õ B\#^G=޳bkLscޢֽq% O*D5,H_JGW$>mO5öua <}ZPw<_/Go˙`W2i'"8SB·߷gSt6|ъF}~ڈ:@*~{g#d#T1Z2Wsi%Zz1~>2ORP9d7i\ikI09Galx0.l. jHCá7k 6_%L3sK`V98756C0Q-!yﴱ2@) wǧ؈q*|;V&k#qA ]A l|'Dm۾f@0&р)b?{c*mG4EjaQ >ܧcL }VؖKAy_PF m"7*j4c_,ꑨ6}+sy_k t|ʭ[5;6eʼn˗jM^eE8^ =MҤLɣN6d0ؑHFBhD D<瓇'@"|:3J !*W4w( O]ga- &/*8GO \gBZX7YUm 8wZ7&b)~ EyQ )ZLrsDd5M|MSy6eu>O>YgMӘ]!tBX%͜:lxiPž(Fe5BVzL_ c=t 7ޤiR,I vS:}h tD 55555555555@Gdk\55555555Dp5]Fk@k@k@k@k@k@k@k@k@k@kЀ:|XpZP>EЀ:"@VU^u{%H@)'1bXe{.=yBg\qPI)v̘⼎/;NAQ4 puta2>k3ױݽE+6Q:QDm峉N Jv+Cav$q T~qBUdslݟ1\ք(sVzm#Z=_HT{|1mv#^nJ+Md4P4,Jwզg,Zz)UF>VG3pZ.B/\k@k@k@k@k@k@k@k tl;~Nj+S&n]#_qGvd軆 -6m]}bw|۶㥗B]yƮZE[ rΜɖ-J/q˜ĉĖlTȗZ.&鷝;8c+Y7m[>eӑ#4~#FBT`Asz)M]O_d#-o@.\͑#Ch!6:T@%st%\-mvA)G;KV,︜F)^KAOt1-2Z,5|"'\$Z0hv~8Dd'܈$uͼDGI4tuD' Ey$*-5݇Y wuFDZ:1r]A]Zk@k@k@k@k@k@k@kE tD!e޽tV.RǏolOʽm:1ys㘺:}ZCy,<%p -߹Cu+[Lz~ YM,aB93fvϷ 4ԥre|fhܘ6@(fGS`09U*ҥѾgimZڴ@7doAaKkKdGR ݮ<сDF_xt,FUk1U#"A8E؛od)x4(r`чDG,%*'Qn%!jĠAvJ 76( 2 2\x3h&rL3]`GxDͻ9gf=ukGU}&^G\-lLA'< jy 7s{VJ= Aq kthfу{D)Y0`%۟:VG3pZΟkj h h h h h h h x!={6m8|Hg93b:p|XӦX;Fgy[LOuR@# йm迧QY(Gu{Qw2.:*Ц=yvE]ow>$}h!z|Kz([l.i_j.Ͼ/@ N-~?CL>vq~܎BTg&\$"Ν&Jˏ\BD,^'Y!9? ! _2ϺWA;[Ws^B c@S#ZwAFԀo4Q` D_ΟDůXKgC=̓r%|M Β) ~.R u؁2w]xI0zaɅ3DVBs-* ho'b7CXmEg#iğNCakѐ/E5DK$0TxRkM͇plm&G,"d'd>~ _e~V7s{'V3ЕCW(ujW)M4O]NRd(I$gzzӡȢ#3vL Z6EpvY|@}ߧeE~J_/"o|fe\;~N58=H7q(/Ŋ/n}3;)}8Cb:qP8m)չl޳ҥ݇P*2/WXЗΗwPx EQ=VC7W\^RUO4vos`1*:,NpxJ3x8VhZa)JҤ t|;9>z-sH:rHF4ph)"Qt3,7[1I5e:p2Y?'b2-,h&zpA .,՝;0@x"#wWz7}:fzI^N;5֫e3J/% 02%; IG~D[V0E7%5XL⚆=u!ƭ5zȳbgƣsVr'f̣x\ƢDCkyb{;4MP1rQh B};tHG, GOfЈ}YD& 0`Viʥ. xB<"K9s:9`ыö%"] ӷ^],Yz:q BALTŽ4 z37s9_>~J>YF;Dz5$y#5,*h~F: F%,ȈTĈohNd4R^luM)D82:K+M%zBB 3 zyݽ|rM|Nzڴiy 7dz+0eì;iRRxb ?1FmF_mYX,^HD31 ^x)h* z|AFj }ƃ!jm dٓg' RP SF*.-_2vX̎٭ B b-Bn;ul>B1GM_4nQ"RC( ƭ#tr$GcD:=΄FY8%8? pX@.AXkwzOqomԡwvD /Gz\c }<3@UR\ъ3_\~x0fQ9I:}CԺ9GZQYꜩ O^7;#+)K,OL^$(ҩ}0)hKvy]о*; E&vcik 0xTV|HCLf2sz_Px~e/lx9 t#5>);7](ctƠȐ%K :) O$q;RIP߲G 60LuQ^y6ӸMTC0si)cX ^9.ABD )OA&~\m~JjOHm4pe,Znmiԣ.bppz~yԙS}7oS䷖7,%dAu_37:H_?XTh!1f-mد<2ݖ_r_3*0 .@FU2@$Sqnt׃3NLj=1<_x9T[I^g%O&x g5d?ӱH ϶q%=s3g,{s4 7y}v">\QL*>t9/;ȟqXUc򄃺B7t&+B)\=_.d$6c~Lr^Ug@Ã)`ᴜlW|@:s$xoLk?X'|7HCÓ59:i(c tQЁ;9V(L]ۜHaw ЁSU=|Hccd aQWX,\6@Wg%?w@Rf ظѨ;xT^F)ٶ0䨂PVC2 !?:*opp7f G~#<{jϪmQW:wUmvV-!+Q' xBkFQ%/R͡$3Hٵ,f/"_ Ad ̚\(YJo,Q_#?X%(*Е!4^ĝ *`@A6f}<&S9f;خT\;lG읶4Y`4m4 / cS;=Gne @acoѢ(UT& 4y2>9Ezc0+U{PlWDz+M7(8 A7)C j{(29g|r)~Trp_Q5?w ?c)t{ x~TG;qX$c52Z`vD/qXz!&]ߒ<rz555555555qA7dPmɀFUc%&[X73tѵjU[C"Y&{z)*sw^coFx0ci:*D?.h?~@kHz*UgҶx:ёH^_%0ipq.IxD?<ȪYt@R5,g|e2 C;d,J "Q&9Y1y00r f #%Ӊ=ĒyD|iu3|t~c!Kx+؉ #A5z.r=QHbix ,="BR&Y*΋ɟ;!D2%Y Ha\cZR8 2x61x ekC ,G^w{Rm8$x`w.A oKhn(m""+$(.9>t+I$prF .mJQaq(Aj84SRѤon{Ly1<_^Kj.*?t\p:ɞa,¿l>yg;`^B$j=ߊ ośw?ӱHd!!01Y+>=<&^? cԥ MӧO]@Oҙ@:^B qﲷ ^0=RWN/Rcj@0qw@l<~O` mL@Dośw?ӱȮ1V[t&"%*Mz(S#v s3y C;ao'Qx5-<#&A+AJ6&na>_$lAR@)]dF˫{6O B/ 'Y &؉@q*\/Dgv)q>/ 01H?ͽ̙Fٶ\G6qi%y&/خJzaS6Mct0?x: #sѫTelBsc_:٩SxH4gc5ȍ!\uL-j  PRlI4n Mh8U='l87HLQXoK3lZE7Aa]k*<.03*@z0a4W=]2fTB@e㊝H70[MCp  HjW``CjI C` "%C6Ķ*9EYVMx{Y=OӶD‰*]k6ց7nд{i*N]RWʖ~p?hk#D=zCk@k@k@k@k@k@k@k@:޼I٥zq y_yň%[ x ,u4ȷW2@@.Xի=aTjZx tpڌg*m+2mfy~L"ڿ$XEL/|EX1G4 Ó.tc7+Ҝ E/sѮi:1_V}%M%e/%kN`;ȟqXYDXu {Ka>Z@H@d\KX44r!ZZZZZZZZZ>h ZW'h]+@'%1]͕*Dl:Nɓiv[ 1[UY@ޮӧ>43-[cд6eboܴ3oH"4Yحys dRv>M]txLHr f686}{ H<- n$t! 韓D9ᴯQeф=' %Zqa7jF6(v`CD'pB ZS,+W9\Y̧vd^nU^pJ֟c.n rtVy t0Swx%fzkƩ< 2sJ=CTI]-߀R Ōjف!k'ZO"߱ȗ)Àj<~xkIv{e~ͼDG2X=2,=[s]qqM|w;BsLDnoV~h!J}t!Z3jQymjTg 8˟jcYNi󦥶 gvMnYpl(b7$u娞-Uc _`wa̧t\g,}]FO#~#RHo ϮLw\p2ɾyZGXI*a?^ۣvy r}(W'b~'8,SZZZZZZZZZj Z]Q졉wivIժ]@v(1Pa tdOQ,XhƜ#w<̩RѴO>1W-⧟$pp$ hp!`]A@H~HO 3Ё<O@ϝf@UBYRz^!x?ХD1).&kƊKvw'vd#)t:᤯3z/!'faͪ*BH}ՊfLl`ro-Ĥu"F  "mGԻ%lI!|dmu Izna 7myW5 Ɉ]$$LΠ8,u;-loSAX_!]?| %ă h 0 .xHi",42߰^~(s䪓FJD=GO?JS^%oƒ@ 8O:/8ośwんj)_ I_5<99iKҔ7MvpZή:]k@k@k@k@k@k@k@k@k=!O?X͡Ʊ"h!@!e޶mKeRΒ X)۷)IugIZ371&'WEk~Q*s\NYҤ=A&]KA׮Ek.L+uF:.1a0I`.&jբem8@_ϝkC)%r tl>zzΚRxT#[YPb({tUL۰R.~@G9Vy\Qw^a!4rl1YyQ f֞Yv\?}`,Nꯎ3 YcR]HBX RM ¸ H+< `ş{4  N F8"ON` ր  >jx ox}nxx#AMKpzn-6C ux8I!:o0 ad U (u:~cm%[R̜z*6.gN `"(BXH*Α?vw` @HB8Nq+D>I J$)Q.Æ70@u! WQK=!v'N"Ʌ b#t}8p|.EݷEC/~,L< QrLx9$Ȉ[Qi ;/P성jW鿧ѦhMOdDЁ<md(b'-] 2U!Ɣ8}b[8Hť ipK=%59UW^-7c` &d`&xH`lHZtwS E"d\Q%pmx#Kf0<d,ƟJR0t@Gm!t,Z9s%pZct <7@zn9sQZ~$7#AO@zv3GʕOeRڶ5x0ztgfrj4-UYw}w{Z[@@yr@45{6]PC o05Wlsy M+7x jq1s|)@c rٷ8(JE}U?5EkaFx$)f tè 8BAZ YRB+dHG{  !R\Ұ]#-zz1MqDsp` pK6Av3il,0H,?8_;lM!JII[bN0k[x2 k K^ 0@TH"eh|8.C<,ɞMDڈ,]9FZz@z{}6$z}O !<6H7{|"~|1}Y9ݼ/րրրրրրրր@Dh:̊Do(a΃}x m87}oEga?5%&[V2֗؃d֭=K.^&+U+XC27n(6`ZD]^"ƎBpw'N4H!/HƧSŊTyM̲Sw;  ʐ%K ޑ;J:vpH[>7oTMVy<|BK.=@bĎA;&9lß5"_j[2GEW_Rqo]° Tc#IW$$fөN8Mv5OUୁp ] /@+iEYx5>j}rElhƍ'BW54]B;# ޽C`A=G>CUB.M̤s._ xfJRvܘV613C1paCzX 5ؑ$)Ѻ:I.">rHq\:jHε|~>xmC称uc*C:`Rbҗ}y5`6}v“l4Ҽs86٘;>4+\Bb+E߶n:K~+ݾ7k}Z}ju\bo 3Ӽ N'ŋFJ㛕2'O_fYitu{.* Xk-mFōV4KF$R{ܣ=S!! .`A2K.֋!yRrA%{IbGŸ*x!욕Gq^N@=?o3w5.|Z GM"4þwPx EQ5VMDj3B84Q-f¼/gN7|z555555555x!Pu^u\"b&xR?ϣs=;NH|&kO4)smϔFax\#o>ܜ9wJ87"]E9kT{7Qz Q~JҲk@CZiv X8-ɗR755555555h@Ʌԧ55h`4M\'{p-|!Y/蝵~^ ?E^U* gn'$!}\k 4"N "*ꖴ' hy\ v"#>wDj $rh@-7¯ԋr᧱QӦhM53NLP(m5g/dpt;TbT2^Y"J/X- ӧӢրրրրրրրրր@ת^, hźl4]րրրրրրրրրրs l9%"wrsZZZZZZZZS/бqoPLu|mƍKd軆 mi-۶/͛4v*rhH]8#wNlThQz\&N$PfB|Ԣt0Y_OiljopXgڼ9lr(,k5  Oiuu}%cpnyvn!:D#/wѡB*3,rlicJ-%u8!\괪dym6JZ xUib_5>(xp"тDc!z%;QFla2󷮙WW$:C4Ϳ#//FT:Q"͵d]@cKdu3K@2q Do5a|D8EN\-/d]8;K؜N%jY.$ՈK@'1QDĐbU,\>,Dt`Q*D<ضf!5l̖5QD?it={wܥAiM՚Y7uNy=QKt)V<h}0s=[\1v0FŸMk-8R :[<#L%b^D}exwБ 'ƌJ J3=SSi9ٮ^k h h h h h h h Do @ܹTn:Fha+۷o 4" @ yq0(LJUǍ,ޝƍKA׮Q#>T@J#MF3U!@MʦiB02OZ)9]ŋ%JVJ9shߙ3aHuw{>!oתUR،P 'ש&j8n'WlA6>>N, fDڱqo3y]Ic+rOC;}94rێN n:p ][{m%Jሼ~Ihl}&:}44meg٫д ܏/R_:k ~J*kٗXU= ?윒0j} ;uL[`GЕ;FRpa-N/+Ё -Z:}atJ4SRYI,Y(mbqziEOu/UX=X9H4/_d1O az[q7}ȭ{E*PxHҍ6ami@@IDATQKٰ?oTw"=VvA~CӾd9۳y}wId9:@Ԭ,Cj )0U$D,,jDe}yHiCw Fz>&Yv_Iy\65ZvA dMWӥ!fA|+u9s?//kwIJw؏,z.gp+ww =-9]~D4,Ao|T`d% &ʽYd؂u6+z*@y8Q)cʬ;z|H:`̅!_x&x~Tތ0c:"?ye.O>sqhSw6#+WqHA ~ c_grٷAJe]n6>[=A 8Aai,`V.܂*A@xbLHevy'`f'Z1S 5|9~oJ|O3 EhD`^,#Sz$I/<;mɵ2~OO4iG35:G$6 ikx֑"s L i& ȬDúfN _mxÈF}#QT6Do;<<BC5w.m$ƒ(k"-F[nv_<緜 [.$9WI/J[*m> }oqƑ4wRqQ&y?VCϝ{xoL88apx&+3EɁǙͅ3V6,)kxo މg8j VSǾ^j6Q& {u|C q_i>i݇<io=l_|kH܂yS8wmx Xxzvji4`4`4`4`4`4`4`4k:^L=?H?Wn{k:3 ~ \Pbt\|wh;nu|pkwƈB൨@G3# M>Z|)uӢE5|Zʜ$J8V D3/N-5+(o r ͯ6(HgOIr$O"#a9wlTER@(pjT}O/IM##dZ!tX S*uwn:c~,aDF,;پs]N4p-Rd:fu('-e^rllm^J\ӷJp `iT$ P IkHIۉ`2- "C`d/a@! r;{aS=&k6D\t}Xj cfVw_:֮Gxϫh]u۔uTGfȏ9J/ W;<ATHP-(eᔢhZՂRK-,G]F">4*KگR]b[_Z+˓Z/ۗ^e <-/m<(Rfvݝngυ #`o1(*SSwD_z7Mc*`I͜%v^)9;e*RP/ӷ0c[g1L;qu]8AxRVCƾ38g0FFFFFFFFiO٘ƌÇ:bM4/@/Ƕ@t67;Vx#/b~KX4|%[XR&H@ͅ@|8zD֬Tw(*}$ݦtmt=V3w 8}Ѧn">\9j'*ovuhe "gÜc ʆ6nZJy ](0W/r?9 viĞqnO%9HDTUD=xZ"-dmeoWad6~"K׺nUj GlP~aoك^(% q._y$*"rXSR,ՔYr@͊rK;;)!\7%I C~1[=#ڰ(-ϑ2EbtWDFq KvٴM_m8~0G ۈBWPС5l 3qO/Uf[J;" D/{~2g@ i>NCG^Xb%%.>" w,LTu'{.mP⤎#q2欚q.^|.j7R:Ё1&EOFW m@w1o!r('%̜rxaz8MAUAt5jCܝۃ.p3;;PEr%xo Ge(7~8c³c<4`5Jj Ã~E R<;T IS|2-&H}WHM}=yxo(;˻:ۯ_O0Dv £%;etx Xxzfhhhhhhhh;ts2D~bZ9`CFX"*g.y @;K{[8 (с*U2WQ X,p Dz4==%#! &ŵ8*j)\3Jn{6RXJe kdW,N":RCtҿ9< ]'uwM;@`%kc- r8[/D}`t9[E"!nkIً/hTQ":4ȱaqttЉzQ!'m۞hK$i=PJjk+Q/Jqdr~I]TWmv ݿt~nmW/iD W\Ts˒[@RaR|/;O?Mv26u:ID[>4iooNHui"ӈdsuQ9ݜ6mj*T|U|Z#Z%Gw*O;ʱ7W .ggim >=J>GTn*w$<3G V gjg]3ߏ]N1mctcRJY](j2Q$)`qk3FFFFFFFF$IBL'ӧڎ!Ǹ9{c AiDcQ5r;Sh ^i̫a<F*2%H;Um0zeg ̵ytXj*$@20G dv"~|Sl;wz2xy716X^mUTVR"qtQNpp'.gK *E%:f0ԙ_.wNʕ+Gk2qL.>k'y\4 o' m"wTiJ':QϏҪJUZksxf-ʫMFy[bA\Z=w\j8v ©MC"s:~&X4EqD$THa{Э`'\Hpi9Kkn7+)T-(q+}+C ~K((ǚ+y5cYz=s~XI1hQs˗_~ڥ:\Rid4`4`4`4`4`4`4j:N1ȐA]3O"7Alm۞`@Sסpt=@D=pyd RCg0"zdiǾ;@>ЃC3KM2IQntXGj՜:IZlNvwuQtAJ0!"𲁺.M <2hKj3&d HOȹ[Կ*X9r c(# ~ 2S:?C#QOH؈lgG!+h\N/IoXM%-hګ6bN iO pO ,#9 .Y&h+>N&[*?੏B=@?GHnp9 e9fcwD(DŠg"mMֶ bӂ%"!^  † @]1N|h:)㳊nxSy{5uBZ&5sx wUs|0svqJ D90t$V<!GK"H5,Cz)x#@=]C9= JȬH˃ǝL` v2)k"ZT[dž&>}g8qǽ^~;#F:+EHOR?AK/Ic ^ Q-:_Oc;J+I%mfta>JՁ{ZtQi|%*N@KnM=at(g[Oa>;?Ha=RzG 70.=6tN$)aOHE|3^rg V#EW|eH!.#2Ezt5 >#2Etqta}ShjXs/93q@)+ɜ!K 'm-+9U D:мA/ؑ22rj@V~'9uDy 4wUt{hʽ <Qcz Ls|tmA((W X9rHqNEVy D::yJokxËG2'R|qN@^$JD2EqXAsRI iSa<+7TֺMyb!*] T-]CT^6b~0C2P#IHo+& W'pE+>)Xm@*,5}޺AHYf}]NY;#E譢PDqZ>ܠI9&";#nWZ1?FK?[8!Y:`QEmϭ<ή;+9f #ƍM;VT2TGq\uѿ Ew\Xݚ:F:-z[ | DF*A၏w@cV~H$|wRKNYy~ UZ8Hr|"J0qpoqL*)\X{9y]p:Z5qus8}U5fLdNOdBf8}NJ'ʦB~HxX?^ƥu|NMJH8u(^Bz7JOd%V=a2J17v$L"0mK՗RhZokaV#?/LD^@ehr[_~UUr[<=~h=E?x]Ο(S R&9e ,VZH\h+JqEQEx:P 2G viʽ%">Wsa<z6?e׍{x|tB.b屈>REo _]asJgQC 9J79#ӋPy}D9 $_tWN)&q1̪n@F\#"Czm ~{ae@y1 @sshb%E]veWQ򓴰B- ,JK i\"o_'WAQEz*(g 8\3۷oӽ{k_qtlHE,;x%{{) wM7zyS>"zBtjY tzZh]E4 Q]f͢Cl4ʺlTM۲EtGt \ArtͤVĶ+:}F_s,VQotoܪgYvCOy&=5r] 9!zNu~UXE'֍>rt[@ v7}g[ XCrYDv׃ Dps# 0f9 Di] 86Pg'HFv7 iUњ+ 6IZ1 ![z87D"L`CJ'%`<ɐ\1lٲcmzbo E6HQDUiP Bt=r#73iT9v;>AMF #v(gn>kz-eQ@"9${Hb,Z-[B 0l!{s,=^]c2oSCmC@ *蜣2Q@1"R{nmŊo6ӮaHNP t:L\?&)O !1s"m; @0[%Gj *:Ӫ ,ݻQ? JQ8hF3/BCsh)_?E$j3'`o"%{z:Y        x']W9mÇ!Q,Rc?_3PatH?8n` 9s<~R'LHڵv*ŏppIJ!h~ u! "5@ڴ6N6E:Pt̐ׯ/zG A@m3>A>%vww-]syсAx2VzV/Vv#]WH!TR`,Km7@4:OR^6J@DyLIux Q e#%:XT 0٥R/|qYU v0al"p`uFגUdJ'-^XEוC*"\{D;[Hz%]"BZ)ˁ*dc"!>. 8OM\y<)y"=2ˣ_ 'qI탻d-Y?Kx C5LQy pqZYҍͻڷK3J'/G&&ͦsΉ:1( BFADٝo"zc|)z$zHU0lt#kToI=U%֛zpb֔,O2tM$SE8P5 uie "qSNjsvz2ʶMJB|k߷ !z(|wgCol[SU‹~PD aVmUH@r=d21F3}YtVn:x.A}!u/9mj+Ԍ%fg*;812wO<СTKS-Q+OF9s0.A(SD.!e"љ{]ϞL_|g\tl9v-]J q:I^q޺.sNhHBCEW#1cD٘}b˖bۺ4`QƏ3Hc7=~n@ |;تǒ+MjP8eJ3 Y;vP"օ>.(*8nuX!3kм*%7sTg^vܽp` 1_:<kHu_iE 0l, H(KTx9qX"df,6T`Ӏ60#ݏnvJ9--0ψ1bhQW,F+k4>TS.0ɏP8.yl0 i`~Gx:@P" Rn)xd(&\/ `Xߘ 0AJ9"䑁H  30?m =N|'eJ34>*}yKз! )=ûAjJGPi6eǪORԖ#v&^q<sqO(X6G%<7(u风YݺRc" P ˡ!&%]!~>ELɊmxT?߮RL:Aʽ]7OB@*=pSH u^gkޫzNtBPB'Z2Mr 'wH<ň |\L[XE|sYB~@͔5n_H):_6I?*NCfx?y\lK ң ;@Ka~{-u N(DB:80 >#DF%Kg@jMQ;kV S40000000xg4op%gK L :suE'wn:;_JN/5/~Yɒ?;yU6L'\U `;ȓ 7nwg:PШ…tU#0#MJxM ?櫰1,`@G@6"yȳz6*̴k ",ikK!:ϣ(_c( ?¸^g]~!NL8"i`F*%X^5 )5sIZk2 10 ~D4{,U$[f2塏&<G:\:U07*ecӊ9Q$/ 'bJJsjd1>aN҇݀L֐+%\ЅJ%Lv`}TTJ{:'KZKpOU u! x6[`.w`G /¨jcT]Xo>Os~߰珞':tV^-a]ӒRaw6\_cǽ*u#r"$ pn</ "CljB<߳{hft5?׽f.+ڼE|D'֎'5.ై$C?_͈*oGFW3W/Ӆo(ZՕ휳'<N(Q5rz p =!p7C>DLBTa ?S^Y'`Ѣ,@rjgFFFFFFFFC%au;k%m1͚ }G8=V2U`o2q"]+c#sm`}#H+Ȓkw j#R!G>u|9md>Ȓ.]DSk.^ *H# *QM5fN)JRZFj_P~A| #ܸ}6՜Ur5f/ĬɛTmj5*()?{JVB 2,&d@Ny_d/-[FY<nä^ۃ#Zi2 W 1L,V^@v>͙ ai<Ts[8REE]=KV7HXNVY:.Mݸ,lAACPRv\G'q~U_-TG>28֑D kqO`x D 7! ivZKpY1HΤ5㼂>4ȍ刦np~〡jNq9a'0/L ZĴ!tKZϬǪDC;N& 7?&"K?tORF,Zeщ@G-]+I*y=;?ˤVb[+0q0UsI,[OFG)flQz|sU?>Uc-LmqG wV݄c.i,y8T9@$>ZqlrN-*xK+`aWg[U;60000000@ۼ7< f."")Q`Ƒ9 $'¶|vE0;*X)ΝRK/80Qc+]( =k| H'(*%AZ@]xWe#X7-R"q,ĕ>]i藋g,N9Bz*nW0mR/,"voA'"c}߸}V6. _O7OSGy")]=k7=Sy[z+Sc!ps8z#3|g=,<=ΙMрррррррt[ˌhh i`_xރAxW"t5GtC8sxN*vWg_ARQuMp֔  S檌Ÿ b`"c@X<0Ex_';/zyrd^gU.F)wx}[ODzCއh߸}DHA:Ҏ3UsFFFFFFF csFFVb٦܃ϫs\ 2xvm{[^yWq_9a NuR)Fp С4fFW~5mhhhhhhhҀ:ޯihh=8&F#*[[F.O+jʍQ:%sz|61h'UJS" Vb%a OiM5r>č  / (ӫׯa yfFFFFFFFFFFF >Ms-FFFFFFFat͙Ѐ:P20000000pt8U000000000000K #,me4`4`4`4`4`4`4hϽ4Wb4`4`4`4`4`4`4`4`4`4`4Nk@rmxz;,3x _q1ōĊE> tO{5]yELġH`}캳tye8j_(FKWJDDTa+9t'ᅯ~vJ-qX֜C@Xσ_=F4Q/MA?/zI=替É o&DE.|8뵙}wSw,X*;-U*PhKe4n\ְض.ΚE>l4e&s_ȑ=uj*1#+R_y{[(web<ԲT@MgmN~MGfmpa{_-KExLVuMݲE,Xj(`mBfЯ ׯ}4µ: Q̙~_?+YԭmL:@ 2&ۿ>:Ya=⧏O`$زkDDwo|HtT> K5yhtc#E&JJ#6Ȱ? jla[W"4<" DgsB QDkUL<ݸJcK}ޕ^щ~\'U5 zx ණg˖wӷ>mha|HԢ4VmLTM3Ѕ?C7򜌹'{{=#lVe|-Ux^Xn_+A)c36QD)f=ZD%M زߚz93tDdֽև㛏iDTզ , xn+z,E#4<э݈|#M.%u8$OV(P}]wfp|OU)`qfm4`4`4`4`4`4`4`4nk \[.Y@DŽ-E2QgQaȑ@-}Gj|̜1.q@IDATI_kgO% ]}DKQת48.rT5>d+&svi~3HA?~Bn9x~XZ4.Q"r#U-ZDG/^ t*4O([{KvV*͋Pp;Pqg2u#{ }؛;uѷ<7/'v~04|,#i ۚqGoƄClK{ \Àg _Vj_8ڮ>A.czv e;o]QlWRȅjOW8aDI}/߲=RCKߋWF~?ȯD91`d=VƌڥZٯG#M>Y3U?=u &C 뽅O?OPۡ tJ4SRYI,Y(mbqziEOu/UX=X9H4/_d1O /'LJh9D }e }  [.I6K,[.f'Hyj!7HsEA+S,swwkVL[;"s]xsZщ5]#JĈ""'Fpă.9umlU꘽cyo*&jET<::NNG8Tb&Gʤ |eb0BtKB)%9uHU>Z.-кGs\?ɜ* o68/Ε/( e|rt_t}JkҕW(Ydu>z5zNIs%݊R䘑K'W^('*e1@uݱZɾ;@0T=1τ;f+'}A AT_Ï\3fgNʼnOT4NF_i(e=2NŘ9yc= 0D+f{{"9'MiȾ̋edJ$): sB0Cm'H{bI89'IHtPEo\ų!He_H 4IXDf&E0Ç->v7eJhەx#,y QRؕy,)=7mIZY(F"V˱'U9l_~o{cI5ݭSw/sp[Ӆ-DyIȫ-ֶKUsY{>8H;(qEJ<П!,Tûϝ{xoLۤj$' /!?ob;]?sLH'g7 [w巩`I^WX{NN\>QH:Uz6Wo nwgm3}$H`ly\! }]"p La\ߡ)`qکͦррррррр;xɿV3T86;G G\q6@%O  rArE݉9Ȼ:pj#Fע.4cj'ԁObfk(s(qt\ZM&Lͼ8Ԍ碼-ʁ+4|z#!>%ɑN.?|\qt_^eORIE>7gSm==ǿ$5 ܓj݇xcB&l/O8niǝ:<6HqR6;ZP)E/V 0_n.Z4YTg; u֍"E痓E|}|i'3DiU(hWbg)Mq./peI{/Ӳ6YXRHay3|7ij# c|!@DET᝚:#JգO|o@>OVn"uuPKjt(ANy`]E/Sz ǀۂN1L;qu]8AxRVCƾ38g0FFFFFFFFiO٘ƌÇ:bM4/@/Ƕ@t67;Vx#/b3;clbI  [WpT7d,գYRQDIw6#Ё%X!C{3R9sDEpUtЫD`Kg:qջSo^)@ sR-U cj%eoN7KW4_SnţLxូIA>G3n:y۩D N5G횉;J+#6T *IGV /TQdd({5RB}R lߴA;Q`x ^~^s$+҈ s%Q> A:#nᩪo{DZ CRKeV9OdZ-J dt j/-{u+TE9!8?02oGD">z t A겾621KY1Ccsg?E:1d2 r?]0v%; 4DG2Hg;jz[iSDAcsѓOO 耡?iܣKVKDőGDbᎅ˜ 䲓tye !P⤎#q2欚q.^|.j7R:Ё1&EOFW m@w1o!r('%̜rxaz8MAUAt5jCܝۃ.p3;;PEr['tA! v \%Et φЀkn+Ղh A)sHP/# $MYI DyS.D_t+ޛC;yӄ hNYi8*g=70000000x755y/bƤhLL:IX~"r!M"xct@t L]0CEyӥ C@GEh;{M}\գWu/BH7ض2gFƉhRٳS:uD;g@*EFY#ڴ.[+&]Y@Gp7MQ")}v֗QԚSKb'Ѫ/VѡiRG΀MsS5ɭ'4R RFtx[0LTvD2 CAHz6*U1Q>7_ʰo?k(Qz]cإ`.c=nWW7^8QMٻ1@,}c$m|/HDTHA#R2q @.0=p_@ PG#S2=vh\AwצdrٮD+P$vHAP1Y8Dtl$  Nn^cC3|\O\zxGd\v%ӳa^{ / !y.w`~H$` v=){J=JD"}bs9Vdw燆B'WD쟸_m{-%ʒHBDB)կD@*ő%RwQ]J׷rL1t}JIZj^M/IG\MrQ͙~/KnHroKDO%D7ӌF:PUׁ֮w=*(Wծ͖%2uqp+p C3ȫM(|Ժ uҊd;Xl]~F_kA 0+{OE?N*SMҳRTD}ZHEa24b7jSYZuXF.5K?٢߂OZņ8\.\(wi.KMgf, ihV e0&{b a݉Md R5M\ o]3G?n*}L /"gRq֙U!E ]>//R.%M%=r9" FGɣ.$km@AZd #k5.!5 6d|fLP;=^5kD|"oK:B˛.Jv@GӺ胈P}ysTie0SBjw'E:p\opxXTHS-~J)Q%Hi?ȹhv٢jU^/X HA/qsΥ̭掌c(4$>*DCBU[B7 R* U@aCw|sUX#,ֺݬt Psw󷠨sË~T|qh(-` kDvcYz=s~XI1hQsǏS. ᒚL#VqA, 2蒟y2 Gg>@ok@  ҼDd3r #,γ'Uj<#KLk=:ϞQu@]hQMr;-&O8>WйHzתEes 8*uUz u ԥwQoBnog@GI@^(PCO1!AzZG5:UAp@GHƪk#Dy^K"0%z~r@*Ftg;3>_AzebI2xÂo"(Wmi6F^sJH{ZC~i(`a{tp|, Nde8l؃>z A!}"0ڿc)#H~/7YׂhGO ʖ@z%D 9te6@T;%)n,k *M1ԑv 0א @&U )E.O8Yݘ9e|d3c#bҽ-=R9@Đ~ H'POE4{xbC; JY dЉzg` SXBGDDVƱa O#Nq@舑8uΊaQEǓ叅ВK(fҘWCj w1莒JR%{Yj&]vAl&Ru垖>,]qT_ /҆DSDlDO]/# CD*|ߖXώsXxO/ ~sQ$ KOks OIR𷋯*A?f ?jG*|xWC]FdP=#-1>;jj79}GxL#$~ӱрррррр{w8~&e#]( #iz8{}>e  P{NN/~հtO_r'gЁ4RV 9COz{˗)"[BWrJhty h73"^б#eeբ'Osݱ։6|?@h@7 Д{ Ρ9y3Lۤ?QQs$ ۆ(be`~@`#A'BHw`I <髐6_Ԝbz}irlC>H$ *8v5U~{HUtTޖ(N r4)$QdtM:J+hgKV"D<8_"JugE4L!r߸iNJ|_(ttٟC540@ [3qOU`HZoKoDH%?<h*þUAɛUzI)+Ϗ\L'XwÃ~jV 3mWWY6qw^H~~o2@J̆рррррррр x'[ӄ 7p:(D%(DM9TKi+Y=:"&9Sh)6+D`$Yx4g*t $9 >%W] 7 8\mN.,H쀎rs<.8F[~:M9YDzE3&FeR@s2!3GoEUT|_eSNk_s FtΟ?N]xE*wT@tEDPzDHWJHAz?  #U{f2y6K#˖ٙݳ-d)^ `g΀[LYo YU A}˷J1 vk|L"pKէe3hZo_V } kd*RH0 opQOV*6H#Zt`KB z*HINbŪH4[p`+f7_tP+w*21aj1$J=8esHZ"2#}r?MoNm߽^h7FRQ>0a\E *zc/D5u/}!Zh XytB1QQ=Nρ]WIvW|mc#_=>ũr[k~nqd[L`diŸ/GϬJD2q cQek&CەnA%:XÿqFy4}R Qvb U}Vٵf?^Bl46˅.l&:ZwJ:Y,ߟ=~&S!ML V9djuuVHVS)^w^q{0]",\ٛ p}Cv] "v`^"*c A xVg9P®I8JgEî̏iT;sf` #:qB#`0 A тPq}9+m+-Z \TA|{5Yߨя?>`^ӻZ&' Jh܉@.Ӧ>v'`,.M֭HDž4W]3U+$7ҠTk jjiTkx[qZoP*^g®-5-3F^1 xw#KRtqiY6邏kۂ" 7߶ZH8!. |83oD@ Όeݵ@T9ę1:9 nc 鵐fW_!m*t'J`~PHAJ[&p!2% F2;\0YSmY{\E6X||eoACK_O5xH?%V>|Xk]A|1<2K$&L;6 za݆5~Y0mX:'rvmS촒vI/~u_}&cFy!:W8?z}嬖,ny4G&Ko.+SN+U/,؂MFt3L'dIil8F{WG rTꮫ! RQvpAZd(dŰ՞Q5tVpM~*4EͭGTk嬚U1`I':/O3ˤY'QRݷ:lQP,|/K|F/DRa4>,7aJP.@CoANA/Az=ehgTbz|t3 mn۽-I҂hw)ȳ8őcwLr$pq9T:P5q~)VTvjfn0 A D??Gg*zgPoa%:v1Aѝ )4G:ȏJH9r#ʒ& MkUວDG˟Ӝ?F}ТE;c Q"5D|6i/,NtP(k4~}+/<5 :76Qd(N8p [WGuD›cE  <`*Y(ҾuRH%ƲDNІsB.U6\+NI見(*sF b$28We'%A:XT pOZ;|KUH2ōk*ΚR%چTD874Pq%]s'!lzrk"!^+N'ЦP\x2! 0%:eMG+Lu}0 c{{,]4gT'K*Jy;Rs!E?nIoC lBf!C*t'=j]Qm1xZ~A"iqKs;"|ggf "S>֕ͻ娚m)eݿG&~/:~W:('Di_J.ysߦғOD(z%-kzXӃ#7leӋeg,8U[CmY0yj͒%mъʈ+ۊ6*m N}Ƥ·J 7WҌuUJ9}'sZl >DRɊ]a$#AQmL2wɠ$AG՝;WDr_紩7R3Sٙ A `0D?=ѡ KS-M(ǏS YC; i`+G%[rHD,ڽgOQO>em,N"::Dϧ4_]ָiKGO9G_O8'4VԱ"{2HhCj۲moo۰TIKtlfrJtqM)RM#u'D&]noA )XAU)U_ 7Wߋz7Ԝ]Q#z=9WJ9leik-Qp1pLoӟG֢|H!=  ,ӢOhB!0$a}I=4S(ѱSΝ[Do9X]c6 A `0Db ѡ#~,AxtsV)\8XXԂ#O^؝Fg&Miљu<4 yΓeLF#MA4Dx ؐ_.agn펴H'LgPg_Gʨ ߵrJg˔LGqr#1poT!N+v84+iDrMi,NMP#iz3 w'SJZCڮҗO =JpL~1Z*՜yin͹g[pMU ETo}1\%H We?;VBLmghFя#ztФ3QD {iibI-)sIءh~[7F舗$H9 bw/ˆȐkRCT‹/uMۉw'?({ JY#~€}VoC>DL-rzԞ)[?Ŀ2T Wmr뺪gA `0 ItX/f&%XH k#`DfbSᆪr Yjp7;.\cK+c5km`~#HmȒK7 jxqPEG>pVM.\HY6K%9/0WB]Oh XJ&Xm-G e) Q/[&tGn9=ۡl|nuRœ!y-kO/[q_K )7N5դM66Xz*UX *$&ᑶ{ȊTcqQɘ7е> ,tXtƞQ6oաH)^G s#Zi2 뿎6XC vVux E^5W UAǻ}s$z,{ KYΦ Fg6(S !8(;y܀lε1ѱݶ|$#:29Nb=s"DMX µ&pAsusE@ܰ[;, 1Hθe㼂sj4ɍcy? ʹ〣jNQ:a9ag p_@ijpx⺅dX,ĸ "j!}dDᑦMFd!"rDʸ! y fThRd  ${QՋiGs鮿Qcj \z8,D+(XV$C)i;2DI72z:SD;p{ЬC"*晛t}~/p![pDd!eTfL':Zli\MtVd2p m_3 pxܫ"J\{w =WM:Yz 5SM a~@| 0u2=%tdjF|0 wVSnb`n l"u8T9@$>Z/KC· A[ MīlS+aa]W{[U=37 A ` #*/;wYK$AܸHdGL4;.qdIb''Do'= F>?БQ91}90$ BE3ɶ DLoÓ tRs \qAsM@Fw}:Exҧ'urt%O93JÓ>L@d"!I"rdnHxJ7z5s}~Y_)v-"9quq l<2>ܾp*"9eSVon t g ~25&6 Ʀ `0 A z!`u  82V!̣~1 by`Q:Ƈ]Y!Fg"E1TT]/us#:rA f"-am9+A `0 Ctľkn `x`-zHԔu-b!RN}7-#Ml9ϞMx_;Aa#Z|Q;Ŏ3CtfxKXx.vj `0 @G134&*fA kޝS^xzb@IDATV!W2T;( <~D Q$Q}<M h$"wo oYͬ A `0DObѱ)dIW(Omz^ eJ4Q#l|1m]{wb6 /V|6_86ț% #+Y^6Wn[__bE`{O*,H-˖ RuڦMdQ/n\ڮ]iֶmm۔/O%qn(Y(ZZ/Mݸ`^,Q )^Jnѣ4z:TH\pٶr=n7Α:.}uJ9z'z5u]"XNKt߯qG!R^"Z8.$Ҙ2ORՎyZoJjZD#滟߃{Dm[Ew <'D_&*65*7e-n?-RĬ6 ;$"yG(ڀ-&OA.Uĵ!h[L :Q5l[ QO@ ](3G:ެؓhϦ[؜Q*ײՉ mh"s u EA?%NxN>*Ftx7QDcC7q85̑ϽꬱD2í{oWtCŮjϬMX2MRq0憀7ЮDu#V6}P͒Anas84<.=foVlkO jAtɊ'E˄ϕ~c}!h_xWysԧ-am;_37 A ` co޼T^&:ƴl)/YA R ?uq2u*=zCX={Rd`joZ0}Q5译ǎsU DM˦-[hR\3ϟ|ՐaK&K!VnrJ9ssAڪHcE}<"P[jTP!,FEyDFuŎZv;p.aluJ  )SRŲ@Ƙ-6}bGtn*YҤ!g?iΝ+8 `JeKǞ":N,=A緜DiQn:8Vo":O#S9'H4/_e1)R{u}#(rOˉJs@sȈ$Ɉ3iP,F:n __yQl.c#1=cY hZSЙr{F',&z-T>s#mzqD&VPFA.ۧQ5$B햮:,ݻZ 2.At\a5o'Ad.ޖ0;u8A$fRYDJ#Y䩈Lp&G.G?D ]WkFhYeɃ(ٻH_нٗ, }p:?# v/"<_:Yh}ySHַn;K kwVx56ε9nD0Go{&)zOe_߶x+O,'}:޶S5sA `0 @ :ٴ1Qd9+L޽,AmQ/dRU`bH{^MB4o?I hV$`}S4֍pc%WF{]&ҫֈ `Gg;`c< hzBZ9~C4[fXbI*m@!TI EA(BZ(I<,6G]dS6z._2m| Rx٠uJ@@qv 7C %!)[1jiKHٙ͜IfsD:lj?m>GW^yRS|l5:nIל)PhE+NG㋏Kq_| Qrwy~yrX~|Hk{ ,Dޠi,Cѣ;(y}'%m[z׎_#M~a?g֝ΊL>e|([l]BE;+o"9~r~D>П!+YCϝޘuЄRrD֫.o?Q#4~,\ f@ʾ M*.vw)-k]_-,gSy{u|C ?C,|ƻ/w,}Œv2S ˞o*`]6-am;c0eA `0 At<_kKY{"ON#~ ё3mK L rAr%ݙ9ĻU%:pjC kQfLF.x]DGo%ɳg"Ỉ8)#pj)mEqqEUmۑz-e:X1Qo /1=3dfztQ ..uaJ9_$Fͱj݇y14D,\_R^۹Yv86-'k^@ 2$^ZHZ-XY[pՐk9-pcL Tf29D_8rF#*T~rݙnm !#'/pL_Q'ʐZLבPvs 8z1o5B_#"+`p~s݋uLAtAxكc7r 5IpʤR,)= %͗}J z @VJE_Eّ{R@|&"Y !pVCkj+spg8ʊ$dȰqKrZ"'Rw*8X n:]2.VVͻgesvqQ[]sO~-_Z~'HHm(liAtPyAA6PzyڱU8Y{#;hߓs]Uj -_8[ 2+TE 9a?p6sSED">J>` C>?eLɌir@͢)rS;/)ց ekH c c4Rh\l|CjQ( oZH 2BJΡQ-w,tᶸUSY%ODq]ŁȺe0i=VXFB2 Qz\)dO˄ b`=HG#w3h$ePs>%g;J E f̉耣?Y-i"DbN%ˆ b۱ bA#uQu+ޱ8%ϒ\!dzEAi5@zU"]WR:сI^N""M$CG맮~@%:jHMRbPK^S\iQ-<7|}}i P;#g$HIhJAdV ҝ (ATzqG3q0 ;Ge(7~8 DŽgCݣ6A\㻭V j}A)sXy@S$ o]X@)=}܅ν}'yṰ{+i̮]nDxAYY9j޶߬ A `0DO= Y_X},>IjRŢNZcEϞ4L`."?7HDW({ŸBٳˡ`Z9GMǹGJ?ć,YB+/lx9E l%:JEG e>uzND&E/[&._*%=DGHWE)&ЇQ՚^Kbo%.}$FNDǛI5ԠАCD*?\N*:؎4LUߐij@T`d<0A tmlCZ23wޔ[|s `9v RHH81!'S9'+ʐz"A+aWt y|}*kĮ Ndt #绎R_8_ݙѽ6ah\GDU5Ý$tKQU s R}`d80 ; .2 =p] RGc2=Rπh½|&GE 6e2zrm岷SFJx8| @V*?F+mzG9&!: ƭ=C:K%#ZŹ6lm~  ̎[S-y/uB|.wVHH;~{SceDSb`а9v>)DTȮ|AmGiH+R!z"SLVb>A*2K" ]_FZF){'4b@Ԁ%҉j^nC ]2%g:(9'$R"MWM !"w,.6}i7'My4d}f ?Kd!Ou1[OKKu]R) (]ZS}{U%oz/ qA<;ZPW$vZ ވʃ-SVHBn>9 Pu=DzyhC/d6!ruSJy](j9sR\-am;빙uA `0 @!:q$DoLO _zIcܜG}AFiXc!)=kvDSh Xni̫'g|-8T<BCt TǏ)/kla DϿqXNJVbJt *ǼAAڡeL1cu4cWO0QAt>R(vGU# iA~ 48pC g/K{Gԩ3孟WM(3g~v˝1X@nAKPc!QďJч`WcFT ސ.]#rcD.G,QTOI%D8/YG?R(BE|P^~jgf^5" gJXƽ{4cfR7q\uhk.ZaEU7bJ3 ?^'!:nS?\w L'As!4?|$*AJ-X5@N>M~(6jUH,j&H՝yϘ16Zhl ϩMbУB4$ QQu%DM1g|c;"@D*ӉSaѭ ;ZorJk׵.7+#[Zsc >*gZ yI(!(ƚy!ٹS8uJO$VrԌK(ַo_٣!:E4EF~q㳚xSyug>Y^C:'7 qWuo@fVN)r=|?MiWĐf"H5fy1Hr"0zxv3hgL!0vȨ G cj0"5lhW_܂(+_Fc]/S񛍴ۍbg {;툎S "5.-I>Lϣ$$P}6A(ӷ L`oSNΊN-$aGѕX{1|bDieD?0DuTH_}TF<q=S'6V)塷\(AwKOksZqڊ|V D*⛁[TbU_)"ỌȠ{n( 2яǏigU|9b;  aEͬ A `0@ :\Jyq28Za(D/4= oS.'Njn 0?̯h~~q̩ï }rH#bbx#ˋח\@qSZ%摈vD7A֩`凥K@J >YvGX!?AsOM':hG:wK8\SkЛq9N:sũGd! mVk!OAVt =Q0=: Mm5p<@"U]5v"8-zDoP{O(Y8}^U-9i!VT7 )|0dv`=Q&k>Rwʪ7W_d64˱UsKT"p $߰<SkkUNF;?s; X2DG $f `0 AqG4eU Q rǜF)eɕY@)]bݻbkDuL"f2>eJdyJtPe~'" +48cĉz%ѱC{Μ)Dب}yd]$v_y,Gf-%p X#逍'QUEB.9)OB":=VggΜQhS{%,ւ;~oJ2ga4JOdV=c-ߒ*^,9PcNCfh_>-NlDC@z}nVKX##VB?Qe=e}ڶԶPyAz zx]+`TnSA (Lr2 N,VEz-B.ySJq%NE&=L:R 6G viY+tSD}į̑Ҧ76W|@4]woOF#(ES0n."K1ۺ|oB>A5$*bzt盓툯|/[(_1{*A[-&0ӄ?_ogVws"q8Wk A걨ٵg[J qˏ }ŀ8z)}(;ͪ>+\c ZEcbl6RTHBA ~;%JH,O?驐 +@B\5s~:ݺu+Uv Ex;yox8H|}=oMN8!z.|nD;AJ\/O r JȈW|s 1 ?ˁv}OQ=RDî̏iT;sf` #:qB#`0 A тPq}9+m+-Z \TA|{5Yߨя?>`^ӻZ&' Jh܉@.Ӧ>v'`,.M֭HDž4W]3U+$7ҠTk jjiTkx[qZoP*^g®)^#ܢ;c0=w:<~$U@XM֝ nN:i-k N;qm> $~C1: 3F"AXv] DiC! q699˰ΐ^ iovUV!5W `*mNb*"B~ "9or)m DNʔ/Jd;Jr fBNes {[C4`=}."Z)f0M.}=R z X92BZn^+GC$"K$&L;6 za݆5~Y0mXnA-?k6EN+i稝R_ib1fٞS_kѨGэ?oPj9AC':kOir<5$/ =%֚khm^zQqmWbvە!uMV'NsdktLJo.+SN+U/,؂MLIfgI~#'=r;YYa+tNi=bgrFtdvhH-k{]F4AHb%g> :7 }"kMi22 8t>`a1D׳[vHe ᨷZlGj .:ӊݻQ?ޒ$-,qiF3/"vqݻi>< E\>aMr%Dʼگ A `0Do%ѡCi&OY-rX]LPtgj8͑9nG` 9k􈲤ICڷv)4ŏhp$hhX H~xH +с:MD (u: __ l G A ,*)M!~?Y|w70:QсX_O.Jov]!RIt,S8܀д|k,ʱS"J!m]DDTQ2 zz8R_\ѫv"lfrDỊtuc8]T)\-\KR%چTD874Pq%]s'!lzrk"!^+N'ЦP\x2! 0%ԩ.{돭 `$qn.ѳe |A<`ǒJzApN\H_طS]Oʡ́g6j!GՐ!Mp u(ɶL٘X<#{-u:+N1Ag Ox3A)p\ܴ\teqjk6є2S#f~+^sZDm 4/K{9FoSDqɃ'"eVӉܵsSẙiGn &JZkM H'96Xq"`H[u/%KOӣ-Wm U8D[cucR{F[L\iFY?:N*ھ9~_Z-J"P*S$ 'oY辋,C:+|7 :hIR~DXݹsE$uNڊ#5jx0yKXxLA `0 @C  rDk9X|85Ο?=߹v irثV kVj۔3C* |mLi t<_7}|D{I6j'U\5rQuqM@tx{aț_iF`Rot#E t,]YQүH8l*,z4H;+;1R}N d СPƁ?ڭi= )͸AM$&q.~)؁hf )LfL>AiW'8vT[H zqYs'8Taafv  .Nt@s$![kĦ 3p?e 0=y*O+QDY!$t:pad~/Ȟ#ҳ-J&%8?xwYd38;QzTل?M,j@e[mu ,"tNAX,Pu+^m9L':@*TY/7tDԛC=BMi``O>ӫOӮ(aʄT:zH]ȼIr[.GDD%,E4}kvsoaFgi ϺYϹRa/N[k؄J3ǠSdzC<"uMk%Mգ ;@K2-&"! N@%ܗNDRMQ;wn`[vh0 A m1DNiֳAHI1$AHD&;g[VmJ&N/5MaYyBt,`fJj5+SFÇT}`! ­RpDGr%'+с zϞMy`+ֺJCtn|BO3xh'˘n%F1h&:/ձ!]Ne*i'ӑxyz_rB$ H'RV-ւHY@O֭v)pH>'LgPg_Gʨ ߵrJU1b[CzWޟ1^J91iŚ(.ʜf%-HW)ũ b}= XoƻdA^irO =ݺg!Lַ[cz.}{hqMU {0<ʲaO_;f)~U[vc%ԶȜY{fT!/a=G M 8aEt읰^*aޒ2dy7N{CmԉxI≔ &q?l i)e~}o=OK.z/B`[[S7՝SWԉIDGCwnԁQmIA6#L7oz=^ D.N9vk|m&5Mۉ4 -h+!JGa7!=! gkF;C$.-";a=Ç>Ŀ2T Zwn%,몡ܺꙹA `0 @@ FKI>RZ*ѬT!\BbֳMǎ #XZ_A[8MZ,^8ThQjđ)BU jEDmj E?iP"9֩R%"3@IDATɺ&V[)8:EY2D H˖ ݑ{[zNvh([='v]Tju=~F,hbxm§p(Cʍ맮Si5@=J&VB Ex^8"X\a21tO :D)g͛cu6R/QcLj@.F+#d+En F&P@G{;CF欅ݵi0ZUsKREыp{j .z(Y!=GrʲйtӐi<+Aљ;ʔ8` ʎt 7 0[smhtjpf IubD @(篓:|QVCJ#\8ޠS㺀"t čF[;h, 1Hθe㼂sj4F*ɍcy? ʹ〣jNQ:a9ag p_@ijpx⺅dX,ĸ "j!}dDᑦMjHk$+U7&w}epzH}ZS>Hd2tQV'U[܏]w4dqXVQ^HSRwd.IoeXuvqYDT37^B0BʨL%2Q :Nt"ȹB2`e6l=fjWE3 "@{j7rt6j4so؛AF5UW]madzK#V`";@  @DprH}x_o#A҃8W"|Vºf-zfn0 A ;DGT^ʫwHq /Ȏhpw\Ȏ3%ON\aseO?{. N}^-:+~#r$b,raHQg'mA-ކ'};Ձ$R6HHہ'UI/<i<1qtOO )KsgbINDD"/D! %@; A^bFG(>?[r ˛R*w6{SDNu?E$7.G߇nգWE$G<<{ *y͍a08NTrqOBp2@z@#S޶sؔ A `0D/ 9ZA !GF*Dy/FCAl7O"0J#:my>by<Ĩ"PL{@"F륮btvDS]Sn0L%,m3Q4ge0 A !`w 9pb4<"c:y`=~$jʺݐ~ z)X>ʛ&gϦDK[F}7A@ܘA Pgݷo_ovA `0 CtDg `0 A `0$ 9A `0 !:"k'A `0 A `Ctd0 A !:1 A `0 A 20DGdme0 A `s-͙ A `0 h":Bmh 9xA `0 @ E.\snܿO)%IK/;ޟ<{F^Q).cZq.@%Gm#=0uB*ؼWc;Dqua6s(Ir|+V&0Q,Du>qdmx /~M `#8@r&zUdeyͽ;.eT"*X{S(o.Oحه@d!:uDu?, b>=}B(I_T!D'zS>Zn-[vs3A `0 XAt&m=~&['k+F5VKS7n?볗/uT¹CuRrt(^2p=+һsW%\-ovMs':Kv츒vI^MEhW%IJ8ַݼkQTN&:Ka=n&*Shl8d"&|k 1#{Yce [rkާ醊]՞Y5dX͛~7E/I'8 ` o]owFmћ%nGIDGp4ߣix?\,'Yo?ё'+f->W}}]wfHRj~ `0 A z#+SyBRyӲW_@g= 5lH%lH:? YקO`Ƒ ԩSlr={RdhzN.L_T kcGU(QSui6}@`8Wƌ'h5ⲽ{iҥbIAw:x\$lQ|>'VU.TjAb:с~jN6q5djLGy4wRg[koΡe$sN:"ho)ӑyf& x $\#MeIJamiңDpWKtlK9B=fֱK&@?{WgU_kiCiT.^Z@A7{ܙsϝ{/߰=QfKg+ }7OXUO0kcVǬmk:T'b\\@o&Yk>E<_'j?`Ѣ(`4 j;Ȕ#-G9 j8'B:nJa:r~c9_ow<gD!G_h.9c@)PǠsSv50000000j Bv(p0]n,u81b P)[ZX 8]qb&I%{cDtM4Dž4.y9{LR;cytF9d2vKCq@ DdDD;+zH, vHm Kx{ODZwa 2A<xUú#˪4$1_n6:7a%Qꎶ0R b#&ma=. ?J粃jO΍tUÌ"i֣<Ȍ )%\+d߶\dH=>rb1L7bƢd nkO~!( wخU}[/K a,uwܻMT1O!"u+Y3'秀o=&Kv 7wVh5"]pvT^]|b>%)M-˾~Nxqwҧ)`i;u^60000000x5!-hm?qB/KJ93b:p|l+@U;ck`UBbDz;FrTDQ .]]=l4yQ$n\AzIn?0f _.A[@-%eJqcyHtA~.Pyk֤{SֵX:@} ' Vyx+.f PuȚ*!cv0d/^ t_Z'b/ذ?+\r(󴟟l|¼sd>:z@KYj O6 < '|5O3ec8.f?p9}Lm<2]O65ZvA d;MЫ)Ͽ@:hh\[n4{7߾.ȲJXrog rw>$7D`d5 K2O %@- +ڬU;PT^!Ʒ4-B-ܵݝ*ky XxΝFFFFFFFF] s@EuʹH 9J Gk؉@Gȑ"'Fpă.u6lb*꘳};yo"&nMT<::ϘAF*)'Gʤ駴iCYPȥ (@5d5SWjԺ|yq[r4Džlc̴WAEfJ~7 t~䊓޿+_J~:V3 ql1zz))ά9CW]RPt`~:%ϛJt/AQcG/Ul_v^={`c6#xLPqx >'C:=M\#w 0$)|v uT=ּ ,7/β浯J|l|z#/|OuAػ&J@c*JK[x w**=,P =A qkԉzD 8\wXGX!T>Α%2&KFV:!]9wP ,;WԩW8WE:zHl7uhe "O<(s"JeIDOuTt\ sƱz}z@W#a }xB /7)(Óhm8op;s"FNTjˡUe>pJ##V@ؓz</PA( H2AC^{KpPe?fC7.#<4= /ՋkaxEMYnO%s<ᩪo:{-T>YS.;Ւ}ø #U,^څLP~nA qUOn;zQ$J ^xÀ 2*"r=@P,݌r@3>8w.S Z1DLlZ.A. l4Vh"*gN;rĽ-41JtJa 24 ,!p & B uX椤G &59*jkS&w"t mP\Xe$PWY?Ht$ п{Uaamkg݆w4 ;Jε%=p<_rۺVH` ֺxF%A§EUtaqttЅ{Q!&uoOI'TH]458/@82HQToq=A׷A 9&>/mi_҈#@ަy֬ZuTXKTxz)T'qSǥ.终(јӋOjJ h"ӈdS q3ݜ>}q9Uܙ?ŗ`!ӘDu`>NM@)QT|fM7%gV R%Vho>Zq-=OvlgMLNvthm;*D:,թ~-$ҕOt^S!!FT ( 4rn9Q^}Zu*к k.HK͛G9;2B1iH ATT]?l Qxw,"8$JtTRǰV`lV֓oo$ri9Z|.P{uDяÕ! @ D"ǚzW1_{=֜>MV,V,jο%ZsǼ`߿:.T2000000xg5V'd .8O"7M lmOw$"#Q>aq=j FČ*=2DZб8(y`KM2iSQnt xɓ=Ww tHԮM󰻫HpuUF }+һ7QuRU*܎, Щ3&d V Կ*X9s$Ǡ(# ~d:ߺ db鑨ё8ۙ?%ܓˉdh&xՖgc4 7Ҟ '@C cH08钃mrf@RȚ=xQm]0 yNW٘Ѹ2Q1ǙHZWD{;+5 z%D 6*c _5rS5gx{7snqb @&$u_ 䃙)E.}qe`>{rtoK՘5B!~ 䠓"ཋ{xt:xFPuL*MkA?H,Kְݙ~<(Ѣ86wTG3|y'P otJz\gŰ(PtQ$esџR<ȫ! @i/CemVYtaAt@@GݭtQe|*A@C\/"b#fQ$QU޿)'AsXxV塧\}I~;izζSdaOHE|3@/#OZlAQo_Iw5eDs>(BCL%P0+ɑ9cG~'u3I @DZ7)_Bm܅0=ыIӣ@Ǚk)SD7H0DA|XboCI[W K{i;@h@&3CTO{{ӱ˗)2[BW2U,D:P…A/ܙ02rj@~OV0صkEy;Hh@ a).C's΀3jRr㳎ۧojGaDqֿ:Wp@GHƪk$FmJ,_R<%".=/zɀ< শ p,5g" -zziJ<+ҕEhDc浀Q-qd% 2S"F@U)4Yb_E9[kyD$㨻b~#GVPhAlxWPeX7y9k(2 Ƀ)-]CT6b~2C2P"G@J֜[ -W'pOa`kmi<(r *t]Ss?SEȉ3{=(Ґ*zTޔ:8Ɵ7hRI舟.>uṾ ҲO5N"p';c?_ QV:NL:#3(9'FdNρ< 8z-xT [XBHxX?^ƥuB&Ks%$Ғ{~I k艬 UA` )=ᄝCdA tc,NH/UXK-!zLG-݀HiqXQ+X?0yɐ=m}2H \0QSS(2,Vh%v#2J>u 0 dTOx.sve- _XӷLݻU@A\5K*p(3@j̆рррррррр x+u877q ʥO/x]kqB$ q%[X|{pߨt?7+'D@ǩWԩ*Ѹ3͞MhuٸtiyLh 98fRbەEx>9}*M5[l NySY] {d'D7\ ǻK]_&A+Vg9 Ơuҍn~R*k NXC1F"A`d\ OəᝎH2qpǜpN@Z#;Û]}U*DUe qM z R@EDȚ/aC$ Q [&0IJ <ɐNf ude8{[#i, =GmO pT^1`d!@n WExZzW/:ri'ȍd?HE9'BY;([:Rhg•WhZFY+@ y4CԦqѝw(kpe@סNt ocʗN񫍴k.U'}(rȁ+dvە &'qdj&Y:b>SYT$ޗ=NH7T?}nG)W)elŷI!bPN:(E v(L$h<0~@Jq.E;E< K}IhPH 0" %K(Y~ o@xC{Z}S9G~A4+`y> !1s"m#wH%aJDя)L~@Xmw;~ -,;N<ٜ)})>q~Z /OCjgGI7O{N׬n @L5!>x.hHTS ?ˠހC tc+*s'G~T Dr#?] tG:ܶql+ ԰nt:_@HLm˿o<5DJ1LhqQ{Pp][ “2Y 6@[ 7@!*)n0%r 6H4:Ori^6J".z!d"mu\;Dx"*WGlcɛ.5,ULI9 ۷Sօ>.*hoHϺ5VH4tV3՝_Ww/%`ORy0|LbxJ<(c骒GHlΖx#OpXv=Ui@ <_}Jt0GXHWD7l}|TSISr%a'?FC16(\FL0Zl< ( `N@-%<v2k'\ǀOg TΉGK#)!>t9G>i'(5@IDAT F: X`oX(x yܖe6)v\i}T03U K$XS@(=VIBՔSl0&*'W @q n;d/q2wPSg:WyLc{yFM( Cc &%t(?xwa\HT"'Y du`MSW)J(熔B{;isЁr{ WǑD:*T[v(NY\1~Z$GBq]T,C..PU¹+:훸b$Au՛6,.OIS~zP$"*[լ[XE!S5#jDgݾzA)cA "~t!3%!Og<[?{% fX{EC HHg`1;Y(бSɑCDo|9H]1 3@L9hApA ~ Ap@6Xsέ@GpKR?\pVc97ֱUy2ϮϞQaDB̅[` cQKkGD wViSn1C_7y8T9@$eip9mD $poĝW"tVºf-zfm4`4`4`4`4`4`4`414!7y+o>x@͹DGBH;EGv(=ixt+ޡUyƊ@8w\z"H+ ˁ/TD zS]EHBpogu`0N8m֮L3G%!uc7(i0cp}}8HٙbݠuӾ"7n-K߇7E$G4Q!aHJz*y}rޔ~?/JjL/'푍zWscSn4`4`4`4`4`4`4`4vio250`!jDH@ + v:#:"9"\'Cx+@3a俯D bTTݯukg:5FF<,FVrTٵ9mQ<纯KDy~;"|40( C-qb{ $0t(@Հ"͕       [0@ǻu?  d*֑I߁K Kx= ]͍BUDN䒹s}!|Nc俩oc|K_xiQ° 'EL=@ܫ㾄F"`UӦррррррр[t7 hhhhhhhhhhh]Ҁ:ޥihhhhhhh 4`ӵ9ррррррррррр@0@G1jNUc             62000000xw4`w^+10000000000x5wsmxVрррррррр ᯊݸynjIġH'||.ݼI1F$Qd} 3tye4X4FKWJ1Š-{t? ӷ ӿуDZn?R%YCv':d B KN&J~[;sh8پF3ud/Ѧ*D?x<<^=}E;r5EIs& Ӛshyb3G!Fx9ѫD1cMNtH~k4%*Q)p$,继fN Dc34bժ`P4ih`^۩SKe<~|֨ض.=n=|Hق;Ͽiƍ)p GiD,Txqu&PweUr޶V(% j>C`.WHyLVy$MۼY*Rj.lBfo ׯ}TµERlm;~Ư[FǏ>rJ[~ ퟴeIDOu >:YifLHvlYxTxF&:"`ǥ*ܼFb}QJT1d7kՊ, x}Ohz"D9!J@)5~*|s{77u߆ItxQD?sԪeQ=ssdMܻ[E)@8`*QD-k&D <#>3t,Økx7ۂo `sϞkD*7UޟA8C"D'*1 pz?3[K^,V]0 p>㛏iDTuס - xn'E?Fhy0;ѡ;F*%W.X8f'EPhfi2w`8[Tgi5/W&KVrJŋŋڪHt5>nթj S \ lw&6 PEyGjܴhl|ౌq^+|M:}=&|g[:+o\*A)Q ~nQwq 5r]8(q%*((\h">V8fDI/߰=QfKg+ }7OXUO0kcƱ[$٭%dcm#Sg0FDJX-, ~ykᵇ42H1zQz9N<1;c3u@@J6>LW5Kb(Nb;eT1Om:1eKqL_O^V'ӵˏ%7E"?yB^mq#R@ǓiolUȦG[c P#GT8ce6& nժg:p&MPd4|@ƙ%E z)tK'w t@ 3)=;Oʖ0:N>Ev\IbR%='+g h#qa? h^/ᘧtD]E4_?G`oLDFĎKԸ3Qr}`4n!濰'>!DT~H.ïDkczW5+l?`JCF&$*[FAMz\:~>de՞U9骆DQǓGxŗ9h)SKVȂI|m.i2e$̞Mr9zd~!^BzmrO~!( VNAN \', ~f_b{gm y 3]9[@ַaKM;+4.u8n`*/.Mk1f|}Ƈe_?'Zysбi5_Zj0Sתl!"=| sGX]D(fa.Fˮ^hna(J7. y[7V3S ɒ2%и1<$ ? R޼zD5kR|Bch> ghV\@~WE7r^5sKU¤PxH}FskߺC yelXPU]挕].a._kYl6>wd9:zQ dC9(0myNj>f,=T@4.IܿWFɆ]].zl z5]a)Ͽqdum\fץ?YV K\ L3W<=g-9]~L4BhaQ @ ͗d>K,[@_VYI?m?"w$Coh.C[ ZZ{gk;]U;c3ux瀎›irFְwh# *DN]0umU1gv޲EMjݚuxtt13TRNI׋Oiӆ2D:KPLjJjG]ujh 72io'鯽1Wtf6ccSJS" YsB) m tuJ79^Ǝ_>8z*@yx)K,ʬ;>$0zy&$H oF1O^d '8)vu >."=alv&8(t>'JoC:Cp6q]0L@O~ *ہ2Ay%Yo^~ek_H;cl\G_ ' Cw?"LU ( "E[%(T Tf{X$A`~D0s9.BK|O1 ]hD`^ )=b`j },oۅ|?Z' 4I>I& tPŹo\3a$2'?h@uHdhad3}\O _ISmxÈF}-LTtz|U$hm!j]mhq$ϗ\\sx #qwϐ,s7:08_m7ŧSuO==ǿ$5 .'cպ;@ǺE<^wg0v0m#2hdzP txzc_ _qn:2"/6%콮#0!yD_r,%qU?nLE"/BdCWVG D+ ㇎2} @hOZL#qkԉzD 8\wXGX!T>Α%2&KFV:!]9wP ,;WԩW8WE:zHl7uhe "O<(s"JeIDOuTt\ s1Jb>]=pUx~u+-l辁^y~٩UY߇ eeZ.dZt j/zu+r D 8!8?~u?A&SED">Tu<~611[y*A>ВM%ȅ`w!?𘭞_BȂ8E;UFp sɨS;bGp[%P+Q%٘*&}NF$B  1:+"8%XW'ڮ6͠P[aF%^ 8qj5d/T~K݆9nVFF}iD#) d*Sk=I8꧷:-ln" D/w~2g@ >HGG^X⤌#.9,u.Fb9]}Yl<`d5_RD"N~$?5mW*n.jwR:Ё&cDK3Eʣ+S.ĶZ RӻpJ5igKLj94zJx vGF=PwA ֞ԝ(TBzq'kpd#2D(7~8l(X a m%[ V{A?"`)!;T IS9Qu^ԬW#rzbyW{uh¾}DxbtnO OYoN u@sDy/)bǦ&8)3qX۫ cbȅtI|; Ǜp+ddE2e^^T C r6X t_{|*Z,<Vdlx8 R.W.W XŠȨ_~uVH KYDI &hQB6xmǿ(Yjϭ-ÓhUUtpԑ3#_|TsfMzr O2\4Y?'[T^>yICƕS&-j\봎$X\BݤIڵ+b NUc L`$,F]'W8~T 8h>.T3e~W^I@c=6j(`Vs|MYr0ϝ|r|N!zK.2"`D% CDq#FVbx Dul0ӽUcB>7_Pe؉~oNķr(\#x2x0:+׫S /_A#D>"*gN;rĽ-41JtJa 24 ,!p & B uX椤G &E@ڔI)}~!=]s0r~sٮ?i3#2? 8_i8ՋR+uJ 5$I?ϔ%ۮ9@UgJ3XH. W (ArIyҳPm)Y"K# =ި]UYjML{ZOA6r,p1`̟&R01֙UT@Q.}((=\'QUNIH p/CfG9s!BQ=\ ^r/kp4ӧ_ YrY#PxmM7%gV R%Vho>Zq-=OvlgMLNvthm;*D:,թ~-$ҕf@ާCʫCbPjAi܆s49Tu/\( Vuϛ7sn5wd;DcjӐQ!~&XXxr nu]› :*nKjOIO\m _tdFcPZ@WD2SPoqP2HATH~?d埒F2i4 |A j˳1sN iO `U1c$q[t69D3XsO w)dMe{rtoK՘y0B'9ꩈ&x"<$A]@,udH 5CEX BARXrvݙ~"TD~{czGu$>|g8q} v@GuV 5@OR?IK,cP[ tܿ|x!Dw_ (۬r >JՁV[a颏*T|^TEF12I:U%SOa]>;?N^ZAzg~sQ$ K9FOGo_U"~?"?j;+"jˈ *|DQ>K*aVp#s ?NgY f7       x+c7oR.ڸ %9Za{wGAO3׮gSn`ƃ:|}iɓůL9>9wHXLf*ףc/SddJXhtz i32^ع3aeբ'r`ݱk׊v|?Uс9 R]' .8NTS5gԤ|8gOO&1ˆuU#>t%KԈB+XN׼$gwk lrixkh[oP!|j3a,VGlQ1jݹ|{!4Lݱ_$H+fSťUgw|Rn 4J|Z}ks,H"A"8H83Vr}gtBdf'mv2l2BZ#+ǛU}YUhL`͙~5=H&AR[&p!4) F2ـ\0ٸCMQ{_aos{"U0>ŲSD WSy )ǀO/"u֡aȟP7Km 3 (uj (~]:'rviS鰆CbEhW]If{[bt6K5fԠQYGѝsw({ToY@mO9 qBMz9dU'=)z*M1rʐ &4k{_9ϑ!9 B'I-$,䛋ҤJ XMhSȑamڴ!)5с`xH?4Tf>;_ O59maY#9)<uZQ$j#/k0YWG eK}%_z{ Gz%#?a:Ɉ ыcmu!מUԁ/v`(|Ʒ2DgJ2"x5׻yN(Uk'ZԈGz 49Ї#ie,C0#c_Iv֠k (%>AA MF #V:(mNGfήL 2Wu+aH#[|H$kSHb:_rTuCeR },9<ֽŚŶŽT#[LS9(3;t[g՗f_82 :Q&hcݏQ_i*٫wEDo66ň^>yiBڪwn2Y3'YXǩn1xQ` J/tcs%qj: U2W 2dpe=5Zq qeYѢPCt!5[z"z(|w̥fYCc,S{٢ >^y "0U:3wRI\=[:BDYigA>7a,0"9dY9mjsԘTV)ai;ceF@#h4OtHymTK9c٩S o>hB%wePq-zX}H,ڽ{wL|gX\5Itlv~^؆&?Hc'9gS_fS]Gc0cFWeO m(cJtkjCjڙUghv%1$7GTkv-{.Ag m :<=֐bI4"'v~/H(8KSxaaD$6 0: 9JNo+zP1+t(L?i= )⊩jj**1SŏP41QZ>6J4SpL]ʣy:DP !RnItl:}'8Tgafv ,C*͑Zyx,'uÁ83p?e9_zOۢL?p:padz/VO!@߮DRzV@ a2ՔGPďivme'WAR׆#vƉ^q&}sOHtY6SyLcyFM\@AĦwC9IXF]2iT?xwE,Ru+uT^6DG1ŠzKQ2 Qwƞ|&t-NAH[8!k7r[JtT(7i[ĸ#2|z!$AqդA *1+:<$CRroN߂i\=R$"*1_q;]| +w<o>Ko:FzeKr؆Zb=*#S1hc4sDž|$I!5  $ҢOhB!0$a ݝҮX)jiDo|OE)ai Foh4F@#0DCEYyN4J 8$ 8c+_ DGpK&li`uXͨ5dk\ggϨA{ RAǢyrƍD645o) Jt+pޮ69A yh'L]g4cMtL(0.dž t9 ;TksHGᱪ}߈~#Ci Gz$i c8਄@udb]Nc;iҗH $ Bڈ ӭj.CFC4Ds8Kax& h DFClE=y[ B$ H'8m)ʵs8R('VYֈĐ d H4c ˴IpL"}1!G=y<~(Ϲ!̆{B6q̵& E?)DrM P 4K`zI%R!mWHMK'J%vx&d}SG-hmxVUCܗa2{0<2gO?;ia*gLma9ͪ4pc=nhR{SDǁhE>̓fQb5ҢXioȍ*3~L#$ hn</"CoD?v߳{hkFw}' ׽K檝j1vwk?$#1ʹ-"ɐ/fD7W[֫e{%a-[ɄͿQ |;m%D9V ' ht}Y[T~2,S^~'OI 63aa^uYO5F@#h0_mLJ6:XGTƍM~q%D{,c= !hXr="lW::G,OΑ%d1G Q}|Hl- XֱhlaNF& \@ TCoo#Bd|&G[:T@Yl8:?GHk\ʚH2lJCw/#A!9抡]J\l/^ʖ+!#]*Үa1{ S8a!Ou!{~K7Oޤ*Py?*?<i_(dc)q,tЁdb('jmoZƣ8Xǎ4V#D+In F&R@G{+CF&˱i>'?(W#xw@\tQCﻏToE/*s2;ٴn! {L~GJD0eeG:}mhtlpfͺ%_ ybD @H篝:|Qfp- o\IqU\:@Hy~1;vZpڽ)? bN*Qξ9jƱy ־〣jNE:a9ae p_@iCjpxgX,ʸ "l!iDDᑦM>\;Do`$j7k"B^4JThRd2%  gOĨ{Q iW 衟Qmz5ZrqXVUncY I2' F J:-gVrcFC;67 79jDEܽp_.d ,JW45 DGM+I*M4=`lW bD{dp0Uq2UsۤPsK5޴"Pod?-o S;S\N{&[ \;*A$w3T!BCD@48-|!\z N$9Nīḧ́y]\n^\#h4F j %7< f-1b")RȎhpw\Ȏ S&JD\a[e/d.ZD:V@G 'H(CsBE3. Drp?W tRs \qA7<i\1q۫qOW YKs#R6+}:8A/9y2dw@6m>#g bC ;vCoP))iona}ϦǨ:hۧo %+扛F$G<ZF !GFJDx/FCAT7W"0#:"!Fk3G"A*:s":rF r")aiȉ>+F@#h4QMtDkX#x`- !nH?O/Deط*ٽrԫTu= H>Cɭ'FziϨ=Ǝ2MtަxJXx.jR#h4@G@3h"|XdF~Spn9נR}./gVQF  0# \B^sAϺ(!ZE̝Q\ˣDkJ>e, *C R+ 6}`Zj%Aܫ^qm@B@u>}tF@#h4F@#DGd\4F@#;4F@#h4F 48zF@#h4-谅Foh4F@#h4D@aޗF@#h4ȃ&:"ϵgh4F@#h"4pWkv,}F@#h4h#7p1%%H@}0{5]yČI)%PvvY K+Ǝw/8P&\fѹcD3iSGbX6@BIXcE-$:}(U::-<띛DFU!s_GmX,>@TSa+v(wܔ2Wʰحއ@X':{DSD/D|^<'z(nпރ oFD\&wO OۙMk4F@#DLѱY|yW(Ws:F'ҳ˂ĉiPƲyzwR˖~>MXv>ڃȝ!}-)V> ۛCaEfKu+-߿(#Mo&ǡC4g΀-˖b|LfqMڸ(^0U+T\^/M߲d/^n#//Uϑ#'h5A-+_>͙3*mUUo>J-;.TdM5gJ%)?ުJeaq%Rݽkt#Qd׈L%:Wa=FLLى*5` ;V#\-[h"|hoD;!d Qů|nْ0qn :(v\W{| :#zx Ӄgm+fvS7n:D5_mG|:gUQ9nÝx6;9m=!j^6gIޯ/VƟZ̟.d!Q8D}׃*VBDL46lwq)%̖ǹꜱDۊ2-z:o7ӐC]՜]3dHͻw^N@cAC:ԍod&ZD % 8(|pcZ7͊}k D-?Y7Pйҟ` J33O=%,zM{w+]}m[nCʁ|3v,] i jde/b3osMKRC,|/^܇$*{ٗq3ݻdƗDȚ]})pyh:Y3ٺQ&l@&~~Z~Dkihhlצ\su8Qr'A`\ qc{xXz޵'A; -mT"o28rIDžXC\FD~ g f3GO={-DK)OS :p9 \gH: r1C$G<"yX=ӈB7gqj{<J4e82L0ѹ6mUQD.mޤ/ެK4aKX{,ظOa&u+3'ԈE}6Qf?{i1q ͽF%|A۱&Gm ~a"_ѡD'4^b)xJXxNW5F@#h"6Q0_ѶGm?Pr0Ӈ^^NUgP#V<}_=$&CǖǩL<Q **"Q$I3cG#U=lޮ]4~0Hn>Hr/ԉQ*Z cFyѱ eIC5h@ YD5xÀb5ժQ> ^ aAֹűKt FOO8h#1Zq7:VGEV#VI4 5"=+,Rmy9Q۪b"vGP=*ؑhHtCܧr8} &Rrupu 1rx=/&!gxW1b}S4H'=L=&QtV>R]7vtp킨67^M>!-w2H TCĊ9<v~n};KL@Yf{?5aLؕ{ =ѷ_CTޢi~dQH NX )H-H_fE%|@! )/Lo|;2MA+S,vwwwz<%,8=ή:KW^SSt`~:{q)f'W^9NUTf^qXCg.2Ut 3a8DU1~܎O^$tOй8S>m'";|SXLP DI-{7CFHN1!ٕ/8  p@=knŵ5He'!4ryʈ21O}N|Su~ID,@#}L4PHU9_:H{ Lm i0 'I0mB2 櫁{Ѱ)ˈe<"\bYNՔalQ3xrZn^ƨvӸ/9q37k73'q.mD7OܤRP3S<)-5:^Kן؉cS´ дCSY+d OЩe(ZhDDڀ|ƻixЮ&ߺY=`;v逛`Ze* Sv5F@#h"0xɿV$&8֝?O"GLZҸCte D l"\fEt@\|wh+n>DNUmZf1F~JM?,`*k#֯I"͎)cTRZE /꾫4z##M~RJ'\r2GѝwhLfό/?|B1W8u&H/Ił#XCѱf 698w;r8ն/jͩފT (6`$-F+Ύ-RS8ڈX𥳐uqn#.Ob";W'ߑ3b!QYޏ&ts[D\ $2vDyew@ me *DR:jn7GO#BhkcDd Qp`bS8]b 5(- M{3UI|qlmRZ\S7 r d id$ PIq!% ˧o%*XEiIg04=? 4~ȥo<=U0 {@[njd$Lh,)-\K;1sקpy1AlގuK9ZYX5,!CA1whҿmW uT; ̦y7\lC@\ 5g F]j)+(Z ̗ۃ7є2`T_k>0u֙b嗓|ҴϦW-UV%hƲ:TojT,_}_Lw/n$Py(m-}&x6wGbP&#N͐}%O|oH>Ofm"yP KuB [YTdS>~spQdZ.c`D lLcw+^{ZxE㛲B֬- bu;qrF@#h4@&:αd&7:DY[(AGXv#G+%eEt/Lx%KF?׮mhTÕuw?%F}ԡ9sRaÌUYR"Vvm. 숎}Q3:#᪩D΁EcOgfNFn2i~^J5قҒeKFN@trq10wfW4N@{,뼎^G3hiTf!,ToeRЭe|+={lڀh֕x$.N_BEJDi)F"%gQ0B4=Hcé.%5GSDFE*,Muʧ$Hg!OL.+0i)`a g J\l3Qj>s9R= HlC ¹߬Q^(i ^5_c*"r!SR/1 ;}::wnx"&B6,$<ҸS 2=2O־HiS ;QDp@Ut 9t2Ñ.:Jrp*ya*)q]Ł.ȺDV Aj+,#z Q\ҌQ֨#?bAj]^sl ᙣD/˄ b ށ7;\DF44$"dz&~]=НoqmQ z謣pg4#u} %H8<XCQ*7tecA#,+ai_eHd!dfyAuaժ&K :PT*i=Nt:>{h_nx9*ڱ,'.m J3mHNj94zMyة9;v@Mܽ*]fZ}&PwA ֝ ڋ_;SཅA=(ChEHO [8a8&<p,'ami5kHAÐ1IDZLD=7_;hgWvkИ{hƃ2r<%,3!`.g|ʣ\ޭ⃗/Ռl|JX6sF s޵fGt,gRdʕFeRōeW&*\={R,؆жJ{/J3kD7#;o>jӪѓ[OhpF\urQy哗4 ù`Mz_6j9Oִ_C{F X^JL0&¬ Lzl38 KStuT8~4TPui* Ԁx8a.%Fi$A"؆Q翲F}}`tDӇH,s `9v /֑J pc%Cv&s#NV8b!/E>װL/S%-g"U,|U-GU8G.rV~e|Ug Fb/ J p3chRvGibD,)DP6Y=­ǎ&˃L@TH\u@HԿ p/QQCM4^gk#$l - 2d, N$pRCt hht  mCDDz?-3h8aV"jl/uBl|.wHTQV=){ 0̈D&[Pb`а/v^cuۜhC)>HaBD"͍m4[4Pvui!GdA/ߧLeo6}n_Ґ>l!UZ*сTXv73靧4ɃKH/t4P[>d#-%t5ڄJXM(8N#ꔪƦo9ɓe5X}hbA[T5|U#Z%IFF Ӂǎr,yU{ .Z׼ $$~ϵ1pTGDTnJwDZpn+ ;M3ݹߏ^NSy`ڬGcM+ew2gV8 ~I((ƚ9:/=_W=kΜ!*^TbKtZ$=kԠyy8*uU 1=l.?&#:*HZMj11DO+ι[ Կ,ɱ}"HiN^bHu|kvňD5?9R }Ntg9/Y "IFc;FBo"(è G{)!iNM~i(' C9JZN!=EMF#0b$@Ȁnkqs8ʠsb?„_F~L%E$7hG @z%D @8tEvd=))wSkTǛcG-½L o@2AހR"ї{GiGĐ{KjjS#/Ԟ1Hr'#0zx bt:xq(ɘ5I:rfo~o"E%3i%;3*%#ZvŎvrKϷq=^~+#^x:&I ~oRcº ){ ] PKtܿrFx DwSJT*饧8v5#aGp[=l7aQq51 !CD*|0ASaI=7G5~sվAwKMkֳ[s>ulM]|f D*⽉T0bU_)"ỌȠB{nQh "`XM|S9b; c &:jh4F@#D͛_Ph!<iz%8{};a !4!-mח:E8"C`u9SKNdgH#b6S1|=[oo:~ EgO2NiG"Zs\AЁv32t 'OpݑWz@USq4ߤݻtp`*9SզV}4>sTR|N徂#:Brra96ĶaRXY?Y%:%R-F[c\)Ҩpp s&Gùd25ăAɴ[2Y|C:($ֻ9 z^B 髂3 E+>)\ - is@i.9ˑ( 3mXƨkr:K28>" BfSun@P /*9ˍc7h\qFё8cb蜣ENƩevUDѱi2,:́4S7ndAeJ8R/}!+?AV`d`HZe7&Id#hȐalM>z )Ώ\ULܕAj[w>HW9@)nr!pOPd:_D矀S2L@F@#h4KDik9eco8T#Kg+Y3=%:"&9Sлh]l 3` I&U@t , =iWY]7aGtdV&k-jX9Fo9k4>xcrdFBPK0*ؘqUP |W(]-bTIpD … r7.͓rj$|ĚZq(IrzhP,-{  g%ej8K'$X"pm[UӉhZn^ } sd2Rp0 p N[W26H#ZtQ`KBT z*HIv&bAʶH4[%/vr|!:.?)Q @*u㈗B!-j7EdW*yA/VB=^h7FQ>K1aD O༔Uֹh=-`Ai5$j>Q=vρUONvD6s)LsDYA|BR"o>N&q#VHF#"Cz,l^yqpVt%aeBu1`Dks@vl.Go[H R'~0N/?Ms U䒓4W/#z4q8G-&N4^BvDZu4X6(Y&ohtFt \rrt͸͍eW&aMt<&_slf#OÛXC[܌zCSk gyzl(#6Eh A0UDC$&L+6 za]~#j,u1"d}iS鰆CbEhW]If{[bt6K5fԠQYGѝsw({ToY@mO9 qBMz9dU'=)z*M1rʐ &K'WsdtlNahI/aR !梽4eB2*$WД2G[xؼDԷ58*Q8j^#\|Anl'f/4 h1rh4F@#:萧qM:pVqt%`:rQ…)CHԩtì+cFtr3iHLtP* s蘲y3ljP`AJ.fceݐikWsg/ b^HO7V8 T=rA`۴6m+E ,kaMt$@/0O! FΗw=c /Crq[iaqHF;AD GdHL%Cy014TSI Wj},|W%~,j8O?a:Ɉ DىѶ(2xYEۚA`|{x3Fc4!J8TII#GDޥfKMRjQ#"%CiP @I~#'=rYӑ :'i4j2A9mu:2vvejPP[#D Cb$.@:vuڢ,嘪7aJ4eЮ" t>`!1DֳZvHi 7[TGj *:ӌwݎ QEZ4yV1GSor}h_ E ԾeM2%D<ܯk4F@#Dl"$ѡBi:DE%ʧT x9D^&(2Qa6ItHcQq"0ф5G?N''m]Jt4w:Ϲm# ,.]J0V02RLtNɓc1TNGߧuisQ}K cgw/JӅlj.BŽAxrH]uq/'V%t#UW(naօC MKηVb ;%o*<`ҖU@HDi(8)!, 砦j#*O|ˏe)GC"x*~ kJ"cI}hObIJnC*"Y{DnH(ϸTڮV CH5HN65׻ݏ&8:^:E Ϧ5tB& FbluNclʢy |AkٶR0qDGYVyy&; YufWCrsTAf2ȎDpc )FpLC/IbIQɊ"F|OD2a;P* Ӏ:p#ݏꬄD u9-pEׯ3bh7@+D{`T,F+:ǫ5>rLrliLjp((m&A`*%L1u* p`_[Rm!4A@ʦcq5Lt]z,<\8NC%:9U+>/l_s608u8@13^%(i'~\G9B LU)UxCJ!LR=LcP:(q\ yBjp8 4ndb ).3;f6/ rl7?ψ\³kR(HslL稔R^MD]8 צR&%?xw:XYY d)⎺S:*X/Xh"bP(s̆(;hcO>H{Di}-ֈԐK^n9ȭL%:@*Y-7tDיO6!(B;hq!U%7^={EםcR$qZj0]-b[2'#pم&SBU"g 5{^GԈZϼwɑR~Y{\KG%|d*09m &tZ:9$)F!;wa4ރDZ4 M(DB1 =B xs_r+7E͜9O8i `FF@#h4@!:Top%O L' #:ruOLtt^jr˖]]WL܌ZqR?+{U4tsV*P HXԔ#O.޸НF#?E!>ta#cCiҽp " &ؐ_.magjmH<v"M'?H{ JsXHO1 J'eXQۧ2j(NHƉT5mf¼.+벞k4F@#D "%atۘm"%u =KXzfCرt{Dآuu Y矴#Ke/bFNU &d[6d c>N(MAxA<6F)ԅLuP6ptJNָ5eʕ#^V-5Gp;CsCN*R5:_-W!CFz 4x7ƻT]¶c§pHCʍgoSuaC9nIU&Ue H~T~dy*ҾQɘЭS> (Yp%ĞQ6Oզ0)޴G q#Zi2: 3FcIn F&R@G{+CF˱i>'?(W#xw@\tQCﻏToE/*s2;ٴn! {EJD0eeG:}mhtlpfͺ%_ y-29 vb={"DM µ&pAs&UsI@ܰ[;-h(uo gJq^ξ9jƱy ־〣jNE:a9ae p_@iCjpxgX,ʸ "l!iDDᑦl ,U`"!]Xhr?sz*)2 ̒ڳ'bԽ(˴qqݨ6M-98,D+g$CIy;>N oXy ͣsQw/ܥ{ ق":@@' )MGw3hc*t{@ mJS0eio;CbD{dp0Uq2UsۤPsK5޴"Jod?-o S;S\N{&[ \;*AX9 5:G zM߳il= r N-*ZLXuYO5F@#hQKym#!"숌w5Q qX=eD;UKAvѡoEc)Ο#Sg (TD ^] h B$wA6\ݺH'(*8 %'AZ@]10NzcހɄ;Av>"A\5Z~tV8%iňT|FDG 6#˳G1!o!7(e4Kҷp}gӌcTXJtzˈӷsatM##azQ9iHJd+F@#h4QMtDkX#x)ZuNԨXS^'zx2DވhQJU@h{;xr뉑^A3jqHA&:BGoD <%,,2A#?wj8k)gf޳lr3bX"2Ka@} M9SmM(^p4!s@ESvQi}F@#h"\SF@# ރHDek Hpjr ع}g]APUmzy} q|.v"?Nz5%J2He-M`Zj%Aܫ^qm@B@u>}tF@#h4F@#DGd\4F@#;4F@#h4F@#DH4!/>hF@#eDGD5F@#h4F@#x&:KWv_x)QvEn%~'Y$G c Dca4zp(~"H|+#:w(uZ:m >Q+&]h">L*QޝDFU!s_GmX,>@TSa+v(wܔ2Wʰحއ@X':{DSD/ܽE4sJ#Y50!79?OxD?M\K{Svzh4E J{Ξ!˗l:uz-&Ng/_%NL׷lzwR˖;Ի~>MXv>6ȝ!}-)V67nkoo_]Y(/5+]:PU[iFy1hz6?9;wmY,c2ێShƍFq…ZB*i-'c}ucpnyyQ"Ex8A׬ Xj]iΜAU mڬ}QlɨvҧU'kگ=P,IVU- c-?HE':"`KVyhT{c$ʔRId7p䕭A4b=yD47k|ݿ(Y*%>FTkQ2nx,qn :(v\W{Ԍ2kYD۸&́ۢ ip4_mG|:gUQ9nÝxvo?pe>)/M۵z%9kDYHX=ףpe~\i!&p;8@e&* ?|ʜDޯd-w1DfkKjz"/A9cҙ.n߂w>*x;Jj :qtU|={BtcZ/;#I6d$ h.Z[V"JE#s7V3ppOb+ߔ=3?тVZE=yYuBj4F(Atl:v.\hٻ p+-C Htҡ4* (%.in.- yg}f3'1Μݳf̞PRs11X%G|BEM@:k@ʬeq8i=zYkW3&]q_S@:K xH:VymyOݴHȖ& gnepdnL.Xɖ*=fϦie6FK |B5jP g"j jY8nE$*^*N5=[(\[/Y9B.9R' }KG#T xgDW!ݰD#DǸ5s=mb^PO0z,{y6>?؆0Gc&9Nyi௸^ V\Œ|>&yV{ݚ-dk@.ƠPrj@ )F>HBXQ8㭽,TRJ A##ڡOT~{7|s,icG,\yGjk-QoΕ;48`Rκ|:;< Dat{'?6xDz?QEyn׹QcT/Y]w#vQ̙}=_)P%Ο>Ua_ee=v~v XحȜ8p8p}؜>Ug@VOeSE)~~ĉB<ھƈf!Wf@Ǐt&e0溰e˴-n޽KٶM+nV?a=H̍Y.z#~lwDLjͿaM[3mi']@@" e?`%Ygwk`EJbV P "Pj}`3iGQ/r.ej݈2@ VV)(Y_/*b{? #ΜyZ!Adb",ee- sllJ, Pi_D !Aʪ@-rgV?iO_TU =#d1dtID;pO2(_q1O[W[C8d|d:46s >02PDqg&9;2UMuQ skmn @[}R'ofA < ApMV)H[+y<3Gx5 aG@ul c2w:&bo+];$fEQ{ Ba`E\?iQV%=eaؾ̂,c]@f}!Y0V]>AQ!@Y҇NI"gu00?z]yxv)pa5- . NselqZ#7=:|RwlYPE1&X0’Q_`TE \oYs^s vՠaI:n+Z;6=ZAq+g#"?yNf} ks%Mum]2}˾]n=_q8pqc]gHIJ=[k@Gh4T*}Ȯnpjv]XS6nkjiZ X]%;@G h?Ja2j 1-[RV#$@biH)Iu`U JiY0jgi4d D&W/ P*eZoJW<\z.D гGhoꁫ;oRN)FjGJTfm"x |.,= ;Qny {NXGmtk)G?]#vEa 4 /Z0"Ƹ "| ?βc^ T;}Í &=Ƈ6 I0[ I%[@V `LȿpZ\,qHs,|Sd;ĵ5OQ~G|aE.چ V0/)00S?:R3zF9y"\œÜ|VÒ }$)j_Bj~f!u$@fA[c?OѹgԘAw6$P)肣!<=/g,E tѣ;\$d;YKlXCб|l utaZ 7Xfdj@x T}_ 0`v-L%Ū@5Ŕڔ1PKD25Ĺ_I Wƺ,Ȫ>=p! 82Jkw h[ ceZn HGn5n `RpU%!Cx&6M X+i b>b:ܟ `v` <4s!Zo AX<Vu_}YxR"2'O1nIO%=I}fpȊ.a2ȅu$|!=C4yx|e4}є` efrH(砙HB@9\}}=@?U<ÌdqIY@e<{5<@FUciWh {ʿE F~`pV@Tx#i>3~(ѣP(mѴZ▋5 W;67%k%=JMkN,;7jLs L9]G>E^[T(QDO(EJ؅R.+i.PE s kw`-5 @hXJ޷3's()I&R3n~l-uۀ0FEC6U盿kмS$?e+n!#c=kWw{ES?=]n=Ocq8p8pထ/5qlcpc}tbmɒ/@MǾ@:tx?դбl^%[Li&^uj1*\XG-Xл^=*#2Ds_)eJUycPAV@SԩZŊTW30' ?L{}4%};)3kXJLI$6CrU?x5W>oMw_&)\Bvcge&$5ϣagٚUVg΁?@f$AxY =qa[y_VnP.nw*ІxڈzDpnuNddl<(֘ɢ6#[uL[E߹`:aE·AxF֎Dħ0V2}#&,8L@|/4p[p]%x+ZGD bs ? h,9K?*ZYm\+" YuztCr V? JY@`JކST BzkVcCM-PXV.0ȼ%V7k6Qk4ye7::2@x?u|e)5ѢR_*iyG [.hVlTmJo`Wtfjy& 4L#Ju+E0I+N`94V'^x>=Qiztb - ֯Ykd(ѢЉ'4+Yd$W^+3~xZJC'5sXBTwTшL ٭M*A]ހw X$H0x@##?a -~`w A I~X:s%V}G œ[)Rl4,|{MҸ uGBX]$,;s(RH KY(y})PK,U[ # |[PmC,nfˎ\gxBc;Ҿ%k áY5w-"a!fEV5Qk|9رCYv£9+Lf`fd[l N8Kt H.T_sIugե'P}5PfR+zp.o3K.QZ?ݤuЁ0fFЪLJ;Rк@e0 :UA5zv2GB( `$I *<7=v=\繓WC>;HYk6R?~ X*@7LBXK   ڄj"]%1&d]8 !D0#ښ1ց& a]yx0b>nG~'-~+ 5\GXL|CVjU X T4\A( 0>D3@\B&TRp -^4a.BxXĵs#qdۯ.lj l:5V J\X 4UAE̊`ekPE q侅  EIR@iF?*{|_X\%}!q\baَ3 1Fފ؉h` $U DTV4!A< A!1[9: @*5+o'4$b.-oJ1КBm@LV@G)Q@"*dHLٓkn6LiZhy-캚+5߸KE-XtCSP:[\sm{-%`RbF w/jGFWYt_LӪLӎ ,@UGV@$Jvntxa#*>ܚ`aB7n _v쑀ɟy@(vyO̧Et J$4{{z:쮃VC !wmFs ';Hqf=(R.>TXLRdN98.p8V_|A_gkVb݀u72IpaqqdTjeogY6U5 ՟w ʃTG3N 5usի4F}O4kۗ5U ̽&eȆCXIZ,hbFCvvצsXIЎZ:ce!X˾o皨?'5cRƍKM;[ydfـ]n=98 CvT*q2`Q?wnJlb5:p4m[2 @yzXgX[Hp 581b\yq  &ÇT.5ɐFt3ś:ѹF KCEP@]# g e5ຕ:,T_Ba!m$2~E@>qnjFudh=TFGc9j矴ot`OE,Crw8pÁ/k4+$e@ߍٍT#5+h)ĻhǍ;w.Vw ǛDPĉi V+ 8PjnD$]V 7 8ʱM|)ZTGbtle֮ӧkzld?<ЗP\4aŤ}@@Јllq>:4hL1tePv  qb+f 6_wwE RͷeK#n$+aLӻ[LQҝhXC]ٸҏ6Rft-Юު(cvֶ="- ^ H-\@<'3F |5\@eɏ}OIe]}}k-40:wFW)C)66 |sfyQua2ֿ@G8xM6њnk*/vi dE iO6ƍtOr ݌lZ^\s U)Cp 5EhA5U?0nOuIo`mXB/(+[;Z/`Pdn*p>s;QV  6h³N*ͨ<Cz Yꉃz#^݇"D2=M{$(4L jؿqMo}B5u 3/Oe3XV!Z"?o̠'8+(RUgT*>as/ݺ>jo:~HּH+=-4{oYOP+{OQp _5OcX-?zICBdJIo![Mm\m.ܡ^JoϠC#kxr"hs!diR0މ!oudoz2˔e0=73<Û1Ѻr_TkHL>Xe3jϠgOzc|3>R-Z`}lݼAAol)}.n ڲJ߻E:Ho%(o[ѾB-]f7 D*Yr>-![T#?vܰT?w4}-,c=' ,;LD[;zX4,$w+jy?r& 4Y`:F hD6awttm8#HݵQg6)cIMFџ YB%o oœ}eRKukm/feV-ZロH"Q嶭TA&\+kEn;"C?[CHo4=HKO7ۓ&eE*C ʊTa؎VKqO+KUH'c9믷ky=z{?mkz Yox} xf٣ h4ue9c=c8VA-VE[b> oZͯ]y_!ųF6%pI~T-L qi.ٙvr|v֦a&ASXhg_P/"GRhl$^3Lr:g8u*ܰ]n=_)plKmԄ}h=tV1sGk0^h\WNQS /`U9hס .*ݾMM9ȽG(}d4+_RjJ#IA2@H~]T[# ~}['7_GG/_HVtΜ~cͭR=yA tI N|w" bj8Ŏ&C\%kVkE*cŭ|sKmU?ܾKWR[qo 9Y~65VQ}//u>xAla[\UX^t(S#W0bč/ܩHk8.HWIpd$W2nopn#2*g&~OnnЎ/۪z"By2O'Ǔ&@Uj Dx s BYv>ℌS>Zy 7@jյHvT݉+7f7]s "@)#h5\ta&)Xq.?1}y75#4dT]åHmݮ77M|-+Ê懯$]Qh ?/ Ub,1bs[/50H5ߪ; T<'4ܗ^nV›wgt|C ~xJ_ڭOΧSbq<{L[;XD;u<%ȕ=FV"Q%ϑܕD VqҧOﱌ̙9͗Ԩ"ք:2R N͸nAnw$͵ZGK?x-mD^c78}% g|=@RqpE)ߓ )_ڧk88dkGՍ⠙"Ɖ(Ż?窯aٟ+mmk;E \섈)}oau rvڎ/u̡G.[ QRčJ7 *nxKtȓucw礪ǎQԋ/}P\٭*~,'jټڄkݡCk\&#݌1lBs޽{'C_$eMnsl>7n u\*QUހ-]N,=AӫMגFu!7$#="vǪoD}?ⲋxXٓq! p*!*oZ7G3&z5 u6y{c;W-*n`KڷHC0ӈW#t%{ܑ(T Y^k&#O {EPYA|">MsߌJLzx ϛ3N<F0_*P)VC̑@^}'m@@/>'[n|va@؏A孄9&F-#d_ = W Q6l@A 0.NCDwk4(ӕNZ0r:*ኆ?u|bT4Y.@T\ fO?S)c2[[!P㵍 qU'mY *A4usV*'BR{<fCX9JCܬLN@PUbߗm 4nžܪq"?vyW~V#Js.zx=q|&["k%\C,x1am2֙fdgY}6hoM'}}W TZšJfk=km@b0G2nd[뀜8p8p@+t(D#0ϑLkYb`D0H =fvkte`>bE Ͱ,`jR3<|H5I fDT@@P[K*WsF5.+]w tG],se= M0άҪ1j2oi2B#a t)0F \.SMcFTj}5%RcUۊ XYiӌVQ?p > P %r  d, Pc&a"/ RL"(G!hC&AjݝpOjɮ 4Q $!ZQOr36+}qtpW77,bj#'YGƵ%@4'T@p .>ǯYC{"  "Mj*$ !OcA/L!;s,@z8V6,9 y#Ko°JX;ˬiji|!X'Fk‡z) @޿z猑}uj~ "PtfZ~uWžq+5n2>|!X٭1*%NUԐ{5X!I}V{ <1j1[H,N"s WdәghZi5Ƃ ܏>GiӇ|c0б{nZjևq|KsJW,1u ~1(ՙ7@B#Sjf8D {%_.qemt {*5Pdu #v ɧ) QaU [MJ}{:u3@"hV~ܬyj yT܅ˁ 2?2oXR4eQbE\넢 ֮xG:ت48ƚqQC<=;a96& ;Ey.yfZaC{MԶo4$vloE򍀅X6e9%^IȠ Jt728U/MhYr  @79._LQkbkWقd޶m-K8&k%GF5 O!PT`ܿ_kcnǎA.^@Zhzx͍6(]]*Tpam_ݬf:EeȒ%)*bHK W9sPcjZg%o>PQH"#vPl諣_'oPɵ靆h#sGSjPYse<2i[UD11O\;δo ɍZ4!kEkwMpt k dSHk"\_QX5@`pk,8nJw~z7ʞ?#z7\{@- TmwhC@**W%vY[ۡQ+&Dpu9-4п 8;QknXmpRhZi:@k9En NRGs#nGZIZزoO F-!*YɺЅV, n`@[}ϠU!̅u:KfxK D{Ľ!e#H`k{Bv6iCd4V o*3gA[ SZ;_]>'2 K@ }갰RsF8`p." ᣷zv[ S|%p>k%aR"l`RϾ8x58@A!45kZ _tB|J>!?^Ұ:J~S/":6u>)n0CBͺê%:Oq 2 Έ ^;/vצv^Wu=l[U#ι9p8p@s:žN83f -o p/Zf<Š[hb|iy'ú嚼.eiDQ,[m@U9'ÁWտƞ.`a8y8p8p0r9v8p@$sn1p"m|g</!snvjF|<#{|]rgY1z@iC Z0Xdt@dHd9x 8M_),qXN18p8q:pÁ:ܭ8 1 KGR)^Wu9`ط\<~-"rTy8e:qw8jsYן Xح؜8p8p:9pÁD*|(`Ìm (v:y%»嚼.Eksʹ55FTJS8 m5`S>4w%\r>p8p8*qYJW3t" nU8"8p8p8p8pt9{p 9s8p8p8pD2#/ӽxtxdp8p8p8p8p8pÁ"_gl8p8p8p8pÁ7ֆz/);Cp8pÁAݣDqP)j(sw3::Ŏ'LHѣFA䲓ta".iSF U \Hw]IcS5cӇDѢ8I/!+<|ѩCD-+D1b/f,'ʁxKt|?QʴDZ?M&lL>{[/JU!*T=?">xJm q.J3EDtp N$z3Qݖ@]7!:T0F# o;q8:N9hdϐĎKsL=%{v ^"бIhWLzիk5v,=|D3Q"OM=y2sEc oNQxU4f*r-dȕ>=ϒ+Fopk\㉿]}٩/5E'o@vcDNZ8o/W+Tb<&#m>v~[FK]0*TX>N֯mWj(s@uٲm8r/_:Ӧre*#"aR9j'%͒o&m5r>|;%ɔڞjkVkZDdr"|FFt@ ^% \B`1JT?AbY Uyw.фAD ѝ[|M0h#]3µ ~[# KRS* qҰѷDlqD=y?Me΀g?FyUJaz~ɏ\%-fqH\6y`/A=*-q|POX`Z@KO)>k$pMR*D7Yϳքh$\ BdB ˩9(R_ynNfP" aE;SK-+sҪ.k$m[CԼ<2q:1Y`"xN~ك=2 P(ƺ[Уe@ǝ+whpZugץuuȍvAyy>m0Om$\38~,767&s7ƨ^B/FZ'325{SKBI?!} T/zXr|/a[9%8p8p+9}Ы;$ܿ.˒:ERرԉSx.kDqH eʤV| Icjn@25lH y:0}EJd̒*y?OoLoi t@ ΞJ%" 88t$C%:,Rh j0\ȩ,)(s|Ys 2DAXx]x #*!vf,؂Da2amo4k7Zek hN~V?u`/" BqArj|vl[x|8p8r:~YhѣZܯd3HFC4w܊[OYbTn @[}R'ofA < ApMV)H[+y<3Gx5 aG@ul c2w:&bo+];$fEQ{ Ba`E\?iQV%=hl_Qf~. qz_h3+h. ب?,|Xf'IY 3:K_.qҊ<e.`a/cr8p8pɁW8DZ.Ƴ[$lў5_#fh *XN d>dU78Fh5B)7kQ-ZhVځ݄ H@Xb-)+T4q\LՔjpJQr,5ckX4^y o"耫c W~e(F{7qxazp+N.=Iv\TSQV٣gtUz7x#^ T#ӇrP(K,JJ])B/] LCHsF!/eO8{6 gY܅'a'4Ahc:dSo vv.YZK$h,jdnj@2-';8]az z[AY&aa" pK! Dr pw)ڰecFgHC /c" ۹$ xK8x+ <΀\v6U`y?gԵL냑=kGϡi/]@ 28gX/H0 s 'uuTpqv<# -bnv}Y1KG1b 1ϋ,tC̉eEr4ԃ-X1竑 G&?qUM=p>y"|IE ą4 *%Ikʫ}ФǽYc9(5 ނXOym*^\;'ϞinqLIV@yvh0k_eEt#{FtnK9 ̝.8"Q zR`JW,=%p@2O뼓e'OoF!oUi>Իn)Zj3wхj]|.`>;52p; `+.[C!KˋUj !A9)c* ?g΄P@IDAT},4e.ks? Ӎu+ YU/k|ݻ{*~C\q$dٿ(ж6 ܯ  !Aj7Aѥ}OeBJBP89 `[?0Mm<'@ k87W|(!u5?Ari '2xhCyD߿<DŽۺ'='!4!+xL"zVb,>X OAtҀ6{}n" f(cF]A^czf$ؘcM,9ܫ¼0c=?}N˾ZF1S5P,j0S "Qޠ|Mў{\-EmlFi\)X xi/^+pdTQ{?G/mZsbى!P`:렭}Pp`ʁt=q.":w˭~ 蓥P)q]VҎ;\J5jR =Y[FP;2cOP.1RbMg4,a~lMQF2kT$ yz84kSu& ;ESƹf?2(&j]_{u̡GPT~8UXqO,4'9R887&GW9 Jߖ,I-t tL`Cj`%et,ۻ,dSڤIWݺZ -ύ@ ?oQ9!L)S/p@:Z>9vPdt4&oMw_&)\Bvcge&$5ϣagٚUVg΁?@f$AxY =qa/ 77xp |!QZݚpޭ '4QדS9󌵺 1vm1ђ U(&@ ;5$MoQ *}h"v(om[ zwga໢\dJE#y wW NT'I<@FC_%VXHCXcq-n̘,ZAi3UDUp ;/V L\ |A+4ljXOm@| c`E cp 7l‚.dAg \XpMěXqZ!6CKpbA8BU0Ƶ"*Ph\gL|A<,``E+ 0@{:YAT!W ۰Vs *_aAV{ jl3^ *XEڞ1 $KcLYVr#~.! [pÂ0o-q?2 ?Y߸=)g*^&̀oȖqΜP=u/clQxG߀Eh 4| a:JdF ӬA˕+c"C,mU/QBeʯ;% ?lui|0СOE-ўqGV@GyZt4 jz9ꛠf`y,UjEnm=vz%J:Zpԃ@ Z ;65׺ fVG*A@0U.>T@3^xl _RF@J@. %)EP P)nI.ᣥDR?ܝ٘9ϳ̝;=Pe A#AP'yBЮ dS`V}yY Gt)m , D&!H%zI"AМWW$5ƄK3R[379:@Va0K@09x>K͙y¯wDX@u`%pS#u*Őx=GAP,IdpAWdj pyVu \B hi) 8ZfĽ4cA 7w+] UIC7,`_uk.75!+T!2+ m@ʊ`s "%1@;Gu!,̍l'=*Yds{Xj*\daَuÜ#Iohd7,Ҡ뤈 U DTV4x 9ZaΑaBTYy;i>ihpih]#'k&(D/4? ה(]"*dp iFI&Rmi*lk쟢TcV o<Ȣ&,Qfj-֣\xN[<ՒI7TRΑ;iWKJ#+Q/J'4OLy>CW@JvN9txa35;܌`aB7nLPH}@ vT% V-(V`6ז9<8R[e("R mC4Qr^|" Hg n8/R^RCU8m+kE '58.p+Xߟ=euXwP& .jgFY{;.;kS~&NAyjhԉRZ˞'.{\ 4:AzO K(2 XyTΜb %z_gp3te7Iޘ/ﻯ-Vh˷kL-Qqn42ҮZ5m f@2v -eUMB]ۘH(n*3sak]4Բ|yX)Xe`̫Ѐ)-ZPW^f M+yvPlţ~0A[V @pAat[Fz}^ijHݦӫk@;cA۶U N-˔)C}En`ɪ&,E@bC ?y鬮Ent"}@Cki!6T[Q }nZ>t$RCXW?rȆCXIZ,hbFC쬇M1X@)(+ItƚJCX˾?'5c^w~Ȳzrg8 CVTq2`Q'GJlb5:p4eK2 @yzXY[Hp 581cyq= &T.5ɀt3:ѶreKCEtΜZlT}0a%cw״i#ɏq+∊)K/$Qn`9oiаTik3~WK3:2V": u'g>`(G;/l)Z .-<$ܞ3E4T7 Bƒ,nqHR`t cBV)[n!4HXMH"M qa,\l 0(VZ&= -owhkͺM2k)|$,! ki~BY<۷{@y\x|FR`Vۭ7xנ  g؝P/֞.!C',J-Ib8@ 7Rp'[Fche|A< ](`K7MeY&]|AZhLQ.MM+ \[xR 4# 78hWuX鴮! wu~my'3eUcVjE ʨϡ*#sXA:@1d+)eyg2:f 5 ӡhN9xr-M tm)f<јVsB ڱիP&[Q/4U-cMqġ]$LPSqazczr 娓C̎Դ/dM;y2 <=+W?vA ռAa) ByҲR5)7sZ"t'RkfYጁ'i (j9 PlPl_c$.ѯ fm^k!w J,(e{HX˾z|RLc b"`ηnMBt9#(v98p8p& k=46[+eMwt#yd8y2}6zL|}?쯳xm`[C6{>YQ0@HXd)K%Oϟ, ].֛Ёf̠<6U+JżJg($4|9x虚恒 t 6#|$hht PLj_XeGm`]{wb ^1=HdFZO$?>1VpJKqj vq_Шh_m6\], HvN7 eB2r}#Nx̧ҍvۘgvMPxO2'7ҿ?,5:f}~Q! !Z"*|c2(tW ld)5p+f_XI0#hVR"3@ۗjU-Z4U)M?^\am B,DϔtmNAV6 h#F "5lN%>whn*`DIg0/CLXr@0l8s+FVZ|!N܀Y dufAU\xU7qf u,jp3tpA\Pc4 w^Y T*Zs/t!e/N[Uq6E@u|fpZQE2  ߘ߅s#5zMJ쮃΁(nu9pD »k0()G(/Bu xp҂U  <(*cO(rP~dns @%-[UZŋ{4$C9^5I t^d'ja=fL6=K_oP:n' 9@䊳w8pÁq s V 2qF>R{@ځ,'r .0q]՝:i@?&:2J81Mg0:J"J(!bpXG9Cӱ#zZ<3c+6Mk`#:B#sԺ|_Cq4dŤ}@@а,lq:}04FM^3awN 6?NJXqIvM<]T->{"/#] Hvt#!] }f @PғJG4!l+kq @$. tQDp*A` PpdEcW`RYWj2B!D2l!<>{M5KX @%G3e%2OjeZ#6^|OMnҤ oD7a$|L6RL([f-|o"h=4A aeܨ ҭvjntj9XNn*/V5Cc;QV  6h\'fTVX G!y{=YǚD;E &xGJk[5qd'jvCx?M雼%)Mam!8p8p G:{떫R .]ܿvu<{gpY]sǴ|IneYwQ+W>}12#@A2L߆^L¯ZLC<g-:]w˲EuVwʡ]3; . ͆TZ.W6~UOMf^ozVBiѓ.)E&q|.:4u3/7e3X~MԵD~X.c:6v.X~5U5}C~,ݺE_US' ={=R"Gs=-,GWe߸wg+=j׼gg Q.B?W>n^Mm+o͓l5ڰLo߽pp^vd7{z;xZA)o#l.޸/^.X cvV ,UMQ\F/Z}lFx$jpn\1+id &Z^nB?ru 2Gf겖}-~a}ǁiC?e>0@w[thiZZkQYΥ; >tލ7\r<=d>hufkHL>YJ-K.0qu_-}e[~&Mڇغy\9{ol)]:͘rmYݢerj)ڷZCer<~ef}|_Wԭ,oRFϏ sR1|eO\5r.;LDNp[;ixI9Vj8raOd>|b7U'X oI7h̤ ,٭l?Ny8x9bg8Xkɉ*ӧw<2ħ \lyaa:h_~ф{t-@mLc~@k`  j>Z{0V_@ӧzNTmHyE6ЁAS؞p%@ݱFѮ uP-Q{%5*!!6fm/]B%o7K`\nv$6 \)\@°J.0oWWC*~hjNp~NjfEٜ1\e5Q?{כ;^#{6}ƾ -׺1W A&epߩs5bv#VkW>WuyWkdC]Ώ6k 7Cnzutk(w`adPeZ5ָ&W @\#MK̨6Ckc@SҧW$*o7]]s9fumo :H^ѾbZ G*]g&:.>H?Q}J ,RVr߸Βi4.ٹ쬇McM}ȱbh@P/2GRXF$^3\qY9`JY\+Nr~6l[/q9p8pp?ap%v5a>gQ*UL/,,S_j[v:w*p0ҁRԈc{Ҽ*MnP0PUElaA/Y)bp7 ӂ GEո5@eƻGG/_HVtϘzԮRǏ .)]W߉Ou!+A,-g-i|ڹU0X(8, 8nT7Yk*83#Wnn:1*8V\pfJq-?1my75#u`hwv ,ur<4XVxFp_+7-HRX%*2#6/zEV 7pܭ4{rjHCr2u`YdN욎7 œ+7cj 0K/.r Ԧ/Q68!ChbZ%{"x eJ)Ǫ?p]E XOKPюE=u2<:B,\{ _eOC|@}\93YXGf/O4[Rwzkx):O2Lp[jcJ&22g6_R#X|RK5c߸nAnv$^JZP#y|[5<_\]ӿ #{m3gۧ|O*D|Jۮ_5ul,␩U7f'",e>!|i֦&A_S丰׊e'DLXcr\cls䈻Ҹ /%Yܸtݠvw8p8p/<@8^jO0fnZx͙ tX=$ݾN3T)J'vXX*խڱD@sl/{Jc}\z۫1.i%'O~t>,PZ/юzq?k{S Q9ssJx>ǏSq`:jsViN”9E ͱULް2/q@GirK7ۼf3މ'hZiZRY晛a_X덬?G\V/5FC!(ܲJVu @^IFݳ|^ONh.ڂV  Um(b$N z#!LFhƂXc8I+>Rd JU7",S뚞+$t`x` U Ra# B/y3m@@/1'[n~B#"F|o-U?t?sw> { l ؀#Pa\yJC7yqJ` OHʔU~Uc 1OF.Us]B s88'Vo S_uM7I 3"+HYҺ7#1BA<Vg`"ȗ<0^w0\gDr5l"[%$}o=[qni1}0J_2`gjY! t39D#ڼBg+fURaYw cs!փ?tVą7k$= μ:FݿbAV3]J˭A*8 /*cO @2w9CP|} Ǫb `ack(aHӰ6ZL3sY}6boM'}ث{TZšJf"j=Dm1Qlҥ#^v%z~p8p C&z!y,dZ~(;BA@z~x@U~Y zDT)O@P1[K*wsF:ΜI FF:ey1y4d:FHŊ˼u斦@XC71:h-pۓO4!!Qf(-KylUm+2'ch[G!{KO3Ze Gx9[Ѓv2B1NGYM XpAƪ SVOqBX_661b 0pGIhwzQKvoьA$A^+<{Mm(`B_y O jo I֑{@xDtO`!<\|Џ_-D +u"Mje' !O#wWܟ37P`՛@g,d ўRp lX4@Wl+aFiV"Zx@BN0kGz)@>z#!/elc EtfZ~}jWoűq+5n2 >!X}'U%~U I T"ш%]Ye0lA3j,Nr9[y>tziZif̀ ÿ5~0" =f7-bևqO)s7F7Nˍ){T}?p( DL@şJW {%_-qem aˇD>BQr~9[kV"xhħC57R([hQ-@*Tjkf5[`IV@ @KSo>V)9GSUΛם7_[ z+U 2>|MGpz)KTEBގa;,Rm Ă7jv=2 6FVMrkicVKT~py*вͰ1q-ZnE;ch6R.d+fpgok T( 0Zݢ_^tƍ^ sG||}C(AU{" Bs^`4~d.5ZTf"݄7_u"*W`@>4ZV8kCZMJ w:pcw \ۑ@%D/:>~$hF@p9V.b!XXwcWXґAB 7O;uo7#@p_0sMdV_\$# q{+B}tZ4=qFREY;c 7 0A4^-񄵡cR5u= q-x?5 K50@"!s1҈ꢦXXΑVY9)FT(Ps`?\@>,ldaݟt8H7ݽxWT&5`ഃ4C?KZJZ_D`e4 /e89nIZok; =7hM.e,7IPB1>hߤ},˩YOu= 0bdF VY3%,Vsu, Ͻy-g/m7rseOH9k*  "V}C0kSkSX5,wj Hշ="ۊȽ0˾sY;p8pÁ t'Âmڝ; % XR?#2[vf+cƤd ,WxT{.f#UTy x a\ 4֒>pc{V.BΊ@}Ү2H:,>`1N.X !|~U|~P)q8vjhH 5@0 |6C0)csDdN<|š `NpyU-~O;!\X%L>fa5f t0¶ yRš"ݦR´ 5~_t ?ǁOvF 5Xx혌OX? kFN]yϟ]nqŸ+ Xحk,N80r:q8p8%|e ܴx i!r_E_ENw³Y1ߺO!na&:E ' gm^]ns8pù8p8qA7`X 2ڻN_>q^8/\g!Owǁ*q8N?rr<p֦, flNY8p8=pÁ`"[yJ$?eÌm 5(v:/u'"?yQ3M?m5P(eA.z1 O70Y2N'8k ۵Hn+;w[թpr:)p8p8p8x9/\v8@9s8p8p8pD1#{8'8p8p8p8p:{f8@8cs8p8p8p<@G6{ .!ium+Ip8p@q"ss֍W+OZ*_xe5CD* dz`;y"H|<#'Fr\vvJVC@d":yD5>Wjy}aÁq wʹSDûxr\.ӟv=3~8\@'E~9VԵfMcЃǏ%?Ĵ'w):Kp~%]}FZE[wLӤ2eK:WW rPw%N^ڥnj&5k8oM߼]ҥ]6;Fc׬ђOU3'G֭m3Wh(k˕.U/P e⮷|AXlKfKi爝$SjqEiiJW婖fEEX '%)`ģw\D'!~8D? $ Dz^?MGyQ 9!JQ.@IDATD%UXy֌=,hVqF-!1>g*.x(nuQfv#:K4A=M={+5^9s~g;g'Xj~#QNDVה.Y]%z3G`eYp=Գ@zs ^ hW?BY({$Q I(O~&x)1vטy:}N6h4c) \MMT2E/l<8`>ۆhf[0הN]|CtdQ*DCLz+ǔ9#p(яz iJMuUu%ʒ˻J,DmYNk3N{6[nٻZ5O{Vv=+6O Xحg> 's::D1瀒a{nz㏩ P @5:-5?O'MO ˒kGqcŢ7nPKx>ȓj0|8e Ij dA- ǭhJJ[>p|7 V' "F5w4֥"7H2tR"m8ΡSϿ2gox#ϯx]D/Q9*ى69`ϫA~<i~a t 蘲Qn[DY^ Bw_zzO7$Z8*:}+@.ʠP]rjN_ *)F1xo p_{2;S>/ߓVu!hFҪD_4l`~x_%"Q\EjR|2U_o]hR9o^j]G9#Ё~Q^C{>FPP%ͫƙ)yr{>8wmN^A+*5+,KEq|q:I -d1ɑ1V; Ys+p" 7#$̷t$Yͅ2@J}x "*XvfL؂yDay2I#FjQXXk@;wvۡ:*7l!UyT?9cIF#ĽQ13ѥzZqD}=%ѬѢ߳x6ר. /ٲRВԸD^"V P "Plj}`3i8F*.e&j%dc?/-,PBG_owO$8٣UAF R&u"ž|%@I``: 0 2>)ɞ3goUVrr 2k@<+BW(аǃQCù`J{eW_WZ5@#B9w0>KD9L"̈́5b?~`i&, } 3 uGԐ &+Qíy<3Fx5 aQ[4זa uL"VvHM;!F% kADyZ>cz2`AQ:>Y҇NI"{gc,a~~"I%0vI%Q)pa=b!&Ӑl.2<޸bт@Fp)b{lYP1&X0’QQ%U'׃?sU8k 0Qύ#,["mVB%,T»x Ϩj  ̫6f W~F&E"z3<$fH>ߺBmYZ;ȱ]n@q8pqc]gHlaFG\Pr/[<1:@ubuLٰƯ]hDNll&LF*ʈ+G9ef0B t 4)]Wu]UHjRSlq*KF+_ZMTppl1wQ"i)K,:g7SRɥ'⎋Z%w^IkʫcڻYhYsZG>$k9=ƱpFIJJKd9y&(6%HMGz oґ9Gc-F4qJ_*sCbZݎ_DL?M)yޜY?Jq2nAg֞юVd"]KP6\;FgU?E9v|`z#J'O/<]/₤,Ծ &{n9 =H[7+NcR:|L47WVu.k @p%^=EFޞ2w]em5IX)t{xT;SDzXIZ7yM3ڦzl[O9v8p8\y~ec߈-~R:p Xh \dt 5[s:k}j<@\ t4d0TX1j.U{i57[9&$+Ԫ?Tұk_eE:=tϿ=FJWG8/_4e)R(~75=-p@2뼓eJ=#3V0t,C?z0p㥰^^υ,3G75t x N tj~,bw[#!KˋU!j !A91PKDo  O$}tc] Bzˬ>#p! 82lo5F 4t1²z7A#`B Bg0{)G߿prBP{q#ƃ`x/6*!@ k87WCq Kk> !C3U)mU럂/KD7s>"Gg uO*;OjCh4CVt4D.#`%1اY|pH(鋦A7=_mH(3k罉44@3B^-ENx9HF1tY9ܫa^a@=z?Z|rs_z՛P !FО{-%j1*JK[bM]AM)M4Z ##rЎKt/AE;59D Y0ߜF[`ҽ({tauAMD/LIP YJ1t[F-TTOµ7{`-5 @hXJ-Fo{. GR/KMHvAxQ6s_;u :w6}űTpX8̘=YۼA>CH 5ьp'أel.>v=z& ;ESƹM2{au'j@gӜ#G(ON8g{"z9p8p#i} V…w做` ,d`M" Xr[f@Dz{Bl1J֨Ũ t#zV&͖j k4iSTYٲtc{,! Cyб)jZKh#PR8_(K҇,?4h>L7y}$'%y3 kXJJI2%[Sr^'xUw>oMv_")|lRӄıy:6[3}"`H܃|#zZb)#>$ \@hÁq`FmDho( FxNAwy_BO ο՝OkX^ CۨBX;pgf'H% Zt`Eyq P0 ODe5"m%ݹzk]6*y-_h?2Kzdot?mNUnLY[`9EƵ0 `71H^[0P |q"h`c1Z[qX29 @`oPluq/[,8"MC!6JdA8_Ct:Q akEpU 7 nUϘP@\#O4V%=TvmXa WxYL-r² >+*kaE `041w/K%<!W plAqg$#?? HuG̛>B qV*6ؠ%%͖Tl:ԃύEo$,&{Lb">chVܠrZyG1U)M6D*hY1YZ?nIC2 "PɞP@:ց09e&:ԅ # qT߂]}"JbVau,'j@˗Ӱ;Ԫe,<>eɴŒv뙍Is8pVx怎,5`;}p)W<^&̀ǖqϘIrO3.}@GYh=kf\?,ZD˘_8FP,TwDԩzuбAKhe(]*QWt@b#zm2}C#}<:r5EU'VC}ѪU-1=1JK.e*i>;cx"7|C3#h/+7Av4k}ן[E[تypAV2|їN'4@ h$5 2M{{(zJN.q d >cj8KG,n[`!:0AaE8, ЃO\*V$htƘu?q@Cp`Fk&X*,s^j|\S~'-~+ 5P.\9A$ij Vu \BW+늖Am "GЌE\7u.x3ޭta3\W% m"WCgFhe~!"ܨ[Xzhp@SXȬV6*+3-\A-LJ376`?JsaEds{Xj*\daَuÜ#Iҽpos[1ȒW5@D7iE?x@ A!1[i D#ff 5yfBBm|M%ҬBv 盐ّf4kR-զ6Qɶ&Z{)kI5f\_cX,jeFQڢi=.봵nS-)qztCI:m.Z(~T;4_Lt_LS+L|*H:s-f4jv3v:nܸA#ǣ3R0^؍3S&@| Xu~ZtHZAxOCX_^[|:Hm:+`H}GlfԷ Dqyj|A8cpPyxJ}5n[Y+jc?Q8qՌ]Bv!(_T2IpaU83*2۹쬇uY5 5w ʃTG3NFzXl?uf;x M`ѩ{YBiYʣrCр Xح^spq:$XoL+Z4ۈ5p h87MÇkkeLiW63؅Vy薲`*&!.mnbr_ S?zD90B.l jY<}X@@20h@ɔ-(+XZt]n45UnS5jq @m*_jeʔ>"7dUc"Ct1D!h{ּtV"7>!Q8傉uD-;hG Bhм U!ከ# E%hAHpoԴH5V'B`91@fnrDU]@-bqR"vC/7zN`B87Xp=HCy &#h¯'@r8!dUIIAtU2ڔ1Xj#:K&Xx;hg"h4C{,@ZGZzEt(\=x9MШ"ej @ ȰC#YԄ/~` k^JA t#oK'Z!!h.sD:sQKj:@6hChQ-œ ڌ^(AVl tH:|!ΙSk7 t /U&{ >1m$1ntTQ5e%5 ,G- u]?v*bm/jif@GXj_D!ޓB@DL:‡r,hCc^ 6e @@+AEq&û8&ABX-I d9XtL\*e-F%%g  lUm Ή}ѲpvMجpd,^ =򋒰+#,HG1G Dų}+Q5g$U3zlj{ o2}֎ bB8$}Bν'RK1|Boi1dKN)-1ܾ_HWAF- 1Mx>}YIW{EkErZ(SCSӊ`/p^7>Ʃ$D5 I>~oC#/^1G>U-Dז~m>#({R5aZQY?2~ý#O R'|&3^a0IPB[KM{_bNѴ=,,asq޳rcw 9X`\ͻ$s$4KJYlPkz.?ej1f-fTnPJUf3(8$@@A_ |rûFz.ixI0@)(+I~V!Y^#c=,5J1-<&7 w:ߺ5 йs砺rv8px&׮QЀڸBlЗ5 M]Z30@㾀Y~_HmWLM@@@I:~`k|HBLhTpBsVYtdKh>B44PqDh&Z/te#6md`eq b.Zp˽;f/@Z2Xiz#P@˧㊺r+?>1VpJKqj vq_߰hF5|~ ]GjO\BbZFu h=TGQm3;&( Y|'yMG [##cG iZ"YU!iثB0)=&"KwejIᐴ[,0XJRxMJ*@dhT5PV{[EKx*?]iNj+m]AHe♒n )Ay.7TavjmmWB(iȟ{1" (;@MnܽuS' H0g< BG@' &XQ@T@)]t{18X\`A-бZMqwz@ǕWhdΑZ|!т,7_F@9 {4]{9@ݱ<;}BW81+a%6]tE Rͷe!#n$+aLӻ}C@ JzR 5ѕg)Ljs!Z`FhIJ X^gem#X"NȀ{Baa>^ɭpIV$?}&u&#R@$ XcG-%6%T -^fOQ*-,jT=VZ/;kod&M FtHgD]`+dhMmv&CT:Z͍! jwQHñ*, ꦩr=v2ٺsU'kXR{$Aʭ܈Ȳd՞jvA%}ϓg)3~; ܊{ /y~2f1J{V/,R`',C d*LBpI2j[a a%7Ś1:@fǫ<>}{[-4p:ȷGҕW(m64}fyY~0ֿ@G?;cJ )A=*emJ*qݺ7ynzX+I@x/9xkx̆Ղ![k 5EXA5U?0n_u^Ǫ@T}XBmb0(*cO8(G~a dtub4aJ3e+,#_nKؽ]˞McM""b|{#82jv !Nv.Zց[if[+vX/\ !]l߃W=qP7my#2{7eZ}$ GO\eVsz#Hϼ60/cA6Qzvb\yXbժg.װ.Wqѳzv^Q}]VM\37^DZOK^}ޝ9JV#|zг9D^*Ƴ-D ]x_5W4by]/7iWm6OՄk2 zّZWk9ҧd۳ɻx"`b{t`)ގ)mZ):T7EreDjQqnhaтu/Mg^NAQbL]Rϻ2/츠80{hNc-_r٢CKs-*+ݹtY?ۇnw׻qK㼴lc'YZb '+[wc .뢥luԤI2Xp0[7/+G4qo-;߷Ks]-[V\^-EVklXӯ׻̬늺2\MQ}_*Ɵ#/\»F΅x烰i nk>6 5 "NJ:WSs|GzX",l#݇]U|D - ru_uQtt%)p/6#ZrÅݺ30…=/[^XX4_4!}=]eww :F)Sz1~Z8ڧVb.бiw{@AD t`P=>7o:F hD5awQt8#HݵAg6T~KcIJtYzgzPAۼRC$f.׷>7/cZ8k%\Po{ ːV-Zロ@"QV~r} U D`Rkp-@iܼ_Dn~,Cݛ魡}d` 誧IM" A ʊTa؎uVKqU[Qt#)%H "%t( )"(|Ht %tǣAΛ{۽7{w`> ,coOT!Wg)i/Teu vD=ya\#M&ul'cQߍu!Wttk(w`idPeF_vM*;5ڐ:FeYbfFC2~> t_t`- =syAs5~h[xTxAj/J;rVx |JE?R¾dѱ~ℌs} nk* "WF`΃9Q4ގ .5]=_RsA*i'}S9_ ~cpiٷ+M_ ʰ#3G$]Qh fBr\*IV1 90K .Rj<ݟJp҆?VT"51'gN}ēiPL"˘u/uήx~C ̹vq.r q Qӎb?!BhbFe_ʔ Sدnjp]z",过[߼EFi݄X=2v=юt]{ZSikݫwi`ځF;9,<^<|Y!Vdඊ-4,JY'uUL3L9c۴G.&jRK5c߼nAnv$ϵZGK?x vm牮\d79% G #{m3gqڧ|OPŪnsL!S];n1NDi-E/Y} C\ t=,Sy%M#bM}q_+1eb=A5Q dΜ94w~wїإq _4a9JDAŝ354444|q:Vq|1۷Ӣan6!iޖ-tPr(i„~ba=\^Ė#v/HS7-96$@={y(Wc\A3IC>]ԋ/4STɫ  2jѼڄ{{sc"qb@W3fc t5 羯ܺe{%sfjPLsl>ׯ u\*QU,YVwyW;0M:HU3՞^v\=q r8NǪoT}?rxXI3! pӯBU]xFR/fUX`ԈӀ2[0?mWq{[2HFFT#oqw+戶q'ܑ(T eac rd䩁voPh2`*s2T|.RBp[QI)]oayr^ꇐx` U R<[1G},O |ڀ l_ Kl^-0} O:) 7)#ڙ?a?#"FV}o.5?2t?kkx+>'A,\/G@G) øΆxb NHʑ(?*+̱'9a  ]B SG88'vo S{ IL_ V$i`~YqܪDpiߴ!9RA<vgD>% ?<0F?attOYS1(k٬2X!;d+DN ҶH MvUr*F^;g@IDAT1Ԝa(S qVx><wj3#`q1QrܯLm (qڧλ\B9N!(>fUq*0V5J/4V:ӊNjMφ8[@)dt,JpZ/!"n<5@xۄyRi    X^ML L}\Vl\@ ]R &e?ݻT# ɒQՂ},Cfly.͙d46kS+05W~Ulcc.Ohrt|VŎy5r@?GyhAp JmdYpj[Q?a @;yzpBnhK2[e ]x1[Ѓv2B1NR{,@E&K< ck61lDW togFQ>n<@0 Rn[gHvoьA$A $׎>(7c8o WF7@'w%|"+xuUn\k^@> } , ?BD@ОBBH{y `B=}߾)ϙCh v(0-n,Z+ @z8V6,{Vn#X{طJaXź,eU"UZx@BN0叾jw= .Ăzr|Ϙ }Ps%<.BUjwN<5\웷z MmْKB@V&1*%O%JZ#4%au2%|xn:Vj?f[9D㫎Ӵ kAݺG3w`,cFMM)cd1ˇƔ^^=8RF2 檑/vВK`Y4cR.NA;Zb!#%rwf e) Y] RM>a`ER@a>ǼnEPhS]tJYPjGdUUp`: )^+!xPtuPtHXBTeQ[UXb?wF#׼>p8$6u& ;E).yVZa{MԶo"AÈX6e999999x*33(JV7ibdUӇ%X{,+pA0tf_+u3/ɼ͛i[pqSMJj*DC2fwZk +s\Fh)mXMek PʕVƾY):EeȒ%Q*eH ꯿N^-[ zV 2?%; .@Ƣ"mJrO|nkXL&עWiDt%>:5#mչUiWHfxAqmvoO_'ci6J,b+6ޚp!Z) Wp jwa7%k0nU"z3ܓ+O @ ETmwhC@**WeWQ+EpJNs+";Kh3Apں;QzBbWF; gD &jmQ^ځ,@nGZIu?"(}@3ޑKJToE  ƺЖ¾n Z\yڶ#PuAτk":* hl#[껔p$՚EC @g&UӾDv58 hcҷ؟&BBzmUQW  ij˯OXڿ7}<xC-爵V{ ͈"iH{fTP/szZp.puQ6·wj) t]^sRMPj8@ =м평о6t8%%lZm:%H\bR[tmбZgd_RfC(ީ8q9i2 r`˂G27saZr1z=>Y&EV.*ݭ4O; *sECrĻP Yvc_K<`[>YOu=v 0bdE VY3%,~ 1z; Tt'>r n`N#7_u-"'@Dr Fɰ`ۺtwcr`H>ϖ $Ax&iR +zp?P4C@nBX7p} |sИ2BIQyY误@U8GX '">z20%Z"k\_͑I~`)I89ݶg13,˔MAvޏ(#\`^wш_N/VI3%OO|jXi؍Y%r.l>CMW0](i(ruJ&%͜԰7;]iD`j,u1 &~̜v654?azO=Ms@s@s@s 9nQs@s@s 80uUZ@ S ZV f>H>4b4\O)`^44444 hCtރBcx| bvg#VL9`ķSIL9ψCq9T4q2O7龾Si`ƦjhhhhhCgE W-̆mÁ\ !jQubyF$"rMbyF򸵿FZj/6/ 4.@6 ,߷%lbIs@s Fq@McyYGf=|,́q@㟮999999999999999s@O%'999Ds@OӃ|h#y{p t8睮999999999999999#8qIjhh<@{5444444444444446ֆzQsV́舢+ύtmJ0!LbNJE=G}7=S.Qx(uҤ7v,;B8M$"m8h!*!kCܟ(Aj[CtQDzGD=ŷC":(m&ze9u(޳&K'hhhoMD Dׯ9!Jؑh&f[0TMTX|)H[5=t_.g=D--ڕŏO/_FÂT+X>51NU$5E^zIS7lqrOO?~W,پ-/FJVJٳiɓF @E;PST5^F2ExSxځh,+(od߻˚IoZ@MkDmjn-,iKEmN4,Q{/ھ#X:  {UQ*By]Sr~=Ǹ58IXuOуgyE#OZ}_ZztL @7Yec|~h,{S V\R|>N}لh$\?,r47/ drbZNC9A,DUzI3˒ߙʽԲ瞴 A##_>Qr K X6\_:t-bkn:wllW7c$"Y"oE Plj:TuPW93ЁA|}I!JT̑6-ݼsv:Ell/` t@ ʝ^L% 8pNH;E,Rh jv5\}Ⱦ,[:YJBSeQKJvAM5lOT-C*!선ql4[d#|è"f z,S5;mw?cv(87 _iE>EܱAχ$GިIOZqD6;Ѣv.i+^{54|ٴRВԬ5D^"Ož-`] E ly\'Bg4_B~IVU()4}ha:R`b8nLq:߇ޯgtHP%YK<A{llJ, E[)|zW#!=gwZaUuUBm [3=a{ <`k{>~.XnHy?Wf'D?4֡HDهAX$JX̙Xl[K d~!DlȤOdC m? tMb>'(}z{f;v X8o<:_s@s@s@s@s@@:~YhF?T3Lf?4iBdU a:%6L5gUkDXo˗mb*bE43?FڴFҨ=G8>H*ӡ=T:|穒/7ɑ. jؐ8$*!>Ɏ'+U/j֤ k'*:k@<4*I^ [|<5!>xc)ζҵCrm䬍+Vv$^ʮXX?(` ۗu/@Hփe4UOl׬,\fIڅ>3>p]dyG)paj5{wD;o} y{[D+ t¥-}W~*1* .Rz#={էDOndn7,`Qmey'0ӌ-\=UsI8ψXX{ i4 'i=켧g N .99999 9'9xv4>ek + ' AwueZŮc4~#md h?ab7Ri88,VFXat?eK`$@, iSp5%i1꺪aɒԢlY# @ؚE1bW,3B:tf%3Shk-ct웽\CHG[RR ң{htaz荎oPS5O>J6鳔j4Ac5q0@KW=c8sCTƇ/B3'X\B ڗVp ߈!3_-~s pyĮ(Br&?\"A[|9Q &pSPAXo!8_,+eγ@Ej}ٿ>b,ca|hZ0pK! Dr 0o7hA| k G*+>w￈/%Z8I\ \k-pwQrܢmXjpͣ~unfT])\s 82xM{bd3tqΰ^`a$wԅkR];e\9!dja)_wo3 {̶EF>_dE/ 1U qSʙP. ǜ1W3A '跳Di҉l%Ȁ6& Z.S{źջ+^k+}s~'7%Jb|2Xs]ӈt#Aٜqn(NힶߺO $СEE\EғڗҜ.=]u߾oBJd* B#TsйeI@xN7~dG`%́xתn;bDODI~=ϱ15 ;0.!RR3r҈nŻnR悫HX5*~+ª"ץxa[?p?Vyk(ཇtDó6 $<pO=D|mXSDzXIDƍf3ڦpZO[khhhhSt G3߷F"{ z>t`0ȇfJW]gb3HXFw[d-N헼ݽ#w2Vpt,C?hCim^<>x 7XVx42p; Ӊ})|8 ;slw`SBX^; *:AN{(lCe q'PUatc =>n{*~C\q$dտ(_5z-&#,+@ЪpA:vq _wE|= U {srA1l~cŦo k͕q?bukpAOe7h=-a:p[ա_pe7Rs>"G' uˏ*;OjCh4Cvt8D.#`%1gX|pH(?O z?ڐQf&*{eHn0s(G:Xf&؜cMxtY9ܫa^a@=z?Z2s4_=â#=u7o4( @*B<z髴cwKƢfQ- >vmlFg2Jo7oQn,kN,3Bօg'?3>(8tm[?/ ];yͫ~轥QxJ!+;öjBE?-JT kDS2XψY #@}lhkm3u9^MؗE@Gy+¼@n%4V-0q'};csؘ;'}8r^+!D{F+W6"^ kV$ px֦$<O kejXu̙Cs<9UXrO9,599999` t8ܘs']*}Q}jyc?Xc C"zիn X_4`K2LI_שcĨpW b'Xcv^uR[WnP.na@C0^6"޿l" z 'XNdl<,1E+#CVi}܂NdDA vЊ 1Z;qXЊ29 @`oluq/[Ѳ,8%M8&HpɃ/(s<|YD,6}WB*]=s:Cr vrGXo d Jzc  C%hoZ U" `j[ch,U$SaE `04PUWXI1ue~ܫ#pL*Zs 1 7A:V[ <#< 0GT>ȼ#%֗2'bo h4uԆe{/Hbr\:3b$$,;vMeEW4?@7bB?*f_"PL0Ҹ kVWihQ˒T3>$!_ 3u?> ި4t:䰑לsR8Æ,2J|Q}[(3~x%X%O`kg}6L`ʼ0񲖉!{ @%+ }Eqw ?Oh@ (] ZaLٷتȹA-#Kszװ/]wDL6Ig0H O([`MgaYaCBRtI}/ X1J#ɰTo`> pIC4X/̖ΪP $wGe-| px֦;-"a!fGѱV5Q:]oݪV5,;`ќ&3w+r X8g599999`ǁ'ǒ_qrfi'F8( t,ڕ3p |Lr!sj1it x#tupj,Z֬>r:1{6c͌X<Οt7i 4|< tϕjX7/u-?v@ YhA~ W~W_Q,'f7wC: ?ӎqGv@GĚ?Ҁ/}̪C}@}kX|4\ސKZуvye@={2&>! Ye;7AN4пh\<[D[تypT-Px=!F0@ͤDB};=v=\b@d >Z'@ XܶRBtaŽ?pX2@$TH&4U*I1!n %M5X(L03K0_$D 6Jn ~S#u*Őx=FAP,Id B`Q{,@Fpyaea4--eB@E6oK!/}#lԡEj l:5v K\Xh6o`{ "%1@;Gu!,ʍl'h UosaG2eJ.Hsre[׊gsWs$-*p9~ ⹚W֨W=E잪 n"I+<œBPB:V9GZQV坤ݿydbXl¥eӵM)^f*"Ob:>=)%˒̰ :oB6Pܩ Tm E2P-@''0<{кAHu#Rnz=^ϋ qEق- o%S/7vME 3 d4/Wad:|+-k[:N5[DrO D1( @S +oL} ]yC^|Ej$ЁwIs,)}yO8m&4ܙοW4(Op5m4Z~ZPVԉfC UŖ-|hNE>,DJRlX7R`.Z:ӊC+Me YrW ywKAPLV>gzt'RUSs+ʂ`AO/bluhAl}F4"piӗ5~U {M 135Ws}'akSk;e%Xs)w?*ò/;&j}'yXz05%&߂U"0Ԓd [Z 1SF ~LL_c/r #ܖMx>}YIW{EkGrZ(SCSӎ`/pQ7>Ʃ$D5 g@?t.u~1zBȉN< nD٣1+5բePQXAMȓBV)oO1Rt$A ]w^[.%~!1uEfGԴ/!we%WyN1{v~1$;듯\KLIh?@i9yrA]\~+A,I}B)FlSxV\B஖n%69frzz`֦&}$bX۽dya\~x/Wl1YRd9ݡ УG@GP҅5444r:]DyCj<@D/#@Ǒѣf`}?;CBh]o}`fJ(?>MqX]Z%b7+ſ9~q᯵ ^̫t&-Bc,`?,=|=1vpNKqNj vY_߰h_m6\]:/ HvV7 eB2G+$@ߍٍ`W3;:,&`9Swт7o]72ЍPi v( 8P:nD$j+atX5XYkӍV6o@Dž=hTQF]u!9&,7CFW3|N#mk_ t8++4J^ 5+ˮx+jŇtOd-k 8vt#!] }V_6kΠ7AXC]xIm.@ l-5Z_]OYvWSx2 9pq綷Z. W/-p76v$?}&u&#R@$6"'5u(pS9pP 4 [ Tp[XZi[VZucfxec& 7iR7FV> 'z6RL([;g-|o"h=4Aaid8ܨ ҭvjntj9XN n7/v3k0]y®L ,U@pJ΍,쿴ړ2^훯1wI~y5y¼TAD\l Țp+ ہU]7ú%·.gBFAWܳ~1Yv2BY"O}e@IDAT-3.v 3k!d8YKcs FhLxI&}nͨ1#B41t5W~se2E={Undݻ]{>9UGw eMԷW'u\ΠAcUF2|>+zw^-wYw9ynvh4ezwٛ6y٤IF۽{{jܱvg`ĝΒu9{ UUΕ;޲Cs !F_=d#˲EwӬvQZӱ9s?e;#BPi K\yX;W=Sopz3FzLwEzT4uLjؿ|.j*꼞غ_r%ǃqqN6? Μzb +;4{z@9&3=ZhZF`r--sSG=\ ϔ:ӊշhn w]=]fGnV.E~ >-dzﱼ\;y]#AX4YNur8YQ9YFĚNc]>:_Ua9\F7s*W](2[|u\gION/XrZ/~tý4Y`9F hD7ttt zv`CEC`,["h+e–PA][0)r]yp1*FԕIs5؊$/yOdQ9Q>  ₉Dzw(mD{h6>[2͓` ړn'6Mʈ\87*;Ra[ڕ .] _Kȼ}\]YB:)v~vMuQ6\iWKﭰsFD6}n e}ԡ9W A" ,Oϯ,!KD*~ʾya\#M&ul'cQߍu!Wttk(w`idPeF_vM*;5ڐ:FeYbfFC2~> t_t`- =syAs5~h[xTxAj/J;rVx |JE?R¾dѱ~E/>!|i֦&A_S/2GQV 䚨_gKҸ/%MDtӠNǚ8xG8>i0~7`a4o:`er4aBc? | TW/bJAueZrl{Jc=ͼy۫1.^Ӥ.t)RUՎzq?{h\m½޽ܹ1M8OC3c:sWn2ʽ935(QrKG9 Sj:.(ת@,F}̻͋<ϫ^zWn$媙jOM;JF\9z{cU7>qE,[$jBPW!*.Cm!DOD[UܞGFT#oqwޡ.ъ9B(p;@M lL?q a2@X(\4 yl0 9GO`At>AY !8$˔\0a9]l/|X@d#/H(VC̑z S{?_s6 [ iWwyHNMER~D;P uX ݈vBRтG&gm oeD!6 T a@O`to^ܟ @ BI9gXz96dR5'~baRAsGĎ t `uOcr! >𜁹Šd< /+[.6D3G:'TLߧd>|yXs1ި1R01J0"Jd_oG $A[c>ewƮHSh M,l@Ye%.o#{t/ 5/>NOquk4(F0r:*ъF$]bT4Yn@T\׍f}@ɳ&jcQֲYeCcwP5qW'mY<'1URYw c9!փ?:wRą7k1ywj3#`1q3]N˭A*8 /:O w@spf~c=;8K-Uy!Px1amL+rr8Y}6=lM'}W JTZšJV"k=ko@b7OY/Si=44444<5@rNtmBU+z6i @|n.gS2eEwt]ѿdɨj>P3@/,Ej8 Ba\5ĿMEh#$H mI#ٽF328_;{E܌⼭(`B_y ܕO ZI֑VqxkP"}X@* W=JZ&5H ~{d/oݾ)ϙCh v(0-X3XuwTs== PlZ[m0b]β*\ڪC-<!X'GkRRbA|~=9{OVḡ WWs%<.BUjwN<5\웷RSfK.s@!/?ld-dS/ <[EldYwAQV0lAY3kEYl"s WtUiZi5Ƃ nݣ{û`0Eб}vZb攱XFsrc[//՞ @B#SqqsEŽq;hI%rM1Qލ{XEQ b-oڑ}9Իʿ5_U-K9]ҐҦ)b?V,a`ER@a>ǼnEPhS]tJYPjG ?u ,@S6VB>7肵+ޑ"@ʢ2 <0 $_zXMIN= yeպVEzX^-{>=+#H_Q|3`a>cYNkhhhhD$J̠ Jt728UMYr : D0bbn݌ lA2of%@Tġ l̇Y(*]Ve1CJ /-Z~h3~F e*ESԾreUnVuJoNdDd m;znJi=Rũ믻W9}^U)̣hI%sNPH"÷R\G.SɵF,eg]:pNxH:kun?G~DE1Ұ{8sP'DSXm5ʢ&8:\2tZ) WpV }5^ݚszq7*x}Ef'W졟 7. "оT@UگKtWQ+&Epu9%4п 8m͊k|X8 vNcψ 1>qM(- W;pcw #-KQ vb l[Њ`c hˏ]a_oh7a. %Dtx [VN '.Wia6@uVz*ݾpjt鶆Yo4𗆔mlH;'A;'4\f8\NlYXHSf.@[.[Ϻ'ˤȖrEIbG1]yN½WPtW.q4t5xTJZ"< akvls'멮G֎FH*˗bdۯ!\{GUA_Z ϶w7/WrsyoH9k* *!A; >zX!5Ix)r5,wj Hl#{=,{k"ۊ3`a>}DzH#"l[n .s,Xnyx ٲ8[$$MJ\5cEn?6fCt4V o6?oA[ SZ@;)2/ H@ }tRsG8`p."᣷Zn[! S%p>k%iJ"l`29vݶG!v<]4W@xULIVvc@G.%\r˪3cL: k(\?uIDI3'5;)NA?ǁ&X;9!\7x혆OX?kfN;]ySi9999@GT999aӺf -o p)x+=ˠ[xb|Xi1<#vL&1<#xRڃ*,QAez2=v3aWNkh<kӧ:CzƢ44444@#Xs@s@s 97K i6|mA4;ʺ)KqBK'5NL9Ϩc5)Nx2k"F؝kw(Zk? |F_`HY1zm.rpZ/ab4Сónj:ecA!pz wN;B,ٝ/ZZ1<#rL&1<#y[D<PP+q:qHk<kӧsvN .99999}hhhspm'`Fq~D։)DoʼnKԮy߸J8)S|{1d,{f"WV[׉=˿atٻ p+-n%44 J !ܙsK`93g̞=_q`<$'a~zD)zu`"P9E<#gfr\fJ9qDtp _ԀYץRŋfdz,iyǎ#ztxbΜ]Eȑt*$Py30!Y2/=`4`)0)\8r-UΙCΟʘm" -*U9x%Ne:EԲp܊ƭ&Uk"zΏQ?6cAğ’ƺTJkN"~HO5x;A8s(qٌ܍-=B:~k_9 zf_4֏}=&/cmhi.-:n4/`ŕ?8ggש>V3X ն.g\-bZNC9A,}_3 JL^jRF'BЈFy~toq(QJód,fc'Qt'@y}Y7q=t}9)su~` 1_{/Z0@:̳'?mIn98V,zo~|7ƨB/܏صND;g?E8Ok#}@}NV]_zȵ]n=Gt8pvHZ?]%#LXo'Jf˦X`hrUot 5= \l?tZ' t1ݾ]+n5~_nHq?+r:uhmZ=}be݈ގ)Lsvm 2Meߖ!?<)O/y];FE^p󞑯vpT@z2{6m:zTKwcft hAPGQ3' 0Uf7OߖqcT"kVvK jݚF޶FҨDc iC)̀1CZt|_ ɞݝY;չEX:N!"bi! e%UȽ*QwęV*$P/R2} v3!z OY} KcAM⾆f: Bv\I+ tkYEذ3X pi6;HHo {m l/Ԩ_r"y.ȟ(WaӇ~loQf~g. q_h3h. (kfa,yRHLo%̏]f8E.|? ".~YD*Ǡ Hh)/޶0ϭYk䆷9@?\ 23TJ dT%U׃q]q:%f. oXN%ۊ&pPICplDRD'"ɬx>j T#гG<ߌ&,u^3{B/] LBHMB_Rq.l.Y.e?rY-hoNXQ-4KW靿AC*<5?b] U&?\"A[|Q &pSPAo!8?,e@Ej}W=b,ca|hZ0S~X"pa a 5‘5C g" YM1AK'n p .\{@А.:' K 4$@ <xcL2Af" S%OQ7Ŭ1 b V=3fj$=!v(qRD}ۈ&m0 he/=K|rbr6ǛWX%w'5t>\\*4ffU=(5 ނXOzo:O7ܠDQ)qĦ1:nMל7IŦ}ѿ\O_}:2[|DBY?JKvwwa~H~Dk;)G4͙#Q -[tvYm?cUNO,ҵesuUotuSԷCw`zr %͙b%EqAMFj_Jsh– tm5zn *D) ( ^#TseI@xN7~dE`%́xתj;b$tX\3XL ,%e<P]qXsU$j{`EsaUs4z˟Rk3¼_ۓ,AЬMC& ;ܓnm2QB)VG5Q;ٲEsݐc~&3QTTv};8p8p^ )Brq p>}/l!Kʦ6π/2XIf@ ߭ٚ,x?PovUkQ F)B ?ݥ t|G9>}\sՇcH:.K#K)]KMj_'~RdȞYi&H㧍OI& F>,0_Z)ROA=v  :dٟDF!_Ui>ԻMj2w[WӅj]|.`>kevAcQS}) slO`Z/!,/֜SSXx)ʡMylYL:`y!KW*L7֕ `,fGPzQw5f7$GB[iymk#`AM $71}OcWed){g$6 kzAc^Z€rp:5?Ari '2xhCDjS`es>y#Gg H+Oz+=OjCh4CVt iD.#`%1اY|pO(KA=omH(3{i=hB\v 8~XUaF2 8\2^ #+1 gҊoVc)4ч >=[z=pSCJ/RM]N- )eZ #X w.lZsRItn9 'O?,5Х/ѝ*7U\|8sb%UB)duմsNRZ(_|T_)¹5z Ě -  >G ҃}lKhkm3u1^M؛E@Gy3¼mn%4V-`-3jOv1w"?vp#((D{F3W"^ kT$ ph֦$4O ki[djXuϝK(<9X_rO,6'9B8x7&G8 J,H}-t tLdCj`%etػ-fS {Z -!M@*?gB5jPLA4IЄv@:.M8RB9YNvӦieOh_R8_( KR,?4h>L~}g"%x?pάc)Y0%HZo! t/K,&b{.]} h=-w>vV]iBb_lF{k+p$k ,zzJ0Ws;Y; ᏍB`1f%Z $pgn'HҔp-z:V؇v(bw'"7eUѶw~,u,vTK?zoD,hI1п9!W ~{VXd\ S=3VGl1Io}g܂zObA vЊ 1ZqX29 @`oPluq/[,8 M:-H%ExN@g !PQ2HԪBhZw!4a[3~A<,``E ?MeUItYATvmXa WXYM-p² >tVTK5 @CaMo ~1okzEXw`# wc*dQB`V [OznM)fـu[;:RIE)3)VXˎSkZ2ChyG.jVle^`ofj}6 K3L# u*D{1I(B`94V'f\kxҌ3IJZ_WJOG}N,?Y"DZ &V`<|U hZ!&J< 1%PHs;,?2쮃tXB hb1o˷}<-2EIUHV /ͬw5 lم{Gd4':#He I/u9)$E KҼ~R//Ӓ4Kk/FɂHqېlyزc YB-ݑ=YK(߄>t=z&NѹE$6C 1+:@Zyʕ4bNf F0C]n=18i8p8p/% 97~.ލpXsdV(бcGJ01˅TǤЁur0@G a5H,YK5!:0(2h2ݯJZ hlTWU?@ѣ#Ǎ[dd'C#4YB{ Ye*?7~iU3LgW<ޱ{kW֦RV]r%+ag/_Ioiafz|C(fF 5Ȏut͵YF *q7L3?Ќ0<BY@ h$5 dOPp:b)'LA}]ZU'@RXܶRBtaŠ?pX2@$a}+ yzEJRcLȺ?q@Cp`F5scde"C q#O8_Wkh$wpa=0?Q\ Y1cW2bI*P%dpEV,!8poU:=p]63PCmrp -^4^k#qdۧ.lDj l:5V \Xh6E0^q久  EIR@iF?*b\X50oϾ\fa Ü#I M< HufeBjjҊ}?~60-ts ~I{r J9H؄KxfBĄ]@LV@G볭)nP\*dH iN2&Rmi %ϛootT}Nuo܇Ȓ,Qfj*ʣ\xnA[<ՒI7T#Tz ?hk.ZlvTatՔ_L\N&0IDATt_LȔIN*?rVjͣsS7P _+ǣeH{ `7ϸOUš)`Ձi!݂k f= a}rzm= Px +@H}GlfԿ-$q˅Մ`@8bpPqxJw5n;Y+rc皨P\lƭb!;_|{ )%L\X+J- vM;AգP'JFzXlȺd;xMdYBIYʣR 7f0,֓:8p8p0@Qv"ܢG|A)iHMc4)V`t vs, x787 ۩J}'O( 6@zލMSA-˖O򊕲UƼ (ڢ%Ңcܺu4}3:r@ chmEhsA))4 'x&!R~nyM1[-F e҈L_/0oDퟲc hK+ ^bD3ȴsNZbty҅' t7\L 'o}S71 @>+oL ~ QiyC=$ЁqslH{.%˝;oG5䛣h;w'ZR~% ON6mRƊ:lh1` {j1#ީȇEHI*-2gb,!<BRQsңbnEY,( ik!#?v֙hL/OK #׺b]17?kثA baMg$i  >6ŘbMv}$k.+Y^GzX;Dog/W<O $F #-vq88p8p@122ȠR.ˍZYR<SuqЁ64;-[jV@8Fk F,q @^tC@{*3%&Tnt xѣ5@1>Ud tH~*-֮?@soҰ5Lh Əoi#FV@GQ),dFeA#2nRY\Zi+>8𡼒F8)ИW}aåM)(JЅv>h!Dn"h49I%<2ga4pg#I],CZL9FyWH? X!@EJPۄ!Ace->Tc #y7XF=X{O9?#J| zN|r8FIT!"΀v='5heGv'ԛ @?}RK1|Boi1dN)-12   p[ @c0K7MfY&]AHaAZ(塩iE\~ +R]Jf^CTÚpV3Y\y()#* Xo|xЀv< #Djтqϡ* 1j6O ^} @@vט1LOLfy5QwbR۫!/L@ڔ1>o4&՜Xda1:ҔJCuV N~űCMo]IGSqasNzeU̎i_8 8K6b\]cHw'7\KLI[h?@iX.. bͤ[>@]#6 g v6-W=5eOV7RV@?c2R &)*KBKftfVgTZժJg(88B9t虾끀 t| 6js#|$hxt-PLj_ Xe  ő-7wւ[<|=hуTK+MopV QWn'>&7P^W[i#Y@'.4"-@ F+WE6ᦖlT_H:CU~YOZ9WvU~̎ @C (p~%qA>IRT9'U&dPdL P-@:)5p+f@0#hZR"3xۗjU-ڻ)9yRӥ&_ 30rgJ 6V6 h#kcDmQnB q=~^L[5 E-}i8EpfE2=֙W5rHadѢhߤ}u'Ա̀FL i=@cѬ*,۷t\ua)4S_*kϱb^z3b!-:,d37;+-Ԋ,B AP!\T/c~7m`2BF*$bc/]QPp݅w%PR`Q_sz'HE@!yPdnPJ*Ƞ*(+8iJZe>t=,G'ŵiXI0)r$zV{M6BϘ$m>Z+o`:n'9@p%@M*W;(X%HJǁKnW3:,&`9Swј[i]E0ЙлL_\Aq*.Hbo qc=|=N|W˗OGbtfgZȾD^Fk?tfPb>ks NhD8~*Fژcޫ:ٙ3g0)>{%4԰8VЖDv=1 q1 {$0T_"HT?a `Ojsh-#&]+ (\Lj#}8W C@ !:frH"zid9[b-0lOj2B!D2k#Bx,}R_7,q`7<@: ]VOQ*-,>ZiSVvff?zשSS4)Ճ=g$]`+dҞhmMv&CT2HTMO-FuHn;Us(Sa_rvHuTxj%,wU 2*T@(Ijr+7"ljnjOxo Llϒp1kNɳT#]/^! 2Eӊ} I1r E,dعh(f:wwʡ]3;K. ͆PZ.W&QC8כ1¼Qz[2>i޳.D%.~h i^z˯z+fJIԵ:]D~.S:7r]>gժg.׈.W$8ϳzt^ #hޯ[^DzZ"7zZhpʾqYV=|zس*YE7=ӍGZry\f8F?GiXu^nٶGޞ-!7,. y/,_ ~]:TE2ieJb)Dx8y+yJ^E^98\|L xn~_yI{cq`OݯZڀwg-wϵl+?4|zNr`W\{KO33Wkݢts=*nZvPI7h-#ubqF9…S}|1ڶF߻M:HoRoPˆfpG!8 &um/rc9^DY|+Ǻuz*dU#OlQ>  1Dzw[(mE{hzkhi-飺f{R`Szp"UsUҥ>gՕD|솥jN`ŋDl\ǏeUQ?C֛?A挰#{6ƾ 73>jSØ+UKc2fowެ_ik+8C\#M"u߶VN:`@pݫBx^k*8nVw\OW @MK̪:KkcPA gV 2k7]]s=fu&mo :HN!ѾbZlwY)֤G*]X #: >HA}J T!Lqogz|yafvr|v֦a&ASXdgP/"GRhl$^3\1X9EcJ9s\Nr=W? Xح88p8p80~q oMس*fTKʦ'UhpS~ڴmKU9hס &*߽K 8ȃǏ)e„4_Rj:"G,?-ZD2@H~]T_' 4/]ϯKWE!5F8yҦ5kjn*S->KhUwǓ܆s=vt-gԷmiB ڱU0*X(_8,ױJUe=Fl_ RT _pܭ4{"UH~1wLDS7ϧYyiûk:'s|@As1wE\3ps4$`^ũPBJלu빋K~,F;: X?V=юt]fQ,ѣۏvWJOגY$y״z2%r;8ף$Ulf{݇몔)Sz-+shmLTĚUGÕ\5 >uw{ e_:r_;XϷuhZ*3Xkp勒<}Pdn=*|nsP!S];n1NDi-E/Y} C\w=,Sy%|MbM}q_+W1e"b=A5Qgܹ4w٥qc_g9J1A3;p8poxyr`ݴ1-{Hca0ԤD ~bfa=DĖ#6^ѱnplWI$uҏSBn:ǸAS%NLϞ=ԋ/"}7/,[֣~,3j٨ڄ{Cy13Wc̙1lB5}**X'MJw9 )6Q":.(*{PVl.j{' U2P` ˆN/vǪoDy?2xX7FT[! p!*Qc;>Sc W A1ĝh [1؂ Cs0~ӅyWצѪB(1X~x J}"%b>d䩁vP8_\`.sRT؟|.2&b*7B,S놞-dt`O` U Ra#}-^_m@@/>'ng@؏A孄 K9&F<2JC= W Q6l@A 0.<7>߼?%A00e%JϰJXD̓՜RAsGĊ t `Y7mt! >󜁹Œd< /Θ,DF:'TL1`Zz9&ϱ c.}TcFaؾm|S_F\ l XqHmgoM'}W TZšJfk=kk@R4S&"AAnd[瀜8p8p@Kt(D< 0ϔLYea`D0H ]htxd$`67Yt,`fJj0/ZTtG=}j߉*` 5dvInt Qٳ]!x@܇R{ cY|BSJN3넴*J(Z;gh5DXcr˱={LnNqSXն"r{pB{w4UrԏC=h' C ı>y DhIX J}0F Q+%¸;,/FEyhHC`څtZ{f" d&qVUYEyPR:A+ɗ1U?#uZD X@* ֊m&5H |{='YCh B`(0jY3MYugTc=G= PlLm0L 6+XC-<!hECZSԹYP _=|I}Rj~ "ٱo|߁U;oJMm-e9B?V&T)k(-dӆ`^OXIAnuÂu "(5?Fy(-#8Cd<tf^af̀ x?Lt~Tøiȟ˜>xctܘ|΂TE`*gjha=2w9XIGXEQ='ё=b-Oڑ/ZugN kܿ[ߡ6[MWS>=a`FR@a>ǼnFPh^ItMJPjG #?u ,@QP>7肵+ޑPLci1=$ۼg>c kSkоSx^ogy}?DmAOʈzWQ|#`aq'Z:ӊ'$7vjl$_V=514ᒡMߐ |AT( ta77:7 ڻM?e??\~r->>!x\t} >'> _=nAA[R5Ls+"+Ch#Apڬ QτƷ1_= Zʵ,0#Zi3%; hH WD]>}"hF`V.b!XXwVY֙A^B 7O6u\7h /L&2+ φ8ZK X:HRYd?qFR[&~ XC sKb` qU]F8et.BŎ`r{PY`uҽRWYSu{(jЬd;1oڍ+z-EZK~2#U,oҒ cksu, Ͻ[y-WP #ss#ɪE$Ĝ5]e+ >zX!5Ih)r5,wj Hշ=5m0˾cYw8paɁWK֍&nq,Xn" vOlq@z J'r)z)]?H/_n"^BX7p}wS =hAc `\@p+E]|Xfaai[OV Up'x,#[識Q|V`To<q}9< /pKD };m;p8̓RPsI8RnrNBCXs\`^?|]__.⤌C϶֬4, RJh..mDM3(i(r]8IGUIr~k3n]vL' kFN]y_]nqž2nZd-aGXp?x)vtS3*rM^'5#ᝇtsF-~,2=-|'דrq2p֦/%v=08`8@s8p8p?TsPsyLϒ/bIk< bzU38-*U9ϰC-"rTv8e:|qw8rsY7 Xح؜8p8p:{1 a }E9J~*džiÁ#\!Qu^ Kw[5yU3Z[~B~XGQވB$Ӈ 9ؔ40@}*}A*ޫ5"Lp֦/ ݹHn+]v[թp|rcE@IDAT]>!J# t_PB)EA0@D:[R@DBN))i7ݻf o'n͝o's a!A@A@A@A@A _t_A)% SBtTA@A@A@A B@'ϖSAD@GO.     h˱A@E@@    rB8|3S…xzw}mT^͕WH[j!mRdOAm6Yv1mgMNmu*nZtD*"|ƌE] K8hh=㟕ҏ|JWM~3=ƿD{~+>!J +%T'JΝ&sm(%E "ӺD.tωDk$ѿ2 piRo'z#B܎_CN̹~g`lQJD뗇_.Wg6|-s<)Mf6g~ }0l=(@g!_IbD2gcG?W==dUg¤D 'K%Lj<$א},eVtu;n߻,cGJ7.x\F-IA[UhQSo42@Bth{y΂͛|2a_Tb[t6;fqZD-nV*H4%:Nt`чodRb>oDDַ+ז&%+"~Pjn,iޜuǎʣI%&l|Y[ER?[#ww&ʒӽZn#iq^et}l~tN{XaQDKW?) Ǭ"!>d+.+~U>ǹm}шhXo` 罜ӋuUsކrsITz^3֙;Ӹޯ&Ձ#S !cE\zi%MS"w{G<'[vagdru5EʛE<&:F}_kZ-z߰"see=׾6L,qƵk<:< N4j,_66&o݈V31w]F&s vLbGw &BPKwpªPQ׀v= Oas ;,ݶNÚEPɒQym; MŽꝈwP!C%`ua)ˆ+7nоӧi.[[\_DǍ۷iVys|:q႕k =ҧYU$o=iR@jD2{ׯOl":p4<*S$3{4tM~8ڰ>WRN&YI9Ԭib.c}t U*޾xPg} Ƣc.+3VUp"w)7#6/Rh$VLe. YHTgO7D>$*R);7Rr*lìð[dT=^EN04?z`ٹy PbJq(!:&7p.UUyX>\dgj?@=cSFNs}3警`n avyz96S^D nҤ +`EJԶ%`]a CYs86:cmٕ*G-RV _%a]O-RPU~V?v#$^?x_gAthR%YK YAPkl4EJ,[qrqKi**2q5\eȳa0̲mPEYW.ưZaSItz\HTːgyrvK{J~K5_qur똹ˍHd<,Ji8g*"< wZW#usR <~31y`ѫ@Xx8%,^$_A@0x"yTZg" O[GC*mTĉ<ISFe'dL?>OS|2uLiwz㐘$[/J6-::TNs= "ܢpPRa->3<9V>b*W!J:y3¡{ LLtC!$LF+upcuڹچ"u5s f(! kߛ+!pMV1J[+\Ey0jn^WdP¥YoVeO*%v퐌xkFϔ+VD Ǯ&*P[I?~xwUf6V^DU _ff.(\s2 V39c{ %NzhgL^V?p+*\_ SAhlʼ"d.zQ!:A)jz[Py'X0’є߲K*58ts~ $ Ȗv%E)xN&;~{Դ ›rЏj={=@QMެ^υ,'Gǵ32p; ӘA$ZrqVlƱERi,,/5SXyh()(1rV&{e'J~3x8_ѾMevc.+=2.k|]N3@T3HtY 8:1mmBf{'#,+ U&t66@鿆I f8V.4?X6_c蕊Y+m |q=du\kpAOe7`CyUj#tD dp_'_^n_%}YPMc7A YU.#`%Ꮨc9ϲ`/ǟc J]|ۣ bi&@*kдсJ;|Lq[fn V ]Al>F.9ܫu.*z"s!6{{ F}vUUDT/ӖQ[\-ňiB鋤?ߚ`*c&xFȑy[ۯ}c1G-c[cL/~:~;7$]>!I3%wC_LmML YR8`kRYGE|sl :Xc 1>IwMx'dI+Ui&pf`AxQ6}_;u ul̝O0%vyۯZx]6" Į}"Qd\#265@$yhLvDhVO֌ċNƒr":˚-)R׵jY1*K8ڵT\To_}UgQ|ItPsl;r(Ftl:xO`U( $::IL(e S~ icև{wRSUGîRdOAmq宛ۚxn9W>f1^9vNo>MPR|v3<+K?]j̈́Ķ5syEt+lԼysoyٿˁD܃|*K ,|o>޿h FH`=ɲJ!́ Ǹ<" Ja1l1ZRb*eV1HhO+|Zy%١aWܰ˶5TV2*.n<ࣱŬwLRL=-:ow?ٜP*),>tlT Z1ga1jK;7 } _\'30ArAme/7>]m2V:}#&,8L@|(/k2["\)Ê*ĒCJ9QQg*+2ZuY ٸ 'QJuq   `&Kg(=3AV[It<+@B@0aU "CoiSUU#aEpa4kAx˂eVy>:1ehX`B(F~J%:ߗ&5!Vɣٚ9^xazш{. (ATB:}E&w &~dg;,?1D";¤ `V?,!i|E*;)J!gF']7w1ٴdrx,ϐ;sL +KYL~Q+ɲT{0JPۆ4X-ĖkrNyH&D:t<y:& FK^<16bfbqYղ쀅GS0C8IE i 796k"gs| XoqB3KT@#&:vX>`)t":p>[:BK6]([6z9sfz%Kq9t%:LFyfF ?yL8τsz[ Mk*7c"},m^-^ga_ҩō͚oWӯk oK"<2Ra0USn\A=SXE5ݽq'nY|[\)mӆ|ԩS>@7i1e"I0͍A!Yא`fw ۩4I3鍜Dfxwn+,f$B.fPA-c=C]А}ɧ|DŽ;fRKyG, FX*@7LPBxI(ӂٰ yzEJ2cLX߮!Dpurcd5V #?tRkq5?2p(j:[: 03ݛ{eVn{tiXmEXCq:ު.t.5,4@z0rS--db+&\Paf,9Aʍ ~ne3\W m"WC$he~ա&pQʅ1ĢAM`ekH *\…A-f 1ݢA$trc dYpbk|_xs{X@>9ʲԽgW{$DxROq 䀂Nh-s&hhp{2o<0Bbq;Pߌ}-Ml1IkLU|ݾ7#QI-"diʙrKZJ_8=5[3po'm+ִZ+3߾ KyB`i1e*ɣ\x=ؖe"TLhThi&Zrש@:ibe~??UT"9+ugЮ(fjW+{ uȑ(`GbGg@E*g\`L4oۂcMf={Tewm;[V@c2&"1"hj|0RXm5Q%+)]O0'|ғ]׵ >'99p8KX/?{AVv,q7eZj>E: >`Ʀ;!գQJ]FxX9D:ގgi֭4q:wp.,`Q(6.P >A@xd=& cE|M׹ōz69Fx qn fU`2cVd.5./pJ&F*%p;U;cl X=ʦ*ћHNt*}^}Eoӆ%OբcĊ47ɳt( D6fKpA(߫zi4?Gr ՜\r`onCt׫}{<[o%׿ds&;EQ8Y1,r[ ?X{Y 9cT3nD,;̎@10{ .R_-k(*0^+(WuaXv38\}a/`V \FTQ84E}F^.ǜqR浢vCY0"7r@B&Xp>HCyݛ0~,U(.JVSL%VhW%2Y:KDTJ(1P0 fg"h4\c0X8AR.L9)k>6c"5|s{"?`A<(2!EՄ+_+Kcܥt  YpA2dH`,aGroa5HH}qmAL9*%X=٣K N,d{^d#f b@6T{n_'y}yƃm8 pi '#v_fY6H,Hw:-GEs̥/{[[EDblNۛ@kē7FhxCsp%,h1;ڟiOqu"Yʉ+`:zo"UlE"4\yЗZIz#8q"Y,vFD# -a<ӆw* DqtZ`.sp@fnE) k|3'Fd ]rW\]#bA01[Y: &)wHHs(g=c'm>??Gud:}8ûƷ99^ҊdC!-A4q!^ϑ1&A;SPVi1^GxX `~0X?5g&Fߌ1m12 ,ԨcEoi PؼH_igi%DL:‡bV,h8cƼ .m3A@]hCTnOԞZꪂFc3L7 J2nq.Z`wנct6%+JiYզy5JJA pWW AVV&=0J9Zv^jwca~7˘k)l:TIXBnVʯ`#PTY~+QՏgS 3`zk @ހdz#JהrH6]\8ߏSI,I Ő/;n0.ǬL_ c/rH7IF-h7MeY]|,Zo+m2;biт2}h*Du\VN;Q4C4?YoaѤsN z٧3Zi"Puu,nOq n5G:zR1:JMu7!7S n֮ VdHbM ѾQ7ҽ(O<N*5z# Ξװ~؟{\;2OЯ_o#H8,1@)-@e Afu; 2-fL|{LLJ`p|e, \7 }Bmsu0k`a261 ;eoXò{Hcab֏yJ1-\iփ{  E¾]M]eV챗a da;dU5 J,1In dӽ*s(WC\+OĤ$e3N[Җ}/ŋ :t<{uxcSt$z16bm$c_~ ;TY{BthTd- 0!HxVAA*AKvݐH5_z},сv`1 BfL|\v:Ν-"~>k)b#q":`֎&YGcutgw!yX- ꮸsbVD@l1 4҆2u&DG}yv0 8_+&exv.B֞tERu ( @FB@& fzH"PH ]^hXC]}= fΖFfiJEy-$KNm[mK)@ Zy])ѡLw<.'"7 k$o?}&uLF(?H9lWc*ԲāET-^zNOU*%,kXNOo=!_S㔛4_FV>sƸ6RL(g-6mkA1R.R3= k@t3gnڕtf9l XN 14U,^Cs[y X@r$3H77"lkmi=M*վrX7Sp?~85'98ez0-LlßbL\"lґvE,d2J%SߚJ0[fk$:1: ;ǫH&}r+m߼}4H%:𐗆g)SI&4zeyQU!?]kL0)m|_16bQ~۶%X ܃WɞƳ{RZAB|9󯿆7vub|uHqB&lrΝp)SB^zձ|ne*u1WݡKٻkcdxdMV|wV4]ퟹtkNsFkָL[Σǎ*|G˗oW}&F\l,ؼUS%Bens3 u~kqlkB V~=w?٢Js؟la4 mCNl_O< +#wB@iJb%nz:כ<]e>|KON3 _:UY93Gm_uy`gPMYn#kaFw!w<}uA;ݏp>[w-koRFIZ[ڿ\S/Q~utJϫz锰22!9|˓GT>toύ}<4g6Os۹\"Lљzb?cmJ14zq8[tXiݓxEu9|À z\ A?Oo9XLӪ3CzdϖZ]cv /< V|S)͚5 h,#ubn䉩t_ 'sCB-sΝqmUfوl}eN[9SnHȻU~O\P?O,XXr y]5Yw>˩\uFƘmn2s|oGxX""n#۷CMUH1cB2t,зd/իCN:_l@#A@l`j'ၗq"&&Ν'gKI_[~RKtOǏ|'Q+W! .={Ze|}|=]ĉIt M {&:ЩnOw>g7GMh(cC ?׸y#` (Qe2aKĶaR@R8冄|R}#xK-ZI{smfロpRUN!ׯp$(X!  PO2;s*ewm+wkhoDw:(0>kwӖV4*rA ۸[ҵCscb*t6]v|3'=턇El!!o5T=O7 [o(ẅ ٽݦ/~k}mpqmU&IйJ;q}ųNTq % 6}L"߷;A15q ZF:%$xè PM .5zҋ&W炘 U{.G|]s:X*ZCi+MM \vE\ :FI:Ĺn*yin/3VZ5r]nR_t}5,uNtft|C ESGW1X/ӭ GWƗOҢuUb‚kTR3,>a;!7מhGv-)uش֥[+M/@uYTzrr8bXIp[fmʘ12eN±mZRXpjpwj1>qZw{ :VBx:z[.{ޖ|s.?WAptaZ{/S̸ qC>S'qS"ۮ  qL׎STUsw~K :!|;3iM#cLcNZ+W1eDxg0َ?۵Ow>.QR'LJoTZ oLszy 86a&# -KI$S· +!erpEY#j963gN'cNt5 r/eDuKҦ+[ϸ5k(ca$:/sf9.W/O^d%娞jNI ;.DPFs+a :yѵ ˩E`P޲+QÏ2u˔+{?>J=>~|vk>8b q'kڂlIYq(>{LDn}j-sv}nވdXK)gE"IY}Q4ɤ>Rd4+&B`(.:J|[8X‡~(6? 1 *Ž4?sps6([iW̷xDNuUBE?cӉ#"F$U-^11+<2~ưVz~B !,c*l؀_V_ y"*c ђ=QJMSc lR1O4%Ӯ#EooS-Z|i_xV8˒N%K gOD=>R%?ȗk0Őb9fZVE"%G o A@/>q{@cG}o={< 'U}C>|-Gt}P& 3?StloEG)vTwN]R6 Bk{%pu!*4=ptQ]a4(3ZȝDHKSS7GPq-WN\ݽqeIFo ɢ1olHވW(ь@I+Aʼn& */F̣w k/%B1JKNx' n&cprz&#=]ֳAOp@OyG~V 1ț<طM[֡PqJ[3nL+X#k&:f.}w>#Պfb-O}5ۊѽY}D(vٝf@}((:i wn9> 8^gX.Xf_eVWȪ|8pbݴZ`F328_oҜ \^Sy 8V7H'(wELSn eiO(`<@q .>G-WxV |׫T"&Tj;-Ft>0o(ء@k6c<aT #8~?yAi-kAo{n_mnUDi~1싦Rɮ}7Fˍ%wTs p^$:PI9:+GôeZr,kdHbս}<@訕h5XK~mhemYƿʚׯ֥X^&)j0p=@y纓`@*©siI 3&.{yNp@q , #za 1. _9`L8(\CZ|=`41ID)xU9yNZa},#mdW#NXua{}_  {6-ݶjcFvA. ^ 5ij(ˍ+U&ʕFBֶX)߱uoD H Ц_6% G[8U-P{>7ڽ?E-s}Zbm#v *ܦ0!o2GJjYp"W^Ϛ_A=ꈪJWxVTJTma+ 9҅:q%z9 ^sʢLpp P|\_QX5@aiG1_y#Jnމ:Oq<P+ .>gDGKg;% (&%p u+"o25s Ƿ -;Um/A9 \+8ij?&B.`Y^T)W;pcw܀@%DD]r.QD fFDE -?bzdҪ"i*Bf;  .qO8&r^\$آ {g.,`ŴfiPv(bB?gfcB`н2BBz,MQL]B} ĎOm~}R< @PPs|L9 !P(\3Ȭk[[e*R;?g0)ȲGt}|q tŻ4hy  ;&δ,><^BQ$?(Y/`K7&Qd>,́i㠍t%~:?:ƽ#xN&W:W1Į)hcouC c`$}bc4X8cbܩA7T{a}Ot[Q}}l{}_  <DGdh[^%.r,Xnxgز0[ďR'IB\1C%wߥS6鋤Ã:GP ܤ`Znl5̘2P#8c.>,s`˟v}B.eR6p$\D`zL&27X].IRg3Qi[<s 2gW^qy|`Rš+*\jEnٽ <]_.dLBȲgMtQrYurIuliGaL"W_RLI,o}R҃ >QO0vF 5X3;50&~H;68?` `=׈ @# DGc*-  ii fY@If;aU'>1=,E!:|#YM?S ݒb r! !_479gw*f <} Ũg!-?)ɓr<-"rR?8e!$㍀M7 ^ } r  !Џ-F⍼[*hCC76J0Gf'<#nIOakm '&+5G/7`S~Ht}߿JPne!EAqB@ƦӿsфEt%تROA@!:…H     d# DǓ  # DIA@A@A@A@A@A+BtxF2A@A@A@A@A@AaG@      WyҿEIƍD#+ktMʘ2%Ō#}ڹ}zꩧ(UтCtFA@A@A@A@AA#:_O֭T0m0`=wF\I{O WS'IB5  R8q\Xi1D!!T嗩kٸ{> X68@g\č^྽_,ΐNїSxa'NX.yr??H.L3x`VrbŨvѢf.?ŏk'3nJ֬ӗ.1THQ2Q"*ϧIe-     U?'#C{ϵojTJ?JK>Yf"ԺbEe$SA@A@A@A@A@N*c|TB- bӡCل .kQ)%W:d;hѣ6-"d MpWՠT)JݻiMVz<&;foO BHLLxVQ@(h+ϪVʯqz给3sp%5];FD I "lą ɓ     ÂCCtgWI9fꙄ GQbGdi3cU;K(aj7n ',7YK/5jXzqMvc`ӦvxD%&Nj0A\ilm Ö-oݢ"lw̢`حeqR0[6U=_8({;&k2.^ 1_u%OA@A@A@A@A@,"˖5&8R1Z5}?|8maD8+)]Ej0`+V~XɄ1cҲMɢq4@J@"E_~wUx|e%(]ؽEtU iý! .l     х@p95ɍ1,+M7ZVR{vGӈ}>q"m8pڝnITg9w.-/<KѣicV,W&сp5Ȁkָ\QuUJo.tKFlI۞YESJi!:P?9$ _}b1qK\L/qkFPx$xu<#&$OA@A@A@A@A@0":n߿O:9BpВ9rpo_R]RqX~)-KA([@~e`/m?XäIVn;Mʐ"ECt2pnL֔$:TMx]V!ȫLrlwF,[t]pP^ח      P`7=Xb4 >qbs݌k }do A!O`Dv 9sh }пnjm2(q( Xh@:֨A7Ȃ͛6&"իVb Da ˎvcҮ'df9legKl}-9& ҿ,"     @d!0ѡ|c'Cȃמ{˦!_rlO?@ 8sdRdU|[d D61l2a֪E9p]pE,0YֹX܌dс6ְ[ ר~RֶJCKTh{!ދ񇠟)[ &7&qLi&odNO8 A@A@A@A@A@A 2НVlg-D6 }V'[L"^^Rܣmם;k )ϻ׭k+AD9`yK`WZ٥$+ߨ-mWPˍ삪.B?`z6iRzpa=sqIu\Xi*cɓrH09H &[z O~ʱQr<z!GaA@A@A@A@A@"@ذ(8 9(wF5^I6[18p<9z̖ͣZd7oMX'g-) 2|rvۺbEYс-8Hދڽ{)S64g:Yy2f$%,1hdRMy-|^Uzeja X#     ED*ѡ9pZzyϬ ҝhtZڝR&qp ˚kn+[ > [?a.[9xzeV{WȖ9w.- #XQLVEØI(ˇ sjxaIͱ+0 n\#0QUE>X;E]o:Itت[ٙ`zX*8+_DsjcΦM<)}D)9O9uV3@| d5, A@A@A@A@A@A  sG"pqMg(֬R[ :,@Dd7RI&rkC8tNZnAЏ%]ZUaeiNk}86It#b(AҡzuIt`ߗ)V>(_*n“6*iqZ."     <(谟`hh6_@_MJ^ (TȵtOJ4ɒ+=G qB}^B>Dwx`VŊQET/j׮0~޽,^쫸+MJj\}lvزen>uW)#wƌT,{v8f _J^L&Ŋ+R7뗹]>v~\*`f=8`:\r.uxII,>@ޝE7/8 C_HɃkZby'߭;@Eðe@B$QD1qjD_5 {^!9ls&Jɼ޾)wiIA@?{W/EEԣ[$n;EA " -%!( J7g/H7_]g="("("fvMɁIJ lԻ9aJ϶FŋMٲ)Y3M$Q#J(pɓ5{Z}׊nlBȋ3fΖ*Fnxn>5 vO>Zu+ϛ׸nժr^lϱc:q"ܿoWy=.qbka@"G*q!:gEpkbmz*q} yq(Ѣ mH;u͌V+U̗k{w!+0i(YVFtaF@ߺC+ .#}Z+q uE@PE@PE@PE@PK<2 3֯Rq #)đG=ujdR٤CP __'8'E" O7n&ÆJ_D1bEָ[wsX#]0" :>ƈ8L 4FR8}Y ׮aV]dP:GPlPbD'QX/Hٲ!G)89.\QDNWnľ'1Wpu';&fsg8F5y*opBPa%:z8l͇@p%rq` qp^P'=/_0~:%ۦNʤ5-KHzEnΦ("("("(##:|̙X{CH.i|Mt05Տ74$:JSSro>_2tM6EތF,DUnBRP9dat \B3G$qh$V5|qݥSqcGS%Wʕ cj͕+Xz`XDt8/@t읳?2m$y)R+Ѣ4q%3zUtKdk&C#t6HGY|d{]4d|4(JGPE@PE@PE@PEFGDaEJ$  r|TD >ӥÐKbڵVha!:ߞeW.TBI E#A oL= ի1nrk+#|jyvz/K%ʋRY)$ !ĴJѵGJԐH9L 3 aFU>(}sȚ#x.sH](5 ꄄf!:Nl:a̎3{.KRBޮpȓ D|VB#e2bčlgܺh8?[sW1)ˈsb`",DeHǵ@.aCu[~ԯ8$ zkY ԐXX4$d4xPD'Κ+ w0pI:PJy#7)rJp 5F팟&B"gEg<b ys5(ezDMz5v[R)"O:x` px?pO#eƷ{?CtK+̙Ϧ5"("("("(:NtܓǠK*1Bp_G-$BG=}TDGg6h.]@[i@pZX۶ܹغ"ޡBш tbrNp`Q$_ K!mǖ1[ZږAyݔE^,z:*^nQw'qxqFHϋG/,Dǃ;`Ѝy+ܹ0UQGh1R Pݢ:>~s~9@ևw͡Y *< b('!%h*ީeV !< &ѳpጙZ40TZS>q `욙[ly3#M?i5`rވB"y iGȘk{U/zo5П|?XX!0ތ0{$}`#ieZu*/R,q V}QoYb89处>aSqxadf 5n1{΂#4Έ4 [Mso YQfKIy4w"/uйi 7hk8g+0UȆ> J M3~;࣑lRVGH*BfHdF&ZQKg+(;Yڔ߁+T'kX)sOެhb=Ҵ-0usZ&9~Obeͅ 9B+ue@_:g ZϣEvnxE4VmHbtGk!U.R伫S⸲54ιFnt 1BziT> H"Sw^7_MJnʍm!RRBNkSQ!'\b#bP.z/'9Aܼw7h+ԂQ1a DPE@PE@PE@PEB \D^fD޸ bٶŔǔ\*)^>J#kT=`@4@HɉЈ1chpnć%mrwFcmjE蘼f .[fȕ%ֺV{ GAtp͒ٳq#m(Bҥ[L +XZD n &:H,{]N̔Xh oSۗKt+:4CEh8<4UUr(֭*;K"c8+"cM/1evEC9RyȐ!)ňXJF[s0= 5\1aJg1 v92Rה=/m֞3k_(3_BHu\jΝQ]ċ6}3#)z{uCǀ =i+[LƌVGYmQPE@PE@PE@PEG DǟBrdLiEn4̕ q,,i@zI]UTH |SFᅬ I L1c +rbApޝn-["Wڴ>^q!r^8Iy7ch}FLjhV#GKNKtܿu.k'SN#Vw\7_|YblQcFnJl}kw&9 Bُ3:גa.ԞZ^"ho}`Bdɹ%:|a3BnBV5vNO;[MiI”=6V̓ꦍ)҃B*YC*o&@nL4K[Tyv_W`1„&!p8 `H/#$h~pLu{]I@GPGXQ3;醋1UkE@PE@PE@PE@Pg0NXPL.Kj" *< cΡ.A $X9q:V) I$^jNf9[HJ+Xe ?ߞ% ,߽Hݓߖ0>qsGдwq6Gtם^9qέbޛ>1bZ)Mqڡ凰ghD2 DA밤kl}8KbqvY5JPh=$:%T&: SgsZKt\p)l ƌ("jDbT130+)FG-"~,()FKďul#3qĿgט߁7Q*hIL/T郶Z,az=DO>jgN8_gapN|)lyasB< 'Т mK+~}˔A#.j{QI":OHtfO!Nu Ik^Jt 6 <׍ ߄#9@q'{iw}TܜNiI ŔTތ5_1-%:S]^À}ПF_i k'w[m?}昺k m[4Q3% Zˇ BjIMPE@PE@PE@PEC D0x@IDAT | H$ByzMz9rCȓ<]DےKZ~`FrIŔXv@z.]r`J(o$Wɓbt0.k]?}_V{JmjIZ; J|X"rӪOþyz_DGIaOpF,?E?~=3`Fiۭm"O )q`ꁸz*Ȋsi$,D猌59ϓLtS X:G(ƫqcNε2|m2&E},".BTv٧`IJz i.7*˛͟ tobZ~= LW ˅qޚh>֐4IR]r~Hn䷒p I"ZSU% %5E@PE@PE@PE@PgNxNxq:)iBʋDAgzE}Zo3Sܛ= /q.>zġ`$G#y2)T[ޮ!b}>{ %{9_/l㜕.4_Dn"hx2ύͫ| ژNi&sN<9KH7G&^{/V#26%:#=S1e ,|&FPLF=fo$ja(_mFr}orwzO8?ɄX~< aʫ9Kc05B΄8tܦ5ˏy JwC"N 3!~4{EŐh1F2zQEPE@PE@PE@PgH%:lLG1WtOcD/$>\D+dCVD4l{Mٲ4MsYRO?g!h4Crapy+Oݺy;DG9yrOo/!Y2dw /41͚$Rpat~_q-_c@%)*V 6_D+8^DP{jm**saY%uվ+-h 9zʊss{+,X>DBX3H,qDg$6騘*$m1`|>-$I.ט=?`E@PE@PE@PE@PB@`N"("("("("("$"DǓE@PE@PE@PE@PE@PE/ &("("("("("(O"JtF $ @QНցEpl1I: ڨgQ_7Ѣ>$K"@!p=Cts3c ?3F/^OW~<]VPE@PE@PE@x' y#MgDmG{1$@7:Ip5DO՘vr~8IrLEdA"E9+}ۏox A^ uҪUyU#ztL!ocە+"'O[lU]`A(P &\3gsvNu BYǭ ],>?cŊx5{v?zF i&$Βud^F߾t.BX/Ea-|A|MXF>oϘR8i/E@PE@PE@PE@ÿb:wƧ3gz9x~ilu YG{C׉q}6=z N̘8v 곟ݪU57>G$65^|>׬XR֭ɂ-[V}%+$Rӱȑ`c H]زn֭~OۭZ5TΛŇjv>ނٓÞZձ߹=[z`,͟,u!@lkpsܸP y5vm 7%+#oKͩ@tfD@ahN{Z/~RE@PE@PE@PAdm؁/Zu F矷)&D_ʞD#0liOxFtܼ{FĩK%c)Ȇ+7obɓ+_Mtܼs3֯.,ݵ #bH'dN2;.؃4??V'Ư4A~B":ȓ1]J$I}fInΣG1{dJ'A),dlȐ,HW#Ҡ D 7t6p6fnu3⣲D,t𚟎l,jwn %8VH$5y^&DD@"?tq\۞Ov?ABjS~^'"&)KMh{vE@PE@PE@PE@ODtk?G޻:H"It}pxE y#:ߏSZݪK.+;XeFzܳes  I*b4?t ~w[qNAqF׮x1A2NDt+Z&KʔqcQdC(+Έ5jܹWADž{]ulx\$:㇑@xս_ ! )wn]7K$1^ \zt=poy t6=.5,.8Ź@K7.H FsdQl_VcQ'b!0≑ &FtPpD:0¶"]P/yҧGތCVӱ?>qcWaEQt>^p|xŲfE!ChrGuXc|`էmrhXUItIϞ]N2` 8+e A@95@c*ҩݎ|O‚iʚ$Y`) rͼL!gm^{>sI|؜/keo A#c&#cOU*k8ۻ $qiβr_`PMl54B e;ݘ~W>z-~|ub ~iF} eҩHjֽ( B6<_v6gݾ6Q,<O4'?U3=<293㜻Ob $c[ &B"+޳$lc B<=Wg4 a$8HyÁ퟾%;h o~ܼw7z]ЂQ1{]hOE@PE@PE@PE@xxj&%z-ye1FE^EC#*YXxcL4|4|!SzԬi`fIٸi¶(e`کj<ٚS46̘a;UZW˓`TtN(cGJgDǘe0exzx0?cp`$73uU- ^WI 7L-5( \9zuձFBtPc_\5S 138\ߗt4[&%fGS3 f`;5II TRցm㊎ñu,o[F`No?oRWP[1\n^g5ˣvmKrWt^Ӊ*;R Nx!I'į[K3ωOɗxj8z{SC}q t*iT]2ӤRn1nƢiASf},oc1„)˧`Vr ^VZc~q%;5ɘ W+SGMȢa$m:D9 NkEm4AL7Ff^s۝D͇C7:F?\b-FF(N{P$F E/B(Ϩ v:yĐH,3R4 $h$_OWҗ}hj{6e~9vc DSy=i.i^DJoU='pp!I֣r?Ϭ2)j$QHT1?DxLFFz26:@yȀQ5ԏRSE@PE@PE@PgSHlB28-d0ra\H(Q adISک`zX)9ê PC#FzIX )S$QAv6 BF l*+1oD[J S[j>IZPe8iN!$05̊LzO":*o٤^t+X9,0<|+m{Btpm$DΣljn9Wdz DX5n#SLhD0{leD 4Qc[ġL8b#ߺ\ sԈffjhu&8R+5 n?5LGf*S/wZ'?(SvB!opz&<{ daW i-^X9O1~')ط(;z⍂#XULt#gt3*`Jza@ߴD rĴ_r:g3L #Z灑/$H2F̈ ♲%1(^db<,{^ o!K^=I_F0}޴{j{ڠa2 +h}f WIy޳_G9;Hg3gʞן#I[ogTWY}D+%cߐHvzj"("("(" TNQB_LSE˜2~Kt8u G7H2cV:0zm5۾fi}w`:vDs1S_z豣)NO9煩Rq٫)#i|ICcZ$F8"s;T:cBB$~&RV.!MԖ(t1rezi0$N(B Мi(MO?!>79NvbWD34K6CRm"Q63M-茐`Sӂi : F'={!igZ2ʢo炧; i]ywS櫭YRu_$ M1USf0a͛ e"=R~?]_[$F-F(SJQ՛XHh 1Sz4e O4Ӹ1ƔY:2d%6>$]ǰ{zTE@PE@PE@PHG :h*K$l"B$TSK_}OK&DAZi_LD jl 6au0 i6`;m|ʟOz ݊g>rnPw2=G:3mv:.oܑaʮY>sFo8 k Kkfn:㌔"|/b4Nm `J%o#zٶ !I$PbO*Q!gYhEM?=RI}ޠH/sڥ uXMC^PQ(}\Sv:5Rg[F=WA6ص#V4q2%'-h? 㕑ۢ \Ҭ)c"\cѣw֖(Y )Q !k^V5NjDǾ'vhOmq_DGu4 [ŁKl\$ƺuFt0\ѲKt͈7=j2fS%z)OѱqF,kp|qn2:o-RsuXCO[Sw9y@¶hP5KΗO>Ù3mzTE@PE@PE@Pg! ؟Ę-[PڼxZ3]ˑ D!:NcY}6&O&4KC_+0ixĪϏ4iS׮ť7W4M~z}oF @1r۶:'Z ıItF[ &tI]DV`Vȧw0qo>d]|hDǂv yf$~11::&4%>J,i/)@-m%H;9msy9?{쵴8?.z 5y{D41]SqTzS֮HV>Gtk*1€b$D|(ۜ ҋiLIDNLITqK%US_' WPcݰD0Ց-!d=S;GI*;b1R73xL$ܽMLXi6)oǏhNN3 ^pGD`B!t1^y1ǴĽ=/Wg#UŠ>51_ꎨ1ˇxd;--wXQoq"`H38韢?8K^Bù &+R<9kDݣD 1F֜߳i!՜ΒW jLȫ ~&3P0dרq' 6:kbc8ֶo>gm_ViChm5~pg:ݾiR=x`t0i$j*g55mL `=hL3uD1 iF9h40qŹ~DPx8l%dO2emrrz Ԍ(YD"-&^sjf{$$f$>nm2R$ Z5(}yd9>N>=!7#;HOME@PE@PE@PE?SOthTR-(FI,l}fm])[cZ@~jEE=camDc;&:IڌfBh\䐴N%}ytD[.RVBTs e3BxOsPVaؽgδNn"y5$}s4{u$:55/^ǭ^I K)S⊤!3ij$,rʝ>5K"j*SiPgjt/=.h=,c甝xTx)≷=+;0&)Zhy'Aɒ&2h~5STܶ+ǮX#nCŒ QutUd,np1[ly3"B<-"cDu2T4xLrm6:~eG,}6N57]:?=l1F.AlkZ"bŀkZN$[JG/''4Q t8S?aD(s}51b5`a;}25HBHpjJTZ$i}&#$꫉.딶 +s3EdyO5e?Q$h_\fkwπB{K}  t>3NM@I*@H}y1ML=Ϙoև\ђ>K5#(O1rۜ#UY7vT)'a47;Z=߹e4gH:#d +e̜3„v/sjc>,nHr(*gm#<4<\@ݮ$̧cE@PE@PE@PED!:L0J\$ BB sOwyL]{ZKmc7C/h^ͮݾY$@y5$_D:{6p:O $z#N: y^)O_]5I&hQcF/lE$P/ `ЬCq`W|&P\:aucF @"{XݲVT?ö݊cK@k읫vY= >EM|Ƥ,~U x IT8 3 ݢΧ9 RŐ|riM rz8\<'LYĴJ>SN=I6>s:)_1p*YE 尩%=ӁMc$]e6c3 N|LD5\DۛWq:ؙ^{Ĕ8ӌui= )=-Ӂ1-IFH"q?75cLnp !&>i! EBB4 SӜbv=`LdV"* ) "&4 q{'IĴW!)JV?8yĤ[]4D}ӍNѤֺp -Byr 3voPT":WE@PE@PE@Pg|?V ))ه*ys/mIhgit7>ohT"Z<YcD$Y-(7$!_d}/;vX]fvjEx'BFA:gb_Oh ;*xRN\Slk^;Beu);-*p;Cl["W㸮U5>>2OڎdQPc!I&{ݞءYd8p5'+M^}8=jc!yG䖓8" u*u/O_Ǧc\;uZC#:Z_ύvl.tS4&G;pylj',ٞ(S"dz1xf QQ|q6q`5_6N(&)lQ&uE=5Hp}1>8MDE[CyKspNa1zW Br) wHY7)$Lh&au2FeLSHهe8ARhbRHN0¥\cwmQDtFxzd^h]>26&E!U($,F!J:kI[%D2#:>lnv;e%ݘ\K]'ԧ=g~邼Tu'H҃Y_rMGaL;H ;(Ԩ_pugMI3ךQtއH"Nx $~ξBe ߛH\+#YB3F05BLg;B튀"("("("<*xTH:@8`Eh>.Rʾ^<_#С<>ߤGZ}:xӹFw0#&(NgŨR[4Ę?zʵu("("("(" %:|!cDu8U}筈. nrt\U1q^F"KF: vJ 0z^kO+ҩL#]^IdQe1sK `Ch`ڴl5"LF3ԔUPE@PE@PE@P2Jt}&%@IDAT'qPihfJtУmHqӨAs:mk!JtWuFS,zj^ Lf=UI5BE@PE@PE@PE@PDWD%"i 3' av61zO(M͋ת ^1;W{BTn$,ok3*"("("("D"<(zHc_x$K'fS_1b'.[/RPE@PE@PE@PEFt8Т"("("("(""лw]K"(O JtE@xRPIy't"("("("(CD&:qRtjE@P DG8Dx:8;8PBOǦ}W] JWPQcF R$,mvTP'iwE dqrkW#Eǀs׋L1UU.8H:9ӆmF7W,_k/A6z0ObJZ6 F[DW9 H"8&GINokM$iMaa?{{@>>}9ȞRUͬ~8oΟ1sJ/|^b>nb@/}YD5gd? E7@dK=6r5ymܺ| bEC"ugeȸi{"_ؽ (U>/"3=œD4clܺt+.͏*V R'>~i諿%_7yx /ɘ~]Lۛ݁ՂKPXn rt sߔtk߻ Vh%l\0y9߃-ˈFAoJMzBLAGSgǺm̓yXʴFP 6>Gd@7@_ ؅ h2th:NˇBt,۵˟Az]Ď}IW^A5h2lE*xIϔ) A f͐'Y˖Y}6Dї^ l g8qSnV8ϡX1kPzEY":ۇ&vtQl*+a/ ^>;Ș}N~u!5s׭^-d{ $ i&?Hb׭v\}eә}&?H\<ɘo?Onܖx'u:^_  />$<E$ZhjymUqv>ނٓaԂ!p'{/@O;wa\=~QEAi'%,;.VE@P"%:"K3NǻӪϏU>qX*/  Z{({j\v-0$}d(C0 Cy&:NV7n`cuTq-x]<2bX 4%˿N\VQB8mNMٲ`}Bpɋ)% #"p36K"rd_:>KDv`N3UÉ}!P:2+f[=,[P$Qn;' ;#ujsQm~۴@2W:}=V qdcSApi 0oL˖vǘcWdH+湠*;κe'sw:]s@s@s 9樥9[/+L!)k !44nߞRk$O-@3]j.H#Ot4T班[i !K'-Ѷ$|_-M:YCUP={@,Tk@̶~sʖ: !۵n:j߇& tD`@cȾ arыKNY{8Ļ"nr=}]8^āDCvKTd3ż "aQ.RI9?ܢ܈yl’72o !UpvUZ0OFbˆ"<6`,hЖGFY^o:"۟Ux{NquJ<8ͩ!LmCI$y x0ѥG*p ³+Z# AWRsޭQDݗAbp啬`yAUX#X^a蓷(щD,?ۊnrKX 1Lu`0bp:F~i).N_ _y{ݺ怯@SwDߞ-"T}i-164ϱ#JF6^@tpg| K(; /mYİ~U"׀G?7`✵ZVZgmCs_IK'W7hl(:w!Nk:Ѻ%[*4jN`^ڀY94!ftZ ՚p\izz;EZ*lf ?zbʲDeO8g7D]γy ~\r/LCÒ`Wy>5,Z~"`&>{,WDژDcE LѮՅpm 1h_,D0@R dG oqK1ůg+2i#Yi7P<{OȖՈ2Ad`3?Kў|>~~wݩt;Rs|H<]~2ekfMce[wTBo i#6X0&XaG֗Ik1n_R-|+0J+/4݆s9a =Bw㤬y]@KD٭Ӓ}7"k]_1᫔4GRP:%˕6F\u҈/u<;e/x}T!?LGѢRrQ2eԽanpn9,6t2L90a^aK'6wX[0X@"?|zi#=yWҏ޶I:Id_}yaGxd7AC b| R+Zm:L5s󈘥ŵ@ b[A8q uy}yު!))ܿkjX^S)ƞωr +qi`T\ŠsU,Y,]_cJ7%~?fX'Yb=?K89A*"mV!XϡT"x*X”Š vtWpBG}G= do߲1w ^;B#_ _999^@|3%ǎѯ pE[!KZ3ДSiVAdC@ ]NPXݏߥӫS ,-qn9#]ԚSRL-׭7#~j/N9|&p@P;%abkdM$XFjݠ {7,?S%́[C U¢7oGN"ڦfZuQH$@]o_p >ܼ)_'  Zkb!qDI܋5pMEh,@E#Tͷ}sklX~{ek-"oM5_kL{]Fy&(lK" `V*( mУ+"O&~NJ#abanͅ-/;c𕷾<9=q=?%|޵}Z-X8kD+83c&0.Z~ Y%;|cY~ PSľ7ߢ=Oy-kvRNEi 07o#H46%Zj)^KCRY!n4ZYfntxa/u͙ؑ'wДRSJT_i/;Vc*ֵm_F Z!Sx]$|yKW$nGcq']A ؾx_\k a:Vρƫh$ò ; `h \{hǗ02 I=T ~A]獫XCp=kP8iBۿ-\`Z^ %j􍵄8Dk_T)[t(BģairA6$J>jYmr%Ӱy]?q]M%$|_ jv}8%+ Tkv?= ҩZ.a0 MEIBHZI2lu `<dی6tKKr(+/k+]gg;w=9`səҲf$ڜOwGs999@cGƔ{2DZP+nP3iž}4`P6*İWβv")1;@Kv81cvWcJV"FK|[qIiJEE#;{2w/إ? 3Z䮟gJLXCP[%ذѕCWdq#3® b竝)V"W5-HW< Ih:APqqEFH ,A33f6n4&n`)P +E'NQ)wܔ$kCC >3>5IQ ڭZŠ7XL!F38cE8!B _a=2NP|eA#4;n@ ` `Io(h:k(=Rc MIxVt !v.nAvͼ.g/|O^YШ? `ý,A +آ7_nʒeVk‹|>&Z> p`-sij?v]U8eON+o}}\:/-ghDB :06}3zy$ܴI <GT¸B%_ΉACMO$nB X]UxluYb%oEֺooQJ 3ЃM:|D:?c?x(7U\b}ivzs ke0Jka`S}Zu0Z_ta ㅎN k@A0y+[tN'>!8Uz$߾<{ˣseK |;@;;7@c!S~kfjZxYV ho Ə~Rv +xqe<_/A8`Hum2_rߋ kP[A!u}xL3¯YŽR丟{ C[Xy+#/$tVb d.32WLu81L:o8TB(KrX̓Pؗ~_ob¸Zӧ-UfE8gHךB# ϭ E"ԛ= C DVn0Xkq7WDd-(bxAa. ̟JyWCXb I/y9@U+4jPtPAB n8ȓ[]}bHu xTʜ$S!_ _)ֻ/yŹzcs;' DY$qcܹatpq.@0pbZ@ !$L(G-_N1cxlmw}:5ua \Rda}t-nv&!w@{ߣjSуkh`ҁFUS4XހޕjAV$UeLm%\Cd(AXh| Q9mjFI%5Z-`nd0l<:} JHѪYuo ͮh@GZfD*68R-yҡ-5<5wIXB(wJ*|psf.LAX@b%}6 Z( f^T7H(l,DX@IbK nw$- d>YXlY J PW,@ /0 ֖иb<C/ .X@JD4;xUp״:5q>CЀg*܆SE*M\]E!4Bf#ܘ# PxfIY=A B8,` M?.⼠Y ԗߑa;Y=w~cxApMפkCuWg@#\ 9{kk؁걧XY5`G<iDj,1tQy1S 00BT+y-'4#fuM)z\1hBǷygrt?ݞ%0Bᗙa V:S|vMeXš4$¼cxtmJ_*=5KҠ 0%O9Bd ׍ oǽ8:fmSRwCw 쵲I'f[槊c* `5_ϟ VL>S+ZGgf1U@\_ \iXx. )xBmij:G=9/HcL;^@IP@yT׆Do]E0XR2(cp#XTcrXg&浮摧ɃOiˬ&f]@@0USXy/C„F3Hihh\h#ްޘ6bjO,Yr3RT,K*?L,ș6-n(!Ϟږ/O5 2E$3Z4ʞg Lwzzbݺ,ql@Gĉs*Ca. ZE=\Y M;FQnP9&hh[~#*ޣaBZjQκ9%{ tl\ЊP`ltX֥ȝ;aQȚ3VM.ā@ɕ'iӍJU~)]mSL_3fІ FI1b0#arjh=#)& )[@e^)JVFXv,7ガq)&|\G Uښ3>,bNp+hdTw. Β H1B[2co}OBZ.fO@NAC;"c_Րj3LWGV;/3^!愕/T!4p[1 LjGy,(rc;Vа,2`! jxBY&-\|YA ;!05w$݁܀a )BhǢ3GXx^*U[?j`A p;osJ_xni3Xk/^ZAE<R{~kϭmP{V:ŎFu7l}~{ۼgZl z Ё`e^k\;Xd@IT>K,cΏYu\r%ہ$D5Qr#sXI(z V12Gl/1ȭc\KVKXߗRf D<-\ "oɗq/<0Wa.wX^ uɁ @? NQ1*9D;R#m @C>g$@v)kB?cufW^PA<7(@&o)Hk uީ a}AAPp{@ A& 7JUWS5 k*(hs[X8/,a9\Z rbqqO wl1d =$rJg`Xy7{%=j#6B taQ9qܨ+%qe6ωԅ @LG* 鑏?2C52E$r4,+DZtq]fIO=3ґ]}"A eC)`n+Y2`ow@GűW%Zm\8Mf* yh;e]`sk5қoiNb%_n5y jpf*;[no'y'Q"htUUd*lbûY$DܓbbVCYN/ܯ`a\@pJ՜H2u!†gm W7VK~·Г-p1@`*jM#ȭx/!s\pc$,Ta !(ᇺ[cEXz)΁@,JB4TBPUB`gZ3_C%duQ: )+!Bf S=Q$j4Uk @ "r?|#0;-`BB8Zw@XwWw2C6V1 /"`f*4kGp%WWJM?n;[sCwɯ,ozk=b@ETsXT5&*PSH1mG1H8]݃ŽPv@wɨ/AcwxPf:8 8)q5D[ UJ.lmGSpQ݋;$NQ6I~$.tP p N1AiQG0@r ! U&ϿJ|]e_،G }`~&hGH>̵jAjƫv7wsqz9@nhwXOP s3Pw W"L@h1!O2^JKɀ%$Sc PoPMZ%fzϨJdZsӊH/rNiok3D7XxpS͙Y쎱`́ 7h6Kמ?3Tk&aU}Ob!"jv$AVhPІfA󍧋{. tdUa4j.׻ nrRH=Um=:$pkBճ@\RwC6C  1_W}JFu/k mMB _pv94}KIBdlQYNU(C nýY>)aH uݵ6:2[:+ ` :86btf m*>[|fU@ǑGhN `j>JO2 {[Â(u`d1' p z$1=}eB Dj)Ć{ ,Uoo960@X"kVnaVw*k~J 5Pq/<I|sݑT 6ue7,4Bp܅"6@(ܮ0}2<䵃 f2I7/M<#P]@GdQU-UE(k9c΍9$I:x)|{KrVu  2}I;+iB$(}&$ $#7T%(]5 ¤bJY?% }&ceJ3՗A۶9qwjO 3>6_&zۚCm_6qKG/e6`{+n<%DF){laәFa[irg焝Fcvcgyi[LfÛ㕝VzçvZh"nxCk6:?:Y~6ּHLWKpf˽ٿ|`Lu8Ν4i˽-=[dÁh r+rf3G\.9ԟ]wiݚssWBwo*c_n_5,m o}}ͳ;ۊ~ԲvZq qަYsB?Ss zv%gwXFڲˌo1~3;웾HgΝXg N9Y-:;ԝ-{r>AǍz3#ͬ;b74Fy3Owx,en0sw+.,[m={:k:-?q8pg{wܷ˸ǒ r6e+d/WoQ5q/ʻˣ5kzUhpˮoCғ{OXc`,˜(usTdQٰ̼wUY̲Z6:0ǰf~U"۟9LzEUb7 n[.˄ ˲Gey⠽rS٣{wEGItʮ zgv0x΋ޜk=oϣkhhhcHYG ۿEQMo0~sl jvaHx>u<+:j$ErNޮػWd~ 6F$Ё 1ΏƤxARw$*'pNYRsxFe:v=l vE4v,wi%p?oL1ѕnc4U([$H?Ii9z%Bh/ X<~dYYQ3]M(sFq8T25[Wv373{&]_sͺu|1"H_9Cs4n'MJkި/Kl\n׉ڙAP[t)RY+P'4灀mҲ վHo?ж5fRi; %VCkzQ]K D+ﲞ]ȁr=]۝̱W%(mДõ%޼uKe `5/w9vTc٘TG[CFzb8s_*ML@?xgUnN@Ǹ~*BY#0ϰmfhGU8%u}u$ ګ=ypCKe($`r5~Zdتcי9ׯe6CnuJʪ2q]FmꭁzWs@sMߚ@;~l鐞-ۦ Y݇`J0QmJD'I֐wos !_L uhJ6;,ÓBB^'Nd `Jgu8$!U.W*QZ/4 侤_gy?V2wƥsjU  !Q׆f=ZV-q]]흴-ymjSZ9gvQ?WTFu ?BNs cmF,=vLKpxuV6%ɋg<ؐ t`%Vv֤hGUx]$.ye},fp CaLcK©f~Xf+~>Qᾮ/^xPZ@ew%;( eyC:P;5A*@x{GrKQv<:"zF@IDATbW(\c w$;./E$ ]h@_,zOxJJu uP[I G żR̗DZ\Z~Y*yk4k:>x劋\*z߾O3S1@#Xs@sMb\/AvEGR=D tpiܶiS#FBԶ|yrđP8w5=Hjl3<^/;e萅>yB1#VbU6ux&K]] y+ʱ4fAndwq@oS|6i#(-˔ϊ3"P:[j18Ppa/@kMnԧN*=o oecĠw6nn,o'a ĮhFQcDv'Q6Sn=(a)kլbċᬻrg?F1yeGʓ}v0՚]Y/"wf|[;'lvK"XPw醿w!(,D~a'LįA՛rI敵d(d˽YU~L1?@]9S}P?wRƏĎDJBr!~B"V3C7~r D93e"f*@' 5@H1q׉ukkG,jMDw(yGpŔD[5#&{@@qWB%wlbx"BШjNO .U_r|?qQpԠO-40AVU8#;2x&稦}h7Ǽэ`I~.ߺ[|Qj?z6'BPwI[zD{QoиeD~hw",'Ls=__/bh[_w/" qg-be{ 50XT-nߛsʠh >;=h5DxyUoo@_ 'Ѕ(ZhToa=P&{OhMw z1:O7΀}ō0M)58!9ۑ1> jS?^̭3?~NK'Wu1#n=ǴP7,jynlq$U#-p %O-*ĭX8˸ ZjmV}`y%b"?;lGsZ!Q=E:bS^ĮB?Ar16Ԁ2R#bmx![DeǃA;em'+1]a0GVžd"]7i^,#ɚ$ `b&aHHԧO#W/~Y _yq ]Ts@s@s L@GgVr>!Q>F._nd4)Y𳣻Q՟6)4ݾ]1F6@ڝn~CesF6YD*!*zծMQ !  v[(S&~}< o%!놴7|D4:%wL :}Ct8y&~0_o7zFOJ|_J)%n^@``,UPE< GSF 0^0rA6#'~S'܃:4%^ID9 R/PAQ7!xFC4cQ^k+=G~n%,JkʍpGB} /2d%W/`p  "!qn 1 6,[5ظyb׌E r GXhg #+ `! G Zw Q%aϭ$@s ?`C^0(eQ,V#oj:gQM~5s+~&=3܃*%!)̟Q[90fFKvh}mXwy] -wCW4,Q kFpns~Xy?qx\Ə~ظtN8=A:g@Xx8).Z}fTaKI{Lo?bEP0 ¨ $_rኆJh^y6GP<ؐ t@Ƀ5thU#J[$K Ü./J(oPJPʲrH2r``/m`[=) PL1ΰBf!\ 0s:"(#o=نD b bRiHZoo Q!& rUʑ+Aax%dM(3zQ*.ّz-Ёm7-m^<LBU ;< ևZt4/#yyK `5@?;R#|(o#[R9VflsrcT.rϏTJd2Y'J2P";DDY+wĻ.U(E1;[JЅ7#LXŠ\~5E hH#^mq87qbi׮3Z4b7]t?ш&M(XihBVǚӲ6B)wt"ހŀI˹-ZH _H o1/9>IH9wH-sZR;m/9)J(TBu,%=$h?mBФ.- #pzrKe2?3U`iR$PD[W 7EyM5Pѩ5Vg` ^̎Bֵ!4$ 6+-Rgl&zؿqU8U&"]:єB]axֶbBX> Ֆ;1U +M8`/x'g{.'-Ԩ8ѮDEʉň5_Xo* }ĢI߿_W\A z]x{6/ҚK\N4(odܖC/]a,)k`{Z|4{s_ߝjOam",2,]]<K{4{AXb!jGs4yAn!@ Z|(cvג;3j`B#* o:n))LFtX#ٺ o Qb|OyzN!UZ&,(s~ؗ a :4o3c/e+3 Pˍ.Fޥ{ b}y(4L 4P-M;UfuPuԝW}NyGO 6e; S uso7KUX@aN+V۰"v@okpo2ArYXжF*Lz3_p׾'"cvk5+cU.ƒ#r@D !t\lS(rKBYAC;PNE@yH_%-P@ 'ʘVJ%zcضxpAouP- j!S8%l5WAek2!DVa$(`N) 9QDE#̟ľ$\ k(|%rXI,7I* \cmOZPw㌪38gVI/c,t, 4 8p:{:=xR%LHi'&oSN\3؁E: _eፇ,g2JpR̂"hW]=|հ䈗6%ʐc-C%@ia* / #,54֩NAqV+";B/}"gW-u:m2XܿCp.#Zkb,w!dhfx`I A#*m>)6Kxd<4C#\&!EZa .CkUpSy[, \ @uhۉte1G YkS4iZ:~"=yO0!ɒ*WV>?iѣFۓUx2XMTCj=q"]u+$1͛ۖ;HSׯc.{.˝>+VyF;tHf{%/{0hPHԩQ˗ڹ|#Z4ʒ*,Srr=IK6oeٲAcׯk,(^4( 3kRz^y"ZspOJ Œ QbWgbΫr8pƉx ^"?3 Qf4)LT(Ku5QlDK}tO<.9999999999-4-Ǽ(}5w.?sm-խÇy pLO)[,%U9#e^8n\%I`Qrԡre-9%XaFIBo 0d5lH3f̓5+6]dP'},s,#ހ~߸~ 0p5ujuv`~PŽ\M:nMVr^ʎ+ፇ'KN8!2<ޒ߉2"n+cc QǺD_o"8keD_4g;Q0b=-L&= 'xEgѯ̶DqY@Ɉn\o3[ur[ܷ s})@̭D?#8k{q@Rp tX `S 8~).<,ʘ" q62 rÊS2M05.Q*e˿:b[^xl]Wi-} 4+Wh Orj }_dT*G%5!nѢAsׯI+#Xj.촀LY$Qi֭̊Fe#%1AE;vNmvMf:P`06T@ʍ0n?SW^-͖y M,\?FT0ͬmm^!.xjM;,,Ԣ+7ľ۪Q=`.)s|.Ep(-ˮՓRܼßzS xڴg3Qx ~OtF.%Ypiޥ&eD/: &ji{q@R?36ܠųĜ%n('}m.@5حUyM0?}J9 7U@dpM5)@KuR*,+ȑ& };:w57{Vͮml!Ɵ-`Z >b 81c҄/ !~Z`ȗ耫M:$'¾@Gy_j/[gfC6Ěxx(f,vͤMf!N㇂3H|.MH4F/&!`Q.=b>VZ$Z5_YZlCC7Mʵ{ ! L 'zpOİyj=<ͩZG~V=@}ȼܚo64 [[LܽB{ ppؚ4[%~aEu'Ԍ]HbWRڹԑclB\Bu:` ^ {~}cۿ:V=hٓle|8HR-:MܕIZ® E2+l8CW_9R(Yd1:nI'W bRh Q;eRhQ)gcJ7%~?5]-V4JU mljRAG{.)VX%`J.,;즳tyezr ȓJ;ޱzVSrvm$ʘ-XPB惬[*f)v\.ӓPy0ײ+[Wc9 J'<ʹ}ۈ>(#6ΜkH$DK j-e,ed)-EvldDoEctA@Y`܇yl`=Db= 5+Xh9n^#ڸ\X~ $_QuCw+ z@Njg/hYesB յ՛[5\C qJX<]n&AZ$H/[SЭ062WVu,̷ ~.K^uo* &> 8`#qE2[nK) U@M `û]{vYGrD %ߎΫ*j;[Z8yYx?!XHS"V&qNj8<̨ǹ! Tm%I՚05fzlP{@ED c4@ ļV`F+{"yGVs`r_W#:y*Ewy&׭&|;?u͓GY!&*7Va>ϟX;hgܤ1~cpc *̂m205w/uw#6hj1W2ΰaF>dcWWNjA\6|l/2 &()#OTmotyᆪcpW5:.ZD1,n';/ڕH`g9>ȒX"2&On42{M)iF6z|1q\`Б0}BJW"ퟱ^_1ACְJS1PL B4p@n5B籐5%hhYiBW%4mۈ B hWʮV !%@7_@^뽒R=\ `mwp C!2A.@$/#^"44a!8b&X|aDgBԀ" +  AA3(?G|+IA_fk)WA!|vm_nքUS&Iǃi) NJ:T) Vp'5hFvSL Я+Xs \pB]Kn|뺽pZgKXj: d7},7Zݚ#!~H42UnOT Mm/`$8 ).XHJCB@إ8(,Xa|\n`Sd1#ƛ[hN(b@8yM{LB >;l=y7(2Aar! 7DzHp,jࢪj.amj<»֦@qθ V <, E,CIuj4{?|6P }A)=<˗LE!sj-0r%hZK3@8M Gp?:p? @$ܻI K_{!STU9\Xep"E8%=-`gkT,t}}&\ WNn=() ܠX!ǽf]\!>+P7/2LZ#s|'hJ}h#߉ј\@Xd <`7YhƁtEQ,/q  +*YP4+B:x9nvA덩кd V)k˘0wf$0BuPJ,9 h8jzC5rx]p*ׯ2(GPu_)"Zб#%dpJ}ͣ5j֌9tz~"ID(}т ~Ԟ[ۈa/쀎hQ{ݍGoe_/3,@왼5[DI$6GY2l!hRPZV]V+Q,_-vi_gJ쒯x tL,2m=ghshd\^X2XΝ;ԩ"W+/Wմ+M0 i1oτB;y9!\1ҭl J ZƂL*d.,9 o%`Bk-깅(URA[ #HOTpFS{Lio_Wbϕk*f-4?XX\d>,@ 2*4Q;o*\}Ư9bX ;t#*܇;ϠZu/ !3 G m?Q X`bõJ+* .XG@`ok{^Wv#RoNSk{\6w@1S[XT|ǵ4\-"`q#A_~tߝؗ'WO7퀎bS˝| jĈvpAOqRġ;f[ t$˙Z`u(46XIrኆJmWzw 'XOrcӳ(W\H DSѶV,HPn@ЉOj9##2dwQT](M@zM)"JGPT) "X{CnH*MzAt^lv&~;3͛3wӊWo[UR'c𽧎{[JGLY).ngg4[a{tSO-S0 N7-˦5(U޻zœ$2|ڔK(ؤE)ˡFݮԦ+mLu'bv,3} мLs!fFp[w_ݧ~J戩r2ض 9ջy.jtF{Tzδq1sMƼ_:z56r=wŝ6OT=kߏym[3>߭$ wwrG;WK!⋈a'6ZrOʽ}*D&zx,}|tbhl IId{٩]#y|A?$zC*eJN:9e[~߂FFJt+w q>MQQ]ITU]-i[kI#vpmlA D8W=Rd K5#T(;K-XTQ+G-7"`dm#L*" 4MLDFLW#Wer3ۮIeF}p86曧5xZ0[H"Rϖ/1?!٧c̋FFǯ<}]fU9rn+[{%H] K$*4bH[:|2RyhLSV٧Erfz6>f,)b#{kYsR0WX}!rǿsgẺ:}W9Ӫᣐ*f5^W#MǖǗ͌z.s;D vi2{>1fh3w"sا%^a4` 9߱yWkj3ߵL gSFp<Cݧ4避(xs8t(V>yf)b"czXk9%]e^~/a$:t**V:{o3}sΡPB;*dyY_( qU07Ŵy 紩eKgIy)BXFGK$q3MT[qD1O%ÞL+Cs;ylStic9sS F[] G;;5} (yRf sO:Wk3×Iwc@"1PݭRtfgn6mͰ^;e+ٷLSITHɦYy"F2LMcF}yMWTDݚK[d;s[8ᯰ>hZ'} Ti&Nse:*"jLMğtUARwnKcJ w3QO|oOlZ O{Kb6k[%{т}ݸ5ޤbZ9~K=%fܕ+qCn,ص+W%D/ԣ17IPE@PE@Pn.]R8d~8JEьzq|HX/g q!YNDS8SNe' :ZCtend,ϐU՛NpJ[,ne{-G7u"0Mj략;?Y xƝ~7mvt8 SD*6y9tDa*x+K=% }z)1-+ SO=UBp0S^Y[%ig7GvNL!h_w )ÕH`TyV+yFےBBxMڙ[X,=^d8?g2{Y:_~|6k7 gj'U;'?:ͷZ>sg<<51Mӵy'_>nG6=wd [&Rlr/5hKf#pۘJ2FܞQUO-Do# y ~r% q^tݬ%Uʝ\t("("(E@DΞ q~\;CH vo.bؿ2uth Weްw/Y<w#6ynًAl! v;We `#!uxMԫ`yK nݒL V 㢬(|omǠ6m@uk?Ϟ;?󌓒*" BMѱk. 0ϟB˹O_;eqz2VʙOխh0RqvDt IxpPFTS"qx瘰~$zHs=`zIHֺ7~1d|W Ԙ*vaS0tq&ҁ03M3TmYe;A4HZJ^ۑ`Yu32 WR+F2bnͮ$pBqw}~6AiN5͌Rp@>N/4*̎;M!a86QG8pĒJy _p[$·![e>›P6^/Q{3Z(6 ء֎/sP$H,XSsB"w2govON:Qbmo[ kx}ӖQOLSF2b$ع2]ߎO 3pTomLWѶK! z5Iϸz&F۽;IJ${>1غ$%'$d"]LȎ4B&C|ixzcʝ9\,b3mKZjsŘlPB(ӹ gXIbF}pU\5~OyƓ;O_ܗXi6{=%5Lt(hʞ{"HNL3uU:_lR l:ȝ.e,t%3&[/2:7YSeHFmצ+#{d\h|9T"Q ԉiC'6SM1 ŗWleNkl7 LEcdL/LjF>bDOꭸt,f!"x):|yF2{3JP=ϴRdeVǃu$?iI1 *:/>a<`K0SC{ywз>DzTZyI6>(˴r١S!c6"羍a[L$=-I:oSy ::y]q}̙?RJsG΁zd/NSNyGSL Z,QDH-C\>s9w mo q8SiȤbjR)n#q239^97aRɍ)F Iֻ4XGٳ_+y}Yy KcDC 4o2&9? sL-D,;̞"~o|W 1}/[3m-5d.~x}$.i.RQX4:IŠ{5QQl<w\5.0v3h4:s'vDM<[L6i'LF%mPvVw|g1=XȚR˃Y~%m';[k3q\O;) [CR!mvEN8Z("("(@(JtB;;g/]r(V9wS25*[5W˦Ksq\R;Yc4DyѥN'–ğ'@r }(^+^?tA4\$I`;wvR?y7eT/Z@SSZY* q?  glg %c=3'z4k#qF46-dUjPG[mLƳ֬B_L؞{[p!P{Hm *?9ſ)N[?b=ƽ8ΉphSag %utʶNي5F q*Iu=_;%o_-&=P\I%u1L^v3ĨA^w_+y&u_'? $J!^ślV`Mú/egW0u6[p h68oYF(S^U?͜CwWXi?!CN®L!WH/3Ut.g4n#kI[%mKn(!!h%8)<ζ0="vJR[b0-ڬxѬo){;<.2)}hk=ĬEd$Ҿz?9|+>o$Eۘ 7sQLZoʹ)~ /IZd9H.Ft=hE/xKH\i:ߕI@4^AmlΕ`lRxj|3+:x!@Hc 'F1EmOHTf|^bixc =NQx_ԓ-~*"("(";(;O>(;d?!ِ5MHCKDjIWN5j`lk%@x1-Vg>IEVqN{G6AB}\3/~O. 2΀')3覣N$S\ȑ‰ #@<ip54c67=s3~3SP?$KNmoe݄-Sl[o3>P#Nn߾=(-kȹ@^& {G:I80uujɗ߻/{`8S$ Q!g>ufQn *f?y/ $ +#O^WhLsV!C̐cڪض_sϻj"("("(!DGdi"(@_aR1)3غ9H$;o (/_Qqi1  rD 65E@PE@PE@P7o="(wF!4ڙA\4'KfjV3Uu80bl*yi:҈`3n,"Ìpg~ ,mķ.x;5X$F`҈q,S ^MPE@PE@PE#D\Ϩ()R3D(ΜjQCnvo@0ßJwnIYIŴ\UNs IC_ ۻVS"$ܽaHPE@PE@PE DYϢ(",bLg^}SPb-#@-@nZgd RtҴ{I)!-w? TlP|0PIEvktPNIǀ߈ΈWPE@PE@PE (]"( (/HH)ۘBRQE@PE@PE@P;%:Ļפ("("?o] ]v QE@PE@PE@I(q("(" Ag("("(@L!DGL!("("X#-@I*"("("p#Dm|ԏ9#e$H,+ʲw]~{E .E $/_鵋װEPa!/>;~+\uJP`3@=ToX6m.ۋ铢dv?q9?;~Mg@ ed_dzE@PE@PE@P!DGS;8#' @֬c\$+t|vg?gsXD .^V9çOYt\z5$GQFƬ_4mxCShQX1\ӡ ;aҶm8!'v̟?mÇcəuKDY؂{t{]ժx@D薾3#GF;-qF;&K/gz<<.<ΈvNj|e"Ⱦgf$HZwK7v*V[4Ҡݖv1>pEĿ/>eJ ƅN1&o)E^7v$;%r=Tz 39\,HʛG܊sGMK#` `E6WE@PE@PE@JtD8ջI>{XsI˓-€gɂ_~9\)Vɓ&ˇ!6݋C5D`S{I%ѤwZcTFʗn/d޼uru۽;ٲo˖ESWF=h筚5죏F$uwqzi옽#g≋P#XىmcrX*+m7kU#Ę5.9hr\wB~ʚ]L܊s3%:AM("("("p!Dt' !q9犎J )<2+R# Z&֒ ܷp Z<4-,ad Lh2'Kt\|:t;{PQ#)& #P 9}B4"Mj+eˢ{]!Jiy˔ g/^ĺ={0~rɘ/QJyH$V+}즒o{;O* L +À1AK]~: ^ o IQ[ۢJt4(SItu'ԗ/:?V̼DsxNT3vn! u- LZ'@' =:IS?Rۈw+@DǍk70i맿Ͳ[e-Fn:o2)44:6/87h};=pe:AǛa zl?X - !!R]}oĊ+B >x:Bo8.@wYs$R? gpH)o1^‰'pO0ټcxb$ؚjڔbO<:I{e';bsWSLuq,J^K+B.ۋ/wҥyצʕ uGESyqT_{_u0'|^CȆIC( 31u2Q' !_aPsV{6ꏭo!謈gA's!{ps~4%D>*DLj#uVglF#ht~WRbOF֭@?jH_>̊sU+>)l9~FK>3CFi@€Qy.Ԩ%sdsxzi ѷ8ub\9w?ou"؏Ep[ih+OU5:CH"% tplxB-~Fvπė2@Љ0n087F01:kymi˾,!5F =mQyIȌfjHda`ψ$ ɿ?/M tnhUJ}`W<;lZ& :DdD@1ϐe!9\Or.uiwHnSeD`6F `TD1{P8)BGQ*+)Fż^g9s0|,a{Oş&:o0 \덩h8!Lie _DG \>+04'+:߃Ƥx}}7tvSG$\#W`N;9+]՘_NQw{?Pc`N!jupCGEt*SL_tFz-wHT>vqTVݳuӣ#:thpIETӊ(AyRCLYnF >bxj%2@1m)Jn;eVW!&S w.=,t)˚U0z"n`(cns#``6;?i! ٢L1+XD#i1mQ='#2[ÏP~jׯ0dS=|Kb#7vIP,Ea 'PFA#}:>=f"DdzusϒO}wWQ2ZG`P= ]+GB<윷!HMt\qؿlrT] %^/T/ 3:p$m3,iX ~u$h % ^3}f~xPQ?l;sdDϟ15)Qt,]x!? /wB̑(y%w{xqow<̨㮒ˈen}<$?naO !C#";7Qԝqg?}D0ZZSTmؿˈ3ʇF-ş{0) sVgc<#ՅzX&dI]7> [_Q3O^Wǯ%-,v\( Gm6VF9/Q> Q"[E@PE@PE@PnoCD#?־LGv6Xu5 K6oF|Rj˄XndTtַz)1Yf., %::JW%? 4R$Vh?H:d ذw{J*d/ +Vi9^{BA@Mt+ D!$sY=79ᮧ t"_DG)aW~¸0IƷDG xt8"Nn/EəjʟBtq3w}! xnr}VQ{Pm<K{p!oH5#T9!K`%[\QvK^?<3oS<|&6qoKt>u:h}KƖcpT?=D[ [t!&:>6﹗}S\rIO4U4KPÅ寓ݥS:{-DE(HrpNT afǰ%U#/Zz"@c56Hfd`(yL(K}0uS'm/#$ ͭAb~C𶗟TW2"<=?aW_0䂧\ kcL=h!>#Q2QƖRp觎j-;ؽOı/! #pO3o8":f}%nc%:8?lEv:XA~ZEx^w&:彖#^x%YeWG$:t**V:{o3} >jPuG |?/BtPQ$$.ܫ1-FK7~zpFy_Fj͟6Lk$&;%֬gaI%;˅.L b䁜ml*(wJ[-hn5Z lG.8.NtZ]yIdJYc[)cZv!^<<mfࠍZ.ac"$+@62'q[ YMJnbI=mLT9'pxXǻ]LM !7>W.q6w;{l@r.ik̖8#ns{Y[3Ÿ'd)ݣBl`jgAF!GIAt#w!Gc8*KR|c3~bNDEH"("("(>ZE<'d塝c2AzEÞk휯.m:ר·HmzNtbJ'CƣBtp ugё\.Px`fb&"6bbgKʯoQK5m~#u^U/67m[(@fK[g:Sc *rۺa0x(.J HFUTz ~-lpmlA߂}qtQmۮ{8 Ώw Al:1b*oOZDǔWE|jx}xI(3mԤh]Քx;݂VnrMz,jRر꟩h az# eDYtWL{e]>7l.[ǭtp_ r`)$1FP69sDYOtMpMċeJčpoLK#*乾xDc0bxz$2~b$[H1] -[ѵ׮GIiIu^Zay$h5`Zխ"("("(@ DGR\{N[wk;tS0x[tڀشo_Dvq&gHȞ6- 39h.5jp$_ĉC4 oT\6"ś`Ȯm:%$g "a|&첈RBn6ѱk. 09Sˊîʯ>kSAխh& bҌP鴿BÈ#uV$N{iu->{#@H%G4F{mƴUɓW6OƮ(y"u-Oy.݋e:W[?wwO ;_o%HW/\uRF1m= 7)3fl2I5jxp!:EXrFeϞ=&ֹlJ!F݈y]))/MBzA`:qDATB:҆h|g%%֗@'/@p[̪~âS6Q=l [J'>M#s| gKLX {m띺%lkԢrS#ϖ&Lh==juP}N /ҁiŘ^6wxd5>#Q8L͇B4x5lߋPxGS{{ˢrN,\05\ _gOWĜygi+$V ?aʽ >^2ep "nQ7qHI荊rQ'KX/@PE@PE@PE FP#F;X < 7AS!iSׅ ̖ DZDJ҈(.Rȑus$-ħ!8s뉖GAICRj]kwsWӞ+YoT Y씄8,Ԡ~lvyǎuxԲ3"#@WʹO"9r̙qZ0'3TN'X&:*KڱGrt%15Vt?_ i4+vs,q|qy4H;s<ùX"g%ږ@>}niBegc}f 2yj/A+MV3\J!evӖ#A&_Ye][z_62:*G_^ 3B)5{tk)QcI0I̤bjoo4mߓvIo y!ce-B 3HD/ _7at/9ٿB, 0#}yxAw{4eC$Cx|H0>ؾO>8tZ|V2Д6SpxN-C\>#k]Gt@C+Ŏ9;MgSNIFArw8K<^J4īs~z{q\h/ [&:ޟW]q jn=(t1F4dݜS0!8ZbiNn"zQoL xqѢUWqS uU?!fPFρ)hn'E3C2 t3"e!5~rʥNvB B0uޖy`[cez sզ5#:FgFԛ$p8cL+fLḏOK:Sݗ5R4懭!vdk#"XO1L ~G@IDATm~ r_$/ǦM G&*% 4Qgz+,*dk)qRC05>44e$HHJect93Z+upB~KfބȻضӭ"("("(E@[?Ȭgf(3'F/]ftV uJuDm|&QaI2?H"IJt1SD>0$"b\i2cG'JĻ}1c`Y&KA褑SuA)9w[&IUԨlYG;ĖϐkR-Hii=쁳mI Rr(Mpu3¡Nن11x$N5F8wNsP̾3QfUW9"n$I4]Ϗ1L^t8[u@F ɯLǽ.u_'? $J!ݻ̮_jeƉߛ^lW[g#M"F1B:(ҤSܷl:kV:e?UPgU|fKQqkuHA#LTVZ*̸7bQpGbByF-v OH婔[Mѷ8u&:}c&@] k4}xGV:FT|Z3Zܒz*FFeG~*JQr꫐ ĨA1 mF@r`KX/yj?E@PE@PE@PP#0"(@GF:"A`2_DGvzm%cdk 1%,cׁE@PE@PE@P|"DOXPPEv@± NWcyΔ"2%:"BQpr>hRQ(M0s-̾~*E X"~ѝWE@PE@PE@%:"GkE@P0$SL?-/lUf.LDsj~X%_ GU@E?cAPE@PE@PE "SE@|3G =EQ>*}] 2<[*Lym@q[G"p*pxg٠OT"X"رvlW("("(" ( ꐊ"("u.3i2DPE@@v"("("(q{'"("("pG#}{E@PE@PXE@XWWE@PE@P@P#"("("B@_h"("("T,U `ԋӓ)"("(@"DG{ ~8)$Ad{X^=G"q„H"ċb.^â/C`^wW箔y(\y\*s ~ٺ}j@'S-Mۆ"i(ٮdL DDaA1@l@W2N~ahIc,al^WE@PE@PqDyfuuAȚYK6re7oF9svuJ@Cpi,,ݲ^ #Q({v1.{Gm!vT-Z-+V t/N mpɯݸ!m۰a弶GrDݒ%Q6~9`F>FmW*(P 6ѭ<< QQyocJ/gzpEĿ/>eJ ƅN1&aϿ{M@f=l\ T wl2|ۄsKM\(Y8 xY3V|V.vYr[sNw` ` "("("psP|SҤO=&H&MP,O [B vɟ% ~|pZo'Ovʛ/pذw/:׮.{H(?&{گcTF/Ua_Dɼy@'%R1X{weCߖիMU&}4/"{Oc=L\!M{&5C͢}]!Jiy˔ g/^ĺ={0~rɘ/QJyH$V+}즒o{;O* L +À1\l:]Not2)m6-!<|I5}L[$IHb[ X98w1cD5[$W)R/ʵb` ).P`T` `D:"("("ߌSJ-Uh>!|$)z4ke[DUzgm-#=o؀J Gر?NQ: cȗ@^?̜Ip (R6՜=F#5. H Xv"l =d"I:v#B$+k;(X"~("("(7%:n:?!u"FİuD/N8ܠQ%:1S/15F|qrhSNկ bܹ~V '`abtJRo ? 2š :J y3+ {^no%b8(LOF}jtqgm})C,ɱfܸ~DjyqFlC ߾l|22̈́,%r;D XfmQ aӄM8AzS%F"j^-,+c=80E2"g,ōEbط|rˁc}jtlO^D2ٰm6_̈́Z?뗯cUU<1.ӹL3p5&CbR#v'EvY)g)ۙHM9i^=p<3vl1Z {m(vفsv:<*9-乷N݊Ck98dt7R$b|'?2C?]T/Y O1I$saaTE-3:e FoԾl<`Vq\<'{uql<\WOSF'ry^ T,]Yjm (TmX8-uy"`N9_V8,aFEFD\,{4ؽǟRz:w(Q$q3~3qB~^ xd5>{\ <%KN@_{n .UZyv?OH^2=*f"ۆcP;+?l# [mb|1QSDu!dnFǟ1"<^#smQ,Z3gmN]b9#D98V6aFaT}q(X hHOrbGˬeI&_,X"~LI("("(mɍ4w:0q0$En~.~o*ѠL'םS_B [1-R:@;Q%:Hjݺx=kQOfBFК>ZQD|3$I`B';bOl+1xq,J<^K+B.ۋ/wҥyצʕ uGEYb8P'#OkO洉._2$ j0IkXxȬgپ@"hm[t]j?.+UCwgmI¡XC2в!Ult-#QzGPa^._t4'gtXyS_ƉۗCMZ.; hWo-o{B$NMvD:o:wD”MI$1T i.l ܹY"J2-wsv=[Kd$E;Dz^a𹧶SpyD+mPTS|Y!/LU)sN?`lh~\6!V1!wz}7ݸ&i/K1lyuҳ%Eh#ES6m#: $)Ț& >WѨ`ZTk |RAR{w'}5I8kn#\xk0gzKcjnBe̪nZ:;)AC)$UQ@醑nI.ns;潙7of`w=]aJ [F5vgMz?0=#_sGtIx?u?:vj|w߆HY(%]}I9Qj} zB7JGEQRk1k>8DǴJjlDd"Fv#wɟvjтJ.^z.{|X;"G!eG6R;vN}f!||1 L;P3 -rMogz@ [X9TQ2W9 wQ.׺Pz3B?D!¶YR:D M)&U< UyA*B $HKDZDJKjGóӍ7T/SG:Fsֆ&D<\X[5.*&=5D稵缳=daLj r4c[>S;đvhi43"%Q OC`.Bq |ɞܩwE"ۧI[׵C.!|ς%FP`0x׃VMb" ?X1q%5 U`?OvM? >IJ<"=-̕yHϴ[M"(af"D< vCD@TT7z7qQB[C4Fk~H{û -ցAc1 \%A?;֕Ϻ9{ DŽ!^ݲ{I9}r/tdn?=}| 7<\>k9gC[}gPi؎vWفfI%:89A@A@!:;ΤXޘw/]c%ӧ&P5kVz+rds(W%QΜ]͚jLwDp+ed13R޴i)|O,DGYhvX h-c`39%·3\π%KԸ-JE'6TݺQ(y =@o?VioBQcGU#y ;OԹ;/m<cvP9܆eKDYvhG+5D.0EC_5*Y=olZ +(rHc5xG*yJtX~gۻC$K>Dp`9F4/^) ꫯho <# }ȇcn[~9klէTWD:hh8sGtӈ6MJ?~أfx=|F}Q8_^2ͨ@JAѝswIhm#{(zO_Cyj]bRm(AkGȤ;{qS)@4ЄX?Z#VE]Sލ74-9[pD'`~ᏙlYI #7k5R0H90Dd U zöq$?}`g:ʛ:bkz+zAR>3G˜Nh6C Ù@zJ{c1&n JT{-ӕd;1׻4>m̙oƲVg?3^(,y90y4YyN&`@&>>qhM mN@-+׃ cRY H5Qi~SQDu/f"&jNI94~`b:eڝjH:5i1>XMş/V59$M.3YW[_> ikeox!?u*md]&EcFK)Vpq@]~ Qtut>HAS_~yu6=fhQlfGH uDWy[q0Z0.d:a]ᲃߊ%d"\eYWc+!gS]$|n :BTK|Dw+Fys4d_HtZ"DxbZ_ l @kf.$G;!`>GJgX"~ofWy<qC/2 ~C͕A P(_WGx2Bz Gtk!y@&=VGDҼKطC8eY[]O-c}9KgxX#V9cPza.{t#   F# DG8zuΥ(;jȡ7X~ )xKt@pȑJ+W@r a=GX`^gC*LFĈyp4D&T v d@C갹":sIթTn"DK]aBjHhXhNZWDG17R|s5iN9+i,*Ct$ΙZ`U6"BI dze2:pZ%" h8WјoqLj*P8W FzhODQaD~԰? 6L wWz-zZKt JhPAJ0DTg %Oh"^wZd l#:mmF b&UUZ ~QP F+:PeDlH? L/V"l,_Jޭ^ңg.;+7z^ȈzாI5a*%LS;Pȿ ^nۗՈpxưV9EZOjǸEQ* ^&ua\ ,_s8%h "}2#wg}\qQ2|ܻ"Aв:E=}CrvTF:;T_Gi騾K9ަglE7F]M& *}үǐB " @(Xa;/> հ=X㪿# "q4Y7Fa{-DG0ނ  !:S7fI&Ev[u1=JK5];¯o}3oDZr'wi@Fr:l)km||=$ 9UL&\h}҃-S3gAsO&:ht# :G\ģgv`\хG ݍo5;t8h?fҁ5?vK{%G)w a;ݍc#}" p69a߿wGtT_i=eC oMj ZDKt`mS+LNhBepgPɟK]{m㫳_Qմ i~"PlǬjh鴠=*lhv"$z; cFhqD; 3R@c;>!`9F@ )gn|&5q48})t4Hy% l] (yl[^o+>J:pnY^>`(\)OsBumۆ>sH <@&{^dxk}|xuf [޷t$R+!Œmvj+#*އ2g5em r`=ҵ}T%y:gHh4*Fjlm[06jk֞H-mN=}KBtA"A@A@A@!:<e4m߭[GVB_ށ*UeX"G4t:8zK SD<&:elf;&dt&MܙDc q*<.Rx)*h#:|C׺76LR-lD$lX+ s^Dcз1 }?A@ksH'"Nݺ7093;!flBtA#A@A@A@!:<e5{CGb/H K+I =r&Nl]CBt`&vqS *C5?8.vwWQ"vS8vÞ;#:0'icG4ܣ!)RH ŝilAʱ%Qc*K;WCOrR5/NA>85H@\ػ-FnW]c9jЬvDt4vޥsSiH!t󯛊mVK¬ y9[)VuYE[oQ?Ӻ=FЗBSܓƤjN/P,2v"7gcj"i`;"PCjx5&8@\jpex>4o'|weho@6c;Ql>W] rh?:VqC|~98(Es zMWxLKt>MMl,}8j _6i (>MW 1oa~ Wk] Z>ʺ.IHC3} &4j+Oqhϸ{>K贤ͬlWR}']|6ӛp 9yQ3cZ   7oOZZ<qML 7~䄰ܦSJU] s4ug[b!:0K)Sh_)qLtDqV#^&m؀.T9~J"6mfXkA.4c?M21rc'4?"FP鉲' QH{Cy~8zjH['{_3: E:ݙ"Gw緞'>*ci}ׄQ7fQQg*eVEeW2rF?fHR &zwi8QpSIlHOVnMw[~(uFI z O Q":ߑ Gq5n~! mHy}ֹatX7fG14hG s,^"Du!B "c5'_K̹-X SG.=pM5Rj!nl;+W}tb9AZDW mDKȕ_Xs($3ƍ "̱5U PWu "^<5[d5N5L~nLB[>ßM};x-*aBD'V"rg<6CC5gnT{z&uos8cs1%M4[~ u   z" Dk&:ɹmdfU9GTTS+% `с[T6}馎6ѱ5w.%dgn-ipJϟ>Ys^zXBrTՇ'DOiixfusk7 >1S:/˿;VGQlZ9h݄SHj8|>hB  Dh̎Nv}RXg~Jth~S2c\(֜qwNG.G> &{ˏk2\D vz;z{gqQxL *6$p?;"?9t` Ey"ERj2]\r$YuJ c[!)﫱8rgs:$ 8N 6 ʉv~=*m0'fր̒T{$&l*&J"\c1-Y#{vQܵ蘷}; Y^ %Jƻ1Uۗ s?#uW`DGPi@L3%xn3c#Ȩ.UqէhjũINp?yXam EeŧW4`Ǧv|GZJh?,0sN|\D%? 'uTq .{Z}͡69'D֜R( ӷUHDrah֡t {,C `EZihn~7E+*=Tܸw=l i!*tSǷď'=fCtIw,~nZ9֬Z?4R@!'N+V/~G4X`[Nvu_3 #@Č8bӯرG}X%r8:uݬv#Ɂ@TZ8X92&"wfsbEwe r@xb k~h q0wu=H|gb[H#*n"901Zy7(  xuӑ# lpƔwܺF\+_d樍a&pA82G8R;U)Pq"H>JAE81km<0GB[On>11(,Q#l[2NUThQbW=AsCŊ4 ͹h'ޥ{~mA TX pq#¡vЬCj 8;\Kxi!fW w%V"6zF5RuBU(ǬJ:,l   ?_Lw\T{Z (kT{q=1{A+c綠*1JD*Զڎa;x7cHjQyT쬛rU[5ϧ爣ݗrTB#DAD웸O#HpJjqhi sjSn~é-lգr(C hL3N8tg5 vJ_5wc(|Ķ=G IJYLƪ &c¥*}`Z8 و Qz5^_}3د_ݛhuS rR}x7לcp$ng,EѴ hf]m3>]ÙEMJ.+хѕ>=Hֶ˪qzc>q? F2c{@ay3#6y^!7Ԋ!6ԡ[b1̤UZ3WMg:we[C5W"%L j֎#szj$#Y ѳ?U8f禝A@A@A࿃gwzƽ{*rIE.%/u#;NqKQ)1 r%K\'S!CW)q}6"(%ɕ> p#)⦉;za|`sqAGάg'%䊫5DGGU7n/ <2~o -"9qǍZ0Q/ot@ jF7_Q!zOD- 8(g~ۿsh>Sh[gDq b5v81߈o o=XxKXx/ A@A@# DG1 D!Y˝%\dI !"<6uD^G`/WGStOԤxfA l_ލ.  /!:^&2 @"Cm>!%X@IDATRcfBt˿G&JikN&VaM_Bk?phY%, 'w"  Bt;  "C&YRMu >9ԡz-҂Ј0Vִ0q0.iW2 -&LA@A@^gx]8VqSť4Pɾ%)vA{nZj 'gݕxA!гZ*$ـzD+؋Uf{tDއ\K a=zA@A@!:^(A@cH1Nܕ܇ x"%A@A@A!:sA@A@qI)  @# DGC,  Bt2   Bt~LV,  # A@A@A@'Btڮݻt#;6E񵽗 :wEōKQ"E +s&)g8GW&ope5zWKlղO$zp(r/{{ۉV?(OT}Ce'3qL*ԶPh +c٦woŊKڿϱa'G w[~_Y   o[a$x~ʞ2%w/M߲ůyR̙Maѣ4vuZ`AZWܡѫVcg~@rL{"D@WmkbO_;O f} h.% Ml&ZɟM\8qN_Q{{'mZY͚U6>LC/7ۖ+GųgMH/>'1媛+CY5gpTvPDDEL rӇB7AҢ[]B6Ki爝v淩>[G;YPM0<o}.Ot`(Qe!XgDq|yYq#;`}x w[~OY   @@k[`0:A/ P hʦM4Η@)RШdKv_/V G8::LHO?wt7Ph`P=8ԱR- Ng9XzPLTC>1-T1k?{IThx3j="rʕB>TIDǝwS~h촒|DBtA&&:XLDQCm^Aic֏vՉ^r+r+8ѺDϞnAc><_xU"   @@}i17WqSHeHYys%gm 1wDçOȑt`AdÝإK-n fhHDċ98,CJ&glI}'ҤrԡbEY!S#/:aB:3'KF=Ѽ)CҤnŒM6&Yl(]M%&8N?n%D30|.DG(pa4b9. 'qʨω>PTiHr3uhCx Cvuo?#Cw﬷BtA@A@A@##`_8RS&!`9hWDHA~ѱqf4լ BHQ\aZ{7}{J/ 3n+y,[8qNB>Ё"XBzj{^#,sKuHl>ɞӧt<~vDURY&^ *w-X8$a48ޭD > ⬆-" 匍_g9زMe=|Ȝ;4F 1_##    Bt/y D,e)h^H=z`HL)o|@?X]q#]#7WOpƏ}F )5 +ViG9ea2˜Mtxs@)GszU 9{aUxFr$Ĺu\uފINKrAgsхG)RH\oWv`zr %˛RLA84EONy楨#tt ?:%ɓ2D{ޛ;/ҹʾ+SJ')-RBu :]GG /пKi>HCYf߯wqh!zt*N,=Aw\dѻ-ޥO^ H"~iĽ~ob_= qSU<0߷O(n*ԥx>#vu: E5G֜kN$ G\i*1~XGwMLP?i|,߉r2$Z0 2JV#|- /)l&ڵD7D+TmL_'JN;7m[EtSQ@= ο^6.#zȁqʧ9{!mruoZur^MDN|)9 Q#x:51c<8NӵUnt k8]Vך*Ch1˸D s>yDRp%OKt!7QD`if}ՔuRf=0o oy$i"   픧oߦq0?o\"ETg/^S?}ָ#: @ns4H;.qScUߟat-j1јXũMg9=i'ZjB~cUxD?e_,]wX^EiK-#?e?k0OCgyUbUDR=t_SJ.ݣ"Pqd9jZ3kh?Eʘ:s!Rit%E:wp첒v Gn_J+ծrx':@M<fdL@Ir%QCɛ!L\K^*E~]=xTQ抙g ?O [U BDN;QQ™gh‡iő)sNZz*g;/Gׅ34<.ͩOjάI) p*:1D?"c]QPɆ9>!o`SD8@ D+C^>ڱ|DB>=ќq[Ar AAf%5b_.o)Cb?n$.6#)l;ug bYQY9o[/IԉXJI`.{!QD/@oirJ,e\dNxumA`9ۏ5 f{֚=>L)z[qD*8wgsA@A@A@xjrL<,]oEǾ;Y`mʔsy55`>Z)m˘pp!PʷߦkR"%:VG?͟fvmARk $X1>wu2E Wcxj6!wpHuL [F5vgMz?0=5_sGtIx?u?N[7yڝN) K/)1p߿|_\!3՞S[_QFHa:JAjzx;a4vVi_r\,P|D".{[ܹC-ZPeO?*qzlAb׈"_~SM\VNAWݏDY*zLT Hrۙ&z0`dκ9UCrᝳw{˵.=Atzz) "E-aքBSfI(O>>aO g9j-9lym؅faLj rTu38TU4QL A"IdYw" 1`AQ-]Ɂȍ_E]cw@Tx˶%SmL  "a5]v cT9>6*q@ݴmDy&v廷Z .{yDpK%N]}y[[C&tQ9 #/ǻZw$I -߃CΞ GhOٶ˗Ӱ;";ь75q[~''   !:ͣL:草{5^0=F%ӧ&P5kVz+rds(W%QΜ]͚jLwDp+ed13R޴i)|O,DGYhvX h-c`39%·3\π%KԸ-JE'6TݺQ(=@o?VioBQcGU#y ;dsGt?ݞ⥍Bv 6Plh7whƱ? с:& 4؝wfvUtU3h PNQX/6^9‘J&8b'#9ArCmQ$ŋ)eʀ,0ʰ: vn{jcv#Q$_OsnC ;A>":T}1wD;gӄCꗨjx=|F}Q8_^2` @JAѝswIhm#={'ޡ< PDGD1ٶf CuGѵ#TdRS=øƩ^ clhBHue iFPU~.})ƍs k0Aa!g {D)ɖ՚tpYGEf2DBD>3""Ch7fx,_]cN/ 2obU15!z$3?:Ri!Շ4d3a4YxNǘ(aR=6TZNWw=ǜΥSߝu}ES#p);~v@_7 uvN_wt\K|-gaqYDnݒ\!FZGORNƚ"0/˕x{,3f M[پaA2D 7ZkV-N|?ʐ(1PTvвTwOSğMR, 鋣_CMt4\PhdU xo]%ЮQTUmUd}.{Jt!GP=գRU"/B;wY.]4ըQ#vᭁ@ zlC*ң#:J/48Я_~*^QP TD5)g6LuڝjH:3}!Xslo>0`4EX[ZOʓ4dU_yle&3 R}{n:u*md]&EcFK)V &7L:!叩ߏ!.׆-dBr޼J/d_:{XvO2[o8Nf,#JSeuac!QR+ح#ee  %c"έ~? ]3I?:7K v J\617[8BcL@־grX}Bl둁Xz8=kI81.nQSj{-&DG0   Rݹsi~+9r"8_@ n:r-Eʕ%:zXِJj(1Fuwc;;>{S&@ہh zWDGp"V:[=4耐{`2p4LH )  [I舙8&u@onkiN9+i,&> ё8gbj}=xA؈#&Jg+x;Gqjj(@ h|;FPrͥ"_7[T4ҧ` Q0A?Cj韆e+bvL/ѣYỏMN+ Dm6lk3mc59og+?<*oVt\ʈ؈0}@4_H_^!E'zYa<^ңEÏuV]gڙBpۈzா4HFz:&TJD;iHX%s/(͚k4b) vɢCDW/NG9x3h]Zȿf3p\KH)g{ 5+1iDuZ٣^F'KJ^MEKlܬ!v<z#s\)B2'E$#q7wh#uGx@lwFBt2   /BtggD8͒~M8""D6cm9z"jFv_/VSYc;5\{ϜM#G'rNԒ>H#b6dK(_kCΟAZ)b2Y@sOs> l?{j65G F4sMN!="{\ģgګGj]x[^#CCOl&XNc Qrrg8v=" _go+kpwDGU.C oMj ZDKt`҉e'T4L!28`3%۷/㫳_Qq]G18ӂ̫0%yq|&7ÖhqDI 8j}^s1&8L~@w) Mj4h>€!%gigo5":Hٺ!f(77Jx 1GܫR*K4zy^ #m2ܱ_6ix$?Uۘڐid&˶yMϿ}_h!:|   QGއw*~n]woUјlH[U/"qzO,$D#h5vf”&Q"{mΤ ,Y4Iw)GDAHYcMkֈ;)36Mac6ǗMt̬1;B|UGfx ;Y`mht ֫,6uQ[tW$TTSQ9z#* -D冔SsT_N#ե}_&N2WL?&2*O:Hˀpڷ~Nv{Z2R .` 7 n%:X:aY9zuIF+$:l#\oF|E7cGttޅbs'xBB*!z{ʲV˪C7XwA5k92-$֓)5JsJL;0M&Yxpy_J|Q\<%:~㈗I6 UΟRHMۼY7ZХ زŏiS Aܘ' AOV Mhӆr'nx3RP!my +qх(iroK{o27M77EShc*N_s6@w/ޥURuLu L:1B*g 7,cIK˥Z_}7+]ڳktV^wQ]R)T툎: P*Y[ǙO4*}L[}(I$&:܇9vfduMP(G~jU=޿6!(6NԂD`H1TSlnb[NclBj*!9b¶)C bC+2f+9WfR^6.Κ3րL .}ȡQe4,ki{TI!#kSnYu貫ИS\M(s'eg~CD|jnJrSD"FP鉲'/%G0DgUqW5 N3P8Ѩߝ)rt{~~y)(":\o}MH!zchE=|RF!mUH&:\iʬʑT\ ՠaG̘GBڪ;Ns{QpS'춮A^-bR >JT1[~(uFuY8J8qGAX>eH OŇ SbD#vb~zuKlNAqcc' s,^"Du!ʦy'u]s27.f]z, ZHelmXq͙#Re!e.G; :@6]b$UǕ+8&>G1l> g"l uf_v(M&iocvY,lc4f}ļ•yKXxNA@A@A# DǫZvV'9WMt@@Çj٩6yFUΑ*ƊfBaXJ'¤JM7u5Ps)!;skGN딆Sj=`ڵtê*D_jxBt\{d~ឆ7Zí;x3G`+0#@O7X4iScg㝈e8;iӪ>1vHRS` ]uČBuԥ%+A?o5 'nаlÔSҦ>`T~ İa9"Pqy1;(81W1(U;|:}O3vAH +,iCKi5\@Pѱ*G_hVL IcAB&:@*\ }QHdТU{xB =m"nv kD/C珟ɕ'i=~t)ªl&Qg)?.y_?؉cNk|tGן8{1P&-m`lY`Q7Vc,Dg1_wiK Qkr;pI6JT"G1;|v!Jca.ڭԀ $J5gܟ$Q(Acغ'Q+*Eũ*uXRW5$L@B?)r2y~ގ<ܿ<9o(i 'se=;3WH :5gWӦ -amޣA@A@A@\|TmHk0l~]Aʟ!CDhG\暻cJN/5eKᮍ'DǼij%J{S} 0I97RwFtufDt9.Y9:06aڸ;RןZ}VgOɝ'JOXXx :VжPT?]|>AO;yllg~tò;jEZz LGGI7U\E<˞V_ks@2I&ѩ5 ;tm-R-aEt\u(>Þx'P:5Xqz`[o_+ s&:ƊJO?U47]fO:"CjH24ty{d>8*D9jZj=:RAw{rJ|\hw!n|& 8Օ1¯ DDGtE<+NtG`eRM΂ Qc}kuz+h6"9aȑY.ѵ psHwƝ\:_~hyrzu]Ee@ B@79;vrA@A@A@x=|n.WtH3>(ܸ1]3n+Wʗjٻd9j7Gm +e&$%ΆNU zEGas:tPQ"{̚E8o7 E?QibS|[Oa $ʀ%K}.ۖS-ZTi|O?ybP'Mlsn9lw_[U'Vƫ:CfĈphw;4NG_irwU? }` }M2\F:*LcZq%xp6 ş/;.*ǽ}-AZV*޽8E˞xw^]ۏ%"j[pmǰJD1BNI(OeOD\:8@4ς89R9ԡz-҂0V!g%,<    AV! VqSť4Pɾ%)vA{nZj AXqW@Dօ% NT[0ѣ]   @8B@p0d) e##z {!:cO-ٽ~YX6(Vpy0q"=yܩӥ|C1E7nPC_һRJl4|8 c}գB2Su @8RǬYY'9R{PE&n˝+W S&wߡSOJzt IšXb1eODm {x KͫZ?"U'z Ʈ$*RuO >GΟ={JTQQT w[~ WY   !:XLHܼ_5N!!I*fE {J̩ 5$>f#G[-Go " w>c.Bk?{EE{{/4AP. H/RUPPt{^H/R;ϛLvM6ߗ7o^3{Mtܾw:pl׮@Q#IK13N;)I7Ҥj"PY1Sq4Tɘ<9ΜiƝ;y8fn܈lS{%:J6yLfAJpDZ_~rD E؏+)ZYPTk0\{ =sNCHlu,P&5#\<{N"_8"("("(Uj&$DCt Is&Ftl8pLhUtV%pHU{\!u|9}:VmMn$ [aD^K(H$)ɡVھ5rM4@"!qI9 V;#:>^o$M'knM]½?Lqٴ&$ HVCĎl\?hndl|qJ-TF%Q9 FĨ?Fd>xttDE@PE@PE@P@@@1A1~N dq[AXjL0jKLd#E~ H]̩qk`k܎s>RJe3#K/ƙ7kNn<^΄scn?kqKm>7ǶE>,Nǀd99W/(_5ߒMDA˔H[Ξ.P S㦕@2tڼذ8S!P0-;f!p[_OHBjPR)ȴek[~NKa1R=#^Oxu%z}+4J'?$|'4.3ڵFrK(`aIDPE@PE@PE D)\0j6 HQuL2Jt]I#~w\oDm1.RJt\X?Em!:AkT JtNß<.'DYRKn:p O?XB9`+չYgb;̆3M=E| \ia(6y&3hҗG8}%;DGau|u(9"@IDATO7ݐ2v'!ReXCua!:x_e 6 H9-Ѿ*`YQNtTunÒeOTRR]|2ET+2_wrN3s㋑`kQ`SS>vtbƋx.]u_m+"1C6cA/aww3~^/]N`f&][7$͒BOEȱNcN6]{kZFܻkITa[ g9>y!j$<5rloMj*A[YH@r&Φׯڶ y@av44^@ʴֱmgkk$dVןshSv52 9* xyqߟ?ihk" e_%Gi_B9C 5U{]Ν;;ZPzPE6"4ꛡ/QO~6QD~*Jw/m)O5ޔBt/Rvu~HHd*>''/o p5E浜gj/rh!{}:j꓍ S}z]n0aֈQXϱc 姑+"7tB+rJ P]:8GOx蠐{HI4Lx)(Mv[0?e|t>g9mR~>jyvOٍu#AFLDG)zxB&*dC򊳛97x=[ סVnɈFP8k[@qM&PzL}}O8_hO!DD7oI gyCE/UVQB?gQe{ s̫cpdC(sh9%4V^4}P̔7݀2#6%zev/"VO˜"mkِ,_FzD~Z͂}KDunng۶7[gRvO'K!zt$na `Dy0mTYm\+ל.3D+:Hz? TP8zI4ѵw]#?9KL)`j&OK؟ %2?q=Er_ݭTrK4B-Ms?kkHD/[9[Z)3'e$)6wD3uDxPlh2x u E@PE@PE@P^BᴗT""znx&Oƺ}CT۵!~7LEg}ׂ\Ey}ѣ]ۉ\W='iYX"B"`OT9{N@ ͑V,Dt9K[ jNcy# lg8 y B. ^91ODGLIh{s~פ]Ό ƾ9սoy0y|PHhpqtL5|!:ko8H]=vՐ^'9~[Q}Tu<9e@teHx?D'\xDs0#(x-3A+伌 3&ri̾!P C8`:-j< c/ f нE>hZqȠ1{ -5}UL!nV iVs( SR1}헒3 Z3-fSSWÓ> bI"oXǷjkj$eN7a"P8iLx玽aM×-=}Z깍]9@ _wvmov6 Q#)"("("E@(tGʛ\7n* D;i^蒞 ?|VȔ"Dsmk. -MҤ$7蠣$Thie\ey51BoD׹ARf|2q54ƞ?nc[S̿@́ {-_=*l4y$:Dݓ%zC*I;scRIEUp0=K$xؿùq:/irRp'9+D OdʹÇ2d-ZC-ֈI;_b޷w3׉V|2 #'A2$#5&cBp`0lONzS@Կu钺-sJLo֯7ے*}Ƶu=XqUok"V@J/`v{" Bbja[QEwzBYDɴ`LF[rĊ"{T>7cw;|J͜9~ IS#-("("("D@(v)DH")xl[ @&ȟ2]qv;[űGı.SA -4 po΋.vKw7Q"6vWchx|0[P!|N'yEzp ^'/yDǢvFDݕWS^L;?jM-ndBw pBtPQ$$.oF\h1rϳ<ڻWd畚Ԯp7;z"*$ͨg`9&_6DU9/#HDZ~x!ze9Sb-3inwe|#e>SA"CRРm=tc2֚X95 Ld NR .Ad-'|=ugrp>Ys/ZejoP6y#PUW$tVz*w]ձtE6tpG-͋s*NV[[!M46tfoI3B㩎 &V  nzi9$zHD{&LO7t`QL{Dc jL[(x2h]uF糝#9?Fi(":V} z2W>SDbsG1ăL(r4evȍLsT\$ըaGcV]s#;1wbS8kة*2f$I>S6sIN!Ǖ)- F,5[ ^I$<>ֹpⶭ^l5l'N31Yk:hӷy,a=pcs]N{.sf&`X_F1Qov=d-*n-w[8|T :.y1 5@ҬIWogbm@ɑm~O_7?Jv*D)bN\3ts*TAײv\9r~݆÷BވgE$hz5!; 2 ":vN!6C3QBItTx(AqCQfjxtsEJM*iH }CKaӠM4ǔlα"l t'NfEepr[|,'> %8c)=_JM,r:J-;&L>۱X>JIZkf.8+:g,Rܿ$J*>*:voJ!mX"D~6g4HH/cq.{X{BTZ$$g|%,U*Qcغ'#K9||%u#WZ:5pE};(+)u[K#%G~!oYU"CVF7"x?!KwC{%257{It̓=ʛDoɜ9ԏ{_PE@PE@PE rP#rpQW?$ڢ8ڙҁp@xєCа!d*9IDƑ 0OD}>{F[_7Ef&e˂ݻ`$Lj]!]=dOsw'`D۞dTjռØPyqҲnýk@tp;,Ɔ6h,iy&AK6!n ia*=l~`ށvZ7y淞XQtR'ΐؐ 5i#: l8A4|j%H 0% rգwln]waF7N#VX!&qxhi#d(fwŘV΋1:NwU|ڦ7ߵR@1O'I*#U >S6Yuf Y lcJ(̐ !B':+H:% VHI7B0u]d;8ϺJoEf$#:FrrnQ|uL"Hx3S Nl]-8/#@n8]OQPm$vݱҌ$'ތ5y=xK:1!Y9 AԵ5bZ YK~g y wȽn{E@PE@PE@P%:q͆ Y&MPHޒ~=,YbT 5w͖IF/ڰI04,3K$.q7vV(K#HBofMD1u*VKnC~h3rI#ŶSur H?ܔHzJ~\ӷrm5G:TKP_{ޞo%P}Lu*=Q"4Avڙ=S1pH/oWh]/w5 B_?y=LBe-$P^1;)B~~5ܺp K:.5@mӨ0/$.LEz-z2#o҆Prhr9lqLtmJ] k4e^Kgucku#/]uÀA=-%Mb AZf71 3(NRaVapuT!pI.Fotoj ە$GHPv3[PBsTS:wSw15UssV+jv^ =zo{9kX"uYCАqZxӑp9j(*ݏvWE@PE@PE@x:PoQnD8n0H%:Qn~.H^H8,.qcBJIaJ4?NjnL3t~y̟tGiP)rMAx)!UTm3 Lq8Sb> S $z$"L_Es+m$:nqi%y!sth|nɑ(5IS(%QKI?2*"ak>:H m y*o)rA!T=0}89/u0)d43B.!Cmt}ObNcG ,m("("("P1)"D6b Ng:Hy":"gU:jxh#Yk$uD ^PE@PE@PE j!DGԺE@P0 pmmȭɔXL):)]59 B~>4 lfuKXϞW"("("(Q %:("/c~i2.s'G5MCuQF,jH=[Ďc Söj {)KX}~=VE@PE@PE@(5BPEzegHL30MPG>(VwP@\!гfh]8DAe*?Qw=zSE@PE@PE@B(n.EPE@/#@lFrč<꿌^(+RNPE@PE@Pozu"(""D SE@PE@PE L(&"("(@TA@r't"("("vnʹڿ\ (Zƒ8⧌mG԰:N Amz 1? k@aBMd> xE@PE@PE@P#DGpLښc{ho#omvLZ.{Ŝ9}0|rsXX1T/Z>e]ðK~~} I3Y-Z4GxRX0jcW?-gw11M`CD]''?j8xGΝ3Ǭ=93j/Rs{1`"0}ۊQ&oۄ 'e3[Bz;w^L55%ڕ?>*UrȄI^XHjol|m^;W FH&a0`Txpkp5 صqX@\@@wDI?_Ϛ'-BMd> xE@PE@PE@P#DGpLښEb?4l"ٲacD.ӥwflچK!8cÇn.ď'.]B /c*Z64$¶G9C@W$RԩyX2d.Ԃ[g\gUSDt\;q DkI%s$R Mtk5[~قyS͞6r O4~ڱyRgBW0| PaD֝z uGRd>JZE@PE@PE@P\(/B͛:.H )lR#Rs!Zr'&AXq}42gH~F6\}O"kaCH0j$I|0Fri'4iFT-R*Wv6 ur0zj:x0*'י3Mܸs;̍-ujDG &Iެt<Ȓ2rSI?8W\Q>V#ct|9c%eTK= j˘{0?G~{IX.ʤrg؉9Zd> ']PE@PE@PE P#}J̈́UhN!<$)~n7ވ Mj7ߴj+P D/ONJݻM#u$cx+LYC+b)%ɝ䲟֡9::9JYFZδiCH$:$N>#GPPjgDի !)$wͭ\X4Ǒ4>ք@×jq P͐o>"VIeH$#g5Ljg_~("("("((>1@4"܉a,n`++A F-Иz)lcȏ2Z95n\”hat?>aCt5 ;$Sj(il\BF$:NL9?o"B̰I79 {/ EZV,ңFǕWph!I%ϣv>䨔{ž9=ftx(vM܅{7!M4HW,8.qdČiEf+A }/l;K.!nҸHU(rT́Y}KjODǩͧp|qqoGBlfd|)cE@t8sfɍ'?"˙znbG=SΕ;P2.8SN!iB#l'%; |}cBcXo+,$Θ<vI]^S&#x.꣈L4,I("("("<x;呫W1B4Fmۆ3)ICFq[XK4GBԵ-DG!#hʔAW^ ItsE2Kjѭ[ZI< ?X?ly:º>LS|p44: 7-lŦB6|4C=todǒH>~HԞ"^cRƮ$D b>,DK, >%ڗ@̵:#Չ@NͳavXɐ@*CJ]FB qe[3vrVΉwY1|n|1l6 lJ1zNtBx1مb+a!Q"yf,h_%NnKţ~b ̬?ӤKs놤Y֔ZHW49ikoMˈ{w2*B6csK5g]'5$@D'cFZm ^WMWa>"h ?KIw;h^"Uɭ+\H ?{\ N\[|)n;Tf$bj8"rKތm[/tV$0*{h_ }Qm0 VH I%RKG8XI2C㵎- ,wzE9}}fkO{"< Do]H_~֡"("("(O%:,~_9cDU,;|.U'RYpаm^EbhjF`ѱPs ?,]hT0nJt,ޱΚefNARo_$\mNß<~/cjNBdUXko穙Ǽk.ܾ$FtIL&윰<׍Mhޘ1ny;ߛMt/6b yy>9Qgzk@}~y#Y^ɂ/g |cxycv}XU&flF䪚 xƼHoصkЪU+SuSEկ_cS/1/&+' zshf᪼{.~yfHZ_x$ `WC7Q2 >׎]3 . n>V\ek"؏EpZ| WW>5@Myv&B$Hӝ`GZ86L*!bčK?_NcR# =VtlP\#C3Om͸;rH,rWc(Z#Ʈj!fvH M:\>4ۑHJU2FJ0$O ymc@q!l>jYsKJ`f'"$9/,uwHn @(i'60$3(Z}-a[fc;X8nP4X\6!h7p.0_H:-um9q u[k|/X#Y29'p 0:>m-M\#;\^j Y("("("D2"B0\7lߎ `31*5+Cܹ'F T?DG)A&JyZfLoDJ+l.bŲgG̙񼬓|_,DGSZ$svEq; 3tINt: w|3nQT)Se$:Bk[7Ď)p7c_ܽzפ j)b%eF{;)h=dNbB6 NmA<)ew-haqnlu$LHh0}mN\éV^StUSjkbE/7΋8EiLc5(g*f18 &T -_0s1%{m1$ͩS>}棏>B_!<ߪ瑕ޅonjcTD=}c_-r5 a m9[GX1oDsituܾxS6](D,jodht0Y~Ƶ IxE =}R1PFPcLN#~h9eKf߹|+.uD&?#8xqIE2`+ۘjXa UjԻn= D;nay( ٲ"$#ƭ"JA"0vKkFFd0UUq3zi#~Ī,_ekۑ}S0(]>kELpML喫IC W*-zE>6NX0MVcd1Ƭ浖,Znӽm;+bĮ.z]=q)0~b},MB,"oبˏBvp5wFthuY"NGȗY_{L#S.C[%c v-<ʅ_KXy]ZVE@PE@PE@:(up2E{i9%b\7_ζ,Ct0Z̙X&t(k%ypmNѲFF$X#GYtYOu4o;Nc?MUmMd+1H0?>Ch8ǡzhᡦ|`"4߹sggP*T[ojƑ@$zTV}3ӣy#:*`48دw&կ^EP?e&BÛrXvX3=VVC4;ΐt L3٧$:^e0ȼLM5@-9q3UW abw\}ѴaOK؍w&L5 9vla4bEZ䆝NuE[N/~jkYsP5 /^wܝ &5F"3x6@M\LNTKGupUjACa(43־[SW$3.$h+!M4a\IVsWax9uTt?rXnkAA[3:VQɂI=n+&h QD:䠵xY[-=2 2)J"Yoq 0Gy`Jt,m("("(SQ&՛1w\Q"qpɗDot6/AfCj_tZ5DK Uayݍ/V,Sa!:6㣫8 i$QAq. BFm{":rN-n5k|AyEGsr)uU6 1Ţinu Ytz":⧌,Mj0'W-)1t$Hh&w@IDAT"H?%Z^(68`0$[lhX^qv3z&gwk:j-Q G/m (nɤ*P|OI`|+`)q7(Ft9M~6L40@#]ye*>9g)LI{@\f'׷HKzYJnW%igc{ e+2sK+Tx$U7?授zFNjpM0("("(ӋQ"~7›JDDR96Xobjrv 晩oZ7k(oo?zK5b;J$7 4R$VhC$Lt 6#Gbω!9*E6:gI~ ޗ{BAs_It|, o!9l='9 @cg.4?}XIV7mjX:"wyޘ75ңݘ*{FĜ?4?u$t͘!$ǭ)hHg %,gϫ{E@PE@PE@PQ~x\ ŵ:dE]B_ PEPGS)3eDǴ|C~;G)\kr|eY]LE]DI%@EY@_)8dKoTWe,`Z{OJDsj]% 4bt [d_:'/]$כ@׾~JK.Ƞoe9nki/6~zJ$vHHZy'dɲyhDPb=y[y23_xQ+"("("DJtD>JD[G;S:/rH6DlB%:8G38r^<2yۧR#{}xk 1sF_ ѤlYϓݸ{վI-+$#`L)Sp!xnxۓR>^v*O0NZv{w#cq%͒74h &Ļ $-L ;׎_ vF>O2+:NuX=msD-?6(0#WMIFDAra@zTԞZ۔~V ($~VedTaC>Q`&n,! $ՕmL ş$DDgtEXz|A"0 )"馜v[΢㽋qYZ魨rS>ڌ`DHT :jՒ/ID0 of>yөe ǣ)*ν;V1Qכ1Z7o^wI'&$3|!6F[,_ $"vl:ya Yܸ>ONX׻t("("("t"Dy<ِ!3>4פ [S֯ǐ%KLv*F12%Q9 sew$9#%NՊE}|`IHͬYX":(=NjM xHmF4iؖb[NaIݑRvZIUTT)b/kVHʕ}ijkcp$QG:Sf;P;Sg3NZm*C宦sCA'I bE$+fG?EϏ[naI%8pq5浜SNǽ\lɐFn^g?f?2nkx|mɈwHiScl jXT+)]@U|M/E$hiTW?,nw8cvҁsB#:HQc)җH^y2u$:,EuHZEB`ѺAڐV.푣R ׁ-0NYA}F ڋCL8{ unp.s0%^^~BL#$:h_L&atiW R78.*t32Ο">%M-uRծwb _hbjBNc.=`NuJ}E خ@V]mO<}-+Z#cb5N Ϝv:22.;Gew^{N"("("(O'Jt<-ʭҍ&r]"- 8#%%nXH):)\xՍi9S(m*E)(/E<* |> t3)gJl;"{ާa|bsO!D#Q[hNrTmDG;M7.d>/$|2yn-#9eѳ&3|]Z$j` iǷXFE$LbGG~8Z!Q!Xe-#EN8H4/"1S<1UfFhs3…!$p.SIb}/ao?E@PE@PE@P3Jt xE@PE@PE@P#DGpL5ǎ5.7@혴n]`w+U9sۅcevU}]aK`A93fDi#Zh8/mێ Ppa|`MǮZ9[,gwX0]`CD]''r%~?tGϝ3Ǭ=93,2sت{1`B0}*UP.oۄ 'e3_G;w^L;5:«?>*UrȄIf$HZK7n>6ތgs>:DwŝwH&a4 Lj g]<Ȓ 6(ig [ukPϬE@PE@PE@P(mMqxOC4Al0~ 0%wtxbl֭~\Sۤl Þ'y{ЭW}qKh2`@Г>U/Z]WAp\*lQ#̑>4NtE"UzNǏy/C j@߶ }q6ZV^+"Hv,=⮋q(I"z.)@="Fߎ͓Z?#:Ο዁ҕ #:p&>P5s>*"_TUע("("("B@\!$.߼iイ"AA˖*:"<RJ%wmmeۼG!Cp+ adõ۷q̖h+&}~w{]IF$/98B8픐&HŊsjfNTOƱR%c:sIw`q#N(%2ɛ͓YRFn*pDZ_~rD E؏+)߳`jف?{xو_@4֘^w;Q8Gg_~B+"("("DJtDOz#Vj C4'$O͚~ѱA|4qiVSYkv=#=Vك iX{7S'N$p o)cbyEV,ED \vhNNB3mZ|߸1ӨOQT.T(aZxUH'iJt䨚{#V x(vM܅{7!M4HW"8.qtQĊ iE;A y܇ť7i\* 9@Ҭ%'88#u\>321"@t8{gǩ ӋVnlG=SΕ;P:?ӛN#M4(ں({÷ĸtҁû}@w݁->)g®5I8sm6<3vqE[u ѢGCEpd]vԧ*<Y^qHsm:R?|n$c7}Xgkbrrf^^T Qr%D Ń.): \:gE%0ZV3 "HM+cd)dIya ppէF`Zwne*>/)^Զ4>Ri֚NKa jC#[BԵ'DG3!#hM˕C^ Its'D2KjmZI? /a[-L2Xgw30F'p慍T&zhҗ8sFau|Ok8͐2v'!R}XuCua!:x_}16 H9-թ*W\>Q 4$K<K=RHeH)w1ˈCHS!l|C~}ޮBj9\;ύ/F钦M)FOQ8ٮ/V9pl1~i96[̔7ٌm ߈7yxt=8On8ftinݐ4KRԝR銧s?!Ǿ:9ٸ~7iqpU&QP]Ȇccn)/`#@m P#c|?䫦 L $H$ h*s. $߯'.V-}_^w7* z31Q%o[/tV$0*{TX0x86+${UI%2KG8XI2C㵎- ,wrE9}}f+݋T%"{{3 y[+"("("4c3 W\3y;4:+~-Vk׮u֦릺kԨ/._b_~}MdQFkܿx9~|ZA& ۲wQ8sfuҁ9u*V[c 83 3tINt:w<3nJаLSe$:BkGĉ%p7c_ܽzפ j9b'mF{;)tdNbB6 NC<)w~oeqnlu$LHh0}mN^鍧n^StUSkbkDE4Uvq3T5q |%:-< U#*VfN4F"0dϯ{@v= IsiO8Yc)bٽg tXun4+kNtD\gno &Y5-m=:&H>c{4wE:"AR|tLϕvo8|D#u_7(h3ObV: ]0Y0ih-8hEm:9_BN_od#kK=GFcx@&%RI$k3-n%QX)[L0ME@PE@PE@ DGI OݻWH\3 B(KtPpŐ!FjHr0aDXp^wc*BFċTqX@4(ݸ{5nI6mj=aNI:uP"9 *DÄטbѴz,p:i=SGsC&5磫Gkݘ`N`4DMt̟mw/0ir64Y$8sWpzoi얌(` h꿶7 dhXDا= /D#} !z& ~D簟Mj8? 3wW*z)OF0|(߫|c^+DӜDG-A-QXUQ} SwuYdʌ؈< x~HiX= c7eC|qyP%!V^/mQo:o]0 -L=z*@~C!? I.2`ڬF۸V9]f7VptFdk<pzf^i=kL4[~fssRΩL?Z=7h9Jezx} 徤[h|9ul!azlExni ϜĖ@|jQ։A3=&P#"1E@PE@PE@"DGwCvD1y2ߏ⥚ܱ#6ܹf*:[Us ǎa_-){zW>yL#Eb6DI'k7r$xyA":?>gL<Ә}sC8I{}AptZԞy4B <+Ugkqčl"xnLjoNZm96簭2ԝ` )wcI[J4DaИh>Hmo5"m:H9u5CDwի5<ﵬ%i~YRLGk1i_Zxይ~ LfuU#_LD jl HP8:L4[ÓЦ9/(d]?qinzjܾ5S߶ukq?nf]#4r[jQA՗[JHRŘNRpM^$AHL>⺭sK"x"\/SH179hZQ7Qof#\8!z-gN9 !IbJtBE@PE@PE@P(n#015J$mK K$S=Ctpcb8vqebTB06\?"KݒO>1Q"6vW3hx|0[P!|N'yEzp+^'/yDŽ FĈ+0ͧ1x"(ݥ4*_mߧ}F~ԝ,9Z,z'opk:85HH\8wbgyw7,+5+]nvDT":HQ;RQg\l_Fj$SCsZ} Ygzb)F|lcj-D{֥qJH K[kb}Mk:ٙf-^N\ȐV[nO J6DI=| 瞷ޠmF@ loHKTctl nZN%xۅ؎`mw,m_-ƜD/I/Z54BQBV~3̛EĜ_"ρ3:|QS4I:L/!I$ ZRKMjm/"OPE@PE@PE@xxL#lSDy0ㄐN+&|hQntW6]WwkYnQtcΛTD?XƏ?4拄al+D]]PX1dvwvYoI5{wL^.i+m\'Q67];d(_qX]g1b**3WL,*#JPj]RMy8Wo:Ӕ[me2kcW 7^ضMO_GZ`f: v:1b*w]×#-c޻"@?lG] 8~k]ktiZߤs"W\Φh0r͹RHC6sIN!)- FLcI5/v~zNX0JEAqVVt'OฬQiQiۀ<d?mLK n*D /쳮wk2Ȇ)Z˶W{b,ZCv`tl:#Il3NI4LQpڤBx*o rpwNgwv5vDܵrH V7F3ѷJ)/]x2 yZ)"("("Ec7\B_PEPGc)3EǼv,25nڿ 8&joǻJz*ݛR>yj+ R8Xֵ~7!x*-{ 2G_u(N҈1 Tl x}K9||!^>Gljy z,P^ S(FJBaWoW Z9=<1o,Y9|XS»'1Wx=o^Q.sP? 5jE@PE@PE@PA@5G_ ~hbhgJZqESi}&(-[Dh!GϋG&<9o^jĻom|!:fl܈ !/d7Eo#aRK.o :9%ɔ)8<{wItmIFuYi,= &'-;=ܻv K Dױ"lqƒfIoziMoÆk';|#'~h^yE'u rxZݞv9V#ˎMgѫ&Z#[" 0 \=&x7V9,jV:5q7XI{>$:b'7b7Ί']!M4Enf]0gw^Av䫗3ǽ0h}J$U9ٯ4U_ɪ[:SIueSBgd :]D" ֹ(._BH)"x.xk}AkV)mFr0i$GNjIpID0 of>yөe ǣ)*ν;V1Qכ1Z7o'NLHgyB}m|-D,%䅁dq<;a~l^~lӽ"("("(%:[ :C~͚%9ez Y4X*j-a`8'iX;H$Y.q7vY8I#HBfbMD9u*VInC~h7rI#ŶSur H7ܔHe~\rm5G:WKPۜX{+nؖB10(SGol1#:hLcƠ)#yp_}qpι S$[o5"NUZ"^í b_}t8F8 sߙӛNǽ\lɐvn$jelDNJJ6 dĻI$ƴQЩ=6 5)dݔ}Pcx iU\L"A~UPv {1f!8'-4$5F2*}h! [NŚ>/$.Oz-z2#}o҆PhUs9lqLtΚh+ʠ8PFJwo[zY+I*'4"A|jfFgP 6z.M1pìz " BG?#C~]+r~] %nW!1B1jصTTlAv ͖϶RMQiLTiq?Ybەy1/%beE<>&!Qi##!sԨQv',܏U׻t("("("D@}r w HT]"- 8+G$%nH):)\xՍi9S(m*E)(/E<* |> t3)gJl;"{!D#Q[hNrTmD'w>1N߸te:4>HAFϚ̤nt]kj}ܒb05%6jDdݷ9s ѐ>ǜ:LTi2Zj6J>Ow'1ߋ|\6SE@PE@PE@x(E@P"F1Y׳]C$<*5hsՒ:"jyu("("("P#j]"(@}IѶm6ldJ, ɔ nȜH?IE} 봰ʺUl%,gϫ{E@PE@PE@PQ~jE@P€>7zv乓(a#5%ԞZq;kMo omvLZ.*91|2sXD *^>e]ð%K} I3Y-Z4mGxRR0ZrcWœ-;vXӮ]0!?oY:Ιcڞ˜uKDܹYel޽p}C*(7om{օ[蓲@;}/ϛX [m:|!p"#& u[Hjo0|l|t^;W 310Mi0*<Oq ')~" %g@H(  |=QS(wrM`x6p/]fn} a/ao֣E@PE@PE@,,dMɋ= žoŲe5k0"`ao=oV|?woRlaɓQ#E!'Oa` n{N]mO]/G}k9> 9߯ uOիSE@PE@PG@0W7o.H )lR#Rs!Zr'&AXq}2gBDo0" nƁ3g0[-Țl{Kw $i5$~P#P 9SB4v#Mj+ժ9:9S=5<Hɓ̙& nܹ'N`ƍȖ:W`G$oV6OdI&~Y+JtJtD0~ t??Tvy1pmmM 1db'ED \vh+G#,ɡV>5rM7F"!qI=ʅ V;#:>U $M'knZQD>>RTxԵGyϝ4NA{]S'תzyz)~2@7HP-;atXGoI073;[~G+"("("Y(YFԉ/w0^H7nJtPcQ 4^b &)C@/V*SVMypVFֽ?DGQCH1:nR2wKۜ?ITZH4ڮIRdiyDǟ㫏 H/VȊRzr /9g32 Gzgo`8dz1rʍѱgܹrJgpzi)E[ţ{uVP@Z:xx!;pr%v/ֳ8cbLؕ&vgzyN=|fgu.c!Zh(Ҳ,;ˎTS Y<iWr~\GRMqfsp87 ,'G~`%z@JJr PO2**ڶز$pI4< djip Lheya ppէF`Iܹl |%"ni$с* <<\lyQOȈaQU0chWM2cgص lgxƟb jS}+xx<ɬ{{Cx2qwn_ӆ@Rl .iry$Jŗ}u%|At%[ή")]?۸uå}b]5G@b@ö@K5 Ei#puS&=p)YJYu}:o=ČH*$H%hٗ߅7nPjЉ^)"("("Pa^0?p6 Eq~(~w +F&҃GL|3pHoDm1N.R8%5V>}xkQOfBFК+/8:OIS?eRۊwG{DƂ eؖ`,ӭ Ygb;Č&3L=E| \ya(6+:Zn^eOt?ƙ(ݥ4O嫟 ei R&2@B\q?~cӀMsRJw͵:#C\aqp{k I5&_7tI=RHeH)w1ˈCHS!l|C~}ޮBj9\;ύ/F钦M)FOQ8ٮOnvl1~i6+RC6c~[񜊽 Xx݌JV 9<$f4aҥM%)Ntӹ<t#Յl=ƵXXznhdlHq_F2d'A?4 z8hY"U.\ZX~3}za:'wcߡ c1 G]%Nɋu\U!ֿ]Y"?uew,9檡Թ+$F BR$$W6g[!/V i5sI lX ßtj-'Ĉi׸$8z4 aLcƛMPwY?i/ {$}fu2Hk…RbٛKX:^PE@PE@PF@F8a.QK}(o> onz2DGW^ Ѽ+mm pn>3V +ѱh|=S<bׯo4HkWQItsƏ7{yp _ItPȝ:,xʺ#–o9V^my1o /A'mrsNze8aڄv ~DGqf fgo7sշ!"CRd|1#n_m󴷖ޮ 1DwЌ\5r!z|F"xŮ]֭[.Q#ySZN;㾇^& zNv;{iW?gEFlgA€Qy%:a~%s~sx5\@~;&7ce&]H %ϝEZ-4٬=6 5)lnʳ2J$A ;ItaT 3nLu/.d 4F,3:1b@1)<4t8˾#G!}5Fg{|c"fvH 2ԪJՔ>S ,⣌83Hn0 Iz |6FL"HHFVKO$%~.mq՞ %c9o" FD.$t|m8:{-ia9sߡ\~9,i: ih:guekumv= 8O> R=prǽmlG`=VZeO[eEq32D0c%'8_[8Z- C5|Y0Jj L|%s0-laʲ'K5b!_50҄IKgXP_ cw 1b%A2?r=͗RHҒ4获&Z`1b:_~("("(cE@ aDoپ$z6b֬h.Zr31Ń!:x7Q/ϏO5#{#:(8D"]a[v.!.93:Jt:5i|39s@0MHT:DGXs?}3㶮T ˔1e_6N#z@X1 ݿq}3ݫwMڠ+#vBLyLḵ7NH9 4hino;ȓK?Zj|'yVsc#aBBlvNoKSӛYXZ:Ì&N9g[4n_RR̎ypJnnZYb[AdLSp5C z>gPB=vy'?E|ɲ%3\_^]0ItrE#aRzi96VlFFC91b̧= DsF) cyKLDZRI-qa@w1"rFǏXN>/Py#tFj"&g irUOjN+$ǨTZL|mÝ#1HY$OmkJqIJx;HD <2ZHs>*VZQ%Fd*0a[Bcmu1U!U<-Vx5Y~:C23$Hј)3#_h]+E7-ST,`ag\s^pd ɕ Is.U11%7w}~ya7(3ᴃ;eNg8o0}>ݻر /I!&]+"("("(a"2zchO8-DL0'sSރٖeF+1KEʸ.Y2gæ[oO%*m텐ZJTFș^Ʒ⎼ј_D~O{UNIyR̝u_ ֬1c}$⯄APq7LzSsaJ+DtĊ =n07 ܄HOj?F-f#yhshSvM65 9* ryqÖ[LULd+1Hv7s:X*鋮|norʕ8/mb'f GFtTShpھK"zQr!&N {BtpU 1s8.㑎3y !l&TU/~"*|%^QQSd;sMM5@/>q35abw\kѴa굏l>;&L 9Na4:NsƗ,M o9w7]=O8^s,du)_r[rqht%q28!xӊ lXX[{੭rHL߰O{$HHkDU y=1,%jnIH` :Yuh"r[ժo )nx/ƪuX1GV:Gm #x>[ia5Os TX-/Pm-vK>lPPFD"("(""DG$Ay#)znx=&OƺST;v!~;LEg} McǰFۉ@=+iYX"B"`OT9{N49gKJBy":뜹i-X`fh#澚PA^ C4s8zNryЃ 6U$1,횴 ߜ?{?m#y]:f4ϛ  ?ΖR/Dymg=# _ǯҀ$'o {#:jY^Kp!m\ۄ&ЂC&i/K?TxრK'8cbe؟I 8Ӣ̓$]T80scja.Z8ƗKdfJ5aeö$޴)` )wc)&hYb,Za?nP6sSWFp7Dmn\c}h#ZF gd"A`;>ܭDάVAkǽ]HvCIچ[%+u'|u8}!tV9k/xfVry6S{lTnLoeI30gL׀f]{ ctjFpBxR;#"%(Ӟ~, ((a#{E@PE@PE@xZP# ّ&ާ+V̍#dCM`ڪWD"gCt=6Çi*L)RLt8׶VȎO4\ѓ4f'BK+2em/_FcZ#ވ_shD3nhMMtLy} jt;/cMGC9A=Y7D"ar;0&՜*ZT3%z@^-١$bpnJJe}!:Ij9J c$JsGq?q2I4 Gz saH!fEK)޶xj&"}t$fW"pjW:l;8#޳=w/|GTLSEjBܵs%]H-Z?~eȴX?[C-D50i/F3ş1~&=$hѦ Ԙ, ߬SA9v5vMKcԿ#ui6us+J\g~:v۳ͬaS!Q'S-WE"QБBM xSce9Or5y2$MC=X`˨ Oj~:$R Q\-lIQWi0HB3mX-9AKv"q3Kފm$.:|!OW&8㺨#؍˖ňCp.+q^MݒO>1Q"6vW3hv7}N'yEzpV!<bc'wΥͧ1EbE&QKiTr+O=;iYQf [-Fm']]| jVP艨Dt4v θ:蟣?.yU g7t jI;y6uPF?* 2d}8%|+٬>Wup>S#51y#PR%!)A[Y8m$_K[ Vp7N"{h!=m|ń  /mqؖD Ol)\#-,ammMj^x?ζ,T5Yz#q/3N3u)!j[MKQMd)FK(ld,tj%F*Em/1q.\HbJHV9vѢY̆/>&VFH"("(""DG$j`lj7M$ ;|͜jҦkn-2ww+=6_ Vu;۰Ha,};5$!tP1UKNWo鶜=sBs`.,ӹe-_I?I&_}&9GH@yK&HDPկ]Y{cv3+9Ӑ95AB6$: ڝ1TRwR||&Z6 lYu1+*o?{^+"("("Pq#|^|sxxN`jw /ڌ}NItd>qU?֞ Fr4<C1}+ȈցDGˡCqXb."HAY8h$+wmBNqtb4@1y*'A(n&::G+|)oqyEW,*NZ#May%a4#T1|b88 &.3A$zHDț"h#F 0 'r1mUDUFc( 1㺞Obd"+cg+JCX|xC0E/9{+^,c8Mk>z!&>(ך +at(N-` &#kk ''oJJ/,Bc0e' O:ʧ,2S}{J{Fi۪;ҿ3tYyBo챜{[׆ua/?ժg|“9מ _/Ԗx9K?{צ{E@PE@PE@P7Jtř[OQ V(P0^}Zà&zkO֯֘ر oPEY#[^๠Cv~:dfTZfK|2#ݰ*Z] Ow&@,9W숟~h#JW%Wah 8r<{]Lٴ,h`^!:b,G>{$ @&!*QQ֚ːu TzP! MY'mH/g \˲cq(M:!|AA |VI=s$eP#'<Ҟ_2A*ӤB&vPA@A@pC@p垈_NqEavG x=ŨQtOcT6DtmWtFt]rzI:)]J]`N= l˕Sw#H RwGttbd<7mfHXdT:u\5]fYsrb=@t`z?@QYL{= X]H UdEZG#-벌^G|:1t:4xn%@ % #s۾w,PU-j퇇f*ioF+=ntzr"&q2φȐkZR0tM0.c<LV7榆srxoA| :?7coߨWdsv麵 ,B?3pF+&aGOt' n֍I9PB!2Эzn7 \ 6"9a5#5 kA`k!j@ظ2CvCݙ}ފͧ;k \W&sMhpoɎiAO4ly:,V63c1w%o 4+ל Ⱦ3!ndT@IDATi$`i@Y iϐ 36BĒxVd' U> qpFH&$Ѿ4x  9. V0atTHg6qB9;t8@vܾ ث6o oǽ   ۋow+ABF:Ar`2gDt1>>GfNGJt p6ܠ~e'v+P9   ? /?P)MG;GTXH :qQߴ Q:t@N z=F6Q}`-aЭVF    =BtxA@x 6Hire$͙ln^Ĉϡa]n_v[;oc[qo#r͂   1#bY  5X 'H2@HR qҲά9qt3DW지D;}AT^J! zכ^׀*A@A@A@WWd  +6QzI7ޠr   BtA@A@^+xn,VA@A@BO   !:"ꝑu   F@'  D ֆ"%A@A@A-E@Ƈe_sn޿O cǦ$QȑT|ާϟk(V,A%+_SH xmn IJ;yHC"lWh=A֗bfU/WT1wF#x+;hݿtu"ezZq.@qǡb݊ieρ˕yD'HKԈKtQtD ;9] 5o oDžiA@A@A #}>]9C:ƍ靴iim*UٳێMa4q:uXhQ[iR+4~~=z#OTlTEr.'{m)T+Pڽ~7m%{;zh4''M۸:u^Qk{7cFjPʙU6=J#W4UFey'>m> M>TM(oӼ?:(m7+޽8UCm:|/ѭDQdB\Dn H:qKg9dOBNt{?~HQcFx߄ #s&2a(峂'>Q*&VFDØo\nrF/ QӰ}D!= ~EK&w!$L%,z]A@A@B@Bb(p!؏-ZP,YhƖ-4)iи> 2z޽ҥE2#.PigAY+ʼn.ܸA-F6UU}R=8jh:Q~h֌efe\'&ő*Υz;]:ݮj}4t{^>kSmcâ&36V^Mo>$!:lI! {);O\>v`Nu۪דo[b su@3ۙ\{D{̏xj[Dz:Û_7   @# DGcngXʄ{q ),)RPK@wߥjɑ@_Clj7v,] D6?x@'.]mqde:d#i50N`"P09cL4w Mj.Ljִv ub2zj3f *&%3{TtC:x<-عL(b2ɕɕ2%TRo[Bt8󒏅xɀz1a"{X9t WSA<\@n$M'`=~HP(Y>Qvﱿ8k#Z+G|yBtU[.VA@A@!:Ԉ0NԖIXM$֭8qEt8y>svtV=k0Cl{Dzl"y&E¤)WҕHȯ*^3;}Tq@\b?ܛfn姪ښckRN!e=v7-žS>}~|v\ti2%sP"i^ʱ'oÉO#qZL6,f_RL,s=~x Q=P-¤zM1?d?z:0%~eg[oEE#23G]cqI XwG݃?/C3GD {@g̗؟t*,{OYs 䁭]c3 Ku;d3HD']v *,p<{%_gwn..kCT;>b£&kEi1o oYN-EA@A@A@!:nOvI1m~ ڊ3Sz3'Ō' Qk>bx?OA5+-8ƲwQw |wScܹQaI~C,lGp$:<%L [L۱R%jZ*!_տ?ňPؓOhXa#66={&{Sيt.B5F ;`Hc5G*[KtZyfVg([9Ar#!8}(ϏҦMٳ' c͙=~igyGpWb Tu&n~MKA?":l4\a 훤?cw[wԺl&郧moW6uwKov\J{'%t?8CJ_2BIj}@ݟJCSUP޴zv+'Yj%ΒX?~}Wv욊Lq=qTbJ2ͦ6TWƐj| U:#Ny7i$ͭ=N9@tW`1%hSI߶juDy_ӼǰBz dOzF,>ڨJpHkU T`XDM# *0̬XvWiB-_;>'ӺA4- @'.# "_`WL>-RTɫ3?I.D8"ʎD6lWDD#}qHf5D m_#Kh"ߐ τNs=ǹ2w<}JSѽ}}*n\j(9ټ%,^A@A@F@F؃p7M=a1-_Z'/k_!:0xZ T~֍$N2ss[,]4R]2'Gex FZyXKdk z>T}\/ƯS9^uh-jXPMt@`aj 6TZ `VpFtDzVt["|" 7-nHJ]wNVږJ$H'K@O>Qo[iYegUd7Y`m%:&A뱮аGc4!}MpQ\2}A7EXmptF4 հt\VV!I(~|QF]~VD (O^?OLi362u?]tD~@Hd(4J]UT[Z "K;,U5͖7i^}~򛪯=v3+M^3sLڼ=&c`FK;An|3EkjpM] 8Q6!ͫw0oXC峈8 `6 ]7mS6WkV[y>NJٺ{֔Y>`^~(@1Z4UtNu9?ajfn#z9qhd%v2O!Q!0 O x$3 \ C[6)Bd4A{ :%ف}>Y7])=` 4հ2Aal@^W:$8;Oؚ<+?k#rrtp/ X$.TMq FL   !#BV\rV%-%: ݖ"@}j@r a#GX༎TR#=cc' c! $ 49`w=:L@$ZTΈOӪEҿ~}_vӬD܃K]aBkHhXhXΈ8P+ڡoH gjsӼ&(nʸJAUZ6H'9u98&irjEN6#d TZ" h8,k.~(oӼu̯U4ҧ` Q18CjX?5J wz ;NO%/J0܀rTn`9ۜޟFg7U2Y#:moGJ>:ZTw A>Yʈ؈4 x~@4_H_^!ETcl,_. }QHfq"ɗ5r:ϬF9[l6)n`BA47T6OzDiA鹼FcX+֜&#џuēiE=RE^_AW0M{,3ް"eF0ltOtZR0Y*_S~;ub|2hD{k?G`%'wrFt$̐zgC4H㋏Vw5!:xL:F5ŏu%G)Wau^ZD-vmEz+nkc$8ye@SkYc&ZqJEs ҟZg*~_ci%:zI '^"{E+cs0HWassDA'$)8ְp cjɇmG8rH4?H54HA0ֆĻ6hr4iA;曤[ `lJ!vڍuV] SC =ns/Ƃc'3.>սnԄu(vAZÑ3D&8 D0*S&9l1_:t'ܽuX!tҪ nESYflHV@꽟.9"d j~ߺSU#sf£JCuQΩDsD560 DAD   " DGM/7lKwVɆ:UUX" gqBCtc='4(^2$K6a]V&;`sEfĕKt@t ,5ϋ5n4E1WD׹Il,5oH&:|0-8Fs_u۾fMS9;;Go^ETKKN:C$:Bd*z :Gkźj#sX7V{8TR@ew+qfvV-[9⧍ʱ3g86{Ӿ% ȑlxoZRt)duZ7 n$:X:Q99zuT W+/jz5^z)z1V; {_'Uz*.,g3tun[R˄4k92-46#8ժD7 zv>C37H646@fd@z` Ռ`5.Ɏ}Us׍_qڸt*׌Y;-MV{\z}Os\6@<"^)ohLvC۩OW{Q~Za%֝'JdBpqzjlAAWj9(q2&[:3!shY=GDLF|[1L65 $]*S…t1rB 4ݖ+86>AjZ?0!: *A@A@7!:"؝}[^Qy+?86E&s$y'7N!:0ai;vc8v2ehRہ!EtX7gɆn_(kSv8qu0 vo]uZ|ǴoyMt쾒vIQbD=u)ۏ&$VX%?)I?qj{f ޙPc*!: N(ַ/[y^u[߫e@f'" !x5GO#TDUM/#FZLϳhpl`M6tN?A_{b}LL|/"{;k0KjjϤ~ XS4௻!1Q᤿M EdC=΃ Vw--413ITMةlu5hHT)&c?OD,}H#V0dCt8m9p;GB_% LYhwUF'S[ gZoZo#=~1a?1W[ 2^Zjb dD]̈́e+~dɍ+"2m*mY50CjeN*ğ, }b!: BA@A@7!:"Q r"=g[(U*ji:dZ:kr޵j9p.m:;{zMӯ&n{Bt`>3fQ櫘d.+GLߴ Cv”Em֭jq9ע}mlDO*1rc\3}8|"l0ne(w,?mսg6B**qX"1$"̟:d]*[.T[RR1skGtl~;~w(gݜdaSm!:I'SHCh#2pfEt,8p\xki[.M?tVho)娕UM5urs G:PZ6۾IhGKTΔ"_ UiDUh(ʑ4؍K k!ڌ!նIԱqtZŞuC}uzwsmx9B݌ZeB+cm+\8Ei~L\}PF cp'Nn{0Ĝv#*fе Jn58;T5mY;+Cz'0gϫˎ`kΓi]C2Z]N0I_Cg[gH@~% S~VvЕiȬ !͊H~H^hg4SwB|&Z lWuË1Ƽv9A@A@A@ooĽ8ĵW><8Ap;ף!5|'Mc/Jtd>h3f 'MJӻxͨ%:ڍG-! xNK_d' G9Hq$:'DNGQN!6I5*l?!tcMtn妪W<UNWt"a;踯#*Z̈I#BO2g՜E'XcQz$xOȑ`zwp(=![5ʯM-?|7R1Ͽ}(j,s~a\jF8p",>)!E;荑G{-v4zJU}D3M58rc6f}6 EZB*8# .}u ͪƛ5saB@4!6sgG1s"J ~:9 ].c=;nZmwÂMY<>rLtڬ$:`!/׏%-<|E8-k1}p\m@|qUCQ1D}D #ս/9%ךHBӺruUn-#ȸu3MVW#6W_v>Cly,<63uotmPDcS./4ɬ:rΎEzgg[qfmA@A@A #wO7S[uDo/jmެΖ;]:΢JA 9J#N軍Igajo%:qϧmZ9SN8Q V(P0^}Zà&zkO֯֘ر oPEY#[^๠Cv~:dfTZfK|2#ݰ*Z] Ow&@,9W숟~h#JW%Wah 8r<{]Lٴ,h`^!:b,G>{$ @&!*QQ֚ːu TzP! MY'mH/g \˲cq(M:!|AA |VI=s$eP#'<Ҟ_2A*ӤB&vPA@A@pC@p垈_NqEavG x=ŨQtOcT6DtmWtFt]rzI:)]J]`N= l˕Sw#H RwGttbd<7mfHXdT:u\5]fYsrb=@t`z?@QYL{= X]H UdEZG#-벌^G|:1t:4xn%@ % #s۾w,PU-j퇇f*ioF+=ntzr"&q2φȐkZR0tM0.c<LV7榆srxoA| :?7coߨWdsv麵 ,B?3pF+&aGOt' n֍I9PB!2Эzn7 \ 6"9a5#5 kA`k!j@ظ2CvCݙ}ފͧ;k \W&sMhpoɎiAO4ly:,V63c1w%o 4+ל Ⱦ3!i$`i@Y iϐ 36BĒxVd' U> qpFH&$Ѿ4x  9. V0atTHg6qB9;t8@vܾ ث6o oǽ   ۋow+ABF:Ar`2gDt1>>GfNGJt p6ܠ~e'v+P9   ? /?P)MG;GTXH :qQߴ Q:t@N z=F6Q}`-aЭVF    =BtxA@x 6Hire$͙ln^Ĉϡa]n_v[;oc[qo#r͂   1#bY  5X 'H2@HR qҲά9qt3DW지D;}AT^J! zכ^׀*A@A@A@WWd  +6QzI7ޠr   BtA@A@^+xn,VA@A@B}Y@IDATO   !:"ꝑu   F@'  D ֆ"%A@A@A-E@Ƈe_sn޿O cǦ$QȑT|ާϟk(V,A%+_SH xmn IJ;yHC"lWh=A֗bfU/WT1wF#x+;hݿtu"ezZq.@qǡb݊ieρ˕yD'HKԈKtQtD ;9] 5o oDžiA@A@A #}>]9C:ƍ靴iim*UٳێMa4q:uXhQ[iR+4~~=z#OTlTEr.'{m)T+Pڽ~7m%{;zh4''M۸:u^Qk{7cFjPʙU6=J#W4UFey'>m> M>TM(oӼ?:(m7+޽8UCm:|/ѭDQdB\Dn H:qKg9dOBNt{?~HQcFx߄ #s&2a(峂'>Q*&VFDØo\nrF/ QӰ}D!= ~EK&w!$L%,z]A@A@B@Bb(p!؏-ZP,YhƖ-4)iи> 2z޽ҥE2#.PigAY+ʼn.ܸA-F6UU}R=8jh:Q~h֌efe\'&ő*Υz;]:ݮj}4t{^>kSmcâ&36V^Mo>$!:lI! {);O\>v`Nu۪דo[b su@3ۙ\{D{̏xj[Dz:Û_7   @# DGcngXʄ{q ),)RPK@wߥjɑ@_Clj7v,] D6?x@'.]mqde:d#i50N`"P09cL4w Mj.Ljִv ub2zj3f *&%3{TtC:x<-عL(b2ɕɕ2%TRo[Bt8󒏅xɀz1a"{X9t WSA<\@n$M'`=~HP(Y>Qvﱿ8k#Z+G|yBtU[.VA@A@!:Ԉ0NԖIXM$֭8qEt8y>svtV=k0Cl{Dzl"y&E¤)WҕHȯ*^3;}Tq@\b?ܛfn姪ښckRN!e=v7-žS>}~|v\ti2%sP"i^ʱ'oÉO#qZL6,f_RL,s=~x Q=P-¤zM1?d?z:0%~eg[oEE#23G]cqI XwG݃?/C3GD {@g̗؟t*,{OYs 䁭]c3 Ku;d3HD']v *,p<{%_gwn..kCT;>b£&kEi1o oYN-EA@A@A@!:nOvI1m~ ڊ3Sz3'Ō' Qk>bx?OA5+-8ƲwQw |wScܹQaI~C,lGp$:<%L [L۱R%jZ*!_տ?ňPؓOhXa#66={&{Sيt.B5F ;`Hc5G*[KtZyfVg([9Ar#!8}(ϏҦMٳ' c͙=~igyGpWb Tu&n~MKA?":l4\a 훤?cw[wԺl&郧moW6uwKov\J{'%t?8CJ_2BIj}@ݟJCSUP޴zv+'Yj%ΒX?~}Wv욊Lq=qTbJ2ͦ6TWƐj| U:#Ny7i$ͭ=N9@tW`1%hSI߶juDy_ӼǰBz dOzF,>ڨJpHkU T`XDM# *0̬XvWiB-_;>'ӺA4- @'.# "_`WL>-RTɫ3?I.D8"ʎD6lWDD#}qHf5D m_#Kh"ߐ τNs=ǹ2w<}JSѽ}}*n\j(9ټ%,^A@A@F@F؃p7M=a1-_Z'/k_!:0xZ T~֍$N2ss[,]4R]2'Gex FZyXKdk z>T}\/ƯS9^uh-jXPMt@`aj 6TZ `VpFtDzVt["|" 7-nHJ]wNVږJ$H'K@O>Qo[iYegUd7Y`m%:&A뱮аGc4!}MpQ\2}A7EXmptF4 հt\VV!I(~|QF]~VD (O^?OLi362u?]tD~@Hd(4J]UT[Z "K;,U5͖7i^}~򛪯=v3+M^3sLڼ=&c`FK;An|3EkjpM] 8Q6!ͫw0oXC峈8 `6 ]7mS6WkV[y>NJٺ{֔Y>`^~(@1Z4UtNu9?ajfn#z9qhd%v2O!Q!0 O x$3 \ C[6)Bd4A{ :%ف}>Y7])=` 4հ2Aal@^W:$8;Oؚ<+?k#rrtp/ X$.TMq FL   !#BV\rV%-%: ݖ"@}j@r a#GX༎TR#=cc' c! $ 49`w=:L@$ZTΈOӪEҿ~}_vӬD܃K]aBkHhXhXΈ8P+ڡoH gjsӼ&(nʸJAUZ6H'9u98&irjEN6#d TZ" h8,k.~(oӼu̯U4ҧ` Q18CjX?5J wz ;NO%/J0܀rTn`9ۜޟFg7U2Y#:moGJ>:ZTw A>Yʈ؈4 x~@4_H_^!ETcl,_. }QHfq"ɗ5r:ϬF9[l6)n`BA47T6OzDiA鹼FcX+֜&#џuēiE=RE^_AW0M{,3ް"eF0ltOtZR0Y*_S~;ub|2hD{k?G`%'wrFt$̐zgC4H㋏Vw5!:xL:F5ŏu%G)Wau^ZD-vmEz+nkc$8ye@SkYc&ZqJEs ҟZg*~_ci%:zI '^"{E+cs0HWassDA'$)8ְp cjɇmG8rH4?H54g: Hދ4iDATJ4QD+*VKS@"Ҕ*R7齃N6fCB{7͛3l{b)"9a8W01II4)>}qX)?{PVs![WF6D۩]s>ƂwG .}_wzZ1>DFvj}x[Y(_$!dǻ_D6#N$_}-kSIE݉@{n@<[ٙ(S@ٜ͌+3#X挐$к:H!ONS͈]WP9nFe7%Z=*"("(qɎx;$B6 . $=K ct>\aș>}D{mKx[HZf=AH(L@q*,2/SX;x-j#:۷ͼ6)6?`䭠Ge#l4$:D? ѫH9^96m&ԛ)YT'3%z@Z54p>4R=+Ô!:If0sx̖svn<}K#=ŽqȦ#RlYRt.nuS[n%:D:%zcqI%It^pJ a7xO$O^Ap ih衆 !2+ؠ:!K]e"ky$2-:֫0Gk5Q n`Zs)Lgjv]줧#" Ak`*(Yj'PCmgWtqn}5I=q>W]R]ɚA1~[u}޼ڹFh,8l&u2q`~Zt R/75&,^I;E)(wKebIL[ίp-*<)^$dBDFqO_v%F]Qh| mz!zZ: !Xsj==–nDF !bEakI8*n+TI }D|Mrg͒ )t!ZTV~hJtXT("("(w*Jtı'{Ce+& T?g}(Ҥ~лa;i}h>"Ԍ"!qޥowbs/O~bA jWxKDI3j]*֢)'>if?70jIW0]>/]GdN;%ܞs2;o2#zcu"EVТ"r2r^H S4_w PtȐ8 |`ykyAz4F[k@ѲNIM*BR0=Uβ&q*[&:h*P[ۘ:Z.Tp& 5/|=w_h%9z#;L3>ʔTLM勐 ter )FKVLL]6{,9q%F*L[?FcM8&JHV\R\XE@PE@PENE@8dH7H}!;rKeΌ֒ylbn2sQW"ݶӺݻ}7M8"6s:v,V7-DG"ke@o$e̢EK#{Kz_S{˖5]WȭrūL~Q] DbMt,b9-bLATTn8v#&l1XygBK5 ߉z)vi[䨘#\[1 r̯c뛭/< 锦:BtN駘/Γ11]SW{-yO]?66lZ3SՔM6Ez9Q[K؂XPl[7b=7͔;m脌2rdMtu ypn쬺8Oz4hS)Nۖ tx;pbV=>rNz,3҅5:fg[CrNo;, Us:$(J6:ϟKL(dSns;M׎vx%D x!QL{Dc~y<jL[2xo3Ue,܃$)^E¤|YqXB,zL1zc@%J.^3)*^/)35LM"ߍBG1m/83 .Guͭ0g#͝ 9hC~,P\oH/?tRًi@pBۏwMe/g86>j3g;aQPڢ"]9NôWڼڤuY{=Zbq-R~ /#d{4P|^֐`T cF.+4K 9gΧV'O'IRwk`HJBs0e/>O:D5Y2S}qI{Fm{=YЩ;ѿL}4]|iP_}6cd#t1VS~_tғ1qKIor,a86=*"("("(ۈGz?ovV;O蠀Sŋ gώZ"jnbH-QtbeB5afncKয়NDˣu))ˎ {b/yiO+k4e_G䞚|{Ԯ۟ILVv6FDtDvSSIVc{]n{0W.3Gnk á5(y"4 y1K?Yo>.'vBӘ@S犩EH։ufcѓ6lT5 (Mg՟ iot")e6oh0F)0ZDEXj{yOp4 j:>g'r+@4nMD+Hf %Z15w^Hd.j%|Fk{l\TZvCrr3Gav5=N-<҇!b?"GC[wWKxr&S~&+vYȔE3">OXz.$ DLDm\ࡪN~pYζQ׃4*=UW.9H]_~$M'NLG1ejPs%,3zƱ4%9G-($82Kc?oRu%: ɄL% 8X"q^SE@PE@PE@Pb %:b {!9~hhO=xeK͑F[99}~ $Ԉއ.r%"֏>j˨`$Lԩ]ݧdr 6{&`DH2gz5ogDgWp'@G,-.\,K; =iRdIV=JZJ?8LV|62Ќ35HI*{*C\9{.wSm vep`4 ߡӻOh l)€pzx,yrvKZSB+|ioF7ёĸz!&qlcdHBv;/яuctMwz/Fhb#SOtȗI![:+QEd7yS,x)"Ü5 ]>q{}Pw]%n}:~{vS!f$#:Fr8qjIpk1jH3KvSݗbu̡6ѱoJ飽[ßSҰ[O=괤ɝ#?xY wYs9'w4_C\weȞ }r$I x`*kޚ2>en$Ee acě>Sx{~zTE@PE@PE "DG\}2A!3>2׺5ʅ/ǐ9sLjBe 'Q%jÚ`8"iX~w,H]mLTL4FDdM96.w7Q"ߝ8l1(ȑ&;SL}lHtK0^I3fݑRv[fIUԬbEbg=},Qs{ځtϾcqGǷ5)Mpu3ᥝ/79~Hn!7Nf?(}( }skdC~QT}$Ocpye. :Q: nઃqnK7- 6(oWFT-V1Mt o?~(׵ضj*#M"F1B(޲l=dnXi8Z{5Da'aC:Ȉxaʨl峡auKچy!qxRcd)=,k0hpzH孚WK<ڷ[h2Z_gQġ?wdg#?'N⋞ z9i5ʯobHMGM1Ng*$RbGE_7nJ#$F(^}OYx;ƥ(wF,djxVd>8=K6wk­1m`iOB3|EH|E,Yr{t Isw`'/˵k2 '$.[#[zQ75X6ͦrs:}h,{v-綟E@PE@PE@(Wmc"'Ν3(u,n\ODvh#0rE|]qʘfP$輏&R䚂'CƢ1m3 Lq*g*׽'6$z$mBѨ2,MZx&ډ'0?/$2yo-#9RfOyҚqÝZ Y*2OڹQ킐TQiO:z)"D0a&OvDPۅJ*!̘"tA89ce;D;W<Αح` `+"("(݋w;WEBF:"Ap2_Dt3]d>w3c'}r݊bkP߲ KX;ݨ^XPE@PE@Pz_@P/mF+͍0%ScEdJtDNQp;urpRP~欇OWTlo'KX;.zъ"("("c#E@Pn1'zhre+mTEX8u/S#Zݶ=(,REFE@PE@PE n DGx E@P 蝴HO=rVʉVE)"iIԑ%61@/C_<8rs lP4@ڞz-E 6jxi-a}q"("("*Jt*z1E@PE@#9&诗b j%:ngw("("(!DG`8i/E@PE@PEB@qbE@PE@PE ( t"("("WP#>]"("("lو|"("(@@Qv\e]"("("ܥ(q>g H,K ǏKyݸ}ǎ!iH*%Hp_%/"M C G6k­/O<(ԠPT1CY a"ށlJ`d#2{zWG Qk5{jX@`$` c6DŽ:ر Ȕh|L\3P v\dvE@PE@PE@Pb %:b [0o1c=Kߤ Ȗ _ ˖vXʟ?ϟoN-e&smpdcyz(ڬhteʿT5UNN:$HlD /8Htx+!7.3zjܗ>te\:} IS {p'x3a@`3%O dB|mh ďaIYuA¯f4*lY jɛ`_KlqU2OBDo0" g.^ĶCD[5Y+ԡy64ICw֛UE@PE@P@@5.LK*V[!zEtuk3~#:Vlߎ7;9;I:| =2cͨZhzOi}߭2|˗cȜ9fL)Ar'BWDGTS&k{OH˟% >o)Em'Y{7(^<z!$ӭ4%:n%kЊ n%:s՝S~;HPt8g(!alt{ {! 6U"[+"("("S(Sԉ)c7nP^H4iEhLL)EHpW/\E♐\l9A ֡s)(X ЧF扛q%d;fUdfP 7kAyb\GwX?z}(lyֳ*G*NM};{K>M~׍\gg}Ўu{AP]I욿 6e4ArVgn?1㸎Lf2?7IR%n1*_IJ|E'ꍀ p 'd.,V LUn)f1~h,q?P߀=ہG(Ջs훜1u[`|,\@ŇIAGf*Ѽl[Igkv(*^1$O!i9w]ȆIă6~6EG1q;Ȼy&|6Ď~ g~+wp@c0>N@|ҤITy;.îsu (l=ƅsGj:. u'tZ -EtVw:Q7%+oBJ<y|fgYg+w> a" }fxyrmޔ-p9tu 5: v\ػ3E@PE@PE@Pb%:bh_i!+9mQ%:T`R/]qägBGtP܊who (D8+œD]FBt2֪reR%n#''DKRK,ޕV^^5Cׄ[eW+bYe-FqFLn9Sg%ڔ0bS!Gm|Vf /~sΡ+2[JxXphCZl!eB+C $D c6t9 %VO|Ww*u8}Єpa?-2hH)o1ɝ'/B\YPvC~Btw ?ߛ@[BRIR&A=(xl=Ue=6Jw!1xN~a,D^ƟjV9ݿb?&7lҥyɝ hez7ݔ>\6=Hў%%J,Ă8Spv $@D/cF<-·xC~0!8'ۇٯ&9$H`ǟ@nsD/cڣ΃ 9y~=fI4(fDJ?9w'z95mRVǹ^C!?D~̕|KO$[ܖ%'0wRm f#d,Xl vWIQEH=B^,jsj'$HhkW7^;L衇957A!wpu# b3wzi&\ ӎ}7\OՄ@.NK{M?Q}e|Kΐ(ּwH0_C+dgҕȀ<3<("~کH/?=:GEq`#<ʏ'-pǻ5FK!HHFp~#7NO_$¶ q.|c>w^ Vrwҿ! Fċ/$Oή|m8G-֞j9}ZX _9^-ƴV݄`4S>`[=GΕ_GG2B9⸷~${h c(S,(#~tF}c(FwlRŰ{cG|}͞${H?#O :<=a4#yh|`ʲ/K{w!j3HF'͛DC:^X~juq#^Dϗ#^ݷ /%-9HƨFw<'GmKHes]Z"("("*Jt*܁_l%zc8& ڪyh=/X$ (DIK/J1s5|#s_Z~r>wւ$ȘR_̾3${va_p }21PV`tv7ё<}r[i5N^7c[Ȥny{0)HƴYLueijPgw#F:`KcvTF'0J<]`:%pn.q 4zY#2)wHpm8l|é,+ˏ6T!$ѕjV'bk'&<$Hjw"!Hr|J閪ȏ%; :59Iыtyr98/I K}y3?9t^${trfG #Zy2V8hGk/z\H_sqKlyGyC2o-$Hј )3#_hȯ:e~2EU| n^eBreI Ej"sqL6F -U=FBy!r)NmFg8k֭&w=a6d^a˓F~ł%,^PE@PE@PF@F8 _nD{m%b9[ɿl>_,Ct0Zɘ'$TYӦٹӌ9}bX*)f~DeDA~).Ɏ"%2P4Eh=x*^YObDGTsd A&:o0)f&62Z$),yH<:j*:D:sC@HW ^Ԧ&:ZketAurU z+f3:טۻw(1Hv/l}yxĪ}V L_tV_}MsS~'!8?}bTV}ӣ#:}3Eە~KCL4EEbq/zɐtBy !RN&u#og<#fz8;+:=f"DǓ49gI w_^F#DLGhGChm!Hny;PKmc"րZ(BYS^wf2۔,]2;,iX c&β!Y=/eDlrV3/'Eq?YЩz)mEN 6%֨߄PyԎF tLX/6Ou'q\s\;'ƣ;dfO*(xzBL=TCDZtFæ,_vޟ'9>cVY܇QW4 M[, ޴2=[t;}`rW\~]or^Qv$HHDU H K5&$˗a{0S:uh"rtw6 WzGm6MVi Ya|7, lÉcz燯g{tk*, 2p$]kB JtXD("("(w*Jtġ'HTk_Ȏp H#G,{,/$//B,|1}eq\v߳KdbNN䦒}gQ!:F m)U}qX)?{PVs![WF6D۩]s>ƂwG .}_wzZ1>DFvj}x[Y(_$!dǻ_D6#N$_}-kSIE݉@{n@<[ٙ(S@ٜ͌+3#X挐$к:H!ONS͈]WP9nFe7%Z=*"("(qɎx;$B6 . $=K ct>\aș>}D{mKx[HZf=AH(L@q*,2/SX;x-j#:۷ͼ6)6?`䭠Ge#l4$:D? ѫH9^96m&ԛ)YT'3%z@Z54p>4R=+Ô!:If0sx̖svn<}K#=ŽqȦ#RlYRt.nuS[n%:D:%zcqI%It^pJ a7xO$O^Ap ih衆 !2+ؠ:!K]e"ky$2-:֫0Gk5Q n`Zs)Lgjv]줧#" Ak`*(Yj'PCmgWtqn}5I=q>W]R]ɚA1~[u}޼ڹFh,8l&u2q`~Zt R/75&,^I;E)(wKebIL[ίp-*<)^$dBDFqO_v%F]Qh| mz!zZ: !Xsj==–nDF !bEakI8*n+TI }D|Mrg͒ )t!ZTV~hJtXT("("(w*Jtı'{Ce+& T?g}(Ҥ~лa;i}h>"Ԍ"!qޥowbs/O~bA jWxKDI3j]*֢)'>if?70jIW0]>/]GdN;%ܞs2;o2#zcu"EVТ"r2r^H S4_w PtȐ8 |`ykyAz4F[k@ѲNIM*BR0=Uβ&q*[&:h*P[ۘ:Z.Tp& 5/|=w_h%9z#;L3>ʔTLM勐 ter )FKVLL]6{,9q%F*L[?FcM8&JHV\R\XE@PE@PENE@8dH7H}!;rKeΌ֒ylbn2sQW"ݶӺݻ}7M8"6s:v,V7-DG"ke@o$e̢EK#{Kz_S{˖5]WȭrūL~Q] DbMt,b9-bLATTn8v#&l1XygBK5 ߉z)vi[䨘#\[1 r̯c뛭/< 锦:BtN駘/Γ11]SW{-yO]?66lZ3SՔM6Ez9Q[K؂XPl[7b=7͔;m脌2rdMtu ypn쬺8Oz4hS)Nۖ tx;pbV=>rNz,3҅5:fg[CrNo;, Us:$(J6:ϟKL(dSns;M׎vx%D x!QL{Dc~y<jL[2xo3Ue,܃$)^E¤|YqXB,zL1zc@%J.^3)*^/)35LM"ߍBG1m/83 .Guͭ0g#͝ 9hC~,P\oH/?tRًi@pBۏwMe/g86>j3g;aQPڢ"]9NôWڼڤuY{=Zbq-R~ /#d{4P|^֐`T cF.+4K 9gΧV'O'IRwk`HJBs0e/>O:D5Y2S}qI{Fm{=YЩ;ѿL}4]|iP_}6cd#t1VS~_tғ1qKIor,a86=*"("("(ۈGz?ovV;O蠀Sŋ gώZ"jnbH-QtbeB5afncKয়NDˣu))ˎ {b/yiO+k4e_G䞚|{Ԯ۟ILVv6FDtDvSSIVc{]n{0W.3Gnk á5(y"4 y1K?Yo>.'vBӘ@S犩EH։ufcѓ6lT5 (Mg՟ iot")e6oh0F)0ZDEXj{yOp4 j:>g'r+@4nMD+Hf %Z15w^Hd.j%|Fk{l\TZvCrr3Gav5=N-<҇!b?"GC[wWKxr&S~&+vYȔE3">OXz.$ DLDm\ࡪN~pYζQ׃4*=UW.9H]_~$M'NLG1ejPs%,3zƱ4%9G-($82Kc?oRu%: ɄL% 8X"q^SE@PE@PE@Pb %:b {!9~hhO=xeK͑F[99}~ $Ԉއ.r%"֏>j˨`$Lԩ]ݧdr 6{&`DH2gz5ogDgWp'@G,-.\,K; =iRdIV=JZJ?8LV|62Ќ35HI*{*C\9{.wSm vep`4 ߡӻOh l)€pzx,yrvKZSB+|ioF7ёĸz!&qlcdHBv;/яuctMwz/Fhb#SOtȗI![:+QEd7yS,x)"Ü5 ]>q{}Pw]%n}:~{vS!f$#:Fr8qjIpk1jH3KvSݗbu̡6ѱoJ飽[ßSҰ[O=괤ɝ#?xY wYs9'w4_C\weȞ }r$I x`*kޚ2>en$Ee acě>Sx{~zTE@PE@PE "DG\}2A!3>2׺5ʅ/ǐ9sLjBe 'Q%jÚ`8"iX~w,H]mLTL4FDdM96.w7Q"ߝ8l1(ȑ&;SL}lHtK0^I3fݑRv[fIUԬbEbg=},Qs{ځtϾcqGǷ5)Mpu3ᥝ/79~Hn!7Nf?(}( }skdC~QT}$Ocpye. :Q: nઃqnK7- 6(oWFT-V1Mt o?~(׵ضj*#M"F1B(޲l=dnXi8Z{5Da'aC:Ȉxaʨl峡auKچy!qxRcd)=,k0hpzH孚WK<ڷ[h2Z_gQġ?wdg#?'N⋞ z9i5ʯobHMGM1Ng*$RbGE_7nJ#$F(^}OYx;ƥ(wF,djxVd>8=K6wk­1m`iOB3|EH|E,Yr{t Isw`'/˵k2 '$.[#[zQ75X6ͦrs:}h,{v-綟E@PE@PE@(Wmc"'Ν3(u,n\ODvh#0rE|]qʘfP$輏&R䚂'CƢ1m3 Lq*g*׽'6$z$mBѨ2,MZx&ډ'0?/$2yo-#9RfOyҚqÝZ Y*2OڹQ킐TQiO:z)"D0a&OvDPۅJ*!̘"tA89ce;D;W<Αح` `+"("(݋w;WEBF:"Ap2_Dt3]d>w3c'}r݊bkP߲ KX;ݨ^XPE@PE@Pz_@P/mF+͍0%ScEdJtDNQp;urpRP~欇OWTlo'KX;.zъ"("("c#E@Pn1'zhre+mTEX8u/S#Zݶ=(,REFE@PE@PE n DGx E@P 蝴HO=rVʉVE)"iIԑ%61@/C_<8rs lP4@ڞz-E 6jxi-a}q"("("*Jt*z1E@PE@#9&诗b j%:ngw("("(!DG`8i/E@PE@PEB@qbE@PE@PE ( t"("("WP#>]"("("lو|"("(@@Qv\e]"("("ܥ(q>g H,K ǏKyݸ}ǎ!iH*%Hp_%/"M C G6k­/O<(ԠPT1CY a"ށlJ`d#2{zWG Qk5{jX@`$` c6DŽ:ر Ȕh|L\3P v\dvE@PE@PE@Pb %:b [0o1c=Kߤ Ȗ _ ˖vXʟ?ϟoN-e&smpdcyz(ڬhteʿT5UNN:$HlD /8Htx+!7.3zjܗ>te\:} IS {p'x3a@`3%O dB|mh ďaIYuA¯f4*lY jɛ`_KlqU2OBDo0" g.^ĶCD[5Y+ԡy64ICw֛UE@PE@P@@5.LK*V[!zEtuk3~#:Vlߎ7;9;I:| =2cͨZhzOi}߭2|˗cȜ9fL)Ar'BWDGTS&k{OH˟% >o)Em'Y{7(^<z!$ӭ4%:n%kЊ n%:s՝S~;HPt8g(!alt{ {! 6U"[+"("("S(Sԉ)c7nP^H4iEhLL)EHpW/\E♐\l9A ֡s)(X ЧF扛q%d;fUdfP 7kAyb\GwX?z}(lyֳ*G*NM};{K>M~׍\gg}Ўu{AP]I욿 6e4ArVgn?1㸎Lf2?7IR%n1*_IJ|E'ꍀ p 'd.,V LUn)f1~h,q?P߀=ہG(Ջs훜1u[`|,\@ŇIAGf*Ѽl[Igkv(*^1$O!i9w]ȆIă6~6EG1q;Ȼy&|6Ď~ g~+wp@c0>N@|ҤITy;.îsu (l=ƅsGj:. u'tZ -EtVw:Q7%+oBJ<y|fgYg+w> a" }fxyrmޔ-p9tu 5: v\ػ3E@PE@PE@Pb%:bh_i!+9mQ%:T`R/]qägBGtP܊who (D8+œD]FBt2֪reR%n#''DKRK,ޕV^^5Cׄ[eW+bYe-FqFLn9Sg%ڔ0bS!Gm|Vf /~sΡ+2[JxXphCZl!eB+C $D c6t9 %VO|Ww*u8}Єpa?-2hH)o1ɝ'/B\YPvC~Btw ?ߛ@[BRIR&A=(xl=Ue=6Jw!1xN~a,D^ƟjV9ݿb?&7lҥyɝ hez7ݔ>\6=Hў%%J,Ă8Spv $@D/cF<-·xC~0!8'ۇٯ&9$H`ǟ@nsD/cڣ΃ 9y~=fI4(fDJ?9w'z95mRVǹ^C!?D~̕|KO$[ܖ%'0wRm f#d,Xl vWIQEH=B^,jsj'$HhkW7^;L衇957A!wpu# <@IDATb3wzi&\ ӎ}7\OՄ@.NK{M?Q}e|Kΐ(ּwH0_Ce2c!o`cF~o(]׏*زYDji0;ӾErQxO'[iP 73d7CtG"ޭI36+Ͽuwo :*`eޤ@X"@"$\V>oώ(2aj#Mc{ߦ2 @@Ԍ1jǤ ו1ius;]&`'cR=3sz͎{cc=벻""~NQ@gDwfRVsgrV{?d7ga ѓh0~|D |@+JvbZ*;ȋ&7QmaB鋌8sSjtU7-_|Tn8뼰 4VLPpj=DeGiZYiG$ .~VTR>nڷoO|sgsv\ױ08Uݽɬ[}߮ﳞ~=UVUD .gΧ42ݽ|'fo%ytO\wY]zӼz͢fhǘgHS$tͧNyD PP9ʤ*6+q,j1%̘P߻z~~gtLjw#q8 dL !輣5"ϕTgnܸqW{lqݼ R(X<N $"lҎ hMTUHöq_Ek!ֆz[3HYӪ#&c2irQl& cG*-[?p%, &'JLύk-N¼ %<ٷm$`yma4:Ȟ-8 N}NG֪09D1\R[gRŝXvOi‚-_1>{hތ4kel `H]#F /Ө.cUkf ?0|nZ32*Q 0QCcRن(qk$w5oHQg¶#{}q߅܃Utﺓ'^I;65Dː?lt}R/  F@gp(Y{¶L1_*7//ʾV;ob*Sڴ zyڞZmJ$H'DV ]VG XLSU9FEv[c|*?Zl {8Gb[% n|Ζ7\T)o>h@*ңy":J (48pmWQD(.wPA6 с fbZv\F*N7D"iMS|ceYtNȱhJ)؊N&f3 RuM}vMF646љ8aLКKFWNpԡ u|Hv";1bt.=YǢ %C&@bGۊ9:j֚V~cϱto2g:K%@$qlffw #' d'XA0? uqga2]YCs?uch4?Vvs2/DV@dA7.?;nCel x2A] lm{xm_#>?ybj fs#<&sD~)"q8l(+XϞa    DA/ܹ4s߾eWlTF!KQJW3D'܈r*: a0%]~e4:=@O~Rzb9Itb E(͎h1AK6GUUvhY:*ow\ʈ؈( x~@4_H_!ETMl,_:#сGf!"$;euY/ģS?pSlRbM\˄J13h@' lWUV/ЂҳyưV9e:ߎ'ӆEp#R g͉HEƱ8S#M>lWe8bKdFJ5AqE 8$޾V !Hs48݊THkӛe{۷5ؑ3u5 N mWHA5\tvh}Z+Y? 62!dǵ_p&!:ea5@^- O -ȿ u~6e= 魌09"d#jqC;өQ+2Qgʞi/s*Q=Ƒz0^A@A@^V@wv<ڵt֭UEgkg mUi־YYر4 {MtkdG&M`9W &MQ驐 fhB,U]n i2pdZXW3_hQUQ n@Z+}34;noÑ mhlɀHz` mF PEb>ֿu96c~VS]A1}epw1λuo5׃5gc}-uQ'y&D@SИ0۽>vCD2 dpXET'DCs?EDDɘL }{-7iC&f袣9v- \ZcZ3MAo6}dj $]ΥS…tДqB 4Wpl,MxԈ~`BtTd/  /+BtD;Z^⍼e+%cɺ6 _$>,D&0y2d,]Hv`HbE#N=z(){Kt>wc7ꞈ :m, &M=DDzhېm9zdKQe?il~Mb=KPBTRAZC~5jiSOc*!: N([fO-F<ϻ]2Y W3itF}<.<8H&fn#ʞ_1[hb|$SjkbvձrT!uRtT ~96idGly ùZ-۽9:wJ_(a‚-Ľ?k~zk4Yy}Q>kLz#q3 3ъTHM厐qt@Tf>+KFLH]t&K\нHiGisL߻.q$(U!S&,O,f>0^A@A@^Vw,G-Q8FYnɩAjF.f;sN*p/m:vʺO-{v>#ıa[mwÂKX<>rMtZ:`sv!Տ%E8kA}p]m@m|!DVnW`@Gzkn<+NGR)YX~(BGؚ,v]F/qOg l;\]3pֿ0H}4]oYyOmf.{otmPDxcZ_XғE*OgWqfmA@A@A #}[7S[vlN)6eKʲ™3S|.nfRfDjNg\J呍:ZݧN5k 祇U-P>/SFm!:.5'54qcwS}9sTΛl :1CZ|?,gڴT(st13NX벉vtxwRcCqC)2kD/Cӱh#A )칞U"K2p%|[zЕ&MtYn'0BڕhrNn\Vb &Ar(Mc_.0(~iJDqd v,C)`Qib{cvc Z ]>a}Me8Q29 %[x7b́^dG CwW,(DK)`;nd!G [_}耞 9a rMjNLsOې^ 24A P4uB LI=s%eP# <҄_2A*D1]B\&uPA@A@pC@p鞈.9"/;ڣZo0:sr'^=yC":pFtGt6z(R7Wzxj޶m4d{+T 0' :9%c,:g 6w%`lեR%O^_ugJOXXD :A[QT }}6AO'ElgtAaYэ7o\L>bRS$ȃT6z䒓 D~⺊)ȖgEt\e(]?ɞxX*#[^ϥqw^[gn`; hbG*bl >. SӤNγ!:Ȥ~q*wlT}vuUw/X\t{WLT ?k[5ɂ-D39h "vɁu0)21JH7Xf}ε)b#)Nh & '3i:DYOY|¥5m Wc}vOrm z|ta+\= ?(mXYA׺LL3G=| ND}[>H6=C3δ#KYMN E AsPf "{&cKA@:Rg$.VsՔ1EHlf zcO   DT舨wƇu597h@9ӥY[+TeR ]Q}9jØM0\4,s6r$y.q5v/Dgϧw.s:tPQ"{ΞMP.*:CL}j@t/i$ŋm.ۖS.\Xi|MycP7]Cszi/tvbʓ*ӄ"xCfĈph{?{NgGb~ۿtzgo }P }c;M2&<2JcpZqpp6 EMv帷fLHYd=Rx-{']w\kQ_m ڶۮĻA$C(DT\r˩geݔ؊InU7&/G8Ge0Ý{nE:xAʨTSQMlFA>/ .j̩A)򦰇qDma3=0CKd$D{U=- ] koOu^T;v诜o7#9ʱF|N⻎7M4N䪹` l_33  b~t_- RD "CC5s8$ к9bbT4c ~ ގqF^%b\ABt:EVD{8Hm_‡dqܷ:זfn mX9?*j3Ys!ѧkY J˓ӦE9_L]{>"VA89g w&-vo3ݚ6GiL*7ג(³JXֻ~A@A@A " DGD3/غ.p:]uKEn ")sձx.r?E98GĈN "W i.HIT\CPGNGIt%p6ܠ~n'usP9   ? /.w/U)vIۆlSXH :qQ_%Q:t@N1z=F(Q}`+a븰VF    ;BtA@x[ire$ʒnhu/ϡa[n_[bWq"r͂   1#bY W KIK%/Aq qcv9{C$X } S@g{-益RlC^WϞ=}*A@A@A@WWd  #&I=z_l}A#DNj A@A@A;'%  PB.Y   !: A@A@"*BtD;#A@A@OQOA@A@#Z,KA@A@W!:^.͛t3&'E~gu>'OK#Z4J/E)<6~?GI% iHm9 P"] b7DwnEJy_WgѪye~ytQ: J )𴦕yB@DxB\Ctx7QTD5XYo%JzgqW{N_BBWq!GA@A@A@xV}vݻ~$&ˀӸÈL:SDə(?[ z5X iTCt`'ч.]ǥL2w!$L|%,|z]A@A@g 0oae`?ԫGy3f7ҸCLI>,;vzE8Ϝ&уǏ+|%Ŋ\B*Wȓ:V`}:|8 cשCzӸNLt#UzΞM{Nr>Ȗ:5 o8jΝ4`w^+Rܹ>Dt8s:ӊN+{$DG $Ϥ𬉎Eю1;(qjgr /BtD;ؔ9˃_۹l$+~>| >OԲ'Q^yܣc~'f?_#?3x\   <{x1!qmuKB ,cҤTĊ(;S-kHy":>|HGc7ȆwsWk2D7SWH j$~,g"P19cY&M꺐&| A'H4֙)yru9}mF%Htdlf2ɓymJ٦zWgLN\|,DSԇla/]HjЗpv5GUhԅ~%KT%ʔ=4{/j<_>8#]p > C^햋A@A@gԈ0qNԈIXy& 8Dtl=rsvTtV˕3CX?Ȟ=XsК}ԸQ!9kb8n\t@W@n\'Zk˔"P.e۠O *3g`ELӄx;C+"8pUt Ɖ51`5}ruog!RX3@H *`Ok}D _S_y !])|z]A@A@g g8/t"F={h4G/$#BKt@cQ 0^B &c1 uUߧ&ŋ)65kTyD&JÌ  H!:Z4b2iSd1:J#8q_e齂P6ϓԆSt%J-1e(dOVktl1z=7e\5q_V=U-:0E NKn=乒S) b'֞1R)(W\-v4=?H:OW\ bPҜI2oQ z:%:4]}yHr&tQgh:}-:4v?J~ZR9 z[ӽk(uttQOs'<ГOhy`\SgǴkҮ@wOMghzXB43a*9uk9~z |2\"r7MWO9 Ni*q#K?Q㰎d$SnO})MEG`+DB[sB?g &B@+ 4oUn}-#D YQ'ں^=b=&#E۫@4Xq<5v ص lz G<0~'5ol"hSyh2cjXl'/C67%J;g,W Nn|@x$牣TmMֹzDw{h$qlӝ[w˿O%ݒ(zN Xiљc:&waM@.{I"Ӊik>L/:G?aGYJ~O> r#fގ9\E'Z&ƪ<`˞$ZTgER>qj-ٻ=KYe΅iͪ#3uͫ7OCgRbUעF64T^e6 1t-*ԱM~kVV#UU=ԞHʀ +(rŵ84D+C||RKkVsnu fTA4$ )ir#z*E̤)WRZ_]4U|&ds㍁`~`WC>z|'.vrIDU[~dyۜ=ld){_GcoRApi3[м:T4PY(eMO84Dߵӧq L6:ɱјX`znq*[A\&1^?УrRoģ")ixcO)CCtL0,>FA抙(ķ9dq5kLTtqu̍D;n:x1o+ؖMyӠt3"G[oD=HtٯAn9qz.@uԅb$Ao?u֙iU!%ʒr7q!Zhj٬2 嬗 .PJ A:D&&:1v؊P# ]9zEhG6D/<^:Eusֆ&D5@$lL)'D/U#:u g&NZ5OGCmTX~)9\^B&.^mV$.\XDGHwNѣ, Cf ݿ~_ j!EÞI68}p:Dt;ю⧋B߮:ЊV}JMT1D@@"c7 m!]լMe":/!Մ"#mdc0-qtQV=lyQC$Kn"i)UT@&كvxzu,`, NUwo2V߷_irթUѡfc¦ i8Dt)7MLw/ߥ[fx}DS8]V4^z"1fRj'q">]SGwрd3~N2J`MtJomL 3&Tޣ."oaƽ;Np}CB+cHc5:hus%7n[\7oj9@t g1%hSI)`<5zS
!;\ 8+91HI%sZKө0/H Om[>=i^cX,@IDAT'#}'wG4AݻI`ǎM 2$5<]    Y#p7xEz֞-GL 's}ʍ/DΛGXʔ6m(e„#:p?Sc߱Uh`bem恐=9*#bHUZ"XS^2ej~͕u]mܨKBP<tZkՕV: !/QcEۇmmH~&E6eNDVe訿 ɪ0Hѿzj-ӟTUmQv]_hJ;[+BÞQ#VIB,4߹Mx(*U>8OD68#j`ꁄhRJ) \[DUч}>"=v"&ͨF~zBCt`鹖񶊤S'y!HZ䔺/ߧ+j4E5]j,rl1Rz8S>LiknvSiӦц  a9ztf|48Ns7҄ѕuhCj9]~?>qX2KceOֱhCɐ ХrѶbN;fߘs,k[`ND-#hD=vy kŴBȱG 1B%yH#b6=QjxQ+d;ݽ۷KZ=f_p4Gv?G`v;GdƧv'ٳĶw^\'RЯo{Y?Päk4)~z!JTSv^{ǮG"^~" p69a_DtTPiC3+GMj ZDCKt`mMKhB3\wPJ{]=6T{&}"PlǬϩs0HaGsDAs7jqaNH/U-8;R i~jh;mb)89a!;:H!jH#7N"%gftmM!v l] SC =vP!g\~{M\ {0vAZ֊#gO+Lqܱ`NoX| ?Щ;a {C@Ӣz/xm &wC]ͤtپvOerCz+c >L,`"tp"NtjD`Lxx˜JiOq$^!: "A@A@!:"Нo}v-u+pUљlH[U/"szo,,DA#h1v:M)m^61уIXr=IO-Oq*ГOTz*.,K,3tu[eB Ռ1Z`jUTVi =;D! ͎p$@AD 3@2 Rc&?&x:Hev4l_хر/3w]#qiXTWfDPLhܝhL?}{ݛn BcYX_Kkrl:@u /74& v7n ,i=yBlU QO{64Q2&3^c *Ah|P hi~Ρ]d ;z8qDDF|[1M&}:=p uAWŶsp!]=&4G;k)MK/5b   ˊ>ῖx#occXd.M$v с ;LL;ٱ:c-RRDAtȆne*JcϝfcƘa'Nˬ,H=I`k77ѱ26dEwRT_Xϒ(ԱTV_M?6k39wǔA oS#ąi{:>}; hV@LDD":@A;R9ꔪ4!t響kmpVK,մρCE4R-ɒ+t/D(!RiڮQKa, dUȔ>˓?Oo}b!: BA@A@!:"=Q ?s"=Ng'yrjizܧS .=܋tN;O bŢkb!:0WS鏿V˙f-3GL^Cb޼E]mMj9ׂ.]h́DMҥ bƼd`E`V(GxcMtlq įMekw"9Aw<4^,DGQG@#, Ne"R\ :)֪EѢDAS=`O@Xx'ן&FRSJ~E-"A(y.~%2#&ݦ2ݧNG cPz$'k`z⬉;!Ji`9{o ie f?H'םqS:S̖34xuE*ch]uQ7fQԘQG*eVEaw2+rF~m~6 O >Bڪ.qG\4ilƛh!?63dG1s'Jǵ:9U ].cS7~˞H7ql}ֹ~ (nl.\0)D؜DHd&DscIw?iN=<~P_/"\dz4Px_k?dkH *1At[!aup |ǰ[+Sbф@&wl}:?&n dܓ':&N+E /L;R!M[~Dx=i@S]!2:V?cgd_D~*cxѥ 7EƇӻuYA@A@A@ooxnVoVǮ]#耀Smʆ lR,jnp4K鄾T>c6ѱ%;3:kydNi9c~pS4~:yaU ˔Qewo |M5I 5 oTv>zՆ&7#:BNL*˙6-~=ʜ"`ALf3l${']:5&=cXa1й?QXQڔD%M#q04 Cu5FSaeV?̯TDC$gn(8q c*PФډkα; BҞeۃ,iCK\@P=+c4:;O!EQ[@B&:@*|4#*к"ȠE3lzs`۲9Em tl1>|;HmJ6{gU6 g .ut%D@"p v񶽱UXch\9 cؗˬ5 :GRQm25Y+F %Xhޘ(X{VBOC_kiASi9uLgvs:junMX6s/9?FF/ J p/z[2~qai_!:b,sG$el `V[BNe:]{@tAT=SQ1~؂\SڬӜES6ó MhgeA8& d >+iR\Ib/4L4QL+}%,|rz9A@A@A #ܠ~'wˎ6\~Wr搈9x9 8Ը͕އ.^m YŊu>U{B$L #:BNsD5Y] 0}Y i*lXat hD-ӿ#'u UM^91d:4x%@ Y fJO'2Hsu֙XN{4DG퇊ƭz<"C꯬O 64$:l2i⮟bŷ ]=U]]{];"8 שp8;:"oڮVg.c raB;]~r;q6L q́ m}8sYsvC#؈@Dm Gʳ5C a c!QtwS6ŹpiM@X?7_\[i :y:,F,6c1O1JbiVе.̑cgj'_i$QVҦ iϐ 36BĒxVS'Ū , zGC-n*AM~D1c;WGm W2NU9;2R[D'xw/hε[Cm`i ںL=EHiZ#RiQ:uvD|מϵȮeNx@Y6BݹIݛz Ai7& 命u4n& ,f-ǦA@A@A@Q K,WnRH\u,^\Oy8.1E$uE(C/R$}6"8&%͞6DH6x}62z :֨`ZM[MtCE;A++"#n긔0CB:Ϋ@M,eGqxΏ_ i& U>tڂ88wJq@#ď _/&} |E:G@J<&̐ ̂w811GȎ5*ރ{+a}r~A@A@A@xuսr傀 T QG :,Qxx.7᱁Qy~.9.E 7ۉ|%,|.TN,  <Bt wUwҶ!ԅ %RcgBtNAponI.Ne^ѫ6 gl_;JX:.lт   c'#A@3Vf$"DA,bm݋kshDVցūXuܫ\   D AV! }cURǣERKPqBiǘ#swz(Ig@/C߰肿ـhtDE;:6ճgO_8A@A@A@!:n9  xBm"DrĈIFRO[_P{CA@A@A@!:Iz      @D@xSdI      BtxA@A@A@A@A@[7ݻ8n\{aWnݢQD(ky1:}ňNjGQ#Gر)~Xr(A@A@A@A@A@^$xwΣGgh!& ͬV*Ӥst6+rO{/˖ѶGjDQ)sԴD KLzNHT&W.jᇁm!-YBH"-ZP1B8M6ePѬYti0i:ą cNtT@*% ]dZ/$TXt&;]k'/.w>XUo臊*s|^+OZ "[Wj('V]?͗L*;Kt8uJ&,[}0gJ*3DTM,UU_ۈYh\RWm&rj,/h'$G]Uیj݇]=xX,yB@@@@u0v*i6i{wV/o[ &fxLIך )7se6YۦzzCV:ܐ. {H^iU"/9};ѣNŻG"߸0f|-UJ<]8FEkm4oIt     \$:.‡M?B[1e]ӟ'k%Zmn7*+l_Lbv[^6Rvsuciin-HN3l[buߥHtXB#zǟ|uǏaa?<u%,ea-Vab,?TI~]~nYR     p H'l-_/51k&)iUH*n t?kڪ(0eU,XSix=ԟ| KrV2m:eJ}_rJtl9Mu^E!mg5CI谙׿ܥY1I[ Ё6,޾G^\6i(`KݦI kk-     *@#/&ircګIx飿F%;vHza-q>HN1gOI6mXwdn !ցܨQûZ\>;aômGHK /tIwQ%6:ѱO+}<8VkuO=9!^v>u }Dn&= r c: 6ɒT`xD@@@@0>Sʪ75߁yt@xmd%t(vX+jY|=7iS޽\1n,ӊ;uٲڿg%^DG X٫δinʕa n91:ѱ^P J.xXAi#:DG%JJzB45XVE|[b]+@@@@@d$@#'mdczɓ{OnEqE%ekaƍ5Q(kkQT\mé n7-&v&Y4 z[=eK@]=uy 1HD%,eQLZF%h2p[w"rw1*:j/9?fnX+o֬A+:OU+<ƮZ媏 QI_˪]w{&IزֶA>袟E]ݒ-cƠ~U5IN~e_v܋2IM4*#@@@@H$:9"#tHyh)t.#:O"9H5,;zN3f BXآ<5#jsW>L0i~3 '-^}IWx\wߕ~RH!_yElVErJtl6`ZoY FR':N=+Uu抅UXM`LUɓV<"*XEuXnfIߵ+F={lqZI_v^g_S.mG|     @r wN#PWVEFm3U_u_'a7 jr_[_})+V;Ӧݬ%- 氪z 6b"9%:̽{>MX[py'ǤNtuXEUvXDd׊ ĿSuFǪUk]e|et3%C9t[=@?&Q4Ki8UU     $'v;&fw%&&p?͓.t0Yob׺x_̹sPoװy >0zm%4L<&:p& h5ǡ?ڶh&½l 9]%;2Ȧ - MGk|Ikd,nY6*]+jV$?QJ+O.w`Ց KV[Zmc7yH1JDLq˕Brstmmr*%~wtKbl.c5Cg n;,cB6pT (8*UAYO{mNV=rcEFFc dѢ86Lކ#0Q"{vi6K&/?e@@@@@j q>}F[R❥KY:uMg$vS|ܹפID~!q~@CF{7miU]oß[DF}eKgiɧ\kU Kt Uro޼&@@@@@^DG|ħTښ(VlYR脆UbW6_Ò^@Z`-revV`s~y9"V`?Td6 lX냭d^$mTFDȼ5kk>SlCUgX[QLZFMǴZ,)?W Ď o밈UQ\/k\JN6}\bҫn]I6[Ot~1s_c]?c/ob     p- HOw6Rj;.yy9qnזeҥTisTŵS8Nf-[+;6kU*Rݽ:D\jKb:Z      ёxGBJ"矗 Sܗ       x y{KD-ZY@      @HWΊ     @D%@%@@@@@@ iHt$+gE@@@@@K @       4$:ƕ"     % q y @@@@@HIzzYoruIo i^s_':p>q½L_5!    \A$:l[},zJtSGuXپP 5h %g ?ΜqΜY~頇=p오JBk')4y]2vBu萜?ok3f{:eJΩ*zs}>(+T,Y2zGSrf+q[2eP KHnPܧYo%\,۰A%ٸgwjxPEX )*cՏό@@@@@vDǶ#GdwIV5mW|mDUY:ΓDGZM0qRTa䙵%E3dз*;£wKAB     $?d8uLԁV1O[-*&Kjٳ^4v|ǧ$–Ν0%4a7ߞļwCޭUd5%:߆%V댕QT%Ƭjr"ʣ-Hjm#F     $dIk[Q}Pe{Vvs2&iE|.)7|;Ek5 CŽ谄C];Y;eDM伦mi2'Xܡ ?,w 77O-u*2ji\[en\&:>=Vyug[O@&:-jF]p-IDAT7W6>†7פ}/n3#     H5YppGwas8~dg8[CDxl}DU1_Jq='~DTM7[ov49ތ MtO*iuǴ=$uT2aR2[ pˁ[fr녳%:,aYAڟ-ڶMe;=X@@@@@Dd87ěN(z^X%F3mTp޷M)!Fbv>TL 9Z`+*Ξc;qa$:\)N>M^_˦uܓ}-Kt؆pgad&p,"{r$I.Vc&jhm!^Xŋ{c{     pE~KRDΨZ9=RPD I헂.M,~=9UܦMr݅$:Vo.FtnCgcvWΪ&k,T(y-KHǸqb KtX#T#g¨9$~7n It~D[ n1̾VY/-/a_0;"    $@Jtx 紪cr_i "B_t,WNʝ[Ϟ-,qˑzs݆'$.$aܨQHSB"܊wΝᣏlQ//VuˁMt2jX {O%ͲhWX P'9e yg4]׫O>DǷ:d&9{"VXw"E\#     /,~V2B+Dͯ6|dѻ)rLwhUMC=R>^,'KCxR[%Ŵ=WkMn4Ix{     p $DGj;l,ôc޼o|1o.Z$>^=iU7z:#p[.%*rdq%:c'?d@oRQWP0GɡG Z1Ilh>MnId&,5i4`ۚ$Mؤ]%Mza1gO%F>meddGJ&y- ⫯Ux{!*Z_eaZm%Kꍇ rm?@@@@@ \5Gq%5I`qR2[_Tw$6p:h7-i_$ݤ/ѱ@ c.0ѱۃ=ALy谄I]MXV:$}jnXw`$UIңNsBeXDG|m?|\xb-&`DG%gLz^ɫI_d/V!    $W2a *ӧCWveyˡCZV5o.JoͽȔ>L~XG'ORjeƬ޽-hիeVNqi+^d[om(xXitce[ާu]+#KVR^+eVMri‹v 'Mj     pu \\^\Zj{J WzdnmuQ9sErd膅goڟQzjkו/[6!]+@@@@@.U蘠|7uDܼ4     \L>a:6poҹrIԩ/B@@@@@(p':.-/     I,@#9=     $      $:#     @ H:[Ό     I,@#9=     $ 8u7$Ǎ7Jip4"    \W\ceoyLz)){M2hLvm>#a>}{x`:8cFXwzQydI3qk;1eK. M.m֦LB=G#FÇݲ/n˞1ɒE.\XyqUow=Zyy#7+W2UUg]Pv:uӽŊIeX<1      8u0@dN$&,2wj~$0ߔ)a׽V-~ᣏdΝǼߢQ`?Μ})}bVޒ&U*;ȁGMAg.o5j$yf޾i^i5t{^{䅚5%dmC~i8^ ;&W쿮pާ4ViDt}~Ki[w}lj6@@@@@VgMsե]2ny ߸gOc r攇Kݪ/P%[捱I[ozDGrcE5ZDDs[>MinOVM>, ̗֕OiC MX\DNM`4H`Rˮ5j8uJ&hM`ѤҎj{ߙ3d]s٢E:/ёNF>@OnԪ;.iBrVpd ~Im $:<1@@@@@ \1sڶĠAnʥ7ַv"颪#]݈o[TP= [OteT|Ȣ7BKH?^ 谖M{&MX' xiSH|1i믡fټZYb")%p,c1^=P%:P6~(wޮ]_ɒC(@@@@@Iئ3 Fhۨk.ڂ(\gp4xR%GuR%:ZP=yRi8ummugB֭谝͙#/v\'-_DGQdնm3S&W1$römy_$uW{ 77Kƍ$WYRMNaN     WEOtіLSpMpڴIJZA1v86L_g_oyɜ.]_DGqmð*ěML$ess/"?%$1ɓ{||yX[D'N~f< Cs7}!iF+.$Oth%K7tkU᫔jۯۢK~Pii=2     pm \DH5 =:Viٛ7K?v\9ԮhR`2E  u%:yaXQgILMz'عRb#Z>^н%9,aqUĉ\կ/K'u0*mDn[\94 yZkQweVXc3M¤J2aRZSs/ê;h£_I!     p\P㔶nCzcޖ-rGD[ Y5G}[b/V$z|ѽsg)c;Il#4F%iu?|z9 ^?.oi59\աHhc&lFMB@[`K/ Zw yqEbq϶`vH6)]A7emxBA(-s-Cd8     \JtzcVo^DE[n\EoVԛ]n5kw~)ÞyF|LZ< !;vA3gYa7VU/TSQ )vQ 谙!~۽mˠV'NZVw)v:4i,Єٲ(bVx "j/[WI[G%\#     %DϚu etUn4vlzZ`з 4qP\^Mt4~8[W͝۽6ۆ|{Bo[m>Usxi\H7K~^ʒ6HmMؼ5NӦn}ItX몸Y2d6l4[lH?;M_i"XGVJ"1f,`     )D Z9ЯJ)/_ᆃ]n?s3"tDDG:u&eDyFuq1wgڒ+DGT1ږ+UpayB++M?.$δi2e wmȭyFv7bʠ&"1kemgcVWJcOTFzNsIM tQ{y]-7v,     \] Nt::r&W!qX%F3TpΎ·Vo̗ʐpR':-^,Yrju«O>.5DGBMtXUKq4b/~չ ^R/錍GKäIU YUQ     U-pQ͋y *:ӢwE㵅RHh$6QYBBx/: )[WA PaI Oۈ50f n*!֬FDm }4~It"TXH &NRQgR0Gɡ]:$jcbxr]y:VoLӻ@@@@@HDrJ%F*7mZߩL&:l /jv}f9)Ϙ651n|̙]!~It;Oϟ rq`.wdC;eսyڏ     צ@':Y/;NV^]<.#fnQC[uY3i"W>'`-֨!GU۶![w7߸QzTQ v5Yh2'ض4<8ݯ^/ֹcZ"%Tt1B6\2Mf]jX;Eg_v&YgGWjYH7L:>,N      J&:lpyw;re [%QTB]SgnrQ93[rd(Vm[D@@@@.\&:&hA UY| @@@@@YKYuE\$},`_ (example: ``GAIA/GAIA3.Grp/AB``) - The FPS service returns a full calibration instance, which is then split into a filter object and a calibration object that refers to that filter. - Each one of these components has its own ``dmid`` generated by the API. ``dmid`` can be used to reference them. Example for filter ``GAIA/GAIA3.Grp/AB``: - photometric calibration identifer: ``dmid="_photcal_GAIA_GAIA3_Grvs_AB"`` - filter identifier ``dmid="_filter_GAIA_GAIA3_Grvs_AB"`` - **Coordinate systems**: Can be given either by ``dmid`` or by parameters (see `pyvo.mivot.writer.InstancesFromModels.add_simple_space_frame` and `pyvo.mivot.writer.InstancesFromModels.add_simple_time_frame`). - by ``dmid``: Identifiers are generated by the API when a frame is added in the GLOBALS. (example ``{"dmid": "_spaceframe_spaceRefFrame_equinox_refPosition"}``) - by parameters: The mapping parameters are given as dictionaries as for the mapping (see below) (example ``{"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER'}``) - ``Mapping``: Mapping of the table data to the property attributes. The fine structure of these dictionaries is specific to each mapped class, but all follow the same pattern. Values specified as strings are considered to be column identifiers, unless the string starts with a '*'. In this case, the stripped string is taken as the literal value. Other value types (numeric or boolean) are all considered as literals. +-------------+---------------------------------+ | **value** | **attribute value** | +=============+=================================+ | "\*M31" | "M31" | +-------------+---------------------------------+ | "ObjName" | value of the column "ObjName" | +-------------+---------------------------------+ | True | True | +-------------+---------------------------------+ | 123456 | 123456 | +-------------+---------------------------------+ | 45.8987 | 45.8987 | +-------------+---------------------------------+ - ``semantics``: Semantic tags (text + vocabulary entry) that apply to the property. +-------------+---------------------------------------------------------+ + **key** | **value** + +=============+=========================================================+ + description + free text description of the property + +-------------+---------------------------------------------------------+ + uri + URI of the vocabulary term to which the property refers + +-------------+---------------------------------------------------------+ + label + vocabulary term to which the property refers + +-------------+---------------------------------------------------------+ All ``semantics`` fields are considered literal values. Add Query origin ---------------- Add the Mango ``QueryOrigin`` instance to the current ``MangoObject``. .. figure:: _images/mangoDataOrigin.png :width: 500 DataOrigin package of Mango. ``QueryOrigin`` is the object grouping together all the components needed to model the origin of the MangoObject. It is always identified with ``dmid="_origin"`` .. code-block:: python builder.add_query_origin(mapping) The detail of the ``mapping`` parameter is given in the `pyvo.mivot.writer.InstancesFromModels.add_query_origin` documentation The ``QueryOrigin`` object can be automatically built from the INFO tags of the VOtable. The success of this operation depends on the way INFO tags are populated. The method below, analyzes the INFO tags and insert the resulting query origin into the annotations. .. code-block:: python builder.extract_data_origin() - This code has been optimized to work with Vizier output. - INFO tags of the VOTable header are analysed to set the ``QueryOrigin`` attributes. - INFO tags of the header of the first resource are analyzed to set the ``DataOrigin`` objects. - The automatic mapping does not work for VOtable joining several tables yet. Add Properties -------------- The main purpose of the ``MangoObject`` is to collect various properties contained in the data row, although synthetic properties (literal value only) can be added as well. - The properties are stored in a container named ``propertyDock``. - During he annotation process, properties are added one by one by specific methods. .. figure:: _images/mangoProperties.png :width: 500 Properties supported by Mango. Add EpochPosition ^^^^^^^^^^^^^^^^^ The ``EpochPosition`` property describes the astrometry of a moving source. .. figure:: _images/mangoEpochPosition.png :width: 500 EpochPosition components of Mango. It handles six parameters (position, proper motion, parallax and radial velocity) valid at a given epoch (``obsDate`` field) with their correlations and errors and the coordinate system for both space and time axis. .. code-block:: python builder.add_epoch_position(frames, mapping, semantics) The detail of the parameters is given with the description of the :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position` method. The first parameter (``frames``) specifies both space and time frames. It can either contain the frame dmid-s if they are already installed, for instance from TIMESYS and COOSYS tags, or the mapping elements needed to install them: Both frames can be automatically extracted from the COOSYS and TIMSYS VOTable elements. .. code-block:: python frame_mapping = builder.extract_frames() builder.add_epoch_position(frame_mapping, mapping, semantics) This automatic metedata extraction can be extended to any method parameters. - COOSYS -> coords:SpaceSys - TIMESYS -> coords:TimeSys - FIELD -> mango:EpochPosition .. code-block:: python epoch_position_mapping = builder.extract_epoch_position_parameters() builder.add_mango_epoch_position(**epoch_position_mapping) However, it is advisable to take a look at the automatic mapping, as its content is highly dependent on the VO table metadata, and the risk of incompleteness, mismatches or confusion cannot be ruled out. Add Brightness ^^^^^^^^^^^^^^ The ``Brightness`` binds a flux or a magnitude with an error and a photometric calibration. .. code-block:: python builder.add_mango_brightness(photcal_id, mapping, semantics) The detail of the parameters is given with the :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_brightness` docstring. Add Color ^^^^^^^^^ The ``Color`` binds to a Color index or an hardness ratio value with an error and two photometric filters. .. code-block:: python builder.add_mango_color(filter_ids, mapping, semantics) The detail of the parameters is given with the :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_color` docstring. Reference/API ============= .. automodapi:: pyvo.mivot.writer .. automodapi:: pyvo.mivot.utils .. automodapi:: pyvo.mivot.glossary astropy-pyvo-b70558c/docs/mivot/annoter_tips.rst000066400000000000000000000121241510533647000220760ustar00rootroot00000000000000*************************************************** MIVOT (``pyvo.mivot``): How to annotate data - Tips *************************************************** The annotation process is intended to be performed at the server level. How it is implemented depends on the related DAL protocol, the framework used, and the available metadata. This process likely occurs before the data table is streamed out because the Mivot block must precede the TABLE block. This means it cannot use the table FIELDs, but rather some internal representation. However, the examples below use the FIELDs to demonstrate how an annotation task could work. Map a magnitude to a Mango Brightness property ============================================== Assuming that our dataset has the two following fields, let's map the magnitude in the J band to the ``mango:Brightness`` class. .. code-block:: xml ?(jmag) 2MASS J-band magnitude ?(ejmag) Error on Jmag The MANGO brightness class packs together 3 components: the magnitude, its error and the photometric calibration. Mivot serializations of the photometric calibrations are given by the SVO `Filter Profile Service `_. The first thing to do is to get the FPS identifier of the searched filter (2MASS J in our case). Once the filter is selected, the identifier of the calibration in the desired system can by copied from the `FPS `_ page as shown below. .. image:: _images/filterProfileService.png :width: 500 :alt: FPS screen shot. Now, we can build the mapping parameters and apply them to add the mapping of that property. .. code-block:: python votable = parse("SOME/VOTABLE/PATH") builder = InstancesFromModels(votable, dmid="URAT1") # Add the mapping of a brightness property builder.add_mango_brightness( photcal_id="2MASS/2MASS.J/Vega", mapping={"value": "Jmag", "error": { "class": "PErrorSym1D", "sigma": "e_Jmag"} }, semantics={"description": "magnitude J", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude", "label": "magnitude"}) # Once all all properties have been mapped, we can # tell the builder to complete the mapping block builder.pack_into_votable() The mapping parameters can be interpreted that way: - The photometric calibration match the ``2MASS/2MASS.J/Vega`` FPS output - The magnitude is given by the FIELD identified by ``Jmag`` - The magnitude error, which is symmetrical, is given by the FIELD identified by ``e_Jmag`` - The optional semantics block of the property (see the MANGO specification) indicates that the property is a magnitude. Map table data to a Mango EpochPosition property ================================================ The mapping of any property follow the same schema but with specific mapping parameters. As it turns out, the EpochPosition can be very complex, with six parameters, their errors and their correlations. If the VOTable fields are available during the annotation process, the API can extract a template of the mapping parameters. .. code-block:: python scs_srv = SCSService(" https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main") query_result = scs_srv.search( pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'), radius=0.5) builder = InstancesFromModels(query_result.votable, dmid="URAT1") # Get a mapping proposal based on the FIELD UCDs parameters = builder.extract_epoch_position_parameters() DictUtils.print_pretty_json(parameters) The JSON below shows the detected mapping parameters as a dictionary whose structure matches that expected by the API. .. code-block:: json { "frames": { "spaceSys": { "dmid": "_spaceframe_ICRS_BARYCENTER" }, "timeSys": {} }, "mapping": { "longitude": "t1_c8", "latitude": "t1_c9", "parallax": "t1_c11", "pmLongitude": "t1_c12", "pmLatitude": "t1_c13", "errors": { "properMotion": { "class": "PErrorSym2D", "sigma1": "e_pmRA", "sigma2": "e_pmDE" } }, "correlations": {} }, "semantics": { "description": "6 parameters position", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location", "label": "Astronomical location" } } This template can be updated manually or by any other means, and then used to adjust the "EpochPosition" mapping. .. code-block:: python # Add the EpochPosition to the annotations with the modified mapping parameters builder.add_mango_epoch_position(**parameters) builder.pack_into_votable() astropy-pyvo-b70558c/docs/mivot/example.rst000066400000000000000000000245131510533647000210310ustar00rootroot00000000000000.. _mivot-examples: ************************************************************ MIVOT (``pyvo.mivot``): How to use annotated data - Examples ************************************************************ Photometric Properties Readout ============================== This example is based on VOTables provided by the ``XTapDB`` service. This service exposes the slim `4XMM dr14 catalogue `_. It is able to map query responses on the fly to the MANGO data model. The annotation process only annotates the columns that are selected by the query. The following properties are supported: - ``mango:Brightness`` to which fluxes are mapped - ``mango:Color`` to which hardness ratio are mapped - ``mango:EpochPosition`` to which positions and first observation dates are mapped - ``mango:Status`` to which quality flags of the source detections are mapped A specific response format (``application/x-votable+xml;content=mivot``) must be set in order to tell the server to annotate the queried data. (*Please read the comment inside the code snippet carefully to fully understand the process*) .. doctest-skip:: >>> from pyvo.utils import activate_features >>> from pyvo.dal import TAPService >>> from pyvo.mivot.utils.xml_utils import XmlUtils >>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer >>> >>> # Enable MIVOT-specific features in the pyvo library >>> activate_features("MIVOT") >>> >>> service = TAPService('https://xcatdb.unistra.fr/xtapdb') >>> result = service.run_sync( ... """ ... SELECT TOP 5 * FROM "public".mergedentry ... """, ... format="application/x-votable+xml;content=mivot" ... ) >>> >>> # The MIVOT viewer generates the model view of the data >>> m_viewer = MivotViewer(result, resolve_ref=True) >>> >>> # Print out the Mivot annotations read out of the VOtable >>> # This statement is just for a pedagogic purpose (access to a private attribute) >>> XmlUtils.pretty_print(m_viewer._mapping_block) In this first step we just queried the service and we built the object that will process the Mivot annotations. The Mivot block printing output is too long to be listed here. However, the screenshot below shows its shallow structure. .. image:: _images/xtapdbXML.png :width: 500 :alt: Shallow structure of the annotation block. - The GLOBALS section contains all the coordinate systems (in a wide sense). This includes the allowed values for the detection flags and the photometric calibrations. - The TEMPLATES section contains the objects to which table data is mapped. In this example, there is one ``MangoObject`` instance which holds all the mapped properties. At instantiation time, the viewer reads the first data row, which must exist, in order to construct the Python objects that reflect the mapped models and to make the data available through them. .. doctest-skip:: >>> # Discover the Python objects matching the TEMPLATES content >>> for dm_instance in m_viewer.dm_instances; >>> print(dm_instance) The annotations are consumed by this dynamic Python object which leaves are set with the data of the current row. You can explore the structure of this object by using standard object paths as shown below. Now, we can iterate through the table data and retrieve an updated Mivot instance for each row. .. doctest-skip:: >>> mango_object = m_viewer.dm_instances[0] >>> while m_viewer.next_row_view(): >>> if mango_object.dmtype == "mango:MangoObject": >>> print(f"Read source {mango_object.identifier.value} {mango_object.dmtype}") >>> for mango_property in mango_object.propertyDock: >>> if mango_property.dmtype == "mango:Brightness": >>> if mango_property.value.value: >>> mag_value = mango_property.value.value >>> mag_error = mango_property.error.sigma.value >>> phot_cal = mango_property.photCal >>> spectral_location = phot_cal.photometryFilter.spectralLocation >>> mag_filter = phot_cal.identifier.value >>> spectral_location = phot_cal.photometryFilter.spectralLocation >>> mag_wl = spectral_location.value.value >>> sunit = spectral_location.unitexpression.value >>> print(f" flux at {mag_wl} {sunit} (filter {mag_filter}) is {mag_value:.2e} +/- {mag_error:.2e}") Read source 4XMM J054329.3-682106 mango:MangoObject flux at 0.35 keV (filter XMM/EPIC/EB1) is 8.35e-14 +/- 3.15e-14 flux at 0.75 keV (filter XMM/EPIC/EB2) is 3.26e-15 +/- 5.45e-15 flux at 6.1 keV (filter XMM/EPIC/EB8) is 8.68e-14 +/- 6.64e-14 ... ... The same code can easily be connected with matplotlib to plot SEDs as shown below (code not provided). .. image:: _images/xtapdbSED.png :width: 500 :alt: XMM SED It is to be noted that the current table row keeps available through the Mivot viewer. .. code-block:: python row = m_viewer.table_row .. important:: The code shown in this example can be used with any VOTable that has data mapped to MANGO. It contains no features specific to the XtatDB output. This is exactly the purpose of the MIVOT/MANGO abstraction layer: to allow the same processing to be applied to any annotated VOTable. The same client code can be reused in many places with many datasets, provided they are annotated. EpochPosition Property Readout ============================== This example is based on a VOtable resulting on a Vizier cone search. This service maps the data to the ``EpochPosition`` MANGO property, which models a full source's astrometry at a given date. .. warning:: At the time of writing (Q1 2025), Vizier only mapped positions and proper motions (when available), and the definitive epoch class had not been adopted. Therefore, this implementation may differ a little bit from the standard model. Vizier does not wrap the source properties in a MANGO object, but rather lists them in the Mivot *TEMPLATES*. The annotation reader supports both designs. In the first step below, we run a standard cone search query by using the standard PyVO API. Once the query is finished, we can get a reference to the object that will process the Mivot annotations. .. doctest-skip:: >>> import astropy.units as u >>> from astropy.coordinates import SkyCoord >>> from pyvo.dal.scs import SCSService >>> from pyvo.utils import activate_features >>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer >>> from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder >>> >>> # Enable MIVOT-specific features in the pyvo library >>> activate_features("MIVOT") >>> >>> scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main") >>> >>> query_result = scs_srv.search( ... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame='icrs'), ... radius=0.5) >>> >>> # The MIVOT viewer generates the model view of the data >>> m_viewer = MivotViewer(query_result, resolve_ref=True) We can now discover which data model classes the data is mapped to. .. doctest-skip:: >>> # Get a set of Python objects matching the TEMPLATES content and >>> # which leaves are set with the values of the first row >>> for dm_instance in m_viewer.dm_instances; >>> print(dm_instance) The first instance can be accessed by the ``m_viewer.dm_instance`` getter. This is a simple shorcut aiming at simplifying the code. .. doctest-skip:: >>> dm_instance = m_viewer.dm_instance >>> print(dm_instance.dmtype) mango:EpochPosition We can also provide a complete instance representation that includes all fields in the entire hierarchy. .. doctest-skip:: >>> # Print out the json serialization of the Python object >>> print(repr(dm_instance)) { "dmtype": "mango:EpochPosition", "longitude": { "dmtype": "ivoa:RealQuantity", "value": 51.64272638, "unit": "deg" }, "latitude": { "dmtype": "ivoa:RealQuantity", "value": 60.28156089, "unit": "deg" }, "pmLongitude": { "dmtype": "ivoa:RealQuantity", "value": 13.31, "unit": "mas/yr" }, "pmLatitude": { "dmtype": "ivoa:RealQuantity", "value": -23.43, "unit": "mas/yr" }, "epoch": { "dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "yr" }, "parallax": { "dmtype": "ivoa:RealQuantity", "value": 5.12, "unit": "mas" }, "spaceSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "mango:EpochPosition.spaceSys", "frame": { "dmrole": "coords:PhysicalCoordSys.frame", "dmtype": "coords:SpaceFrame", "spaceRefFrame": { "dmtype": "ivoa:string", "value": "ICRS" } } } } The reader can transform ``EpochPosition`` instances into ``SkyCoord`` instances. These can then be used for further scientific processing. .. doctest-skip:: >>> while m_viewer.next_row_view(): >>> mango_property = m_viewer.dm_instance >>> if mango_property.dmtype == "mango:EpochPosition": >>> scb = SkyCoordBuilder(mango_property) >>> # do whatever process with the SkyCoord object >>> print(scb.build_sky_coord()) .. important:: Similar to the previous example, this code can be used with any VOTable with data mapped to MANGO. It contains no features specific to the Vizier output. It avoids the need for users to build SkyCoord objects by hand from VOTable fields, which is never an easy task. Homework ======== Simbad has released (Q3 2025) an annotated version of its Cone Search. It's a good case to exercise this API. .. code-block:: python SERVER = "https://simbad.cds.unistra.fr/cone?" VERB = 2 RA = 269.452076* u.degree DEC = 4.6933649* u.degree SR = 0.1* u.degree MAXREC = 100 RESPONSEFORMAT = "mivot" scs_srv = SCSService(SERVER) query_result = scs_srv.search( pos=SkyCoord(ra=RA, dec=DEC, frame='icrs'), radius=SR, verbosity=VERB, RESPONSEFORMAT=RESPONSEFORMAT, MAXREC=MAXREC) *The next section provides some tips to use the API documented in the* :ref:`annoter page `. astropy-pyvo-b70558c/docs/mivot/index.rst000066400000000000000000000054441510533647000205070ustar00rootroot00000000000000********************** MIVOT (``pyvo.mivot``) ********************** This module contains the new feature of handling model annotations in VOTable. Astropy version >= 6.0 is required. Introduction ============ .. pull-quote:: Model Instances in VOTables (MIVOT) defines a syntax to map VOTable data to any model serialized in VO-DML. The annotation operates as a bridge between the data and the model. It associates the column/param metadata from the VOTable to the data model elements (class, attributes, types, etc.) [...]. The data model elements are grouped in an independent annotation block complying with the MIVOT XML syntax. This annotation block is added as an extra resource element at the top of the VOTable result resource. The MIVOT syntax allows to describe a data structure as a hierarchy of classes. It is also able to represent relations and composition between them. It can also build up data model objects by aggregating instances from different tables of the VOTable. - Model Instances in VOTables is a VO `standard `_ - Requires Astropy>=6.0 - ``pyvo.mivot`` is a prototype feature which must be activated with ``activate_features("MIVOT")`` Implementation Scope ==================== This implementation is totally model-agnostic. - It does not operate any validation against specific data models. - It just requires the annotation syntax being compliant with the standards. However, many data samples used for the test suite and provided as examples are based on the ``EpochPropagation`` class of the ``Mango`` data model that is still a draft. This class collects all the parameters we need to compute the epoch propagation of moving sky objects. Some of the examples have been provided by a special end-point of the Vizier cone-search service (https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/) that maps query results to this model. .. image:: _images/mangoEpochPosition.png :width: 500 :alt: EpochPropagation class used to validate this api. It is to be noted that the Vizier service does not annotate errors at the time of writing (Q1 2024) The implementation uses the Astropy read/write annotation module (6.0+), which allows to get (and set) Mivot blocks from/into VOTables as an XML element serialized as a string. .. pull-quote:: Not all MIVOT features are supported by this implementation, which mainly focuses on the epoch propagation use case: - ``JOIN`` features are not supported. - ``TEMPLATES`` with more than one ``INSTANCE`` not supported. Using the MIVOT package ======================= The ``pyvo.mivot`` module can be used to either read or build annotations. .. toctree:: :maxdepth: 2 viewer annoter writer example annoter_tips astropy-pyvo-b70558c/docs/mivot/viewer.rst000066400000000000000000000223211510533647000206720ustar00rootroot00000000000000****************************************************** MIVOT (``pyvo.mivot``): Annotation Viewer - Public API ****************************************************** Introduction ============ .. pull-quote:: Model Instances in VOTables (MIVOT) defines a syntax to map VOTable data to any model serialized in VO-DML. The annotation operates as a bridge between the data and the model. It associates the column/param metadata from the VOTable to the data model elements (class, attributes, types, etc.) [...]. The data model elements are grouped in an independent annotation block complying with the MIVOT XML syntax. This annotation block is added as an extra resource element at the top of the VOTable result resource. The MIVOT syntax allows to describe a data structure as a hierarchy of classes. It is also able to represent relations and composition between them. It can also build up data model objects by aggregating instances from different tables of the VOTable (get more in :doc:`index`). Using the API ============= .. attention:: The module based on XPath queries and allowing to browse the XML annotations (``viewer.XmlViewer``) has been removed from version 1.8 Integrated Readout ------------------ The ``ModelViewer`` module manages access to data mapped to a model through dynamically generated objects (``MivotInstance`` class). ``MivotInstance`` is a generic class that does not refer to any specific model. The mapped class of a particular instance is stored in its ``dmtype`` attribute. These objects can be used as standard Python objects, with their fields representing elements of the model instances. The first step is to instanciate a viewer that will provide the API for browsing annotations. The viewer can be built from a VOTable file path, a parsed VOtable (``VOTableFile`` object), or a ``DALResults`` instance. .. attention:: The code below only works with ``astropy 6+`` .. doctest-skip:: >>> import astropy.units as u >>> from astropy.coordinates import SkyCoord >>> from pyvo.dal.scs import SCSService >>> from pyvo.utils.prototype import activate_features >>> from pyvo.mivot.viewer.mivot_viewer import MivotViewer >>> >>> scs_srv = SCSService("https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main") >>> m_viewer = MivotViewer( ... scs_srv.search( ... pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, ... frame='icrs'), ... radius=0.1 ... ), ... resolve_ref=True ... ) In this example, the query result is mapped to the ``mango:EpochPosition`` class, but users do not need to know this in advance, since the API provides tools to discover the mapped models. .. doctest-skip:: >>> if m_viewer.get_models().get("mango"): >>> print("data is mapped to the MANGO data model") data is mapped to the MANGO data model We can also check which datamodel classes the data is mapped to. .. doctest-skip:: >>> mivot_instances = m_viewer.dm_instances >>> print(f"data is mapped to {len(mivot_instances)} model class(es)") data is mapped to 1 model class(es) .. doctest-skip:: >>> mivot_instance = m_viewer.dm_instances[0] >>> print(f"data is mapped to the {mivot_instance.dmtype} class") data is mapped to the mango:EpochPosition class At this point, we know that the data has been mapped to the ``MANGO`` model, and that the data rows can be interpreted as instances of the ``mango:EpochPosition``. .. doctest-skip:: >>> print(mivot_instance.spaceSys.frame.spaceRefFrame.value) ICRS .. doctest-skip:: >>> while m_viewer.next_row_view(): >>> print(f"position: {mivot_instance.latitude.value} {mivot_instance.longitude.value}") position: 59.94033461 52.26722684 .... .. important:: Coordinate systems are usually mapped in the GLOBALS MIVOT block. This allows them to be referenced from any other MIVOT element. The viewer resolves such references when the constructor flag ``resolve_ref`` is set to ``True``. In this case the coordinate system instances are copied into their host elements. The code below shows how to access GLOBALS instances (one in this example) independently of the mapped data. .. doctest-skip:: >>> for globals_instance in m_viewer.dm_globals_instances: >>> print(globals_instance) We can also provide a complete instance representation that includes all fields in the entire hierarchy. .. doctest-skip:: >>> print(repr(globals_instance)) { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "frame": { "dmrole": "coords:PhysicalCoordSys.frame", "dmtype": "coords:SpaceFrame", "spaceRefFrame": { "dmtype": "ivoa:string", "value": "ICRS" } } } As you can see from the previous examples, model leaves (class attributes) are complex types. This is because they contain additional metadata as well as values: - ``value`` : attribute value - ``dmtype`` : attribute type such as defined in the Mivot annotations - ``unit`` (if any) : attribute unit such as defined in the Mivot annotations Per-Row Readout --------------- This annotation schema can also be applied to table rows read using the `astropy.io.votable` API outside of the ``MivotViewer`` context. .. doctest-skip:: >>> votable = parse(path_to_votable) >>> table = votable.resources[0].tables[0] >>> # init the viewer on the first resource of the votable (default) >>> mivot_viewer = MivotViewer(votable) >>> mivot_object = mivot_viewer.dm_instance >>> # and feed it with the numpy table row >>> for rec in table.array: >>> # apply the mapping to current row >>> mivot_object.update(rec) >>> # show that the model retrieve the correct values >>> # ... or do whatever you want >>> assert rec["RAICRS"] == mivot_object.longitude.value >>> assert rec["DEICRS"] == mivot_object.latitude.value Mivot/Mango as a Direct Gateway from Data to Astropy SkyCoord ------------------------------------------------------------- A simple way to get the most out of annotations is to use them to directly create Astropy objects, without having to parse the metadata, whether it comes from the annotation or the VOTable. .. doctest-skip:: >>> from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder >>> >>> m_viewer.rewind() >>> while m_viewer.next_row_view(): >>> sky_coord_builder = SkyCoordBuilder(mivot_instance) >>> sky_coord = sky_coord_builder.build_sky_coord() >>> print(sky_coord) In the above example, we assume that the mapped model can be used as a ``SkyCoord`` precursor. If this is not the case, an error is raised. .. important:: In the current implementation, the only functioning gateway connects ``Mango:EpochPosition`` objects with the ``SkyCoord`` class. The ultimate objective is to generalize this mechanism to any property modeled by Mango, and eventually to other IVOA models, thereby realizing the full potential of a comprehensive and interoperable mapping framework. Class Generation in a Nutshell ------------------------------ MIVOT reconstructs model structures with 3 elements: - ``INSTANCE`` for the objects - ``ATTRIBUTE`` for the attributes - ``COLLECTION`` for the elements with a cardinality greater than 1 The role played by each of these elements in the model hierarchy is defined by its ``@dmrole`` XML attribute. Types of both ``INSTANCE`` and ``ATTRIBUTE`` are defined by their ``@dmtype`` XML attributes. ``MivotInstance`` classes are built by following MIVOT annotation structure: - ``INSTANCE`` are represented by Python classes - ``ATTRIBUTE`` are represented by Python class fields - ``COLLECTION`` are represented by Python lists ([]) ``@dmrole`` and ``@dmtype`` cannot be used as Python keywords as such, because they are built from VO-DML identifiers, which have the following structure: ``model:a.b``. - Only the last part of the path is kept for attribute names. - For class names, forbidden characters (``:`` or ``.``) are replaced with ``_``. - Original ``@dmtype`` are kept as attributes of generated Python objects. - If the end-user is unaware of the class mapped by the actual ``MivotInstance``, if can can explore it by using its class dictionary ``MivotInstance.__dict__`` (see the Python `data model `_). .. doctest-skip:: >>> mivot_instance = mivot_viewer.dm_instance >>> print(mivot_instance.__dict__.keys()) dict_keys(['dmtype', 'longitude', 'latitude', 'pmLongitude', 'pmLatitude', 'epoch', 'Coordinate_coordSys']) .. doctest-skip:: >>> print(mivot_instance.Coordinate_coordSys.__dict__.keys()) dict_keys(['dmtype', 'dmid', 'dmrole', 'spaceRefFrame']) .. doctest-skip:: >>> print(mivot_instance.Coordinate_coordSys.spaceRefFrame.__dict__.keys()) dict_keys(['dmtype', 'value', 'unit', 'ref']) *More examples can be found* :ref:`here `. Reference/API ============= .. automodapi:: pyvo.mivot.viewer astropy-pyvo-b70558c/docs/mivot/writer.rst000066400000000000000000000211311510533647000207030ustar00rootroot00000000000000*************************************************** MIVOT (``pyvo.mivot``): Annotation Writer - Dev API *************************************************** Introduction ============ .. pull-quote:: Model Instances in VOTables (MIVOT) defines a syntax to map VOTable data to any model serialized in VO-DML. The annotation operates as a bridge between the data and the model. It associates the column/param metadata from the VOTable to the data model elements (class, attributes, types, etc.) [...]. The data model elements are grouped in an independent annotation block complying with the MIVOT XML syntax. This annotation block is added as an extra resource element at the top of the VOTable result resource. The MIVOT syntax allows to describe a data structure as a hierarchy of classes. It is also able to represent relations and composition between them. It can also build up data model objects by aggregating instances from different tables of the VOTable (get more in :doc:`index`). - Model Instances in VOTables is a VO `standard `_ - Requires Astropy>=6.0 - ``pyvo.mivot`` is a prototype feature which must be activated with ``activate_features("MIVOT")`` Use the API =========== Build Annotation Object per Object ---------------------------------- This documentation is intended for developers of data model classes who want to map them to VOTables and not for end users. A future version will allow end users to create annotations with ready-to-use data model building blocks. Creating annotations consists of 3 steps: #. Create individual instances (INSTANCE) using the ``MivotInstance`` class: objects are built attribute by attribute. These components can then be aggregated into more complex objects following the structure of the mapped model(s). #. Wrap the annotations with the ``MivotAnnotations`` class: declare to the annotation builder the models used, and place individual instances at the right place (TEMPLATES or GLOBALS). #. Insert the annotations into a VOtable by using the Astropy API (wrapped in the package logic). The annotation builder does not check whether the XML conforms to any particular model. It simply validates it against the MIVOT XML Schema if the ``xmlvalidator`` package if is installed. The example below shows a step-by-step construction of a MIVOT block mapping a position with its error (as defined in the ``MANGO`` draft) and its space coordinate system (as defined in the ``Coordinates`` model and imported by ``MANGO``). Build the empty MIVOT Block ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The MIVOT block consists of: - A process status - A list of mapped models - A list of globals, which are objects not associated with VOTable data and that can be shared by any other MIVOT instance. - A list of templates, which are objects that are connected to VOTable data and whose leaf values change from one row to another. - ``MIVOT`` is still an experimental feature which must be activated .. code-block:: python from astropy.io.votable import parse from pyvo.utils import activate_features from pyvo.mivot.utils.exceptions import MappingException from pyvo.mivot.utils.dict_utils import DictUtils from pyvo.mivot.writer.annotations import MivotAnnotations from pyvo.mivot.writer.instance import MivotInstance from pyvo.mivot.viewer.mivot_viewer import MivotViewer activate_features("MIVOT") mivot_annotations = MivotAnnotations() mivot_annotations.add_model( "ivoa", "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" ) mivot_annotations.add_model( "coords", "https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml" ) mivot_annotations.add_model( "mango", "https://raw.githubusercontent.com/lmichel/MANGO/draft-0.1/vo-dml/mango.vo-dml.xml", ) mivot_annotations.set_report(True, "PyVO Tuto") Build the Coordinate System Object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The space coordinate system is made of a space frame and a reference position, both wrapped in a ``coords:SpaceSys`` object (see the `Coordinates `_ data model). The time coordinate system is made of a time frame and a reference position, both wrapped in a ``coords:TimeSys`` object. - Each of these objects have a ``dmid`` which will be used as a reference by the ``EpochPosition`` instance. .. code-block:: python mivot_annotations.add_simple_space_frame(ref_frame="FK5", ref_position="BARYCENTER", equinox="J2000", dmid="_spacesys" ) mivot_annotations.add_simple_time_frame(ref_frame="TCB", ref_position="BARYCENTER", dmid="_timesys" ) Build the EpochPosition Object ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - In this example we only use the position attributes (RA/DEC) of the ``EpochPosition`` class. - The reference to the space coordinate system is added at the end. - The ``ref`` XML attributes reference columns that must be used to set the model attributes. Their values depend on the VOTable to be mapped. .. code-block:: python from astropy.io.votable import parse from pyvo.utils import activate_features from pyvo.mivot.utils.exceptions import MappingException from pyvo.mivot.utils.dict_utils import DictUtils from pyvo.mivot.writer.annotations import MivotAnnotations from pyvo.mivot.writer.instance import MivotInstance from pyvo.mivot.viewer.mivot_viewer import MivotViewer activate_features("MIVOT") position = MivotInstance(dmtype="mango:EpochPosition") position.add_attribute( dmtype="ivoa:RealQuantity", dmrole="mango:EpochPosition.longitude", unit="deg", ref="RAICRS", ) position.add_attribute( dmtype="ivoa:RealQuantity", dmrole="mango:EpochPosition.latitude", unit="deg", ref="DEICRS", ) position.add_reference( dmref="_spacesys", dmrole="mango:EpochPosition.spaceSys" ) Build the Position Error ^^^^^^^^^^^^^^^^^^^^^^^^ - We assume that the position error is the same on both axes without correlation. In terms of MANGO error, this corresponds to a 2x2 diagonal error matrix with two equal coefficients. - Finally, the error is added as a component of the ``EpochPosition`` instance. .. code-block:: python epoch_position_error = MivotInstance( dmtype="mango:EpochPositionErrors", dmrole="mango:EpochPosition.errors" ) position_error = MivotInstance( dmtype="mango:error.ErrorCorrMatrix", dmrole="mango:EpochPositionErrors.position", ) position_error.add_attribute( dmtype="ivoa:RealQuantity", dmrole="mango:error.ErrorCorrMatrix.sigma1", unit="arcsec", ref="sigm", ) position_error.add_attribute( dmtype="ivoa:RealQuantity", dmrole="mango:error.ErrorCorrMatrix.sigma2", unit="arcsec", ref="sigm", ) epoch_position_error.add_instance(position_error) position.add_instance(epoch_position_error) Pack the MIVOT Block ^^^^^^^^^^^^^^^^^^^^ - Pack the model instances previously built. - The latest step (build_mivot_block) includes a validation of the MIVOT syntax that works only if the ``xmlvalidator`` package has been installed. .. code-block:: python mivot_annotations.add_templates(position) mivot_annotations.build_mivot_block() Insert the MIVOT Block in a VOTable ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - This straightforward step is based on the Astropy VOTable API. - Annotations are stored in-memory (in the parsed VOtable). - The mapping can be tested with the ``MivotViewer`` API (see the :doc:`viewer`) - The VOtable must be explicitly saved on disk if needed. .. code-block:: python from astropy.io.votable import parse votable = parse(votable_path) mivot_annotations.insert_into_votable(votable) mivot_viewer = MivotViewer(votable) mapped_instance = mivot_viewer.dm_instance votable.to_xml("pyvo-tuto.xml") Validate the annotations against the models ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - This action requires the ``mivot-validatorXXX`` package to be installed. - It validates the mapped classes against the models they come from. .. code-block:: shell % pip install mivot-validator % mivot-instance-validate pyvo-tuto.xml ... Valid if no error message ... astropy-pyvo-b70558c/docs/nitpick-exceptions000066400000000000000000000010131510533647000212370ustar00rootroot00000000000000# Deprecated API py:class pyvo.dal.vosi.AvailabilityMixin # Non-public API classes. We should probably remove references to them altogether py:obj pyvo.io.uws.tree.Jobs py:class pyvo.dal.vosi.CapabilityMixin py:class pyvo.dal.vosi.EndpointMixin py:class pyvo.dal.adhoc.AxisParamMixin py:class pyvo.dam.obscore.ObsCoreMetadata py:obj pyvo.registry.regtap.Interface # There is no public API docs for this, yet it's useful to leave the reference in py:obj pyvo.io.vosi.exceptions py:class pyvo.dal.exceptions.PyvoUserWarning astropy-pyvo-b70558c/docs/registry/000077500000000000000000000000001510533647000173515ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/registry/index.rst000066400000000000000000000656501510533647000212260ustar00rootroot00000000000000.. _pyvo-registry: ************************** Registry (`pyvo.registry`) ************************** This is an interface to the Virtual Observatory Registry, a collection of metadata records of the VO's “resources” (“resource” is jargon for: a collection of datasets, usually with a service in front of it). For a wider background, see `2014A&C.....7..101D`_ for the general architecture and `2015A&C....10...88D`_ for the search interfaces. .. _2014A&C.....7..101D: https://ui.adsabs.harvard.edu/abs/2014A%26C.....7..101D/abstract .. _2015A&C....10...88D: https://ui.adsabs.harvard.edu/abs/2015A%26C....10...88D/abstract There are two fundamental modes of searching in the VO: (a) Data discovery: This is when you are looking for some sort of data collection based on its metadata; a classical example would be something like “I need redshifts of supernovae”. (b) Service discovery: This is what you need when you want to query all services of a certain kind (e.g., „all spectral services claiming to have infrared data“), which in turn is the basis of all-VO *dataset* discovery (“give me all infrared spectra of 3C273”) Both modes are supported by this module. .. _registry-basic-interface: Basic interface =============== The main interface for the module is :py:meth:`pyvo.registry.search`; the examples below assume: .. doctest:: >>> from pyvo import registry This function accepts one or more search constraints, which can be either specified using constraint objects as positional arguments or as keyword arguments. The following constraints are available: * :py:class:`~pyvo.registry.Freetext` (``keywords``): one or more freetext words, mached in the title, description or subject of the resource. * :py:class:`~pyvo.registry.Servicetype` (``servicetype``): constrain to one of tap, ssa, sia1, sia2, conesearch (or full ivoids for other service types). This is the constraint you want to use for service discovery. * :py:class:`~pyvo.registry.UCD` (``ucd``): constrain by one or more UCD patterns; resources match when they serve columns having a matching UCD (e.g., ``phot.mag;em.ir.%`` for “any infrared magnitude”). * :py:class:`~pyvo.registry.UAT` (``uat``): constrain by concepts from the IVOA Unified Astronomy Thesaurus http://www.ivoa.net/rdf/uat. * :py:class:`~pyvo.registry.Waveband` (``waveband``): one or more terms from the vocabulary at http://www.ivoa.net/rdf/messenger giving the rough spectral location of the resource. * :py:class:`~pyvo.registry.Author` (``author``): an author (“creator”). This is a single SQL pattern, and given the sloppy practices in the VO for how to write author names, you should probably generously use wildcards. * :py:class:`~pyvo.registry.Datamodel` (``datamodel``): one of obscore, epntap, or regtap: only return TAP services having tables of this kind. * :py:class:`~pyvo.registry.Ivoid` (``ivoid``): exactly match a single IVOA identifier (that is, in effect, the primary key in the VO). * :py:class:`~pyvo.registry.Spatial` (``spatial``): match resources covering, enclosed or overlapping a certain geometry (point, circle, polygon, or MOC). *RegTAP 1.2 Extension* * :py:class:`~pyvo.registry.Spectral` (``spectral``): match resources covering a certain part of the spectrum (usually, but not limited to, the electromagnetic spectrum). *RegTAP 1.2 Extension* * :py:class:`~pyvo.registry.Temporal` (``temporal``): match resources covering a some point or interval in time. *RegTAP 1.2 Extension* Multiple constraints are combined conjunctively (”AND”). Constraints marked with *RegTAP 1.2 Extension* are not available on all IVOA RegTAP services (they are on pyVO's default RegTAP endpoint, though). Also refer to the class documentation for further caveats on these. Hence, to look for for resources with UV data mentioning white dwarfs you could either run: .. doctest-remote-data:: >>> resources = registry.search(keywords="white dwarf", waveband="UV") or: .. doctest-remote-data:: >>> resources = registry.search(registry.Freetext("white dwarf"), ... registry.Waveband("UV")) or a mixture between the two. Constructing using explicit constraints is generally preferable with more complex queries. An advantage of using explicit constraints is that you can pass additional parameters to the constraints. For instance, the UAT constraint can optionally expand your keyword to narrower or wider concepts. When looking for resources talking about Cepheids of all kinds, you can thus say: .. doctest-remote-data:: >>> resources = registry.search( ... registry.UAT("cepheid-variable-stars", expand_down=3)) There is no way to express this using keyword arguments. However, where the constraints accept multiple equivalent arguments, you can pass in sequences to the keyword arguments; for instance: .. doctest-remote-data:: >>> resources = registry.search(registry.Waveband("Radio", "Millimeter"), ... registry.Author("%Miller%")) is equivalent to: .. doctest-remote-data:: >>> resources = registry.search(waveband=["Radio", "Millimeter"], ... author='%Miller%') There is also :py:meth:`~pyvo.registry.get_RegTAP_query`, accepting the same arguments as :py:meth:`pyvo.registry.search`. This function simply returns the ADQL query that search would execute. This is may be useful to construct custom RegTAP queries, which could then be executed on TAP services implementing the ``regtap`` data model. Data Discovery ============== In data discovery, you look for resources matching your constraints and then figure out in a second step how to query them. For instance, to look for resources giving redshifts in connection with supernovae, you would say: .. doctest-remote-data:: >>> resources = registry.search(registry.UCD("src.redshift"), ... registry.Freetext("AGB")) After that, ``resources`` is an instance of :py:class:`~pyvo.registry.RegistryResults`, which you can iterate over. In interactive data discovery, however, it is usually preferable to use the ``to_table`` method for an overview of the resources available: .. doctest-remote-data:: >>> resources.to_table() # doctest: +IGNORE_OUTPUT ivoid ... ... object ... --------------------------------- ... ivo://cds.vizier/j/a+a/392/1 ... ivo://cds.vizier/j/a+a/566/a95 ... ivo://cds.vizier/j/aj/151/146 ... ivo://cds.vizier/j/apj/727/14 ... ... And to look for tap resources *in* a specific cone, you would do .. doctest-remote-data:: >>> from astropy.coordinates import SkyCoord >>> registry.search(registry.Freetext("Wolf-Rayet"), ... registry.Spatial((SkyCoord("23d +3d"), 3), intersect="enclosed")) # doctest: +IGNORE_OUTPUT ivoid ... ... object ... ----------------------------------- ... ivo://cds.vizier/j/a+a/688/a104 ... ivo://cds.vizier/j/apj/938/73 ... ivo://cds.vizier/j/other/pasa/41.84 ... Astropy Quantities are also supported for the radius angle of a SkyCoord-defined circular region: .. doctest-remote-data:: >>> from astropy import units as u >>> registry.search(registry.Freetext("Wolf-Rayet"), ... registry.Spatial((SkyCoord("23d +3d"), 180*u.Unit('arcmin')), intersect="enclosed")) # doctest: +IGNORE_OUTPUT ivoid ... ... object ... ----------------------------------- ... ivo://cds.vizier/j/a+a/688/a104 ... ivo://cds.vizier/j/apj/938/73 ... ivo://cds.vizier/j/other/pasa/41.84 ... Where ``intersect`` can take the following values: * 'covers' is the default and returns resources that cover the geometry provided, * 'enclosed' is for services in the given region, * 'overlaps' returns services intersecting with the region. The idea is that in notebook-like interfaces you can pick resources by title, description, and perhaps the access mode (“interface”) offered. In the list of interfaces, you will sometimes spot an ``#aux`` after a standard id; this is a minor VO technicality that you can in practice ignore. For instance, you can simply construct :py:class:`~pyvo.dal.TAPService`-s from ``tap#aux`` interfaces. Once you have found a resource you would like to query, you can pick it by index; however, this will not be stable across multiple executions. Hence, RegistryResults also supports referencing results by short name, which is the style we recommend. Using full ivoids is possible, too, and safer because these are guaranteed to be unique (which short names are not), but it is rather clunky, and in the real VO short name collisions should be very rare. Use the ``get_service`` method of :py:class:`~pyvo.registry.RegistryResource` to obtain a DAL service object for a particular sort of interface. To query the fourth match using simple cone search, you would thus say: .. doctest-remote-data:: >>> voresource = resources["J/ApJ/727/14"] >>> voresource.get_service(service_type="conesearch").search(pos=(257.41, 64.345), sr=0.01) _r recno f_ID ID RAJ2000 ... SED DR7 Sloan Simbad deg ... float64 int32 str1 str18 float64 ... str3 str3 str5 str6 -------- ----- ---- ------------------ --------- ... ---- ---- ----- ------ 0.000618 1 P 170938.52+642044.1 257.41049 ... SED DR7 Sloan Simbad This method will raise an error if there is more than one service of the desired type. If you know for sure that all declared conesearch will be the same, you can safely use ``get_service(service_type='conesearch', lax=True)`` that will return the first conesearch it finds. However some providers provide multiple services of the same type -- for example in VizieR you'll find one conesearch per table. In this case, you can inspect the available `~pyvo.registry.regtap.Interface` to services with `~pyvo.registry.RegistryResource.list_interfaces`. Then, you can refine your instructions to `~pyvo.registry.RegistryResource.get_service` with a keyword constraint on the description ``get_service(service_type='conesearch', keyword='sncat')``. .. doctest-remote-data:: >>> for interface in voresource.list_interfaces(): ... print(interface) Interface(type='tap#aux', description='', url='https://tapvizier.cds.unistra.fr/TAPVizieR/tap') Interface(type='vr:webbrowser', description='', url='https://vizier.cds.unistra.fr/viz-bin/VizieR-2?-source=J/ApJ/727/14') Interface(type='conesearch', description='Cone search capability for table J/ApJ/727/14/table2 (AKARI IRC 3-24{mu}m, and Spitzer MIPS 24/70{mu}m photometry of Abell 2255 member galaxies)', url='https://vizier.cds.unistra.fr/viz-bin/conesearch/J/ApJ/727/14/table2?') Or construct the service object directly from the list of interfaces with: .. doctest-remote-data:: >>> voresource.list_interfaces()[0].to_service() TAPService(baseurl : 'https://tapvizier.cds.unistra.fr/TAPVizieR/tap', description : '') The list of interfaces can also be filtered to interfaces corresponding to services of a specific service type: .. doctest-remote-data:: >>> voresource.list_interfaces("tap") [Interface(type='tap#aux', description='', url='https://tapvizier.cds.unistra.fr/TAPVizieR/tap')] To operate TAP services, you need to know what tables make up a resource; you could construct a TAP service and access its ``tables`` attribute, but you can take a shortcut and call a RegistryResource's ``get_tables`` method for a rather similar result: .. doctest-remote-data:: >>> tables = resources["J/ApJ/727/14"].get_tables() # doctest: +IGNORE_WARNINGS >>> list(tables.keys()) ['J/ApJ/727/14/table2'] >>> sorted(c.name for c in tables["J/ApJ/727/14/table2"].columns) ['[24]', '[70]', 'dej2000', 'dr7', 'e_[24]', 'e_[70]', 'e_l15', 'e_l24', 'e_n3', 'e_n4', 'e_s11', 'e_s7', 'f_id', 'gmag', 'id', 'imag', 'l15', 'l24', 'n3', 'n4', 'raj2000', 'recno', 'rmag', 's11', 's7', 'sed', 'simbad', 'sloan', 'umag', 'y03', 'z', 'zmag'] In this case, this is a table with one of VizieR's somewhat funky names. To run a TAP query based on this metadata, do something like: .. doctest-remote-data:: >>> resources["J/ApJ/727/14"].get_service(service_type="tap#aux").run_sync( ... 'SELECT id, z FROM "J/ApJ/727/14/table2" WHERE z>0.09 and umag<18') ID z object float64 ------------------ ------- 171319.90+635428.0 0.09043 A special sort of access mode is ``web``, which represents some facility related to the resource that works in a web browser. You can ask for a “service” for it, too; you will then receive an object that has a ``search`` method, and when you call it, a browser window should open with the query facility (this uses python's ``webbrowser`` module): .. doctest-skip:: >>> resources["J/ApJ/727/14"].get_service(service_type="web").search() # doctest: +IGNORE_OUTPUT Note that for interactive data discovery in the VO Registry, you may also want to have a look at Aladin's discovery tree, TOPCAT's VO menu, or at services like WIRR_ in your web browser. .. _WIRR: https://dc.g-vo.org/WIRR Service Discovery ================= Service discovery is what you want typically in connection with a search for datasets, as in “Give me all infrared spectra of Bellatrix“. To do that, you want to run the same DAL query against all the services of a given sort. This means that you will have to include a ``servicetype`` constraint such that all resources in your registry results can be queried in the same way. When that is the case, you can use each RegistryResource's ``service`` attribute, which contains a DAL service instance. The opening example could be written like this: .. This one is too expensive to run as part of CI/testing .. doctest-skip:: >>> from astropy.coordinates import SkyCoord >>> my_obj = SkyCoord.from_name("Bellatrix") >>> for res in registry.search(waveband="infrared", servicetype="ssap"): ... print(res.service.search(pos=my_obj, size=0.001)) ... In reality, you will have to add some error handling to this kind of all-VO queries: in a wide and distributed network, some service is always down. See `Appendix: Robust All-VO Queries`_. The central point is: With a ``servicetype`` constraint, each result has a well-defined ``service`` attribute that contains some subclass of dal.Service and that can be queried in a uniform fashion. TAP services may provide tables in well-defined data models, like EPN-TAP or obscore. These can be queried in similar loops, although in some cases you will have to adapt the queries to the resources found. In the obscore case, an all-VO query would look like this: .. Again, that's too expensive for CI/testing .. doctest-skip:: >>> for svc_rec in registry.search(datamodel="obscore"): ... print(svc_rec.service.run_sync( ... "SELECT DISTINCT dataproduct_type FROM ivoa.obscore")) Again, in production this needs explicit handling of failing services. For an example of how this might look like, see `GAVO's plate tutorial`_ .. _GAVO's plate tutorial: http://docs.g-vo.org/gavo_plates.pdf More examples ------------- Discover archives ^^^^^^^^^^^^^^^^^ You can use the registry ``search`` method (or the ``regsearch`` function) to discover archives that may have x-ray images and then query those archives to find what x-ray images that have of CasA. For the arguments you will enter ``'image'`` for the service type and ``'x-ray'`` for the waveband. The position is provided by the Astropy library. The query returns a :py:class:`~pyvo.registry.RegistryResults` object which is a container holding a table of matching services. In this example it returns 33 matching services. .. doctest-remote-data:: >>> import pyvo as vo >>> from astropy.coordinates import SkyCoord >>> >>> import warnings >>> warnings.filterwarnings('ignore', module="astropy.io.votable.*") >>> >>> archives = vo.regsearch(servicetype='sia1', waveband='x-ray') >>> pos = SkyCoord.from_name('Cas A') >>> len(archives) # doctest: +IGNORE_OUTPUT 33 There are also other type of services that you can choose via the ``servicetype`` parameter, for more details see :py:class:`~pyvo.registry.Servicetype`. You can learn more about the archives by printing their titles and access URL: .. doctest-remote-data:: >>> for service in archives: ... print(service.res_title, service.access_url) # doctest: +IGNORE_OUTPUT Chandra X-ray Observatory Data Archive https://cda.harvard.edu/cxcsiap/queryImages? Chandra Source Catalog Release 1 http://cda.cfa.harvard.edu/csc1siap/queryImages? ... It is not necessary to keep track of the URL because you can search images directly from the registry record, for example using the Chandra X-ray Observatory (CDA) service and the ``search`` method, inserting the position and size for the desired object. .. doctest-remote-data:: >>> images = archives["CDA"].search(pos=pos, size=0.25) >>> len(images) # doctest: +IGNORE_OUTPUT 822 Sometimes you are looking for a type of object. For this purpose, the ``keywords`` parameter is useful here. For example, you want to find all catalogs related to blazars observed with Fermi: .. doctest-remote-data:: >>> cats = vo.regsearch(keywords=['blazar', 'Fermi']) >>> len(cats) # doctest: +IGNORE_OUTPUT 551 Or you already know the particular catalog but not the base URL for that service. For example, you want to get cutout images from the NRAO VLA Sky Survey (NVSS): .. doctest-remote-data:: >>> colls = vo.regsearch(keywords=['NVSS'], servicetype='sia1') >>> for coll in colls: ... print(coll.res_title, coll.access_url) NRA) VLA Sky Survey https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=nvss& Sydney University Molonglo Sky Survey https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=sumss& Search results ============== What is coming back from registry.search is :py:class:`pyvo.registry.RegistryResults` which is rather similar to :ref:`pyvo-resultsets`; just remember that for interactive use there is the ``to_tables`` method discussed above. The individual items are instances of :py:class:`~pyvo.registry.RegistryResource`, which expose many pieces of metadata (e.g., title, description, creators, etc) in attributes named like their RegTAP counterparts (see the class documentation). Some attributes deserve a second look. .. doctest-remote-data:: >>> import pyvo as vo >>> colls = vo.regsearch(keywords=["NVSS"], servicetype='sia1') >>> nvss = colls["NVSS"] >>> nvss.res_title 'NRA) VLA Sky Survey' If you are looking for a particular data collection or catalog, as we did above when we looked for the NVSS archive, often simply reviewing the titles is sufficient. Other times, particularly when you are not sure what you are looking for, it helps to look deeper. A selection of the resource metadata, including the title, shortname and description, can be printed out in a summary form with the ``describe`` function. .. doctest-remote-data:: >>> nvss.describe(verbose=True) NRA) VLA Sky Survey Short Name: NVSS IVOA Identifier: ivo://nasa.heasarc/skyview/nvss Access modes: sia - sia: https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=nvss& ... The verbose option in ``describe`` will output more information about the content of the resource, if available. Possible added entries are the authors of the resource, an associated DOI, an url where more information is provided, or a reference to a related paper. The method ``service`` will, for resources that only have a single capability, return a DAL service object ready for querying using the respective protocol. You should only use that attribute when the original registry query constrained the service type, because otherwise there is no telling what kind of service you will get back. .. doctest-remote-data:: >>> nvss = colls["NVSS"].service # converts record to service object >>> nvss.search(pos=(350.85, 58.815),size=0.25,format="image/fits") Survey Ra ... LogicalName object float64 ... object ------ ------- ... ----------- nvss 350.85 ... 1 With this service object, we can either call its ``search`` function directly or create query objects to get cutouts for a whole list of sources. .. doctest-remote-data:: >>> cutouts1 = nvss.search(pos=(148.8888, 69.065), size=0.2) >>> nvssq = nvss.create_query(size=0.2) # or create a query object >>> nvssq.pos = (350.85, 58.815) >>> cutouts2 = nvssq.execute() Our discussion of service metadata offers an opportunity to highlight another important property, the service's *IVOA Identifier* (sometimes referred to as its *ivoid*). This is a globally-unique identifier that takes the form of a `URI `_: .. doctest-remote-data:: >>> colls = vo.regsearch(keywords=["NVSS"], servicetype='sia1') >>> for coll in colls: ... print(coll.ivoid) ivo://nasa.heasarc/skyview/nvss ivo://nasa.heasarc/skyview/sumss This identifier can be used to retrieve a specific service from the registry. .. doctest-remote-data:: >>> nvss = vo.registry.search(ivoid='ivo://nasa.heasarc/skyview/nvss')[0].get_service(service_type='sia1') >>> nvss.search(pos=(350.85, 58.815),size=0.25,format="image/fits") Survey Ra ... LogicalName object float64 ... object ------ ------- ... ----------- nvss 350.85 ... 1 When the registry query did not constrain the service type, you can use the ``access_modes`` method to see what capabilities are available. For instance with this identifier: .. doctest-remote-data:: >>> res = registry.search(ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] >>> res.access_modes() # doctest: +IGNORE_OUTPUT {'ssa', 'datalink#links-1.0', 'tap#aux', 'web', 'soda#sync-1.0'} – this service can be accessed through SSA, TAP, a web interface, and two special capabilities that pyVO cannot produce services for (mainly because standalone service objects do not make much sense for them). To obtain a service for one of the access modes pyVO does support, use ``get_service(service_type=mode)``. For ``web``, this returns an object that opens a web browser window when its ``query`` method is called. RegistryResources also have a ``get_contact`` method. Use this if the service is down or seems to have bugs; you should in general get at least an e-Mail address: .. doctest-remote-data:: >>> res.get_contact() 'GAVO Data Centre Team (+49 6221 54 1837) ' Finally, the registry has an idea of what kind of tables are published through a resource, much like the VOSI tables endpoint (as a matter of fact, the Registry should contain exactly what is there, as VOSI tables in effect just gives a part of the registry record). Not all publishers properly provide table metadata to the Registry, though, but most do these days, and then you can run: .. doctest-remote-data:: >>> res.get_tables() # doctest: +IGNORE_OUTPUT {'flashheros.data': ... 29 columns ..., 'ivoa.obscore': ... 0 columns ...} Alternative Registries ====================== There are several RegTAP services in the VO. PyVO by default uses the one at the TAP access URL http://reg.g-vo.org/tap. You can use alternative ones, for instance, because they are nearer to you or because the default endpoint is down. You can pre-select the URL by setting the ``IVOA_REGISTRY`` environment variable to the TAP access URL of the service you would like to use. In a bash-like shell, you would say:: export IVOA_REGISTRY="https://mast.stsci.edu/vo-tap/api/v0.1/registry" before starting python (or the notebook processor). Within a Python session, you can use the `pyvo.registry.choose_RegTAP_service` function, which also takes the TAP access URL. As long as you have on working registry endpoint, you can find the other RegTAP services using: .. We probably shouldn't test the result of the next code block; this will change every time someone registers a new RegTAP service... .. doctest-remote-data:: >>> res = registry.search(datamodel="regtap") >>> print("\n".join(sorted(r.get_interface(service_type="tap", lax=True).access_url ... for r in res))) http://dc.g-vo.org/tap http://gavo.aip.de/tap http://voparis-rr.obspm.fr/tap https://mast.stsci.edu/vo-tap/api/v0.1/registry https://registry.euro-vo.org/regtap/tap Reference/API ============= .. automodapi:: pyvo.registry .. automodapi:: pyvo.registry.regtap .. automodapi:: pyvo.registry.rtcons Appendix: Robust All-VO Queries =============================== The VO contains many services, and even if all of them had 99.9% uptime (which not all do), at any time you would always see failures, some of them involving long timeouts. Hence, if you run all-VO queries, you should catch errors and, at least in interactive sessions, provide some way to interrupt overly long queries. Here is an example for how to query all obscore services; remove the ``break`` at the end of the loop to actually do the global query (it's there so that you don't blindly run all-VO queries without reading at least this sentence): .. doctest-remote-data:: >>> from astropy.table import vstack >>> from pyvo import registry >>> >>> QUERY = "SELECT TOP 1 s_ra, s_dec from ivoa.obscore" >>> >>> results = [] >>> for i, svc_rec in enumerate(registry.search(datamodel="obscore", servicetype="tap")): ... # print("Querying {}".format(svc_rec.res_title)) ... try: ... svc = svc_rec.get_service(service_type="tap", lax=True) ... results.append( ... svc.run_sync(QUERY).to_table()) ... except KeyboardInterrupt: ... # someone lost their patience with a service. Query next. ... pass ... except Exception as msg: ... # some service is broken; you *should* complain, but ... #print(" Broken: {} ({}). Complain to {}.\n".format( ... pass # svc_rec.ivoid, msg, svc_rec.get_contact())) ... if i == 2: ... break >>> total_result = vstack(results) # doctest: +IGNORE_WARNINGS >>> total_result # doctest: +IGNORE_OUTPUT
s_ra s_dec deg deg float64 float64 ------------------ ------------------- 350.4619 -9.76139 208.360833592735 52.3611106494996 148.204840298431 29.1690999975089 243.044008 -51.778222 321.63278049999997 -54.579285999999996 Note that even this is not enough to reliably cover use cases like „give me all images of M1 in the X-Ray in the VO“. In some future version, pyVO will come with higher-level functionality for such tasks. astropy-pyvo-b70558c/docs/samp.rst000066400000000000000000000032671510533647000172030ustar00rootroot00000000000000.. _pyvo-samp: ****************** SAMP (`pyvo.samp`) ****************** SAMP, the Simple Application Messaging Protocol, lets you send and receive tables, datasets, or points of interest between various clients on a desktop. While the main SAMP support still resides in astropy (it might be moved over to pyvo in later versions), there are a few wrapper functions in pyvo that make a few common SAMP tasks simpler or more robust. Most importantly, pyvo lets you manage the SAMP connection in a context manager, which means you will not have to remember to close your connections to avoid ugly artefacts in the SAMP hub. In addition, there are convenience functions for sending out data; in all cases, you can pass a ``client_name`` to only send a message to the specific client; a simple way to obtain client names is to inspect TOPCAT's SAMP status if you use TOPCAT's built-in SAMP hub. If, on the other hand, you pass ``None`` as ``client_name`` (or omit the parameter), you will send a broadcast. Sending tables has the additional difficulty over sending other datasets that you will have to make the table data accessible to the receiving client. The solution chosen by pyvo.samp at this time will only work if the sending and receiving applications share a file system. This seems a reasonable expectation and saves a bit of a potential security headache. Using pyvo.samp, sending an astropy table ``t`` to TOPCAT would look like this:: import pyvo with pyvo.samp.connection(client_name="pyvo magic") as conn: pyvo.samp.send_table_to( conn, t, name="my-results", client_name="topcat") Reference/API ============= .. automodapi:: pyvo.samp :no-inheritance-diagram: astropy-pyvo-b70558c/docs/utils/000077500000000000000000000000001510533647000166415ustar00rootroot00000000000000astropy-pyvo-b70558c/docs/utils/index.rst000066400000000000000000000010521510533647000205000ustar00rootroot00000000000000.. _pyvo-utils: ******************************* pyVO utilities (``pyvo.utils``) ******************************* This subpackage collects a few packages intended to help developing and maintaining pyVO. All of this is not part of pyVO's public API and may change at any time. It is documented here for the convenience of the maintainers and to help users when the effects of this code becomes user-visible. Reference/API ============= .. automodapi:: pyvo.utils.xml.elements :no-inheritance-diagram: .. toctree:: :maxdepth: 1 prototypes astropy-pyvo-b70558c/docs/utils/prototypes.rst000066400000000000000000000102531510533647000216240ustar00rootroot00000000000000.. _pyvo-prototypes: ************************************************** Prototype Implementations (`pyvo.utils.prototype`) ************************************************** This subpackage provides support for prototype standard implementations. ``PyVO`` implements the IVOA standards. As part of the standard approval process, new features are proposed and need to be demonstrated before the standard may be approved. ``PyVO`` may implement features that are not yet part of an approved standard. Such features are unstable, as the standard may be subject to reviews and significant changes, until it's finally approved. The ``prototype`` package provides support for such prototypes by means of a decorator for implementations that are still unstable. The expectation is that they will eventually become standard at which time the decorator will be removed. Users of ``pyvo`` need to explicitly opt-in in order to use such features. If prototype implementations are accessed without the user explicitly opting in, an exception will be raised. .. _pyvo-prototypes-users: Activating Prototype Implementations ==================================== In order to activate a feature, users need to call the function:: activate_features('feature_one', 'feature_two') Where the arguments are names of prototype features. If a feature name does not exist, a `~pyvo.utils.prototype.PrototypeWarning` will be issued, but the call will not fail. If no arguments are provided, then all features are enabled. .. _pyvo-prototypes-developers: Marking Features as Experimental ================================ The design restricts the possible usage of the decorator, which needs to always be called with a single argument being the name of the corresponding feature. More arguments are allowed but will be ignored. If the decorator is not used with the correct ``@prototype_feature("feature-name")`` invocation, the code will error as soon as the class is imported. The decorator can be used to tag individual functions or methods:: @prototype_feature('a-feature') def i_am_a_prototype(*arg, **kwargs): pass In this case, a single function or method is tagged as part of the ``a-feature`` prototype feature. If the feature has a URL defined (see :ref:`pyvo-prototypes-registry` below). Alternatively, a class can be marked as belonging to a feature. All public methods will be marked as part of the prototype implementation. Protected, private, and *dunder* methods (i.e. any method starting with an underscore) will be ignored. The reason is that the class might be instantiated by some mediator before the user can call (and more importantly not call) a higher level facade:: @prototype_feature('a-feature') class SomeFeatureClass: def method(self): pass @staticmethod def static(): pass def __ignore__(self): pass Any number of classes and functions can belong to a single feature, and individual methods can be tagged in a class rather than the class itself. .. _pyvo-prototypes-registry: Feature Registry ================ The feature registry is a static ``features`` dictionary in the `~pyvo.utils.prototype` package. The key is the name of the feature and the value is an instance of the `~pyvo.utils.protofeature.Feature` class. This class is responsible for determining whether an instance should error or not, and to format an error message if it's not. While the current implementation of the ``Feature`` class is simple, future requirements might lead to other implementations with more complex logic or additional documentation elements. .. _pyvo-prototypes-api: Reference/API ============= .. automodapi:: pyvo.utils.prototype .. automodapi:: pyvo.utils.protofeature Existing Prototypes =================== .. _cadc-tb-upload: CADC Table Manipulation (cadc-tb-upload) ---------------------------------------- This is a proposed extension to the TAP protocol to allow users to manipulate tables (https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next). The `~pyvo.dal.tap.TAPService` has been extended with methods that allow for: * table creation * column index creation * table content upload * table removal More details at: :ref:`table manipulation` astropy-pyvo-b70558c/licenses/000077500000000000000000000000001510533647000163565ustar00rootroot00000000000000astropy-pyvo-b70558c/licenses/ASTROPY_LICENSE.rst000066400000000000000000000027301510533647000214150ustar00rootroot00000000000000Copyright (c) 2011-2013, Astropy Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. astropy-pyvo-b70558c/licenses/LICENSE.rst000066400000000000000000000027511510533647000201770ustar00rootroot00000000000000Copyright (c) 2013, Virtual Astronomical Observatory, LLC (VAO) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the VAO nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. astropy-pyvo-b70558c/licenses/README.rst000066400000000000000000000002421510533647000200430ustar00rootroot00000000000000Licenses ======== This directory holds license and credit information for the affiliated package, works the affiliated package is derived from, and/or datasets. astropy-pyvo-b70558c/pyproject.toml000066400000000000000000000002651510533647000174700ustar00rootroot00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel"] build-backend = 'setuptools.build_meta' [tool.black] force-exclude = ''' ( .* ) '''astropy-pyvo-b70558c/pyvo/000077500000000000000000000000001510533647000155465ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/__init__.py000066400000000000000000000025571510533647000176700ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ PyVO is a package providing access to remote data and services of the Virtual observatory (VO) using Python. The pyvo module currently provides these main capabilities: * find archives that provide particular data of a particular type and/or relates to a particular topic * regsearch() * search an archive for datasets of a particular type * imagesearch(), spectrumsearch() * do simple searches on catalogs or databases * conesearch(), linesearch(), tablesearch() Submodules provide additional functions and classes for greater control over access to these services. This module also exposes the exception classes raised by the above functions, of which DALAccessError is the root parent exception. """ # Affiliated packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * # ---------------------------------------------------------------------------- from . import registry from .dal import ssa, sia, sla, scs, tap from . import auth from .registry import search as regsearch from .dal import ( imagesearch, spectrumsearch, conesearch, linesearch, tablesearch, DALAccessError, DALProtocolError, DALFormatError, DALServiceError, DALQueryError) astropy-pyvo-b70558c/pyvo/_astropy_init.py000066400000000000000000000002721510533647000210040ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ['__version__'] try: from .version import version as __version__ except ImportError: __version__ = '' astropy-pyvo-b70558c/pyvo/auth/000077500000000000000000000000001510533647000165075ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/auth/__init__.py000066400000000000000000000003541510533647000206220ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ["AuthSession", "AuthURLs", "CredentialStore"] from .authsession import AuthSession from .authurls import AuthURLs from .credentialstore import CredentialStore astropy-pyvo-b70558c/pyvo/auth/authsession.py000066400000000000000000000071351510533647000214340ustar00rootroot00000000000000import logging from .authurls import AuthURLs from .credentialstore import CredentialStore __all__ = ["AuthSession"] class AuthSession: """ A requests-like session that pyvo can use to dispatch its network calls with authentication. The user adds their credentials to the credentials object, such as adding a cookie, certificate, or password. The network requests made by pyvo pass through here, and the URL of the request is matched against the capabilities of the service. Based on what credentials have been provided and the capabilities of the service, appropriate credentials are added to the request before it is sent. """ def __init__(self): super().__init__() self.credentials = CredentialStore() self._auth_urls = AuthURLs() def add_security_method_for_url(self, url, security_method, exact=False): """ Add a security method for a url. This is additive with update_from_capabilities. This can be useful to set additional security methods that aren't set in the capabilities for whatever reason. Parameters ---------- url : str URL to set a security method for security_method : str URI of the security method to set exact : bool If True, match only this URL. If false, match all URLs that match this as a base URL. """ self._auth_urls.add_security_method_for_url(url, security_method, exact=exact) def update_from_capabilities(self, capabilities): """ Update the URL to security method mapping using the capabilities provided. Parameters ---------- capabilities : object List of `~pyvo.io.vosi.voresource.Capability` """ self._auth_urls.update_from_capabilities(capabilities) def get(self, url, **kwargs): """ Wrapper to make a HTTP GET request with authentication. """ return self._request('GET', url, **kwargs) def post(self, url, **kwargs): """ Wrapper to make a HTTP POST request with authentication. """ return self._request('POST', url, **kwargs) def put(self, url, **kwargs): """ Wrapper to make a HTTP PUT request with authentication. """ return self._request('PUT', url, **kwargs) def delete(self, url, **kwargs): """ Wrapper to make a HTTP DELETE request with authentication. """ return self._request('DELETE', url, **kwargs) def _request(self, http_method, url, **kwargs): """ Make an HTTP request with authentication. This function looks at the url of the request, determines what credentials it should attach to the request to authenticate, and then dispatches the request to the underlying requests library using the session that has been configured with the credentials. Parameters ---------- http_method : str the HTTP verb of the request. url : str the URL to request """ auth_methods = self._auth_urls.allowed_auth_methods(url) logging.debug('Possible auth methods: %s', auth_methods) negotiated_method = self.credentials.negotiate_method(auth_methods) logging.debug('Using auth method: %s', negotiated_method) session = self.credentials.get(negotiated_method) return session.request(http_method, url, **kwargs) def __repr__(self): return '\n'.join([repr(self.credentials), repr(self._auth_urls)]) astropy-pyvo-b70558c/pyvo/auth/authurls.py000066400000000000000000000074551510533647000207430ustar00rootroot00000000000000import collections import logging from . import securitymethods __all__ = ["AuthURLs"] class AuthURLs(): """ AuthURLs helps determine which security method should be used with a given URL. It learns the security methods through the VOSI capabilities, which are passed in via update_from_capabilities. """ def __init__(self): self.full_urls = collections.defaultdict(set) self.base_urls = collections.defaultdict(set) def update_from_capabilities(self, capabilities): """ Update the URL to security method mapping using the capabilities provided. Parameters ---------- capabilities : object List of `~pyvo.io.vosi.voresource.Capability` """ for c in capabilities: for i in c.interfaces: for u in i.accessurls: url = u.content exact = u.use == 'full' if not i.securitymethods: self.add_security_method_for_url(url, securitymethods.ANONYMOUS, exact) for sm in i.securitymethods: method = sm.standardid or securitymethods.ANONYMOUS self.add_security_method_for_url(url, method, exact) def add_security_method_for_url(self, url, security_method, exact=False): """ Add a security method for a url. This is additive with update_from_capabilities. This can be useful to set additional security methods that aren't set in the capabilities for whatever reason. Parameters ---------- url : str URL to set a security method for security_method : str URI of the security method to set exact : bool If True, match only this URL. If false, match all URLs that match this as a base URL. """ if exact: self.full_urls[url].add(security_method) else: self.base_urls[url].add(security_method) def allowed_auth_methods(self, url): """ Return the authentication methods allowed for a particular URL. The methods are returned as URIs that represent security methods. Parameters ---------- url : str the URL to determine authentication methods """ logging.debug('Determining auth method for %s', url) if url in self.full_urls: methods = self.full_urls[url] logging.debug('Matching full url %s, methods %s', url, methods) return methods for base_url, methods in self._iterate_base_urls(): if url.startswith(base_url): logging.debug('Matching base url %s, methods %s', base_url, methods) return methods logging.debug('No match, using anonymous auth') return {securitymethods.ANONYMOUS} def _iterate_base_urls(self): """ A generator to sort the base URLs in the correct way to determine the most specific base_url. This is done by returning them longest to shortest. """ def sort_by_len(x): return len(x[0]) # Sort the base path matching URLs, so that # the longest URLs (the most specific ones, if # there is a tie) are used to determine the # auth method. yield from sorted(self.base_urls.items(), key=sort_by_len, reverse=True) def __repr__(self): urls = [] for url, methods in self.full_urls.items(): urls.append('Full match:' + url + ':' + str(methods)) for url, methods in self._iterate_base_urls(): urls.append('Base match:' + url + ':' + str(methods)) return '\n'.join(urls) astropy-pyvo-b70558c/pyvo/auth/credentialstore.py000066400000000000000000000114061510533647000222520ustar00rootroot00000000000000import logging from pyvo.utils.http import create_session from . import securitymethods __all__ = ["CredentialStore"] class CredentialStore: """ The credential store takes user credentials, and uses them to create appropriate requests sessions for dispatching requests using those credentials. Different types of credentials can be passed in, such as cookies, a jar of cookies, certificates, and basic auth. A session can also be associated with a security method URI by calling the set function. Before a request is to be dispatched, the AuthSession calls the get method to retrieve the appropriate requests.Session for making that HTTP request. """ def __init__(self): self.credentials = {} self.set(securitymethods.ANONYMOUS, create_session()) def negotiate_method(self, allowed_methods): """ Compare the credentials provided by the user against the security methods passed in, and determine which method is to be used for making this request. Parameters ---------- allowed_methods : list(str) list of allowed security methods to return Raises ------ Raises an exception if a common method could not be negotiated. """ available_methods = set(self.credentials.keys()) methods = available_methods.intersection(allowed_methods) logging.debug('Available methods: %s', methods) # If we have no common auth mechanism, then fail. if not methods: msg = 'Negotiation failed. Server supports %s, client supports %s' % \ (allowed_methods, available_methods) raise Exception(msg) # If there are more than 1 method to pick, don't pick # anonymous over an actual method. if len(methods) > 1: methods.discard(securitymethods.ANONYMOUS) # Pick a random method. return methods.pop() def set(self, method_uri, session): """ Associate a security method URI with a requests.Session like object. Parameters ---------- method_uri : str URI representing the security method session : object the requests.Session like object that will dispatch requests for the authentication method provided by method_uri """ self.credentials[method_uri] = session def get(self, method_uri): """ Retrieve the requests.Session like object associated with a security method URI. Parameters ---------- method_uri : str URI representing the security method """ return self.credentials[method_uri] def set_cookie(self, cookie_name, cookie_value, domain='', path='/'): """ Add a cookie to use as authentication. More than one call to set_cookie will add multiple cookies into the same cookie jar used for the request. Parameters ---------- cookie_name : str name of the cookie cookie_value : str value of the cookie domain : str restrict usage of this cookie to this domain path : str restrict usage of this cookie to this path """ cookie_session = self.credentials.setdefault(securitymethods.COOKIE, create_session()) cookie_session.cookies.set(cookie_name, cookie_value, domain=domain, path=path) def set_cookie_jar(self, cookie_jar): """ Set the cookie jar to use for authentication. Any previous cookies or cookie jars set will be removed. Parameters ---------- cookie_jar : obj the cookie jar to use. """ cookie_session = self.credentials.setdefault(securitymethods.COOKIE, create_session()) cookie_session.cookies = cookie_jar def set_client_certificate(self, certificate_path): """ Add a client certificate to use for authentication. Parameters ---------- certificate_path : str path to the file of the client certificate """ cert_session = create_session() cert_session.cert = certificate_path self.set(securitymethods.CLIENT_CERTIFICATE, cert_session) def set_password(self, username, password): """ Add a username / password for basic authentication. Parameters ---------- username : str username to use password : str password to use """ basic_session = create_session() basic_session.auth = (username, password) self.set(securitymethods.BASIC, basic_session) def __repr__(self): return 'Support for %s' % self.credentials.keys() astropy-pyvo-b70558c/pyvo/auth/securitymethods.py000066400000000000000000000002411510533647000223110ustar00rootroot00000000000000ANONYMOUS = 'anonymous' BASIC = 'ivo://ivoa.net/sso#BasicAA' CLIENT_CERTIFICATE = 'ivo://ivoa.net/sso#tls-with-certificate' COOKIE = 'ivo://ivoa.net/sso#cookie' astropy-pyvo-b70558c/pyvo/auth/tests/000077500000000000000000000000001510533647000176515ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/auth/tests/__init__.py000066400000000000000000000001001510533647000217510ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/auth/tests/conftest.py000066400000000000000000000012661510533647000220550ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins astropy-pyvo-b70558c/pyvo/auth/tests/data/000077500000000000000000000000001510533647000205625ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/auth/tests/data/tap/000077500000000000000000000000001510533647000213465ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/auth/tests/data/tap/capabilities.xml000066400000000000000000000071051510533647000245240ustar00rootroot00000000000000 http://example.com/tap/capabilities http://example.com/tap/availability http://example.com/tap/tables $security_methods http://example.com/tap $security_methods ADQL 2.0 ADQL-2.0
POINT
CIRCLE
DISTANCE
POLYGON
REGION
CONTAINS
INTERSECTS
AREA
CENTROID
COORD1
COORD2
application/x-votable+xml votable text/xml text/csv csv text/tab-separated-values tsv 604800 604800 600 600 134217728 134217728 10000 10000
astropy-pyvo-b70558c/pyvo/auth/tests/test_auth.py000066400000000000000000000152401510533647000222250ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.auth """ import base64 from requests.cookies import RequestsCookieJar from string import Template import pytest from astropy.utils.data import get_pkg_data_contents import pyvo.dal from pyvo.auth.authsession import AuthSession from pyvo.dal.tests.test_tap import MockAsyncTAPServer @pytest.fixture(name='security_methods') def _security_methods(): return [None] @pytest.fixture() def auth_capabilities(mocker, security_methods): anon_element = '' sm_element = Template('') method_elements = [] for m in security_methods: if m is None: method_elements.append(anon_element) else: method_elements.append(sm_element.substitute(m=m)) def callback(request, context): t = Template(get_pkg_data_contents('data/tap/capabilities.xml')) capabilities = t.substitute(security_methods=method_elements) return capabilities.encode('utf-8') with mocker.register_uri( 'GET', 'http://example.com/tap/capabilities', content=callback ) as matcher: yield matcher class MockAnonAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert is None assert 'Authorization' not in request.headers assert 'Cookie' not in request.headers @pytest.fixture() def anon_auth_service(mocker): yield from MockAnonAuthTAPServer().use(mocker) class MockCookieAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert is None assert 'Authorization' not in request.headers assert request.headers.get('Cookie', None) == 'TEST_COOKIE=BADCOOKIE' @pytest.fixture() def cookie_auth_service(mocker): yield from MockCookieAuthTAPServer().use(mocker) class MockCertificateAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert == 'client-certificate.pem' assert 'Authorization' not in request.headers assert 'Cookie' not in request.headers @pytest.fixture() def certificate_auth_service(mocker): yield from MockCertificateAuthTAPServer().use(mocker) class MockBasicAuthTAPServer(MockAsyncTAPServer): def validator(self, request): pw = 'testuser:hunter2'.encode('ascii') basic_encoded = 'Basic ' + base64.b64encode(pw).decode('ascii') assert request.cert is None assert request.headers.get('Authorization', None) == basic_encoded assert 'Cookie' not in request.headers @pytest.fixture() def basic_auth_service(mocker): yield from MockBasicAuthTAPServer().use(mocker) @pytest.mark.usefixtures('cookie_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#cookie']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_cookies_auth(): session = AuthSession() session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('cookie_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#cookie']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_cookie_jar_auth(): session = AuthSession() jar = RequestsCookieJar() jar.set('TEST_COOKIE', 'BADCOOKIE') session.credentials.set_cookie_jar(jar) service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('certificate_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#tls-with-certificate']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_certificate_auth(): session = AuthSession() session.credentials.set_client_certificate('client-certificate.pem') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('basic_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#BasicAA']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_basic_auth(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('basic_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#BasicAA']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_negotiation(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') session.credentials.set_client_certificate('client-certificate.pem') session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('anon_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#FancyAuth']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_no_common_auth_negotiation(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') session.credentials.set_client_certificate('client-certificate.pem') session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") astropy-pyvo-b70558c/pyvo/dal/000077500000000000000000000000001510533647000163065ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/__init__.py000066400000000000000000000027551510533647000204300ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .sia import search as imagesearch from .sia2 import search as imagesearch2 from .ssa import search as spectrumsearch from .sla import search as linesearch from .scs import search as conesearch from .tap import search as tablesearch from .query import DALService, DALQuery, DALResults, Record from .sia import SIAService, SIAQuery, SIAResults, SIARecord from .sia2 import SIA2Service, SIA2Query, SIA2Results, ObsCoreRecord from .ssa import SSAService, SSAQuery, SSAResults, SSARecord from .sla import SLAService, SLAQuery, SLAResults, SLARecord from .scs import SCSService, SCSQuery, SCSResults, SCSRecord from .tap import TAPService, TAPQuery, TAPResults, AsyncTAPJob from .exceptions import ( DALAccessError, DALProtocolError, DALFormatError, DALServiceError, DALQueryError, DALOverflowWarning) __all__ = [ "imagesearch", "spectrumsearch", "linesearch", "conesearch", "tablesearch", "DALService", "imagesearch2", "SIAService", "SIA2Service", "SSAService", "SLAService", "SCSService", "TAPService", "DALQuery", "SIAQuery", "SIA2Query", "SSAQuery", "SLAQuery", "SCSQuery", "TAPQuery", "DALResults", "SIAResults", "SIA2Results", "SSAResults", "SLAResults", "SCSResults", "TAPResults", "Record", "ObsCoreRecord", "SIARecord", "SSARecord", "SLARecord", "SCSRecord", "AsyncTAPJob", "DALAccessError", "DALProtocolError", "DALFormatError", "DALServiceError", "DALQueryError", "DALOverflowWarning"] astropy-pyvo-b70558c/pyvo/dal/adhoc.py000066400000000000000000001215061510533647000177430ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Datalink classes and mixins """ import numpy as np import warnings import copy import requests from .query import DALResults, DALQuery, DALService, Record from .exceptions import DALServiceError from .vosi import AvailabilityMixin, CapabilityMixin from .params import find_param_by_keyword, get_converter from astropy.io.votable.tree import Param from astropy import units as u from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Resource, Group, VOTableFile try: from astropy.io.votable.tree import TableElement except ImportError: from astropy.io.votable.tree import Table as TableElement from astropy.utils.collections import HomogeneousList from ..utils.decorators import stream_decode_content from ..utils import vocabularies from .params import PosQueryParam, IntervalQueryParam, TimeQueryParam, EnumQueryParam from ..dam.obscore import POLARIZATION_STATES # calls to DataLink from the results pages are batched for performance # reasons. This is the size of a batch DATALINK_BATCH_CALL_SIZE = 50 SODA_SYNC_IVOID = 'ivo://ivoa.net/std/SODA#sync-1' DATALINK_IVOID = 'ivo://ivoa.net/std/datalink' # MIME types DATALINK_MIME_TYPE = 'application/x-votable+xml;content=datalink' # monkeypatch astropy with group support in RESOURCE def _monkeypath_astropy_resource_groups(): old_group_unknown_tag = Group._add_unknown_tag def new_group_unknown_tag(self, iterator, tag, data, config, pos): if tag == "PARAM": return self._add_param(self, iterator, tag, data, config, pos) else: old_group_unknown_tag(self, iterator, tag, data, config, pos) old_init = Resource.__init__ def new_init(self, *args, **kwargs): old_init(self, *args, **kwargs) self._groups = HomogeneousList(Group) Resource.__init__ = new_init def resource_groups(self): return self._groups Resource.groups = property(resource_groups) def resource_add_group(self, iterator, tag, data, config, pos): group = Group(self, config=config, pos=pos, **data) self.groups.append(group) group.parse(iterator, config) Resource._add_group = resource_add_group old_resource_unknown_tag = Resource._add_unknown_tag def new_resource_unknown_tag(self, iterator, tag, data, config, pos): if tag == "GROUP": return self._add_group(iterator, tag, data, config, pos) else: old_resource_unknown_tag(self, iterator, tag, data, config, pos) Resource._add_unknown_tag = new_resource_unknown_tag if not hasattr(Resource, 'groups'): _monkeypath_astropy_resource_groups() __all__ = [ "AdhocServiceResultsMixin", "DatalinkResultsMixin", "DatalinkRecordMixin", "DatalinkService", "DatalinkQuery", "DatalinkResults", "DatalinkRecord", "SodaRecordMixin", "SodaQuery"] def _get_input_params_from_resource(resource): # get the group with name inputParams group_input_params = next( group for group in resource.groups if group.name == "inputParams") # get only Param elements from the group return { _.name: _ for _ in group_input_params.entries if isinstance(_, Param)} def _get_params_from_resource(resource): return {_.name: _ for _ in resource.params} def _get_accessurl_from_params(params): if "accessURL" not in params: raise DALServiceError("Datalink has no accessURL") return params["accessURL"].value class AdhocServiceResultsMixin: """ Mixin for adhoc:service functionality for results classes. """ def __init__(self, votable, *, url=None, session=None): super().__init__(votable, url=url, session=session) self._adhocservices = list( resource for resource in votable.resources if resource.type == "meta" and resource.utype == "adhoc:service" ) def iter_adhocservices(self): yield from self._adhocservices def get_adhocservice_by_ivoid(self, ivo_id): """ Return the adhoc service starting with the given ivo_id. Parameters ---------- ivo_id : str the ivoid of the service we want to have. Returns ------- Resource The resource element describing the service. """ if isinstance(ivo_id, bytes): ivo_id = ivo_id.decode('utf-8') for adhocservice in self.iter_adhocservices(): if any( all(( param.name == "standardID", param.value.lower().startswith(ivo_id.lower()) )) for param in adhocservice.params ): return adhocservice raise DALServiceError( f"No Adhoc Service with ivo-id {ivo_id}!") def get_adhocservice_by_id(self, id_): """ Return the adhoc service starting with the given service_def id. Parameters ---------- id_ : str the service_def id of the service we want to have. Returns ------- Resource The resource element describing the service. """ for adhocservice in self.iter_adhocservices(): if adhocservice.ID == id_: return adhocservice raise DALServiceError( f"No Adhoc Service with service_def id {id_}!") class DatalinkResultsMixin(AdhocServiceResultsMixin): """ Mixin for datalink functionality for results classes. """ def _iter_datalinks_from_dlblock(self, preserve_order=False): """yields datalinks from the current rows using a datalink service RESOURCE. Parameters ---------- preserve_order : bool True to return the datalinks keeping the order of the current rows. NOTE: There might be a performance penalty for keeping the order as one request per row is sent to the service. When the order of the datalinks is not important, the execution is optimized to query the service in batches. """ def _get_results_tb(rows, dl_batch_tb): # Creates a new DL result table with the given rows as the results data # and the dl_batch_tb as the template for both table fields and other resources tb = VOTableFile() new_table = TableElement(tb) new_table.fields.extend(dl_batch_tb.get_first_table().fields) new_table.create_arrays(len(rows)) for index, row in enumerate(rows): new_table.array[index] = row results_resource = Resource() results_resource.type = "results" results_resource.tables.append(new_table) tb.resources.append(results_resource) # now add all the other resources from the dl_batch_tb for resource in dl_batch_tb.resources: if resource.type == "meta": tb.resources.append(resource) return tb if preserve_order: # one request at the time to preserve order for row in self: self.query = DatalinkQuery.from_resource( row, self._datalink, session=self._session, original_row=None) yield DatalinkResults( self.query.execute(post=True).votable, original_row=row) return # results from batch calls are not guaranteed to be in the same order with # the results rows. To map the links back to the original result rows, # create a dictionary of IDs to result rows, where ID is the value of # the column given by the ref field of the service descriptor (input parameter) original_rows = {} input_params = _get_input_params_from_resource(self._datalink) for name, input_param in input_params.items(): if input_param.ref: for row in self: original_rows[row[input_param.ref]] = row self.query = DatalinkQuery.from_resource( [_ for _ in self], self._datalink, session=self._session, original_row=None) remaining_ids = self.query['ID'] if not remaining_ids: # we are done before starting return current_batch = self.query.execute(post=True) if len(current_batch) == 0: raise DALServiceError( 'Could not retrieve datalinks for: {}'.format( ', '.join([_ for _ in remaining_ids]))) batch_size = 0 # unknown yet batch_size_determined = False # apparent only after first returned batch while remaining_ids: start_remaining_ids = len(remaining_ids) res_votable = current_batch.votable.get_first_table() id_index = 0 # Datalink spec: ID is the first column # Datalink spec: "... all links for a single ID value must be served in # consecutive rows in the output" # Accordingly, the line below should not be necessary but in # practice it might be needed np.ma.MaskedArray.sort(res_votable.array, id_index) last_id = res_votable.array[id_index][0] rows = [] for index, row in enumerate(res_votable.array): if row[id_index] == last_id: rows.append(row) else: yield DatalinkResults(_get_results_tb(rows, current_batch.votable), original_row=original_rows.get(last_id, None)) if not batch_size_determined: batch_size += 1 if last_id in remaining_ids: remaining_ids.remove(last_id) # proceed to the next ID last_id = row[id_index] rows = [row] if last_id in remaining_ids: remaining_ids.remove(last_id) if not batch_size_determined: batch_size += 1 batch_size_determined = True yield DatalinkResults(_get_results_tb(rows, current_batch.votable), original_row=original_rows.get(last_id, None)) if not remaining_ids: return # we are done if len(remaining_ids) == start_remaining_ids: # no progress raise DALServiceError( 'Could not retrieve datalinks for: {}'.format( ', '.join([_ for _ in remaining_ids]))) self.query['ID'] = remaining_ids[:batch_size] current_batch = self.query.execute(post=True) if not current_batch: raise DALServiceError( 'Could not retrieve datalinks for: {}'.format( ', '.join([_ for _ in remaining_ids]))) @staticmethod def _guess_access_format(row): """returns a guess for the format of what __guess_access_url will return. This tries a few heuristics based on how obscore or SIA records might be marked up. If will return None if row does not look as if it contained an access format. Note that the heuristics are tried in sequence; for now, we do not define the sequence of heuristics. """ if hasattr(row, "access_format"): return row.access_format if "access_format" in row: return row["access_format"] access_format = row.getbyutype("obscore:access.format" ) or row.getbyutype("ssa:Access.Format") if access_format: return access_format access_format = row.getbyucd("meta.code.mime" ) or row.getbyucd("VOX:Image_Format") if access_format: return access_format @staticmethod def _guess_access_url(row): """returns a guess for a URI to a data product in row. This tries a few heuristics based on how obscore or SIA records might be marked up. If will return None if row does not look as if it contained a product access url. Note that the heuristics are tried in sequence; for now, we do not define the sequence of heuristics. """ if hasattr(row, "access_url"): return row.access_url if "access_url" in row: return row["access_url"] access_url = row.getbyutype("obscore:access.reference" ) or row.getbyutype("ssa:Access.Reference") if access_url: return access_url access_url = row.getbyucd("meta.ref.url" ) or row.getbyucd("VOX:Image_AccessReference") if access_url: return access_url @staticmethod def _guess_datalink(row, **kwargs): # TODO: we should be more careful about whitespace, case # and perhaps more parameters in the following comparison if row._results._guess_access_format(row) == DATALINK_MIME_TYPE: access_url = row._results._guess_access_url(row) if access_url is not None: return DatalinkResults.from_result_url(access_url, **kwargs) def _iter_datalinks_from_product_rows(self): """yield datalinks from self's rows if they describe datalink-valued products. """ for row in self: # TODO: we should be more careful about whitespace, case # and perhaps more parameters in the following comparison if self._guess_access_format(row) == DATALINK_MIME_TYPE: access_url = self._guess_access_url(row) if access_url is not None: yield DatalinkResults.from_result_url( access_url, original_row=row) def iter_datalinks(self, preserve_order=False): """ Iterates over all datalinks in a DALResult. Parameters ---------- preserve_order : bool True to return the datalinks keeping the order of the current rows. NOTE: There might be a performance penalty for keeping the order as one request per row is sent to the service. When the order of the datalinks is not important, the execution is optimized to query the service in batches. """ if not hasattr(self, '_datalink'): try: self._datalink = self.get_adhocservice_by_ivoid(DATALINK_IVOID) except DALServiceError: self._datalink = None if self._datalink is None: yield from self._iter_datalinks_from_product_rows() else: yield from self._iter_datalinks_from_dlblock( preserve_order=preserve_order) class DatalinkRecordMixin: """ Mixin for record classes, providing functionallity for datalink. - ``getdataset()`` considers datalink. """ def getdatalink(self): """ Retrieve the datalink information for this record. Returns ------- DatalinkResults The datalink results for this record. Raises ------ DALServiceError If no datalink information is found for this record. """ try: datalink = self._results.get_adhocservice_by_ivoid(DATALINK_IVOID) query = DatalinkQuery.from_resource(self, datalink, session=self._session) return query.execute() except DALServiceError as error: datalink = self._results._guess_datalink(self, session=self._session) if datalink is not None: return datalink else: # re-raise the original error if nothing works raise DALServiceError("No datalink found for record.") from error @stream_decode_content def getdataset(self, timeout=None): try: url = next(self.getdatalink().bysemantics('#this')).access_url response = self._session.get(url, stream=True, timeout=timeout) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, url) return response.raw except (DALServiceError, ValueError, StopIteration): # this should go to Record.getdataset() return super().getdataset(timeout=timeout) class DatalinkService(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of a Datalink service """ def __init__(self, baseurl, session=None): """ instantiate a Datalink service Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such usecase for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) def run_sync(self, id, *, responseformat=None, **keywords): """ runs sync query and returns its result Parameters ---------- id : str the dataset identifier responseformat : str the output format Returns ------- DatalinkResults the query result See Also -------- DatalinkResults """ return self.create_query(id, responseformat=responseformat, **keywords).execute() # alias for service discovery search = run_sync def create_query(self, id, *, responseformat=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- id : str the dataset identifier responseformat : str the output format """ return DatalinkQuery( self.baseurl, id=id, responseformat=responseformat, **keywords) class DatalinkQuery(DALQuery): """ A class for preparing a query to a Datalink service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. A session can also optionally be passed in that will be used for network transactions made by this object to remote services. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ @classmethod def from_resource(cls, rows, resource, *, session=None, **kwargs): """ Creates a instance from a number of records and a Datalink Resource. XML Hierarchy: .. code:: xml """ original_row = kwargs.pop("original_row", None) input_params = _get_input_params_from_resource(resource) # get params outside of any group dl_params = _get_params_from_resource(resource) accessurl = _get_accessurl_from_params(dl_params) query_params = dict() for name, input_param in input_params.items(): if input_param.ref: if isinstance(rows, list): query_params[name] = [] for r in rows: query_params[name].append(r[input_param.ref]) else: # scalars are also accepted for backwards compatibility query_params[name] = rows[input_param.ref] elif np.isscalar(input_param.value) and input_param.value: query_params[name] = input_param.value elif ( not np.isscalar(input_param.value) and input_param.value.all() and len(input_param.value) ): query_params[name] = " ".join( str(_) for _ in input_param.value) for name, query_param in kwargs.items(): try: input_param = find_param_by_keyword(name, input_params) if input_param and query_param is None: del query_params[input_param.name] converter = get_converter(input_param) query_params[input_param.name] = converter.serialize( query_param) except KeyError: query_params[name] = query_param return cls( accessurl, session=session, original_row=original_row, **query_params) def __init__( self, baseurl, *, id=None, responseformat=None, session=None, **keywords): """ initialize the query object with the given parameters Parameters ---------- baseurl : str the Datalink baseurl id : str the dataset identifier responseformat : str the output format session : object optional session to use for network requests """ self.original_row = keywords.pop("original_row", None) super().__init__(baseurl, session=session, **keywords) if id is not None: self["ID"] = id if responseformat is not None: self["RESPONSEFORMAT"] = responseformat def execute(self, post=False): """ submit the query and return the results as a DatalinkResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return DatalinkResults( self.execute_votable(post=post), url=self.queryurl, original_row=self.original_row, session=self._session) class DatalinkResults(DatalinkResultsMixin, DALResults): """ The list of matching records resulting from an datalink query. Each record contains a set of metadata that describes an available record matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.Record` instances) are typically accessed by iterating over an ``DatalinkResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``DatalinkResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.Table` via the following conversion: ``table = results.to_table()`` ``DatalinkResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.Record` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def __init__(self, *args, **kwargs): self.original_row = kwargs.pop("original_row", None) super().__init__(*args, **kwargs) def getrecord(self, index): """ return a representation of a datalink result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has the additional function ``getdataset`` Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- Rec a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- pyvo.dal.Record """ return DatalinkRecord(self, index, session=self._session) def bysemantics(self, semantics, *, include_narrower=True): """ return the rows with the dataset identified by the given semantics Parameters ---------- semantics: str or list One or more term(s) from the datalink vocabulary (http://www.ivoa.net/rdf/datalink/core). datalink/core terms may be passed in with or without a leading hash. Free URIs may also be passed an and will be compared literally, i.e., without any URI normalisation. include_narrower: boolean If true, the result will include matches for any term that is narrower than the term passed in. Returns ------- Sequence of DatalinkRecord a sequence of dictionary-like wrappers containing the result record """ # If the URL juggling gets any more complicated here, we ought # to bite the bullet and only deal with full URLs. Sigh. if isinstance(semantics, str): semantics = [semantics] core_terms, other_terms = [], [] for term in semantics: if ":" in term: # it's a full URI, see if it's ours if term.startswith("http://www.ivoa.net/rdf/datalink/core#"): core_terms.append(term.split("#", 1)[-1]) else: other_terms.append(term) else: # it's a local term core_terms.append(term.lstrip("#")) if include_narrower: additional_terms = [] voc = vocabularies.get_vocabulary("datalink/core") for term in core_terms: if term in voc["terms"]: additional_terms.extend(voc["terms"][term]["narrower"]) core_terms = core_terms + additional_terms semantics = {"#" + term for term in core_terms} | set(other_terms) for record in self: if record.semantics in semantics: yield record def clone_byid(self, id, *, original_row=None): """ return a clone of the object with results and corresponding resources matching a given id Returns ------- Sequence of DatalinkRecord a sequence of dictionary-like wrappers containing the result record """ copy_tb = copy.deepcopy(self.votable) votable = copy_tb.get_first_table() # find index of ID column id_index = None for index, field in enumerate(votable.fields): if field.name == 'ID': id_index = index rows = [x for x in votable.array if x[id_index] == id] votable.create_arrays(len(rows)) for index, row in enumerate(rows): votable.array[index] = row # now remove unreferenced services from resources referenced_serviced = [x for x in votable.array['service_def'] if x] # remove customized that are not referenced by the current results for x in copy_tb.resources: if x.ID and x.ID not in referenced_serviced: copy_tb.resources.remove(x) return DatalinkResults(copy_tb, original_row=original_row) def getdataset(self, *, timeout=None): """ return the first row with the dataset identified by semantics #this Returns ------- DatalinkRecord a dictionary-like wrapper containing the result record. """ try: return next(self.bysemantics("#this")).getdataset(timeout=timeout) except StopIteration: raise ValueError("No row with semantics #this found!") def iter_procs(self): """ iterate over all rows with a processing service """ for row in self: if row.service_def: yield row def get_first_proc(self): """ returns the first datalink row with a processing service. """ for proc in self.iter_procs(): return proc raise IndexError("No processing service found in datalink result") @classmethod def from_result_url(cls, result_url, *, session=None, original_row=None): res = super().from_result_url(result_url, session=session) res.original_row = original_row return res class SodaRecordMixin: """ Mixin for soda functionality for record classes. If used, it's result class must have `pyvo.dal.adhoc.AdhocServiceResultsMixin` mixed in. """ def _get_soda_resource(self): try: return self._results.get_adhocservice_by_ivoid(SODA_SYNC_IVOID) except DALServiceError: pass dataformat = self.getdataformat() if dataformat is None: raise DALServiceError( "No SODA Resouces available and no dataformat in record. " "Maybe you forgot to include it into the TAP Query?") if "content=datalink" in dataformat: dataurl = self.getdataurl() if not dataurl: raise DALServiceError( "No dataurl in record, but dataformat contains " "'content=datalink'. Maybe you forgot to include it into " "the TAP Query?") try: datalink_result = DatalinkResults.from_result_url(dataurl) return datalink_result.get_adhocservice_by_ivoid( SODA_SYNC_IVOID) except DALServiceError: pass return None def processed( self, *, circle=None, range=None, polygon=None, band=None, **kwargs): """ Returns processed dataset. Parameters ---------- circle : `astropy.units.Quantity` latitude, longitude and radius range : `astropy.units.Quantity` two longitude + two latitude values describing a rectangle polygon : `astropy.units.Quantity` multiple (at least three) pairs of longitude and latitude points band : `astropy.units.Quantity` two bandwidth or frequency values """ soda_resource = self._get_soda_resource() if soda_resource: soda_query = SodaQuery.from_resource( self, soda_resource, circle=circle, range=range, polygon=polygon, band=band, **kwargs) soda_stream = soda_query.execute_stream() soda_query.raise_if_error() return soda_stream else: return self.getdataset() class DatalinkRecord(DatalinkRecordMixin, SodaRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an datalink query, The commonly accessed metadata which are stadardized by the datalink standard are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def id(self): """ Input identifier """ return self.get("ID", decode=True) @property def access_url(self): """ Link to data or processing service """ row_url = self.get("access_url", decode=True) if not row_url: proc_resource = self._results.get_adhocservice_by_id( self.service_def) dl_params = _get_params_from_resource(proc_resource) row_url = _get_accessurl_from_params(dl_params) return row_url @property def service_def(self): """ reference to the service descriptor resource """ return self.get("service_def", decode=True) @property def error_message(self): """ Error if an access_url cannot be created """ return self.get("error_message", decode=True) @property def description(self): """ Human-readable text describing this link """ return self.get("description", decode=True) @property def semantics(self): """ Term from a controlled vocabulary describing the link """ return self.get("semantics", decode=True) @property def content_type(self): """ Mime-type of the content the link returns """ return self.get("content_type", decode=True) @property def content_length(self): """ Size of the download the link returns """ return int(self["content_length"]) def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. Raises :py:class:`~pyvo.dal.DALServiceError` if theres an error. """ if self.error_message: raise DALServiceError(self.error_message) return self.access_url def process(self, **kwargs): """ calls the processing service and returns it's result as a file-like object """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) proc_query = DatalinkQuery.from_resource(self, proc_resource, **kwargs) proc_stream = proc_query.execute_stream() return proc_stream @property def params(self): """ the access parameters of the service behind this datalink row. """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) return proc_resource.params @property def input_params(self): """ a list of input parameters for the service behind this datalink row. """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) return list(_get_input_params_from_resource(proc_resource).values()) class AxisParamMixin: """ Stores the axis parameters (pos, band, time and pol) used in SODA or SIA2 queries """ @property def pos(self): if not hasattr(self, '_pos'): self._pos = PosQueryParam() self['POS'] = self._pos.dal return self._pos @property def band(self): if not hasattr(self, '_band'): self._band = IntervalQueryParam(unit=u.meter, equivalencies=u.spectral()) self['BAND'] = self.band.dal return self._band @property def time(self): if not hasattr(self, '_time'): self._time = TimeQueryParam() self['TIME'] = self.time.dal return self._time @property def pol(self): if not hasattr(self, '_pol'): self._pol = EnumQueryParam(POLARIZATION_STATES) self['POL'] = self.pol.dal return self._pol class SodaQuery(DatalinkQuery, AxisParamMixin): """ a class for preparing a query to a SODA Service. """ def __init__( self, baseurl, *, circle=None, range=None, polygon=None, band=None, **kwargs): super().__init__(baseurl, **kwargs) if circle is not None: self.circle = circle if range is not None: self.range = range if polygon is not None: self.polygon = polygon if band is not None: self.band = band @property def circle(self): """ The CIRCLE parameter defines a spatial region using the circle xtype defined in DALI. """ return getattr(self, '_circle', None) @circle.setter def circle(self, circle): if len(circle) != 3: raise ValueError( "Range must be a sequence with exactly three values") self['CIRCLE'] = PosQueryParam().get_dal_format(circle).\ replace('CIRCLE ', '') setattr(self, '_circle', circle) del self.range del self.polygon @circle.deleter def circle(self): if hasattr(self, '_circle'): delattr(self, '_circle') if 'CIRCLE' in self: del self['CIRCLE'] @property def range(self): """ A rectangular range. """ warnings.warn( "Use pos attribute instead", DeprecationWarning ) return getattr(self, '_circle', None) @range.setter def range(self, range): if len(range) != 4: raise ValueError( "Range must be a sequence with exactly four values") self['POS'] = PosQueryParam().get_dal_format(range) setattr(self, '_range', range) del self.circle del self.polygon @range.deleter def range(self): if hasattr(self, '_range'): delattr(self, '_range') if 'POS' in self and self['POS'].startswith('RANGE'): del self['POS'] @property def polygon(self): """ The POLYGON parameter defines a spatial region using the polygon xtype defined in DALI. """ return getattr(self, '_polygon', None) @polygon.setter def polygon(self, polygon): if len(polygon) < 6: raise ValueError( "Polygon must be a sequence of at least six values") self['POLYGON'] = PosQueryParam().get_dal_format(polygon).\ replace('POLYGON ', '') setattr(self, '_polygon', polygon) del self.circle del self.range @polygon.deleter def polygon(self): if hasattr(self, '_polygon'): delattr(self, '_polygon') if 'POLYGON' in self: del self['POLYGON'] @property def band(self): """ The BAND parameter defines the wavelength interval(s) to be extracted from the data using a floating point interval """ return getattr(self, "_band", None) @band.setter def band(self, band): setattr(self, "_band", band) if not isinstance(band, Quantity): valerr = ValueError( 'Band must be specified with exactly two values, ', 'expressing a frequency or wavelength range' ) try: # assume meters band = band * Unit("meter") if len(band) != 2: raise valerr except (ValueError, TypeError): raise valerr # transform to meters band = band.to(Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort # it to have the right order again band.sort() self["BAND"] = "{start} {end}".format( start=band.value[0], end=band.value[1]) @band.deleter def band(self): if hasattr(self, '_band'): delattr(self, '_band') if 'BAND' in self: del self['BAND'] astropy-pyvo-b70558c/pyvo/dal/dbapi2.py000066400000000000000000000160141510533647000200230ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ An implementation of the Database API v2.0 interface to DAL VOTable responses. This only supports read-only access. """ from .query import Iter apilevel = "2.0" threadsafety = 2 paramstyle = "n/a" __all__ = "STRING BINARY NUMBER DATETIME ROWID".split() class Error(Exception): """ DB-API base exception """ pass class Warning(Exception): """ DB-API warning """ pass class InterfaceError(Error): """ DB-API exception indicating an error related to the database interface rather than the database itself. """ pass class DatabaseError(Error): """ DB-API exception indicating an error related to the database. """ pass class DataError(DatabaseError): """ DB-API exception indicating an error while processing data (e.g. divide by zero, numeric value out-of-range, etc.) """ pass class OperationalError(DatabaseError): """ DB-API exception indicating an error related to the database's operation and not necessarily under the control of the programmer. """ pass class IntegrityError(DatabaseError): """ DB-API exception indicating an inconsistancy in the database integrity. """ pass class InternalError(DatabaseError): """ DB-API exception indicating an internal error that might indicate that a connection or cursor is no longer valid. """ pass class ProgrammingError(DatabaseError): """ DB-API exception indicating an erroneous request (e.g. column not found) """ pass class NotSupportedError(DatabaseError): """ DB-API exception indicating a request is not supported """ pass class TypeObject: def __init__(self, *values): self._values = values @property def id(self): return self._values[0] def __eq__(self, other): if not isinstance(other, TypeObject): return False if other.id in self._values: return True return self.id in other._values def __ne__(self, other): return not self.__eq__(other) STRING = TypeObject(0) BINARY = TypeObject(1) NUMBER = TypeObject(2) DATETIME = TypeObject(3, STRING.id) ROWID = TypeObject(4, NUMBER.id) def connect(source): raise NotSupportedError("Connection objects not supported") class Cursor(Iter): """ A class used to walk through a query response table row by row, accessing the contents of each record (row) of the table. This class implements the Python Database API. """ def __init__(self, results): """Create a cursor instance. The constructor is not typically called by directly applications; rather an instance is obtained from calling a DalQuery's execute(). """ super().__init__(results) self._description = self._mkdesc() self._rowcount = len(self.resultset) self._arraysize = 1 def _mkdesc(self): flds = self.resultset.fieldnames out = [] for name in flds: fld = self.resultset.getdesc(name) typ = STRING if fld.datatype in ("short", "int", "long", "float", "double", "floatComplex", "doubleComplex", "boolean"): typ = NUMBER elif fld.datatype in "char unicodeChar unsignedByte".split(): typ = STRING out.append((name, typ)) return tuple(out) @property def description(self): """ a read-only sequence of 2-item seqences. Each seqence describes a column in the results, giving its name and type_code. """ return self._description @property def rowcount(self): """ the number of rows in the result (read-only) """ return self._rowcount @property def arraysize(self): """ the number of rows that will be returned by returned by a call to fetchmany(). This defaults to 1, but can be changed. """ return self._arraysize @arraysize.setter def arraysize(self, value): if not value: value = 1 self._arraysize = value def infos(self): """Return any INFO elements in the VOTable as a dictionary. Returns ------- dict : A dictionary with each element corresponding to a single INFO, representing the INFO as a name:value pair. """ return self.resultset._infos def fetchone(self): """Return the next row of the query response table. Returns ------- tuple : The response is a tuple wherein each element is the value of the corresponding table field. """ try: rec = self.next() out = [] for name in self.resultset.fieldnames: out.append(rec[name]) return out except StopIteration: return None def fetchmany(self, size=None): """Fetch the next block of rows from the query result. Parameters ---------- size : int The number of rows to return (default: cursor.arraysize). Returns ------- list of tuples : A list of tuples, one per row. An empty sequence is returned when no more rows are available. If a DictCursor is used then the output consists of a list of dictionaries, one per row. """ if not size: size = self.arraysize out = [] for _ in range(size): out.append(self.fetchone()) return out def fetchall(self): """Fetch all remaining rows from the result set. Returns ------- list of tuples : A list of tuples, one per row. An empty sequence is returned when no more rows are available. If a DictCursor is used then the output consists of a list of dictionaries, one per row. """ out = [] for _ in range(self._rowcount - self.pos): out.append(self.fetchone()) return out def scroll(self, value, mode="relative"): """Move the row cursor. Parameters ---------- value : str The number of rows to skip or the row number to position to. mode : str Either "relative" for a relative skip (default), or "absolute" to position to a row by its absolute index within the result set (zero-indexed). """ if mode == "absolute": if value > 0: self.pos = value else: raise DataError("row number not valid:" + str(value)) elif mode == "relative": self.pos += value def close(self): """Close the cursor object and free all resources. This implementation does nothing. It is provided for compliance with the Python Database API. """ # this can remain implemented as "pass" pass astropy-pyvo-b70558c/pyvo/dal/exceptions.py000066400000000000000000000164671510533647000210570ustar00rootroot00000000000000""" DAL Exceptions. """ __all__ = [ "DALAccessError", "DALProtocolError", "DALFormatError", "DALServiceError", "DALQueryError", "DALOverflowWarning"] import re import requests from astropy.utils.exceptions import AstropyUserWarning class DALAccessError(Exception): """ a base class for failures while accessing a DAL service """ _defreason = "Unknown service access error" def __init__(self, reason=None, url=None): """ initialize the exception with an error message Parameters ---------- reason : str a message describing the cause of the error url : str the query URL that produced the error """ if not reason: reason = self._defreason super().__init__(reason) self._reason = reason self._url = url @classmethod def _typeName(cls, exc): try: return exc.__qualname__ except AttributeError: return re.sub( r"'>$", '', re.sub(r"<(type|class) '(.*\.)?", '', str(type(exc))) ) def __str__(self): return self._reason def __repr__(self): return f"{self._typeName(self)}: {self._reason}" @property def reason(self): """ a string description of what went wrong """ return self._reason @property def url(self): """ the URL that produced the error. If None, the URL is unknown or unset """ return self._url class DALProtocolError(DALAccessError): """ a base exception indicating that a DAL service responded with an error. This can be either an HTTP protocol error or a response format error; both of these are handled by separate subclasses. This base class captures an underlying exception clause. """ _defreason = "Unknown DAL Protocol Error" def __init__(self, reason=None, cause=None, url=None): """ initialize with a string message and an optional HTTP response code Parameters ---------- reason : str a message describing the cause of the error cause : Exception an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url : str the query URL that produced the error """ super().__init__(reason, url) self._cause = cause @property def cause(self): """ a string description of what went wrong """ return self._cause class DALFormatError(DALProtocolError): """ an exception indicating that a DAL response contains fatal format errors. This would include XML or VOTable format errors. """ _defreason = "Unknown VOTable Format Error" def __init__(self, cause=None, url=None, reason=None): """ create the exception Parameters ---------- cause : Exception an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url the query URL that produced the error reason a message describing the cause of the error """ if cause and not reason: reason = "{}: {}".format( DALAccessError._typeName(cause), str(cause)) super().__init__(reason, cause, url) class DALServiceError(DALProtocolError): """ an exception indicating a failure communicating with a DAL service. Most typically, this is used to report DAL queries that result in an HTTP error. """ _defreason = "Unknown service error" def __init__(self, reason=None, code=None, cause=None, url=None): """ initialize with a string message and an optional HTTP response code Parameters ---------- reason : str a message describing the cause of the error code : int the HTTP error code (as an integer) cause : Exception an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url : str the query URL that produced the error """ super().__init__(reason, cause, url) self._code = code @property def code(self): """ the HTTP error code that resulted from the DAL service query, indicating the error. If None, the service did not produce an HTTP response. """ return self._code @classmethod def from_except(cls, exc, url=None): """ create and return DALServiceError exception appropriate for the given exception that represents the underlying cause. """ if isinstance(exc, requests.exceptions.RequestException): try: response = exc.response except AttributeError: response = None code = 0 message = str(exc) # if there is a response, refine the error message if response is not None: code = response.status_code content_type = response.headers.get('content-type', None) if content_type and 'text/plain' in content_type: message = f'{response.text} for {url}' # TODO votable handling return DALServiceError(message, code, exc, url) elif isinstance(exc, Exception): return DALServiceError(f"{cls._typeName(exc)}: {str(exc)}", cause=exc, url=url) else: raise TypeError("from_except: expected Exception") class DALQueryError(DALAccessError): """ an exception indicating an error by a working DAL service while processing a query. Generally, this would be an error that the service successfully detected and consequently was able to respond with a legal error response-- namely, a VOTable document with an INFO element contains the description of the error. Possible errors will include bad usage by the client, such as query-syntax errors. """ _defreason = "Unknown DAL Query Error" def __init__(self, reason=None, label=None, url=None): """ Parameters ---------- reason : str a message describing the cause of the error. This should be set to the content of the INFO error element. label : str the identifying name of the error. This should be the value of the INFO element's value attribute within the VOTable response that describes the error. url : str the query URL that produced the error """ super().__init__(reason, url) self._label = label @property def label(self): """ the identifing name for the error given in the DAL query response. DAL queries that produce an error which is detectable on the server will respond with a VOTable containing an INFO element that contains the description of the error. This property contains the value of the INFO's value attribute. """ return self._label class PyvoUserWarning(AstropyUserWarning): pass class DALOverflowWarning(UserWarning): pass astropy-pyvo-b70558c/pyvo/dal/mimetype.py000066400000000000000000000066041510533647000205170ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for parsing and working with mimetypes """ import mimetypes from email.message import Message from astropy.io.fits import HDUList from ..utils.http import use_session mimetypes.add_type('application/fits', '.fits') mimetypes.add_type('application/x-fits', '.fits') mimetypes.add_type('image/fits', '.fits') mimetypes.add_type('text/plain', '.txt') def mime2extension(mimetype, default=None): """ return a recommended file extension for a file with a given MIME-type. This function provides some generic mappings that can be leveraged in implementations of ``suggest_extension()`` in ``Record`` subclasses. >>> mime2extension('application/fits') 'fits' >>> mime2extension('image/jpeg') 'jpg' >>> mime2extension('application/x-zed', 'dat') 'dat' Parameters ---------- mimetype : str the file MIME-type byte-string to convert default : str the default extension to return if one could not be recommended based on ``mimetype``. By convention, this should not include a preceding '.' Returns ------- str the recommended extension without a preceding '.', or the value of ``default`` if no recommendation could be made. """ if not mimetype: return default if isinstance(mimetype, bytes): mimetype = mimetype.decode('utf-8') ext = mimetypes.guess_extension(mimetype, strict=False) if ext is None: return default return ext.lstrip(".") def mime_object_maker(url, mimetype, *, session=None): """ return a data object suitable for the mimetype given. this will either return a astropy fits object or a pyvo DALResults object, a PIL object for conventional images or string for text content. Parameters ---------- url : str the object download url mimetype : str the content mimetype session : object optional session to use for network requests Raises ------ ValueError if the mimetype is missing or cannot be parsed correctly """ if not mimetype: raise ValueError('mimetype required') session = use_session(session) msg = Message() msg['content-type'] = mimetype pp = msg.get_params() full_type = pp[0][0] params = pp[1:] mtype = [x.strip() for x in full_type.split('/')] if '/' in full_type else None if not mtype or len(mtype) > 2: raise ValueError(f"Can't parse mimetype \"{full_type}\"") if mtype[0] == 'text': return session.get(url).text if mtype[1] == 'fits' or mtype[1] == 'x-fits': response = session.get(url) return HDUList.fromstring(response.content) if mtype[0] == 'image': from PIL import Image from io import BytesIO response = session.get(url) bio = BytesIO(response.content) return Image.open(bio) if mtype[1] == 'x-votable' or mtype[1] == 'x-votable+xml': # As soon as there are some kind of recursive data structures, # things start to get messy for param in params: if (param[0].lower() == 'content') and (param[1].lower() == 'datalink'): from .adhoc import DatalinkResults return DatalinkResults.from_result_url(url) from .query import DALResults return DALResults.from_result_url(url) astropy-pyvo-b70558c/pyvo/dal/params.py000066400000000000000000000344271510533647000201550ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains functionallity related to VOTABLE Params. """ import numpy as np from collections.abc import MutableSet import abc from astropy import units as u from astropy.coordinates import SkyCoord from astropy.units import Quantity, Unit from astropy.time import Time from astropy.io.votable.converters import ( get_converter as get_votable_converter) from .exceptions import DALServiceError NUMERIC_DATATYPES = {'short', 'int', 'long', 'float', 'double'} def find_param_by_keyword(keyword, params): """ Searches for a specific param by keyword. This function will try to look for the keyword as-is first, and then tries to find the uppercase'd version of the keyword. """ if keyword in params: return params[keyword] keyword = keyword.upper() if keyword in params: return params[keyword] raise KeyError(f'No param named {keyword} defined') registry = dict() def xtype(name): def decorate(cls): registry[name] = cls return cls return decorate def unify_value(func): """ Decorator for serialize method to do unit conversion on input value. The decorator converts the input value to the unit in the input param. """ def wrapper(self, value): if self._param.unit: value = Quantity(value) if not value.unit.to_string(): value = value * Unit(self._param.unit) else: value = value.to(self._param.unit) if isinstance(value, Quantity): value = value.value return func(self, value) return wrapper def get_converter(param): if param.xtype in registry: return registry[param.xtype](param) if param.datatype in NUMERIC_DATATYPES: return Number(param) return Converter(param) class Converter: """ Base class for all converters. Each subclass handles the conversion of a input value based on a specific xtype. """ def __init__(self, param): self._param = param def serialize(self, value): """ Serialize for use in DAL Queries """ if isinstance(value, list): # multiple values return [str(_) for _ in value] else: return str(value) class Number(Converter): def __init__(self, param): if param.datatype not in {'short', 'int', 'long', 'float', 'double'}: pass super().__init__(param) @unify_value def serialize(self, value): """ Serialize for use in DAL Queries """ return get_votable_converter(self._param).output( value, np.zeros_like(value)) @xtype('timestamp') class Timestamp(Converter): def __init__(self, param): if param.datatype != 'char': raise DALServiceError('Datatype is not char') super().__init__(param) def serialize(self, value): """ Serialize time values for use in DAL Queries """ value = Time(value) if value.size == 1: return value.isot else: raise DALServiceError('Expecting a scalar time value') @xtype('interval') class Interval(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize % 2: raise DALServiceError('Arraysize is not even') except ValueError: raise DALServiceError('Arraysize is not even') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size % 2: raise DALServiceError('Interval size is not even') return super().serialize(value) @xtype('point') class Point(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize != 2: raise DALServiceError('Point arraysize must be 2') except ValueError: raise DALServiceError('Point arraysize must be 2') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size != 2: raise DALServiceError('Point size must be 2') return super().serialize(value) @xtype('circle') class Circle(Number): def __init__(self, param): arraysize = int(param.arraysize) if arraysize != 3: raise DALServiceError('Circle arraysize must be 3') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size != 3: raise DALServiceError('Circle size must be 3') return super().serialize(value) @xtype('polygon') class Polygon(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize % 3: raise DALServiceError('Arraysize is not a multiple of 3') except ValueError: if param.arraysize != '*': raise DALServiceError('Arraysize is not a multiple of 3') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) try: if size % 3: raise DALServiceError('Size is not a multiple of 3') except ValueError: raise DALServiceError('Size is not a multiple of 3') return super().serialize(value) class AbstractDalQueryParam(MutableSet, metaclass=abc.ABCMeta): """ Base class for a DAL parameter. In general, a DAL parameter allows for multiple values which are OR-ed by the service. As such, the class behaves like a set that stores all the values. When a value is added to an attribute, it is validated and formatted to conform to the using service (SIA2 or SODA) and value errors might be raised. The `dal` attribute stores the current list of formatted attributes. Subclasses must override the `dal_formatter` method that formats values for serialization. That includes unit conversions and string representation Duplicates in the set are determine based on the formatted DAL representation of the value. """ def __init__(self, values=()): """ Parameters ---------- values : sequence, optional An initial set of values. """ self.dal = [] self._data = [] for item in values: self.add(item) super().__init__() @abc.abstractmethod def get_dal_format(self, item): """ Method to be provided by subclasses """ return def add(self, item): if item in self: return self._data.append(item) self.dal.append(self.get_dal_format(item)) def discard(self, item): # relies on the fact that both the raw and the formatted # attribute lists have the items in the same order. It # uses the formatted list (normalized units) to get the index. index = self.dal.index(self.get_dal_format(item)) self._data.pop(index) self.dal.pop(index) def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) def __contains__(self, item): # check dal format for duplications since the quantities are known return self.get_dal_format(item) in self.dal class StrQueryParam(AbstractDalQueryParam): """ Representation of a unitless, single-value parameter. The formatter is just a str() cast """ def get_dal_format(self, val): return str(val) class PosQueryParam(AbstractDalQueryParam): """ Representation of a position parameter. Depending on the number of entries, the resulting DAL format is CIRCLE, RANGE or POLYGON. """ def get_dal_format(self, val): """ formats the tuple values into a string to be sent to the service entries in values are either quantities or assumed to be degrees """ self._validate_pos(val) if len(val) == 2 or len(val) == 3: shape = 'CIRCLE' elif len(val) == 4: shape = 'RANGE' elif len(val) > 5 and not len(val) % 2: shape = 'POLYGON' else: raise ValueError( 'Invalid shape {}. Tuple with 3 (CIRCLE), 4 (RANGE) or ' 'even 6 and above (POLYGON) accepted.'.format(val)) return '{} {}'.format(shape, ' '.join( [str(val.to(u.deg).value) if isinstance(val, Quantity) else val.transform_to('icrs').to_string() if isinstance(val, SkyCoord) else str((val * u.deg).value) for val in val])) def _validate_pos(self, pos): """ validates position This has probably been done already somewhere else """ if isinstance(pos, SkyCoord) and pos.size < 4: raise ValueError("radius should be provided in the pos tuple " "for CIRCLE searches.") if len(pos) == 2: if not isinstance(pos[0], SkyCoord): raise ValueError("a 2-length pos should be a coordinate and a radius") if not isinstance(pos[1], Quantity): radius = pos[1] * u.deg else: radius = pos[1] if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg: raise ValueError(f'Invalid circle radius: {radius}') elif len(pos) == 3: self._validate_ra(pos[0]) self._validate_dec(pos[1]) if not isinstance(pos[2], Quantity): radius = pos[2] * u.deg else: radius = pos[2] if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg: raise ValueError(f'Invalid circle radius: {radius}') elif len(pos) == 4: ra_min = pos[0] if isinstance(pos[0], Quantity) else pos[0] * u.deg ra_max = pos[1] if isinstance(pos[1], Quantity) else pos[1] * u.deg dec_min = pos[2] if isinstance(pos[2], Quantity) \ else pos[2] * u.deg dec_max = pos[3] if isinstance(pos[3], Quantity) \ else pos[3] * u.deg self._validate_ra(ra_min) self._validate_ra(ra_max) if ra_max.to(u.deg) < ra_min.to(u.deg): raise ValueError('min > max in ra range: {} > {}'. format(ra_min, ra_max)) self._validate_dec(dec_min) self._validate_dec(dec_max) if dec_max.to(u.deg) < dec_min.to(u.deg): raise ValueError('min > max in dec range: {} > {}'. format(dec_min, dec_max)) else: for i, m in enumerate(pos): if i % 2: self._validate_dec(m) else: self._validate_ra(m) def _validate_ra(self, ra): ra = Quantity(ra, u.deg) if ra.to(u.deg).value < 0 or ra.to(u.deg).value > 360.0: raise ValueError(f'Invalid ra: {ra}') def _validate_dec(self, dec): dec = Quantity(dec, u.deg) if dec.to(u.deg).value < -90.0 or dec.to(u.deg).value > 90.0: raise ValueError(f'Invalid dec: {dec}') class IntervalQueryParam(AbstractDalQueryParam): """ Representation of an interval DAL parameter. """ def __init__(self, unit=None, equivalencies=None): """ Parameters ---------- unit : `astropy.unit` Unit this paramter is represented in DAL format equivalencies: list List of equivalencies for unit conversion """ self._unit = unit self._equivalencies = equivalencies super().__init__() def get_dal_format(self, val): if isinstance(val, (tuple, Quantity)): if len(val) == 1: high = low = val[0] elif len(val) == 2: low = val[0] high = val[1] else: raise ValueError('Too few/many values in interval attribute: {}'. format(val)) else: high = low = val if isinstance(low, (int, float)) and isinstance(high, (int, float))\ and low > high: raise ValueError('Invalid interval: min({}) > max({})'.format( low, high)) if self._unit: if not isinstance(low, Quantity): low = u.Quantity(low, self._unit) low = low.to(self._unit, equivalencies=self._equivalencies).value if not isinstance(high, Quantity): high = Quantity(high, self._unit) high = high.to(self._unit, equivalencies=self._equivalencies).value if low > high: # interval could become invalid during transform (e.g. GHz->m) low, high = high, low return f'{low} {high}' class TimeQueryParam(AbstractDalQueryParam): """ Representation of a timestamp parameter. """ def get_dal_format(self, val): if isinstance(val, tuple): if len(val) == 1: max_time = min_time = val[0] elif len(val) == 2: min_time = val[0] max_time = val[1] else: raise ValueError('Too few/many members in time attribute: {}'. format(val)) else: max_time = min_time = val if not isinstance(min_time, Time): min_time = Time(min_time) if not isinstance(max_time, Time): max_time = Time(max_time) if min_time > max_time: raise ValueError('Invalid time interval: min({}) > max({})'.format( min_time, max_time )) return f'{min_time.mjd} {max_time.mjd}' class EnumQueryParam(AbstractDalQueryParam): """ Representation of an enum parameter """ def __init__(self, allowed_values): """ Parameters ---------- allowed_values : sequence Sequence of allowed values for the enum """ self._allowed = allowed_values super().__init__() def get_dal_format(self, val): if val not in self._allowed: raise ValueError('{} not a valid value from: {}'. format(val, self._allowed)) return str(val) astropy-pyvo-b70558c/pyvo/dal/query.py000066400000000000000000001102341510533647000200260ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for walking through the query response of VO data access layer (DAL) queries and general VOTable-based datasets. Most data queries in the VO return a table as a result, usually formatted as a VOTable. Each row of the table describes a single physical or virtual dataset which can be retrieved. For uniformity, datasets are described via standard metadata defined by a data model specific to the type of data being queried. The fields of the data model are identified most generally by their VOClient alias as defined in this interface, or at a lower level by the Utype or UCD of the specific standard and version of the standard being queried. While the data model differs depending upon the type of data being queried, the form of the query response is the same for all classes of data, allowing a common query response interface to be used. An exception to this occurs when querying an astronomical catalog or other externally defined table. In this case there is no VO defined standard data model. Usually the field names are used to uniquely identify table columns. """ __all__ = ["DALService", "DALServiceError", "DALQuery", "DALQueryError", "DALResults", "Record", "UploadList"] import os import shutil import re import requests from collections.abc import Mapping from io import BytesIO, StringIO import collections from warnings import warn from astropy.table import Table, QTable from astropy.io.votable import parse as votableparse from astropy.io.votable.ucd import parse_ucd from astropy.utils.exceptions import AstropyDeprecationWarning from .mimetype import mime_object_maker from .exceptions import (DALFormatError, DALServiceError, DALQueryError, DALOverflowWarning) from .. import samp from ..utils.decorators import stream_decode_content from ..utils.http import use_session class DALService: """ an abstract base class representing a DAL service located a particular endpoint. """ def __init__(self, baseurl, *, session=None, capability_description=None,): """ instantiate the service connecting it to a base URL Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests capability_description : str, optional the description of the service. """ self._baseurl = baseurl self._capability_description = capability_description self._session = use_session(session) @property def baseurl(self): """ the base URL identifying the location of the service and where queries are submitted (read-only) """ return self._baseurl @property def capability_description(self): """ The service description. """ return self._capability_description def __repr__(self) -> str: return (f"{type(self).__name__}(baseurl : '{self.baseurl}'," f" description : '{self.capability_description}')") def search(self, **keywords): """ send a search query to this service. This implementation has no knowledge of the type of service being queried. The query parameters are given as arbitrary keywords which will be assumed to be understood by the service (i.e. there is no argument checking). The response is a generic DALResults object. Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ q = self.create_query(**keywords) return q.execute() def create_query(self, **keywords): """ create a query object that constraints can be added to and then executed. Returns ------- DALQuery a generic query object """ q = DALQuery(self.baseurl, session=self._session, **keywords) return q def describe(self): """ describe the general information about the DAL service """ print(f'DAL Service at {self.baseurl}') class DALQuery(dict): """ a class for preparing a query to a particular service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query can be changed via the baseurl property. A session can also optionally be passed in that will be used for network transactions made by this object to remote services. """ _ex = None def __init__(self, baseurl, *, session=None, **keywords): """ initialize the query object with a baseurl """ if isinstance(baseurl, bytes): baseurl = baseurl.decode("utf-8") self._baseurl = baseurl.rstrip("?") self._session = use_session(session) self.update({key.upper(): value for key, value in keywords.items()}) @property def baseurl(self): """ the base URL that this query will be sent to when one of the execute functions is called. """ return self._baseurl def execute(self): """ submit the query and return the results as a Results subclass instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return DALResults(self.execute_votable(), url=self.queryurl, session=self._session) def execute_raw(self): """ submit the query and return the raw response as a string. No exceptions are raised here because non-2xx responses might still contain payload. They can be raised later by calling ``raise_if_error`` """ f = self.execute_stream() out = None try: out = f.read() finally: f.close() return out @stream_decode_content def execute_stream(self, *, post=False): """ Submit the query and return the raw response as a file stream. No exceptions are raised here because non-2xx responses might still contain payload. They can be raised later by calling ``raise_if_error`` """ response = self.submit(post=post) try: response.raise_for_status() except requests.RequestException as ex: # save for later use self._ex = ex return response.raw def submit(self, *, post=False): """ does the actual request """ url = self.queryurl params = {k: v for k, v in self.items()} if post: response = self._session.post(url, data=params, stream=True, allow_redirects=True) else: response = self._session.get(url, params=params, stream=True, allow_redirects=True) return response def execute_votable(self, *, post=False): """ Submit the query and return the results as an AstroPy votable instance. As this is the level where qualified error messages are available, they are raised here instead of in the underlying execute_stream. Returns ------- astropy.io.votable.tree.VOTableFile an Astropy votable object Raises ------ DALServiceError for errors connecting to or communicating with the service DALFormatError for errors parsing the VOTable response See Also -------- astropy.io.votable DALServiceError DALFormatError DALQueryError """ try: return votableparse(self.execute_stream(post=post).read) except Exception as e: self.raise_if_error() raise DALFormatError(e, self.queryurl) def raise_if_error(self): """ Raise if there was an error on http level. """ if self._ex: e = self._ex raise DALServiceError.from_except(e, self.queryurl) @property def queryurl(self): """ The URL that encodes the current query. This is the URL that the execute functions will use if called next. """ return self.baseurl class DALResults: """ Results from a DAL query. It provides random access to records in the response. Alternatively, it can provide results via a Cursor (compliant with the Python Database API) or an iterable. """ @classmethod @stream_decode_content def _from_result_url(cls, result_url, session): return session.get(result_url, stream=True).raw @classmethod def from_result_url(cls, result_url, *, session=None): """ Create a result object from a url. Uses the optional session to make the request. """ session = use_session(session) return cls( votableparse(cls._from_result_url(result_url, session).read), url=result_url, session=session) def __init__(self, votable, *, url=None, session=None, client_set_maxrec=None): """ initialize the cursor. This constructor is not typically called by directly applications; rather an instance is obtained from calling a DALQuery's execute(). Parameters ---------- votable : astropy.io.votable.tree.VOTableFile the service response parsed into an astropy.io.votable.tree.VOTableFile instance. url : str the URL that produced the response session : object optional session to use for network requests client_set_maxrec: int the maximum number of records that were requested by the client. Raises ------ DALFormatError if the response VOTable does not contain a response table See Also -------- pyvo.dal.DALFormatError """ self._votable = votable self._url = url self._session = use_session(session) self._client_set_maxrec = client_set_maxrec self._status = self._findstatus(votable) if self._status[0].lower() not in ("ok", "overflow"): raise DALQueryError(self._status[1], self._status[0], url) if self._status[0].lower() == "overflow": self._handle_overflow_warning(client_set_maxrec) self._resultstable = self._findresultstable(votable) if not self._resultstable: raise DALFormatError( reason="VOTable response missing results table", url=url) self._fldnames = tuple( field.name for field in self._resultstable.fields) if not self._fldnames: raise DALFormatError( reason="response table missing column descriptions.", url=url) self._infos = self._findinfos(votable) def _handle_overflow_warning(self, client_set_maxrec=None): """ Handle overflow warning - can be overridden by subclasses. Default implementation issues a generic overflow warning. Subclasses can override this to customize or suppress warnings. Parameters ---------- client_set_maxrec : int, optional The maximum records requested by the client """ warn("Result set limited by user- or server-supplied MAXREC " "parameter.", category=DALOverflowWarning) def check_overflow_warning(self, client_set_maxrec=None): """ Check for overflow warnings and issue them if appropriate. It will check if the results were truncated due to server limits and issue a warning if the number of records returned is less than the maximum records requested by the user. If the results were truncated it distinguishes between expected truncation (where the user requested a maxrec) and unexpected truncation (where the user did not specify a maxrec). If the results were truncated it issues a warning. Parameters ---------- client_set_maxrec : int, optional The maximum records explicitly requested by the client """ if self._status[0].lower() == "overflow": maxrec_to_check = client_set_maxrec if client_set_maxrec is not None else self._client_set_maxrec if (maxrec_to_check is not None and len(self.resultstable.array) == maxrec_to_check): pass else: if maxrec_to_check is not None: warn(f"Results truncated at {len(self.resultstable.array)} records by service limits " f"(you requested maxrec={maxrec_to_check})", category=DALOverflowWarning) else: warn("Results truncated due to server limits. Consider " "setting a maxrec value.", category=DALOverflowWarning) def _findresultstable(self, votable): # this can be overridden to specialize for a particular DAL protocol res = self._findresultsresource(votable) if not res or len(res.tables) < 1: return None return res.tables[0] def _findresultsresource(self, votable): # this can be overridden to specialize for a particular DAL protocol if len(votable.resources) < 1: return None for res in votable.resources: if res.type.lower() == "results": return res return votable.resources[0] def _findstatus(self, votable): # this can be overridden to specialize for a particular DAL protocol # look first in the result resource res = self._findresultsresource(votable) if res: # should be a RESOURCE/INFO info = self._findstatusinfo(res.infos) if info: return (info.value, info.content) # if not there, check inside first table if len(res.tables) > 0: info = self._findstatusinfo(res.tables[0].infos) if info: return (info.value, info.content) # otherwise, look just below the root element info = self._findstatusinfo(votable.infos) if info: return (info.value, info.content) # assume it's okay return ("OK", "QUERY_STATUS not specified") def _findstatusinfo(self, infos): # this can be overridden to specialize for a particular DAL protocol status = None # return the last status to catch potential overflow or error sent # after the results. for info in infos: if info.name.lower() == 'query_status': status = info return status def _findinfos(self, votable): # this can be overridden to specialize for a particular DAL protocol infos = {} res = self._findresultsresource(votable) for info in res.infos: infos[info.name] = info.value for info in votable.infos: infos[info.name] = info.value return infos def __repr__(self): return f" 0: # find the last file written of the form, base-n.ext while n > 0 and not os.path.exists(mkpath(n)): n -= 1 if n > 0: n += 1 if n == 0: # never wrote a file of form, base-n.ext; try base.ext path = os.path.join(dir, f"{base}.{ext}") if not os.path.exists(path): return path n += 1 # find next available name while os.path.exists(mkpath(n)): n += 1 self._dsname_no = n return mkpath(n) def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ # abstract; specialized for the different service types return "dataset" def suggest_extension(self, *, default=None): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ # abstract; specialized for the different service types return default class Iter: def __init__(self, res): self.resultset = res self.pos = 0 def __iter__(self): return self def __next__(self): try: out = self.resultset.getrecord(self.pos) self.pos += 1 return out except IndexError: raise StopIteration() next = __next__ class Upload: """ This class represents a DALI Upload as described in http://www.ivoa.net/documents/DALI/20161101/PR-DALI-1.1-20161101.html#tth_sEc3.4.5 """ def __init__(self, name, content): """ Initialise the Upload object with the given parameters Parameters ---------- name : str Tablename for use in queries content : object If its a file-like object, a string pointing to a local file, a `DALResults` object or a astropy table, `is_inline` will be true and it will expose a file-like object under `fileobj` Otherwise it exposes a URI under `uri` """ try: self._is_file = os.path.isfile(content) except Exception: self._is_file = False self._is_fileobj = hasattr(content, "read") self._is_table = isinstance(content, Table) self._is_resultset = isinstance(content, DALResults) self._inline = any(( self._is_file, self._is_fileobj, self._is_table, self._is_resultset, )) self._name = name self._content = content @property def is_inline(self): """ True if the upload can be inlined """ return self._inline @property def name(self): return self._name def fileobj(self): """ A file-like object for a local resource Raises ------ ValueError if theres no valid local resource """ if not self.is_inline: raise ValueError( "Upload {name} doesn't refer to a local resource".format( name=self.name)) # astropy table if isinstance(self._content, Table): fileobj = BytesIO() self._content.write(output=fileobj, format="votable") fileobj.seek(0) return fileobj elif isinstance(self._content, (BytesIO, StringIO)): return self._content elif isinstance(self._content, DALResults): fileobj = BytesIO() table = self._content.to_table() table.write(output=fileobj, format="votable") fileobj.seek(0) return fileobj fileobj = open(self._content) return fileobj def uri(self): """ The URI pointing to the result """ # TODO: use a async job base class instead of hasattr for inspection if hasattr(self._content, "result_uri"): self._content.raise_if_error() uri = self._content.result_uri else: uri = str(self._content) return uri def query_part(self): """ The query part for use in DALI requests """ if self.is_inline: value = "{name},param:{name}" else: value = "{name},{uri}" return value.format(name=self.name, uri=self.uri()) class UploadList(list): """ This class extends the native python list with utility functions for upload handling """ @classmethod def fromdict(cls, dct): """ Constructs a upload list from a dictionary with table_name: content """ return cls(Upload(key, value) for key, value in dct.items()) def param(self): """ Returns a string suitable for use in UPLOAD parameters """ return ";".join(upload.query_part() for upload in self) _image_mt_re = re.compile(r'^image/(\w+)') _text_mt_re = re.compile(r'^text/(\w+)') _votable_mt_re = re.compile(r'^(\w+)/(x-)?votable(\+\w+)?') astropy-pyvo-b70558c/pyvo/dal/scs.py000066400000000000000000000452201510533647000174530ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching remote source and observation catalogs A Simple Cone Search (SCS) service allows a client to search for records in a source or observation catalog whose positions are within some minimum distance of a search position (i.e. within a specified "cone" on the sky). This module provides an interface for accessing such services. It is implemented as a specialization of the DAL Query interface. The ``search()`` function provides a simple interface to a service, returning an SCSResults instance as its results which represents the matching records from the catalog. The SCSResults supports access to and iterations over the individual records; these are provided as SCSRecord instances, which give easy access to key metadata in the response, including the ICRS position of the matched source or observation. This module also features the SCSQuery class that provides an interface for building up and remembering a query. The SCSService class can represent a specific service available at a URL endpoint. """ from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.units import Unit, Quantity from astropy.io.votable.tree import Field from astropy.table import Table from .query import DALResults, DALQuery, DALService, Record from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin __all__ = ["search", "SCSService", "SCSQuery", "SCSResults", "SCSRecord"] def search(url, pos, radius=1.0, *, verbosity=2, **keywords): """ submit a simple Cone Search query that requests objects or observations whose positions fall within some distance from a search position. Parameters ---------- url : str the base URL of the query service. pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, and 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service. DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SCSResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return SCSService(url).search(pos=pos, radius=radius, verbosity=verbosity, **keywords) class SCSService(DALService): """ a representation of a Cone Search service """ def __init__(self, baseurl, *, capability_description=None, session=None): """ instantiate a Cone Search service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, capability_description=capability_description, session=session) def _get_metadata(self): """ download the metadata resource """ if not hasattr(self, '_metadata'): query = self.create_query(pos=(0, 0), radius=0) metadata = query.execute_votable() setattr(self, '_metadata', metadata) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, '_metadata').description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search(self, pos, radius=1.0, *, verbosity=2, **keywords): """ submit a simple Cone Search query that requests objects or observations whose positions fall within some distance from a search position. Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SCSResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query(pos=pos, radius=radius, verbosity=verbosity, **keywords).execute() def create_query(self, pos=None, radius=None, *, verbosity=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSQuery the query instance See Also -------- SCSQuery """ return SCSQuery(self.baseurl, pos=pos, radius=radius, verbosity=verbosity, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SCSQuery(DALQuery): """ a class for preparing a query to a Cone Search service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, radius=None, *, verbosity=None, session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) if pos is not None: self.pos = pos if radius is not None: self.radius = radius if verbosity is not None: self.verbosity = verbosity self.update({key.upper(): value for key, value in keywords.items()}) @property def pos(self): """ the position of the center of the circular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["RA"] = pos.icrs.ra.deg self["DEC"] = pos.icrs.dec.deg @pos.deleter def pos(self): delattr(self, "_pos") del self["RA"] del self["DEC"] @property def radius(self): """ the radius of the circular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_radius", None) @radius.setter def radius(self, radius): setattr(self, "_radius", radius) if not isinstance(radius, Quantity): valerr = ValueError("Radius must be exactly one value") try: # assume degrees radius = radius * Unit("deg") except ValueError: raise valerr try: if len(radius): raise valerr except TypeError: pass # len 1 self["SR"] = radius.to(Unit("deg")).value @radius.deleter def radius(self): delattr(self, "_radius") del self["SR"] @property def verbosity(self): """ an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. """ return getattr(self, "_verbosity", None) @verbosity.setter def verbosity(self, verbosity): setattr(self, "_verbosity", verbosity) self["VERB"] = verbosity @verbosity.deleter def verbosity(self): delattr(self, "_verbosity") del self["VERB"] def execute(self): """ submit the query and return the results as a SCSResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SCSResults(self.execute_votable(), url=self.queryurl, session=self._session) class SCSResults(DatalinkResultsMixin, DALResults): """ The list of matching catalog records resulting from a catalog (SCS) query. Each record contains a set of metadata that describes a source or observation within the requested circular region (i.e. a "cone"). The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.scs.SCSRecord` instances) are typically accessed by iterating over an ``SCSResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SCSResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SCSResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.scs.SCSRecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def _findresultsresource(self, votable): if len(votable.resources) < 1: return None return votable.resources[0] def _findstatus(self, votable): # this is specialized according to the Conesearch standard # look first in the preferred location: just below the root VOTABLE info = self._findstatusinfo(votable.infos) if info: return (info.name, info.value) # look next in the result resource res = self._findresultsresource(votable) if res: # look for RESOURCE/INFO info = self._findstatusinfo(res.infos) if info: return (info.name, info.value) # if not there, check for a PARAM info = self._findstatusinfo(res.params) if info: return (info.name, info.value) # last resort: VOTABLE/DEFINITIONS/PARAM # NOT SUPPORTED BY astropy; parser has been configured to # raise W22 as exception instead. # assume it's okay return ("OK", "Successful Response") def _findstatusinfo(self, infos): # this can be overridden to specialize for a particular DAL protocol for info in infos: if info.name == "Error": return info def getrecord(self, index): """ return a representation of a conesearch result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has the following additional properties: id, ra, dec Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SCSRecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SCSRecord(self, index, session=self._session) class SCSRecord(DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an Cone Search (SCS) query, describing a matching source or observation. The commonly accessed metadata which are stadardized by the SCS protocol are available as attributes. All metadata, particularly non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def pos(self): """ the position of the object or observation described by this record. """ return SkyCoord( ra=self.getbyucd("POS_EQ_RA_MAIN"), dec=self.getbyucd("POS_EQ_DEC_MAIN"), unit="deg", frame="icrs") @property def id(self): """ return the identifying name of the object or observation described by this record. """ return self.getbyucd("ID_MAIN") astropy-pyvo-b70558c/pyvo/dal/sia.py000066400000000000000000001036001510533647000174340ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for images in a remote archive. A Simple Image Access (SIA) service allows a client to search for images in an archive whose field of view overlaps with a given rectangular region on the sky. The service responds to a search query with a table in which each row represents an image that is available for download. The columns provide metadata describing each image and one column in particular provides the image's download URL (also called the *access reference*, or *acref*). Some SIA services act as a cut-out service; in this case, the query result is a table of images whose field of view matches the requested region and which will be created when accessed via the download URL. This module provides an interface for accessing an SIA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SIAResults instance as its results which represents the matching images from the archive. The SIAResults supports access to and iterations over the individual records; these are provided as SIARecord instances, which give easy access to key metadata in the response, such as the position of the image's center, the image format, the size and shape of the image, and its download URL. The ``SIAService`` class can represent a specific service available at a URL endpoint. """ import re from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.time import Time from astropy.units import Quantity, Unit import numpy as np from .query import DALResults, DALQuery, DALService, Record from .mimetype import mime2extension from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from .. import samp __all__ = ["search", "SIAService", "SIAQuery", "SIAResults", "SIARecord"] def search( url, pos, size=1.0, *, format=None, intersect=None, verbosity=2, **keywords): """ submit a simple SIA query that requests images overlapping a given region Parameters ---------- url : str the base URL for the SIA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. (client-side default == 2) **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAResults a container holding a table of matching image records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ service = SIAService(url) return service.search(pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, **keywords) class SIAService(DALService): """ a representation of an SIA service """ def __init__(self, baseurl, *, capability_description=None, session=None): """ instantiate an SIA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, capability_description=capability_description, session=session) self._description = capability_description def _get_metadata(self): """ the metadata resource element """ if not hasattr(self, "_metadata"): query = self.create_query(format='metadata') metadata = query.execute_votable() setattr(self, "_metadata", metadata) try: setattr(self, "_metadata_resource", metadata.resources[0]) except IndexError: setattr(self, "_metadata_resource", None) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def params(self): """ the service parameters. """ self._get_metadata() try: return getattr(self, "_metadata_resource", None).params except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = getattr(self, '_metadata', None).get_first_table().fields try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search( self, pos, size=1.0, *, format=None, intersect=None, verbosity=2, **keywords): """ submit a SIA query to this service with the given parameters. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. (client-side default == 2) **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query( pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, **keywords).execute() def create_query( self, pos=None, size=None, *, format=None, intersect=None, verbosity=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAQuery the query instance See Also -------- SIAQuery """ return SIAQuery( self.baseurl, pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, session=self._session, **keywords) def describe(self): print(self.description) print() class SIAQuery(DALQuery): """ a class for preparing a query to an SIA service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, size=None, *, format=None, intersect=None, verbosity=None, session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SIA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. session : object optional session to use for network requests **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. """ super().__init__(baseurl, session=session, **keywords) if pos is not None: self.pos = pos if size is not None: self.size = size if format is not None: self.format = format if intersect is not None: self.intersect = intersect if verbosity is not None: self.verbosity = verbosity @property def pos(self): """ the position of the center of the rectangular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["POS"] = "{ra},{dec}".format( ra=pos.icrs.ra.deg, dec=pos.icrs.dec.deg) @pos.deleter def pos(self): delattr(self, "_pos") del self["POS"] @property def size(self): """ the size of the rectangular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_size", None) @size.setter def size(self, size): setattr(self, "_size", size) if not isinstance(size, Quantity): valerr = ValueError( 'Size must be either a single value or a sequence with two' 'values, expressing degrees' ) try: # assume degrees size = size * Unit("deg") except ValueError: raise valerr try: if len(size) > 2: raise valerr except TypeError: pass # len 1 try: self["SIZE"] = ",".join( str(deg) for deg in size.to(Unit("deg")).value) except TypeError: self["SIZE"] = str(size.to(Unit("deg")).value) @size.deleter def size(self): delattr(self, "_size") del self["SIZE"] @property def format(self): """ the image format(s) of interest. "all" (default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. """ return getattr(self, "_format", None) @format.setter def format(self, format_): setattr(self, "_format", format_) if not isinstance(format_, list): format_ = format_.split(',') normalized_formats = [] for user_input in format_: if user_input.upper() in ['ALL', 'METADATA', 'GRAPHIC', 'GRAPHIC-ALL']: normalized_formats.append(user_input.upper()) elif user_input.split('-')[0].upper() == 'GRAPHIC': normalized_formats.append(user_input.split('-')[0].upper()+"-"+user_input.split('-')[1]) else: normalized_formats.append(user_input) self["FORMAT"] = ",".join(normalized_formats) @format.deleter def format(self): delattr(self, "_format") del self["FORMAT"] @property def intersect(self): """ a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps with the search region CENTER select images whose center is within the search region ========= ====================================================== """ return getattr(self, "_intersect", None) @intersect.setter def intersect(self, intersect): setattr(self, "_intersect", intersect) self["INTERSECT"] = intersect.upper() @intersect.deleter def intersect(self): delattr(self, "_intersect") del self["INTERSECT"] @property def verbosity(self): """ an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. """ return getattr(self, "_verbosity", None) @verbosity.setter def verbosity(self, verbosity): setattr(self, "_verbosity", verbosity) self["VERB"] = verbosity @verbosity.deleter def verbosity(self): delattr(self, "_verbosity") del self["VERB"] def execute(self): """ submit the query and return the results as a SIAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SIAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SIAResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sia.SIARecord` instances) are typically accessed by iterating over an ``SIAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SIAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SIAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sia.SIARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sia result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SIARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SIARecord(self, index, session=self._session) class SIARecord(SodaRecordMixin, DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an image (SIA) search, describing an available image. The commonly accessed metadata which are stadardized by the SIA protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ def getdataformat(self): """ return the mimetype of the dataset described by this record. """ return self.getbyucd("VOX:Image_Format", decode=True) @property def pos(self): """ the position of the object or observation described by this record. """ return SkyCoord( ra=self.getbyucd("POS_EQ_RA_MAIN"), dec=self.getbyucd("POS_EQ_DEC_MAIN"), unit="deg", frame="icrs") # Image Metadata @property def title(self): """ the title of the image """ return self.getbyucd("VOX:Image_Title", decode=True) @property def instr(self): """ the name of the instrument (or instruments) that produced the data that went into this image. """ return self.getbyucd("INST_ID", decode=True) @property def dateobs(self): """ the modified Julian date (MJD) of the mid-point of the observational data that went into the image, as an astropy.time.Time instance """ dateobs = self.getbyucd("VOX:Image_MJDateObs") try: if not dateobs or np.isnan(dateobs): return None except TypeError: # np.isnan can only check floats. If can't check for nan, pass it along pass return Time(dateobs, format="mjd") @property def naxes(self): """ the number of axes in this image. """ return self.getbyucd("VOX:Image_Naxes") @property def naxis(self): """ the lengths of the sides along each axis, in pix, as a astropy Quantity pix """ return self.getbyucd("VOX:Image_Naxis") * Unit("pix") @property def scale(self): """ the scale of the pixels in each image axis, in degrees/pixel, as a astropy Quantity deg / pix """ return self.getbyucd("VOX:Image_Scale") * (Unit("deg") / Unit("pix")) @property def format(self): """ the format of the image """ return self.getbyucd("VOX:Image_Format", decode=True) # Coordinate System Metadata @property def coord_frame(self): """ the coordinate system reference frame, one of the following: "ICRS", "FK5", "FK4", "ECL", "GAL", and "SGAL". """ return self.getbyucd("VOX:STC_CoordRefFrame", decode=True) @property def coord_equinox(self): """ the equinox of the used coordinate system """ return self.getbyucd("VOX:STC_CoordEquinox") @property def coord_projection(self): """ the celestial projection (TAN / ARC / SIN / etc.) """ return self.getbyucd("VOX:WCS_CoordProjection", decode=True) @property def coord_refpixel(self): """ the image pixel coordinates of the WCS reference pixel """ return self.getbyucd("VOX:WCS_CoordRefPixel") @property def coord_refvalue(self): """ the world coordinates of the WCS reference pixel. """ return self.getbyucd("VOX:WCS_CoordRefValue") @property def cdmatrix(self): """ the WCS CD matrix defining the scale and rotation (among other things) of the image. ordered as CD[i,j] = [0,0], [0,1], [1,0], [1,1]. """ return self.getbyucd("VOX:WCS_CDMatrix").reshape((2, 2)) # Spectral Bandpass Metadata @property def bandpass_id(self): """ the bandpass by name (e.g., "V", "SDSS_U", "K", "K-Band", etc.) """ return self.getbyucd("VOX:BandPass_ID", decode=True) @property def bandpass_unit(self): """ the astropy unit used to represent spectral values. """ sia_unit = self.getbyucd("VOX:BandPass_Unit", decode=True) # the standard says this should be `"meters", "hertz", and "keV"', # which in practice everyone has ignored. Still, let's # translate the standard terms. Somewhat more dangerously, # we replace a missing unit with metres; in theory, we should # reject anything but the three terms above, but then 99% # of SIA services would break. And without the assumption of # a metre default, 20% of 2024 SIA1 services would have unusable # bandpasses. astropy_unit = { None: "m", "": "m", "meters": "m", "hertz": "Hz"}.get(sia_unit, sia_unit) return Unit(astropy_unit) @property def bandpass_refvalue(self): """ the characteristic (reference) wavelength, frequency or energy for the bandpass model, as an astropy Quantity of bandpass_unit """ value = self.getbyucd("VOX:BandPass_RefValue") if value is not None and self.bandpass_unit: return Quantity(value, self.bandpass_unit) @property def bandpass_hilimit(self): """ the upper limit of the bandpass as astropy Quantity in bandpass_unit """ value = self.getbyucd("VOX:BandPass_HiLimit") if value is not None and self.bandpass_unit: return Quantity(value, self.bandpass_unit) @property def bandpass_lolimit(self): """ the lower limit of the bandpass as astropy Quantity in bandpass_unit """ value = self.getbyucd("VOX:BandPass_LoLimit") if value is not None and self.bandpass_unit: return Quantity(value, self.bandpass_unit) # Processig Metadata @property def pixflags(self): """ the type of processing done by the image service to produce an output image pixel a string of one or more of the following values: * C -- The image pixels were copied from a source image without change, as when an atlas image or cutout is returned. * F -- The image pixels were computed by resampling an existing image, e.g., to rescale or reproject the data, and were filtered by an interpolator. * X -- The image pixels were computed by the service directly from a primary data set hence were not filtered by an interpolator. * Z -- The image pixels contain valid flux (intensity) values, e.g., if the pixels were resampled with a flux-preserving interpolator. * V -- The image pixels contain some unspecified visualization of the data, hence are suitable for display but not for numerical analysis. """ return self.getbyucd("VOX:Image_PixFlags", decode=True) # Access Metadata @property def acref(self): """ the URL that can be used to retrieve the image """ return self.getbyucd("VOX:Image_AccessReference", decode=True) @property def acref_ttl(self): """ the minimum time to live in seconds of the access reference """ return self.getbyucd("VOX:Image_AccessRefTTL") @property def filesize(self): """ the (estimated) size of the image in bytes """ return self.getbyucd("VOX:Image_FileSize") def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. None is returned if no such column exists. """ if self.acref is not None: return self.acref else: return super().getdataurl() def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ out = self.title if isinstance(out, bytes): out = out.decode('utf-8') if not out: out = "image" else: out = re.sub(r'\s+', '_', out.strip()) return out def suggest_extension(self, *, default='dat'): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. """ return mime2extension(self.format, default) def broadcast_samp(self, *, client_name=None): """ Broadcast the image to ``client_name`` via SAMP """ with samp.connection() as conn: samp.send_image_to( conn, self.getdataurl(), client_name, name=self.suggest_dataset_basename()) astropy-pyvo-b70558c/pyvo/dal/sia2.py000066400000000000000000001165141510533647000175260ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for images in a remote archive. A Simple Image Access v2 (SIA2) service allows a client to search for images based on a number of criteria/parameters. The results are represented in ``pyvo.dam.ObsCoreMetadata`` format. The ``SIA2Service`` class can represent a specific service available at a URL endpoint. """ import warnings import numpy as np from astropy import units as u from astropy.time import Time from astropy.utils.decorators import deprecated from astropy.utils.exceptions import AstropyDeprecationWarning from .query import DALResults, DALQuery, DALService, Record from .adhoc import DatalinkResultsMixin, AxisParamMixin, SodaRecordMixin, DatalinkRecordMixin from .exceptions import DALServiceError from .params import IntervalQueryParam, StrQueryParam, EnumQueryParam from .vosi import AvailabilityMixin, CapabilityMixin from ..dam import ObsCoreMetadata, CALIBRATION_LEVELS __all__ = ["search", "SIA2Service", "SIA2Query", "SIA2Results", "ObsCoreRecord"] SIA2_STANDARD_ID = 'ivo://ivoa.net/std/SIA#query-2.0' SIA2_PARAMETERS_DESC = """ pos : single or list of tuples angle units (default: deg) the positional region(s) to be searched for data. Each region can be expressed as a tuple representing a CIRCLE, RANGE or POLYGON as follows: (ra, dec, radius) - for CIRCLE. (angle units - defaults to) (long1, long2, lat1, lat2) - for RANGE (angle units required) (ra, dec, ra, dec, ra, dec ... ) ra/dec points for POLYGON all in angle units band : scalar, tuple(interval) or list of tuples (spectral units (default: meter) the energy interval(s) to be searched for data. time : single or list of `~astropy.time.Time` or compatible strings the time interval(s) to be searched for data. pol : single or list of str from ``pyvo.dam.obscore.POLARIZATION_STATES`` the polarization state(s) to be searched for data. field_of_view : single or list of tuples angle units (default arcsec) the range(s) of field of view (size) to be searched for data spatial_resolution : single or list of tuples angle units required the range(s) of spatial resolution to be searched for data spectral_resolving_power : single or list of tuples the range(s) of spectral resolving power to be searched for data exptime : single or list of tuples time units (default: second) the range(s) of exposure times to be searched for data timeres : single of list of tuples time units (default: second) the range(s) of temporal resolution to be searched for data publisher_did : single or list of str specifies the unique identifier of dataset(s). It is global because it must include information regarding the publisher (obs_publisher_did in ObsCore) collection : single or list of str name of the collection that the data belongs to facility : single or list of str specifies the name of the facility (usually telescope) where the data was acquired. instrument : single or list of str specifies the name of the instrument with which the data was acquired. data_type : 'image'|'cube' specifies the type of the data calib_level : single or list from enum ``pyvo.dam.obscore.CALIBRATION_LEVELS`` specifies the calibration level of the data. Can be a single value or a list of values target_name : single or list of str specifies the name of the target (e.g. the intention of the original science program or observation) res_format : single or list of strings specifies response format(s). max_records : int allows the client to limit the number or records in the response **kwargs : custom query parameters single or a list of values (or tuples for intervals) custom query parameters that a specific service accepts. The values of the parameters need to follow the SIA2 format and represent the appropriate quantities (where applicable). """ def __getattr__(name): if name == 'SIA_PARAMETERS_DESC': warnings.warn("The name SIA_PARAMETERS_DESC is deprecated in v1.5 for SIA v2 services, " "use SIA2_PARAMETERS_DESC instead.", AstropyDeprecationWarning) return SIA2_PARAMETERS_DESC raise AttributeError(f"module {__name__!r} has no attribute {name!r}") def search(url, pos=None, *, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, session=None, **kwargs): """ submit a simple SIA query to a SIA2 compatible service Parameters ---------- url : str url of the SIA service (base or endpoint) _SIA2_PARAMETERS """ service = SIA2Service(url, session=session) return service.search(pos=pos, band=band, time=time, pol=pol, field_of_view=field_of_view, spatial_resolution=spatial_resolution, spectral_resolving_power=spectral_resolving_power, exptime=exptime, timeres=timeres, publisher_did=publisher_did, facility=facility, collection=collection, instrument=instrument, data_type=data_type, calib_level=calib_level, target_name=target_name, res_format=res_format, maxrec=maxrec, **kwargs) search.__doc__ = search.__doc__.replace('_SIA2_PARAMETERS', SIA2_PARAMETERS_DESC) def _tolist(value): # return value as a list - is there something in Python to do that? if not value: return [] if isinstance(value, list): return value return [value] class SIA2Service(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of an SIA2 service Note that within pyVO, SIA2 services are associated with the (non-existing) standard id ivo://ivoa.net/std/sia2 rather than ivo://ivoa.net/std/sia#query-2.0 as in the standard; users should generally not notice that, though. """ def __init__(self, baseurl, *, capability_description=None, session=None, check_baseurl=True): """ instantiate an SIA2 service Parameters ---------- url : str url - URL of the SIA2service (base or query endpoint) session : object optional session to use for network requests check_baseurl : bool True - use the capabilities end point of the service to get the query end point, False - baseurl is the query end point """ super().__init__(baseurl, capability_description=capability_description, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such usecase for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) self.query_ep = baseurl.strip('&') # default service end point if check_baseurl: for cap in self.capabilities: # assumes that the access URL is the same regardless of the # authentication method except BasicAA which is not supported # in pyvo. So pick any access url as long as it's not if cap.standardid and cap.standardid.lower() == SIA2_STANDARD_ID.lower(): for interface in cap.interfaces: if interface.accessurls and \ not (len(interface.securitymethods) == 1 and interface.securitymethods[0].standardid == 'ivo://ivoa.net/sso#BasicAA'): self.query_ep = interface.accessurls[0].content break else: continue break else: raise DALServiceError("This URL does not seem to correspond to an SIA2 service.") def search(self, pos=None, *, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, **kwargs): """ Performs a SIA2 search against a SIA2 service See Also -------- pyvo.dal.sia2.SIA2Query """ return SIA2Query(self.query_ep, pos=pos, band=band, time=time, pol=pol, field_of_view=field_of_view, spatial_resolution=spatial_resolution, spectral_resolving_power=spectral_resolving_power, exptime=exptime, timeres=timeres, publisher_did=publisher_did, facility=facility, collection=collection, instrument=instrument, data_type=data_type, calib_level=calib_level, target_name=target_name, res_format=res_format, maxrec=maxrec, session=self._session, **kwargs).execute() class SIA2Query(DALQuery, AxisParamMixin): """ a class very similar to :py:attr:`~pyvo.dal.SIAQuery` class but used to interact with SIA2 services. """ def __init__(self, url, pos=None, *, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, session=None, **kwargs): """ initialize the query object with a url and the given parameters Note: The majority of the attributes represent constraints used to query the SIA2 service and are represented through lists. Multiple value attributes are OR-ed in the query, however the values of different attributes are AND-ed. Intervals are represented with tuples and open-ended intervals should be expressed with float("-inf") or float("inf"). Eg. For all values less than or equal to 600 use (float(-inf), 600) Additional attribute constraints can be specified (or removed) after this object has been created using the *.add and *_del methods. Parameters ---------- url : url where to send the query request to _SIA2_PARAMETERS session : object optional session to use for network requests Returns ------- SIA2Results a container holding a table of matching image records. Records are represented in IVOA ObsCore format Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIA2Results pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ super().__init__(url, session=session) for pp in _tolist(pos): self.pos.add(pp) for bb in _tolist(band): self.band.add(bb) for tt in _tolist(time): self.time.add(tt) for pp in _tolist(pol): self.pol.add(pp) for ff in _tolist(field_of_view): self.field_of_view.add(ff) for sp in _tolist(spatial_resolution): self.spatial_resolution.add(sp) for sr in _tolist(spectral_resolving_power): self.spectral_resolving_power.add(sr) for et in _tolist(exptime): self.exptime.add(et) for tr in _tolist(timeres): self.timeres.add(tr) for ii in _tolist(publisher_did): self.publisher_did.add(ii) for ff in _tolist(facility): self.facility.add(ff) for col in _tolist(collection): self.collection.add(col) for inst in _tolist(instrument): self.instrument.add(inst) for dt in _tolist(data_type): self.data_type.add(dt) for cal in _tolist(calib_level): self.calib_level.add(cal) for tt in _tolist(target_name): self.target_name.add(tt) for rf in _tolist(res_format): self.res_format.add(rf) for name, value in kwargs.items(): custom_arg = [] for kw in _tolist(value): if isinstance(kw, tuple): val = f'{kw[0]} {kw[1]}' else: val = str(kw) custom_arg.append(val) self[name] = custom_arg self.maxrec = maxrec __init__.__doc__ = \ __init__.__doc__.replace('_SIA2_PARAMETERS', SIA2_PARAMETERS_DESC) @property def field_of_view(self): if not hasattr(self, '_fov'): self._fov = IntervalQueryParam(u.deg) self['FOV'] = self._fov.dal return self._fov @property def spatial_resolution(self): if not hasattr(self, '_spatres'): self._spatres = IntervalQueryParam(u.arcsec) self['SPATRES'] = self._spatres.dal return self._spatres @property def spectral_resolving_power(self): if not hasattr(self, '_specrp'): self._specrp = IntervalQueryParam() self['SPECRP'] = self._specrp.dal return self._specrp @property def exptime(self): if not hasattr(self, '_exptime'): self._exptime = IntervalQueryParam(u.second) self['EXPTIME'] = self._exptime.dal return self._exptime @property def timeres(self): if not hasattr(self, '_timeres'): self._timeres = IntervalQueryParam(u.second) self['TIMERES'] = self._timeres.dal return self._timeres @property def publisher_did(self): if not hasattr(self, '_publisher_did'): self._publisher_did = StrQueryParam() self['ID'] = self._publisher_did.dal return self._publisher_did @property def facility(self): if not hasattr(self, '_facility'): self._facility = StrQueryParam() self['FACILITY'] = self._facility.dal return self._facility @property def collection(self): if not hasattr(self, '_collection'): self._collection = StrQueryParam() self['COLLECTION'] = self._collection.dal return self._collection @property def instrument(self): if not hasattr(self, '_instrument'): self._instrument = StrQueryParam() self['INSTRUMENT'] = self._instrument.dal return self._instrument @property def data_type(self): if not hasattr(self, '_data_type'): self._data_type = StrQueryParam() self['DPTYPE'] = self._data_type.dal return self._data_type @property def calib_level(self): if not hasattr(self, '_cal'): self._cal = EnumQueryParam(CALIBRATION_LEVELS) self['CALIB'] = self._cal.dal return self._cal @property def target_name(self): if not hasattr(self, '_target'): self._target_name = StrQueryParam() self['TARGET'] = self._target_name.dal return self._target_name @property def res_format(self): if not hasattr(self, '_res_format'): self._res_format = StrQueryParam() self['FORMAT'] = self._res_format.dal return self._res_format @property def maxrec(self): return self._maxrec @maxrec.setter def maxrec(self, val): if not isinstance(val, int): if not val: return raise ValueError(f'maxrec {val} must be non-negative int') self._maxrec = val self['MAXREC'] = str(val) def execute(self): """ submit the query and return the results as a SIA2Results instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SIA2Results(self.execute_votable(), url=self.queryurl, session=self._session) class SIA2Results(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA2) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instances) are typically accessed by iterating over an ``SIA2Results`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SIA2Results`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SIA2Results`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a SIA2 result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- ObsCoreMetadataRecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return ObsCoreRecord(self, index, session=self._session) class ObsCoreRecord(SodaRecordMixin, DatalinkRecordMixin, Record, ObsCoreMetadata): """ a dictionary-like container for data in a record from the results of an image (SIA2) search, describing an available image in ObsCore format. The commonly accessed metadata which are stadardized by the SIA2 protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are also acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ # OBSERVATION INFO @property def dataproduct_type(self): """ Data product (file content) primary type. This is coded as a string that conveys a general idea of the content and organization of a dataset. """ return self.get('dataproduct_type', decode=True) @property def dataproduct_subtype(self): """ Data product more specific type """ return self.get('dataproduct_subtype', decode=True, default=None) @property def calib_level(self): """ Calibration level of the observation: in {0, 1, 2, 3, 4} """ return self.get('calib_level') # TARGET INFO @property def target_name(self): """ The target_name attribute contains the name of the target of the observation, if any. This is typically the name of an astronomical object, but could be the name of a survey field. The target name is most useful for output, to identify the target of an observation to the user. In queries it is generally better to refer to astronomical objects by position, using a name resolver to convert the target name into a coordinate (when possible). """ return self.get('target_name', decode=True) @property def target_class(self): """ This field indicates the type of object that was pointed for this observation. It is a string with possible values defined in a special vocabulary set to be defined: list of object classes (or types) used by the SIMBAD database, NED or defined in another IVOA vocabulary. """ return self.get('target_class', decode=True, default=None) # DATA DESCRIPTION @property def obs_id(self): """ Collection specific internal ID given by the ObsTAP service """ return self.get('obs_id', decode=True) @property def obs_title(self): """ Brief description of dataset in free format """ return self.get('obs_title', decode=True, default=None) @property def obs_collection(self): """ The name of the collection (DataID.Collection) identifies the data collection to which the data product belongs. A data collection can be any collection of datasets which are alike in some fashion. Typical data collections might be all the data from a particular telescope, instrument, or survey. The value is either the registered shortname for the data collection, the full registered IVOA identifier for the collection, or a data provider defined short name for the collection. Examples: HST/WFPC2, VLT/FORS2, CHANDRA/ACIS-S, etc. """ return self.get('obs_collection', decode=True) @property def obs_create_date(self): """ Date when the dataset was created """ cd = self.get('obs_create_date', default=None) return cd if not cd else Time(cd) @property def obs_creator_name(self): """ The name of the institution or entity which created the dataset. """ return self.get('obs_creator_name', decode=True, default=None) @property def obs_creator_did(self): """ IVOA dataset identifier given by its creator. """ return self.get('obs_creator_did', decode=True, default=None) # CURATION INFORMATION @property def obs_release_date(self): """ Observation release date """ rt = self.get('obs_release_date', default=None, decode=True) return rt if not rt else Time(rt) @property def obs_publisher_did(self): """ ID for the Dataset assigned by the publisher. Note that data from a source (creator_did) can be published by multiple publishers and have assigned multiple publisher data IDs. """ return self.get('obs_publisher_did', decode=True) @property def publisher_id(self): """ IVOA-ID for the Publisher. It will also be globally unique since each publisher has a unique registered publisher ID """ return self.get('publisher_id', decode=True, default=None) @property def bib_reference(self): """ URL or bibcode for documentation. This is a forward link to major publications which reference the dataset. """ return self.get('bib_reference', decode=True, default=None) @property def data_rights(self): """ This parameter allows mentioning the availability of a dataset. Possible values are: public, secure, or proprietary. """ return self.get('data_rights', decode=True, default=None) # ACCESS INFORMATION @property def access_url(self): """ The access_url column contains a URL that can be used to download the data product (as a file of some sort). Access URLs are not guaranteed to remain valid and unchanged indefinitely. To access a specific data product after a period of time (e.g., days or weeks) a query should be performed to obtain a fresh access URL. """ return self.get('access_url', decode=True) @property def access_format(self): """ Content format of the dataset. The value of access_format should be a MIME type, either a standard MIME type, an extended MIME type from the above table, or a new custom MIME type defined by the data provider. """ return self.get('access_format', decode=True) @property def access_estsize(self): """ The approximate size (in kilobytes) of the file available via the access_url. This is used only to gain some idea of the size of a data product before downloading it, hence only an approximate value is required. Provision of dataset size estimates is important whenever it is possible that datasets can be very large. """ return self.get('access_estsize') * 1000 * u.byte # SPATIAL CHARACTERISATION @property def s_ra(self): """ ICRS Right Ascension of the center of the observation """ return self.get('s_ra') * u.deg @property def s_dec(self): """ CRS Declination of the center of the observation """ return self.get('s_dec') * u.deg @property def s_fov(self): """ Approximate size of the covered region as the diameter of a containing circle. For most data products the value given should be large enough to include the entire area of the observation; coverage within the bounded region need not be complete, for example if the specified radius encompasses a rotated rectangular region. For observations which do not have a well-defined boundary, e.g. radio or high energy observations, a characteristic value should be given. The radius attribute provides a simple way to characterize and use (e.g. for discovery computations) the approximate spatial coverage of a data product. The spatial coverage of a data product can be more precisely specified using the region attribute. """ return self.get('s_fov') * u.deg @property def s_region(self): """ Sky region covered by the data product (expressed in ICRS frame). It can be used to precisely specify the covered spatial region of a data product. It is often an exact, or almost exact, representation of the illumination region of a given observation defined in a standard way by the concept of Support in the Characterisation data model. """ return self.get('s_region', decode=True) @property def s_resolution(self): """ Spatial resolution of data specifies a reference value chosen by the data provider for the estimated spatial resolution of the data product in arcseconds. This refers to the smallest spatial feature in the observed signal that can be resolved. In cases where the spatial resolution varies across the field the best spatial resolution (smallest resolvable spatial feature) should be specified. In cases where the spatial frequency sampling of an observation is complex (e.g., interferometry) a typical value for spatial resolution estimate should be given; additional characterisation may be necessary to fully specify the spatial characteristics of the data. """ return self.get('s_resolution') * u.arcsec @property def s_xel1(self): """ Number of elements along the first spatial axis """ return self.get('s_xel1') @property def s_xel2(self): """ Number of elements along the second spatial axis """ return self.get('s_xel2') @property def s_ucd(self): """ UCD for the nature of the spatial axis (pos or u,v data) """ return self.get('s_ucd', decode=True, default=None) @property def s_unit(self): """ Unit used for spatial axis """ return self.get('s_unit', decode=True, default=None) @property def s_resolution_min(self): """ Resolution min value on spatial axis (FHWM of PSF) """ rmin = self.get('s_resolution_min', default=None) return rmin if not rmin else rmin * u.arcsec @property def s_resolution_max(self): """ Resolution max value on spatial axis (FHWM of PSF) """ rmax = self.get('s_resolution_max', default=None) return rmax if not rmax else rmax * u.arcsec @property def s_calib_status(self): """ A string to encode the calibration status along the spatial axis (astrometry). Possible values could be {uncalibrated, raw, calibrated} """ return self.get('s_calib_status', decode=True, default=None) @property def s_stat_error(self): """ This parameter gives an estimate of the astrometric statistical error after the astrometric calibration phase. """ return self.get('s_stat_error', decode=True, default=None) @property def s_pixel_scale(self): """ This corresponds to the sampling precision of the data along the spatial axis. It is stored as a real number corresponding to the spatial sampling period, i.e., the distance in world coordinates system units between two pixel centers. It may contain two values if the pixels are rectangular. """ return self.get('s_pixel_scale', decode=True, default=None) # TIME CHARACTERISATION @property def t_xel(self): """ Number of elements along the time axis """ return self.get('t_xel') @property def t_ref_pos(self): """ Time Axis Reference Position as defined in STC REC, Section 4.4.1.1.1 """ return self.get('t_ref_pos', decode=True, default=None) @property def t_min(self): """ The start time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, min time must be the minimum of the start times. ``None`` is used for NaN response values. """ t_min = self.get('t_min') if np.isfinite(t_min): return Time(t_min, format='mjd') else: return None @property def t_max(self): """ The stop time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, t_max must be the maximum of the stop times. ``None`` is used for NaN response values. """ t_max = self.get('t_max') if np.isfinite(t_max): return Time(t_max, format='mjd') else: return None @property def t_exptime(self): """ Total exposure time. For simple exposures, this is just the time_bounds size expressed in seconds. For data where the detector is not active at all times (e.g. data products made by combining exposures taken at different times), the t_exptime will be smaller than the time_bounds interval. For data where the xptime is not constant over the entire data product, the median exposure time per pixel is a good way to characterize the typical value. In some cases, exptime is generally used as an indicator of the relative sensitivity (depth) within a single data collection (e.g. obs_collection); data providers should supply a suitable relative value when it is not feasible to define or compute the true exposure time. In case of targeted observations, on the contrary the exposure time is often adjusted to achieve similar signal to noise ratio for different targets. """ return self.get('t_exptime') * u.second @property def t_resolution(self): """ Estimated or average value of the temporal resolution. """ return self.get('t_resolution') * u.second @property def t_calib_status(self): """ Type of time coordinate calibration. Possible values are principally {uncalibrated, calibrated, raw, relative}. This may be extended for specific time domain collections. """ return self.get('t_calib_status', decode=True, default=None) @property def t_stat_error(self): """ Time coord statistical error on the time measurements in seconds """ ter = self.get('t_stat_error', default=None) return ter if not ter else ter * u.second # SPECTRAL CHARACTERISATION @property def em_xel(self): """ Number of elements along the spectral axis """ return self.get('em_xel') @property def em_ucd(self): """ Nature of the spectral axis """ return self.get('em_ucd', decode=True, default=None) @property def em_unit(self): """ Units along the spectral axis """ return self.get('em_unit', decode=True, default=None) @property def em_calib_status(self): """ This attribute of the spectral axis indicates the status of the data in terms of spectral calibration. Possible values are defined in the Characterisation Data Model and belong to {uncalibrated , calibrated, relative, absolute}. """ return self.get('em_calib_status', decode=True, default=None) @property def em_min(self): """ Minimum of the spectral interval covered by the observation """ return self.get('em_min') * u.meter @property def em_max(self): """ Maximum of the spectral interval covered by the observation """ return self.get('em_max') * u.meter @property def em_res_power(self): """ Average estimation for the spectral resolution power stored as a double value, with no unit. """ return self.get("em_res_power") @property def em_res_power_min(self): """ Resolving power min value on spectral axis """ return self.get('em_res_power_min', None) @property def em_res_power_max(self): """ Resolving power max value on spectral axis """ return self.get('em_res_power_max', None) @property def em_resolution(self): """ A mean estimate of the resolution, e.g. Full Width at Half Maximum (FWHM) of the Line Spread Function (or LSF). This can be used for narrow range spectra whereas in the majority of cases, the resolution power is preferable due to the LSF variation along the spectral axis. """ if 'em_resolution' in self.keys(): return self.get('em_resolution') * u.meter return None @property def em_stat_error(self): """ Spectral coord statistical error (accuracy along the spectral axis) """ if 'em_stat_error' in self.keys(): return self.get('em_stat_error') * u.meter return None # OBSERVABLE AXIS @property def o_ucd(self): """ Nature of the observable axis within the data product """ return self.get('o_ucd', decode=True) @property def o_unit(self): """ Units along the observable axis """ return self.get('o_unit', decode=True, default=None) @property def o_calib_status(self): """ Type of calibration applied on the Flux observed (or other observable quantity). """ return self.get('o_calib_status', decode=True, default=None) @property def o_stat_error(self): """ Statistical error on the Observable axis. Note: the return value has the units defined in unit """ return self.get('o_stat_error', decode=True, default=None) # POLARIZATION CHARACTERISATION @property def pol_xel(self): """ Number of different polarization states present in the data. The default value is 0, indicating that polarization was not explicitly observed. Corresponding values are stored in the ``pol`` property """ return self.get('pol_xel') @property def pol_states(self): """ List of polarization states present in the data file. Possible values are: {I Q U V RR LL RL LR XX YY XY YX POLI POLA}. Values in the set are separated by the '/' character. A leading / character must start the list and a trailing / character must end it. It should be ordered following the above list, compatible with the FITS list table for polarization definition. """ return self.get('pol_states', decode=True, default=None) # PROVENANCE @property def instrument_name(self): """ The name of the instrument used for the acquisition of the data """ return self.get('instrument_name', decode=True) @property def facility_name(self): """ Name of the facility or observatory used to collect the data """ return self.get('facility_name', decode=True, default=None) @property def proposal_id(self): """ Identifier of proposal to which observation belongs """ return self.get('proposal_id', default=None, decode=True) @deprecated("1.5", alternative="SIA2Service") class SIAService(SIA2Service): pass @deprecated("1.5", alternative="SIA2Query") class SIAQuery(SIA2Query): pass @deprecated("1.5", alternative="SIA2Results") class SIAResults(SIA2Results): pass astropy-pyvo-b70558c/pyvo/dal/sla.py000066400000000000000000000370131510533647000174430ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for spectral line metadata in a remote database. A Simple Line Access (SLA) service allows a client to search for metadata describing atomic and molecular transitions that can result in spectral line emission and absorption. The service responds to a search query with a table in which each row represents a transition that matches the query constraints. The columns provide the metadata describing the transition. This module provides an interface for accessing an SLA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SLAResults instance as its results which represents the matching imagess from the archive. The SLAResults supports access to and iterations over the individual records; these are provided as SLARecord instances, which give easy access to key metadata in the response, such as the transition title. The SLAService class can represent a specific service available at a URL endpoint. """ from pyvo.io.vosi.vodataservice import TableParam from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Field from astropy.table import Table from .query import DALResults, DALQuery, DALService, Record __all__ = ["search", "SLAService", "SLAQuery", "SLAResults", "SLARecord"] def search(baseurl, wavelength, **keywords): """ submit a simple SLA query that requests spectral lines within a wavelength range Parameters ---------- baseurl : str the base URL for the SLA service wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAResults a container holding a table of matching spectral lines Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. """ service = SLAService(baseurl) return service.search(wavelength, **keywords) class SLAService(DALService): """ a representation of an spectral line catalog (SLA) service """ def __init__(self, baseurl, *, capability_description=None, session=None): """ instantiate an SLA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, capability_description=capability_description, session=session) def _get_metadata(self): """ download the metadata resource """ if not hasattr(self, "_metadata"): query = self.create_query(request='getCapabilities') metadata = query.execute_votable() setattr(self, "_metadata", metadata) @property def description(self): """ the service description. If this is not provided during instantiation, this method will download a sample from the service and read the description in the sample's metadata instead. """ if self._description is not None: return self._description self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search(self, wavelength, **keywords): """ submit a simple SLA query to this service with the given constraints. This method is provided for a simple but typical SLA queries. For more complex queries, one should create an SLAQuery object via create_query() Parameters ---------- wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAResults a container holding a table of matching spectral lines Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SLAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query(wavelength, **keywords).execute() def create_query(self, wavelength=None, *, request="queryData", **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAQuery the query instance See Also -------- SLAQuery """ return SLAQuery(baseurl=self.baseurl, wavelength=wavelength, request=request, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SLAQuery(DALQuery): """ a class for preparing a query to an SLA service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via the dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, wavelength=None, *, request="queryData", session=None): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SLA service wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) if wavelength is not None: self.wavelength = wavelength self.request = request @property def wavelength(self): """ the frequency/wavelength range the observations belong to. """ return getattr(self, "_wavelength", None) @wavelength.setter def wavelength(self, wavelength): setattr(self, "_wavelength", wavelength) if not isinstance(wavelength, Quantity): valerr = ValueError( 'Wavelength range must be a sequence with exactly two values', 'expressing a frequency or wavelength range') try: # assume meters wavelength = wavelength * Unit("meter") except ValueError: raise valerr try: if len(wavelength) != 2: raise valerr except TypeError: raise valerr # transform to meters wavelength = wavelength.to( Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort it # to have the right order again wavelength.sort() self["WAVELENGTH"] = "{start}/{end}".format( start=wavelength.value[0], end=wavelength.value[1]) @wavelength.deleter def wavelength(self): delattr(self, "_wavelength") del self["WAVELENGTH"] @property def request(self): """ the type of service operation which is being performed """ return getattr(self, "_request", None) @request.setter def request(self, val): setattr(self, "_request", val) self["REQUEST"] = val @request.deleter def request(self): delattr(self, "_request") del self["REQUEST"] def execute(self): """ submit the query and return the results as a SLAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SLAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SLAResults(DALResults): """ The list of matching spectral lines resulting from a spectal line catalog (SLA) query. Each record contains a set of metadata that describes a source or observation within the requested circular region (i.e. a "cone"). The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sla.SLARecord` instances) are typically accessed by iterating over an ``SLAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SLAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SLAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sla.SLARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sla result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SLARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SLARecord(self, index, session=self._session) class SLARecord(Record): """ a dictionary-like container for data in a record from the results of an spectral line (SLA) query, describing a spectral line transition. The commonly accessed metadata which are stadardized by the SLA protocol are available as attributes. All metadata, particularly non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def title(self): """ a title/small description of the line transition """ return self.getbyutype("ssldm:Line.title", decode=True) @property def wavelength(self): """ the vacuum wavelength of the line in meters. """ return self.getbyutype("ssldm:Line.wavelength.value") * Unit("m") @property def species_name(self): """ the name of the chemical species that produces the transition. """ return self.getbyutype("ssldm:Line.species.name") @property def status(self): """ the name of the chemical species that produces the transition. """ return self.getbyutype("ssldm:Line.identificationStatus") @property def initial_level(self): """ a description of the initial (higher energy) quantum level """ return self.getbyutype("ssldm:Line.initialLevel.name", decode=True) @property def final_level(self): """ a description of the final (higher energy) quantum level """ return self.getbyutype("ssldm:Line.finalLevel.name") astropy-pyvo-b70558c/pyvo/dal/ssa.py000066400000000000000000000642041510533647000174540ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for spectra in a remote archive. A Simple Spectral Access (SSA) service allows a client to search for spectra in an archive whose field of view overlaps with a given cone on the sky. The service responds to a search query with a table in which each row represents an image that is available for download. The columns provide metadata describing each image and one column in particular provides the image's download URL (also called the *access reference*, or *acref*). Some SSA services can create spectra on-the-fly from underlying data (e.g. image cubes); in this case, the query result is a table of images whose aperture matches the requested cone and which will be created when accessed via the download URL. This module provides an interface for accessing an SSA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SSAResults instance as its results which represents the matching imagess from the archive. The SSAResults supports access to and iterations over the individual records; these are provided as SSARecord instances, which give easy access to key metadata in the response, such as the position of the spectrum's aperture, the spectrum format, its frequency range, and its download URL. The SSAService class can represent a specific service available at a URL endpoint. """ import re from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.time import Time from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Field from astropy.table import Table import numpy as np from .query import DALResults, DALQuery, DALService, Record from .mimetype import mime2extension from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from .. import samp __all__ = ["search", "SSAService", "SSAQuery", "SSAResults", "SSARecord"] def search( baseurl, pos=None, *, diameter=None, band=None, time=None, format=None, **keywords): """ submit a simple SSA query that requests spectra overlapping a given region Parameters ---------- baseurl : str the base URL for the SSA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAResults a container holding a table of matching spectrum records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SSAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return SSAService(baseurl).search( pos=pos, diameter=diameter, band=band, time=time, format=format, **keywords) class SSAService(DALService): """ a representation of an SSA service """ def __init__(self, baseurl, *, capability_description=None, session=None): """ instantiate an SSA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. """ super().__init__(baseurl, session=session, capability_description=capability_description) def _get_metadata(self): """ the metadata resource element """ if not hasattr(self, "_metadata"): query = self.create_query(format='metadata') metadata = query.execute_votable() setattr(self, "_metadata", metadata) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search( self, pos=None, *, diameter=None, band=None, time=None, format=None, **keywords): """ submit a SSA query to this service with the given constraints. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including query syntax errors See Also -------- SSAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query( pos=pos, diameter=diameter, band=band, time=time, format=format, **keywords).execute() def create_query( self, pos=None, *, diameter=None, band=None, time=None, format=None, request="queryData", **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAQuery the query instance See Also -------- SSAQuery """ return SSAQuery( self.baseurl, pos=pos, diameter=diameter, band=band, time=time, format=format, request=request, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SSAQuery(DALQuery): """ a class for preparing a query to an SSA service. Query constraints are added via its service type-specific properties and methods. Once all the constraints are set, one of the various execute() functions can be called to submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, *, diameter=None, band=None, time=None, format=None, request="queryData", session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SSA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. session : object optional session to use for network requests **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. """ super().__init__(baseurl, session=session) if pos is not None: self.pos = pos if diameter is not None: self.diameter = diameter if band is not None: self.band = band if time is not None: self.time = time if format is not None: self.format = format self.request = request self.update({key.upper(): value for key, value in keywords.items()}) @property def pos(self): """ the position of the center of the circular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["POS"] = "{ra},{dec}".format( ra=pos.icrs.ra.deg, dec=pos.icrs.dec.deg) @pos.deleter def pos(self): delattr(self, "_pos") del self["POS"] @property def diameter(self): """ the diameter of the circular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_diameter", None) @diameter.setter def diameter(self, diameter): setattr(self, "_diameter", diameter) if not isinstance(diameter, Quantity): valerr = ValueError( 'Diameter must be exactly one value, expressing degrees') try: # assume degrees diameter = diameter * Unit("deg") except ValueError: raise valerr try: if len(diameter): raise valerr except TypeError: pass # len 1 self["SIZE"] = diameter.to(Unit("deg")).value @diameter.deleter def diameter(self): delattr(self, "_diameter") del self["SIZE"] @property def band(self): """ the bandwidth range the observations belong to. """ return getattr(self, "_band", None) @band.setter def band(self, band): setattr(self, "_band", band) if not isinstance(band, Quantity): valerr = ValueError( 'Band must be a sequence with exactly two values', 'expressing a frequency or wavelength range') try: # assume meters band = band * Unit("meter") except ValueError: raise valerr try: if len(band) != 2: raise valerr except TypeError: raise valerr # transform to meters band = band.to(Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort # it to have the right order again band.sort() self["BAND"] = "{start}/{end}".format( start=band.value[0], end=band.value[1]) @band.deleter def band(self): delattr(self, "_band") del self["BAND"] @property def time(self): """ the datetime range the observations were made in. """ return getattr(self, "_time", None) @time.setter def time(self, time): setattr(self, "_time", time) if not isinstance(time, Time): valerr = ValueError( 'Time must be a sequence with exactly two values, ' 'expressing a datetime in ISO 8601' ) try: # assume iso8601 time = Time(time, format="isot") except ValueError: raise valerr try: if len(time) != 2: raise valerr except TypeError: raise valerr # It seems astropy either has seconds and microseconds (the date_hms # subformat) or no seconds at all (the date_hm subformat). SSAP # probably doesn't allow microseconds. Rather than fix this # via a new astropy subformat, let's get by with local string # operations. literals = time.to_value('isot') self["TIME"] = "{start}/{end}".format( start=literals[0].split(".")[0], end=literals[1].split(".")[0]) @time.deleter def time(self): delattr(self, "_time") del self["TIME"] @property def format(self): """ the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. """ return getattr(self, "_format", None) @format.setter def format(self, val): setattr(self, "_format", val) if isinstance(val, (str, bytes)): val = [val] self["FORMAT"] = ",".join(val) @format.deleter def format(self): delattr(self, "_format") del self["FORMAT"] @property def request(self): """ the type of service operation which is being performed """ return getattr(self, "_request", None) @request.setter def request(self, val): setattr(self, "_request", val) self["REQUEST"] = val @request.deleter def request(self): delattr(self, "_request") del self["REQUEST"] def execute(self): """ submit the query and return the results as a SSAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SSAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SSAResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from a spectrum (SSA) query. Each record contains a set of metadata that describes an available spectrum matching the query constraints. The number of records in the results is by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.ssa.SSARecord` instances) are typically accessed by iterating over an ``SSAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SSAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the spectra. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SSAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.ssa.SSARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sia result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SIARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SSARecord(self, index, session=self._session) class SSARecord(SodaRecordMixin, DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an SSA query, describing an available spectrum. The commonly accessed metadata which are stadardized by the SSA protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def ra(self): """ return the right ascension of the center of the spectrum """ return self.getbyutype("ssa:Target.Pos")[0] @property def dec(self): """ return the declination of the center of the spectrum """ return self.getbyutype("ssa:Target.Pos")[1] @property def title(self): """ return the title of the spectrum """ return self.getbyutype("ssa:DataID.Title", decode=True) @property def format(self): """ return the file format that this the spectrum is stored in """ return self.getbyutype("ssa:Access.Format", decode=True) @property def dateobs(self): """ return the modified Julien date (MJD) of the mid-point of the observational data that went into the spectrum """ dateobs = self.getbyutype("ssa:DataID.Date", decode=True) try: if not dateobs or np.isnan(dateobs): return None except TypeError: # np.isnan can only check floats. If can't check for nan, pass it along pass return Time(dateobs, format="iso") @property def instr(self): """ return the name of the instrument (or instruments) that produced the data that went into this spectrum. """ return self.getbyutype("ssa:DataID.Instrument", decode=True) @property def acref(self): """ return the URL that can be used to retrieve the spectrum. """ return self.getbyutype("ssa:Access.Reference", decode=True) @property def filesize(self): """ The (estimated) size of the image in bytes """ return self.getbyutype("ssa:Access.Size") def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. None is returned if no such column exists. """ dataurl = super().getdataurl() if dataurl is None: return self.acref else: return dataurl def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ out = self.title if isinstance(out, bytes): out = out.decode('utf-8') if not out: out = "spectrum" else: out = re.sub(r'\s+', '_', out.strip()) return out def suggest_extension(self, *, default=None): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. """ return mime2extension(self.format, default) def broadcast_samp(self, *, client_name=None): """ Broadcast the spectrum to ``client_name`` via SAMP """ with samp.connection() as conn: samp.send_spectrum_to( conn, self.getdataurl(), client_name, name=self.suggest_dataset_basename()) astropy-pyvo-b70558c/pyvo/dal/tap.py000066400000000000000000001211431510533647000174460ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for accessing remote source and observation catalogs """ from functools import partial from datetime import datetime import time from time import sleep import random import requests from urllib.parse import urlparse, urljoin from astropy.io.votable import parse as votableparse from .query import ( DALResults, DALQuery, DALService, Record, UploadList, DALServiceError, DALQueryError) from .vosi import AvailabilityMixin, CapabilityMixin, VOSITables from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from ..io import vosi, uws from ..io.vosi import tapregext as tr from ..utils.formatting import para_format_desc from ..utils.http import use_session from ..utils.prototype import prototype_feature import xml.etree.ElementTree import io __all__ = [ "search", "escape", "TAPService", "TAPQuery", "AsyncTAPJob", "TAPResults"] IVOA_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" # file formats supported by table upload and their corresponding MIME types TABLE_UPLOAD_FORMAT = {'tsv': 'text/tab-separated-values', 'csv': 'text/csv', 'FITSTable': 'application/fits'} # file formats supported by table create and their corresponding MIME types TABLE_DEF_FORMAT = {'VOSITable': 'text/xml', 'VOTable': 'application/x-votable+xml'} # common transient errors that can be retried TRANSIENT_ERRORS = (requests.exceptions.ConnectionError, requests.exceptions.Timeout) def _from_ivoa_format(datetime_str): """ parses an ivoa date in ISO 8601 format: YYYY-MM-DDTHH:MM:SS.[mmm]Z :param datetime_str: :return: corresponding datetime object """ # TODO Replace with datetime.fromisoformat(date_string) in Python3.7+ try: # with fraction of seconds first return datetime.strptime(datetime_str, IVOA_DATETIME_FORMAT) except ValueError: # and without return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ") def escape(term): """ escapes a term for use in ADQL """ return str(term).replace("'", "''") def search(url, query, *, language="ADQL", maxrec=None, uploads=None, **keywords): """ submit a Table Access query that returns rows matching the criteria given. Parameters ---------- url : str the base URL of the query service. query : str, dict The query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to file like objects containing a votable Returns ------- TAPResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service. DALQueryError if the service responds with an error, including a query syntax error. """ service = TAPService(url) return service.search(query, language=language, maxrec=maxrec, uploads=uploads, **keywords) class TAPService(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of a Table Access Protocol service """ _tables = None _examples = None def __init__(self, baseurl, *, capability_description=None, session=None): """ instantiate a Table Access Protocol service Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests """ try: super().__init__(baseurl, session=session, capability_description=capability_description) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such use case for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) except DALServiceError as e: raise DALServiceError(f"Cannot find TAP service at '" f"{baseurl}'.\n\n{str(e)}") from None def get_tap_capability(self): """ returns the (first) TAP capability of this service. Returns ------- A `~pyvo.io.vosi.tapregext.TableAccess` instance. """ for capa in self.capabilities: if isinstance(capa, tr.TableAccess): return capa raise DALServiceError("Invalid TAP service: Does not" " expose a tr:TableAccess capability") @property def tables(self): """ returns tables as a dict-like object """ if self._tables is None: tables_url = f'{self.baseurl}/tables' response = self._session.get(tables_url, params={"detail": "min"}, stream=True) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, tables_url) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) self._tables = VOSITables( vosi.parse_tables(response.raw.read), tables_url) return self._tables def _parse_examples(self, examples_uri, *, depth=0): """returns the TAP queries from a DALI examples URI. """ if depth > 5: raise DALServiceError("Suspecting endless recursion when" " parsing TAP examples") response = self._session.get(examples_uri, stream=True) if response.status_code == 404: return [] try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, examples_uri) try: root = xml.etree.ElementTree.parse( io.BytesIO(response.content)).getroot() example_elements = root.findall('.//*[@property="query"]') except Exception as ex: raise DALServiceError.from_except(ex, examples_uri) examples = [TAPQuery(self.baseurl, example.text) for example in example_elements] for continuation in root.findall('.//*[@property="continuation"]'): examples.extend( self._parse_examples(continuation.get("href"), depth=depth + 1)) return examples @property def examples(self): """ returns examples as a list of TAPQuery objects """ if self._examples is None: examples_url = f'{self.baseurl}/examples' self._examples = self._parse_examples(examples_url) return self._examples @property def maxrec(self): """ the default output limit. Raises ------ DALServiceError if the property is not exposed by the service """ try: return self.get_tap_capability().outputlimit.default.content except AttributeError: pass raise DALServiceError("Default limit not exposed by the service") @property def hardlimit(self): """ the hard output limit. Raises ------ DALServiceError if the property is not exposed by the service """ try: return self.get_tap_capability().outputlimit.hard.content except AttributeError: pass raise DALServiceError("Hard limit not exposed by the service") @property def upload_methods(self): """ a list of upload methods in form of :py:class:`~pyvo.io.vosi.tapregext.UploadMethod` objects """ return self.get_tap_capability().uploadmethods def run_sync( self, query, *, language="ADQL", maxrec=None, uploads=None, **keywords): """ runs sync query and returns its result Parameters ---------- query : str The query language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable Returns ------- TAPResults the query result See Also -------- TAPResults """ return self.create_query( query, language=language, maxrec=maxrec, uploads=uploads, **keywords).execute() # alias for service discovery search = run_sync def run_async( self, query, *, language="ADQL", maxrec=None, uploads=None, delete=True, **keywords): """ runs async query and returns its result Parameters ---------- query : str, dict the query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable delete : bool delete the job after fetching the results Returns ------- TAPResult the query instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response See Also -------- AsyncTAPJob """ job = AsyncTAPJob.create( self.baseurl, query, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) job = job.run().wait() try: job.raise_if_error() except DALQueryError: if delete: job.delete() raise result = job.fetch_result(max_retries=keywords.get('max_retries', 0)) if delete: job.delete() return result def submit_job( self, query, *, language="ADQL", maxrec=None, uploads=None, **keywords): """ submit a async query without starting it and returns a AsyncTAPJob object Parameters ---------- query : str the query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable Returns ------- AsyncTAPJob the query instance See Also -------- AsyncTAPJob """ return AsyncTAPJob.create( self.baseurl, query, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) def create_query( self, query=None, *, mode="sync", language="ADQL", maxrec=None, uploads=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- query : str the query string / parameters mode : str the query mode (sync | async). default "sync" language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int specifies the maximum records to return. defaults to the service default. uploads : dict a mapping from table names to objects containing a votable. """ return TAPQuery( self.baseurl, query, mode=mode, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) def get_job(self, job_id): """ Returns the job corresponding to an ID. Note that the caller must be able to see the job in the current security context. Parameters ---------- job_id : str ID of the job to view Returns ------- `~pyvo.io.uws.tree.JobSummary` corresponding to the job ID """ response = self._session.get( self.baseurl + '/async/' + job_id, stream=True) response.raw.read = partial(response.raw.read, decode_content=True) return uws.parse_job(response.raw.read) def get_job_list(self, *, phases=None, after=None, last=None, short_description=True): """ lists jobs that the caller can see in the current security context. The list can be filtered on the server side by the phases of the jobs, creation date time or Note that by default jobs in 'ARCHIVED' phase are not returned. Parameters ---------- phases: list of str Union of job phases to filter the results by. after: datetime Return only jobs created after this datetime last: int Return only the most recent number of jobs short_description: flag - True or False If True, the jobs in the list will contain only the information corresponding to the TAP ShortJobDescription object (job ID, phase, run ID, owner ID and creation ID) whereas if False, a separate GET call to each job is performed for the complete job description. Returns ------- list of `~pyvo.io.uws.tree.JobSummary` """ params = {'PHASE': phases, 'LAST': last} if after: if isinstance(after, str): after = _from_ivoa_format(after) params['AFTER'] = after.strftime(IVOA_DATETIME_FORMAT) response = self._session.get(f'{self.baseurl}/async', params=params, stream=True) response.raw.read = partial(response.raw.read, decode_content=True) jobs = uws.parse_job_list(response.raw.read) if not short_description: dj = [] for job in jobs: dj.append(self.get_job(job.jobid)) return dj else: return list(jobs) def describe(self, width=None): """ Print a summary description of this service. This includes the interface capabilities, and the content description if it doesn't contain multiple data collections (in other words, it is not a TAP service). """ if len(self.tables) == 1: description = next(self.tables.values()).description if width: description = para_format_desc(description, width) print(description) print() capabilities = filter( lambda x: not str(x.standardid).startswith( 'ivo://ivoa.net/std/VOSI'), self.capabilities ) for cap in capabilities: cap.describe() print() @prototype_feature('cadc-tb-upload') def create_table(self, name, definition, *, format='VOSITable'): """ Creates a table in the catalog service. Parameters ---------- name: str Name of the table in the TAP service definition: stream (object with a read method) Definition of the table format: str Format of the table definition (VOSITable or VOTable). """ if not name or not definition: raise ValueError( 'table name and definition required in create: {}/{}'. format(name, definition)) if format not in TABLE_DEF_FORMAT.keys(): raise ValueError( 'Table definition file format {} not supported ({})'. format(format, ' '.join(TABLE_DEF_FORMAT.keys()))) headers = {'Content-Type': TABLE_DEF_FORMAT[format]} response = self._session.put(f'{self.baseurl}/tables/{name}', headers=headers, data=definition) response.raise_for_status() @prototype_feature('cadc-tb-upload') def remove_table(self, name): """ Remove a table from the catalog service (Equivalent to drop command in DB). Parameters ---------- name: str Name of the table in the TAP service """ if not name: raise ValueError( 'table name required in : {}'. format(name)) response = self._session.delete( f'{self.baseurl}/tables/{name}') response.raise_for_status() @prototype_feature('cadc-tb-upload') def load_table(self, name, source, *, format='tsv'): """ Loads content to a table Parameters ---------- name: str Name of the table source: stream with a read method Stream containing the data to be loaded format: str Format of the data source: tab-separated values(tsv), comma-separated values (csv) or FITS table (FITSTable) """ if not name or not source: raise ValueError( 'table name and source required in upload: {}/{}'. format(name, source)) if format not in TABLE_UPLOAD_FORMAT.keys(): raise ValueError( 'Table content file format {} not supported ({})'. format(format, ' '.join(TABLE_UPLOAD_FORMAT.keys()))) headers = {'Content-Type': TABLE_UPLOAD_FORMAT[format]} response = self._session.post( f'{self.baseurl}/load/{name}', headers=headers, data=source) response.raise_for_status() @prototype_feature('cadc-tb-upload') def create_index(self, table_name, column_name, *, unique=False): """ Creates a table index in the catalog service. Parameters ---------- table_name: str Name of the table column_name: str Name of the column in the table unique: bool True for unique index, False otherwise """ if not table_name or not column_name: raise ValueError( 'table and column names are required in index: {}/{}'. format(table_name, column_name)) result = self._session.post(f'{self.baseurl}/table-update', data={'table': table_name, 'index': column_name, 'unique': 'true' if unique else 'false'}, allow_redirects=False) if result.status_code == 303: job_url = result.headers['Location'] if not job_url: raise RuntimeError( 'table update job location missing in response') # run the job job = AsyncTAPJob(job_url, session=self._session) job = job.run().wait() job.raise_if_error() # TODO job.delete() else: raise RuntimeError( 'BUG: table update expected status 303 received {}'. format(result.status_code)) class AsyncTAPJob: """ This class represents a UWS TAP Job. """ _job = {} @classmethod def create( cls, baseurl, query, *, language="ADQL", maxrec=None, uploads=None, session=None, **keywords): """ creates a async tap job on the server under ``baseurl`` Parameters ---------- baseurl : str the TAP baseurl query : str the query string language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable session : object optional session to use for network requests """ tapquery = TAPQuery( baseurl, query, mode="async", language=language, maxrec=maxrec, uploads=uploads, session=session, **keywords) response = tapquery.submit() job = cls(response.url, session=session) job._client_set_maxrec = maxrec return job def __init__(self, url, *, session=None, delete=True): """ initialize the job object with the given url and fetch remote values Parameters ---------- url : str the job url session : object, optional session to use for network requests delete : bool, optional whether to delete the job when exiting (default: True) """ self._url = url self._session = use_session(session) self._delete_on_exit = delete self._client_set_maxrec = None self._update() def __enter__(self): """ Enters the context """ return self def __exit__(self, exc_type, exc_val, exc_tb): """ Exits the context. Unless delete=False was set at initialization, the job is deleted. Any deletion errors are silently ignored to ensure proper context exit. """ if self._delete_on_exit: try: self.delete() except DALServiceError: pass def _update(self, wait_for_statechange=False, timeout=10.): """ updates local job infos with remote values """ try: if wait_for_statechange: response = self._session.get( self.url, stream=True, timeout=timeout, params={ "WAIT": "-1" } ) else: response = self._session.get(self.url, stream=True, timeout=timeout) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) self._job = uws.parse_job(response.raw.read) @property def job(self): """ all up-to-date uws job infos as dictionary """ # keep it up to date self._update() return self._job @property def url(self): """ the job url """ return self._url @property def job_id(self): """ the job id """ return self._job.jobid @property def phase(self): """ the current query phase """ self._update() return self._job.phase @property def execution_duration(self): """ maximum execution duration as `~astropy.time.TimeDelta`. """ self._update() return self._job.executionduration @execution_duration.setter def execution_duration(self, value): try: response = self._session.post( f"{self.url}/executionduration", data={"EXECUTIONDURATION": str(value)}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def destruction(self): """ datetime after which the job results are deleted automatically. read-write """ self._update() return self._job.destruction @destruction.setter def destruction(self, value): """ datetime after which the job results are deleted automatically. read-write Parameters ---------- value : datetime datetime after which the job results are deleted automatically """ if isinstance(value, str): value = _from_ivoa_format(value) try: response = self._session.post( f"{self.url}/destruction", data={"DESTRUCTION": value.strftime(IVOA_DATETIME_FORMAT)}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def quote(self): """ estimated runtime """ self._update() return self._job.quote @property def owner(self): """ job owner (if applicable) """ self._update() return self._job.ownerid @property def query(self): """ the job query """ self._update() for parameter in self._job.parameters: if parameter.id_.lower() == 'query': return parameter.content return '' @query.setter def query(self, query): try: response = self._session.post( f'{self.url}/parameters', data={"QUERY": query}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() def upload(self, **kwargs): """ upload a table to the job. the job must not been started. """ uploads = UploadList.fromdict(kwargs) files = { upload.name: upload.fileobj() for upload in uploads if upload.is_inline } try: response = self._session.post( f'{self.url}/parameters', data={'UPLOAD': uploads.param()}, files=files ) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def results(self): """ The job results if exists """ return self._job.results @property def result(self): """ Returns the UWS result that corresponds to the standard TAP result endpoint: "results/result". If no such result is found it falls back to the old behavior of returning the first result with id 'result'. """ try: for r in self._job.results: if r.href and r.href.endswith("results/result"): return r for r in self._job.results: if r.href and r.href.strip() and r.id_ == 'result': return r return None except (IndexError, AttributeError): return None @property def result_uris(self): """ a list of the last result uri's """ return [result.href for result in self._job.results] @property def result_uri(self): """ the uri of the result """ try: result = self.result if result is None: return None uri = result.href if not urlparse(uri).netloc: uri = urljoin(self.url, uri) return uri except IndexError: return None @property def uws_version(self): """ the version of the UWS serving this async job Asynchronous TAP jobs are managed using a standard called Universal Worker Service (UWS). For instance, starting version 1.1, you can have long polls, which save on monitoring requests. Normal users generally will not have to look at this. """ self._update() return self._job.version def run(self): """ starts the job / change phase to RUN """ try: response = self._session.post( f'{self.url}/phase', data={"PHASE": "RUN"}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) return self def abort(self): """ aborts the job / change phase to ABORT """ try: response = self._session.post( f'{self.url}/phase', data={"PHASE": "ABORT"}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) return self def wait(self, *, phases=None, timeout=600.): """ waits for the job to reach the given phases. Parameters ---------- phases : list phases to wait for timeout : float maximum time to wait in seconds Raises ------ DALServiceError if the job is in a state that won't lead to an result """ if not phases: phases = {"COMPLETED", "ABORTED", "ERROR"} interval = 1.0 increment = 1.2 active_phases = { "QUEUED", "EXECUTING", "RUN", "COMPLETED", "ERROR", "UNKNOWN"} while True: self._update(wait_for_statechange=True, timeout=timeout) # use the cached value cur_phase = self._job.phase if cur_phase not in active_phases: raise DALServiceError( "Cannot wait for job completion. Job is not active!") if cur_phase in phases: break # fallback for uws 1.0 or unsupported WAIT parameter sleep(interval) interval = min(120, interval * increment) return self def delete(self): """ deletes the job. this object will become invalid. """ try: response = self._session.delete( self.url, allow_redirects=False) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._url = None def raise_if_error(self): """ raise a exception if theres an error Raises ------ DALQueryError if theres an error """ if self.phase in {"ERROR", "ABORTED"}: msg = "" if self._job and self._job.errorsummary: msg = self._job.errorsummary.message.content msg = msg or "" raise DALQueryError("Query Error: " + msg, self.url) def fetch_result(self, max_retries=0): """ returns the result votable if query is finished Parameters ---------- max_retries : int, optional Maximum number of retry attempts for transient network errors. Default is 0 (no retries). """ result_uri = self.result_uri if result_uri is None: self._update() self.raise_if_error() raise DALServiceError(reason="No result URI available", url=self.url) response = None for attempt in range(max_retries + 1): try: response = self._session.get(self.result_uri, stream=True) response.raise_for_status() break except TRANSIENT_ERRORS as ex: if attempt < max_retries: delay = (2 ** attempt) + random.uniform(0.8, 1) time.sleep(delay) continue else: raise DALServiceError.from_except(ex, self.url) except requests.RequestException as ex: # Non-retryable error - update and check for query errors self._update() self.raise_if_error() raise DALServiceError.from_except(ex, self.url) response.raw.read = partial(response.raw.read, decode_content=True) result = TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session) result.check_overflow_warning(self._client_set_maxrec) return result class TAPQuery(DALQuery): """ a class for preparing a query to a TAP service. Query constraints are added via service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, query, *, mode="sync", language="ADQL", maxrec=None, uploads=None, session=None, **keywords): """ initialize the query object with the given parameters Parameters ---------- baseurl : str the TAP baseurl query : str the query string mode : str the query mode (sync | async). default "sync" language : str the query language. defaults to ADQL maxrec : int the amount of records to fetch uploads : dict Files to upload. Uses table name as key and table content as value. session : object optional session to use for network requests """ baseurl = baseurl.rstrip("?") super().__init__(baseurl, session=session, **keywords) self._mode = mode if mode in ("sync", "async") else "sync" self._uploads = UploadList.fromdict(uploads or {}) self["REQUEST"] = "doQuery" self["LANG"] = language self._client_set_maxrec = maxrec if maxrec: self["MAXREC"] = maxrec self["QUERY"] = query if self._uploads: self["UPLOAD"] = self._uploads.param() @property def queryurl(self): """ the URL to which to submit queries In TAP, that varies depending on whether we run sync or async queries. """ return f'{self.baseurl}/{self._mode}' def execute_stream(self, *, post=False): """ submit the query and return the raw VOTable XML as a file stream Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors in the input query syntax """ # theres nothing to execute in non-sync queries if self._mode != "sync": raise DALServiceError( "Cannot execute a non-synchronous query. Use submit instead") return super().execute_stream(post=post) def execute(self): """ submit the query and return the results as a TAPResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ result = TAPResults( self.execute_votable(), url=self.queryurl, session=self._session ) result.check_overflow_warning(self._client_set_maxrec) return result def submit(self, *, post=False): """ Does the request part of the TAP query. This function is separated from response parsing because async queries return no votable but behave like sync queries in terms of request. It returns the requests response. """ url = self.queryurl files = { upload.name: upload.fileobj() for upload in self._uploads if upload.is_inline } response = self._session.post( url, data=self, stream=True, files=files) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) return response class TAPResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.Record` instances) are typically accessed by iterating over an ``TAPResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.query.DALResults.getcolumn` method. ``TAPResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`astropy.table.Table` via the following conversion: ``table = results.to_table`` ``SIAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.Record` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ @property def infos(self): """ return the info element as dictionary """ return getattr(self, "_infos", {}) @property def query_status(self): """ return the query status """ return getattr(self, "_infos", {}).get("QUERY_STATUS", None) def getrecord(self, index): """ return a representation of a tap result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- REc a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return TAPRecord(self, index, session=self._session) def _handle_overflow_warning(self, client_set_maxrec=None): """ TAP-specific overflow warning handling. For TAP results we suppress the default overflow warning during initialization because TAPQuery.execute() will call check_overflow_warning() """ pass class TAPRecord(SodaRecordMixin, DatalinkRecordMixin, Record): pass astropy-pyvo-b70558c/pyvo/dal/tests/000077500000000000000000000000001510533647000174505ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/__init__.py000066400000000000000000000001001510533647000215500ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/dal/tests/conftest.py000066400000000000000000000012661510533647000216540ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins astropy-pyvo-b70558c/pyvo/dal/tests/data/000077500000000000000000000000001510533647000203615ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/000077500000000000000000000000001510533647000221505ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/cutout1.xml000066400000000000000000000260741510533647000243070ustar00rootroot00000000000000
the caller is allowed to use this link with the current authenticated identity
ivo://cadc.nrc.ca/MACHO?54150/cal054150r https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054150r.fits.fz?RUNID=e3h6rssb161oyf7j #this download ad:MACHO/cal054150r.fits.fz application/fits 18616320 true
ivo://cadc.nrc.ca/MACHO?54150/cal054150r soda-146cec58-9031-4568-9aeb-380eabb50942 #cutout SODA-sync cutout of ad:MACHO/cal054150r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54150/cal054150r soda-142c15fb-f9ff-4ad0-b35f-7efb07d9bfea #cutout SODA-async cutout of ad:MACHO/cal054150r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054151b.fits.fz?RUNID=e3h6rssb161oyf7j #this download ad:MACHO/cal054151b.fits.fz application/fits 21692160 true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b soda-2d68bb35-50e4-459b-bfb2-ac2b5da1a5d1 #cutout SODA-sync cutout of ad:MACHO/cal054151b.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b soda-fadba418-47ec-4a3d-b8ab-8368f3081082 #cutout SODA-async cutout of ad:MACHO/cal054151b.fits.fz application/fits true
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/cutout2.xml000066400000000000000000000142311510533647000243000ustar00rootroot00000000000000 the caller is allowed to use this link with the current authenticated identity
ivo://cadc.nrc.ca/MACHO?54151/cal054151r https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054151r.fits.fz?RUNID=of051krube49ud2e #this download ad:MACHO/cal054151r.fits.fz application/fits 18987840 true
ivo://cadc.nrc.ca/MACHO?54151/cal054151r soda-40d74211-597b-4aa9-a153-f5ac67b77021 #cutout SODA-sync cutout of ad:MACHO/cal054151r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151r soda-8e48c445-8f76-4c01-abcf-bdb1c2f7caf5 #cutout SODA-async cutout of ad:MACHO/cal054151r.fits.fz application/fits true
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/datalink-obscore.xml000066400000000000000000000315771510533647000261300ustar00rootroot00000000000000 calibration level (0,1,2,3) publisher dataset identifier short name for the data colection telescope name instrument name internal dataset identifier type of product timestamp of date the data becomes publicly available RA of central coordinates DEC of central coordinates size of the region covered (~diameter of minimum bounding circle) region bounded by observation typical spatial resolution dimensions (number of pixels) along one spatial axis name of intended target dimensions (number of pixels) along the other spatial axis start time of observation (MJD) end time of observation (MJD) exposure time of observation typical temporal resolution dimensions (number of pixels) along the time axis start spectral coordinate value stop spectral coordinate value typical spectral resolution dimensions (number of pixels) along the energy axis dimensions (number of pixels) along the polarization axis UCD describing the spectral axis polarization states present in the data URL to download the data estimated size of the download UCD describing the observable axis (pixel values) format of the data file(s) primary key timestamp of last modification of the metadata
2 ivo://cadc.nrc.ca/MACHO?54150/cal054150r MACHO Mt. Stromlo Observatory 50 inch MACHO 54150 image 2008-10-08T00:00:00.000 88.81762854444203 -67.77109486072844 1.0245546113974644 polygon 89.70783791485876 -67.41840881752057 89.74786512306278 -68.14322955012662 88.22311503462467 -68.14813181576002 87.81208257298478 -67.77896886699358 87.82899304985233 -67.4197778334441 4.353822892 4181 macho000071 4200 50527.522581 50527.526053 300.0 300.0 7 5.800000000000001E-7 7.8E-7 5.799999999999999 7 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54150%2Fcal054150r phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-93cf-fed05e0e60f6 2019-11-20T19:29:40.929
2 ivo://cadc.nrc.ca/MACHO?54151/cal054151b MACHO Mt. Stromlo Observatory 50 inch MACHO 54151 image 2008-10-08T00:00:00.000 79.9564431173079 -70.32295142824165 1.0420934836421956 polygon 81.03835269676311 -69.95434045997953 81.07118698080274 -70.68523150491481 78.84035765138141 -70.68806135194416 78.88155357298983 -69.9513684574919 5.020573004 4175 macho000006 4168 50527.527002 50527.530475 300.0 300.0 8 4.800000000000001E-7 5.800000000000001E-7 5.799999999999999 8 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54151%2Fcal054151b phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-b8fb-fed05e2e4e02 2019-11-20T19:29:40.960
2 ivo://cadc.nrc.ca/MACHO?54151/cal054151r MACHO Mt. Stromlo Observatory 50 inch MACHO 54151 image 2008-10-08T00:00:00.000 80.01859124602015 -70.30750032450052 1.0248990302163568 polygon 81.01558843560966 -69.95461093880266 81.06567094624911 -70.67927922356017 79.34988020864029 -70.684376201833 78.89013903241694 -70.31495449099631 78.91029632344201 -69.95565099163903 5.020573004 4181 macho000006 4169 50527.527002 50527.530475 300.0 300.0 7 5.800000000000001E-7 7.8E-7 5.799999999999999 7 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54151%2Fcal054151r phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-b83a-fed05e27f58e 2019-11-20T19:29:40.960
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/datalink-ssa.xml000066400000000000000000000513431510533647000252530ustar00rootroot00000000000000 Spectra from the Flash and Heros Echelle spectrographs developed at Landessternwarte Heidelberg and mounted at La Silla and various other observatories. The data mostly contains spectra of OB stars. Heros was the name of the instrument after Flash got a second channel in 1995. This resource contains data associated with the publication 1996A&A...312..539S. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/flashheros.data SSAP A measure of how closely the record matches your query. Higher numbers mean better matches. Observed position RA and Dec as a 2-real array as required by SSAP. Target RA and Dec as a 2-real array as required by SSAP. URL of a preview for the dataset, where available. Access key for the data MIME type of the file served Size of the data in bytes Title or the dataset (usually, spectrum) Dataset identifier assigned by the creator Dataset identifier assigned by the publisher Processing/Creation date Date last published. Bandpass (i.e., rough spectral location) of this dataset; use something generic like 'Optical' here. Creator assigned version for this dataset (will be incremented when this particular item is changed). Common name of object observed. Object class (star, QSO,...; use Simbad object classification http://simbad.u-strasbg.fr/simbad/sim-display?data=otypes if at all possible) Redshift of target object Equatorial (ICRS) position of the target object. Signal-to-noise ratio estimated for this dataset ICRS location of aperture center Angular diameter of aperture Midpoint of exposure Exposure duration Midpoint of region covered in this dataset Width of the spectrum Lower value of spectral coordinate Upper value of spectral coordinate Number of points in the spectrum Local observation key (used to connect to single orders). Type of flux calibration Data model name and version System RA and Dec are given in Time conversion factor in Osuna-Salgado convention. Spectral conversion factor in Osuna-Salgado convention UCD of the spectral column Unit of the spectral column Flux/magnitude conversion factor in Osuna-Salgado convention UCD of the flux column Unit of the flux column Type of data (spectrum, time series, etc) Publisher of the datasets included here. Creator of the datasets included here. IOVA id of the originating data collection Instrument or code used to produce these datasets Method of generation for the data. Process used to produce the data URL or bibcode of a publication describing this data. Statistical error in flux Systematic error in flux Type of flux calibration Bin size in wavelength Statistical error in wavelength Systematic error in wavelength Type of wavelength calibration Resolution on the spectral axis Statistical error in position Type of calibration in spatial coordinates Spatial resolution of data
0.0 304.446675349555 38.0329313131891 304.446675349555 38.0329313131891 NaN NaN http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt?preview=True http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt image/fits 100800 Flash/Heros p Cyg 1990-10-11 09:28:07.500003 ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt 1993-05-18T00:00:00 2017-02-22T13:19:40.966036 p Cyg star NaN NaN NaN NaN 304.446675349555 38.0329313131891 17.6 48175.39453125 NaN 5.40127e-07 2.78147e-07 4.01053e-07 6.792e-07 23181 ca90/f0011.mt NORMALIZED
The pubisher DID of the dataset of interest Recalibrate the spectrum. Right now, the only recalibration supported is max(flux)=1 ('RELATIVE'). Spectral cutout interval MIME type of the output format
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/datalink.desise000066400000000000000000000125111510533647000251350ustar00rootroot00000000000000{ "uri": "http://www.ivoa.net/rdf/datalink/core", "flavour": "RDF Property", "terms": { "this": { "label": "the data itself", "description": "the primary (as opposed to related) data of the identified resource", "wider": [], "narrower": [] }, "progenitor": { "label": "Progenitor", "description": "data resources that were used to create this dataset (e.g. input raw data)", "wider": [], "narrower": [] }, "derivation": { "label": "Derivation", "description": "data resources that are derived from this dataset (e.g. output data products)", "wider": [], "narrower": [] }, "auxiliary": { "label": "Auxiliary", "description": "auxiliary resources", "wider": [], "narrower": [ "weight", "error", "noise" ] }, "weight": { "label": "Weight map", "description": "resource with array(s) containing weighting values", "wider": [ "auxiliary" ], "narrower": [] }, "error": { "label": "Error map", "description": "resource with array(s) containing error values", "wider": [ "auxiliary" ], "narrower": [] }, "noise": { "label": "Noise map", "description": "resource with array(s) containing noise values", "wider": [ "auxiliary" ], "narrower": [] }, "calibration": { "label": "Applicable Calibration", "description": " Data products that can be used to remove instrumental signatures from #this. Note that the calibration steps such data products feed have not been applied to #this yet.", "wider": [], "narrower": [ "bias", "dark", "flat" ] }, "bias": { "label": "Bias Frame", "description": "Data products that can be used to remove detector offset levels from #this.", "wider": [ "calibration" ], "narrower": [] }, "dark": { "label": "Dark Frame", "description": "Data products that can be used to remove detector dark current from #this.", "wider": [ "calibration" ], "narrower": [] }, "flat": { "label": "Flat Field", "description": "Data products that can be used to remove the signature of non-homogeneous detector sensitivity from #this.", "wider": [ "calibration" ], "narrower": [] }, "preview": { "label": "Preview", "description": "low fidelity but easily viewed representation of the data ", "wider": [], "narrower": [ "preview-image", "preview-plot", "thumbnail" ] }, "preview-image": { "label": "Image preview", "description": "preview of the data as a 2-dimensional image", "wider": [ "preview" ], "narrower": [] }, "preview-plot": { "label": "Plot preview", "description": "preview of the data as a plot (e.g. spectrum or light-curve)", "wider": [ "preview" ], "narrower": [] }, "thumbnail": { "label": "Small Graphical Representation", "description": "A very small preview suitable for displaying many at one time.", "wider": [ "preview" ], "narrower": [] }, "proc": { "label": "Processing", "description": "server-side data processing result", "wider": [], "narrower": [ "cutout" ] }, "cutout": { "label": "Cutout", "description": "a subsection of the primary data", "wider": [ "proc" ], "narrower": [] }, "documentation": { "label": "Documentation", "description": "Structured or unstructured metadata helping to understand, interpret, or work with #this. Such information can range from processing logs to weather reports to technical documents on instruments to related publications.", "wider": [], "narrower": [ "detached-header" ] }, "detached-header": { "label": "Detached Header", "description": "Machine-readable metadata for #this, which in general will be necessary for its scientific use. Examples include FITS headers distributed without their data blocks or PDS label files.", "wider": [ "documentation" ], "narrower": [] }, "coderived": { "label": "Coderived Data", "description": "Data products sharing one or more progenitors with #this. This could be a lightcurve for an object catalog derived from repeated observations, the dataset processed using a different pipeline, or the like.", "wider": [], "narrower": [] }, "counterpart": { "label": "Counterpart", "description": "Data products sharing the target of the experiment or observation that led to #this but of unrelated provenance. This could be observations of the same object in different wavelengths or along different axes (time, spectrum), but spectra of dust of common origin but different laboratories would be #counterparts as well.", "wider": [], "narrower": [] }, "package": { "label": "Single Download Package", "description": "All file-like items related to #this and #this itself packaged together in a single downloadable archive.", "wider": [], "narrower": [] } } } astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/datalink.xml000066400000000000000000000133651510533647000244710ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary MIME type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/flashheros/q/echdl/dlmeta?ID=ivo%3A%2F%2Forg.gavo.dc%2F%7E%3Fflashheros%2Fdata_raw%2Fca90%2Fn0011.mt Split Echelle Orders #progenitor application/x-votable+xml;content=datalink -1
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt ndndtdihpgea #proc -1
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt The full dataset. #this image/fits 100800
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt?preview=True A preview for the dataset. #preview image/png -1
The pubisher DID of the dataset of interest Recalibrate the spectrum. Right now, the only recalibration supported is max(flux)=1 ('RELATIVE'). Spectral cutout interval MIME type of the output format
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/proc.xml000066400000000000000000000176041510533647000236450ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview-image image/jpeg -1
ivo://org.gavo.dc/s/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/wider.dat An artificial thing. #preview image/jpeg -1
ivo://org.gavo.dc/s/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://example.edu/when-will-it-be-back Something requiring custom semantics. urn:example:rdf/dlext#oracle text/x-prediction -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/proc_inf.xml000066400000000000000000000161561510533647000245020ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview image/jpeg -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
astropy-pyvo-b70558c/pyvo/dal/tests/data/datalink/proc_units.xml000066400000000000000000000161601510533647000250630ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview image/jpeg -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
astropy-pyvo-b70558c/pyvo/dal/tests/data/mimetype/000077500000000000000000000000001510533647000222125ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/mimetype/ivoa_logo.jpg000066400000000000000000000304551510533647000247010ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHHC     C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?((((3Ɵ_wxHTյ{y_QoCHH<}xTW~SI {m?㯊hiڗ+O,i|[\Փ8e .-=~s,}n-+$S|=v= RԮ̛9Z="aI6'U|TϮ[ÿ|/~K+" 5/hd7gC4[vr3I=KkXfUyg,@KZ.y6n~FCW6ܘ>ҺxQEiF/;fVNA5_ rGvk}]v1^xGσAMQ崘1X_8 ;ɵǽp(WGź瀾#$[ N";!3,+o׶_GKO4z$6^".3,[ 2vg\1(;QAkk}~/N}yEs<]|MY0ȟJb]ѱ1QL((((+"|)-C_6By7)9 hjj6^sjwuU(ufv =IKZMτ4\LR/Ex8-j{cXȯ> _ռ@큮ۏ'lgHcxu3SݢDF*v>vǃ5mg㇍V=qtx^=w |_ g'1j2Oxz6ٌhE'_9Y@:hᾓeW`;VMy.τrE% YV&ZiڿIX|X?ڿT6ҝiZ4-l=P5_W%oqqx"cimH}"iO!>i+j&LMeG1\g%{nEiBÆb?(裖9%mH%yըLMՍT HoFkN',3G`Gj҄V[kJ&_1 Z1#Z%6ſ]j@I'mō/H?q~ɿiH[~@ŧ?ķ:U;lpx=$QnG~+ľ" &Kq{x^N W è<́ftgz)kA~^OT|1⧆V~,^Yuf}au8Iھ_Y|Axkv3m+VFٮ6t=ϊOخk4neԭ-vztJXAcPy@1 W%<=u2kVJD\F )̽d#u< W8gpPߑ  #-~'ςr|ҢK\'xQERK_~_4*tH/)&+H r!C^ !In)(lρc>#IB36Po/ I'_&zNJ(XJ|KH4n DFA:Uؘg_9q_2γ⿉:׌Yj(pfFO2y 訸U8ϰIJoJzyω_>&MO>(<[} צeAbDX@+WYsCzgKXKjv_&Z]Wx=Ӯ-=oiw%1yE$*VBH|H*t?cΥ(TzF5Iq h_4%U?_Os_^s(G8.v>? py ImxgIQN5*nw~U\jT}42eG|)?wGiS^Rߩ?PRMGv6}g, , GmkF9 ~ifi0iVzeJ+{X(TW3@ciJGgZ5<Ϩ8b?WVL0ERS5A-6eW'Ѝ(?4;ye/FU Z>jцFa3#ӵtVf_189Ƕ*s܃޹d*)oU>ufp@(=1#=!G WTfm]tԙͳ5Hj?~_/n#D8׻8+(>&_ xkw*mM4DHyWq`x&OxO( D&@ bfo*.VǡթO<ڌ*a&_|cs:F4c= exú~#u-:YmތF#чPG# *]&Sܓڽ[Xx].n%-yQIX6mz"N<)j֨>~g:Q[^DoNg969?儇|~~|;ொ x$4r)G"Sf!݇$7&3#ް'_OIz/-Dv$^ ~%% >aʸS?Eyϊ> 7C=Pj&'d),2)+$2%FSЃ^_}y>$xs>,$~iv$wb0Wįa⟊73x_2r@AizGaF:yc_W_i_c:亖v ybog_|9-{'^_cS^x , Y r @\X6}|z37OV :G5ÿ w~Kt)5ęydNeBpV4k{;)1$r 2H8 k#c.JRԼr1P;U7Si O#;Uwো<0Mwmn,2;WsW1T3ky>2,|L7WP}N Gc~d9ʈ$7#YHWK33Jay"iKi`>FEckmCRռej.uFM TPG O/+O4q6&h&wŮ`YR7݅*nGQU2|VӼkw_xcz,7Ҥ+X#ɻ3uk.G]&4G4/lbIHcS[ijױ9vw̐Ik-kL<5)>X:vUc \:rk?/<-cNSgl+ct>َ58ؚ 9MˋZPU<|k^3|%?K I$Ȍ~`XG|Y2ikwi6mysoFUo`ag3:dS4u[R3u24Iv'bI54T-,z~cJ4[_>ξ7?l]VIcsǗPhڵ k| .Hf=ǜ;o෋fT_ɑ8e<3)"~)wgÖ vE*H+8>!J+I~}O|fˡ3qCwV#7cJIT Z7?g4i'o j~Z+m*a6#z֌^j BKͤw^Lzʤ{c䱍ي9RI힝kaƦ+k+vW cPIU#FP/f#HFꧨniv>0&tMǺFnurubLA=o~T|ICTi2M̋O6Oz/w<= &s9<%̑Ċ;\ߍ~#|8qKh[-Mk5I1piš&{[@?eucB5+n[?tDxX[*; 6";/ҵWin X<1 Cj:> ~ դ$s^elNyս>7c< OJ5}7Xu]:ao Yz20 "o_?>#]Z2558򼪳M=$޾GRe񇇤}G:4=g7O0Sdlcg /CYY#chϮ/fr>.;ºp뿂lm[ox^)?yk%ֹ/~xTPIӼc,,}g}VVm[ i->E$dUvإIt?&݋k~7Lk1X<7n(4XH^}+dȪf;; H*uĮ>Z Æd뎿m(:mmPKKbι}*`z?zl~&r^ySHF~ǧ'?l] |p|]+\iQiR<ĩoKVq&FTRȀ1#̐_~?~hڍg3OrG٣+"$eLjx/u}_R_;6Q;H5F@AHOS^rSSvG/OL2A8ҼIMLE޹q+\^N!gq9xV]iڷ.|-o)V|:ϯ)GcR?#1CxÏo]6N)E|!oyH[s.QԂr+(k=:7 AKk_ƥ 4GcR᭬k⯎oWIKssXrd-pЄAi$=7[Ꮚς5xS_Gui\%A,3K^ƥ 4G:?ƸzLsϴiqKo(}TCJ04TW<7[(th,:LɊn^UF=:7).~M/-wCkIYJ8, 8&hA?ws EoŎFܬ(x2]?U^J*>r3IPH,m<7xR[Bjv,p?td\ wjil\|Hg$// hu_,my9{:.&Sq=^yᇄ~>&K-NHdin'$9gw<}0+(?>.^,u[thth<{ujC'ԩKo&e+~Z5+gLj[='ngifX >B{<ꮌ#@& +) `z{+׿d|5s~9G˿oI5$\*pWK/<_Z-/NYK巷Z'Gb?_-U'_'qJ^z<a>mu]sᖫ;]]Xipt+l<0/%4$uRΧsM$A'6riF ߭+WSs0 uix?\6Nmfpm"D獑I<2_j_5U,`L`wy~`_8oxƙ t-5Kv01I$ Gi$} :ωP|C-ҭU5{+id?vuy"i3_?e;eݎp^ 3%'RE^J,x 2DyIQnT>b224VxV[-Hyֶ>k;wBd*XpЃxşgkV7M*,[C2`\rvH ʕ5kE_(ҿVKhPnRt絑!e۫0lD_x1fm>g<*2y!G.:O NOot ZEMd6|,8/i2c܀>goJxc⇁uk8>'ep}F}זwC㯅W,Sm3!h`Ă=sQjKG|I̞sL|5t b1X#"G)qki¯w j+j@7;QA0os*gSsJ؟o΍? mP\=Zk 'xdޏMd_@~trI&\݌\|a?^ܑ/T~~?sںO;&#@ lJ08_-y_Z֨>igXQG9Uxb+?'FQ zlM]6uW?1jzܤ |MBqt Z,¥0_+R!͆;6ً\2O~k> f,/ xV y> c]0@Gۢ|/ҟ 4Dͨކ B+~~>x?i>%)mά\\1#=(EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEastropy-pyvo-b70558c/pyvo/dal/tests/data/mimetype/test.fits000066400000000000000000000757001510533647000240710ustar00rootroot00000000000000SIMPLE = T / Standard FITS format BITPIX = 8 / NAXIS = 0 / no data in main file EXTEND = T / Extensions may exist FILENAME= 'swp06542llg' / original name of input file TELESCOP= 'IUE ' / International Ultraviolet Explorer ORIGIN = 'GODDARD ' / Tape writing location CAMERA = 3 / IUE camera number IMAGE = 6542 / IUE image sequence number APERTURE= '' / Aperture DISPERSN= 'LOW ' / IUE spectrograph dispersion DATE-OBS= 'nn/nn/nn' / Observation date (dd/mm/yy) DATE-PRO= 'nn/nn/nn' / Processing date (dd/mm/yy) DATE = '18-Feb-1993' / Date file was written (dd/mm/yy) RA = 0.000000 / Right Ascension in degrees DEC = 0.000000 / Declination in degrees EQUINOX = 1950.0 / Epoch for coordinates (years) THDA-RES= 0.000000 / THDA at time of read THDA-SPE= 0.000000 / THDA at end of exposure COMMENT * COMMENT * THE IUE VICAR HEADER COMMENT * COMMENT IUE-VICAR HEADER START 0001000100071204 1 2 013106542 1 C 1445* 4*IUESOC * * * 3600* * * * * * * * * * 2 C SWP6542, NGC 7027, 60 MIN, LG APER, LO DISP 3 C 4 C 5 C PROGRAM:NPBRB OBSERVER:BOHLIN DATE:1979.2609.260 17SEP 6 C 7 C 8 C 9 C 79260123556* 9 * 218 *OPSDEV14*112438 RDXSPREP 2 IMAGE 5614 * 10 C 091608 SCAN READLO SS 1 G3 58 *112511 SCAN READLO SS 1 G3 58 * 11 C 091623 X 56 Y 72 G1 99 HT 106 *112525 X 56 Y 72 G1 99 HT 106 * 12 C 093518 TLM,FES2ROM *114939 TLM,FES2ROM * 13 C 101100 FIN 3 T 3599 S 97 U 109 *120916 FESTRK TRACKING * 14 C 101150 MODE LWL *121950 FIN 3 T 3599 S 97 U 109 * 15 C 101239 TARGET FROM SWLA *122052 TARGET FROM SWLA * 16 C 101436 TARGET IN LWLA *122532 TARGET IN LWLA * 17 C 101542 EXPOBC 2 59 59 MAXG NOL *122642 EXPOBC 2 59 59 MAXG NOL * 18 C 101755 FESTRK TRACKING *122948 FESTRK TRACKING * 19 C 101919 TLM,SWPROM *123115 TLM,SWPROM * 20 C 101957 READPREP 3 IMAGE 6541 *123556 RDXSPREP 3 IMAGE 6542 * 21 C 102029 SCAN READLO SS 1 G3 44 *123631 SCAN READLO SS 1 G3 44 * 22 C 102045 X 60 Y 76 G1 82 HT 105 *123648 X 60 Y 76 G1 82 HT 105 * 23 C 104652 TLM,FES2ROM *123621 * 24 C 111319 MODE SWL *123646 * 25 C 111545 FIN 2 T 3599 S 98 U 109 *090650 ACQ STARTED * 26 C 111646 TARGET FROM LWLA *090952 TARGET IN SWLA * 27 C 111841 TARGET IN SWLA *091005 FES 761 IN 20 0 0 * 28 C 111953 EXPOBC 3 59 59 MAXG NOL *091059 EXPOBC 3 59 59 MAXG NOL * 29 C 112211 FESTRK TRACKING *091257 FESTRK TRACKING * 30 C 112328 TLM,LWRROM *091415 TLM,LWRROM .200000E 02 * 31 C 112412 MODE LWH *091534 READPREP 2 IMAGE 5613 * 32 C 33 C 34 C 35 C NPBRB*1*02*BOHLIN * 7* *N*00007027*0*0*1* 70 36 C 21 5 94+42 2 3*999* 0*0*99.0*99.00* 0.0 * 0* 120.00* 0* 37 C ' i cf@fh= %cc 38 C 39 C ?4 3 t ] D " 4 3c3D3 4 40 C 41 C 42 C 8 r 4 M , i ) K n c D H 43 C 44 C 45 C 46 C 47 C 48 C 49 C 50 C 0 0 0 0 0 020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 51 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 52 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 020 0 04040 52 C 0 0 020 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 53 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 53 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 080 0 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 04040 54 C 2 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 55 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 55 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 020 0 0 0 0 0 0 0 0 0 0 0 56 0 0 0 0 020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 04040 56 C 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 020 0 0 0 0 0a0 0 0 0 8 0 0 0 57 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 57 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 58 0 0 020 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 58 C 20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 080 0 2 0 0 0 0 080 0 59 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 080 0 0 0 0 0 0 0 0 0 0 0 04040 59 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 020 0 0 0 0 0 0 0 0 0 60 0 0 0 0 0 0 0 080 0 820 0 030 0 0 0 0 0 0 0 0 8 0 0 0 08030 04040 60 C 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 8 0 0 0 0 61 0 0 0 0 0 020 0 0 0 0 0 0 020 0 0 0 0 0 0 0 0 020 080 0 0 0 04040 61 C 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 2 0 0 8 030 0 0 0 0 62 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 8 0 0 04040 62 C 222 0 0 2 0 0 0 2 280 0 0 0 0 0 0 8 0 0 0 0 2 0 0 0 0 0 0 0 0 020 63 0 0 0 0 028 0 0 0 0 0 0 0 0 0 0 0 020 0 0 080 020 0 0 0 0 0204040 63 C 0 0 0 0 0 028 8 0 0 0 0 0 0 0 0 0 0 0 0 0 8 2 020a0 0 0 0 0 0 2 0 64 2 0 0 0 0 0 0 0 0 8 0a8 0 0 0 0 0 0 020 0 0 02020 0 0 0 0 0 04040 64 C 0 0 0 c 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 028 0 8 0 0 2 0 0282020 0 65 0 2 0 8 0 020 0 0 0 0 0 0 0 0 0 2 0c0 03020 020 080 0 0 0 2 04040 65 C 80 0 2 0 0 0 0 0 08020 0 a 0 0 0 2 0 0 080 0 020 8 0 0 0 0 0 0 0 0 66 0 080 0 0 0 080 0 0 0 c 0 0 080 0 0 8 0 0 8 0 8 0 0 8 0 020 04040 66 C 80 0 8 0 018 0 020 0 0 0 0 0 0 0 0 080 0 8 0 020 0 8 0 0 0a0 2 0 8 67 0 0 0 080 0 0 0 8 0 2 0 030 0 0 0 0 0 0 0 020 020 8 8 0 8 0 04040 67 C 0 0 0 8 02080 8 0 0 0 0 0 0 0 0bffff0 0 0 0 0 2 0 2 0 0 0 0 0 0 2 68 0 0 0 0 3 0 0 0 082 2 c 0 0 02080 0 0 0 0 0 0 0 2 0 8a2 0 0 04040 68 C 0 c80 8 0 0 080 2 0 0 022 8 880 0 0 0 0 0 0 0 0 8 0 0 2 0 0 0 0 0 69 0 0 0 280 0 8 0 0 0c8 08020 0 0 0 0 0 0 0 0 088 0 0 0 0 8 0 04040 69 C 0 080 2 0 0 0 0 08280 8 0 2 8 0 0 2 2 3 020 020 0 0 0 0a0 2 a 0 0 70 a 0 0 0 2 8 08080 020a0 0 2202ffff0 0 0 0 0 0 0 0 0 0 0 080 04040 70 C 08222 c 0 2 0 0 0 0 282 0 080 020 0 0 0 a 280 0 0 2 0 080 0 820a2 71 22 020 020 0 8 020 8 020 0 0 2 0 8 0 080 2 280 08022 2 0 0 0 24040 71 C 2220 8 0 8 0 0 0 0 0 2 02080 8 2 0 0 0 2 0 020 082 0 0 0 0 02020 0 72 02088 0 0 082 82020 0 280 8 8 8 8 820 0 a8020 020 8 0 2 2 0 24040 72 C 0 2 0 8 0a2 0 088 0 0 0 2 2808020 0 220 0 2 0 0 0 8 0 a 0b0 0 0 b 73 280 0 0 0 0 0 0 0 2 0 020 080 0 0a080 8 0a0 0 8 0 2 0 fc0 0824040 73 C 0 880 02088 0 2 0 0 0 2 0 0aa82 0 280 0 0 880 08022 0 0 a 288 0 8 74 88 c 08a28 0 0 a 080 0 820 0 2808220 8a0 0 0 0208220 0 0 0 0 34040 74 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 75 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 75 C c1af6fbcc 0 0 09d707b 0c6 e 03dc8 c32474e366a29253f47413320314d64 76 29 03d8af6 51a1c9090 05e7d8233122e2a19282e1d32 f18117da5abab 0 0 0 76 C ac30 0ff 0 439abbba0 0445aa4 4371f2a31342b1d1d2c2f151914161d502d37 77 70727e207b197a227b14259a1e92209420951e96 1 0 1b8e1 0 0e02ce0fa3540 77 C 7b 07b 072 07b7b7b7b7b 039 0 07f4c4541ae547c979951ceae9580808645 0 78 03a3234363a302f2f3335332b35bdad 6c1f6568282 0 1 08282404040404040 78 C 0 0 6c2f2568381 0 1 080803b3535343b35313036373633338977877945776b 79 796a7b2f7f7e7f7a7a34777d7f7e8e487f40404040404040404040404040404040 79 C 993837611019 e89 0cc 040404040404040404040404040404040404040404040 80 404040404040404040404040404040404040404040404040404040404040404040 80 C dfbffbe 0 0 0 07f374399 2 2fe66fb30 0 0 0 0 0 0d0 0 0 0 0 0 0 080 81 0 0 0 0 0 0 0 0 0 0 088 0 0 0 c 0 0 0 c 0 0 0 0 0 0 0ff 0 0 04040 81 C 4f1960 0 0 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 82 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0 0 0 0 04040 82 C 83 C 84 C 85 C b2b 0 0 017 0 0 0 689a1e02ce0e1 0 021 0 016809e32 0 0dcc1f5a15e3b 86 b1dad9565668c969c92fc9 0 061415142 0 0404040 2 3 c d e404040404040 86 C c12 0 0 0 5 0 0 0 38a9ae02ce0e1 0 026 0 016809e32baab 6c1f2568381 87 0 1 07f807e7f7a7a3477 0 061414140 0 0404040 3 635 b c404040404040 87 C c1a 0 0 0 5 0 0 0 38999e02ce0e1 0 02b 0 016809e32bdad 6c1f6568282 88 0 1 082826b776a7b2f7e 0 061404141 0 0404040 2 432 b c404040404040 88 C c25 0 0 0 0 0 0 0 0899de02ce0e0 0 034 0 016809e32 0 0d8c5f2a0ab42 89 acdac579927eca7aca34c9 0 0714a4141 0 0404040 3 7 6 d e404040404040 89 C c30 0 0 0 a 0 0 01e89a1e02cdfe1 0 01b 0 012809e32 0 0d8c1f29f493f 90 addad581637cc97aca34c9 0 071424141 0 0404040 3 312 d e404040404040 90 C a f 0 0 016 0 0 01e8a96e02ce0e1 0 035 0 012809e4abdad 6c1f5568282 91 0 1 082826c796b7b307f 0 061404141 0 0404040 2 42f b c404040404040 91 C a14 0 0 0 0 0 0 019899ee02ce0e1 0 042 0 016809e4a 0 0d8c4f2a0ab3e 92 acdac679927fca7bc935c9 0 0714a4141 0 0404040 3 73b d e404040404040 92 C a1a 0 0 014 0 0 0238a9ce02cdfe1 0 042 0 016909e4abaab 6c1f2538381 93 0 1 080807ec97aca35c9 0 061514141 0 0404040 3 1 8 d e404040404040 93 C a20 0 0 0 a 0 0 0 5899be02cdfe1 0 034 0 016909e4abc6d 6c1f2528381 94 0 1 080807bc97bca35c9 0 061514141 0 0404040 3 122 d e404040404040 94 C a21 0ffffe2 0 0 02389a1e02ce0e0 0 042 0 012809e4a 0 0d8c1f2a0ce39 95 acdad481637bca7bca35c9 0 071424141 0 0404040 3 321 d e404040404040 95 C b e 0 0 028 0fffffb8a9ae02ce0e1 0 034 0 016809e52bdad 6c1f6568282 96 0 1 082826c796a7b307f 0 061404141 0 0404040 2 631 b c404040404040 96 C b14 0 0 01e 0 0 0 f8a98e02ce0e1 0 02d 0 010809e52baab 6c1f2568481 97 0 1 080807e7f7b7a3479 0 061414140 0 0404040 3 4 0 b c404040404040 97 C b19 0 0 02b 0 0 01089a0e02ce0e1 0 024 0 012809e32 0 0dcc4f6a1473f 98 b0daca768e6cc96ac930ca 0 06141514a 0 0404040 2 729 d e404040404040 98 C b24 0 0 0 d 0 0 019899ee02cdfe0 0 032 0 012809e32 0 0dcc1f5a15b3e 99 b1dad9565669c96aca30ca 0 061415142 0 0404040 2 327 d e404040404040 99 C b2a 0ffffef 0 0 023899be02cdfe1 0 02e 0 012909e32bf6f 6c1f6538282100 0 1 0828268c969ca2fca 0 061414151 0 0404040 2 1 7 d e404040404040100 CCOMMENT IUE-VICAR HEADER ENDED HISTORY IUE-LOG STARTED HISTORY *GEOMF 11:20Z SEP 21,'79 HCHISTORY ********* GEOM. & PHOTOM. CORRECTED IMAGE ********** CHISTORY PCF C/** DATA REC. 11 1 1 1 768 8448 5 3 6.1 5.0 2536 .00000 1PCHISTORY 0 1684 3374 6873 9091 10586 1PCHISTORY 14371 17745 21524 25105 28500 1PCHISTORY 11.000 11.000 11.000 11.000 11.000 11.000 1PCHISTORY 11.000 11.000 11.000 11.000 11.000 1PCHISTORY TUBE 3 SEC EHT 6.1 ITT EHT 5.0 WAVELENGTH 2536 DIFFUSER 0 1PCHISTORY C MODE : FACTOR .178E 00 1PCHISTORY *FICOR5 11:20Z SEP 21,'79 HCHISTORY ******** DATA FROM LARGE APERTURE ******** CHISTORY *EXTLOW 11:20Z SEP 21,'79 HCHISTORY @EXTLOW: OMEGA= 90.0, HBACK= 5, DISTANCE= 11.0 CHISTORY :HT=15, DC#= 1; ISN: 0 PSN 1 SIGS= .444 SIGL= .421CHISTORY B 1= -.283235346667D 03 B 2= .376096600120D 00 B 3= .000000000000D 00CHISTORY A 1= .964207510446D 03 A 2= -.466539532721D 00 A 3= .000000000000D 00CHISTORY LINE SHIFT = .000 SAMPLE SHIFT = .000 CHISTORY *SMOOTH 11:20Z SEP 21,'79 HCHISTORY *ARCHIVE 11:20Z SEP 21,'79 HCHISTORY *ITOE 11:20Z SEP 21,'79 HCHISTORY ***** FILE OF MERGED EXTRACTED SPECTRA ***** CHISTORY *** GROSS, BACKGROUND, NET & ABSOL. CALIB. NET *** CHISTORY *ETOEM 11:20Z SEP 21,'79 HCHISTORY *ARCHIVE 11:20Z SEP 21,'79 HLHISTORY IUE-LOG FINISHED END XTENSION= 'BINTABLE' / Extension type BITPIX = 8 / binary data NAXIS = 2 / Number of Axes NAXIS1 = 7532 / width of table in bytes NAXIS2 = 1 / Number of entries in table PCOUNT = 0 / Number of parameters/group GCOUNT = 1 / Number of groups TFIELDS = 9 / Number of fields in each row EXTNAME = 'IUE MELO' / name of table (?) COMMENT IUE MELO file data containing G, B, N, A, & E vectors COMMENT Each row contains order number, npts, W0, deltaW, & vectors above TFORM1 = '1I ' / Count and data type of field 1 TTYPE1 = 'ORDER ' / spectral order (low dispersion = 1) TUNIT1 = ' ' / unitless TFORM2 = '1I ' / field 2 has one 2-byte integer TTYPE2 = 'NPTS ' / number of non-zero points in each vector TUNIT2 = ' ' / unitless TFORM3 = '1E ' / Count and data type of field 3 TTYPE3 = 'LAMBDA ' / 3rd field is starting wavelength TUNIT3 = 'ANGSTROM' / unit is angstrom TFORM4 = '1E ' / Count and Type of 4th field TTYPE4 = 'DELTAW ' / 4th field is wavelength increment TUNIT4 = 'ANGSTROM' / unit is angstrom TFORM5 = '376E ' / Count and Type of 5th field TTYPE5 = 'GROSS ' / 5th field is gross flux array TUNIT5 = 'FN ' / unit is IUE FN TFORM6 = '376E ' / Count and Type of 6th field TTYPE6 = 'BACK ' / 6th field is background flux array TUNIT6 = 'FN ' / unit is IUE FN TFORM7 = '376E ' / Count and Type of 7th field TTYPE7 = 'NET ' / 7th field is net flux array TUNIT7 = 'ERGS ' / unit is IUE FN TFORM8 = '376E ' / Count and Type of 8th field TTYPE8 = 'ABNET ' / absolutely calibrated net flux array TUNIT8 = 'ERGS ' / unit is ergs/cm2/sec/angstrom TFORM9 = '376E ' / Count and Type of 9th field TTYPE9 = 'EPSILONS' / 9th field is epsilons TUNIT9 = ' ' / unitless END xDz33@)FFDFϜFXFlZFyFFleFtFF F|FFDFF«F?FFFFFBnFArFFBpFjFFIFF\FhFF}F`F|FFF^FFjF}FDFFyFvAFnF F1FFFFFpFcFFFFaF)\F]FmF(F_rF}fFFFɽFXFF]F$FFFFEF^G"GGHZ\HiH'HH-H W\GBWFEFˇFFFF|FFF5TFWF@FYFFF>F})FF"F/FciFC$FFEF"F#FSF;FwFFFeFFFFOFGFFvF?FFS FAFF"FFaFDF%tFHFFX5FF;FsFXFFDWF]F{F_tFPFF&FFPF%FF F'sF4FNKFFF)FZFFF@FhFBFF4FF-FFF@FzFFFFלFF9F&pFҜFFwF2F_F6%FFIF,F͇F{8FQFFFFFWFb(FFF_FfFBFFAG'GH.#HEJHZGG FhFHFKF=F^FvFFFF,|FFFژFF8uFF[DFFcFFמF@F F>/FKF%bF|FF2G-XGGGKFBFgFFTFFںFכNF?F8FJFFFr4FFoFF%FF`F+'FF`'FxFrF;HF_vFFFfF/FiFFFoFFF/FF+jFmFF>F22F)FYFZFF;F"FxaFEF F_FIFbFeF6FaFWFFFFͳFx|FFVFiFtBFF/FF@FFEzFFF߼FFF#F3FXFH9FnFaFFFFgFFFF3FFEFFYFF\FF#FFTFFFFFFF˝F-FF]FyFFu&F(F+FFFSFk FfFZF_FhFqyFFF!F`FFEFTF݆FRF;F FPFЕF6FF|KF)F>NFFFuFFF$FFYFFFFfFFFqFHFUFjhFFFmFwFF9BFF%FEFMFFFF0FbFnF,-FѵFFuF`FFFF#FUFuJFZFHFyFFoF FFF#FF}FFFF&FjFF{FkFrFSFlFF}FF8"Fq@FFcSFUFF0LF߼sFk;FFFuF F^FFFF\/FFF5FmFWeFPFrFzFg_F*F~FFΒF|\FFFXFWF:FIFFoKFmFZFc{FQPFx@FFFF@FiFFF}FsFFFFFSFXFFvYF(=FgFF=F#F FVCFkF4FFUFrFFG$FaFedFFFwFaGFnFdF9F|>F7FFyFJF=FFFF3FF 0F:FFFFTFFYFuF$~F:FFXFF#F?FFRFFeFF FFFFFJF$FTFE"F]FlFFAFFF,FwFFECńz~DzBDg_&EC9DD5E]Aɴ,:<D#ىC)̉BWC0C0DE,yD> ŎŏJDκES'ð<ENEn)E>Dy%5BE RD;ď&E45E"!ľDl:`Ľ*D5Î؍ԩzQmgpj1CE 2ECkDnÕX5DȯC<6Bt?COpuJoYjDDńC{DVE NPkxD E^E)FGHDH7H@HtHxɡGFרEn7DיšܤDҦ)Ey%E NE?DES2DByDҵCCU DS3ĒĬPC30E&CGD 8CD$C$xCC'DD|CYkjdECCY.ą"D DDE R£Ƞ?ĈOC ŎŏJDκES'ð<ENEn)E>Dy%5BE RD;ď&E45E"!ľDl:`Ľ*D5Î؍ԩzQmgpj1CE 2ECkDnÕX5DȯC<6Bt?COpuJoYjDDńC{DVE NPkxD E^E)FGHDH7H@HtHxɡGFרEn7DיšܤDҦ)Ey%E NE?DES2DByDҵCCU DS3ĒĬPC30E&CGD 8CD$C$xCC'DD|CYkjdECCY.ą"D DDE R£Ƞ?ĈOC This is a “light” version of the full Gaia DR2 gaia_source table, containing the original astrometric and photmetric columns with just enough additional information to let careful researchers notice when data is becomes uncertain and the full error model should be consulted. The full DR2 is available from numerous places in the VO (in particular from the TAP services ivo://uni-heidelberg.de/gaia/tap and ivo://esavo/gaia/tap.vo-dmlhttp://www.ivoa.net/dm/VO-DML.vo-dml.xmlstc2http://www.ivoa.net/dm/STC.vo-dml.xmlndcubeurn:dachsjunk:not-model:ndcubedshttp://www.ivoa.net/dm/DatasetMetadata-1.0.vo-dml.xmlivoahttp://www.ivoa.net/dm/ivoa.vo-dml.xmlivo://org.gavo.dcGAVO Data Center1TIMESERIES0BARYCENTERTCBJ2015.5ICRSObservation time (JD in barycentric TCB).Integrated flux in G; Use -2.5 log10(flux)+zero point to convert to magnitudes, where zero point is 25.6884 for DR2 fluxes in G in the Vega system.Magnitude in G, Vega system. Converted from flux using the formula given there. If flux_error/flux<0.1, you can use 1.09*flux_error/flux as a good estimate for the error; else the distribution is so skewed that you should work with fluxes rather than magnitude.Error in G flux.Gaia DR2 source_id of the objectPublisher-assigned title of the data setGaia DR2 RA of source objectGaia DR2 Dec of source object
2456920.5128546842921290000.02.0244633198665625106712000.0
2456920.5868590062659600000.02.12635918930161776519500.0
2456934.71934151562149850000.02.357379601891061323700500.0
2456934.89549362941934760000.02.471832249920307621371500.0
2456961.89449747331734390000.02.590533097882719547525800.0
2457003.4517497892104220000.02.38067213968078322162500.0
2457003.62791153141916840000.02.48193534119698727515800.0
2457031.44824773961690810000.02.61816298069168623871700.0
2457159.5289318261786750000.02.558240523066988533130900.0
2457176.6052401572000550000.02.43552647443061717323600.0
2457176.78139499341995940000.02.438031295473038531137300.0
2457176.7813949941865150000.02.511615588522033735917300.0
2457217.48488136571567590000.02.700318789111836519312300.0
2457217.5588855411545590000.02.715664252034944317547200.0
2457243.22822660380051200.05.9299803836130423535790.0
2457243.2282266511594470000.02.681859119019044618471700.0
2457276.9587075891714040000.02.603347618259917324701500.0
2457277.0327569814386006000.04.2219148617232065148650000.0
2457314.51514880643684.4916.772456544623715137729.0
2457314.69113340551330620000.02.878264882849528332975800.0
2457340.69249006731747150000.02.582574518630597324057400.0
2457340.7665252461694830000.02.6155846430100111095400.0
2457381.17602146931449470000.02.78537692226139927228900.0
2457438.90777355531788970000.02.55689235560183215526400.0
2457494.62331029451770970000.02.56787198909823326964400.0
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/000077500000000000000000000000001510533647000215265ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/query/basic.xml000066400000000000000000000016051510533647000233330ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/dataset.xml000066400000000000000000000021701510533647000236750ustar00rootroot00000000000000 OK
image/fits http://example.com/querydata/image.fits
application/x-votable+xml http://example.com/querydata/votable.xml
application/x-votable+xml;content=datalink http://example.com/querydata/votable-datalink.xml
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/errorstatus.xml000066400000000000000000000016131510533647000246460ustar00rootroot00000000000000 ERROR
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/firstresource.xml000066400000000000000000000016051510533647000251510ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/missingcolumns.xml000066400000000000000000000011421510533647000253200ustar00rootroot00000000000000 OK
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/missingresource.xml000066400000000000000000000005431510533647000254730ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/dal/tests/data/query/missingtable.xml000066400000000000000000000007301510533647000247310ustar00rootroot00000000000000 OK astropy-pyvo-b70558c/pyvo/dal/tests/data/query/overflowstatus.xml000066400000000000000000000016761510533647000253710ustar00rootroot00000000000000
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/rootinfo.xml000066400000000000000000000016041510533647000241100ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/query/tableinfo.xml000066400000000000000000000016061510533647000242160ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
astropy-pyvo-b70558c/pyvo/dal/tests/data/querydata/000077500000000000000000000000001510533647000223605ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/querydata/image.fits000066400000000000000000020205001510533647000243300ustar00rootroot00000000000000SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T END XTENSION= 'IMAGE ' / Image extension BITPIX = -64 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 256 NAXIS2 = 256 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups END ?=;c?LRW? oʱ?a ?n}?LGx??B64T?㲙E`?q}S?\0 ?Yv+ ?i`?Ʉ\?٬X?6GE?ڰc?w/@M?w}d?f?^rR ?P?+4?dsHM?ȵCrw?L?%!?= ?HtR?R3{?@қ ? ?KR?۸5r?u|-?"{ʌ?ɶ]>?}?F4C?ǎb ?7X?̆@?B?SH?ͷ]?K ?{?=R'?h?j?鲡K ?4o4?~0M?cQ,?Ta |?X@?(?F?gz?ߚWO?g?/b?tU!?ŨR0?̿(?ub 9?Z?@?ܺ\F?NX͓D?ؗQ?HL?r o?d/a?׉ ?ev?NV0?jW+?`-rt?48?D?D?aW5 z?A?s%;d?Nk ԛ?E&sM?(59?R?ɸ52?h'>?ҲP ?d>]u?ڐA J^j?1?.x?%?hf)?ؘS-t^?z"?r?/.?O??q_Q?%je"B?_T?>dٺt?+v?rd^?ZF"?fBu?#bX?L#?Ϲ?/$?ӱ ^?.T"?5*?y+U?f#?}q^X?˃$mtc?2{?gf?Bd? `yZ?.v?ۮώAf?ߴ?퐖()$?фY?>:@? i?E@ ?)aު?6) ? V?Q+=G?U\2`?ئy헞??±D?BdB%?zbe"?{IU?*`?z/?z??n?2DV.?Sl?T}t?]lL?tm ݤ?>T?v ?-3$Y?^r?`vW ?Ѹ?͚??V?﮳?׺?螸46?ޜ?l J?p#t"?6U? bU?[`4s~?ל?'?dj&?+ ?Ӑc?W U?=?V=?S?/?ө?h?$8v?5T0?DV?3>?HĦ+?w]7?ʊ ?ӡҢ?̻VfW?QCZ?܉tߊ.?c? bsc?,m(,?WW/?_??<k?HYB?u?/ 9'?yH ?͇5L?ᙖS?LLju?z$b[?}jm?O~?r?-<-?9?[?uˈ?(&?50F33?c]?+)? q?)?<>?txN?0M?g& ?T?7@??I@?m?-^\ʶ?ضː L.?$ ??f&0`>?PG?s/?jk?Kf|? n ?dx?+W?W@?ܾs?㆝L.?~3?wp$?E^M)?eF@?t0?Pp? |?̈́!D?EKt?pb~μ?L R?mBe6?⒖@?i?l?A6.?j?K_-`?,Cp?dQ?V9׳?j/>? QD?(4`?ԫN?Jul?҃$L!?Љ-?]@?︳b?NC?Lj?+?ꖢ$?P'?6?ԥQ?GG?م?L~gVp?AAb?Z@?Y3?+"?@?@P?0?Dr?]S|?[a5?zk@?,O~z?QZz?1S?ֹٺ?.?54?F UU?ݸzb?J!j?ǧ9?櫲~? -?p?RP٘?ޔ.8?&_?jmp?>6?HC ?B ?G^29?bܟ?g ze?v *ڤ?;6i?c ƙw?K?8I?a?s?z ?Ŷʤ?قWG?В2 ?|H*?n6?٨El?H`?=`j$?#d@?vr?wa?ц?wa,?3]?He&?Fz?w͎?ĝAW|?m'Q?թ#T?3y>x?sC?q`Y?/??ͭ lSt?GG?p?tʦ??}Dg?8z,?5wn?0m?yJ8?ۭFx?3TF?Wpm;?5OW0?_x?x)g?i?`?h",? ̨R?+]?5-l-t?x?WW7 ɶ?Tp ?mcX?7"2h?SzI0?/c?وLkY?G~?k?f-(?5ٕ?ղĸ?VP?d<(?N]?gЕ?qk?[j? L?d;?'!M? ?^W#D?l?r?ٖ3?SJ*X?~n%??@?\d=0?0?'z9"y[j?W[?W48f?^ O?]?_ ?YU? s?h!?&z8L?SE;?u%fw?P]al?" 5?k`n?eUlX?R^j'? U$?8jv?Fr?X?=|?9>?eGQ5w9?)@?b"?VZ3vg?W0?cvX?DP?źD ?\?DBB?؅47?։Y ?1 kA?4?*G9n?$tXI?8?}ȇ?湫}?[zH+?ꇋI?FS?P{?f}%?̪t?Dx)w?|>?H?t7?U?Շc? " ?k]?Uvz?'v?p?L\ix?FkS??壾?g?CPE?)h*7f?ov45?ӭ8]Ռ?,K?4b\?I?HzV??ɳq?d@?1?)?CVgP?ۣVΖ?;>BK ?&~4?ז??W܁A ?0gR? cn?Ŝ?gx?3h2?E,?H?nd 2?x`z?!P??| $F?kZ?Վ?ہ-?6 3`?钸o[^?<.7? T?o0? d?1?s6?GB?o7Lr?f%Q/?)%K ?I.?'iU?=Ee?ܷ"?ߪ?tsh~?n:?]?l_{?Y>?Ѷq?,d?ظt??G&8"d?e.`p?&Ebո?os?Õ/)'?DE?: ?҉c?:eR? P[K??ǿfD?lW?rW{?'?X?.I°?ctUz?S?豈?% ?ɋi?z<}?գW?aa/?8>G$?Bt?O7?F?؎J8?Ō"*?˷tv?j+pS?f?d4?qM ?UU?Ɠe??+Ei?3?-vF?dN?.A~??sL?ߞ((?PQRv?ѪYMP?|OX|?~C?8?,Ӳ?tDbu?3;?'E?/`?Á0.d?n@Z?^?,]y?nr/X?šEg?c ?zF??D?֣g?/9%d?QJ$?|fH?y.规?[E?Ҩ$? @?:"b? C;>? hP?†=? @?kT?> ?IB2?PZy?2G?8j?u蜨?-Ў?2CP@d?h@?q?EBXP?R-?0Ѥ?cCMa?65?p ?eW?"? 31?WjU?M ?!? 00??䁢L׀?xi}??iN?`?mﷸ?ѱ pId?!4L?tR?MHnZȨ?$gH?Đ4 m?X1 ?꾰0?i~?J@_q?,I|n?vp?,.M?R>Yt?fB?a?Vw? ?өr%ڀ?8&%?T☿?Bϝ?@|H?l$8D?Џ ?Vb{?e*i?A2K$?䄖ю3?΂-U?3ûh?>M?4_z'?d#?||?Mk?`Fͽ?[z)?A)?ㄱ(ѽ?Ey?r?K X?؇'U,?Ĥ?>h?#>?;S:?,b'?5i%Z?o? h?`??H?Jfu?H?֩3XL?߭]% ?h_p? S!?q C?vfN?2=?Ata?\!?_?b?9?u ??tUў?oyv?$:?s xԮ?;l0PE?y?Ԋ ?=py Y?Ȑ?A _l?C?h^?#DY?|70?\2?x"e?|9p?UC,?wʼ?[3`?x ?_h H?Oh?~pOC?žx?\N4?w=(+6?㜘' ?& ?ɂh?aՙ?ۂCax?Uz?T Z? ʫC?=o0?͉ν?zOX?n7?tem?ĉ@u4?)m(?NC ?a{?Hsȅl?AǀL?ր??gUeQ?/j?4t?ƣm?^&?H1:k?Dy?t?GsP;?㔿@l?P ?ܳV?!y7?:98?0r@?*[??#kFT,?x?\z B?iR? W.?O(?м$a|?h'x?*3\? l?w0?8P?aM?B ?ݨlٿ?,#J?oLqh?W?"t>?Ψpp?,~n?4%XG?>F?I?[lk?X?ƎK+t?ֵ}'?E$d?;L?vRfǼ?f/&?êO?ޜ6U? YY?w%N?'h?!?Ii^?)۫>?ڳ0~?C?p{~?%O5/? ء#(?Z1?z?w .C4? wْO?Mx ?b?[)P?1H6)?Ү ?Nle?ƱsB?tnP?zV. J?u_l?@x8G?C5e?.b`p?‚/r?.Y9?܋^gn?=qO0?}kv? eYB?O:u?7Ȍ?MdP?\ٗ j?۷z(e~?eHG?,V-t?/n{ ?́#?Hӟ8?L?G.n?9j;?"<?Ç׀?Fsf?z?؊ r? -A?{z?V?Q@?.I!o?1{1?C,?h??Erp?\S?#ѯ?i,x?[1[n??B/r?G?a@?%ę?/m?鑗?Jh?⾼f?" %?y-m6V?.yi?P-F`?̡}?p#a??-HR[4?p?mx?aՆ? ]Wz??]wY\?KA"?;?ZZ?A$??#f]?kMZ(?uFl?&׏D? &4?1O?zQ?B⺮?ݔ*?mf?!?*щIN??" "?y8t?稡dS?~K?إGK:?쩛Bt?<8?L?"78 ? 9?#cH?_?=>?4H?坤;?.?TF ?<0m[?h?Fdr|?W)N?$> C?ҘG? ?d b?^,*?:>z?/t!0?A6>a?ƞ.GW?۟V}=?9?g3k?xД? ?w"`?~fP?4?֗Ղ?,^~?U:{B?,[H/?`1?ږ&>?jl?l}?nj?~X?yK?>N?,M@,?Q?5?~>:?w?r? J?mpl?LL>?S^?b?}y0?+?wj2k~\?΅g?1 ΍?8- ?Ғ O?ƑEƶ?>Q?}I?M? H?x m?A ?9i'qV??:wR-?*iJى?J=d?IEP?iA]?94?61v?#.?ƾI??OrY?M-A?Gm|a?,? nS?&6?6wn ?ԤN?V)?TlP?X8r?0\?=^ ? $ƍ?O2?M?GB:{?*K?ӫA?^?ĵl?USP?ѱKf?3?:M??|?o ?_ ??iZ0?wm ?Cz,?К??E?8?AKve_?p@$?fS?'_ X0?s+dv^?Ѻ3?'ߘ?-?3lư?Л>@?+|r?eh ?M`1?s@'g?i?;H?mu 8?ʣ5t`?Sf-?ֆO%va2?ꤧ=?sD?֫t/J?8Q d?v=?7Ǥ?eU\?K06?TA?"v?uQ?$?T*?o ?L?c*5o:?ża{D?Zk?^K?%* ??@KD?|g?D%?3X?C?NnX,?n0?؊Z??}?֞; x?٭GEiA?D_?fPW3?4\q f?g]?N^?Ҍ? .;?\/?ߏJ(G?J4`?s{?LH??Eox+?3Ey?A ?Bn?8G4?9?w˧H?t]?7?D'?sP?؆oQx?G+{?q镳?oL?&OGŕ?D??V 5O?. ?>?кRC?P̀?PU?j80?nW7?@Bx?@k$.d?^JP?dsp?޸K$|?0Z"? ?TR?? Ƙd?`lh?{mWB?e4#r?}?}2C7?~?پ%?k?q ?kZ׹?w?äI?nBA?.^M@??߉ }?x6?薾 ?/(8?d` ?8 U?딐m?hP?$F|d?팖>?wT?#b]?om?JXD?M׌?Ixi>? ?߷6PZ^?旛T?#ݞ?&Rtj?rzk?L! :?o?s$W,?G}/?(CY?tcw?+'V??X2?϶i?Í?ֽ?Ӵ?ڼ@/Ǽ?"Mk?+#?⦉??ozข?#jT?R9@?ރ(?<@0?-ߖ?/?CH?ɴN?¥3A\?%H?rRx?"l{?4edg?M ?nWS&?B??Բi~?$1%Z?W)Q?yCXK?C؇?ǭX.'?-?{OS?Jqn?࿾6d?`?<?ӻ \?`GD?MW]??? :X?ổ*?7k4?Q ?vB۹p?@B?63(h|?ݐl?`C?kT'?܂6?ɿ?98Y?؜P?vV}$?Go?FcbB?P&q?u\?ݲ2?>ӮP?^3?-M?7ݎ?Dfy? R?vGy?FW?ZHml?wqo?D9?Ҭ _S4?a%?ꔢT?5`?5SB?Hn?\?Ҍ~ F&?tЧd? K?byE?Yd?k?Vo|?I9??jӴb?t?簼ӌ&?yG?Cc3?ڄ ?jӈ?;c?ٞ3w?ҏjz?ؕ~?QV?5?Ӄ?䇏4g?eys?g{'?̚o`?'s1e_?ndq?ԁ?R?qH?նI^%?15~6~?KH?ƵfqC`?%/?%i.?H?=?s%.?歎? ڒe?k ?6>?Z1?؋i6"?N+,??@c?95?I7 ?a@h?zM|(?h S?r✜?p?(XB?ꚳL?'?—~5?(rx?{$?޲͏-et? o?my? ZE0?|a?\C?$eqA?[x?&_?ۮ?QQ? "?FH?G߱? ?>,/G??Mw&?Ǡ?v?g? V? ^?s9 [?\F"ے?p2?__ ?ԂՆ?s 7 ? C sd?1È?d A?*1L?T3^5?Wqo^?s?_ȶ\?;75T?W;2d?ޕKY?kG?NI)?8zq*?GQ9,)?Oq[?" =>?ԁ<(n?ؐkt?P*Y?]wE?-7T?z!?煆?v|mX?f)#?OV]ѣT?Ivb3:?5&?꾔z? U{6 ?ֵe'~?7yl?L q?Jʀ?k9?RP?⇅?;?ۖ! e?/g1?ew=?mbx4??39s?kH?Uh5?!Rtq?sa6w?_),O?֒':B?~a?Zݝ?`7?t:-?tj0?P~?cmv?m?͂úl?鵕?وnX?.ו?ڮԔ?*Pp?Gs1?5\#P?l ?av?^?zC?"!x?;?n~?&X?庸>f ?ݺd+~?WK?ʎ:Slt?l#Z?b͍?J3\??e? v?ChRH?p(K?꘮?*AC+M?ݹ|?#Y?,Zk?吴7&? ϣp?Rz?4X?ۏ(?o2?~s `?ʫ*D?ZǥT?ٮG,?uأ,?RkV?|?䎃 ޠq?8|\?I,<7?8=?EX{ ?ӷt?z_3G0D?"rcj?cfQ?PHɸV?T3|6b?O[?BYt@?Pb?=pR֤?5ZD?LcI?吓%Ҙ_?O$?ғh~?r5M?o?"r+r?9LYD? P=?.!R8P?y<g?kMߜd?l?ÛTS?4.?Ker?9E?xG~?P??)?&(?$RLsl@?Pek?=@?bD?2X? /x?̟ZR?R?\p?I?P(?.?? 0?('%c?۔=N? ì?e4?نz f??oX?F2?2u峧?чs?݊,h?T\(?U-Nj?eͰtj?;>O?S?/ιՠ?ʍq`?f.?j(?˛?\X?7(?U?<ڌ?ڠwM?<;G?Cv?8O@?&FV?qy?tr7?jY?ɺ-?N' ?&c[?j;?Ld M?{=z?^?Yvex? Yt?|1/@?8$?0:ڔ?˜?5A?&HX?9?$$@?2]#Tp?p?7?' ?ǫٻd?!G,?,O?p?b*?%;?8A?,0?葀'?I??Ď ?Ќ|'?@ِ?P?$?i4 I?./?4%?`mEg?CP?.uE?)N'?rOp?ׂ?˃Sm?̲dd?U#+z?۾&?*$?OQ?*V?U?kL[?ѕ.3?O:H?)9U?ޘ3 Y4?ͭ?xp? X?(i6:? ˊ?I#?.\?L?&Z@? 6Z?opu?(j*>?F?W? _-?Kޏڴ?7lK? 6?I?) &U?A?'q_? I?b4Wj?ҬiYW?ׯ2v?:x5?5×~?PK?5@? ?k?lb?ⴧ@?Ţ?5?VZ?הA|?GNb?Q@?2;w٘?C ?GL2?Ġ?_? 'r?13?Wu>?}?<`,?DZ.? ^NC ?Z?\.嗬?X?#?R'Ƞ?(7ȶ? +*??c?c?p y? ??9^P?ZѸ?s4?S ?.QӬ?ћ{? Fp? ?fS=?s"݀?l<"?KzA?FFW?7?dG?gYCI?{?ώM'?iv?=vҪ?͗T?w,a-?4 &?*6y?j$0?ens@?Ռ{?톊?'cx?O?du*?t;D[??ÓIB?ٙ ?Gܼ?h?܊n~h?QpP?t]~?֝JUN?ҾU?Q(?O(?Ʀ?{?d.<^?'t?佱 <?hbH?z??ھH?.אK?ȣvE?="v{?d?mB? =? jJI?U(l?hD2θ?ai?F&,8?,C-?=nX?3_Sl? ?ݦ}R?%Sd?w8X?.s? ArdA?XZ?F-a?`M?(?P? ;G??ߙ??3wg?;U?e!V?pZK6f???IE]?K?ЂV%?+aUd?V?NLn^2?(A?شQ?޺?M {y?6P;?+d3H?܂VR$?򊿆?vQ ?[?ep6?M4?7${?? '?r!p?Jՠ?!?>\Ծ?ڱ#uj??e4?"7?Г#-?D%2#*?ﴜbT̨?4*? @uv?CAm?c0?~-?ĢƂC?B(%C?q ?Vp?]ߟL? @ 0? \' ?! b?0Nʀ?޽q?=>?)3/?\y{T?$b??~rs$?⸍\&??Ře'vz?1D?GL?FK?ƙ'?ԓH?b{^?wހ?sRm?*?o^?"8n?*)?9b?rZ?A?<.?ޏtsx?%[Z:x?T?P$(b?ǷR!l?]|? q?JQG?RJ?1?>ξ?\}E?u?U??T3v?Ռ,"?Ihɓ)?-?šX?XL?Sݹu?莳%]=?O|>?q?fvF`?ݝ?PE8w?&3? Kb9w?,k?;V?\0q?g@?ʲLH?ܭ6((5j? ?F'g9$?ܓf??@W8k?}M1KKo?]O&?⮴?iȦ?Ư&zE,?IdX?㽬u?^g:?`?+XD0?{?9eT?5$bƨ?癰??״Z?傳 Q?d ?̉Ǟe?TyؼT?4*?kU?xa?T?#A%/?7?N~`8?Va}?B(7?~s?muF?ї}%jR?h ?BNԓy?Ҷ[!`?J? +?Ni?JHq}?ْW;w?/yGH%x?@@?I_? ?ۨ?ؔvy:?G#8Z?L„?`Ҹ@?Nuʎ?ׁb.? 9is?vH?|˅?D{] ?=:q%t?M?zu2?5@?{A 2?.9o?IU;?4ظ]h??ԩJ?jjCW?ҁ,m?8?ѭ68\?&qG?q49L?n !?ވ?1Vâ? -8,?ڍis?B?|Ag?>?hNV?$c?}?lB?Oy?$x?vY?!逡%?.?3_{?i?ҳr?A=?^h".r?ٜ?Σ||?Ysu?#?nf?:ٺ?s*,?e?DxwEP?;>?~ ?J #?n=1?P?w? b?嬶m?s.?|g??>ͷ"@?'Հ?KT?.?J?9v+$?Cb?! ?(݌׿?׬j?9shZ?p?A݈?*|8?6 Ț?(Τ?ėZ?K7?crIE?烷SH?V?1A:V?x?fI?N ?.Ľ$?ꮮ*W?qx?Y-p?HTL? :FH?8t?Sr?ךQ?G?x?䡐FJ?qȮn?H?4?h??h?^?y($ǟp?\(\?4ݬb?X!`K?=BXG?ܛS`:?/]벺?+gV ?hot?dX[?8h ?`N?f<1? w?3?*GǷ?Ym7j?Qd?M.S0?+?喸 a?4B? ?#B='? ?C?٩UB$?{nF4?T\,?1B?׶r?uQ̀?.Cn?`'ҳ?w>Ƞ?TDXAve? z֘?gam?5r ?ʦ4!?C7w?܋? Z T?`J=?iu12l?,p?ۃ&. Z?+o?䅺 ?]gG?lY;?3|"AO?m?۷_?@?푅|T? .?5?߈?s͞F0?j/?l? 2r] `?*+?&~8Z?]箈4?| !?G;H?jU?-')oj?.H2?v["?)6W?b8z?6X??AT2?]?Hav\gBd?%v?ƀ?MG?ϿJ,?E;? Y?K ?9=LF?M|S?%;'5?ژkXb?[1 1?_'?yYVg?PU?۾Cm?Ҍ7Y?\?W)[?tX?.?b2?; ?[zG\?'?4ߺ@?!U9? ?gS!?J~fN?W W?L`?j}?Lefk?;Pj?R?RU2?)P_\?g?%j?pd?Ol 0?KRbS]?n K?ߨ?j_? ?%uj?G?非)fu??v'?Z?;?3?(?/|?onX??=j?,ެ?޼?d ?h:M?P?)?2?B3m?ӂЮ?4l;|?θ^l?SUxWQ?ߤJm?d U?7J?]=3?$P?굄 6u?8mm.?Sk?2?4 +?Vk?Ғd?FQ?9au??aH?A,f8?Tz?QSH?/s?{о?cPTǐ ?>J≡?IpE ?t!̋?yID8?[D+?эs? >??o7?qԶ8?ezr ?Mx? BN?|2?-?sJfr?] ?,ʝmm?7(պ?ѝf? ?뜾ps?谾<?(93n?E&?db(? ;? ?YڐP?7}K,?t?\mP?mw4>?,4À? @?sp?I1t6|?p?mn?{+6? x۰?"7%?g(?"+?`"?Qh?3n_ ?!:ò$?%+z?Va ?NEP?wP ?f=?$f4? X?雳U+C?Ϣ`n ?竣gI̵?㓡8?乚8?6wr?4~8?G?Ut?)FqS?,m?Je#?ՊE??刣?[l?œ:@?|p?2}?mpE?DzG?|Z!?>9B?%>'?2m?'ħd@?Y?62d?4T?%ɱ?瓰p?H?`?oR??(д?+Ѡ?@?_ Ui?srl?R@?@)A?E)?X"Q?Y ݼ?"o0?͔ 0?\#?Ra-?K N?wͱEw?Q^?B?ye?M&Ύ?Bj`?jح?z;%!P?Pk?l_?NR*?e}?Kȹ ?E?j5b?UO ?0Y?z+GD?C? k?ڰ>u?_@㲳?G!NE?Mb#s?C?p?X5?+w4?2Rr?hR?ski"P?!4?V[2?ԁed?n?@-tX?_X`FV?^)(?jq `?zz[D?<;Gb?bM|?8?(~?mm b?是ϡM?4?mx?ΘYw?wFP?}Ñ?Hܑ?Yn?w?Nc ?k f-??Hce?߸?:(?4{$?;^Q)V?&Jc?•T(?ϴ?՗.?L?5D9^?j 9?4 P?%??C@?۞cU?W?_)?H3ԟ?::Qd?2tP?*qق?eRx?GY@?^j?߰q?`+}i2?&ؾx?s5Gl?p9 ?h]?Y;N.?$(gǣ?hu'@?]`?'q?]p?*`?V΋? ?[D?hڰ]4?,w`?5SΠ`?x{?IF?ǣj?3Q?n?捦8'/?g+ɠ?Ye? e0?AՓ?* 1?|w XD?y1y.?F%Tv?~C9>??{j%?=?r}?Ad?+.?Z?wTN'?1 ?f5Լ?ݐ6Q?٫Ff?sjC?o{?s?ǜeW??w?STU%?uy^?ގ!,x?"t?V?Qy0?ly??lp?K"7?A~ ?ӗM?q<ɡ?D]?Sz?h?^U?H:n?صT/L?k p?V?z?y?s v?A?>o0?VvE?u"NE?:?x9??[_ ?ʟ?{km?K`?̰m` ?p?: Wk?>mT?V+J$X?!b?&:?lY4?Q/~?4*7W ?#^?P?牻 ?\I?t_ ?🂮.'?֏??k0?A4i???j:?-qm?ބ?@ J&?%Y6H? e/?Do ?D?Rct??vW?H?v3j?;Pq?;I??ҕ3j6?|g?Ǧ6sެ?Nᷩ?T|!?>b0?2?밵S?kHTŧ?w#(?Gq?zO ?O ?4T q?Q)? ?uRE?/"?vOr?c4X?x ?q?Uv?Q;(M?E{r?㿴v?>hC?\C6H?m ?0{?V?ە>?ґp?]#?1qi=?R?2?dzlqM?H-?Ԟ,?G?@&[>?X?i9L]?H7UK4?1e)1?A>]B?ץ?ҳc4?(?ފԓN ?5x|?R pO?dK.?Pgt?˛pD?N?]߉?7O?  +N?lx?1?t$x?^!o?S?(S$?/vq?Ǻ;?odT?&?Iff?Cb?TAK?쎳+?t>?׹QYN?|@?d?BE?߳?Wս?ޓ6f?ĄHO"|?ӬJ?wV1lM ?@w_??f^?|Rn?7wh?lѡ??#}?ݻ2??i?-(:?z(?^?l O?Rgp?k?DZW%v?iMlh?1SZI0?ц?6(g4W?WR#h?L?mqO?y,1?G_!? If?3Gs|u?P?`N?k Y?EJ?ail?K:0?C^]?:wHv%?2P@? d?z@X]?ٲM?6,s?(W}?U}N?d̉?}֦? j? u?}Ph*?vh??Ж,s}?أ<$Yp?fDep?7' ?Y!,?uZ:s?Dy?%?fY9?N?};;k?Sh/(6?E^D??\0>? S?sP?Ô!?(}4?P ?؝WN ~l?yr"9?^ZJ;?樬9gг?Q>q?A_`4`?Ί?m ?ڢ 3l?6]q?јܿ?S)?>,u"? >]p?69?̴P?X87v ?{g`?5%?e d?ퟪtq?bJ?_ɔ?x6C?߈&(?"a?؎T?ؕVV?/L? @?CV7?sdd@?È\'`?0I??ܼ[n?9`Y$?fHJ5?S+0?¥?Ӂdž0?`2 c?ݐoS?m?O\?ܮ5c?랔u\?ߚ?澞?y%?+f?D?8 C?޸?iGBP?xK+?Iã§?!e?rb?+?:|?"V?0p?@a?gN?P?x?1?|gm?P ?E?'w?+nh?nD?E?J?EK?FMv?^?=(?ІV+:g?{P`?4i? ?>u Aѽ?lB?^Mr'?ȫf?˭AAK?̡A4?.!x?ښfbB.? dkv,??ܸ忰?j¿X?*l?ҧk.֦?s;?=l?4/?ɁoB?m?i.?{q?4_f?AZ?ڔ Q?*1 ?쀇0?P?Оa?7x?ᰅ.~?6Z(?Y ?5J ׽?3)3Y?ꅱV?$?LO^7?O ?+txQ?ă^h?e>6ƞ?2~Z?W?'1`?aY? i5*.? }7?~Zk?ͺt\?=\?1{?sLQ?龛T̬? T$s?iHP?V%\?V6 ?X?juw} ?y6Ds?7>+?ze?ҿ?;̄:?vzStR?Gt???9k(q?.g?E6e?ʺ9O??|Q#?V?[0Y?V?R$D?l-g%?/9uL?p۶(?dr?q޾A?e-?ѥeyt?pw,?}u0?ӟd?˅?HΥ6?6 ?G{?m?'Y?$\м?U?XRO?p=8f??8?AY?^(@?d"?j_z?޵!?v?KY?tuz?'q ?t暏 ?:?ڜF4MO?śǟ#P?'?;榖?DE]?-%?qK'? K?6?Y?e68?fv?Kȶ?5kp?d?jR?cы???64R4?m?V|e ?~KU!?%?a?Ϸ,?=Q@?C:[L?E`gd?kLOR?ɾس? k6:?qr ?O*XI??ч|?;T~? lRl?I 8?10T7?ƦJzn@?J?tDP?,/?:"k\']?!n刍?HIF?YJIT?g?߽59f?Sen&N?ds'?-Yx?$&?9+@?;T?tQ?*]f?+PiW&?*!' ?'ٝk-? [?^b?Tz?GJN?!4?d5?$A??ft`\?۱ ?gc9?ّ?ѷb?ŝ??h;sԥ? ~w7$?X˭[?^?w_4)?f=?d?ߠ>[?WS̢?-?&6F?7???:?XE ?(3t)?P&?StH?ίʀ$?N,d?[?h?W8D?{@_?t?ղk?ÛzrX?Ү?s?GM(T?جAhP??E?iz?'c?N1v?1?]N^lT?ѱ?y?Ҳ ?ea`ny?ñb?$?L:Efo?ֹ-t?IjJ?]j?>?a 8?ʋ^(?_DZ?F)De?X#Q?āo?.m`?⪓a?iq?u\?Ρ՛?qkh??MLE?9?iI#?1PV???вTc?͘~?ғ,?Ƀ\B?vU0??oT@??50?E0=fp@?5|{?N#?w? >B?:C?nc)z ?ii?f/?Y]@?Ж9?ڐ-FlB?)ZUV?ڧ- b?8C? ??ɿ%?LnV?aTp֧?Gƞ?N+C]1?@vG3?rw\?$?&~2?aՉ?01T?0?2|?QgC?=$E?JHU?b`"?ɚ?f==ь?ڶ ?Mq?О?0*?[K?u"?r:??ea6?e_ׇ?c2a!?AR*??[8?L?e0?#0?]n?׆B@?*?T? -b?"]bP?ּƣd???ֽD?ho!?ΤYAD?#P?u_ ??0XC?e:q?tqx0?F /?7EV?@gy?˴f7 ?8 X?-:?ي*6?jl(N? dXt?s `?Ll '?Mn?UѬ?L6 ?^P?O2ɔ?]?뉺D}?I~? h? ~?̉s(??=B1`?TY??/Ј?NX?-8@?ҵ0f? pZ?AAF?ˤH1?\zD? 0>?V#?i?kW2?Iy?? J4?Gg?#)Ȩ? խA?钌D?#|?Fj.?.=&?ԥzGx?/JKy?4;?W/8B?i,А6?#X?Z?֕b?Uwp?d=>?Eˢ?C W?E=?I |@i?Nn"?[?м 8?C$K?t=d?1n?KfgN??!7m|?I@?R?ː ?A|4? 6?o<8?4 ?+m?fl?LȌ?8&?8?X 5p?ʉu?Tx@?:u)?NU?1\$i?n`h?,;HX??.d8f?fy(?߃&? .?\s׮?ډ8qL?ĀL?[w;?|Ki"?x\?W8?B?v'q?8s ?Z?<?֠M(?Wf?3^?5."?R0?Ẫ6?/v q?ͫU?6q}?DH?ތׯ?1??oZH?]3T?SWN?K݈??뫳D],?賫?GGK?׼4?tw1?PPL?ǂ~?xT?AlF?ԵWyr?o/?Y@?ޔri+H?u*Bg?Qk ?ʦ4x$?JqQ0?-?G̐?aE?p{ ??҇fX?]?CY6?U4n?ӏ٦?0bF)?˔-?{#|?1s?/NX?M`?㢎鴭,?ؙxEP?1O?3-(?킡,?Etd?x$T?9Be?dh?بtEƾ?7A?<ř?I?.OO?(BbN?)ۜX?L? r(?kV;2Q?⮅s_G?{q?[1 'b?Y;\?ڦ./?JV?7ʨ?Ϥ?i i?3?1̱>?3$:x?Q??+?] ˪?uNs?;?pV?'k1??ﷰ??&?g8(??A6x?p٪?!U+?w4h$[X?,(?(J?Mi?̿~p? Tg?ݏῄ?u?^Bj?aB?迷Cw?ɬ`?Ý`? r?gG?܎Z?Nc?Uxe? Z?w'ˀ?QH?b6 (?I?Й9-p~?t|4?㛜H?1קi?3?ĈG%'z?ƭ?q^A'6?H @m}?Ik ?0'?F'd"?³"?3E??P@?Ķ?v?BNg?Ȟ! ?Β?,mDT?9y??Vf?6⋓?[{VJ?A=?# 8?jdV?q}Z+?ۃn?Ͷۖ?ba?sl?!d?y?]1?y^4?M#t?kO?ԜE^p?s?%)?Hp?P(=n?⦤ E5?ɶZc&?8;4 ?ƒtp?m`:?3 ^ٸ? M@?{wx?J3q?5/?2ؒ?ЦKv l??UoL?v{8?|X?|󷔀?w?8 Ox?Uo?ȕ? b?wtc?$y?Gq\@?~*k^?љ?8`?a?öFt?8 ?ʦ?3ڧ3K?r?s^? T ?ư.??S?b??Զ\?i\=BX?V0?9D ?$b6"4`?BV?w0??+Z? \S? y@?Rqn?砫& ?\?\{?Cjn?j{?n?l2?G[k? ٧`?a?܏?뢇}? yU?6%k2?W ?5=? V?f`?tVf?P|5? M_?ᄐF?'?츂?XaO?Rzx@?P_?v&?z@?||t? l?2?gDj@?DR@?1e?33=?R|?˶{?4q{?]Q?0?Sl?Z+w$?9x\?}Ϡ?j>;-?6 P?>?ɯu?vT߹?֦?̃V?gҀ?DŽi)?߳R0 ?Y?w8?R?D1X?L?(L?ű?ӷvB?i?<7A?p|ő??΃ڵ!?z9#(?`9[Q?,|u~?IӾ?LZ?)M?̭d4?E?~e/??絿W?ZP>F?ςix?ͧ?{V??<=1fe?8?p?ۡQ?3&(]]?鰸Qm?IFiV?꩑kP?#Ȍ?< ?ͺ_Et?ټlX%:?zv? ˊ?ηX?Z 8?vp?:ay?_HO2|??>@?J]?8"?S22?s(*?24i<`??d?\9^u?DMJ\?Oڪ?z}9? PX?%?U$z,?\ #H|?~;?}?6"@?]ď?<~0i?Ѓsju? s?}{od?-sLu]?ڧ2?pg~?Yn?(ter?: f?5\?TU?EI@?z8?׵rZi(?܁?ٰc?D)ߊ?2P?@?=E#??I[?hS?Xh{ ?#*=a?|\i?N?W;!J?**]C?OOƃ?Gi?y?VI@?CR? uB?BL?X7?Tό?ɗUE ,?R;䯼? UԊ`?YZ?Gl[z?1@?y m?y24oZ?j&%?s?&K?曒 ,/?='{?߁=5N?1B?j`?jvɪˀ??y ?Kb^P?՝hɕ?8T?~ۄ&?1}&t?Xe? !bw?m`?-ܹ?7M4h?s ?${^?`r?tD?Ӎ#|?׹Ed-?_ל/z?Ҕ?[q?=aD0? n?_,?o_?mݶ?u?U?@?Kh1?nqP?<?TtC>w?ŝL8@?,~?6 -?=CU ?gף(?q" &?S*x?3&ڤ?δ!ɵ-? ?Ӷ@?r~?*?P5=?&3?ФZ&?j 8*?~J&?х# ?zy!?Wtr?Q .?e.R%?ԃ'h?X_'?xs?+/h?BN?T \B?֐?ȀSr:6? ? ?L?@?W8?(?}?d8p? cp?\ r`?u[?J? oz?0/>?=?zD?g?lK?]N/PP?|^a@?hш:&?JF ?k?z?i&?% ?Ny@?灛dxL?&pQ?$iQ?ly[?n`_?;?'A[?ϡ?كi?߿mx&z?/$r?K)\?F?1@? ?~cj?Ii?<بﻶ?m3?^ ?{qi?wE[?堚X?M=e?Y'+H\??Ȍ?)]ބ?Ѱy?qG]^8?֬i?󾦦?1|aU?h-1~֖?rp?ӠG+? R8??&?ذAx?ڒpNI?E?TAtE?Ь)i$X?gx?ޣW~:?+"C>?ļš8`?YU=?,[ ?QwG@?K(a?H'*{?՞M?Ӫ? !~?"F?哵H?s?K4?>/V!@?Ut?Lf1X?%n@?„\:|?M"! ?rqp?K;.x?D/d$?,UH?̳Y?];]?뙆 lN?Ha?K?mR?Ӟr5?P.t?d8I?E?ᯣSG?wT?K ?j?ڥ޿4r?䏯bp?ts|?< {myD?ãGx?iqg8jC?ki?UC?#8?p0?rDv?}\?ű? c>?U7-!?v`?T?ZO!D?v./ц?/O*?킏Nd??hl?R\?W%+?}|?r\?~ft?`ݓ?;?SG*@?q1)?ԇb?k!}8?­+P?]KzH.?_E@? V>?0?5D? (0?ʼݧ?za4?~{~?wrpv? ) w?2+?o~K8? I>R?ݩ:F?Бx)n?Vp?dTH?욏X0?-K?[s^w~?|^en?IhӨ?UQˊ?v?fY?Gڹ?JZ?p!?@(?RDu?`4?g}ia?0-QA*??B,3?zй?st(hH?""$|n?ޖrbd?WR,?,gg?nkw?]?,H6a?;?=n6>?}?$@=1?zs?+kxF?>?~|W?) ?ꝃ/?>l8?䂏ȁ?>k?!P?]?dߤJ?R&\d?z&?'i2?O?"cN?Mmaٜ{?4}!?IZ??D?TmUs?Թ!?JtO?sFbo?ͣK?;2?%-?^y!/?pS?"0?o \?7?/?NL?.1" ?(^P?o8?T]:l?Hz?i@?/!?RNg?Kx.4?o$s?8w?]-7?m&T?N/nf?4*?w+?%т]?6@[?͆*k$X?l+?a,?J8ǜ?=i V?2DeR?׽-?|S?ϼ7y`?Gz?-?9ה?-]R?ז:?>6&?LeD?¸^ ?˫JMp?TS q]?.%P?9 ?+mZ(?*?^Φ)b?= ?JV\ ?"?:.\/?[?-d0?#}$I>?.9^ ?=N=?Zz? KŐ?E^6?m+jg?\tH?ڴ9s֍?b ?+?Pzp?_X ?"bÂ?T:,6f%?aFJEy?ҩ{N ?蟳e?bz9?)d4?f1?@330?Ɂ7(t?8#?Y`'?E5Z(?<>oUR?ďx`?$@?W<~B?L?bPj>h?ӱ?ϥnĬ ?Dm_ch?^DX?2 eP?o[0?6?YP?Y[6?۫Cw?ا? ,?t)(?F? D??ݰOB?|D.?kU;?MB?/{?uX?o|?xt??5^P?7 ?۩2CE??3F?מB?åH`?ѠÓj?d?U"?I^?& /7?h l?w>_/E?qّ?0F?6'i?۳?Ǘ**??Lj?㒿%?RON:?žC04?ц?Оܔ4_?~C?IW?_?K~V?7 ?N+?FXQE.?nE?Ӟī\?W>?+?p?τ @? #`?lz ??pd]U?Tdu2?\S ?V\r?v}?\>(?J*?]:7n?5]_g?|?cI?$xOf?ME?穇Ռ?Rm?t=t4?dn>?-)8?C]0?cW[I?;~1v?RY?h4]=?3 ?97]85?:2$? 7t??×o?Gg?귤z\?nO?q%W??M !?ꧠXa? 4?{xO?w](?}?Kkh?N? fǵ?ב)?B>?+\P?j.? T?/bb ?Ԁ0r?fD4=?{ng#?AQ?DJ?W ?~DIU?%Ц?翇2?³?8?ʰ ,?]D%?.^i?ߢa\b?z~J?Dj?O؋|?u?H?k-?7!v?{_y ?w?#"-{? W}Bm?.,?\r*Iˀ?}?ӳq?.g?ZeP? O?}r}%;D?Nl?qSw?-q|?=4P?ޛs6?e}Pn?tS?*lM?y!nhx?@ ǁkU?6u{?J+?ӄ%w?9:ex?T0?Yvu3V?fT?h$aA?b~J?=*Nm?  ?ң?zpM?lg/h?]A˿?ْ M&?Chb?|G5H?JH?v8?s p%?@?-'?2mӘ?D?u?P#(x?\?Ki?Ƕ?}K?)Jóx?k#M?PѰ??!ɫ,?0?y ֱ'E?Ir\?T]?/ϼ?V?ߞI ~?N$XSh?R# ?yUNė?붳/t?Sw#oh?u]Z?~kf?磮U*Ln?)?:wݦ?9%!b?/Ĉ?{ ??→il?-\?;DQ?4]G?wyR?՞)j>?Nj?4?޸E[?.?1<?GsE?#a?T?3J?-P?&̾?ߊ"B?愭H?cM?襫12?:K?"*y-l?㉮ZLU?m/?b&?Ɇg)?Ј?_? Z?c+ ?Ɲ?kVt? w?L 1l?;??7?إ?[?ը?Wj? hn>?F?&?=~7?#~wZ?N˞(?ZW3-?d˘?7U??68Ύ?jD?Ј+[?D]}?Bȳ?CM?T?]Gj?gX?H=D!? ;?6r?&?: g?)`@?l ?ڵvK?T? 42C?0"?x,?閖.d?֗hfs?ܿ4?;l擠?ԑ,?(?¢/C0|?b0?>vH?`1Ht?d ?(B/7?³5O?%/?@?%_?}?勗mF?*!;?XlE?Y?YVS?"}I?{;{?7c?*!?c6?հwje?*({ ?gdt?CT?[;V`? DRD?rn?"2@?+Aם`?̽Y?#z?;̳Y?ӣ,F3?.??2r5TW?N&?0j2?ȞnH$??_4d?﫳ncE?b䳙?ؾɴDH?%KK?B7 ?a?DIKB?D%?Gg?ވ?j? g+qt?U]?iU?Kf۳~?;ESw?Ћ?ճV?ߪd~4?Ũ/?ٝh?m Ҽ?ꋤ5?D?<*?G*?zЯ? $N?bX ?5?=,V?|,%Qq? zJ?Fw}?6^?psj?5l?'F?ͩsd?,n+?R݄?N{?Y`_?Dp:?Ѐvs?0l܋A ?Z?6?诿@?|ˑ?V?蠯"?FҪ?ʻk_| \?b0aE?d/R9?dJH?p.;?=?4?VMT b?؜?`{?،z??\?or)?ˁk!d?p?\x?Ѥ}?Z2!x?;@?~vIq? G?*N4?}i?Q?לx`?㤏~ض?̀'i?f ?ԗ,?yU1n?N] ?KUc?O㼍?ކ]0?|cC?6%c?ܶ0Y?_?YF'?ǵЎ?Ȗq!?g? J?]Ǫ?9W \[?ᆲ"?Sa(? r?YO1'?݆k?ߧqP?6V*?Gyn?< 9?&kx?b/?1|?D2~l?1 ?͙8 ?~?? s?şƂ?i"?[nu?{t?)Ch?'"T?;Y?ܮ ߣ޼?B5,?"F?1,? ?_(0?ih'?<?4?G&\: _?{?f?[|- v?\a?J?CC?<?6v?ܔ1 ?T^(L?'R?c%^?#q#??'\?ה/P?1 Y)?M?$-?%֓?[?~( ?(aŶJ?DG?'"jP?|?.y?An1'?œ"$v?yde$??E &I?زӛ?Iǥ?^C? #7m?1O?Ã]֬? |?]Vz?tt?4-Bx?7P?3W?ؽ1"?11?nZ\? !QO?&?5SRB? 7;?.E?o&grf?[0^?Y,^ n?ێղ?uYQe?Փki?~Hт ??\C?S?Jڻ ?ؼ,p?o٤?,?7u?SFw?M4?9N?pЭ?R?$#?ą9 ?;o?uo7?ף?>A?贘?2S?V?(d?l~?&+S?^*k"P?meoX?*?vmg?Fȫf?s߮?K!L?8Yv?~Z?E7Ҏ?7?z4v?br?˴f`?cs ?mF6-Z?exj?ѭ|ߜt? [3?6f?j̰?A?5ѩm??^LM?VG?딤$1?+?!Of??H_?B?]0S?꒑vt]?יe(:?O.T?`L?;}?aT?2f ?qmBl?ϸXM?ɚwpD?tr?' ?z0l`?DrWf\?/?p6*+?[*?I>?J?D?$v0?LCTπ?rncN2?z9 ?&, 7??qXL?J {?ꝩ?ѳ\?j J? 7?ې?QS?B? ?>.?˜o`?°σN?8h2t?fI?⚱ ?ʕ(%$?؉M ?>Π?^c7?1}`?^?.= ?n'hh=?(?\?|6MҶ?Rp3 ?;?#Ci_?㫤ǻ?TZ J?ѣaQO??0?ׇ"V?8?AcB?}?ހj 8?31D>?Մ$y?*6#$?zepcoT?w9N?@O?:>T?q ?ADz?ͫ_8?+b`?˃3?r +#?'/?R I?ٗ7?ȞĦR&?jIt?᥋Z ?Nm?߀3iB?4|?`? ?҈?T?q,hd?B&?wl%?7)?s t?TD4?]# ?E u?(@:?L]??Ve?ݡtA?ˊkX?G%J?~l?L|?!5q?F?T5,?rVS|?)g?˱ž?ꡪ,??h-qQ?֔?$A?`K}j?3G?;x?q?p?壑q?ݷ*? 3?[+=?Th(@?E8??γہ?lJf?g?E14?`?*;gO; ?Ib_?ʹto?}3?Əzx^?x9?FQk?L-?tIov?]=?8x?<e?t?Sx?y#U?MٲX?A ?P`*B?I @@?&? b7?{bwf??!y?_~cp?r:? [?Hnnqh?,<|??LI?_CN?\O?ݡ?&`6?/??9Zd?v,^L?d?ّE?ɐ}`?N5l?̄?څB4? $?#??h|?j?!?Je?UwI?zF?Fb?O`V-?҅P?FK \?" D?k?IPؑ%?NX? ?:|?gI@?O3?pbX?ڜz?ӑ*?iM?ԊYnb?V?灚L?d`fÿ?$?ϕn ?=?ٌS??4C?wc?A VU?$™?ꉙJh?ݯ-?y?*R?Ko5d?(^X??'U*(?эE-?$7?ّAJ?"j?0_r? >D?lJJ?g?o?8؆u@?eʡ? Fp?)R9.? n?G V?H6T?H??O?{ 6d?' ?VX2?g??͉?n;R*?FFv ?=]$?_$C?ʮ.?oh?h@?IDzOT?$qF??hr+?ma_0?U?/)5?߮m|D@?<^1]?^ %!?? ?5?"?*W~?`d6*7?#d]?14)? vDx?˖W,?!d? ?ku?.а?ĸc??xk?̽X^?~.?oD\?^[x?b:5?Wv?ǃG?.Rok8?oo?ۂJM?sz?Zn?teF? KyJ?R3?@L?$T?=bF?Ol w?a%?z:t ?:ۙ?HjNe?}?fH3A?ԲHu3o?OSP?e`?#mEQv?B9? w<,t?T?~ĥf??;?yl?#?ݩV>?. ?jwT?G2BT?iK?ݱ".?ӹ[8?[|F?%쇛k?Uc*p?رU=&?T'?hW?AOD? C!?&0?3? <*?=\P?G\H?קR`?i;o?ra?rIU?O,l@?u-}ߌ? ݂??&?َ^d?€!̰?իˠpz?INǐ?凂?ڋds6?p98?bFA?C?@^b?cB`?_?`v`?<?A^$ ?Dbi?]҇?ܿ)z?N }?<?#jf `~?ron?Q&E?5Xz?z\?62?H?-NH?ף@l"?C@@?%=L?.T*X?'=M:?ѿ$/?l0N?}m?Ӌxv&?WS |=?sL?S/?gѫ?#$?+7L?(׈?ٝҀʭ?ql?2A?7`I?V??dD0c?䧆*?bwke?O׈j?X?ȾH?얷jH?'?pM?jjo ~?%OJ@R? ?Ģ۲;?u[4?ǜɤ?Ty?K~b4?5T&?dԉ`?isv??$l?>)`?,&S4?omh8? Rv?ꚮ?0x@?]xb?ߝ?yE?ɢw?dmb?mrEe!?Z?c(r?}y?Sh|?59?չV?wJ?ہA$?#?qE?L;F? }?&3D?B 4?c)r?HX]?T)ġ[?޽C?( ?5Ѱ? a?nಐ?~K%?S?s23:?pL?N9;?~jnڀ?Si~ ?Ai*?CFfX?Jy?7`=&v?`?`\2?J?`&Wf?%"p?o&{?E{"?tJ?R0?]]CP?Y?a؂?ɚ4x?EiZ?mq?O)y?Ԫ|PA?֥.Y]?lq?`ч?a7(@I?E?`z|??楆]?xkl3?ͺ\-a?xӛn?T-\?N2-?,c;?F( `?sP?ݙ o&?y?t?LjZ|?KqH?B[\?^{ ?4ﰐ R?Fp?rZ?ᴒ?"|?Xx0A?٥ ?n&o?ƌU`?=t?z9?1煙?(E?-Hk?v ܦ?P?擉B?W>vs?v@?5;3?I`?Iۙ?2zL?} ?BN ?]??S?=??-Yɘ?300?`B?» ,R?!97Tn?J ?؝֢?0Dcf? NB?E5=I?8?: /S?MQu?rf` ?ְV?"G}?+ ??܋j?@>G?ޣg,?ɃtD?0C? JR|?H?Rw?no?r2PT? Ř`?No?8U'?z(bі?营!?raİ?;|?ʉwRr?¹:??7vP?cVe?g N?b,߭?w ?CX?rn?'0?LS:?K??گ?v\R?^R`?Ʊ' @?$?6aZ`?NT4?+56?ʽ?b@?8'?f4?+r?~w?tZ'?JJ? x? k?b*#QT?V6{3?ڌE> P? cïw?9?M'J{?<T1??5 ?WG$?o"Jj?ﯓV/?ߝ?b?wbUi?ǁv?޺D,?(dZtZd?1ZT?iL?Eވ?ݝ?,]?kZ?HU?Rʒ3?&`鸀?e= )^:?X[? (mA?9~H? c#?}9 ؤ?_D,I2=?yJ;? {?Xt3?UX3,?]}?g?~;?{Z?5?8E+?R,_?Ł|8H?꿀?9~P?(Z|x?҉4f?P_I?aFLD?LUo?I?B38?VdR?YWWH?ڭos??ZP?c&$?mo'?Ƃ:Q?rw~[?ݲI!? e#?[# ? Na&Q?y*Ɗr?ޏ??'3?C ?F(G?X9|?5Œ?- ?G?W]en?S_?EQ]~?1W?6?x~?о z?蕯fе?b?' v?hUB?Ͻ?K? V(e?gAn?³LW?߽Ksr??ŀ5SH8?>ph?%b/?sh0?F?vs|>?=8z?Իk?Xa?c?YG ?ۿ11`?ś?.9a?T8?Fo?[M?ou?LpV? 3r?͖3?9t?uP? +?.8@h? %Y?U?ؔ4̎?Z`?FABA?q*y&?䬼2?ñK3?IhQ͛??1??Ҽ Oͬ?!"?" D?+pab?_*n?߭݌?5?"X?' ?݀?79:RJ?XD?žwlP?:M?"5sf?[z?S'?c"uth?ܞGYj$?iv?Z?͔f?႑o?t\?pÍ?EUr?7[f?Ƿm?.7jɥ?zτ?aaɜH?~53e?:x?㐵EX?!#?ҠOn77?P?#KX?gpP?+gn ?$)?D%HK?E(?w‰?fFō?v?([ ?5)?jL$5?tψ?紅[Җ?-8(?|?A?E,?Ϯk?M\?@͜Lf?U?Ou?e4?D y~?n0?p@f?ڊ?iL?,i?Ц̷R?ki?3̮~?֭%X?I#?3. ?js?ITG?Q: حx?X8:?͉+asS?Tw>?TL _s?d6L?K?㬷$?p?_+d?鯌#?R+[?'V? `?.Wi?>V? 9??K?k(Mϒ?%u?H>?<*P?CGY0?tN?*Q?D5p?К)cv?ø?X?႘ӿ?1?8?>ѿ?f2]GH?"L?͊*`?ݛh?x?[8H ?fu ɶ?`nAuG?g9q[i?pPT?OhI?&w?V0?d a?@K?דX67?᲍J?M?fts?$a ?x$???;M|D8P?,M ?ַMn|?dt]o?H~h-?ݒ^("??ԙ_E?X`C?FB.X?.=?yB?ه?,h?ef 8?d'E ?ߙ?44?]J?;!?`WJS?ԋ b?(?Ol?r?/Ц?m`?2g?SJ?@E? Ռ7?;:.? `?A4f ?ߎ&? z?jw"?Q?_ (|?Al>?w?B4ދV?c?ܑg[?dk?}7?6=0?0ʯ?;]'j?s9Y?ﴹ?A!r^ ?z?ф?`i: V?ȏИ?"nLbP?̊ɍ?,a?1K?Aռ?AcM??3g.?ѼLz?Bu N?rR;TZ?ͧ ?ܚPc2?rO"?{`s? ?[$M4N??:X?GZ?h:?G61?ctlB ?ͻ@Ӏ?V<)?g&??L7T0?߬?UX?"h ?DG0?$\;f?DP?Qe?ܿ\N?BL?^R[?Ȇc?$|?v?Ҭ(?[[H?◘ ?%u?Ԝ>?|?`.IA?#bJ(W?j!&O?D? }?]?В(Ia?zЊ?D!~?r\] ?a?hX+p? 3N]?M? }J$lR?˜Тp?PX?᳁A :??FwD?S?ƃ=r?j? ds?␤f?G-Zy?ۤkX!"?Ta?ےT?SP ?f=۹?j3A(?/WB? ǥN?i?{ 2?b?ZFUf?۠Omt?7H?mZ?Ƃi/?&3f?W?tbL?7֠? [~Z?S1?ȏ ^j`? kȋ?ݲ_kס?I6?TX든?x.?Hvj?پ3>`C?3{~?[ڱ? ;-N?άe?ސ$G]? A?呇g}?x?7"p ?J,+x?Ѿ׫?qQk?F?^~? ڧ? Z]?`u?hG? u3j#?^.\;g?‰wI?vvP?:?0?I/?׮ E\x?$|S ?U?&f?Ai8?ѽ&:?+ x?F'?_Z?]ɦ?׬jӏ?+[mݍ@?4?k?³-?"ީ?ڜ4l?ŵ(H?4ȉ?6@V?y.d~?אc2L?\c?h~?T!p\? `?M2?z?лw@?֤4 ?=KL4*?J4?0wh?戹{a??d_?Ft?ӝtbB?]?Ae*?)<܇?ݻɠ?/$֣?ۼ27\?30?8ei?DMk?N?Dv?5???҅؀?D?G1?} ɷ@?o\?;=s?=H?֝:n?-~4R?ϳ*]v(?蔿?sX^?ϙ;?g?#V?/~k6nS?l]1Q? v?d%?4m}8?DA,:?nFPX?%Ȑq?Y]E?C9@>A?Paي?0?HoS?zR?^C?W?9?"А? n@?ߙ ?%6?5? H?~?*?X? ?Ԏ`?xl(?Ũ?Pb?5GBm?_LEE؀? `o?<>ݲ?x?e?#ʻ?}\@8B?h}n?sk0N?)@gCZ?(i?B[?bh &?c :l&?1,? ?O?b^?fri_?J)^)T?Sba|?5[?ê9|?Jw<{x?A}z0?{x?Ny7O?o!>?#fXCz?p?R\ ?ld?Cx[?}?GRe8?|`?'?90{C? ÜQ? (d?ó5?u*?+-|?/^?X<?ys5?AtRa?Z?*?<ڑ]|?jf ?Ve!?ۆ?s3?ĵ48?؀]6?_@?yB?ݚR@?Z?ʾ?-?W@?3-R?3|DVh?I[9(?n=?P6Bx?24R?lW^s??,/B/I?Yp?5P$?o?O+)?6?[5t?g.'/]?C? [3a?%؉?V4l?>0q?GOP%?JX?I3zB?&?n?W8?qpJ?.+-?QL>?Й$?n;]F?3^?S p?ԝiJ?W=j?܌?߶?aN$?*S ?86??8iw?ћH?}z?M`_?bʺN?D|A?ꮢj?>gp?*&y+?Ei:z?J /g?ɸiYh?4i4?5P^?Tv?Ǹ؏?z@?-IF2?8m?#r d?~Ѩ=|?tql?@?xal?٬bl?ޥ?Fdw=?E.t?ӀY?s;??D| ?y ? V?>?쬚F^?)i9|S?犦\B)?A:|?ap?=J,g?2;?k :?bJv?kҨ?Y(`-?0`?'M?c%Jܫ?܍wvV?/5?,sٙ=?2Ltl?;Txb?Gk?^YY?t?Ŭ?.N+?`B ?Sr?ߓudD?ik]T?u?㤨?_sE?99ul?Z6h?A?X_?l<0?x/X?٦?F{?띜?٩Z9>?|7" ??˶n?9h? B?uлZIA?D4?HEP?&Y?*?#ۨ?@?ڂP?Y^H?%DN?c f?L`?+4;?{L ?m'S?_n?I;N?ڣʜ?ͬ+ߔ?$|-m?ܢb?Eo1)w? @?c#,?B[%qM?0D ?v?ێ5? N›?D w??.a$?d!;b?ԣNUdB?ܛRZ˥?ICC?Qn@?Md?Pw?h0?iZ?ڌz?kPT4?MlhT?Uo?Д?m r?tظ? >E?mL ?Wk?G?J?}W:3?ȍ8?2n2?xr;V?{-9?9Fx?̨'X?$]?{&?cPS?T$? Z?D.tD?T}5R?A ߇H?w?o?7-?y"u?Go?ιa0?+W(/?ԴpH?r[e??W|@?܀®P?Pa6(?Ә!m?^g?׹Pe?O???HVyP?k0?#@% ?fL?O ?l 5??zR*z?׵?{*R?J~w?+Z!>?u?&W#}?ԲY?i`iI+?I?^Z??(=)@???ٝ-?m?\z8?h>9?:%_vL?,8}?q?%?͹fã`?JRz?SH?~殮?1B9BS?DUC?C* ?q? +?b<+?Uqp&?s3x??o:?-:?Ra4?t?êuDT?qIٜ?$L?^Σ+?f53?ڸ?lg" A?aN?7ޛ?fJ|!?ݠD ?Ɲ^^p?"??x1V'?Kv?,}k? ?ԣv?@财?\Vw?4 6\?(?Ը@OH? {T?}J7R?ë\ ?nr/>`|?/W?Dg`?ݷ~G? ?]?ǥ?G?FBt ?ё!A^?SQNp?%?1í?[8@?ҏ+!?F B?֤/?p?)m~?Ndl~)?5NxT?ܨ?P?eKN?ױ?i>)?QeE?u2+h?䈌lC?=Q? Z a? a?qb?530?Ci+P?]l?aL$ $?F<+?C+c?MZiU?#?QaG?N5c?ze3P?w"oM?۪4f?Ĭp̐?^{(0?y*X?#=?f\J^"?miu?l?灗)he?.r*Ͻ?勃b?2 ?+A?/#B?Y2tL`?RJL?Rp?!O??mˆ?78 ?tnޝ?%!,?&nQ)?@9|?DS?0RL?"g?*T?؈p\@?44?1 =_N?ކ ,?ذ?芝h?]?$2?{Y?=Y\? 4?l"?US}?G1?ꃽ&8?jM?SLt ?"V=?rEW;?NRxn?v#?(2?( ?_2x?2"f-h? Z>c:?7?$?d(?N?;-??>Ի??1M?Ge_ێ?x?b?РG F?"-Քڸ?u`?YF?uz$?>b>?=Yg?|k? ?/>? ?c?);?ҵ׋|?QMף?J7m?{WuQ?r$SUb?Kp?glO?(bxt?ٛ2?F?ְ@?~LP"?K۸? -Io?ٔ't?ɗZ[?0dh7?6·@p?ּڂ&?"Ty;?jo?i2?.th`?ˢ{?;n2>?4;?մSZ6?St?!?eN*=?@`(?M ?P&?d?[gh?यs?c0qA?ӆfN?x ?|(?>\?c ?/E?ٗyRB??ӘfM J?ʂ?JakCa.?눚? @u?c?L?rZ?: }?Н?KW6f?3}84?#I8?wȗ^?,J?@m'o ?I@?ډ fl?nC,?a?BH?ܮ=l? R?v*?rPu?l_Cw@?Ot dy?K{Mr? 90?ѐl?a? 2t?0_C?4?}]6?k8?&?XBw?ܙcN?C=#?!z?ɖF?SP?Oys?{ ?O,* v?UX?>mɥ?`M1?^D?:Xw?~N?YTS,? \??(?rH?A$?ݝ?m{z?:>pl?©p? @?PA͆?D? ?ge)?9a.N?eG4?ҩ.? ?;p?_/?MK)?|ď?g(J>?ﺖ?ytcd?R\aB?-ze??+?YRMI8?q\?Gpy?٣n?!BQ ?ДBU?""t?oes?Ϭ?GA ?yπ?LF?z\eB?SY@?G\?dٰ?7]=0?.  ?]/G?0,yz!? wP8??,\Z?CE?IfN;?d:?\ZF>?R?6 ?.ٳ`?2Ȏ?V.K?iIm??ȚM{t?93&x?S ?6ˬ{?1?wY|?Fn?CMd?/g?Ή4?c;Dy?/? LX?-(?٤xuؠ?o5? A?a'E=F?`"?BsH=~?,?Ы `D?MF u?,EU?-A?ö2la(?= W ?a??r?m-`p?G?u<?0HL?n&?y(t?߽` #0?I R?X?0@?w0 ?h-?Rea?7?Vf?@?N?MA±b?⊴C2?VY-X?R?.V?fl3?M`i?}?p ?צ$??|Z?$Ǵ?>R?)|)?jǥ&?ob9?_7?'C?3 K?Ҩt?M $?%C0?mWd?X'?ƻ?P0O?f;_?面>1?Hk7J?C8)?l]6?2FS?AfZ?ۊ5??ꎆ {?76&q?ˆ>O ?B?Շ7ͻ?!L?KebYd?SA??LE ~I?υ)?ayT?G +?M(?V?t3?E?ݴ2~?Ζ?ULX?Ŧ;Y4?{V۟%??D?MW>?讼h?91~̺?~s*?ũ?8F?էEk(?ݎA~?Q.ő?Z*L"?"ХO?T?jnKi?´j:?ߍ ?nFX?.E@?g?&5Om?x;qA@?'|[?̿ut?O^?3P?JOJbh?|?M4?9 O$j?g?yG?Ƕ|x?6[{nN?w ?/T{Zo?dfг>?uD?ܨK?/a???u$ ?FB?R?W1F?û(4R4?p _?߱:qf?kX?IK?ټԨ@?si?Q}T?'N??VX?߮3@Y?Wua?U: ?/̽_?z?һ*l?ܦhR*?ǒr$/?$Kll?>l,?i +?|GNⴻ?S?w0???=X?A?I)ic?Yl?겲m?]H3(?p?|:2?)a@?8om?䞑Er?ɅOl[?# Xx?S ? #e?:#x?=b)?>?u_??7?c [?h?,aL?yR J?ހ)E?? d,?㺊?⩖?@n(?81x?9׈;?ְ?1 w?B2x?+l[?L1A,?$p?¯m3?Bd?0F?ӗxM^?\u?^?RE?}ߺ?vLk0?l1O?\h? nz?Ǘ˦?k?ﳶAB?T? 84K^?,]\?GG>s?Vi? ?eѤ?F?K/Ѣ?){?|*X?᫿@?/u s?R(J?,?LOD?4@?JnA?t'?Mo&I?rPW?ęUj?s00?g2?.Oq?ޚaJ?b Zp?J?ߚR?_?ԋx?յtJ1T?5-?ǁd?o,?y7W?\v?ZI.?B?Vȴ? ?Uiu?Zv?ո?@?4 |?&{uQ?7Q2y?6\1?+w?3R|Q?چ ,=?k? T?~Qs?ʴHz?T?9쾱?$?d?e&??]8?m(?3c_? :?~e?*A^?ӯ~Ph?ˢT\?p?0?5'|?,e[\?꾾W?+Y &4?;?0?չO?en2d?qq^ ?2߾?,qIc?]AGJ?鏙"`?1w?{OMd??Nݕ?"r??E,?Eg?ꥱU?O?1?btC>?oD ?b92?9J?8?YP?$%E?v:?[D?h1;(?AW?Y-s?z M=;?v?3j?eM ?h?l{0?; ?"Xx~r???Go?S?)Z' =?eŪ ?v ?9$$?ʉ%U?'!?aA?%:?2h\?L/&`?"?Z-{?RLv?YBe?نQ?TT@? vA?Jl|Jd?HXkWv??V ?@ ?K`?h? (?'?$=bޠ?LL ?GT?݋ K@p?l?^.cZv?ӛϐ ?^҅#T?z  ?ʔԓ#?MtD?͋t?%$q?Q"?h ?Dˤ?֪?KSn?8?bJq;d?#m}?75+[?ݾ^Ҥä?G%?Sݟ&?K=+)X?ŠR?74G?4 Ō@?l9?.h٢G?+x&?x; ?ٟeG@??0FN?z'!?mgpM?9gV?#?F ?㈤?ne?s?w(?G3j?ׅw ?y;A?@Q?TJ1?豹Q?̭Ml??屝iO?z6?<7 ?u0?P˗\? |q?Ծ6?W?\&`?X#?T?5v?׌EmRN?ލܟ?wSKgL?[g?: ?AKp?XA`3?Ђ x?K |?o$H?I: `?.vץ?YI9?棰I4?۶(?ZhD?Ue1?C??RtwR?ɰ?B!⭸?KSD?aq?-5?1br!?kF՛? ؐ??N?(?+?8n0?fϠ?B?Pk?3 RA?ᠵ eP?C?淯e?L`?㳹r?'2.u??M?._5]?&? w??kmS?౑ة ?+?*&?s╮Y?A/1?Wdu?,E?}Hx?[SE? e1^f?ՙq? L} 4r?V>z?ܮ?NC ?^?ݐU0?ճW,?ۍ?xNH?6c?׏pXY`??㑴K\y?ڒHL8? ?\^?3b!?焵^^?g?ia4?I\]u?<@_?S< ?k3h;?W?kX?r?RO?̳9?> ?l*?8mQ?9P~i?ʹE?PRCQ? $?nP?ha0Z?N?JF?X腕_?9p?ue0?okM?܏(?ɐ?cJ5?A B?#?Ǘ)aA?s Ÿ?y˟#?}q?2?H`Cj?a~?7כ-?s?)ʐ??i?%0d?y >?3s?n nX?'}?P?[,?X,O ?՞_&?TG?GAq?#U?^Nw2?íX[͘?k*Vh?BE?3[@?=I6n@?;f5?I,a ?pd?f^?M$p*[?ܘVdiz?f(?֍.?b+6$?ˏ@v?zE?ٕ*X?mB?dT?%u?;?]+R"J?Ή-?`>@?`?I? t7$?G?X?{'}1?P!qC?_@?¾?oe+?.'S ?\~?Jڥ?zӬ?,mGQ?P8?v ?g,?,y}}/?&UrPj?ǵKGD? 0Z?^i>$rk?&GO:>?VOb?"?ש'wb?5T?fHel?Xq?3n?dQ ?P+D ?0&t?'+0?;xU?(;ӻ`?z6?}@:_??yP|? 7?D A?e:?}qݺ?Vї?效D?杲D??!}d? ??!~a)?Dzۘ? (?ᨾܰ?5*N?ˀe!?[}93?LA?‡ί? w?]G˲?u؟.?Lƨ(?\!?A H?3SRp?`P?Ԋ٫@?r?qc ?[$@~?~״?h]?vh?5z(&?RaWƴ?F]k?'[?Kt5? &-%?bJ\?&F?B ]?lhx?6:V -p?ΒFd?9*`?իS?G(s;8?2? ?Ֆɐ?eH?>[@?idI?ޠv?`"8t,?z@?b@0?,xc?oe?a/?Ӷ2?ᜒ@(?ꝉdс?J==&??aQY 7t?)(T?(e{?C'?]?M??3W?i9{d ?PZ?rEK?ۘdž? @?4??:*\C?Όl4{? c?]b?Y-?Z?+B?f?FQK ?a8?jl"?;aƣ?hE$?בu??ev1? LV-A?skV?Va?^t2?]`?Mټ?n7- ?c@?""M? ~ ?_P?N_"?^NhH? 5?-KʌX?i4|?ͲP$͘?3tΐ?l?Ŭ`?՟A?mB?:?yv?cf~&?%;?b0?k䍝?uo?_?Гc5P?i$? xV?n??h?/Yt?}< ?JEK?t^@?ף9 v?d?O?%r-j?;?~?#i,?_? ?E W?i?C5?* |U?sKBz?א?T ?NQ?Xx?Rۨ8?#Rv|?m+kt??Wo ? }+X?Lڽ ?K9d?Δ)K`?)I|U`?uF?rI `?֦l5tp??k(M?ٹ>cN?ج lgh?,s"?e^Ϙ? r%%?X#? K;?䙛`?y?Ԧ?0IE6?X}AO?2H2?K=%?̱@?(?>j?taVb?#=? "?@!?Pv у?/+5=?S(?zUJ9?Ym? '?]2?Ġv1?>X}?W5x?lTo?G\?8??ߨpf^?7m ?'qPl?6 sm??E7A`Z.?rH@?84?X;[?k?5#Lۢ?Iq%?eq,?7gH]g?ّ)?z8?燱(n?GZP?6J?kk[3X?Y?Ø2U0?OVO?6?ᦅf=+?4RW?sL?ߥJN?|{e?\,b?]s???_m?l`?Жn?V?&8|}?ϒmX?`֨?8g?|u?U6?ӝ:?qF?rzgZU? p?~VyZ#?Yڝ?\I?킟vjR?ٕ[1 ?_!?dN?^µ%?K=?E`E?Y;?O ?Dc=?zʎ ?O?`??^洼?X ?ּS@?SIz?ԒK%V?QRo?L?/J?e?8~0?|B?řDx?~bI?)f?m?![S?8]R?g3?"+Yo?cO?X, ?zЇ?ײ'0Gt?➭? nZEH?C]?ɞ84 ? Ga?d(?̿p)?i+?ۃ3(0@?.+ĴS?*?!#?W=֩=?Ǚ4h?tb?ɐ:|? @-"?瓭ga"u?‹6YYj?pjr?ǃ+j?u~?팭=h?QjT?b?បGJ?̊y?>3?b?Þat?V?VZF?G?Q:~?/?W*z?N?V?T ׉?,.L)?͓dT?@p?R*?頦0?}L2!?b|8o`?a\*?k`IU?! 댷?U[[@?8g??m|?ܦ׫?3\:?W'H??7u?wfz?3*')?(Z@?K ?EPe?bN6$x?ض!x?Q?YK?RҒ?N\s?躢?Q?:??(Ec?/AM^?( xW6?ڛ?uAk?Ɯ"?ދF!/ ?K1M?N$8?Y&?>?ʟ?R#?˴j!?[5r?9On ??m,?k?V?׋ ?AӪ?V.?$4z?A6G6?0z?~|?˿[H?~O?Ҧ9?h*?~\J?cmV?H~?c ?W{6R?6s?w?qe?eݿ?ڨЩl?ӿ G?ӢikJ1?9ݦ?Ib Tj?cې?pd?Y?e?ZSTku?s:?E|.?ۺ[?tfuQI?Y6ݛvV?4K?Qp R? ze4?4ش?PUE?B?p?kN?&Y?`=c9?)?:f5?q;? ?Il Վ?/?T'?Pڏ}?Z_[?Pv?"?3?ީ?,O?ֵl ?빫?=QոI\?rJK(?G R??{@٨?=}dӰ??o9X?'M>?xR? J8?f^(?-!`?btp?–٭k?ڋ2?+8?$xZ?ŭGw? h?%ۅ?"?6P?uSr*?vfq:?Qi8 ?r35?L<(B?޶7m?՘.p?1k?A5??y1T?@*St?62?QY?&2,?lՋ ?`=wC?أ "P?hz7?dO:?Qǀ?&?m2?L6?}Z#?{䞕(C?jJe?Ѡ&ۆ?Iv?>3P?Ӂ;?7r X?2D?XZ!?9ܟN?U??W&??%P"H?A(? ?̊y?ڈfN?8v7`??@?Pl?ɗ^UtD??~v|?`%n?g?(@?wD?{4ޔY?ɿ\?ݷ$?o ?|1?@|.?ʡ<9$?Fu?vz?ߺ<?T+?\?u4?f=T?A= = K?l/I? n?/9?ߵP?DIQh?pK;?6O?A/?J\?ڈg?|jVI ? $a0?iThp?埐6}'?L:p ?DkZ?筼ty?Ue<?#=Ea?k3Ie?㉄?嶏(?글?AW?ݔ0D9?p@?R_i?٨{c?Ą?9+`?ˉƷ`?)&??%l?،ܒg?'>î*?HJf7?_X &?jiN?m,?$â|t?A`?1`H?퉈{ы?ܓ"K?Ŋgn?5+h?&x?9'D4?6R7s?}{?߀_?яXp?4G-?]yc?t3w?Pa"p?Th9;^?\.Կ?'?׆9ͬ?™?͡UV8?!Ĭ~?0( ?I0?yͦ?D?gC'?,?Jr?[U ?b?vP/~?MEp;?jZxv?~o?ӓX#8? !4?wg81?wJX?R?? ?![6?1cU6?;T?Ez?Uz?wm)? 5?tsk?J;?Q~? ?0?x?iee?Q5X?De?g,`?$D7?+!B w? ???7fx?0BnS?vջ?>ً$??݂]_?āW2? ?\? jud?˔?g ?%UqHh? ?2iK?*?ӎSs?PUW?y$ ?; 瑷?u:W?к47R?ד'Ir?2wI?ct0?!i:?($P? b`?!=?h Ɂ?ϝ')O?KW?wt?WJ8?1?nFԑ?#b?y﫢?̪ ?$?3G?Wtg(ܾ?3y?&;5?m}Ndr?S[u?iq !?H=3?bUɁ?V?)щ\??Iᑉ?dيa?D4b?դS?{8?7T[*@?կ?A'$?1,.)?ܜ)?qwc?69,b?6,?v?HOP?rF{?Á`?ŀ?5>3((?}?bTQB?t] ?$ 0??Ӂ9%T?П6;?VUk2?6H?E l>?{#`?MWjj?ƙst ?m ?q?@#k?ҏV?{?چY?R u$?膀2?oQ?ύn?>"?16 `?ˣˣ?էy \?]@m?姽7?}?AK?P44?́¸?> ?szHV?ȭZŅ?ThX?-LJ^?Oۋ?ܿ~?β ?Ŋ7??t?"u?3nnt?봢tmT?MH?ׇCK|?#09?q ?'?r؉L?Ӿ笓?Yc?ݩ?nh? z?u=?^k ?ĵ/`?(#x?zU`?3pc? r?isj?4ȁϲ?O%?v?:ףƪ?帇m+x?YV?ٞd?3PR?y|?K?%+ ? ?Ֆ't$?Jx?fc?Fk6?Vk?ˮ?4@?>kQ)??Z&.?r"0?"u?۫k?S'ń?I ?,? I?և3fO?!ƕq+?h"?ͩw ?mZe&?v툈?ߗ?Gڅ@?Ss}pB?Zr?NG?0ư?gƒ?/q?&Q|?I?qa@?k?轵mgd?$? U0?B2?C6+'?V?1+&w?kEy~?ÛHpE?ۆ?uڴ ?һN?jԁ\,? T?ܼҡe?u{rY?{n?{ζF?Ĭ?#6(?(/?Υ&0[?i^n?ΑO?Ot?ޝf v~?6Ϣ?(^t'?0|?̢{?nu ? ֘{?נc8+0?<?}f?y??o?8J2D?Q3?w6_?y ?l<?=t?)M굼?Nl?ƬԊD?\??)t? $s?ݔгl?@0?ȫH@?;W?e%?ߜe2?x_?~A?3S?ZHHD?";?![ ?݇߻?1mg~?8r?빟 {?P߮ v?ɹ)J(?x$?>?j?\PĶ?'a@?I(h?Ǜ >?\LJC?ي?fJt?Hlf5?cwY?,Z˿?ݐw??м$?yt?)sN?.(c{?M;D?mgl?N_E?ʐV*?ꋾQ?GGU?L(Q!?}c:?=5??,ȵ? 5?U. ?Wy? G~?/Hr?oѯ?hY^\?df]?-wkM?li?B?զk>?ﰤ#h?LveH*? v2?O?ˑ%3Q?Ю6vT?З9[?߇bq;?quI?M?ӷ ?l4?5?h|?͗`?`W ?ia?˧|??{$.?E??ڡF?ԇrN3?|Wsrz?ed?d3?ڊcO?瀻?a?6c3?I?%?E&pXG?$1-?װ>R?r ?׾?d?\uWf?iE 1^?8e5D?¤ P8? Y?Ɋt?Ʌ??e7,?/rbT?8MA?䌋?abx>?&Z?v_? h?lW??}j]8?C?ā?f?;g|I?dF?ԓ{? ?IMP)?G?4:?)?ƥ?vf?f%!?G?ݍ0g?ٲ$?ׁ?%s?1??Fٴ..?? ?؝ ?A2M?Zg`?'W?3|?cC:?jhaX?Ѝ'e*?EU"?/(Y ?!?;?2@8?o?q$9?'N.?OI`?W(?V?IN ?+? ,p{?m?ھ@?>U? @s?kJ2L?#aU?17A? _{?<?.<{,?yu?R؈(?whP?8J? q8?Fz\?0^?rR ?TޮF?̩?u:?t\!F?Fy.?gO?ۀ0?P\?Xcɦ^?M Gh]?(?iU@?;=m6(?O!qp?&_?o$?o[?7P?3]s?V ?v?\SUG?U͚L?8Lfd?ލΫt?Έ_?Ĕ?7L?uNh?{2?I܄tf??_?Se??9P H?qڞB?g@?|Uf<]?םEkv?N?37?38?*'?\?ʾ6W?4(?N%?.-y>?Jp?¯ek?nA|?ӛ5YF?t?=Tc?1׍c?ֱּJ? %?}^}??m ?nD??#?j&E??7p?&%zA?g?,ԟ?3UD?EbL?dRCX? ?aEۈ?՗R?h?3~y?si? 1"?_?R?$u:G{r?1V ?cݣK?N?Ǚ?)W?XY?"Gꅍ?czE?H4x?ɔ-l?*ɼs?̝]*eL?Џ%v?z>?D?a[,?֋d.6?|v?Hx-?yM/:0?MF&?4#C?+X?^t?쒜@bj>?߸p?߰&!?otD?,0?mc??7?B`(@?wV?c!5@?GL*?g|-J?ʑnG?文?؝,?oww_?N ?ߛW[&?zJ?#ӄ?W-?F))'?=RA?s( ?_ @?,Jh?-cO? .o?/ 5(?3_ E?҂N?2?=}S?T#ӹ?GE?1Ғ?2:+Y?" 9?&*?d?=.t7?x?(,`$?=va0?kj?5?H;p[?9էe?j?UKVl?tf???Pka?iI?I6?O`?ґ\?Պ?5p?喑XJ?gn?*X?8?4%?l?lۂ|?{ɽ"?ͅ?/9F ?㸪G?探?R?au=? ?gl? C;͊?f"ާ?%u܎d?Т |?$i?vUd?ޣ>?$^˅?[3de??- ?@^#F~?M|?Yw,1 ?o(,2H?Љ׻h?Xm?Z U?’R?v(Fp?`ק?mv?~ٰL? Г`?L\g?^[:?6X|?\Vp7?SY?h?(/?Aʣ?~?ٲ@?APVS?ϟB?{y?-? H?AQBM?ݗo?gbbI? 4F?Ѭ>R?42?'=?Mx?gģ5 ?T8'@?|R #>?A! ? gd?Et?^c?5௉??vǃ?C?Z'? W*{?w+%?'bb0?vgi^?y:p?滵"?еV[\?gv?ъDH?ʞi?-?ox?5ه?)|c&?8b?ҳY`?Q?ED?lLS?A?ٚ2?ّ?g?Ag?>?M4?z?I}?nMp?no?t  +?"P 3?␤,„?팳$W?sPmX?MJ?$z?7f?:[?ɢwf?f?N*??㸣&C?~;cc?ƾ ?j2G?*F?۴#** ?eOgX?Hq:?~9x?ܞCz?/aj?"b?#rg3?ɞi ?Փ&??ؕuG?8|?W>?+?LÇ? K~?~(|?(e{6u?+!k?#ʳG)?]fI?9C9?)?~OKf?WJ$?wjH9B?۶OŴ?_\M?qb?x?턉?^8T?0ˊ?lq4״?!BR|?>΃2 ?tI?~1R?Ӥv?rH W+? ͹Jkt?ۏyfL?v}H?(?&|?n ׷?({?}G?V{?)3i?i뢸?v d?e"?ϳN?F-J?!ǒE?T(`)?ƼIp?@28?|+o?> ?ݏot?G->i%u??οt?h?[s??R?لn,B?n? |e?tE?|N?РNX@?ya??D?=h?4)?䷀?? `?~?8M:?vQב@?Kםa?ql??윑`?uU ?R[?{ϑ?>"?ą @?›p?Jz?vD?Bj?}&g?Q??P%,-?d2U?:K?1 ̞?㵅]x?āȌ8?o~_?g:N?l?m??bƸ?פ0?8Q?H??";e?Q$Y?<+) ?Ԡ6 .?B0)3?6?F?B~?˾ ?':,+$?l+?1/X?wH"?zy?L732?W ?*e<;?wN?:"S?ȈK?,!\?k 4?Atn?'8?.).?Ց1n? ЀB?LD$2,?;z0$?cFnN? ]j?™Z?} ?r>`?0)|?SYp%?dqu?)֑Li?1e?1A3?Jl<|??(Ӱ?48m?|dZ hl?#.? acRP@?ŏ"?~?FCO`?:D]?ĵ?X~X?6?U?`BL?QuP?װ Uk? k[? "Š?+8?@?p?gV*??|_?ЇԄT@?H?iq?P77?W6?4b?**?9W8?魷;?acp?XBA"R?(?MF?n A@?Y?/:]$?0U?S?Cu?=Kz?X¥Y?[E.K?ܕ#F?) O?g?J~?̀]PB'?q/Ӂ?Ű #?ʚ澲A?x?>J?րt7?H:?" u?Bэb?xKp?Y?㗤n?/?0p? s ?4+@?=?H㳮?5lM?B+&?j>s?Өr?>G6?у%â?өИ?HJ?T9?^^eYv?0!T?m]?Tm0?Ґ\U?*~4?vC"?濉x"?u?\h?>?K^ ?dJktt? av?]xs"?Us0Ј,?5?WSS`?9?A2`n?+0H??LJm?4?홒.>g?S$!??dju,?B.G?˖T?ce(~V?ƴp{ ?v?=: ?묘H? 'L? 6}?D9O?#h"?ߧ+LD?n`?sDx?)-IeB?1v?cB?U\⇖\?ԀYhp,?vx?Kqku?+ Ob?n:r ?ق ?@#-?Jr?Ϝ?֯\N?e?ܐV 2?Qؽ?~"q?@2^?"|Al?Ptf?5C?| ?/ ?W Ii,?ʗ@b?堵?Dܙ?ڋ??:?W5?ӄ[G>?ȟ? ?uufD?ˢ)#f?؛h?IQ;?, Lu?*ɊA?_cg?ؙ?bTrx?QgwQ? X~?bO@?-?fT3K?@(ʠ?յ8?G?p@Op?_F?2M?i@d ? @7?+"ʟ?]HlZ?짌:8?c:Ğ?B?d&?МYb?{Ȁ?rpm?R?=q?ꐤ?꼖U ?6rS?=8z?Q?i继70?}^C?ݱ=r?qD?!Y? N?oSY? m?1`?҄}J74?q3D8?w" z?DT0?6`? t$O?9IƋ?TZk#6?'G?0?p?r`?҂q?,G ?| ?Ж;?ר ?aEw?h?aur8?x",?,hѻ?/2j?/M F?I%)?(ey?ad`?Wک?S"? pL?DL?ՄݕO?殺/? {?`h ?p1??j G?z ?چv";H?o?ޖmr?r]? Lel?ӦR A?6L¸?Y,c?eQdh?oUwO?2Mqx?\-\ ?ⷠ ?pJ?z}i?撫?M9&\?[$v?c\g?Ј[V?R@?@g?ў?"5?"ج}?Ū7x?g71|?fe|/@?5?)Ƕ8?ԶM?W'@F,?\Z$ ?LTٹ?0/? ?Nɘ? ?̕0?Ƞܐ?ގ4x?~h? ql?I?X1 Z?S$O?@?U-3{? fD?i!6fJ?"x?Ϗ|-?Br?4C?4a?Z.`??P?6E?0y?-?f?~ g?m?B1?Ǘ5]?h%?ٍQ/?T?:Ps?*{s ?옳?ur)V?]!3?Y?D0?ݴ.TiB?}?ahN?''^?4cO? w?c?=Iʚ?߃h7?1?E?'m[?۬?L?dv?u?)~=?Үz#Ĭ?xx|??<'3v?#l ?rҒ?? x6? &.j?ˏ?N_T?㸙V+?`tu_?@S?}W??UbQ?R?:@?hwH?#]g?_?="~?Y\Ó~??8O#վ?Cw?],?RSY?Ct?N°O?C ;^?DZd ?ť8>z|?JcH?"Ea?dP9p?žz?ڷ-`?h{fx?Mda?Ȯޒ1?)??DXF?ȯ Q?؀c?^$)@?w,T?jOY+5?uxV?DS'8W?Ng8?Rqq?)K%?/?a8Kx?j<?럻~?L?0nX?.>v?إt4?-["?i'YE4?ݦT?߂gƩ?օ"h?RBp?fD?~h?tH7~? ?уw^&?:]?_??%n;?{qv=?]@?m6| ?+n?^i?4Zg??Q?s_Kj?SU\l?јQ%M2?Tecj?Z|?"M2V? =@4??d?čh?Ay'p?͆p$?We=k;r?Mv?QY?җs?@C?dĶ5:j? $jTl??`?(?I_3?:?P͡L}? ?몟i? t?[2?j&?;i!??DUu?vzY?J?杖L?ۖ C?ڙ'? i?ֳ2Q?q{?[+PH`?6?兒t?2)7wy?y*w?ܷ WN? 6j?Ǣ?*ߙ?:"?Os?U/x?A>foo?tv8?Nʰ0?b>3? ?K?/.?Q?ý,p 8?0$"&?덪?$rc{U?ڰ}R\?g7+?(e6?4C.??Kz?ĵ?5Eu?̠?PgL?tOa?fG-?ەbw? rm?ꀺ `b?Pr?>?ݮ?٧T^?> ?:?Ԟ$c|?̈́#`?Y&rXl?~ xP?;x? ;?,Ayo?UX?玗̔? D?$w?2 |?\aE?}I?ޓ eB?)L?oS ?ו #s?ԭ?S,?zy!?-"?R.F? ?+?uH=?qU?嚀A ]?~| ?$k~?o@T?:B ?USD ?[?Z%?h?o@l? z?m*-?f-(?DĎh:?R?h1l6?zC8?afn?bAH;?]I?E!z?X?; p?r?qJ?(ݒ}V?z?n??f@w\T??zn[?PY?×{?鍰 K0?}'?=0ks?C9U?ݍdͼ?o{? Zv[/?ώ?u?G*?ފ>?sm`??(]dS?vɵL?V;??Z?ȇ ?c?!%?ԩB?*3Eo?؛4?bx?y. h??Es]?:m?,|?8(h[ ?嵁S?ʹ˅h,?$z?Oq ?Q/bV?@~2`?O6 [?>-m?-p?=?D ?ݳ/1?䠸=&D?Ow?L?G >2F?aXr?M?F:>?݆0?# B?ʀmq9?Tq&S?,ŏ?]~Y?d!?z8*p?!V ?܍^?R!\\?CY?T+l?ףAg? I!?Kj?n?/7L?GO8?^!4?䘿Mک?< ?YI `?Fs1?؟#H?pY,?Xl?6?Pm ?ܯAH?(?Rc?NCsC?/Q"|? sPN?$ݩ?H7N?(d"0?9rG?25$?Fhي4?3O44?DG?ԘhV? w?Sbl?@?SDP?J?yR?Z{ ?"?l?L*. ?׳g ??.}?珍j"?ۜ8`T,?4Y?ójT?!Jy|@?Q^?k̚?~H?V?cv?Zb?D!'?P?ܯf?,-I?Ї^5U*?Ubp?T&??A?3?rE?s ǽ?(*x?{֛B?O~?p&0? di;?cی?vP |jv?gb?ъʠ3ޮ?D?݃?+YΰH?"h?;$?{ JKp?n? ;t?S7^? Խ?ԮyoH?a>??Ȳd?pE+nF?0 c?( j?E?+ ?>X?+=&?9Qp?b$d?Z\?\!?ސ?i ?ڒ<?oI`?98?C䧑:?pEV?3?S?J^~?$? ??9Sڀ?ذ I*-F?]?ߞ̏:?}/xj?d E?& ^@?@??&Ŝ?Āk@?Ÿ2ֹH8?@TG?0 ?Fg?Ӻum?Տ;+?託?6ĉI?i7B ??Nwh?YQ?˜Xj?ؿE?񈌮?e$?Z?܆/g?>?2RN?z?-C2?_璱?ywE? d??IT?0|^?H}?ةn?mn?m(?C=F?}N ?Z ?G|)?o!?^@F€?c ? ?p' G'??J?D$?%֝, ?ὺu?w>cT?ѡ׽n ?#H $??.? Ks]?@c8?Չޅp?=eT?͏Wv?&9v?HT? kFxV[?DH?xEh?װ]Ս?=V?[%ִ?ޒ6?? lC?qaD?[ ,-?>>WP?̊ % ?,v4?Ug?J?ܽv?1|(?Ç(?B!(J?!?ߒOR9@?lM?hK?xc?HH?앂^?╌@Xe?X ?@YbP?"f?4,?j*@0?18Gq?AM?Mˎ?vO ?/8;0F?%Y\\?'B7O#?V?舧?mͦ?(^ ?cZF4?.&?h}i?ö]$?ڥD4?Dad?tD E?Gc?kWQ?0\?Ch"? r?%?_?t- ?P*H?sb0?d[e??Ք:?ܭ;-f? Nm)?Mj>ܨfv?_Ep?ޫ\aPN?¬p?U'Zn?ۋsE?R#܆T?/p ?Gq? TL?,:D?5^2?hVl?Ֆ"?ゕU?zK?1y9?&j?̪c?0/!?5->?U~NE ?ː/?W Q ]?vl;#?\h?.YH?T]s?pb?bL??rD?9?GY?ٮ)m^?£=S*?_v+?qP?n,8r9?k%?"|*j?U͗?4?ԙ.?w5?r?eoۀDX?  ?:f?=$3?eV?ݛl?ʗBZ?ܗ<`x?4hN6?ʴu3P?R2?eO;?5ekz?h?{rO?ڢ7=?#逍A?YU2?tXBOm?BY^?y,?8AU+p?ȇyL?"|?t>??w@?(3F? u?^r>(?؄g$,?1; ]$?T~?}!8?_F65?*+b?.&?wR? (`=?xZ?c7?!둱?ڛy?:eF?.uT?fg&?N5_?#gf?d U?75L!v?H{?8o?Ca?7]d`?q?G?Ju?V@?l|??Qkr?B'/?O@?[F>2h?헑yZ??.`?@}?e?ԋS? ?7O? 6?o?^Y?2t?r?.=?&*o,R?ِ/?~VL?4L?~?)zl2?af?&n?͍ ?,҇B? ?Ԝ.A?wS?)w ?x~?WC4?ux?)`?~e?_d?ue,L?^Ո?# K??_Q?À?heŐ?rc,?ɇ? 31R?C-??㌼Sg?s?;eQ.?L_?"H"?܀0WF? a?-bT?l[?~&?N:w?ww?+,?8 ?r+?_|ꀝ?^a??ZD? pJ5?h9#(?Ř?4?ͤ [@?3i(?T'f??8>?繊s2-?.y?Зl-?‹ڈ?֎Qs ? y?ӂ"?H` ?C_=?v ?w|H?ZX?1ȴ>?3?* Z[?4L?@ kLJ?}{ ?ᦷp e?=F?#,҆?Lʮ?8K?.=?+'?OC?.{Մ?D ?bKh?i_X63?(?ԣL?/y4?,eP?奨oW?}K2?dlY? 217? Z?Oڃ~?I`\?9(^?j.?5a0|?Mm?i$d?a???v?ֹ*eF?M&;R?ҫfi?پ0?N#?꼛3?ӑ=}M?*^?cm;E?ٴp?j6??eVz?ː?$Lr? Nj?7?{B?7 lу?#0?}7V?zi'C?꺁[8?ϙ?-8?wD?N@?GE?LEG:?qA}?霎??sUg?L3Tp?; ?>JX?K䰦?EƄ?񉁥T~?*wh_%?❮u?bo;?k%~N?fHcf?CϨyf?.?};z?~?}~?9i"?ybT?vfR?NjX0?иL?lMJGj?E&l&? ?@&:H?9YT6??Ṇ"&?nr? ?Ǫq?zȠ ?ƬofF? ?] ?GL?" ?>jʖ(?Su|t??ΖI?A墦v?Ÿ9E?@ѧ`?="I{?1mk]"?a ? u?ኆ2???`m&O??h 7?֬yȡt?y_:/?ׅ&?8J9Z?t4?ZvFd?"z42?WG?ƓQ?a͕1?yŇ&?#$Đ?Ѓ\t?"@TM? D@?~{í???٧;sX?$?m ?B%x?6)R?(Ye?K𳆙`?&K@?UJ?-?ӑߟ@?Rq?vyIu?:*?8_?u ?*om?w^`? z`?짌?gۤ8?,WC?#HG ?6@?(?֮?5N )?W? 5H?މ)Z\?<`c?*FL?4HH?!Y?%)qr?ŕ?݅PuI?bn?|^k ??V(?h.?[J(?l?[ȎNg??“F?ڲ1T?Ѻvy.?鬡J?ȉ*6?T+?捗`h`U?:p(K?GI?ɠ4&?䱠nù?h$J?*7ub?InZB?Ǵ?!Z?Ư?: _?mP q#?gtF?4[Ǔ?陴G%L? a/ ?@sA?GKw?軼z?+?P#p?}?Ip7)?SPVXe?ijՌ?ZF?S?-s?LV0?c?A?e!E݈?ꀜѴ?H?եX?lM= ?`ut?th?oM20?:?L }?ȦK?N2?NJL?,2?"E+?튍!T?gF?qUp?_?:{b?Mr/ ?S-?ףҏ?J@)?p??_?IK?j?Qݽ?b'?x ˖?Gī?/<-? 䘊?ո?̴T?or?p5i?~D9y?H?8 R$?Ss?Əf?r?¬q?n ?r?e?%q4v? 'B?׾=E?}\j?6L^?z{?X2?߇b?jL֚?jo׳?NϨ?R`H?r_ ?}H*U ?26t?Y^D?Ch?0?WG8P ?#ͭ6|?^h?eձ?@#?1Rh?/^$?1?s=N?ؗ>?$ÑS=@?Xݘ?#gW?xKm`?ZH?ᵈM/?X*?42?3%;?؋i2? }\p?s*RN?W>ۘ?ՋLq ?G?怜H?P@f?v!?c93?K"E?;D}s?k)"?6!?@Rsl?ߦ_0?)[e?ަG[e?YW?[ ?,`?Fr?G`$š?n?J!?*fJ?cK?M #?珐q ??2l?䟒>h?w{Q/?i?|D$V?WK?['5?Zi?v?&?0GE?{^QՀ?@ /T?^B)?Bo-?wz?^h?}? ??<?x;3?`M?+?Hi7R?h4?VD?_Y?5 ?cSi?ا!n*?{1?1MDžB?TZޘ?vT?3ck?8~R0?㖄w32?n[?:?'a8?Y_?ZL?0а?}u?܁{l?3pG?)bM?0& t9?d/|?Z*R ?!H3zҼ?+0?O8?럤=tH?Q[X0?Md`T?E{?#? `?od+ȃ?:ڠ?UO?5ZR?`O,p?gO?mkQ?*u?p ? WK?!Lei?ꈫ,?XH?ȑ)t?k~Ym_?TuB?*?ފ@8?ڣps8&?-O?d )?ilj?a3?͜A\H?_'f?Em?EP ?B?ۦ?މ dV?[J1z? ?6lˮ?42?ӤW?-PV?1V2?~R?eP?lG?S3|?અ?u~? WS?L ?xj`?/L1 ?,r?N?ԁZ?B^?_i^?`b?Ϟh? ^L??$jE?A^?\r6? ?.);?3!?H=#?Bk8?5IL?Gw'Þ?㇫Zt?࠿j?Hղ???y,掝M?1C?0b,Ƀ?vں 0?߻qz?'BQw?j^?ֻM?&[?ts:"?ѝ/,?JKk0)?+ ?Խa?k\L8}?|=Uw?뙵y?Ӆ?j?䬐~m1?o^?GE~N?䒠(???ޤp?67d?r?0`L,F?Z?8C|r!g?R7,?9@o?NA>h\T? O?ʚ?3&BH?QbI? 2?KV ?C7?K$,?K9f?Mn?ر]58?'1 t?F9?9F"@?M'7?*P ??+?r_k?Y`?@S'd?{ ?e&?L6?[&?*?< Q7??H? m?9,4?3_@?!;?0NO!?ۉsu2?(M?'Q?T&?\?k}?:)M?Gl?ӷ*Njx?v[g?;S^?睐,~Y?禘oY?sS?sRw?Mbd?hF?;* z?2?C>(?ׄX?ďb?M?MLn0?qX?m#Ҫ?3g?~ơ\?ʸ^?)ٻ?D>YVI=?(t?A%#?o/?m??i&?ì?u|>?B?H?q5p?~?vKt،? Ԫ?L0.?D40?>{L?vP?ͮw?f @?u`?[?!;?ybm?[G??яY 2?wu?Rr?Y?3C}A)?њ}p?n 6?BoÙ?y?^֣?胁H? j ?YGwݴ?g?ƂW?M@UЈ?ҟ?U΅l ?0B$l? $V?x ?Ҵ ?W2T?pU[?$?|`?Im?͖r?[.>p?|]l?guQb?o? yI?d9?M6m?߫uŃ?-m?Āf? 6ص~?5?Qn$?VR?dua1?}q"|?5t?j=2|?H)wL?* C@?ӀD?KJ?|-x?&+c?^GwP?wy?_7?}R)nK?ȿ5 ?ȱ_?K$5?֊d?U@?9G\0?ׇ#/??8/#@?#P?^À?ׄ(Q?ӕY0f?#O?Yvґ?0Nbx?֙r?د d?͗#D0?WB@Q?־?͕1Ƹ?grQ?.]r?׭  ?U?2.a|?{IW?:0;:?5vX?TcV?ڴc\?IB?˙gl?e?B@?.g?װz p?eg z?zg5?j6ؔr?V?'ǾT?DA?/TC?abB?C?3G?vP{?/?ݛ|^j?Q9?]8?xL?f ?WS|?B? u9?F'v]?e ?k?AN?< ?ځG: ?Iϓ?,p? Ag?t"?|^r?5v?vP6]?LL~?XD?/@?Q+,?Yῠ??)-U? ?YǦ?D1HH?s=aM7?MWJY?7=?f仞?S!լ?Ɲ'/0$?ߥ4(?WH?N#e?u? 0?ƕ/qa? DC?? hc?}qI?j{`"?+?c?{y?]a?BV?0?>Q#h?ū^$\?nZ`?XA?!ݯrh?l6 ?r} ?O4??z'-Z? Zd?뺵%? [&?* d@,?@?nj$D?{}g^? [ƌ?m?Gd2S?>A?rȏ|?N??DLe??q9J?ʿ?밂@ f?wE?(vI(?v ?/3b?S&?&8p?@?#?f ? ?ǀ?E?B?Sێ@?E??J?룏j ?jKZV?w[??3WD@?e?EDv?zs?Ӏ?Ʈ?z`?퇥(嫚?gY*?~ul?W9?98?hH?י|^?O֠?gpW0?R4e?çiv?,GXz?h&?i?Sv?ߜ[e/j?T G?U4?te?L縭R?~?Ezt?柘=?7B'?IPH;'(?ȧuIA?}/:|,?75?(&~3?H(n?$% ? e?b5n?0yO1?!N𨗌?%$h?[΢J?ۢ<?mSr?@y?Q[ X?ښ?yop?>-@?@ %?J?Lᶹ?۪c?o VW? ?J/ ?53-?Wt2?Z?? 1:*?IQw?}};H"?h@Mjt?y?NH?b6?dE?C˽O?QX??vT[?S?SH4?X1@?]P5?fr?۟u?)" ?:69?.;~?poM?z;?w@ t?v-s2?IdM?¼?Ǧ#7?W}%?JO28?+?{>?բ y?3I? H?(V?=p?Z?޹82-?F;b?p}g?ӹ?|]??xV7`?d?!C?O;9?׭=?ᆣw]?Zvf;?@?ؽ ?s坝?<?`WM)v?Ƞ^?fzk?d?WYG?t/?x9P?|D?#?2$}?HͫC(?+!? =?%HW`?bf?ٽNi% 6?/1D?ݽ_'T?.Bl?0?8Ǐa?R͆+?_?R1"?М2Kywj?\;?+A"?DRL?Ѱl?/+z?Fd?A?䧳ktM?рJ8?O~ ?ℽ?D=?2)?Ml?؀i?Ȁ'6?V'd?b8X?%7C?~[Ѷ?蠜c??|K?i7?dO?뛝M+?`0h?N?틪W??CBG`?"%?Y8?ܷ6$?촎̎?T0"?s篈? lޣ^?yK-҇?0":?A`Y?f̼?D?ʄ?D7Kk?V9M?ۋ^x? D?Ű?陦bM?rm_?4?EB?qwF?垨?A?͙?Ί+?(Z͌?&NJ9C??e{?mJ?jOR`?R.5Ÿ?bZ?umx?nX?S ?/At?X;? ?P/?p?6f?iNAed? v8?u,?b)?M7?҂ג66???!y?F32?_?V?c?6/Y8?n41?}Irx?k??쿀{?ƈ?܃A,?*Gz?+%o?Ժ2+?Y?߲2@?hQx?'=kg?6=0f?ɥv?VVa?vH ?!~}@?lv?]3lP?ޞsf?H?CP?#b ?Lf-?˜m.?=-?`J?q2)^?Lw¼?2a?:뇠?j']?ǃo8?%3@k G?&i j?[`?{?qG?w ~~?v7#0? %ʜ?W8?x ?Bh;;?#*]x?Fu?MX?؀08f?˦ty?"F 6S?݈?'`?/SsK?.fx?h;nQ?hj$?H ^=?ؒAe?m?֨B?^?*c?Z$?ӟ:?w?Ɍ?Ơ ?u֡b??⨇^,?WIm?i%n"?G:9?B~q?a@ .?75?ժC?.?f?%=q?:e?=P?+H?5?fڻ9?[_I???,?!?^GW?+W?={?.Fa? |?e4?+?ꉥC}?3b5?iv,? ?sw?snŐ?єm ?ڨ?*8?x+?LG?NW?8k?p[Ô?DK?,??zdV=?ƒҸ?Vg?@*?ÌSc?oJn?BRm?%J?ȁR ?[#?s8?q=l>x?gT?!W@?d>v(?͹lO.?Ȼ-`q`?@Ǖ?dU?տDl?3(?t*_?\?5C?ћF? 1H?^u?U ?0E?pfѴP? L{?ѩOX?Φ[-?Ðܘ?^z+?Ƣ%3? (?Ujy?pPdUF?֖ӟ?yHb-?̽6^?F?'H?1AI?8)??$wJ=??lQz?sP?U>?w<&0̈́?1ڱS?(l?w=F>?W?〛◣?,9ol? DU???i~?闌P?r? 䆌7?+_Y?$tO ?E$&?!C?:i?xbr2?I?aWm?|J7?経.L?eA?6 3?x>?כ(R?q)P?㜇`R?@ ?߅8Ǧ?q(r?نWT? t?7Yp?~.?T\?sO?9)?^1PNJ?ӗx?젎T8?3mx ??9:?^ts?*?Ď?Ly{ ?qӔS ?ɌS$:?Ty?S R&?íĶ ?#j?bKV?"?ݴ}?dذ?%l# ?Z?x?*$:+?L?pc ?3?cz?ͣ ?ve??~qp ?%,?ۓ"7 ?P?LXȸQ?J.‹n?r;? Nz?О; ?,?$>?|^? ?gn?"&??_C?MǏEN?b4F?Jg?бByO?9~ȱM?ʤh?λջ ?0:Z?կY^?8R?`g*?CN3?↶w??C76?E،S?YX?n=?f?x?x?%+|Y?6{j??m v?1B?Y?=sUdj? pX?,f?^[)?qذ?пԨ?FTC?蚫!?jk?d?oP?ͧb?!Cs?19?R5hH?G?6]zm?՛K? }xl?T?vq]?=>4N?Ы2h?*?qR +?Dx?Zn|?y1?5D?σ8?*Λz?~?Du5?%ڿ?߰ T ?"ﳀ?ބp?=uP?߇?i I?kEH?~m\?u@,K?94?脉7#?PD? C:?JA,N?~/)?nOP?Ocy? `:?/`L8?\NS?g?D%r?XL?3.1%?W?Ѝ!1G7P?]\?#O/?wa?H?nH?۔aD?ں*{j?9&?%:)?QsXy?98?U?2`" ?uI?$(?ڰ*?D>0?+?5>4V?ºd?:Vy?cl]|?yV?hYc? m?f}6!?|?Et?^a+??,'F>b?D?` ?:Xbao?ն}?z?UPB?"aQ?C@̺?ĉ2?]zM?'i[p?[ ?.7?cI?ʼnP?8-29?PZ@?M;=?lY? c?ìVk$?WKyp?ȇR?a~A?ƶHf?mhK?h)k?|M>?"?T'?ջ7¦$?_Yf?w`?6?;a P?0j?ӹp?\?͞'\Xe??@?ca?>`?ް?>d:?/?ɩNZH7>cN?c8`?L(?9?=&?뚗2NV?Kx? U2?# ?6J?&'̈?"?: @V?"\d?y? օ?q?fwU4x??إ?(pk?8?p3?ʣ ;x?E/Fp?Ūd8?Ҙ僛 ?UgA?s>?"a?׈謦?jb?|\T?Ժ/,0?jW&?|&P?f;&? s?ڏC?PD̀?ﳑ??N,?1?v^?F<'7?Ӆl&? !r?>__nt?곜?((?u?.RH=?e?Ö/?z}?`pa?ew)?%p?;%ǚÜ?Gei?4`?;]??Ձn?鎍?S?٢yCHN?Fg?iXUf?d?jL!?ԚO?M?E-b?ݒV?q ?גwx?I?v,?(I?=xM?^?Kg?KդO`?p)R@? *?+6?C$??L?Ӆ_?h+?ԁp?NCo?Å?ʜbQ?s#{? 0n3?Ir?3?l(?Ӛ?أ7D`?Яg}z?r!6?/?|+9?k?*O#?\!0? [/m_?u?L?n=v?F?CfC? }."??\ހ?׫1J?Jˮ?xpT?yv?ƒy g?rz|??\Wvo*?H?*_ ?܋?x$o?b.y?ŪP?)? _a1 ?Z%r ?+F?'% !?V@[?ҤVR?]'?8!$U?א#?*mI0?-_||?i1?-Z?aYQ`?T/?U4?,?'~?9@(?? ?GLH?ꪌNT"?E?*3?! L?Jye,?%1?o62?a?_{R?JJ&?qd?BW?$?Nq:?? l5r'?լ^?ؚeI?Ŧ7[2x?uk?L Pu?|Zǭ?S??4!EЉ^?¯ìd?? n?4D9Ou?R/^7?ٵm&?O$,?J{?9 ^?*De?QV?Չ?Y{?c4B? ?{Y@?[73?G{$?z ?R^?㣌Rh?wrcD?tN?3W?;O8?+ˮB?K?UY??FiP?*:-K?={b*?yJ?y*? ?n ?~;?ԇlb?(?qj ?yig2?͖Y"?8gt̿?G8?b?yz7R?Ô<$%?໣GC=?ג_?? ?G0چ?xR?e~?fxU?:8z?~40?!1\?@}C?omr?&?ܝ䃌p?տV ?b!?1?D.1?8M<0?כO]x?赲[?{?5?A`? Y?D?=?˖ˣz?5?Ԉ?  ?=&?A~[L?<`V?4M?ȑ?ם2m^?0ZF? f?)?jޠ?L٘?ϕh(?'9i$?g&2?ʘ]?ة/u^?jƪ?SIHp?d \}-?فB?j?g4?V?ʿQ͌?-X?ݿ>eR?йZ}(?o?}ad?R?Ly 7? Vw??E?ġV%?nĂ,?E ?s?ʃ [?(1`+0?'8?!`?(z?@'u?ѧ+1 ??#g6?ѽ?D?KOWY?-' `'?ӛw?Qm ?{`2?I;?6F? ?@V \?0k?7'#Kk?ȧA?vzn?ء>l%D?^䱆?*?U ?Ox`\?ۿ,:?xE ?GAK7?U"a?PW\?%y? ?oG$?gYG*{? v ?h6?6⧛b?nU[4?!t `?/?s[~%?al h?ŕ?:͞`?q|p?c?i?k ??Ȥn?3!?ᤶ9?:E~?4d]7?2)'9?HJQ?%o?0ez?ZG?0 h?븦ƒ?̐i?FT?189??آi?3}?c τ?ǫ?Z=P?,~??Q$h4?uz-?; ?lb?ʹ?2?=JT?+߰?'%\}?Q?;?` ??-SQ??o ?x҇?nJ?zפs?bvf? &"?iD??R?rc>h?HA"/?f1\?u@΂?sB>Z?ٻ͘?ݵ S?/dU.?C F|?R?)A)? ? ,^?5{?ԟq(?[gF?ŇT? Y?)?cΘ8?~v?VN3n?uK3 ?#[Jma?e@~?S!s?x)5?ԗ#?p{\?+]?C=v?繼 ?,h*?:c8?J_h-?{#H?P?jo30??LK+?cF+T&?$y? 0 (?ߜvM4??DK?':$?ڋPU2?F95??~? ?]N?z.?x_? ׸fv?ת1>?c7X?/<`?!8?? SG8?F?o?ۄFs?^."<?y? ?hg?jNz? JæN?g*?彂?uz?2{H?BIH?,l?5?@Ø?Q;A`?‰\[[?]?C d?޾C,?x7? ,M?h3P?Fd?DS2?pZbk??zU?0Z?c?NŽ?ZR+?2hD?`c?.??!d???(9/?N?Ǥ?C? ;S??œ ?%=-3?[ ?=?A?Ղ3N?p6eM?sO?œ?Nㅍ?x?/*]`?q?{xr?L?PnFYy?H8?EF?#Y?r^;$|?QOy+?xz(?>FW?x80P?<-M!?ڮ?p/?5:?r񛈴? :?ȦBXÎ?dgp?+Rd0?ҀOr?Ԣ?)EZI?Ȍ ?쩚ݸv-?z%um?Y+O?aO@?wZ^?2SC?}G?6B?Z:?os`?$Y??׎&g?A~^N?1sø?v P?>H?ؿn?9ka_ ?RQt?dQ?hˊ?I8?셭9M?M?/Є?\j_?e#P?k㌄?%fL? vN?#9?%s߃?(a ?3;|?ߋfa"?7p?OS4z?ONF?M?yԤ ?&8KV?KFm?-?^U?8aFm?N?ٌs?ÍQ?+[T1;?ADY?V ?yjz?y4d?:Ásp?a?W|?qT?죓?+?ކv`?R|?݂bn ?޷[T?~˲'?~S60?!K"O?ۂY?W?|3㛥?[Q,?\_0$?;?A?̴?eض?l?ʑ=?)k_?ݯ?ɂ~?,*z? zm 9?ty?P^?R/p?}?EE]?7ڠ?Ȳ"?漁A$?pXE0?dd8?Ȍh^?P?8}1X?ye?cdPk?f9>H?P?#P?)A|T?ܗAҸ?pȇ?ܤQr ?{,??B?FQ?!A@?" i?T?KL?!2L?n^ZXbP?e\!M?ڽt B?nq?h3ȒP?L?Z?Pc?:&?TqO?o?vn bU?r3?d?A(e6?QD?Ǔ}?㉗U?i>^ ?ĨE?ymڐ?1?˹fg=t? ^?X ?v>@?8:?L9:y?63={t?"T?[k%+?~r&?abu?\/?>y?+?Fyaz?C{w?I??-?Э?-D)?:?ﹷL,?C?ʼOWC?F6X\?FN"kD?ՉYV?0(nR?wX?x2Ȍ?{:1X?B?6φ&?՘4?҆t2?Η#[?Tw?еʀ?ޒ`5?gi̠?"? ?f?ݸV?^8d|?c+tP?vq?ڦ̞Z?$?e<&?[?u4?{ގ?kG?NJ Ӵ?ӏS/F?c|d?N@?P1f?,v?UT?Ko?ջu,?% ?ǐI?[?ymQ?ܣǨ?ELZ?6h0Y?2?Ѻ:ZaB?/J}?5Tbf?چx?B !?!2?Ȣ]?݋|4?؟s2ʹ?z?5EP?Y z?S$?w6?k!?~?>D s?].?5{0?+!99?KAgq3%?jZD?*)4x?I?Z%U?&pD?li?/d-l?(K ?ᘕL-?6E`up?K U?6Lx4? ?Z`?͗ј??vlpn?维rFg?9NŪk?]X?j%aL?M ?@?2?̰k0t??17?]áٌ?|j?"?C?f-E??*j?6?X?Bb?o _?l.P?:Tj?ʔeP?/ ?಑?~X?ޤtW4?̆El?Yɽ3c?בp?"~Fe?Kɋ2?ߵͽ?=?B&??bV?| |^?v?-bC?*yS?ջx ?AH'?rnݦ4?^NM? sm8?G.J}?_~?oU? ??o?}y?9?3X?F?p?" ?7~]?N,2Y?W?Z|Ol?rd3u?qW}?UDž?隰,?ͻ^?-8? )??gr?+<ݮJ?Uv?f?t.B?ƑFW?]\jJ?#1g?찙6?>~#?A/?-09?|~?Q?gg?煩?ؿ ?^?,?[D?6?i*cM?Ǒmm?zsa?ہ (?(KE,?A獾?B|?T@?(f?/o?Vϼ~Q?V?9h?I?=.?ߍIfv?ٓ5l?Υ̕?,3|?lm? ?zj4ޣ*??^?&N@^?S ?'5N?Oj^p?qqpc?JrVM?ל/ v?l/g?2:L?)&ƺ?Jl?D `?W@f?E4?a?%(?B?zAB?|Dte?܌?9(UCT?֝r?Iߴ?yr?3.HP? ??q2)?п Ar?Ua?wv?r?YPbJ?vH)OQ? ,?ph+5?̀Ǭ?{E+?ފh X?ȴn? ?ͻ ?мi@sB?Z?Ƭ?}JgG`?ӊ,{?s|?}?PnG/^?:,?Mߨj?얜?5$-ѳ?b?eP ?Ҷ1$?H?E1X?R|,D?,p?I ? /?ڐv?3T?΃J?^@?'?Z??d1X?<t?m9969f?݊Z ?ipg@? {?Bz?qa{?Se5?3`i?j?ZA10?Ք5q5?0j2k}?ƅ얘?ҽ2?s٪Tu?zK ?W?j{9C?͒Nr?YsId?F@W)? ??=)L?X?ʼf?樤ddo?@x ?^y ?)1?+(?Zk(?](9A݇?;n?㋂hU?$e*?[F ?u:%?U?? ?O̕?ϸ/bS?˝F(?ixX?绩 ?A d?KP>x?|+ͱ?? ? z?)^.?|@?ıy}?B??ܨ"P?(?L*1.?E,??8@?c^v}U?װ ?orS7p?dJh? qF?Hv@?ԍ.T?&m?Л? ? ]7]?7?~HM])?xߥJ?3s?A?j 9h?*c?! x6?ޣοz?M\?d?چ(?Z+(?|S`?$OB?Wo?Ćߡ?~h?jN?V9 ^?ïT0?ߩ?CF?j?\m7ϩ?g ?YxT?C?׊]8?U6JWr?xEbm?J"?ZVX?cO?F^ ?ٗ[.p?p$??8:?QQ1)?*I?!ɹ?c֨? ,t? #(??ʻ?j}$?޾^?X.?AiT?&$-?Ӓ?gB?"+%0?펤^?%8?5ԦƆ? ?+D?ᐉMx7?bK*?ⷴ `?vJ?夠"?C|S;?BZ[?Ԇ?9i*,?㒷Z?KN !nP?(4?ƾ\?Joc?8%U?aٚ*?.٫Y$?e?;U?6w?j)?;\/7?ֿ cu??Ȍ?~?RlZ0?8JW?jGK?5?'78?TK?`s{?Nr?u?!?ܜٽ?&lE?7]A?np?9??j?$Lwє?镖U ?r5׻?ҘNS?{s&?ښ?U9%?S+8??ٍw)?"Mh?gR?[3:?W@@?聠x?5Ծ?J?y <2L?!|0?ۖR(?y?&A?_Tˆ?ĬvT?)dtl?E*)?Wݮ?G, `?@b?Ѫ?spɪ ?Ik?? m?6?IDW?3d"/?ax?{+E?=? å+?Vw?j8L?ݍQ?=?b?Z ?˗HC@?cS4;? s?|$ER0a?\k!?PE(?h޴w?~Y?{A-?uAV?Ȥ򪈤? ?j?֭?Re{R?d>0?c$[?+2?ҚwEWf?MT?\?e?#ؕ9?t?Ƀ4j88?ˤ6 4?q?N?&h?ȃ'(?G",?pƧ(?JNx?D0G?. ot?֥R@ ?fP8?N_ K?WAр?* %^?۰czv?㳭1?eS|[?6g??jmiAh?z!$?-l?O)h?F1 8e?^xw7?D}?;g|t?aaߚ??tyvGF?v-s?''p?ܟU<.?"S?q?ӡS9?I"?ؿS>?  w?ȝ~q?b޿?ס;tT?q??GU?]+se?JM$?^G?fp[͐?"?a?r?Ȍ}}P?ʓ1?ɧJ?4X?m?$Bߐ?1 GK?E͈<6P?16?ϲ+?eS?@t?Mg\{?]7?ȟq8?^;=?@bT?GY?GEP>?̭G7 ?⼥6?ć4? +?w?Z ق8?x?{?wRK?|?a ?n<_D?V+?ϏI&mL?mF"?٩.?К7A?~G ??/HP? #g?0u{`?Or?yk?H|x?%OP??˖? cbl?0ܞ0?ߨ9?nMQM?:=x?zi? +?%9?ӳN??l L?Ke?r7h?.;?lg?"?*+r?(?ڤG{?[e ?B(?ДVxd?^%f0?R?oU?m^rh?ظhr?l,?B? ?lf-?f ?Ȍ?u ?TQsZ?[<Ġ?ѸI?nH6?cjC?kۤT? Na?kT0?ni?-x3}??]?X[?ƚ?چ+h{b?[R?–D?J?jez;?r͍(?T?Wޤ&#?ȉBL?rȠ?I?_?ጳ2?XG5>?L|?L?ҒJp?'>@?}!?lV?iD#_?؇h?oi G?d?s?Y᯻?LP&v~?K?޸l6 ?Ch?a~?ܕX\ڈ?ӫ>R?s?R8?wtj4 ?8q,?/'i3og?W ?Dˡw8?׀!?z%?C642?hg}?utd?`.?p?y9;?9~wM?ݏx&?OP?~ҢĮ?m;?F?~>T? O?ysG?g#?y??(?ܜ1`ch?JG?9V4P?t>"?$T?^m`?榉L(#?ꊂ hs?63X?pVX?;y1???E?Zj[?8?S/?:?`?y]ۀ?ꜩ?*??,?\yf?z9?vzB?͉s?ڭQn?7m?\PT?k̶}?I*j?D+? ]?<?ij2|?D7?Y[t:P?fW?Z?ė^&wx?^G?+{@.~?̋-?OQ?Q!kl? u7?v6I/V?DK?.?A5?ͧ?yF?6?)r b??_9!t?d?Ү %*?il?ե 6?Ϫ[g?漇&?$Z`?d|7? T&~?z7k"H?j?x=G? ?N?u?sN]?w;m?!c?SWڋ?qG%A@?R0צd?qd0*?vUw? 0T?g?c~6J?ݣ7?M"?Bo IL?\16?V|{?̄?e'x?é ;$?'Ӑ2?kP??؁?O)`?L?㎜y??l?Ӎ7)?˺{_? x?TR*J\?Cx?F?bx?'T?w=˒?ނ"+I ? _?}+4? _F[9??(?og?h?װTt U?\Ÿ?1K(? jfd??W?ˬ?E?5?I?Ģ> k?ħ֋(?Y3n=03?5bX$F??8]?wC(?todc?gC?u0?yK-l? #Yv?-a?t#x?&c?+eZ?mr?ʇ' ?J-?^yP,=?aIz?ኒXC,?΁?"*?M_ձ? *`?$y|X?\3 ?n' r?/`?5O? )]?Dh?dqn?Y ?i% /_?08L,?AD?/)?pCr?Ru??WȔ:?2&WK?ڏbYD?j*$?i?ȎDOˉ?#|)?h?a ?Vh?Sf?ÂD?鼲Ch?we?Җp?&`??P4?/?:ӏ?[DS?Гa?ý yg?'%6?gE ?h?\pp?^WP?\ީR? ?x[l ?HN ?h?+p??ӅK^?@Zl?ƜZm=L?2}?i]?o-0?ՑX>? JY ,? օ?H.^?ұJ3]?n8?xDT_o?֚y ?މgP?C??I?du@i?DL0?\?] ?DV?N??x?sb? ֩?3!{?Zf??ȡY?# ?UgoLt?d ?C ?o+%?v)?}_F?ޖꎨ~? g ??V3%??=b?hPZ^?TUTP?z~b?4޴?ˎj1??Ҿ8?ꖪrOp?ş^j?0yl{?Qjq,?_Ѷ?ؐsӀ?M,k? 4q?B-t?&0r?|3?k7Wk?볷#?OU?ڒ|?Z0?47@?΄yo0?H?i,?̹W?HW?zd B?٢2 o?_q=?N=Rl@?P7"?:N=p?@Z.?|W?Zpx4?EsV/?nВ??lS?=+?!jQ,?ˌ,MX?ǭbX? l@?*x?_>"S?t7|m?qI ~?ِ?MB?Qj?e?L:x?C$?\°d?v'?onh^?GT??ґ+ڂ?zǸ? ǛZ?.?5(L?tg?&G[N?)T|x? mZ?FO ?ωj?۝hho`?&|H ?x?؄"ź?>M~O@? n 4?Ia3s?SM8?UʆQl?hx?ěB ?I4CG?c+Xg?הڄf?֯f?6 ?!& y]?õ5?Lf?弹b7'?)B??ˍ0[p?&9?,҂?RP?\O$?K b?ܣg޾?;ǐ?ё: ?MK?Xp?'?e;23?؈M?;pjP?ݦI$^v?ķ[?%(pS?ӍȧL?#7?x@{ ?Kic?(Rz?FzH?, .%?׏?]d0?b?B8eVL?,[?鮥Z?' d?.?뙥]?(ϓ??4 ?ߋi7??&?C?ގ=k?bcZ?`8'ֆ?u+?]FW#??tAq?ķhGx?KG?i(? G)F?Կi?L2sR?gnIT& ?TO?P7@?w 4?0J?^%j?VP?,p?29}?uZJ?& ʋR?膬3]bl?կm?>$lr?y?杏{r?Z?5G_D?훿l3?Lɺ@?q`Hj?9l?d9,?ůGb??ڕ?Xu ?|}56=?xd"?¶,@?rl#y9?H'͘A?I;t ?@p%?~ ?eF f?ܡs-.?D;c?r}ĽE?2l?.:3$?d'/ ?P{?@8h?GSn?x.?B` P??~V?N~?/?Շ}?ߦ?M?mh?I?w!?/,j%&?Am :k?x x?ƒNa?yՖ?S&օ0?OK| ?JbK?/T?:͠?%?C˜?ܖ:"?30?*7?.X˃?[?#g?XC{՘?erBu?̤G?F`q?_?]2?o~?So?* ?/֋=??8??򈵖?z?jO}H?.z?,QVD?h?mL?2±$?a8? d(?ɖޙ?JQ?֧f?֩R?¢Nn?sC p?_'A?Ӟgk?o*@?k}?%Z%a+??ׄ[},? yYb?چg?id|?u?\?͎`)?_|R? $z?!?ހ? S:?؉)e?ݢy.?ɞ? ?p.&w?プ[$?߀*1;?b< ?눫8??r+>/?9T* ?4?Dfh>*?2Bʎv?ݤN?>E?Z(4?sPw ?$=b?#g? R8?)f?> R?*L ~?:?Q3G8?ٻ{ ?v?G-M?Z?dKƊR?Iw`?ԀO3t?%P?v`?W&?c*8?弴y?ჰnM? ֠?0H?ZpP?+sP?bP?g>E?yyc?Ou]?Ryk?~H*[M?ѐ?E:҉M?G+6?Q ?ʮhTaP?_"E?S&?Z?1D*?b T%??;&?e.?!?֠*?᫮Uv?hd?+Ę.?\1 ,?ծYÚ?7?SȈ?ىT"?ߚfm?5?jh?^܎S?Z ?Z $? Xv2?BAm:?-p8/?ގ:?. ?F!$>?`Q?΃o?-?uA`??g4?Ƿ?ƞˮu?$/X ?|2X?h\c5?иsK,? ?Mi~??C]h?h?tt?З*?=Q>"?=B?WX??HPw?ksVC?pp?B?QܵdeL? 4r?pF?]?x?⫌M%?N ?5&1G?Z)Z?FmC?S?&?1%?-Kͽ?>?= ?a=~?eEBdJ?':?3t ?H3?kJ?> ?Oz?@30?ߢZ䋺?Di:?<Zu"?lrn͎?@?C ?K!u>?? DAx?ӳ~?\Պ?A?,>-j?@$g?qaj=?^_??r?؅`?Ij&?xuW?K.?%9-?wa?ґA?Nx9?q_?{T?N Xo?PjU?˰+a2?5׃?h?}N?4pp?/J*S?nUq ?Opk?qߴ?A_ Y?]q9?l^ȍ?Q2?A1U??^ d?T?k"K2?CX?G\?ǿ^t?6?Vwo?n(d?c.??5?ca?y~S`P?]޽?yG}? &%?`%?bd?`?x?bcP? 6T ?@?)2Foņ?~j?v8G?6`v?靈~?j,YҐ?tn\?]?Bl?~-h?g폝"?ジ(u?r7?F?ؙD:?"5?b?Ryq[?>(v?la3?ӻ?a?(eﻺ?&.???&Pר?=?t 7'@?ߍ?? #?Y@?~E? xν٪?@|?Ty)C&?زtAj?Ț& ?撛FŻ?(-?4P?Kf? D?f; ?{o7h?OZ̖?2L\? v$V?Nm? Z`oR?ݿ`?ö?e"?яV$?Һ]mZ? 6P?g 8?şlU0?=|b?᝿9M_?b_?I???Vk?peц?C?X?}B/̀?s-?"?&a75P?n$\? 0?2 : ?*PS?]ffI?HLu4?$2,O?ԚY{?  d?ьGHP?08?`IrK9?Eb`?p\?+%?帠w?d?ׇ!: ;?\8IJ?o7?K¢?e?[.?wEb&?|$ό?޼qT|?w@?#2?RX?钊X?矣8?5?ܑGy&?ְZ? ?3#ԑ4???8z`?A6=x?S?x|t?Q`ؼ?Uߕ?sdΠ?4r?%?UrG?Q5?ɷx?>lD?'n?n?B?(9{??38?R!?Н6F?w*r"??LL|?.y?!#E1?~#[?Ԏ-?b9-?p ?)v?cCP?ne?7n?7+?؇Ik@?vB?8u?%P 9?_w ?eL M?PM@(?cfEVi?]H8?듃=?哂IH?vuY?0'p?ؖi?dž4Q?5R?4L?`.?Vy0?V$?ኅdQ.?A2U\?5V/?ڙ?ӜUP? 1,?nt?&?o㝦cf?7>P?f ?=?()?KX?#-+f?QsW?o7?,?ߕ5!F?ld@P?2ˆ?x?>?ín Y??(^,?o? =? 6?tW<?4-"?C *#?oj$?:&Œ?4?7C|?Cp}!?M?iQ D?pr@??hA?$"?$?1uɍ?@=?Rj?'X-9?D46.? ~p@?#J?SR?([?G[)?X5a?&nH?1?x)?æe?D?Ȼݬ?>h@?a,g?~8v?|s򲤕?殧M0?_B?O/?&Q5> ?j|?xC?q@:J??agRK?N~?ú?D>L ?RQ*j@?H;?u[2@?j*_!??Ŝd"?zJ?.iJ?Y5XT?H?篫;"K?X܁?aWt ?|UiZ?h9A?kS^?Ӥˢ?~ T?lf*?ǁox`?ݧKT ?%?^?n< ?_㷵"?-ZqD?Z/4?+١>}?1;W?0)i?ݑOoL??Y|=?s?эœ~?05QP??A \?"JXN?ʐ?H???A%?)J?yN? ¥?iw*?#|+?ta?s~?Py??)G"L?Mπ?ڴTl?_ "?2`U @?7b?w?+?+9? ?x,%?NF?r(Z?/Tw?3}wt?,̝D`?rhy?ݒ-q<̬?r? ,n`ӄ? ϐ ?*7;?4%?҅x\?.Ŭ?C+?@`?]E^?jx$ ?{>?KD`?M ??9 Gh?N <T?5YW?РV ?\NH?۹2g2?y~?Jy?K=??g%w?G݆?ՎU?y#w?6Q?vvwCS?!Ҡ9^?fP?z$a?ͭ)<5?}?g`?5 7[?}5 N?:he?'?(?Dix*?IH7L?8h^?׍-B??'teT?ˈw5?9G(?\x ?wOF?i?W?LGN?t?8W?P ®?T˿(`?]BTx?X\q?:JĐ?սBŴ??bP?אΥ?HÀ?Ү.z?l #?};`?`?Z(|?O}?Of0??z?/?s#Ò?U^@?*x&V?"??Ê9R^x?qB?sc~@?A?߽ !?K<@?ҫaP?5vZ?> P׆? wM?B6T?ݮ X?{k?I ?bw5?]e-?3kޚ ?n:p?C(?츮8 =?٬@?۟'S6?+n(?_;J?P?Mo?-0?A߾0?뢏g?ST h?[cp?Hf?L*˵?wk^?Ȧ:?T_?nk|o?J|P?6jE?㹗 O?נ?U!.?ёq&?_T?B[ `?Qh?@?-Sx?ײ.0?l? {N?GgB#? 2?|y=?~bG`? P%D?W<??5ޠV?9>dy?O?1!yl?\8?%:/F?ԸV? &?,%R?̆s?)ı?Ch?Ԑ,?g|d?ۊ{?'ꠌ?Xv'h?_,T?'?օ`?盋 ?vxA?`{t?Oxs?粶l?䴇J?㯶b?ʘl?mC?#v?0ϗ?4V#?㹅nK?>Nt$S?edd#?=ڟ϶?F[?4e`Z?jp?r?DY?R#O?7JU?BY]?8?h? CAm?ܡ? ?'jhB?H?뉺?٧um?!e@?g?L%?z @?kH@?aT?Ijr?'1?叝l?=?iM?d?x ;?}pys?B?&}X?/|n?vJ@Yr?<0?}?:A?^M$?=]?4vS?ܨ7\?sd֟?:YL? GP?R)?}Uje.?r4.?\/?͓?x]б]?1?>Q*3Oc?ݚV:?ҋQEd?\K?s?(J?4?&LAO?娫C]?تg ?,To?ظ0?5h?{in?ҙHn:?߾ :,?&)I?rB?Ỵ?vR]r?z % H?Xǣq?_>,?Pa?܃PP?vK^?q-?UjB?_@?Bŷ ?FΆ'?N;_w?zR?ViR?JX? ?2Ig $?ݷX ? =$?Jw?$F"?mg}?V?Y[?ΫBO9?o6?~MV˨?Fd?”V?ç<\?ӥF?ȇ| ?I":?.?K'YP?s7?2^?q<ݍa?yX?c?>? ?Slg:?0VOL?r?1?ṯ̌Co?;sۃr?U8?>U?ݚai?^S6?u7j?\Б?n>?LIQ[?2JH?&?̣?D?P?Պ]XO(?N&q?Gdg?sD?<-?Ю`?24 ?I?1tn?,V?rA5?Az?dfm ?Y7 ?E={?3 `?8?Eޫ?Ԇ,l(?ƪЕ?S?ҧ6? ?Bx?Ӏ'?-5N? >x?Q8?5@:?_w6?qju?݁\U?ґҐ+3?KCX?QWmr?Y:a?X?:~?߾&}P?]N ^?:E4(?V)^ ?rÉÀ??eNL?70?Ù`? ?P<?j*?֝;݋,?2ZN?펩d ?ͱ7{?H7PI? }}_G?5a&1?JN?|v@$?8__4u?Wr ?sT?ő9tp?f ?V?GC@p?얛G9?Ӳ? $ ?^ ?_O"?)i?' ?Ğ?t4?C`E? tIb?/)Hx?ĄXk ?ۘd?w휯i?ĉ?\j?yd;.?~| ?=)P?]o?Tm+?#tH}?ڹ)jFP?!pf?H5R@? eG*?5K?S?()?ԗ*0?צI"!?}mNl?9`?3`??z| ?x8g?h?DOe…?lMA?h往?A_t?g2?׫#i-R?שdu?5$q?H?םTv8?4"@?W!?L.;m"? Ŝ?k>]?{,?Ev?^lp'?ij0o?ͨISt?fO\5?fa?KWVe0?c?Tg^_?|_?i?oQT?Eʠ?SԵ???<[?V୆?9f?xNu46?/ qiF?t8Q`?E w?蝜V? QLG?~$?e\;O?)?PZ?Z?5&B>??cJe?ጇ? !H?䊴L?٪'??qD?)59!?#?!P"?͟@?D?%f[?^4 `?ѣu?ޮl?^l?y?r+?P^??zciY?7`"`??Ɣ43L?̘^?ϩЍ?i?h:?Wt/ lX??:Y?jQH.?r s?,??Q?W ?[ D? xH?Ղ+b?્XM+?A.?R*A?hYmx?ĴGs?l.7?)bö?ҙ9*??4e?&ƍy?\m<?},Ro?Y~?P#?i?`f!?ցht?\?קw4*?ƯX?E?š6GN?Ò?o? ?$`0?FL?ѩi'I?A{ ?j96?y=?|w"?2/Iw?ؔNj?靦w?,M~C?lძ ?%Zwng?U S('8?:w??5.0`?v>H?8yڮ?Z:*?l &?T?0LlGO?ן??☞ֶ?Dn0?Y?B3azt?FB5f?Xcͽ?.`_|?ڥ?ƴKd?$=.i?(?/O?(?[E?0*Ap?m W?? 68?nh ]?ԔO;?Kޒ?n0e0ר?tX2?L?룛s? 8Ys?;*"?n+?4/6?^օ?6z-?uTh ?wZ?˥?}AX?/F?옼W{4?Y7%?ӞUʾ?en? `??k[#n?y~?۽&}?-(?ɵ_?Q#?^XC?ꍮ@,?̵$Kn?n$?X, ?6?ӓ? g?2_Vr?jnKu8?A(ؐ?݂*bbr?w?/?Ϯ0?wN[?gt@?kpf"?|E?~(?n2z?ط?ޠص?!B8?4 p?A<+?wZ?ff?ͯIo`?ߜh?T~ ?vFg?B?Hv_?$?^fw?BuPl?Ӣ?\FQ? :K@??R|u0e?'N??Am?~(j?6w?& >q?ݕ|8?e;42?rUi?ar<Ί?Nx6V?媝e?٪|?9(m?ɱ??Q?@?'?{ßk?Ah?^j5?b?3d?z"?U}???l`?ϞenN8?;%?\N76?r"??ٖ ?R`?qݪ?HEG](?+n}?~S\?ӟqw?MQMv?׹?D?WY?^| 4?#ғ?-8π?'߉\?hv?F$Ժ?4ijhP?a`?ٶ3".?&d?!]p?#$2?ɒd{? 7w?Ҍ??t8Ӝ?A5?ϩd'o? j(?1l??&;Jxy?9\?#N(?;V?P{?3GJL?zv+ ?ВX?onaH? %90?wBU?*R?Qv?0 ?aJ?*DD?y-c?ٍMX ?=ޒvuD?G ? 6d?TeW?g?r? 'Wm?TK?ϫV??W^?кؑz?r?(Q1?Ϝ3H?ͭ?'?6Twb?6r?OGo?|?nw?&?d8̇f0?Wl\?ŷs?͡@w ?9K5xN?j /?{p?l3?`q?E u?S.C?X̚?I'RP?,?ti?ڳ 첔?`W&?Ɲ8?{ӊґ?,???Q(t?r?ꠦSε?EG p?[(^??3CYq?Ϗ ?P???eN\?|?_zD?o?LoLc?C3*? =f*?֞S]?Ts )?@?u%D?+ ?Đ,?E8!?dTl?i4L?yGʂz?';N?٦o&=?C'7?nw,?U5?-v@?Z̩?H?䝠-?ݙ~~}?h19? {N?!M,?KGo^x?ީӿV?"1?j ?k+Q?Ѽ ?XgJ?ìsxP?!j?R$Ae?DJT? EQ??k?L_?b?Вh?0j5?Իdr^.?m4? ? u%?H(?H9(?l?>?,Y?k?#"׳p?pt?o2OJ?B۱?"{?(7?-z?v T?c'G]{?`ޖ? Ji?r/?H%_.?,k{?)?AC5?1V@y?HAN?a3?ke]?U`?̵X@?뚼Y?h9h9?e4~? {y?/-?' ?P?JTo+_?l>?\@]?b\`?fH?i㵝?@?0[?ez?.,?6`?j X?w?Ӝҭ?q1? ~?6L?-:?SN'?ny6 ?{4 ? u?-2S`??T?O,?!2l?&ˍ?9aT?ŋ*?8#@?z|_}?9?5;Rk?"ǪX?h@ޛ?-)h?Qq[4?r_p?%$np?ņuT0??%?򠳌?U? IQ?Bԁ?Ơo|?&@?$ ? ?D?v3](?~Ԯk?Ѳb؃?~ؾ?$s?[?uC23?o|P(?ӏmJY?ًh69?ʇd?|wg?̇MZ{?RGk?`޼TB?h2Ott?ݼc-|?e'Q?a0G? ?cuqK?-x,?~l?]9s?hh{?#Tx?ž\?^L͵?Wm?g~d?~?[A?ꘫ¸?sRQ?噥l?û( 6? ?L?JS?ۼ?Α?\?7kQ?%"z@<ˁB?,-9?넄Sn???ˆV?| \?n*T?[أw?) lӌ?pw? ?2?qϡ@\?σ.T?:{?|.b?R?\os?sf/? {?p??`oa'??G/[?Yh?0#E?n`?麿?~#X?BE0?t?k~?Zs?ݽa?Iu?0 N?iF?kat?܏KF?ԍ?X?ؑ^?!_w?׸?|i?66i?Fz?T4B ?(?gX?ԂL?ѭy/)?cc5?0ƶ?_d?~h?6{? E?扶tّ?6H?)aM?V93?8s?-ּ?m)R?ZmB?腘Q?ұ!?k}?rUB1?s=*^?bl?ѝX~?Ԫ6'5?2u: H?9h?n;?f;?Xq?]4eJ?C[+R?yaA?m*sql?y u?^3€?2`?{u?`Ύ?Ӭ#vt? a?kT-?}?19?ވP??y6KH?=4?B?`Ǒ?h%?ܤ]j?ZU?= ?}L?$??Ɠ+g?ܱ?Q-?y?P g?g'&1?wȐ?N4Q?>\?ުS4"?_r˚?"$])?ތ4R??H?ʧӧ(? (~?\Dh?kE"?`o ?Q6o?hll|Q?ֽŽZ?`n?ug#?@?N(R? R?$P? =??ۉ"?)ؾ?;ԭj?E6ɫW?#DgFc?:sd{?=2?\ m?шQT?ۦ ?d:6?Pi?X6=?(XGz?V"?"z?v!X 2?Ikډ?-١+?ބjj?؋{Lp?p?U?׊"?ό?s%?,2F`?%?珝?5 ?퍽?u?nF&?wDwW"?Y'Q6?y8&?Ϲ$e?{x?78k?,?)Se?ffw ?0I{V?+9K?W\?4``?Dh?vh?d7ٜ?y7?PNd?ߐ?M!N???שT?;S&?\#?%Q?Իtht?P8?߅@?gA ?ë==(?n )?Tl?r x+?I/D?}?FQRZ?R?dYe ?Hx~?E$Mt?V ?c̬?Ens?X=)?? q\?>R?j$,$,?C3"?s{ ?={eE?S???ܝj?י?vɰ?X +wR?+NV? LDF? ?gٰ?_#? i?2y?Qʥ?㗢?W .??_W?ݪ?2|Ӱ? ?*P?"Uް?s?ހ9? ?D]?ˠƄ?7K0b?xʩn?=ˠ?[i?7 9?dl?ي(%?E?딉?*k?ۣU?#Ái? }?cO)?5;\%?{=?ʎ ?Xt@?玂hSX?Wy?e~+C`?aE?҉Z?В@Jp?jgT?޴X ?:p?BAp?lCW&oR?ﻁ=\?މ?]Y@?ڛù?S9Z.?_v.?!tzf? 6,?L?u#$?l%?EKE_?୺}Mк?Fp?* \?_?GSmA?צgI?+aߋW?U`?О?&"x?犷;?jG?ܫeb?z?ڻ7id?[J?룫)?~$?G?|HFM?&Ե8?3K? G?pʯQ?5"(??ۘh???RAH?GX]|?5m??dٶ?sZR?~VEs?ƍ8́?W䮹?}Fv?ݗ=??AV3>?5[P?z Bz?~2E?Jʟ ?ϽsL,??W4?Z솆$? g?c\?v1g?gJ$?aA.@/D?3ll??H芾?X=?λ d.?㪠kV]?̵7ɬ??|i?ߦ>Ȝ?7pyu?uf?]b.?]ž?ԛ)o~?FI @?@~2z?Bf?d+x#?3=>0?-M?e%?mxx ?'ԡ=-d?Z|N?9f8?1K&F@?۴32?D?ˣ=??m?r?¿?ߑ@?pa?N\?mLǨ?\ϩ?뺱?OZ(?Y$@ـ?ᔑ?ި8I(?럡>ă?KL?I\?Ψ3)?҅D8?J?(iɯ9x?>?nt@?\?j?VOk?zgpјB?h&U?-h?ա9P? {}?@?*~S?7~?ڿV@&?Y![g?M6X"?,j?v?~?Ь0?ҳ%+p?We?IM?ᐁe?qv"?W_N??xO?d`n?-݌? dy?6(o ?㽨?gp?π?d?mU"l ?6?vP #?Q?D?B: ]x?xb?;h?I+?^_#p?g9t?5ī?|-?+k?㺫/0?،D?zɇ]?"?|?NiB\?`ۗl?ň6K?Z@sD?mi?5oWH?罃g?oQ ?h{$?qE??+dF?yU?&foJ|?i& J? \]?bWV-?㽍?QT3?$?ڕ[?rh?ܝz?Ƈ̡?<\f:?Q??ñ]S0?`?ۂg)?YJj?qK+?n3?N?|t.6?#t?ֹ"=?GX`?I?ʅFp?Tݨ?*_)?ZgE?WXIK?(9{Q?ׅ ?|QT? ?O?S=`"5L?ΫQ?/~ ?Wm`?{H?[٭MPl?]ϓ?ԋ?(?l>R?RQI?*`8?F?auҸ?6:?o-vo?x?3T!?eYt[?J[d?#?lz3?5/?.- ?JH?K I8r+?֦]prX?i?:ͧRM?k V*?f*?y^*?V do? Ӕ?.!;?Kt5`?HX-?m興F?C<?=?6C?Y:X3?X ?m ?Г]Gf?a8t?~?W7?绠-?~ ?A V?3P?}_?+J=N@?в€v~2? XrC?׊Yڌ?n'O?5B?՟f?;?n:?׸|V0?é!?ܳ;?)GSp?%Z ?s}O?1dŠ?#;?p+?(Cz?Dq$?l+(ƣ??g@(?=kc? Siڐ?b ?G}??R?1X4?Ĭ=?KZ|ar?z ?.v.?qm-?09$?&k?B/†I?l?^zޙ?C?| ^l?l7?W ?qаv*?`B? n?kx?;_S?vX?~%ǫP?v/?;pk0?_L?'q?˜?:[?:K?~?彚[?{/ZR'?v?Ƨ!?Eei?#kI?G&?&ͬ ?ǟ8?AakJ?4P?S-?xk?滨 i!?R?:X?쁨r??t&?J?3Qc?kP6.?fM?5N ?]#K?̑n?x6?F4az?{? E?xHp?Vݯ1C?4W6? Wڸ6? `p~?:Xlp?Ӱb?l,on?;CR?[ v?$&C?@F?S!?DCL?/o?Vu1?޵5N?fto?Nf?=/v?{ð?oz”?5n?'C+?űCr?ŊRb??H"<?V ?VO{I_?Ύ?۬k?xGӺM=?t?"PX<:v?'0{?ayVm?D?OU+N?۽?lp#?! ?Z5?XH6?r#?Ѯ-sT?>_?kQE?3?:?Aj_?I\8 &?첷}Р?5F?tQVZ?$@ԯ?^2:?>p?B(?Ըݮ?t5?5?۞b?`0O?dv0:?釀r?pS?X3?W ?LD?B4?i͏?x9U߀?y2P?9V%7?]ۘu?1ʒ^??z} ?) 7C@?Ҷ-M?ߛ{ ?`CΡ?달~?CW?MPB?Pɒ?s%&?r?lvV?קJ?=Sz? 鶱?i "?\Tֈ?/x?֍?JYqQ?7t?ƪk?b@?)?b~?Α?ʷ[s?evf?؇e|&?@5?$ԃ?ؠFGh?RD?vo|?֊K(?mbt?luh?C~Tk?馡 N?7oJ:?zqd??Ay4?MM_?H4ʘD?bh3?ULY?u?Z :$?/?@ꋿ?hAl?˧?q߻8m?䖛QXg?J͡?ivXE?}?m"@?rq0L?%J"?!}b?Hg?I1vh?G@?mVu?鐖|v@?P6,?ixtk?9?/q 1?Z?H x?3y"?Ʒ٠?7k?ER B??3_#??Ԃ{?ԋ}?r?c`"Y?Bܗd?h}pG?¨ P?I Z?͠33P?KN)+?vt?'p?aw?USq? |H?}^;h?HK̆?ƦY?h(?~ ? {?pR?= 2'?fEX?9k[t?ؒ4?}!k{?:4?;$C?gx::?#|O?zC{?%>?J,)?Ɖ6l?Di?U6SJI?=ؤiz?/a#,?\x?A!r\? +?e;?Ңyl?+c?W?Ntu??]a]S?+8?^bWҬ?gCQ?ش̥=?~C?\P8?/W ?1au?"5}z?YOl? X?*?׃ ?Ę+l?08?PH?7P?܃=?ZG>? ?5@?}Ծn?b" ?ϻ"_? b?P3?A\"F?ȏ?t|?m֖?k2E?ⱐg0?j^?rY?ΐ?dh?}g? (?Sr̖r?t?=^ù7? ;?JS>_?N)L?D??ҽ`?!5BZ*?6dI%T?Q?ė2?x7Uh?R+??Ccm?n\?$?bŌ?wHc?ID?(u?QSR? ?;/??rB:?zC'?߫i?h5?k֘?+Q?ƭXR1?mr.v?؜]v[?8" ?L;m?{+? @?/I)P? O>?n?v#?ON'?&q?h?־>?oCP?(?IwT.d?^t[?=Թu?֍Ԓ?0?#??ҏ?(VG?Uzw~?0ly? ޯ?*4?fI`?u(?.j1@?lWf?Eڠ+?|i ?bN?24yb?*?cHH$?=u? D?ȪM3\?k?+ ?؆Ii@X?@O? یق?&m?p_}*?+^?Lv.?m.[ ?޻M~v,?Ĩed?Jut?榛4v?@ӿw?寒f"?݂X%?挟M?Ԃ(ŷ?oz*?)H?\}>?bK(?Xfަ?w7ǫ@?Ʉ`?1th?dw?Щ_?:4?j~?ndi?dy! ?6eB?hS_?i ?*`ܬ8?kP9t?5X}|?=? K?fd?NqNj?j_%?ʍ=Q?u ?득Z?S?#?J4[?`2B?KXQI?ƻ[? }?HY͕x?žtW?_l>?i٬?];?nXu_?Q)Y?O?❆!Q?dg?Ϙ.ڝ?㵬r4?>?ʌv?徨֚?)(H??g ?* ?ƫ= ?e;$ ?02fh?"ڶ?H72?Eg/٭? K>?'(?0P?-}a?d7#?Iv?=x?l^dt?k?5z4 ?bwe\?D*#?R(o?@?J0?TI?=̏m?~zR?yP=?=H8|?;&7E?=H ?"?c۽<:?'k>?z, V?߱S ?2~c?H>?RϼN(?2O}? ]B?tTI?0۟?A<@?rb?xSeNP?8=?>|kà?fܚ{?YK?Q$h{?iF+R?8r?u,ψK?2?axҋ? G@?@~?\S?VKX*?.?.Ux?~'.?~%st?ŭE@?,x{?4-?WP?՚yB&x?H?jk?s;? Z})?ې4o?iN?, ?Y(H?8Q٠?N4?ze?ݙ"e?Ҽz @?%hj҃?&Bt;?o%?i]"?>er?a$Z?Ljmj??0?.h,O?RS:?5 %d`?[P?|j?a?Ԡ!k2?q6@?g{3|?~$&t?ӏ˂?:?apf?}dW^?@h?>C??[)??8z?r#c "?Ơ%?eWE??DeJ?GP?sEy̛?=-bs?] Ɨ@?׈v ?ϺWh?]q_H? B ?^o?8K?ޖ}]?b P?ŌxI?߭衴?ok?@DTP?Idn2?͵I?p6K?;h%?8d?߰]B?߯+j0?KO5?Rp?5N?Ԇ3L?/_E?| J?;=^?}RgW?0O?jDM?O' o?TG8?X)/??B^,?Pw?U?Ak꽀?샨 ?4?rWL?$=)0?mv?6_C?ڸθ*ڈ?0ݤ?7[Y$?⹨X?Y@?c-?sl3?ƄM3?  4?⛬A?ᜂAN?d,6?ȬoH? TekL? ]zb?Q?< 8? l,?? ]ϱ?R~5n?X΋)??#!CpR?:?ٯJ?24H@?q?LFi'N?O9?|['?!?=^?zY?E?%'?GQ2{?/P?٤?|?|]8?B:Z}?{ @?㷠??\ ? ?$N.?`?勍?pd?K谚?ܔx?x>Z?Tq}p?7f?4#cK?):2?g(?/P?AzAf?2?@x?֛:`?]Av?={Z?;:t?Ob~?Ӄ|Kd(?ԴK2W?wU?BtS j?Ŋ?ZX?޿!N,?Ж !R?sʱih?ΐâ?vrPD?- #4?2zV?훫>]?ϧ$BV4?5؎^?'J1F?fΓ:?&j ¯?H>l?E?TWS?سR4?ʌrz?B!|?KH[?Q˵?GٙT?f{?@m4i?Pc?x9X?cJ?nIH?z?#Rq?ޒ!w?IKߧ? h?F-?;9 M?F]H1t?h;?P*??eFX?/L?I- ?$1?#T@?aYX?t@b?!Ѣ?}]*$'h??n!? D@?l?ϱX?ZP *?lHPA?n y?Ra?p8f?f#.?Բ-?PTgz?r?hB l?;|? U?&D?Ӱ9L?&͐? Ț6l?}t=?“EK|? 7|I?͵@m?<}θ?hR|?]ʮ2?LT?3@?Li{`?BZD?"pyc?+?R;]?v+W[K?rp?XE[X?Ӻ&պ?0~>?к:?g?pȵ?An?v'9x?́_l?g? ? 43p?Ӌxf?5\Ԅ?31]?ŜX?5_?8М?D#U??w\.I?|>f?k?0?\K(?f|?̒3?7G?#a??=Z}?TeF?/(}P?ЙQ(H?(?o+?S6?#4o?)ח?3Q?FY 5?堰&F?he?| 6C?ٟ>n?y?̕&p`?VE?m+`M?\O?.B_A?w?} ]?様?5(ЋS??е?anb?89?e8? b?ֽ8c ?L?4p?$?4R ?C3?ڦ?MӦ>?𬣯j?ׇe$?d(? !W? X?Hѩ?ap ?^KC?¨?D?? W?0^+ ?_WvH:?" ?{2d?اTò?""K?`6?|8O{? =ˊ?С?ڢsk:?| qR?P? 2?Dm?ڲrjJ?)?ܱ}?薡BM?&q?׾ -?ABP?9PI?Εb܀?״$?݃?ǾmӲ ? BT?ʔns<&T?_L?@Q?Vr̐?A^?$?:7?ԗayb?G lJ?ع`0? [?׽0?劏sV?[[?[4>/}?ȚdI ?0r&?Vy ?6}?"Im?,?/T?X O?GȄg?ҲG@?G?ݛkN?h%5i{?i'V?Ə@fK~?"ea?O`?Z>?y*?#x ?DiȤ?f7B?~ ?cuc?HxkW?LAB?X37?UIe`?ǃ3?̩r)׈?I??z>Z?^;z?|k^?ߴ$yN?b3(?74yW ??|?6y?$?ӛ)Mz?r??ѐ?iV?[h?) ?FPh?؛]?|ZH?/kH?KNrA?b3Q ?q`?`Ӑ%?Aw?@A?H?Z@?ox~?(]niX?̀H!?;$1?(q?# ?ֳ?K?LNv?Ƭ?+YChy?G٠?RБ ?A9`?`?]ӧ?Z8{`?!?~0?@?7$Z?4\_0?I]T?ZT? b@T>Y?Ʊ3c>?r;?TU?F=?-?F&?$<+?R51?V}?k\?T V?te*?EV?i\?oq??;8?9ZU {?.l4E?zX?Fh ?푏?S . b?ܥi.?L:??Vl|?D8?D-?/a?e`X?d[ ?Ç?&n*?Yl[h?@Q?Hϟb??M?鮝 ?Fr tB? @vO4?`0?"}/V_? =.y ? ?WѠ?˾ֈT?4 ?Wjɾ?U ?f,^LD?4"?ԭ(R?|K%?p[PVd?"Ɔ?\+ ?@:?x?ぅ-#?4b&:/?&sȾv?#?,5r?WL"?9$(??-?㖣?-"b?"?ք?ऀ?f / ?Ҿ?!?0?۰?H| P?O*~?݅uX??7?E?n,ep?>u?Ĉ?1$Ą?otH?ֹ{'9?(>uw,?C)ZI?5bS_&?!F???ث6VD?kax?9)yĞ?APp?r?KǠH?#L?IhxN?F:Z?D ?Ϻ <8?o ?N2 s^?Y^?5z^?_*?Ї6?z94?ôf^6W?{5$?Rv4?¹3?ۖ Ȗl?X=J?i 0?k&?ЯA?Dq@?2?N?uH ?ІgO4?_D?ҺC.?Wz6?ָeg? R??-W?ʌk1d?u/?l]>?bx?H?Җxv?ц+}Ȯ?KL?dp7t?Y.?IV?G[KV?ЯBT@?@!?N;}4)? 2?f!*?꣘Y?Iq&?h /6??7Jx^?ʂ򆋞t?nY?Ѐ۬c? (l[B?0?G$c|a?x?VN?I?m<2 ?ps-?4b,??!~/`?;NV? ?Q(b+- ?~͒ ?{?Qi?H%?8F?-?8!?훘hk!???[?KW?հ%Cp?I!Z?½ ?ٷui?*/J?Uk?J@,?,e?? 7v?c?dCp??9Ҝ??I?[d5?693,]?ܺڬ[?ԵJB`?t̠?!4D3?dbeŎ?FWi?[{%?Ζ8?oL?.&*?Ք4 ? [a{?QY?jD8]҃p?ra?@1?K?̎70?Nj?J?&+?P?ɏ:6L?o/(?X6?T%V?9}՘'?f=d?2,?ԀnBTp?σY?ޠF/*??|gԭt?=jȵ?獢2jg{?ߔB?z3 R?ނfSn?=~w[?u / ?.-M?ױ&Hd?ʷ8~?|?ػ33N?`D?ȿ-/??e?PNA\?֬h"Cv??F?NucF?(O?hEw?G]?_ x?Гop?3P?굎]?[P?㠸?ӧ@?Б\RF*?zR?妣bً? ?q_p?^רZ?'/ML?)~ ?>ט?B0?B)?*?~2?WNI?mJA|W?>}p?5Y>"o?Ղ%~S?;Ω?XoE@?!4?нHN?Ň]?إ*?ܰ1^?a_T?DE?{姍?\b`?lx?L?-@?ĸ?eIh?ԥ1?]O?VHom?p6?쐭V8? d?{( ?ĉl?őm??ܒO?./LJ?4`?醠Cn?Gw?!! y?jR??'>?5z_?R?[—? ?z'?9C?в?޾DwF?-$X{?eMy*?Qx?H/??? %Dϩ?V?+@?_~ ?x ?Nwy+?n>E@?.ƅQ?ӌ!Ƭkx?Lȏ?M?0Q~?Hm?%$b? ql?ұe+6?ƈ?R[2n?J%?q ?S̞"?fX?>[\W?$̜8?틪/%?8O2J?4>p?`P9͉-?1=?^ubkn??ua@?5Qe?'?PxV?zi;?Mѕu?8Pޤ?tR?'{?ZZ?*?77? L?^VP?¼8?UZل?G ?q"?ES?GMu?So?WԠ/G?ԩ1o8?S\?rMH? Uvd?Ǐ3? yh? Mr?i{ǰ?,txO?.P?qn`?aI?;?(5?҉m?'?z)x߸?):?S?u2)?E_-?Tw?f??(n?ݚk?Լ(?\ ?L? ?dÅe?^ ?p Z?ٕy>?50?ٍIb?H??h_xX?v?t?~?ȼPP?C,?Բ|$J?-??%}C?٠?<9ȭ?ܥ ?46䛪?w@?ҁ_$?9WP?u MZ?'Tw,?ۿEqR?7Zq%=?V+3?$5? ?FN<\?r?Xõ0?ͫ1>6?߇١>x?.鋗z?狧w3a?䇹?Y:d?z`P?դz_|?p_?s?pض?&q? =8?'j ?;h?־RE?} ?wm?٩R?Ò9Ŕ?!@[ݢL?$7L?"m"?㯚W1?IH?_߽ ?D `W4??eX?dZ??ˑfLl?ɂ\?ҕZ?SFv? $DQ?,F0?ҙrBq?իA{F?Wl?喟C?a\u8?tЦ}?㍫..?om(;?sV ?lB?Za]c?~C?懊?<1^?t/?ܓ[U?࣡W?>pS=z?pݰ?aT?P?>?YI@?+}?S?Кn8#?tv?K義?R??ayڭ?Cx?k{?a?ݦ?H"?ɸ`?Ȼt?cS~?稳*di?Ya>?jg'? 1F?ܖ!?2ӈ*?Pæ?v? a悖?w9F4?Hz)?BJ*[P?7+C?3y}p?WȷJ$?#5G?qP([?Ϸ2?pa?ÂO\d?WSd?)QsP?F8,XR?ȩ?UEa?z?OU \?]Hb@8?jl?ߚ3 ?7?9Ű?<{Y8?',FP?R?b򓁵?t?J0?줻??%NG?&?r8' ?=D2?b?ꮞ?ŝJ^ 8?=4eC?=G h?Ma?dsK?wD ,?@=?&=%?ػ@|??pFϳh?ԙ N?3ج?hY?y ;d??,;m?߾1?O.ad?Z?/n?ۉXK?Ik@?ۼ?˟_?8<@?腹T0h?M?%I ?"?u?>?ApL?X?!~?ِq?"F?.?G6?l.&?ISH?J>^?ȷ??Mɓ?Q?%:?LR|?[: V?Ih?ΝU'? " ?|t ?ܺV~?h=V?_U ?j h:?_VwD?J_?ϸIC?P?+"?]A?l/LT?~-?D ?챣[Ⱥ?!h?j ?y ͧ?0^c0?>?B?h@?_Qs?ƍC+?] ?hq?j/y?&-uv? I??M?d?-4?=v/?4'K?j?^X?G:? =?Vi|? CC?ʱHz?]?A~?uT>?^"/,??µ33?%eo?˱,v?*`z?ݹUh?@x?Ǣ?3x{o?,E?垯 ?~P_)?0$ˊ?lN{k?9?VH&?٨B?_"o?5c?> ?W\t?:lE?G?j6?l_?ɗJq ?Xzl1@?N1D?/-!P?'0?=I疈?jm?TT]?F? Oi?}?<bcd?W?nl?E$,?V7?tv@?ON?ȥG?i;? (~<9?'? c?VVK?^'6?گjd"?ڕ#!?$|?rx?QX.8>? ?X?$+V?ͧ4i=?MdM4?": ?a b?@Ӟ?ר5?n?[#R}?cW?фp0?Ƹ?l?!Ё?&U?h?E*ֳ?}ET?zAb?]ίA?)]Y$?Fr,;?Mɔ.?#?3h??Ej{m?xz5]?{a?բ/j~?HAi˦?kw?~r:?q`?}? ȓ?֖j˚?o8P?}pT?̫0?ġ\?ωRĢ?LIb@?ج z?ПP9C?U>m?IiJ^p?p?Nbt ?~(?낰+!?u2z#? DB?HEn?~$I{??pOP? "?_z? 顼 ?;sf ?:zX?~?hF ? 3?XV?Ĥ]S?&?ń?&3\?p*?>?8?j$D.? hn?vףj?_bێ? 8?EҨ?KcN?Y#?/L?iE>?,VY?Q0?W&?"@?qwڂ?87܎?^LS=?Ѣ?GGg?֑Й?딚7? $^J?!u? ^0?쬲)NZ?kr&?6A ?۫H=?,u?ץ͉Kl?d1 ?4wj(?uC6?1xj48?\5?Xh?Cr?J7f?<Й?ξ.u삨?EhR ?9Xd??0?Pot@?PZ?%?hf?v6++?.c?H?ooB?\ `?^T H?۝ ?8?ף?>f?dP???rxZ#?MR??41z?е ?g}OKJ?ўls|?Z''0?5a?ђ>?YU~_?3UzP?Ӗ@ʆM?ڂ5q ?ד%{|? !Y?r? ?*#T?ðüo ?(b?ҏ ?Dz'h?)6$?Qۇ?? 9?D;?cO?Bα?lm)?Bf@?b ?{n?ҖC){?@?dlW?(?j?K/?ՏS*p?-_ʳL?T'?Na?#Z?k?"^?|+e??@:Y?B3f?oR'M?hgq?۰Jx?q?NGYE@?O!V0?]? M.?+a%?]E?ȇ,ʌv?(?h?ͯT? F̕?9C0?(5?F ~?F&.?\l?VK?ؑ?BhY"?Ӧ̥4?Ə?an?rip? ZǠ?X[h?5?DZ?9,?I00?,d-?L?/LDa?ci!?rP?87,?F| ?T'h?x"?["^?ep?6N?3LP?N X?SpB?# E ?j٠v?lB?>KY?G肿w?i?_$?L?ڥE?Xt? \?t5 ?/fr?OhMu$K?t ?SB@?Kt?( ?琈og?́u ?ɐ،?yO?Nl4?-Z???ⱣU*?%2|??곣ɌAY?髸9?oI?.E?}u0?HZ[H?_LW`?b^?殆&?xa?JU7)?̇!?Д 3T>?y$?{4?摋d?G{h?r."Q$?w{r'?!?2^1?z6@?. ?s}]?ڊG"?8?ѵy?6Nj?Ѯ/ ?L#?{{+v?/ NL?Z?j??6zEN??ꕽ𣷢?ţG?Ͼ?Ŭ/?H?3aBh?Nz|?O?%?_EΠ&?h.v? ).^?tP֖?VT?J5h?U?m?CG??+Zi?x|?:?ڧ=?$o=?z??ે'8?&@& ?E4kݦ?SG(O?̆o*0?`'J5?inm?wΨ/-?L ? 8I]?Ý+$?c?kp?oږT??6JU?MLd?ֹw4U?ং!?m+hv?Cvy.?V8g]?'Ҡ?Ki?sl6?搩kB6|?lD?̕_t?G[?.z8R?ڂP?*u??ۿ#?ܮM+U?4?m?b?G8?)9pP??P?Şd?C%?D6?Ľ{m?A|J?¯g??l?j^&$?ې,?܄(t?'!.?~to$?h?= 5ž1?y/B?r?jZ.C?FT`?9ə ?kd?> ?2O?ރՔ4?)?h`&?!E.?F>?W:N?(?Μ%?U?h?k?"?#6ϝA?og8?JH ?ε9?oib?t.??'y ?p?ûS?1(?I Xe?qj<?]:H?鍍LEƯ?7|?c{?Ėau?ܗkd?7y?(3m?`2@D?Mxؚ(?BȺa?47WU?]~>?!v?v?&T?yc?9l?]a(?0<?ѿW?nlV?O B?qW?9K[?^- ?Qӗ ?ήI ?f 4T? u:?={o?_d\_j? 75l??at?rVI(?*qT?wu?1]R?X?HBj6P?¹ 7x?ퟑD?~mp;?s? xi^??o?wCgN??SPB ?)Z?"~5?vUZ?^`?M31?ĭљ?pC:N?\@?Ok"?RHq?yz?AZk?~44?GZb?ߐ"mI?ua!^?Ҝx?)?ED6?9 *?t?jRpH?/چ?^?_hY?[,?%?Q8?bHA?h{?2SM?wZ0Z?驖?ȘD@?eP??r?T0?{QeL?֍?S:?o ?3e?Tƌp?'yL6?<֔?gS?82?Xͤ?6u?6s?$?r{@?ⴱ<?=5FM??)S?(5ЍV?.:$ ?rM5??XX@d??ٔ<`?+tm\?Fk?z<0?*"?_xI?$)^.?m9Cp0?LWCĦ?(R?+yw?䐤 _?T1?ϰ?Cy4?_hS?t!z?.?рgW?\WړL?6?[> ?2 ?>?7Pxk<?ג\h?X.f?ћ}>?[6?Vg?߻a$?c=Kr?\̡)X?9?/wL?V\LX?錒, =?? uA?4?`=.?Ő+?O^?X??f2#?,R#[?u_Y?ض{?ɼ=g?㞸wG+?׶fp?tHõ?̹֘?2?{7S%?f1>m? [x{?%orN?嗟K?EK,#?W?Ơ ?13?֢@~/^?r8?m3?"Ō7?Jh ?w)$$^?J\?\-\?ukF"?Ӎ%*?I/p?ɫ+B?s`$]?즎?;J?.Ac?/{9x?ʽ5* ?M>?U 7?x "g'?@T?l+@?T!]N?kz3?Ԃ'#!?zط?]-T?Ԧ[?LV?$ [?p?ŗM?ƙ5?Ӷwt?pl~!?֗y?[fb? ?@C?'?;H?!1?W8ܵ?ᝣ+T?ݳ°?'D??ӥ9 P?w_?ܼa?_$b?L":g?Ιx'?Nn?1g))"?Da-?Eӿ?*]2?~r ?猠>&?1ĸ?M\?LoP?3卬B?̸~?v`LMK?lw&?:{?Gܟ? 4N?6{Z?p;?+>^7?ߞ΄H???۠p?U W?6޿?ƶ? x?fV)?G?ڶ.?HӶ?n?r]a ?H?o~?%? C@? !N?xׁ0?k??\%^fR?%r?ps|L?sBf?Ix?ՓS3`?̇g?L? ~?Wp?ܼЇي?0?9!?ߤpz74?@XV`?c]}qjr? Nq?>?c%=?{rW?: qH?[o?.:~?(?ut ? 5[??5w-?aa?Y9'B?P? m5H?ٰw)?1~B?##j\? { t?…tT??_&?إ7Ӟ?!G;?㶀+?$&?/#߻P? ?ѯ&Ҋ?cc[?K ?\?Q?x ds?_B"? }?v|??S?8G?#A6d?E?c ŕ?'^ a? C?ʢLa?yZ&?GfG?Ř^-?B Y?*]ux)?UA8??BՂ?#?i8?T1+l?ڀs?i5?n?㔳}/N?-%!d?^]{k?y\?Va+?8?Ѵ?]"+xOM?cQ?z7?/<`?[.d?Qa?ד5t?FIDH?8- ??Yl6~?m ??1?E?ɐKƜ?ب7s$? ?P?4qh?V*?Bi?jb?pʛ?F?mR?z9?lꂠ?pEf?\3?Ԥ?'A?cUn?J~?Ò?%D?zgsw?7>?mv?;?b?:{?KQKh?D⑰?|pM?rLx'?xv0?lf?~S18?m ?:k8?6\>?3]x?f,Ƚ?3e8?Gư?0Y|? ?z?3?J5?2"?mEc?`0Ǻ7??|׬?꛼?P0?AV?߬tq? Z>?i@?w0O$?ܣb=?{LL@J}?rfLä?W?t?n,[?>?ڐ,?E??29B?9Mi3t?Ѭ{,?E?ZI?|M?d^q?Q?o 'Y?~u`L?5[M?>[?bd8?yA,g?qc~?\;W\?^?嬹p?Vf?U9Ah?-QHu?!?8_? V.?l(_?+X?ӌ,?Ʃg?-~\? X|?'Q'@? Ļ?-?Y?;MҒ'm?f8 ?-?қd޲?(I˛1?y`?B/a^?EN?ӄr?6n? ?]q ?h ?ʠ(c9?(,?))@?)+"6?QaA~?W?E ?͓\|?N^( ? w?(j?(G?UNK?޲Im?j?~jaq)? ?}7?D)r?QcN? MJ?Űſ?ۢ/gkr?B?H<{?YG%?V5?1@?2S`?:??ԊCL?%H5Oo?٦ ?mj?\?WmL?ь?(?Zҧ?)?oNQc?x?~˷?qr?+?#%dI?'U{?۷oo?fy=?=j?L?~t|?!?Hx@?]j?g??"bZ?n/? 4l?ʅ? ?ì4-?ڮrzi?r"&?93?|*?Z&_?[R?G {?}lj??bŸ?_;/K?:?X?셟F>?#?j?c%?;_B?tm~?.?הۙ?d2?*bJ?kױ?q̵J?b?6>?4`& ?M?5ޗ,?؃'j?m?Yl?]٧`?U/?JCN?_n?μOq?(*W?h ?9?[p.-?GÛ^?^?HC:?L?d(?ɟ3?~ڔ?s2?n4?\/`^3? Cs?BGQ?⌲ ?oaTR?򄎎?k3?폩?W??`T?Ηe?Sm (?X?;Ww]{?'$ `?ٲbA?G9cޅ?7kL?_N?gY옰?9}d?Sds?S?wk=?P:y4?}#y?p?"? er?G_Àb? ?&ԯ}$?4B+i#i?9Glv?j#?U:\?\?-z??.rk?)P?vo?+X{;8?h]yp?ؖ?,83?wGl?^ߣ?<%?6AY?zڭ|?iN?[0(t:?:*!?ͬ?J?#e_?#8?TӨ?@?]?["E? `?`n-?類ʸ?]+20?ٝF? ?JQcF.?,̲?̩t?Q?7/6h?l<Yf?殬??Ԧ C?V<@L?aU?j?}vm ?JbD?K?AD]4?+rX?1x?o$e2?y`H?Mޭ@?gܡ?RiH? SYZ?*T?.sx?. 7?!e4?t?}?_?&K?[|Tv ?)l~?ц^h?j?AXK",?=3C?2jyh?Ae)B?%EN?,h6?~>?Yz?"z?riw2?FFe%?FR?&l@?+}!n?ḫN?(9G?p*/l? IX?Q[?㦂k&?!-?ΟB}?٦ONΤ?֎E?l?겞?*ӹ?T!?-?q$&?^??woq?A.W?9>ɞ?jQ ? (U?l-N"?2]?P36?9@?L?zfc?ҙ?(:?hP5??פ1? ?j:Ԃ;?W7?רѣ?%۟8?ߦC?] ?$?@0h?7?AR'?ѥ=?`#?涣}7?* ?qݠ?)퟈?;Ex?z0?!J8?%o?P2u? Z:?Md?ɱ!'?wBݓG?,Ģ?вV?(?34?\gJ#?q:`?RC2w@l?Z%\0?۟emR?[@?U(z3? H`]??Ūx?ܻIB?=a$NU?jko ?#0X ?.f\?G?h s2?Yr|?8p?^E?c 9X?8͒?70?sB?Ӆ ru?M> ?[͋m?hc?H Q[?ex?rB$? 9ꀖ?Ö?^?I\?C~?zF?,x?QB ?G~"1\?Ͼ?n=?Q^?[}!>?p ?U%3/?bh^?kh? ?-]G߮?n.?^!??ڟ%3M?4kc?. .@?x? yG?61?^M?q!?_|?$ɬF/?}!eQ?z.?VSf?&oT@?#u6?l?_t?m0?+Qx ?w?q?~R?@?ѻ}?* cߘ?[@?޲/?T2+?C%?!y ?ز!@?J\j?r?ċuJ"d?Ï2h?Lmn?ir?X?r?$ki?JP?эLL~?ELR?p4@?ۼq?/P?t_#?: ?| o=?Nu0?G,t?^GkP?S?FZ0?ҫN?r?5f&?bj|9??G0 4J?IYiD?-cZ?y6)cS?!u/?eb-7?:qgu@?]qjL? V(?:SՊ?Fc+?Hel?wkQ?G*M@??TҰ?K| ?n5?|=?ۤpP:?6?~hO?=,Om?k:k?ͭ/D?ְ[e?(| ?ұgqj$?3Iy?Jq?E5^?eA?]x?ƌj?-0?ȴ?L?5?N2?Ix?fӚ?؃m-?墳~]?֯?ﰱz[?]ا?RC`?ٕ?~P?9!??̸?2K]\0?w)?щ-:?*a?ifh*?&%9Bs0?ȗ$?@?/(b?@?+?(rjf?¶~*?te,?PNPzl?- ?TJ[S?)id?"H?պE?w4?՝~W0?ܹ{?Û?.9 ? ?x \?pr?U[<|?-f?``"N?$p?ʎY0?"Kd?QPO?ۗ?O?R1?ی?P$??G'?@J?@ u?`'d?zl/?h;z???pVP?AQ?1?70"E?Ke.nx? 0?-dH?)?U?R?,?KN? ꭧ?ԿVNF&?ḭ?ڠmrQ?Ɋ?rX?uZ|/?:؎? ?((;LX?3 @?, ?Y9??77?L?0>?ɞGhS?ݵ4?hr1P?#ѽ?2f,( ?]E3:x?짶 v?*RĉuS?_,T6?DQ-?.n?l~{1?֤Ց?ke:?4bf?OQ?wP \?me?.RO?p)x?藕 .?ئ!+?Li7tN?ݍ?őQp??j~ZQ6?^IِD?)?rMrZ?ьGG ?K69?葤sK?ٛ5}?,A&?wb?*we"[?7 @&l?#?Y1?K?o?9 t?sG?:9?Ҝq.?K?eͼ?ۭ?r3X?؁TH?-3+?NAz??2?|b@?ּFh?ALO?I8#?ل6&?v0?Yቾ?]?bG?œ9z?${0?F֬Y?J y?]r?"43+?KQ?RٗL?,?q? ?օ?t}> ?M{?n \?UN?Ȓ$??pG@`?4a?r ?שK4?p?wdr?qr"?L鸨?S9fǢ? 7>?ÚQj?̻4"?SK?ۗ4?,3i?C04?CN?ДŒ^|Z?Pό?(?RH/6&E?,l?U-^j?RV'W?Lp?⨉q?ڭM?^?1?]?]wv?a)5p?ɦc?ٹSw? =?M'?9!8?qa@?i?ko??B?M4|?iS?֫Rh?EM_?W?T˱d?~=??T?u+?~Z?K8?a?d\?`?{?:-V:?ն,?oZ:q|?$׀\?wU8'?X7^-?L&\?1>ׁz?4$Z?貁z^?6B?"ƯB?✅c^?ҳ1,?ޕ?X"?dc(?W?OzT?.T_?hkH?_ I? SEd?pL?2L?,?ӹ|?+r?Zd?Ļ194?ۗSC'?Hl?BUo ? x? H?0?d?g ?,H|K?;i?ẇc|'?v8?Cɻ?獾nBZ?s?}~(_J?*A_`?{5m=?Q]F?^L#j?ڜKN?X:?k`x?,99"?0?X?趍gx?g?蘺?~/Qy?\?X"?@?Z!X?ɧ?K? ՟?D5|/?eׇZ?'C8?NJR?ڲj?3 ?Vn?ƺ?ZGó?OrY`?'t28? 6ҽJ?_? Avx?3qIJ?.!?`W?\?ޚjd?$Lr@?_t0K?ݸO?yO(?Ut?ݰ)'*?.???7?`?s?݅ ?cT%?o?l?Vm?ڷ dw?p?ӕwV?#,?kҺ?ɀMk?? >?[?m[?!@+@?偽27??BM8?Z:?tx(?\?˕_?~@?KPy4h?h?KN?)_?:%?B@?op?=Y*? 9r?a1~?wqz?^np?ɉ rt?kOӮ?;?ëqBCDP?Ԋ[V?ݱ8S?{_i? W?28?(0Oy?].DV6?h_?Ͻ,!?.|'?zb=?|rB?#RgcB?ⱓ ~?ʾo?º6? ]?>?ݱ.?߹@m`?"g?9?E?;?.L?C o?*!?+?~֘_?%u?B?Uź?Zq?fn?6^?f#D?n2~-?? ?†?]0u?ñ# ?SuL?[];?swN?3Й?R^T?5m.?v?Ƅ̚8?`?ߙq?i[G?Zy?ǩ?ȥ2;7?嫶}?G >?ܟ? ߮?Y֠?ѯp? ?yAT?U[(?zK.?^cZ? ?Y)2u?̚u??ڎ!4?C0?Y:?,?ݥ:&{f?7&?ު?ܿ|?X&*0?S`*-R?zw?zH?`Tp?T?3'?٬@^?Na?+y?F<,??D7z?nݞ?Ք7??ob'D?w?۫{`?l ?m\mu?AM4?xCMu? ?-i?t?ujWނ?ۦЎ?m?r)ې?4v?Bv8?XmS*?K=!S?Rx-?mN ?+CR??J+"?ᾫ? %4?Т'b"?0) ?7F? H*?1E? Y;?{!p?j(X?I%İ?fM_?8z`?r?dG6?|q}h?ג ?(k?b&?_xN?!?}\ |?:?#?v9A:?F&?,[L?ɰoD?ϧE?:0p?\N 0?_<y?l#\*A?R?F?2ѹ?<rp?/)QL??6Wi_?8~W1?}r??ZrD?WX$?p@>?ǹs~?oM?àaJ?^?'6?3%E9?e”?h hK?:@+$ ?뉴oF?2;q?u[B?8?*XCT?6 A?ӶM!?HX?ڒ?!5[X?ު_?m;2?9?e~x?›?,o{?g?$ &?y7?F? -?<Ѹ?ς?1p=#?#?kT?N?\mk[?O?V?VqL!?^jE?cf M?V&?C6‘?EZ[9?n. ?߂W?Ν}V?Ȱ?jײ,?t7?cø~?39,?rqʁ? G3e?A^$u?i 8?q6?3 ?Kq?u0?*:?vbN?琤Q0?'ꇒ?;ȼ?4ݘ(6?֜w{S?ZkP?3r ? ֬?av}O?9fhW?Wj]x<@?k?w3?705?5tT-?1??ib?N+l?!?q?ew?ߢ\fK? %u7?г?^1B?pJR?LC?9Ǟr?ۗB`è?䅝i?@0c.?Jn?Hc~? y??. Ȃ?؃j?`\af ?I?g*8?x_?n?#2<?> ;?Ԝb?r \I??ߓm?oˆ ? ??6'?QSz?9]f? 1?R.!?g?xϨ?6?l/?^Z?6@?<$g?& b?љ[Mj?% ?jS?]%?޿?-#"0?fM?N'`?Yvu?M)?E)M@?p8ꁂ\?l͈ח?n8y?Q?bB/?]@?ScT?bs݄?*%?}`?VޡHt?`B??<[vvI?k q?ʔ ?to ?_ng?WXM?m?+ ?l~oJ?t[ۆ?uO8SH??Uji?= ? i?Z4?הt4?f0 ?բϠ@?q>2>?&_`?3[?ZCK?D'p?W9?>xvwd?θ9?w8 ?!3?r11?Yʩ?k}?ˌTv?Vx(?qJJ?p?Ӱ.P?f2q?OIZ(?!V4f?ذ?8n?/-N?d3x]8?R\J?NU?ԡ7>+?ٯN? I`?vB\?_l\Jx?q~e\?[ U0?lQ?# ?R#3O?iz ?/.?윥|?͆azٌ?"?*Dn?/D?kI6?5|dc@?k ?r9p?I?_D?Ye?֚^S?H$?~3?֨}ݔd?kK޴?q@?q= ?쉟?"{o?6?(?dԗ|?B^V?Ԇ?Dh?#iDb?JT=G?뒊Z0?0NY?GkgW?ꋳХ?SkWI?wd?ٰ' ?ÛM/l?*F1?݁[(?`ύP^?-7l?wo\? _?".:?=QZ?3.h2_?z?{69h?qKw?4?A ?ӆDR?Q1$?.^"?3? ?ѤPI?Tj?m? -Zp|?ح'F~?"uK?:0ژ?׉D'?υw`?9??du?8$?ݹE?vuq?b{d?ܡ??k?HP?.K?U?5 L?(&A$?c25?RvL? }?ϼ_~?nVa_ ?\?y(}?֮B?B#S>?.im?^1??r5?D(?>H}Ԡ?tS0?nǃ?#R+?e 1N?d?A8^@?Ïkk?N(6?NWD?҈?s!8~??dh? [?idv?0p7?a'Uʐ?t?nj?LH?=?ɵ:?=T%??c)[?ެxd?ݰUO?ٻDy ?X47|?ֻ}ۧ?̋P2d?s5%>?ڇ΂-?\-?*ˁ]?wSIx?ӎy2?ʻ$ʿ?0Bt?ǎ ?L&D?望:?lP?븙qKq? !W"?+ ?cb?l?T&:?g2 ?E ?YqX?@ӝ?(?4?@?̀?)?³0?,$?*?O??![tz?/"? h?͸?Ï,W?tɝ4?mp?خ#ve?u,?_N?.Z?) 8??"bUL?(HC?/\?nig?Ig}h?Xۮ~?:xǴa?8m?*܀T0?b)ݔ?U`?@e??Xs? ?׸5r?$b_?PQf?oc%?Zve?\H?,`+#?D6(?2m;?M? F? ?]nKl?6 8?-p?4*U?sU?t?Gh5?y eX"?N?V?|ûr?{?̇4l?ݘ?0g?[ M?;¹?XIA?3?ݒ*\X?OkB?߼ӂ? x?z^P?Vl>?ֿt.?쟻j?&Ku?K/?Ҕc?7W?q&xtI? Qi(?%`?ڙ/0\n?0^0?"3?5W?8Jd?]@?b()?"Ť?ԊK?&!n?N(]?ËWcl?j@?3/y?VI%̰?(d?TY ?p~??P:Lw?|F?cr?FCQ?+s?I=2?_؃L?l/?3'\ ?(:?MR%R??ưt?B4 8? vX?cP?@pA=?1?{X?]? +8?u덚?耧?_?Fy-l?˧g`?j?!)K ?k?kՔ@?މ5?魬iR$?Q q`,? v7?^b_z#?$*?gy]+<?W3@?N?} T?-4?"KJs?gR?wK?ݬ|?Fzk?!_R?-?O?ؙ? ?AcI?͛:? 'y=?9w$?'=}?0W/?Q=?caqF'??aܿ?J ?F}q?׽x]?^(?jՈ~?jd?mM?ӵ\N?[ӥގ?60 ?;?ܲuz?'ax?#=5/?ke"W?iol?E`?9ِ\?&)c?vׄT?p?ڍxD??QF[?YfO ?̃PMB@0?z)?各?-Rj?܇% ?F@qW?zo)? H?j5?鐶?m2?>DxP~?Qw(x?ߴ?@ř?C?6?M?@rR?K:?wwp?/D?ӎ?,rc?Jqd?[ H? hxL?*v?Ƣ?9??7?mJW`?nR ?ϒ$Se?ڱ|?4a>?A38?W'R?HGb0? '00?ڋ?3/H?֏pN$'?؛C ?W#j1??jC?݌zLw?]#,?%+9?A'?~d?ց?TG?R#^?ŏ7L?am?❀S?e܂_?j`?,,w?Ӝ|紼v?d?]]?\?n?-WX?SȤ?w;Ef?Q: ?ՆVKT?Ai>??ua*?ڐ9X?a+?C?,b ?=-p?f>bI???۳?΃>?%f?}?p0?5ݝ?׌aV?E60$?_}?w?Jk[Y?!@%?vDo#?иV?YC ?}j?9 j_ ?$C[??Єos?(?V-*D,?"ۏ]@?5?b#8? ?ێ8t?F.M?}g,?::@?[??*]v??F?/&aB?F?+2W?APA?ϮVk\?Ե }R?r GT?B?‘1?mItR?ֈ}k8?Ed?Pf?F7X@?҅,?#uc[?WJQ?8㍺?<^?u8f?  |?0>S1?pCi#?,$?*2D?,K?C'?,7?wBV?濕-t:?/![?;B?杓l?tn N?!`?b?P]T??W?KЫ_?JUP0?>$E?wJ?!CZ.?L'?`{?iL?j)?а?wQ?~qv?ęT?jݴ??@?^?%0(?iq?v?ROS{?c>bxu?Q8VX?$K#֑?q_yB\?M ?&W?B?"?LIHX?T/?x&? ?ӄ?#aP8?dZi0?a+t?iQi?Ự ?V4` ? `?*??L /8?ޙ8?8͝7P?7/`?c?[*pH?JP@?-4Sl?b)?ީ%ݎ?Z?ci?9,?(^ ?q?[Xi-?q`7?n@|JS?IԒ`?ѣ4¯G?8r??~ N|?'?Hc\?`ǻ ?ڽØ?ɦZ^?hQ1?9?'>4?Jq\5?Ĺכ?ɅSiOJ?M(?X?|W?AƴИ ?M?݋6?kh?" $?Ԉ(`?a`"/?ق?G?b?ufe4?|?*ypL?Z?妰 ?Ă?H?)G?Ⱪcp?ϧ(?Ɖ?z9v?S5?83&'0?$^w?+ܘ?/xNfjp?WI ?^~`!?U RC1?~8f*?s?w߾z~?ž΄h?Cl?d v?#YJ?ə4? Nef$?w*k?M8n@?뫖+?T$ ?3A??+?qӳ ?\A@?2S@?УY#?s<?:K2!?& ?c7k(?&?wO@X?ݭ??r|?5_?d~]?lW?T2?'y.sV?Ձ9^ʰ?B%r?y|=?xZ?<2?{4?>?qD|?0 jn^?(/s+?A2Ll?̼G?B? Y+Ĥ?L= ?XWp?#a)X?WWX?Q#?b}[?'@'SN?n3 ?䛾|?փs/?wC?2.?̸wBڨ?؍77?ɖEl?/ܸj?tI?E"9&C?寬 ?)&S?pq?T K?9^?NL5?\U?T?4B?ty?f$D&?h-?4|v?K?T?ꊼi@? $L?bZ7}L?6=Td??U?u y?Wo?;1B?K\?ŘX?B o?Լ_F?%/nl? 𰅎?kd?\|?j?_N?ߕ.㐉T?s(&?,YL?f?s?mbR?D? Z?F&?Q"?&mYn? b(P?1 ?.ŷ*?1l?R2A+?TK?oH@?G+v$?σ롪?[he$? ^6E?:5?j=\J?Ϲ?c`?#?ɚ>rN?2p[?'@?-bd?Ӂ?;h?LUL|?F?Ia? SLYV?ϐlP0??D@x?ᆏ%s?L36aȓ?Bn.?ɠ@D?3g$@??ID?̱*?ydCV?Rl|?)"?9?h ? ybH?p`?`Me?;޼?1f!C?r]/?ЍB?O?اp?!?T<8?că ?03-?^4k?Z؁v?r83D?B_|?L q?F-ա? ?؄Av?꠫i6??x{?5?S]?7?6E4L?O?}7R?$L?I=*?TR]?ds}?uQ |?$E^?jDNzx?+M ?"NRQ?hB ?fe?͢^ڀ?t8?oa#?l 7?? t?d$-/?"ǒb?Z?[?n] eD?mJ۶?:w_?bF^?'{ ?͏eU?ץ0`Y ?ߗFō?%n\d?dSL %F?ס?A*X`)?|?`?'qdYv?j?|?WT3?,8TϚ?KW?RF.?$z?]3#?1MN"?].-Ƒ?3{ٌ-?i[&?Pu?tDZ?A? A|#?~6?c•?/73?p?Q8?0?`@?㣯lT?ҼK ?aN3*?3^?ơz)?nLp?-ӈ?L?1R?婳?`68?⪯}?NP ?l4a?l;3?tM'?әT5V??,?pf?{ 3? ?a ;p?\]|@?PG0?qS??3?@?7>#N?9?ʋX?0#c!?]?GsG`?obpj?kH^D?W;U?J_N?Ih?|˕`?d8|?|@4f?+9´?;4Uwl? &~?kZ?(?ʬ>tO?ù/?3?t?əDCW?,?nH?E%}?\*?4':?ê?x?gH?ljCP?smj?☆rO?GE?nr'?}b?ԯ y͸?r5?pyt?Tx7Q?[grB"?;0?Ё- j?3N(?,nX?Ƙ~n?e?+r ? ~t;?MclSC?3?X͝# ?a??޹ j?=?ɸ?ǿpq ?Z}*?뱖) ?HՎ*?D1)?up҂?"Q?: r?Gvr?}n?Okp?Fh?޼nO@?T^F?UztH?;4=we?mXA?n Ь??X@?GzL?ʙA?$C],?Ϻ<?HsI?Ύ@?$)O?;t?뎏R?Ͷm?6A`8?y{?fC^?DږG?sfr- ?È (?r)a`? Zd?b4?SzA?.%-r?î^p?SŰa4?Va?ZZP?6p?c2?ְ=o?ҁ=?SY?z]?*_"?*m?L' ?ڎCj?)|?R ?1y B?ߖ wV?ټƘS ?(Ϧw? 9?2#? t?bh'@? ?m ?]X1?6P?rb?.l?%ڰ?~[q? yH?|MP?ՀX?1%??T d?h d ?ɷ?@|x.?3$(,?w~p?K?xn?A ?:x3U?m.*?4Upz? YE,?B5$?ղ? RP?Ճt ?vNp?J6?u?&8?te?srΠ?bh ^?qav?լ? b?J-:?N.&T??k?|!’t?ݽ?jkq0?'X?lOj?f&)?~Fh!4?y-?ᖊ̈́?Rj$@d?.:?%?8?Y?0|O ?Zh5A?ߌ*?+?8?M+?oW;?kP?ݿ6?_HHH?1g ?3Tvn?ЛK?uc? ?n?"Q?w@"?(S)E?BI,?Æ?,3OG?VS?t?M?(?gN*? hJ)H? t?][@?Ev?ꇮx?`Ǝw?Q?l~?en?֩;|???Sr?T{d?ӋG?AE|?u`P?w?ҐPK]?M??y$g?Ќ8PN?TP^p?䲧qh?T?G s%?7 ?'[M?mt >?g aR?&T5?(NNjLv?B @?B14J?X?Q4F~??ޮ Mt?0u?I;?=?$,e?$0 ?ݫq?а^t?CfH?T)T ?A?{K?ͬ=V??i%??԰/?+1?U,?… PDH?RJ=?tQ)P ?F??AfX? ?=:sb?('H#K?u#u3?YB:zI?5dn?a#?&n7d?'M?~izX`?<9?l0Mt?dzX,?ʾk?O@?ӺC?cqB,?FN$?ޓ@?۩?wr?WcW?4Dt?m9j?ٞ?ZݡwB?c6C?z?B:[`?YB?՗5j4N?w],?N?p,LN?{8a?$-?u[?F?x']?bi?F0?9c{j?k<¼?,%? Kk?rU?У {e?1At}?NHK2?ɜ%y?'b?8C?yL?f*\?¥2?fE?3X;p?h9?BV?"ķP?fCvX@?|TJ0?"?ۢb?6?^>$?W4j%?Qy?"Ն? D?iz?`T씀?Ұ?z{?rvp.e?0Bk?Rv?cS.?:z?WLk?_G[N?J`1?n?)2D?&}gq?VbPA;?誹v?# ?Ƀ?Gld?Qy?n ? [os@?Yŏ*?DJ=?oꮽ?լZ?ú ?Ӵ!} ? ,l?)]`? ,?CBd ?Ώ}6|?ʸnT?XfJ]?kp`?ӥ\?TqD_?F-Q%?+GI8S ?Ҝѹ7?$q?ơe?т|,"?OFj?[hz?ؿSR?wF?^?Gj&]?F21b?!I?\OP??|<_?isI?7c(??To?;B?б][?!_5?66,V?ͻuچ?ÆP?Kš+}?g}xs?⏿x?%q?ʁRc(?ᆻ~dq?dI~?zz ?/?D? ?ӽ( ?`?*34?=n?Ġw?Zk~?E]r@`?& ??o?42?W,s?Ž"o?Hm:@p?I$?P?Iiܤ?b;eL?O.?|귤?wWq?RgFe?LZ]f??_̉?_?[ ?ryL??.ߢY?+h[?,""NJ?>£?Mz_?R3?*$ 7?[9|?a-J+z?A?쫸?3+n "?K$?ݤzr?Ӹ˟B?lj#?IV?15?D?d?eg?οH?׈t;I2?̥C?LJv?ʖ??Kz?ښ?4?P*?RYlm?v?38`h?>0 "?C뛵?nT?a:?J??5W?m?d&P?DA-?rOvj?omn?Jo?C"eg?C?Ě^ d?L?1"W`?#ܐ?53`?m?X?;~{<,? ,Zp? ((?!ͦ?Ʈ H?ҍ(?]?ۿ>lb?O.T?ld&?>"?`4:?kyU?\Q |?4T ?:Vq-?"rxB3?a!%8?hVj?](?Xap?jeq?`9?j?뼷 ?IEM?'m?;f?8P?,?6i? 8?9JA[W?p?]?uGt`?8/e ?Χr?@ 0?V<[b>?IW?dnaT?y$?V ?ݚ/‡'?e&$Ty?L3F?m(?ɼH?'@?`7`?Hxf?[7L?pc"?!o?$?_?)s3?$6?//?ͣ?zS?`?0L?DOu4?SYP? I?Kǒb:?Nj?å?{i$3T|?#f?IP?<\?&o?We2?҉r:Q^?/$X?N ?@;dq?-< F?qLI~?[i?|Ǥo?%} ?i?g3 4? ?[q-?4?^,@?cN:?h TP?iH&Ka?Җ ?byF?p6}\?ӂz? 7P?? B?m2@?r*?5 ?9@?mM?Z?w*й?jth?&?W7?V?@?~?r-?60[?0fp?dB?ZE?b?x!s"?yaj1rP?}d{?q?@rx?OKJr?^j?JR?5⸾?a?VYj?30`?똪^ܔ?rC?֏R?S=E ?ĬF.L??˳|P?R(?~'L?ř$l?ЭGJ?W;H?;_X?7/u:,?ݟK?5?׉hs"?ط(T?.bb?V^m>?RM?!\?߻.f?P?րS?03?~í?>@H6??a?ܦ3?Ê*?M?t?SN]?%>O?LX?ot?ꄀ?K#*??[?K0?i_&d?'?T,?D.[?]@?38I??* ?譜_?Z?Su5#?薚Y?")$p?[(@?~0Px?@?9?d; ?Q^W ? ن?ZM[?ݏ֒x?C?g?]@ѣ?ߟZd?ZF(? 6? ?GVhL?8?~h?&H?ڇ ` ݘ?Qq?Lt?% ?KP?X z?4F?RW??1v?ѝ))&|?3AhN?J x?]??ن6?< ?ݷ`< ?PIm,?ZGd?7ЃŲ@?pm?t?s8?.??؄7\?*Gv?=Uc?x2Y ?>MAA??1?yZ?ecR?^4º?dרf ?(?b?{t?Ox?rѡ/? ǭ?NÎv?ћ!?)d?:":?!k?M?>Xe?%Mލ?zH?-$? c{v? Njx?껡?ɚӯ?pƇ;s? \??PZ;?&?Ă?P^?SE?LEܢ j?gg,?= 3?+<\W4?$,`?z?ӭ;?^?PU}0?;V?s?.|?C&?P-s@?W?>8?X.Q`? F\N?yi3b?hMѤ6?傕<?סo("?g'?ܦ[>?+e ?ױ? zz?8 ?⼆;?=)?Icp翸?νFE$?V?:+l3?•(*?׍}?"OKA?Wz{?O?]d%?nn?OK;?{^E:?&TH?^?p7$?7sy-?nK?n Z?J}?Mr?錔ձq?BC?I?gAu?en$?ܕh\? ԡ? ?Z)=??:ak?̲8?wM&?/?-0B?fd?B8x$?Y>(?h?f\>N? M|[?Ӧp[?X'[5?_B˗?M^n?1r~D?1'g?:ǘ?Cg( ?<|v#^?ݲy?.u? B}c? X?V7?ɫ+!?2?C/?ݐ!Ln?c$F?0VNp?Ѵ}PL?@r1?{0۔4?ĦOo(?֔?9B?чimI2?WBI?Ň{m?0?״?)L?|?l?JQIc?€?ɐ',?eҨ?M.?m~p?tR@{?`I޸?=b)?⎵b?r?;d ? ?D'?,wCP8?>sn?~Ĵ?t?OӦJ?CWs?=^a?<_1??1Z?q!U?TVn?蓨6v?t,?wH?!:? Ww,4? ?$1(p?ܘm^?st?g!? ?=b?4hZ5?4?WXM?"y N?b忻$"??v?sV?0?tGF?O :?˩?Zb?|p,?)??܄LS?6Ŏ? A?E|DA,?ԖT?ng? ?vG?k?]8m?t婴?X ? v7P?@_Š?GYjXF?:?!h.;?,e?B{?X/n?fO?iޟ?Q,t5f?smgZ?ɚ`m?X/2?4=p`?oȿ?65 ?b? Բ?zMp?bU0?>wa}?SVd5?~Qm?;Bz? F?ېj%s/?*?7N:?`yֿ?X^?'oL?ț?Дe?sЦ?! ??@c?ړdk?\C?2LAp?hZenK?Hoޚ?2\6/?{s,:?Oܫ.?|c暱??%%0?y?|Rr?ŋl?D7$?k3?*0x?ngl?F?šc?ڙ1$ p?ԓBeo?Ќn2?E??r8&?\’?\4?'3y?e<x? Xd?I7_?IG_d6?1QH`?!Wc?? *h? {զ~L?J?΁?N*R?-L?.k?&ˀy)?l"wI?w?ԝbF(?'ʟo?X {?EA?$J-?4?xXt?֍?p!?̨??KLp?j2p?Q6?L@e,?ٝQ[4?u?y ?{?~u3?7j8?QD;?W@Z|?X?NSzB ?ح]_ܪ?_?,?^~g?n7"E?)}zl?C⿨??8!~?٤פ^?&Wy?XKC?rX{?gG ?Q*?e0?ڲ ?V;?*ϟ?"47?-fp?ΰ8b?f?e4?Yax?Y)h?ɒ. ?ISз?BX?'X~??0?'g?? L|0?\~f}?n?ыq?QTkŠ~?ՎA5?耞yH?kB?X /s?穁c?Q:?85?IPdrT?MF? ?ˑ$?{0?u7%(? a~?}0)?$jDT?L?J~?Xf??z?ĄH?1~qk`?#R?̮@? sC8?ܡ ǂ?"?oܠ?e&??x?G/ft?ٱ?]I¿??W?OA\?pj?꒿?r? ?ޱ?N^z:? b6?϶ܥ?n-i?&$n?cK% ?[O cl?-/y\? +1?V<_Ξ?R?杮5x?[({Xb?w(=?2A[C?Ifq!?iK_.? ph?^<0?Zn?:V?@u7?–ƍ?ӨѡFB?О{9e?؞n?r?e?}x}?šK`?[3q?#?܊:?yOLHh?xT?K̿Ɩ?<)z?ܨ[?0 `9?`?w?~x\?q ??Z@F ?洛kϙ?d?P?D--A?F8?|1)-?ߠ Q?G?܇^~?GjzP?- Ċ?^b ?Ԥ~ Z?Ww?璜\?_$p?O?6/t? (dx?L8 ?>?,CRŽ??h؏?y?Qn?mLP?'?̷yE%P?ѡ7g_?&P?VH?5%?VX?$9?lp#?K?L@?hTG?Ъ+(?ΥoV?i?}B?1[?/?FIs?)#?`S|^j?cK?/%?S?c?ldz?'tq?CD?$O?#2?Y^?6CQ$?y?9D0?աrfH&?my"H?I&`?Ml0?0xCq?!?lD;G(6?W?8E? "M?5/?&rQ?9Y?i4?< wP?v̨a>?pfM?ܑ>?ꈶG@N?(ϹC?YQ??҂?|[t ?%'?IU&?I'_%?LK? 8?`(Wh?`P.k3"?6?ؘ?WS ? ?yF5?άl*?(}?ιժ? [?4?㭦?M_?*#ʃd?ぬb?]gqc?v?Gf?\p?ۮ,?px?xs?:Hg3?z? 1W[?ԂE?s? Id,?sᲝh?-S'?CwfL? B? ?%as?&2j?c ?kW!?mОof?.LV?Ԫ1nv?E?Ј{?Tt=+? o ?ݗLSGH? =?&H?e?X-\?0j2?ʄ?J*.?/r>|?AA?! ?FD)?A;99r?;) ?7<@|?f*w?̎kF?Þ?o͟?߭Dfnh?ҭJ?x=g/`?M=(]?5Q?ǩ(4?#Ax?^o\?ݶw?ŠR?,j[?x?]mߺ? CU? G@?ޠ ?㯵h%j?Ҝv??)tZ ?){E?;nI?^\ ?L?Dc?~8vC?)?ԛKq ?Cb?G#$?3bF\:?̖|?m?v?wf>?õ4?H@r?kDuJ?崇a?Z΅?ߕ?*2?@?ORUP?A;k .?0fsP?tt?\%6D?F5?j%^}?׮(\?_@~d?@78?V,@?n??:?Zjl?Ju1?G e?ϫ{ ??XsT?ӭҿ ?mdZ,?1H?>?pW?Ó?K]?͓{8?]˼? R^>i:?Ӝ* ?>?Q0Z?Q,n? 9?| ?몘h6u?촡?k^?V"%?|ww?~/?qEG?|{?ra1a`?;a>U?׍-V?9ɶ ?k30?Ppi.? q;?,d?׈P?籽?9A"?ۀ-9? ?h*?4l*?|<,?EyH? A?$?+ ?C0W?OO1?}Bo???d!?4_?쒄X_?\qXGx?N?&J?e?#ȸb?*t??0|?k ?^K`?Ӡ,n?8N.r?Ӌ?_?#?8O?ܾ}NH?w#?͡ ?KVy?E6?ߢ,G?N& ? i"?^L?܅YN?wT7(?}?+b?pO?K?Eѝ0?Mul?з)VŠ?`]ڰ?m?Oߞ?Ͱ?v~D?Kg~?%42?!w?p&(?AE??ZA_0???\?/ϑu0?=q?^7?q7k?bv?&}l?ά% t?`?Ⱥ~?zQ??mY'I?o* ? \?OZ?s6?㣬b?kPT~?Y?/kK ?A13ʄ?Q ? $?.i?,|t?ߢmf? x?x ?f6@?0K$?y]?;?ᬠQ?)p>D? ?߁c2&?š8l?Ƣrh|?杶lX#7?K$H ?֣Bl?<9?/gg?b\?3x?$O$?r)?״?Dd?bA?Ql?uA?¸v?@YsI?쐿?U䘉?>5F?Ea?"LK?N%?lc?:?74?{]rθ?VǾn? z?a7p?ౣq'?ס7/?sf-{?Ю>*V? _JE?vvTBp?}UnD?TF?vMH?btN?Y?ґRʾ? 8>?AFZ?П0\?oH?ܾmt?NR8Y?ͨP+?- ?⏪X?`B=X?Zv?' ?ڴQ?)kjc?@?XU8l?HʡQ?_J}? ̤"?p?*&?ְ/??'VnB?7Ƴe?|?S#`?՜dv?I?#w|ӹ?Hq ??X&h?"Ph?)s}?/b?K^e^?/I?}"0?ڧ[?լw?z@?+b+?{OaW?g@ox?K~?>Km1G?&O-? b^C?$os?~c?Mh?~U -?p?Ô]8?4?a4`??~^U?4?b"?"~!?tY?1o?ܧh?U5x?ʵ= ?띕1B;?h'?JT̄?!mV?؀#?̫?Ø`?Bl:h? ?afd1?\Vb?e?f'?iS?m?nt$X?BL=`?HŌ;??YW?t?ƗĩJ"T?v?+(?"ؽ?4?Xg?@9\?+u܋? ?r&O`?B?-~?늛:?K8Q?*?aJ?)GD?C%na?s?8(?ѧ?cd?I ?w?ܷ/ϗT?@x ?Fv"?KK*?ydN_c?ux?\8?!g4?p-7?A?ɟ8Yv? ݖΒ?9?B_?t;?|Cu`?~_<4?1yj?w|?'X?́h?Ѵ ?ӹ?q H?{D?7?)z3P?4?l?܏@?h@Q^?&#?{?{[?`XO?P ?[?8Ur?˳KI?~D4?t^~?Ee}?ڱ}P?l?LYe?㗓Yʂf?gF 0?Ł2?c(s'G?눃8?d ?͈?#m?6?02?*I ,?2E?O'?8Ut;?)@?B|& z?~0q?nè~?][ i? Wl?Rw(`?1M?rkL?~??P^"?ɷEj?>yy?:M?箾Y?㭽ul? ?f?T$?١S?ԏ?N^?VE3 r?Ҥ\߈?c?ͶP5Z ?5Kԥ=?[J)A|?em*? ?ؘpR?8BG3?Q?n?zNV? (?ͽ@?fh=?ePzl?̜,Č?`Gp?巭5Lbb?_Vq?'i5̼?E90fj?|?/rW?W ?/X#?ࢗ&?]fÍ?њa@?xS?ܮP]n0? #}?ԧ?ׂNT?×m4?ͽ%1?!e?DA?^?J?'9?sn8?7|BHͲ?(?K„?%GN?ɭup??pſ=?҅nf\Z?ط,?C ?̪1?$c?逰8w? ?ϭ8?֔36?ƞ-d1?W?+~? Dp?߇kfD ?z$iH?jPV?޲Q?W?;ϳ?k?$E?En3n?\ "?/d?jenl0?托Ц^???o"?R? ?Ӓ??ym&`?X||?m|Ņ?뚍G:?ml?%e{6?Vk4/?౱?U?o"?¤N?K#0?XA॒?0C5`?ܧv?Vh?_Nr?TOQ?<ߡ>?͆?An>?袙? ?ǼX?%;?_kah?Ρo?/QP?:X?4P?䃗 j?144w?6 i?Seo?huR?(J?d=?rX?U'?MO? V?gs?iIz?9=k?@{D?굆'A?#?C@?]Ջϰ?j=fg?RHM)?ꮿQ?-טJ?.Ar?بU{{?ڽ!_?)6?b_?.50 ?Ã\!QT?d6E.?gn*?beC?|?ThT?}]Ҝ?$;?Ezy?3[?rle}?nP?2 P? %?J')n?ʦc?Q?ӲG?O 7R?uʭ?CB#8t?؟(3?v?;qtד??]ʀ?]h?Ϸ+P? +?p̐G?b|x?ZI?tAW]?YFLl?<Z?er?d27,?!͊?I &?oDDN?|7?蘍= ?‰0?h _?-PF?I'I$t?\ ? L?ϖ?Fdm?wn+?:E|?R-?ȌU?ch/?Q1?L*?a?79b?/t?1f@?!_R?d?P4?WKw?=d ?|?)V\?r3t?p?^@ۦd?ÛeD?KM?~?ژӳaͰ?վ?U'd?.?0{???*.?-?,l:? zᐧ?ϼLYq@?δ  ?ɚA?=)?GY?$t?_?0?Er?/^vc?[l黏?ϩ?z5:?h?Tz?GXpz??#/$TV?Qp5+?룋?֫P_%>?3?z?jj?۵E?ٍ0ؖ?;e!8?I?w?Ֆh]_h?\޲?I??\`+?%D ?c! ?V9?%bv?5iML?mD?0V?&.`0?y!?U=ߛ?w[9?UX'?ǁݜ?t:?:?ot?Bۋu?LYt?2$? ?M6 ?Hf ס?ܨU8?ٜ}?\7]{?D?>&!~?v?>=è4?>Iw?eE?д^ q?:.?Slo@?('8K?㹒.P?X?ob\q?9a?{?߲ض^?I2?=^?N^V?Q|! ?B:Q?H{w|?ZđP?O?rP9?ʀ{L?MT?R?J{O?Kkq?c?5Z3*?$/k?ڏ#ZN?+j574?4j_g?+bf?A?QU?C!?ق?l3F ?ZCO&?oD?PLr? ,?c|4?06?P?) .?鿇*Γ7?:?4Vъ?֨+Al?/Pp?TtF?̏x?륂???3\Ʌ?fɟ?w ?ko?΂ ?>j^ZO?I(?Q|b?99d?n+?5ߡ8?, c?O# ?Jn?k?ğ &?%O ?* +r?u?dk?Yp?J`?ˉrɺ?ΓE?ܢ U?Т?Cؗ?J\d?ڛX[=?? dE؋?L7?X?˨xP?<x? 'P?`ga?ү~,?E_??h?Z7?iOS?ހG0?+%G?K?!3??R~0??'?O8?ȩ?{Tu.H(?gz\?ګt?>g,ɠ?U? Ѫ@?km=5?=J?2zfJ?J?Dy?ZBp?r]?3?hR?ww?麇P2? mќ?ゟd?aG ?"!?Cr9?K}?#ܑ?&p?$f?Om N?XYNt?,j?s5?T/?W7'9X?Q4?Q"?2`TR2?~5?eh>?ZV?z˭T4$?ഷlcq?˞km?c5j?E\`+? eW?Tr) ?8?,,?ƌ]h?.Gs?>kYl?竴7?;B?gƛӿ?&t?)>I|?琯(?"-%9?~s?ۍq/D?f?1 ?4/?96H?.?'^d?ݔ!#u ?LT?7ѣ?Ȇ֢?#]dk?ۡJ[(?3,Y.?ۣ͸g?帺6Qg?Q ?ȻM?}CZ?˭v2?zXy? 7?.?ySBP?+{?ϖS22?N+o?w8w%?m=B@?دzX?\Gy?٦qHt?]):?Hn r ?¢e?퐃W[?Tj?%?lf[(?: ?qi)?G|?sd?UƜ(?3Q{h?⼻gȍ?ڬ2?F6?u`?şY?u;&? ].?,?ɬ/@?_0?]]_?MP?Li:?o{i?Ȗ??:Rx?Bg0??7qL?K%??ݤPl?+Y5?鶹ǭ:?7]:?=?r$?͐I?}}l`??e݌?ȴ &T?gi? nX?,?mA?ق/??ݮHJT?A9 ?aH?ğw~?^T ?kS ?)cZR?T'<?Ŀk?gƉ7?+&?`Sr?^2?#@l*?a(nl0?݇S?hB?ޥT?$MȎ?64:U?l'Q?+Va?hF?9f?8[n1?ϒ?ǀԥL?PU?春:f|?Ȕ;?'{@?Z&X?# ?6@?p,?3>?M?ݷ*n3?nAZ?(2?햐?ڎqM?^Br?FJI|L?,bv?=P~@?Q1?_D?Ԓ;#?8֦5?ݮE#?*x?븧U?L%\}?YK?󁷥?Br ?S"`x?o?!)˟?Վy?9LV?[?6'?\U?*E?ɸ!r?Cј?Tܹ?L?|?E??Յ#X?_'1?M|?6$?DScb?W53?m?V&i?:U(?ט?"%he?W?E?< (P?Vl'? LI?V4?O\u2?qe?<_]?Z#? ,?"ڢ$?0޴?"?/m8]?yt?׷V?9n?YҨop?|4uQ?!p ?ѥX=[?;Cw?hId?KQ? 'DӴB?7I? 1V?CWb/?tF?ȹ>?;ZW5?߸ M?ɂb#8?4Wp?^)?엡?S?-Dx?`?0Z? ?yi2?'|?t*I?yNH?2fɅ?G2܁?b?э[93Ƣ?VmD?#]B?ɁL?1.?L?.H~?gS[?`? M?ڟPz?׀,ָ?Dt??K?3aD? HE+?8뚲 .?P&?ƺX?M>?oo?CG6z`?`Ȥ+?_j?ŽSI?/~@?QjӞ?]?큦J7? ?0F,Р?;{6g?$9Ej?"CaH?wY?q&?mt?-Δd?R ?zp~.?*?0bi?ʫw?O͖s?둋'?΍E?Ҷ&R?ê ?RUEX?FW?FB?ã?'C?'hd ?oU?]i'?8><&l?4r?gE?Hʔ?͜'W|???4pZ?'E {R?I?߷&?;0?7nO??ԗIcp?Bӊm?7aS?_`?U_Y?>qN?Cn?\sks?w?j>+^?sd;?W4?7/ ?0Ѓ?հod?ߧ?|?؎#9?-?Ƥ3?ȕ^"?V/?a ?*nn8?ﻺv,?`??ΐ}?Љvj?&p?ȨS׽?B3$?砰=? ?ϡg)?Ӡd5nGJ??u\?Dq?cqĜ?R+?ߑq?I?}ϙ}?c??F7z?Ic?1d?ކ'*?݉rJ?特~?O?QY?vL?/U"Ke,?l*t?Jsϡ2?H?%fD?q\Z?拾 {?_.l?чI?֗1?c&=?gP%?p#si?r} ?gcsd?^?`jSw?Ll S?XZ?ݚVLN?ۍ 7?ӽI5?[c)5k?Ѱ\״N?D_(?L24?ޭ-?+p?0(?9K}(?_?ܦSmz?"?ѱ\b?ʙފ?헽@# (?+E~?kKi?'Ϙ??Kw?E^??eA??G@@?RS*?䒉I,?v?(?pD=?⁉ƭ??ijrB?FO?ru?_  ?ɛPy4?L=p?G(?0?r[;^ ?ps?]%qŶ?~? ě? yM?5s?v?Ă*h?KvP?gY?ዲ"?X?=`n?, 0k?+G5?:86?E10y?CV6B?H?W?+E6?1 ?9r~?Sr?%H?,̜bK?v??AҴ?>5y?+?{?;s?~X?s %!`???ˆ[J??0%?)?O;q6?aD٩?Zw?45m?%q?%yFa?DpXP?Xy?2ج?F? ?ヰ$ؖt?ˀ蹎?"A?uFե?_1?y?ЬKfp? ,+? w?U?BDZ?ɯ~?;qH?8l}0?r(?c*?d! ?, M?|]?kR?WbA?)j.Р?c ?EB? AWq?1@Ev\?Ώ"?=5`?݇P?}S ?;}?M?D[!ǰ?Ux?z_N?#?|?wД?RwD?[;u?Ӌ?x?살q?XM?ɹuJ.??I|>?סH6?ߨf?Qqc?"#?^j?ݒO?r%l;0?GsT ??d?Қof?A-8?ȰN?֘?A?h~02?Jݢ?G"?E@?ዅbn&?T>F"?ď9Wd?.?%N&?B?4|>?u?¹('?H?ۉ4?yC{?:y\k?߄W&?uxӴ?ӥL?0s8?4A ?3&7v?` (?;?gn2?1?ڻ r?NfN?X?ѹ>&]?wl?wP?\?e1È? r[m-?3G ?6?L- Z?h?T?&h_??QQ\?6?2%?QlPR?]e$?9%N?G%?t7?l3xS?ՔMFI?>q?tAME?3_#?5?i? ;)?CLb5?2^]?Ҫ'?ܰ?䁤O#?|?톕z?}򕄦?*I?NT]?-7?Y:Tn?2sz?~k??ސAH&?%ֶ&?z;_Š?`1?Kei?Vl.?ar@?Ѕ]p?.v?NwF-t?;Aq?B?Ir>r"?`?dH;.?ͫmTP? ?4?/?pS?*_EGi?dM\h?fWú?@$??N?BA3`?[̌?4?1D&2Kl?4Ï ?|#w?*KT?lѐ?:#?\EC?(??YR?+& ?o/?O@8?ϯ3j? ?@d[F?R?>Z7E?MR?9o0?2s]? a?[9?\?}Y3]\?]X?ˆs+?☫F?搧V?n?{׿?娳-x?1;?y9?cP?6Ao?'Mh?~@?!!?魟x?XݠE??fyԜ?Җaǀ?>&zG?`?҃ ?tێs?k?)9?Ϯ5?o4?e6C?:?ԭ ???5?b =^?L 1^?8߾?hj?5 8@?_cD?<?9?V?iK?Hiq?ҤR4?Շ$?x]?1.?&2?M֡;Od?:#?fݴ?fg&6?n.t?N&?K y?ǚ~~?\e@?``0?gJ3?"jg?@Tj\?Q7> ?1X?c?t0?Xaʽ?~=:Z?ԯq{N?ā L?8(:?ѽ2?fj?о@;?S?"L? `W`?.?yZ?QT?%Dg?Sq?Ѥm-4?ՐRf#\6?0dL? ?C?ڰӒ?ݹ0Z? 0?\wH?#= ?t?3?P\N?tu?px?dt)? rP?D޿ީ?D^?+.?34_E?Hi,D?ҙU’y^?JƇ? R?*]6]i?M8?*3?!?n&?FLf;?mT??W(W?qX??-u?`e?}-?#*?[rt?Mt?r߈?yJH?e.=?[g?D]-G?0Z1?xz??}:?̠x?~|?707>?k}ۡ=??ѭZv?NG?Ԍ[2?CE?+2m?N?brݳ?Tѯ) ??ܱD?+"?/l?Sg:?McZַ?%\D4?Z}A?yr? 2H?P?l?ZzMG?\s@?PD 1!?תND?οֲ(t?ݛj?]p ?5Tq?3ꓒ?o}g?P?.D?ȼΛ\|?1awKn?g2h?ю^M.?X]?ɇE?ߞ]S?A6v?+;l8?CaF:@??M7_v?!'??g? ",?=t?PT?n>Kw?Ţ`wx?{o?3V1?Ȁ4?S@?iH???]?oh_?"?] ~?c}?Ωm&Kd?[P?PҶ"p?C?а^X? ޻R?A5?<'?e?lia?C ?6RF?t h?⽶,v?-Lޟ??$?=?ԈHy?,(V V? #?tpC?H"xB?GV?`rfU?'~?h;?-\ꂝ?D A8??I>?,F?EO? FQ ?/t~?/9)?`?2֭#a?ڞ?.{`?־?U؊?&XD?P|>~?o62Y6?E'? ?V_4?(o?xb?K?ˮ:0?ݤ#:?bհ ?~ ?Cb `?^?Az3?{Y)wMZ?P}ؠ?dޏ??ɇYT?Ю?@d ?X?ߩ Pl>h?碁?;B?(?Un`?F̮?h?5v?\? >N?푒P5??R?B??>~X.?4 Q?M_`m?6g?|k?Cbzb{?SiE(?ܥ_بb?H]@?'?f͑?Ͷ:dP?ܾBX@?@?(&{?|~r?V-?ۻs~M?F?Bq ?s V ?ԛ ?g,?2Sxh?U|b? 1? ?9xT?&H)w?&T`?ӸS8a$?3t?Z\$?@!a?$}G?u?3'?jN?6?ur X?J;"n?$$h?wѾT?Lݲ?ד ?J֡?⍹C@?n&?F?%O5?&?F 4j?{%l?LM?Fb?5b?He1J?uA?QO?#?E?_a?V풠?xb?ȍ ?0gC?VqP?֝t Ȃ?i`M&?CPdA?g`?<7o?i9K?-"{?^4? :?]K?˶b?类'!K?=ެ-O?/P?k#`?tb?G?$?+3?L>,?ْ{?ҫ-e0 ?SS?Ƚ%?\]J? P?%Usբ? lU:?v?lX?^z??[?3_-?1V{?@ ?æ#X?ɾb ?Vة?抓Zx?#WB?ӭ ? ?F?$?&^?^ʲ?v$?ҋN?]h3?KHZ s?ۊlpB?p7?;baF? R ^ƨ?VK?#غ{x?V-?l'2?߉?Йsʶ?U`U?HBŠ?i1Ow? ?ce,Ek? Y?=x?ڝ?Q3?f:?h??K*?jr3j?=?5׳?䃁Y0?|tt?ᥢK.?xd?1x?!L9?̻z2$?d?Ą@?֋'?TT6x??@?x?Ե(!?_,C[?1)G?? έK?9#[u?j N?Rk0n?cgN?l^ ?&Se?ã?>?(?5ml?I{tL?!{?]?'X4ST?ͤh?jbd;?[P ?#?(m?BZV?/Z&?D??!U?f1?֥l?`F"?ΎW?WOf3?2K5f?X2?a+?nm?B ?[h?䋻w? iP?̦d:?hR?@VѨ?ؗ.a?,M5X?jUp?Zt ?sU{?9OZ? \V? 0&?vd?¨eB?ߢ A ?5v9O?GU,?в^cu?ߏ?&?&Q9?; MP?{?aE,?K&?"k?((?!y?j?>+0饆?~CX?FO?x??Z)?@!.?m8?%?Ml(?ܷiAoN?|?oI`?=U_?xMݛ?Ժޮ?U./l?z.? ?_5?c?[Ί&?@G?It?1!y?}?3?4(?RO}~?潕Q??6 ?8;2?0? ? q?*!J?vmp?_ t?֩?? ?PT?sF?Ӈtmp?봖Q?ם5t(?O?\ݿ ?G?˝q?ڰ*?Faᘳ?+?6;?km ?m.L? kr?ߧ?D6s`?x6?v*D?F?r8 x?h?'Wli?Ro}?J(,??x?DҀ?Һ)B ?J?l?Ou?aL*?ɥ;/x?Yp?n?kM?р~Df?,`^]?n?TdH?@f3?;co?̙f뒥?rpL?XUp??o h?R8FV?ƅq?Nx?Pl?:1_?|?%?Jd?*KÕ?˔R˅?÷X?sJ%?~sKrG?O Y?|*>?N]X?O?g?eh?Ў?~y?Ut?g?u?1!P?Ŷ|?V`?@OL?>Dž?rN%?[Mί?C l?S(O(?s?^ ?Kr?àð? %?zɄ`?3.u?&9??&?f r?vY0? I?KtO?RT9?y9D?Q2? H ?ިx@?Sd?{?` Wt?ڝ`[Y?,4? +Gþ?:Hw+?d~?ή???5{_z?j[ ?ʚELH?a?zݿH?h-6?5rn?S ?^+?NB?ף8?/R9?}?4?s?㢯?6, ?6Jܠ?o4?gd? O?ɒP?f`?PZ?.H?Iq?~r?βɀ?&dϸ{?6?.Z?RCh??I(r޸J?Nj3?옥?İ9 ?ȹ?#bT?TŠYJ(?ce^(?пexh?f UT?&ag??ç\hZ?.R?)M٠??WJ?9? rط+?A%?ThLh?I??U?l#?}i#Sx?CؼW?/htS?ɯh??4] ?@@Lh?ʕf >2d?h. ? NK)?ޑ T?<(VO8?܌).?%!?{?sW?#?֤C?Ap?+bq)??J"+?,9?8d /?ĮY`?D?t&?z*A? m?AL}{?s?Ь1>h?دXB?6q?>_~?*?02?_D6P?U}?\?Ѭ4?%1?#^?2<5? ņ ?@T?P?0?N`?$!?Ec4?#9? y?T^\#?X?ڼ}٢b?m g?q4Zz?ڂ.8?O?|?LZpK?K?ȉz?;?%e?RD?leܠ?톃)_ ?`B?.J?hUX>?:U? ?Ј`i@?Y$8?{@@?i;h?j -(?_ڹu?Жz?Rr?ǰ?ӳѳ?2?F? ԡ?OJ?8?$W??noqT?c9n?@]T?9 2?YӎRG?Ӽ$?"֩Z?FTq?ʚ쉫?~??\?8P?:E2?ϺF?*EX ?p?'?( ?ڵfs?HW5;?H?Ϧv(?Y]Q&?پL?%/?)?㭅?(C?׌F?Ueѓr?&?^?B>?q5?Yj}?s}?$&?"Xsi?Z?Sp?ꙜB7?< ? ߋM?H ?ޠg?tJ?}pHuT? @n?M6? l?;+O?ic&?7rF]?剺c?*Uvy?;B?rN?8fr?s -? Xb?dtխ?E?&d?5L ?q?fȚ?.\'? 5Y?q}?9$\?Kq?i2[?yw^O?X*y?Y_?Ѧ^$2?F{m>0?7(?7R9 ?РdI?ޕc-v?ՓF^6?nW?`?琞Ej?äO?Lj ??w-v?&}?IĻ?Vk h?5UC?6nn?Ú?$6C?t6?ȒSm?|'䳶?znS?Vl?zX?Μ?D?Ea?B?ج5?ЫV/6?߳F&?2??l_bd ?nk?២27?y?ߞc?.)(?k?7?&K?ޮD?ۑТ"&?~ e\A?`xOmX?߸,?EW?VW?l. ?gH;h?;?A8~?,z?ʳ%(? >~?K^?>vJ? B2?Q5?f( ?~y?~ט?Ѕ<``?TUTZ? /X?{`?7DY?؅t?m;ex?RXJ?N?? 2ߏ?[N ?y4ތ?fgsl?I?!+e?ĠK?r*iv?&?NgL3?>F'?Y4C?vd?Lj$?Ҙ'@??]84\?BlWz,?kWH?nķ?Er޴|?u ?讣19uR??@XZ!?f?Dw?QoX8?֯_T?7]ձ?I9t?#?ʙt?ŻM_?ZDp?Ά9?6RN?IQ7֩?&@?C6Z?Ze-]?{?꜕3vo?қby8?ݥJF?L2?p?ߍX?f?ǼΟ?CW%gZ?1[p?1?3dKˆ?nn?;X?T?߰h ?8Á?%?Zg?`6vx?gr4^?&J?܌?O?I88??FH5??ΠA: ?e?'b?C?g?}?1>? H1?_E@?g ?Z=NR?_dd?#ڈ??:?Lp?ݯQϊ?3VJ?2dxt?n?>?ّ7K0Fb?\V5??مyc ?xޥϬY?\(Z?@m?r-k? W?ʟ|F ?́Zx,?r?c)J/Qt?$\? ]?!#?s`yS?Ė?PU_?լ ?1?$L?o ^?N?ݷ(?Wb?چ0i?u܁?It v?zA?3?ўa?q"Ԉ?݄ ?2?"2?ƊPU? -X? 55?!?⡑?X?^$q$?B i"?Y&h?ĥU?M~=L?;CI?{!ބ?P?э ?ŧ_R?xlq?xVFP?QJ?56?*C ?8 " u?9yKp?:?͸ u?^D}vp? a՘?A?Ǔ?ξ?h?Q}P?D~O?mv2?K&6^]??ERl?ai?p?m#Ԧ?9h?T3p?X{qp4?Ùx[ ?^]L?cY?oު$?ޑd"?/%5?~{%?? K8?U:?j2: ?겒[?c7Ɖ?B?V0?M\?Tո?/Dl?) m?Xb"Ү??=?Cؔ?Am?C~.1?yz'?+{ !?ڦ?m) ?i(o?ćBX?Az2?ᗜ x?V??N2=? I|Lt?;i?S?r ?ݛ>?%+v ?^b?{?Њi6?6~x)T?+ۧa?uܒ/?l(?βٜ?0%?恑JBr?NHZim?v@?ꭘY?,; ?t (7?"g.?0B@E?p?6T?8?'?ܿ?L?͍n'?Ѻe? af?XM'?^ˈ?) vH^7?:a?z%EBe?Y(?7[?97,>h?! KBn??\?9? ?&:Y?\[?o'0?c?8s.?҈?iJ]a?O?@X/?n ~_8?1q?bْ9?|@bj?痞z?ޚ?7~?[0? f?̣x3 9?뷇ݏ>? o< ?ȉs?QY$?W(?eǩk?PX?[C–?U,? ? |4?u'pP?D ?Ѕv02? ?иx? C qV?h?Աj)Z?.X?Ҙ^e?Ѳ 8?VL{0?N\P??&f?ݿX$x?y?ır&?a`]? fP?s^X?"DQY?R++?6?Wa?gRm?[?*H?Zd\?a 4?S?L#?a,!_?5y6 ?.?9U?'h: U? ?=nxJZ?^[?xV?~ ?i?DTF?Ӆv>?u:l?q ?E?ol?l?ɼ<?[y@$?=]?޵iu04?$0?۪;~?@.|ͤ?*#?\^?s?چ5A?问 o ?LR?b?UX?ݴ3L?狣W?̾]?Yb@?sF?&9~?J?[ ? ?s8?|ƩB?[d)?w9Nh?䈊0?۪[?8q&?NjN,??5Ȫo?(1F$?ʗ]0?t2? ?h?Ў\9?] nP?Ye?;Y?t&Q ?!;#y?8?0P ?JP?Mo?ξ%D?d2]?}f?s?3~?qOX4g?x22?Y6A?ލ?T?!.R?dvn6?}#P˒?rG?⬬tT~?ԁTԆ?6+?ؘz ?¹WZQ ?]%?X?ތC+?>oT?Ӓ*?BP|?28N?8%vM?C-?fn$h0?Z+j?s=yX&?COP?tn|_?nyB?q?^Oe/ݐ?GY8? ?㠕Ew ?m;?Ǵw[?v%s?Ns>?~b+?<?`[^??(E?aZ{?8?1ޭ?`?r0׬?\?zP?A ?w?( ?&?գ?Ẍ?~ X?,y?ڲ?f?,b[?P?=W!\?!0?x`6?n *W?c ?^4 ?(? ?溮zE?쇏$!?=h?Ч(?Si˲h?- N? As?C\F?,@2?T??5-V?*Ot?8+`?G}@?א/?\7??|? ($?~p?|? ?0kk4?|,}D?/ՑK?hu>?ٸ?*4L%? |?HO?%Y+ ?HReP?c[.W?p%?F{Co?A?$p?骊وIo?4~?a9g?Ķ2?ҏƂb^?{|v|p?`w?2>%?N-?OO5?dz,?aⱁ?Һ?_#ߒw??,?2?#b?~}^?ؕo?ݐwvkKT?h\?9jd?@ c?huC0?)UV?xƊ b?f ?,?v&G? 3?xft?hhBh?tyw5G?]: |?xĄ?;zt?ȏ?ۤZ?ތ4?H?kdH?A,D??IOZ?*!-h?dl-|*?U{P?烮?;X?GT2)?t?-gvw?9PmKQ?L.?3 `?9m?`w?JtN?r{?"`ʨ?C&?zU?~sa?~lU?i?#Y=>?P ?݌?꿌$%?Ͳ!*??\'?ӵ?Nw ?K>?Ҙ`@?놢?\6?h3?ch>? PZ?ܳz?ߴTj?wȀ?h??R_n?~]Ll?C ?z"?ŵ(?)c.?t͉?ܙ=?ޏxA?\Z$?웓!Q?,ǑF?S2?sW?~P?q5?Td?؞Cx:?,|?k{j?EP?l~l@?Uʃ?a?۞8R?[?3nB?t ?`^?Z{q?о?*_?v  ?kb?ʥ7(?)AD?TM@?JX0?3Z;&?m5?j55?f;K?R 4?ģr\h? H?ä 8~T?}la06@?DqҐ?l:a6?לofx? %?ȋoa?g#z&T?p=vz?繰@?θg&?W@Q?!?Ƴ}n?W?{~\?7Zj?ұM 4J?ٓ?Z4P ?(Px?B̐?ЉHc\?TMCR?02%h?&M&?4 ~?h|S?=|?zE ?M)?w ?٭ńvX?qD?j)?X D$?ǙSx?n޹P?4LG?\x*g?d9(?ѼM?$?h#p?A?e}x?s'?C91Y?ԤN?n\?~(l?2S5>?eBS?1F]J?儬[?[ ?J?ʟ5??*Kr]?,qG^cM?caX?”?}GF@?Uv*?MZ\`?v?w~?g?|m?(?'*?U!?Ȍ?=g@`?留?b/?S='?_+l?W]6B?鐨?`,$?ĕYx?YU9,?4Y/6?IB?Jv-?&?Ih?ӌ]'`?Z?Et?ygu?݀>y?۫D]?إ?Y ?+?p?jȤa?} ?7@??VQ?ݥH{lx? : B?;x`?sҮ?ɰs?r垝$?\|5f\?4SY?g ?yx?2#b?Vg??|NL?߫x?o؛w?e?PP 4?G?]r?jg?j[e?A?n2W?Դ0?U,?ѝ{5?z/?[`??q9=?c`?ԝI?Fᜒ?DeL?ãL=%?L`T9?0?ܗԩ}?+I?Sz%~?իcGMd?ؚ ? E?\v??3?SxeQ?/0 zx?$/a?ʩ*s?a~ǒ?̇W?Jiյ`@?}1a ?F^?er?ףۗyN?4?ڛe}_?!/?}?ò]@?9lp4?R??X?vA?,$?0N ?se2>?uo? fV?w|??c?ՀAL?ٖ?Pk?؅! ?j?E#B-?L<?W1g?n/W?䫬)4?a}>?elb,?Ԁq(m.?cSYٟ? Ohx?-㶷?P!#.?VO&k?7+,l?G{|?V>A?R0?$`?'{ ?7ް?Zk?\_XH?4}}?`Q??y?微.??X?UUUM?HM^0? Ft?;wR*?9FV?#?uc?G캂?7ˤP?gS? {O?16s?x~}S?!ت9 ?T c{m?q|t?QE^?nW??Gq1?76?0[c_?˔,?;_h?h~ut4?Ӌ?ޓD!>??S>X?VKE?ü2-?A(q? $?+k?R 6?^]e¿?3?kn?>K?= ?vgJ? ?3?sk2?Y X?Mj?! k;?e Q?16\?ly&?-(?8h?հ@+J?j?]9y?! ?"J&?K?R߸?se8?㚬aQ?ѹI1?h)?k)+o?b?vL-?h?QYKb?@_M?2~?A3?9Oc?$צ?.=̢?Š4#%?{( ?5W? f?|?,?8?dG܋>?c4t'_?/h?q) ??d?K6Ap?M? VN?G?zS]?¤_?0V?<?Ce4?< @)p?d?`?BkzW?C\@V?>[?(?F0:p?<%l?^U?9? ?ނ ?,J? ]\?ઈ *?S 5l?>L?cċA? sP?ڡ2L?4K?9pI2?/?$ڨ8?nVm?ťM?P?i?o>8\?Ʃ,F?gm{.*?:F?.]F9W?a_S?4l,? 0?鞯Ǵ?1?E?޾CL}6?g֊z? 5?;?&:?5T4?hY?Ȏ?7s0?fY?^nL0?{-t ?O ?ޚaq? ??J^/?/ ?4lu?}b?ZM?Tp?D|?I1~E?n!?mZy)?j?,?c,?|i\?|#L?L_p?„y+?PEB\?ю:?ȼ?9"k?ܳT*?N?ƚ,e ?F2?)J`@?̫?u/?ax?gKSQ?qWs??aw?ԅaYl? $? DA #?^< 2?Зy0?z(Ѣp?,T?{F̓S?N) ?@:h?c;)?ȕls?i)?(PX? Q?^ Cn?Qi*??+&? %`?Ȏ!? l_t?B?B)0?FhX?4?ޛBZ3A?k?۸?k}?. ?&ʹ?F? 9?ݝ?f?^?Kz] '?]al>?ں8?՘=?ٲl?C?3?I_՘c?Hb?v?.HX?B~ i4?wVU?h$l?xk?–'?{l.`84?Qp?@LȦP?N9?ӄG N$??́lUl?r{kM?\|\?셸J?t)'?%v9?,]?3Rl?O{+?踹? ?׆^?6w7#?˩Uʆ?қCO?1?{?VbC?0?ۮud?UWLi ?;HdR?, KJ?cW?ՠ m?hTv}?.x? h ?dǃrp?H9?}W?^?'X?Uc+?W\o?[$_?kP?WWP???f=-~?p?֦W?(Q?1@?ؿ>?ơ@`?ʼn1J0?/ ?˨??:7^fqn?۳b?]h?޶ t?Ꚛ?ü?؋Ft$?#?v)?(AP.?pR䓔?DzA?刃ݽ?P=U?pE?C4?␁L: ?_Yu?fWn?`g?*V?O$$v??{$ P?伊?{q?2ej?W*֑?YZ? ?y(?]Z;? ?h|?rؗ ?Tx?ӂJ 3f?FNb?lUN?t`? 4OUy,?DLnEaY?w@P?}#?o8?(mt`?@R?(R?Sl?)"@? d^V?':3 ?1)?֥Z4? ?p.ctJ?tLix`?V)??p$?{ M; ??? g?tn.?cB@?;M;?җ?DZYv?Y:+'?=Q$?p?3/S $?d?Ѥc?eVp?6`%?5$?N?w.o??I^^?XN8?.e?,;?]W?ksi.j? z?p}[?gx`?6%u?wr u?d??Ԕt?Nfʃy? ?۵j?ܚ'Y?LV?ayL?AF??j?9k/?ٱ?lSYEm?䓷f?'9i?8,T=4?ص?{xy?漯*<?|1?,w0? @c?0Ϩ(?|2?ꍝi?a?4|/?ٿe|?^?*??A5?(y? %eT?ֵI?[9?D?%sX?}*wd@?PyȰ?#G[bk?^#?'h@?d"~7? e{GP?e:ƭ`?u$^??]P?\xf0 ?MKX?}?ۚ?/?`}sTx? p? ?p; ?^M?De1?㳐5z(?G3\?ȠxN?г[?P7 ?A!?J?Ĕc}?&D@?Ly'L?IU?4x?ɡo;?C?$0?ذF?Yn|?_#?g?໊\*?Ň?9r!?J?쾟^&?3+?/W?b}l8?-?;?|B?xu#jv?[ܸ??̥%4?HL?li?7Bx?K͈"?6N$8?P*E?thW??!`- ?6?xP ?*?KҲ^?-Yo¼?7%?4?Y3 $?ٴ:J?FrC?=5G? D]?S84??rӘp?ջnh/r?)b5?Ir?@.Ĩ?!Pb?݂?`8T?؛oTH?ưf%?梲E*?Nvc?E>Z{?c̈?[>@?${?:18?7 ?!^)?i~To?hJ ?H1:?XP0??e?ɮt?Ϟ4 ?:S?a?Mvf?OZ?HV??"KS?ԇ?ӑF a?X yz?)xi?@N=?r Dz;? '?2B|D?S,?n;x?j;`?W<?KN%?0a?- ؾ?䛝+c|?LU/?˃[44?z@?稰7v?ݸ` ?7?{܆T?O(?ӑy?٣ @?S?p&Y?!Ow?lx?7?䥹~?l?)?!s!?ݒ?X?ZJ?C?.?(?e*'?֗CP?͇?ئD$$Y~?ϥ偓?JL?ߓ`gN#?hzo?p?A?N]?cDB?щؒ??⢋F\?xJ?J?ïU?Xb!BG?c3~ ?xFz?"NT+?=%?.*6?Φ/?،Q?r$.?+6l?>e?Uđ?PC8p?`=&?5W$QP?;]l?*t?]*^?M,?yiO?ɒa?*{_?aѝ&?T ?fmb? >?h;?2b?9`TU??M9h?ܰڱ ?]9?߳'[?Y?N?ή`{?ë6l?rHL? -?蜭~U?;U?"L2zd?B:5/4?C )?*Қ?-kbzX?}Eܞ?T"\B?}#x ?U"?n.,?Dݛ]?Ԉ 쳈?E;?Ԏ;XZ?QfO?5ϖ?5 |p?ƨy?*Zv#?8! H?#L?|?AY(?ɨ 3?#?NO8?6B? /Z?0j p? _ahi? =&!? 8?05 8?B\X?a(:?tS!?皼[Tg?bZ#C?Hzh?X8?41?mEkG'T? s_? SF?Ȫ 1?$?j?ؐ?>T?-(@??İ?đȥP?C|?\Ƕp?L? ]?mE??҆?ɿXFʣ?5j?*?<^?Af n?M^J?h0?Mhx?oJHt?ⷢ5?:Bf?P?zlf ?p8?/;?S?UR?kpߤ??_k?5lO?48?ѫЂx? *h?`m9?h0C?>M?7?Ԍ(?K_?/Ho?8dH?k$l?x \?wh7? vU?C)¼?>} :?P'?OX?: {7?@?<1>?_?novbf??@3?GR? R?ܼmW?겖`?Ӳ?Ҙ%ن?ωU?ՁX?.2 ?IA&D?^v^A?ȺW?!7?_{?]Q?و?t?WB?eil?ˉG?7-?`?KR_?FI$?9 ?Hک5,? ?Nm""?ק[j ?W8?@,T?ʩX$? ?\%qH?ʸ_?}:?Ļ?98?I;a*?u{ ?yq:L?a*tjh?gd-P?J?`Mx?뭧+?H?k ؑ$?? ?#T0? ?Aڋ?TX`^4?fv1d?]G?؃?Pkx?ͭ[Qd?x) ?m p? ՠ? iʒ?a?V䙴?g+y ?v?Xќ?~b?67_?ٝoc"?J9"ň?Nx?db?O2B?gB?Z?9?B?3 3?Xț?8"?FZ _o?I`H?&X?m`?S^|_?s|_?N?㿁N[? >]?,cr?I?[?r6Sr?̕:?PZN?b?ې(?E!?SqH?l!?#!^?dE ?v /?**?Ǫ{!?߃); ?YL?Bi4:?Y?@'M?(W;?wPO?{MT0?:NAw@?&z?ots?~?7? 0y?I=d? 6Wf??ԽB>?Ө:?V.?܉D ?}\P?+#?8F?ؒA\?5_F?@?ѯqm)?>iY?v29?y?`?eHݟ?/hE?jFC?|]Z{$?ԽC ? %є?uTW?&Zʒ?쌁h?#~Kp?Ҋ29E?Ґz#r?bb?H0eIS?N?2 )?X?{y?"??ҍ")?Mo?<:D?*OX?z!?Wk?v?bS?H1T:?\0R?R/[?LDSs`?'n?*ΈP?63: ?糌u??,m?Ey?H?_t:%?GVd?Pt"? ڒ?)V{x?GM0?絒ob?`O M?iIzb?w8IP??gB!l?F5?#aH?@%:QP?\_?GV?GS?X;p?V}c7?%8?VNx?a_,?;V?^J?]n?/fm? `?IQLP@?!N?@m?¾?c.F2@?øm2p?*X?߶}?LUF?,ぐ?lߤ?w[R_?< ?2}]?x/^v?6[/@?-"R?l3?C0??^hu|?' ?jn ?f?UT?״d?zF?đAD= (?/kk- ??UR)c?:Hv?on?a ?V? ??(o^ `?ˀeA?z?1A?7?H뢶r?z< ?뎜4>j?Ob̠a?b??@`?V ?ƚ=?><)?0Xd?G-}?(*?[10?NN,F0?3s3?߄R Ȗ?mʦ?py?,,EŐ?׬Z?ʈy{?B4?Χt?^ |X?+ ?;-`Px?P?W< K?CEd?$k?$ Vx?s|?ǫ?O[ux?ߴVYۜ?4M?WiTV ?%G,0? Zd?sm?Vl?a?AzM=?S3?o???Zi^?{$vsp?|?s?2y?rٞLe?&?nm.?Z&so?S?%??ӡߊ)?Ie?fez2?uZ.ݨ?(m~?퍇{?wy?UU?O} ?'l:?wy&?ݥD%\?d8k?Q$?ҽCd?I•?Ii?,"?K?ȔDa?f@`Y?|.Ԛy2?jc?!pu?#/?҄{Á?v>=??zt?bzب?S29?R?X N?ZRb?!ш{v?IL6?T޸?Fqb&X?|*~@?ѧe ,~?2pi?JOd?odl??:)lA?^5?|_Q?gx?N!?Մ?wF?Y%9?څOߢ?&??df?8?ي5?z/4 ?_5?Q+??Uf?埬/>4?GbAR?ҭJ ?H?˕ ?nP)0#?ۛh?ш?Wӣ?;"%?̭IJ?H2U*?-?{?w,[?tH?K9?щW2%`?񙙔??;oh?t2?,#L?E5?Jf?8o?3P?ʷ ?ɭ ?zN? ,?ZoI?߈I4?\ ?ep??Y?DD?3J8?%?3Z?O.? ?/hi?ɝJG?`O@?اQņ??R4?H/?җli?p6a?7?.?} %?e/. ?%@~?as߭?၏Y?pTz?ΰ?2`?W7@[?g{.&n?լ,nzH?#{?~XT??OwO?*TP'?9U?⡅W?ƺk۩?jx?U?V?nGx?#37??ZK?tY*?WP0?Ԇ3W6?K!?P}6? ? `k0?C?*',? W?YPp?c@? ?]&M?4mb+?j|?:?\a?:?hMY)?L%k@?\['g?K?!xH?48?|u?h6 ?l>S?P;,?EEU?oYn?֫?Txi? tm?ָZ]? ^?<-0}?]??_c5&?}`V?*~.u;?JBW`?Q?3 ?oXߟ?~+wx?E?q?W#?7;b?Ei?D1:H?e`!@?/(!h?\k̺ۀ?!Њ?|t?[V @?ȉHR?ɚ?éL?)Sw?>?RE?3P?ice?̒` \?~?\^ ? s?J2?^o?X2?)Y2?ywN?W>K7?#X?=twN?= E0???y?ez?z{t?vQ2M?SzR?YN??" ?炆_ޜ?}Ǝ?G ?5A&8? u?U(?Z'F?W DR?mK'D.?w?>?.d?ǛD- ?hS8!]?}X?I?\2s?iZ??Bq?|?yci2?m_?Ƈ̴? iMKx?t8?c??t? \j?n@?u;동?wi(/?8 .?`D"y?{=?ۣ7~T?u+Z?sb?bv?FO~?!vzܸ?~``?;?1/\?T?N?ot,]uf?dRN?Ę??i*?ųD`?\kB?.k0j?a!p?U D?*5;?GB?cW?؜I?^oGh?~?ghm?9?a ?;^8?犬?SW5? ֹ?Rjv?)¶>?P?wXb?Z"U+??#!CD?߄?ÌTYs?B5I?2Xp?Љp?5?wyb=?ὯJ ??⟪,?JMg?zoǖ? -7D?^?4B訃?ڰJ?ɭ<:?u?snR?)̱@?`@#ڏ?iA@p?Ee?_:j?`n?u?3p?yg?HS?SZ?N?Lw? 5X?!8@?C ?Ҋ?=vw?J_?P^?ؠ\x?dc?ض]?޵᪜?)^?ɉ>?#d#=?j ?ꗷz`2?نi%2?3n? ?`F?67G/?k-Xz? <??ւKt?)xL?%??է"r?ԈW@"?|L?}Q/@?&x?G?}E;?u5.?$7?(n7;? Шqx?c#?5N?Є`1?v˄?"6??#1?ٚC;?Ҵd?WJ₂?RӲ?o)?O?xh?!؏,?;*? c?)KKތ?C֡?\j6I?(0b?GG?У5? ,=7?(.o?ȋ̮t?5[ak?f%?2?u-u? 9?Q?5?K]e$n?6tq?%f?O?&2?{r>??I?WW?%׀?Oeb??إ N?e$?~f?>me?ܣy?h]^?v5$?Zh? "- ? h"?鄙6,?UY d? ?լMP?ԢȐ:?8͊u?eҒ4?.Z'?W? ??g?+?}/?шQ?\?ѓo?矿?*e?;HֆD?-Y"/??c?`r?d|?V?Oڊ?ۡWyy?ھ_V8?DTW8?k"w?r#?)LA?Ō:Np?83? QJY??ؕ'?e?A-<6?獏:"6?ar?D+&}?53,b+? -/~?=T?㔇r?<L?t*?Οj?y+?è`V?ӅD ?uZ?2?ۢ?Ԑ?{o{?E?9q? ?ho?$?{d?9?<*5?p?!o"8? |f3? ?ѷV.B?(&?f @[`?ڞ1D? ~j?_Y?5E?&?ҫcW>?Тy$en?*?]!~Q?n-? rZ ?+l?Q$?ڷ x?~K6?@ah?uJB7?P? ?歷L?:ȽGp?[>r?.#gI?c-M?@?[?A,ۀ?5)?#i1K?ۋ#?ib?ҦMD?牜L ?R܀-1?ᜀ[?$j0E$?T#[?Ly?% f?ITl`?,H?~D?ҕ)?Cjh?e ?0HGt$?FH/?鲫I?n?8M?#!4?㦗*?`H?O0? &fp?ߧ;?H ?1#K?Yspv?0·]?ҿp?K1?{z`?]p%?F?`7?m|)2?,Pu:?1?P3_?ZwG?1$~? `?$փ>_?7,?#$Wm?Tf~?|hz? =?F1R?łqv?CI?^rx?+jil?䷈DJ.?˳D ?׆+?c ? Y?WD??;pK? ?$+??ī?akf?V$]?~(t?{)? Mo?Ƀ?P?|n?L9>?EE.?2Y?}(`z`?JR??80?a{W?E9 g?T? Cbf??~vL?#QEZ,?;n:?E7??,xH?@@?lE\?Wc?^Y[K?j@x ?/1Kx? (?8Y*?䑣?(4 aV?#JIi,?"U (?t6}?׊q9P?* ?E?G g?IISnư?vh?P?S?S`?n?sp:2Y?Psz?J|?%cH??C1?Ea`??-+?B+{?үꘀ?_?zW ?-#?33 ?/J?PBw&~?vPp?뫠 ?/T"{?k_+_?} m}?߸>?y{~?s׫? P ?p`(W0?٥"?`?CYV?ۭi(??"R?WjZ? &|?7A?-8 ?ȚFx?Η?՗[?V?jAX?.Ƀx?!EV?o?'-M?? ;??вK? @?E=$Y]4?*"F_?ҡ_:?`T?ܲV?K?YXs0Z?b??3C?Jfx|?օZ5?8*;*?Rx?,O?Nu$j? Xu$?}^u?&}?%wMy ?O 1L?Ҿ!.?r}Ө?9+?CNR?Ç`?@ ?gf?N6:F? ?ʊ?;r?CĄ`X? ?۶9҄<G?+ ?"d{?ht@?pzу?:>BJo?f~x?pʕr?}y?»K?۞tD?젒k?w{?HUNq?UYp?p3y?NQ?t?Ag? 4?4?+?5?.x? g^O?!I ?~?Jk?댍)c;>?hbb? ? X?DA]4?Vך?x?:c(k?NDŽb?L(M?ͥLh?+B)U.?߷kH?7vL ?#?I ?Y;9?2)TH?`MJi?t~ ^?x*?-?D ?ϧt?Vέ>?۰F=(?.?(Ir?ثǨ?f2?[3L?Ӭa}@? 1O?ZRp?,?#`?? T?n ?95y?g?O*?3Yi?ۈ W?vŠu,?7:t?aA?{.c?:Ŵwq?o1Dɣ?S_H?5?``q?ʾi?ձ'?KA? >?+'}?|\P?H? b ?ڒT?ЩgT?1۫?| B?p5%?ϣ`?7 ?|u3ڻ?(B+?Ȁ?z?&:s?`o҈?ꂧ5j?k[?78?n&?gF?ߣ>LN?\w?^t ?v?4So?W)t?; ?) ?ޑddU?UYϣ`?k?O8?R0?Ҟ]ݨ&P?)@@?`w?ދ}?s ?Ł%?j`d?ѳh?)y?Gi(`?b,Č=?Խw-?խYH?:sí?9 ?ᜟ`]? {^? ?a?̜?їC?š@E?Q9N?@?р?ë\?ѤR?r.b?dޞ?H"?r.?J^?G?HӪ?sPYsX?l]?/j?z@?9Hd?1Ơ> ?eEw;?_$?ۛ?,&k?)??d݇Hr?rˠ=?x.B ?,]L?I?|ae?Ė7?t?!ٶ? #gB?506?nݖh?7?ݘ_1 ?x?ɀ?M35?ؤL?h@?z4?þO?["?C?e ?PU?绳H~?t2b&?'l\?vH?ӑ)?c.2?ʁ6,-?8J?;8' ?҄K?X,4YP?Ug(?ܕ72 ?M?яZW?V훕?PL?f1k???Ep?^?Э9e *?F?Q?G ۉX?hFGw^?!?KڋԢ?!4t?&#mK?̩Nd??D?i?/ x ? %?$}h-?ࡱ2?e-˝?t??=㧜?|u 6?)bV?P?<@-L?>;Z?@1?H]?yT?Ce&?ЫSJj?鲓0q?d?nQ?{Q4?5ܫ?5Y~^Z ?G ?Db?hP?~0ҍe?}H?l)?f~? ՘? ?ٹ6 ?6P??܀²m ?ӿ x?^H?Ay?CS?D?/NSB?1-X? D<3?և65?۱`?K.&v??kS8r?Wp?ގO ?(j9PF?%4-I?K?&H.,?>H ?U!d? ֚X?Iۥ?і:n?wӰC ?ZSq?q%P?Ҕ?^r?[1?$9?࠻'.s?خ:dh?vx?{+F?҃r6?Ҡ =}%?v-?ڸ?{?{ Q?~V!?텻+)Q?uy/?5 ?P (?XXW?l}?nu`??e??adB?X?2-I?P ?'^Z?"~T?`1$?"u?1d]?~bIz?5`?N+?۟+T?륂M??䅄yS?5ݿ?Ø?|?ܚYz!?GOԮ?ڤ'N?}}: G?@?naM?wR?̥?~#?HV?W?ӔIfX?Us?y~ك?մ?ʯ{?-d?⤎?丞/?;l3?z?Qqj? ?WɻO?EQ ?1?rEa?jWr(?E:`?hߠ+?rv+?$΍?jPx?Z?Пh?Ͳ! d?u ?[H?V? p?`8?*_?nb??Y??? ?azi?i?b5?#QK4? <~?m ?d)0?y^?V&:?=?͒F?l?㻊|?y*?z?vRQ?ևtH?R?t ɴ? L^?5uh?NCq?Ez۟f?bF8?FK v?!]]F?r*?{bpy?u'?l?ݎ80?"m?cm?С<p?h?!??wy[?ⴓu V?_T?2?2?}q"=V? *8?LX?ÉW?aC?A'O?e0.?xS&?%T@?2Q?"B?݇cH?Kx2f? b!? cgkb,?!e?/R"?u?<x?[2g??-\#>?h??#~[(?_En.? ?4@?qSm?1?28W ?#?K?`K?{Nnp$?# ?ֶKyD?"'HD(?̒Hi#`?e ?IҢDx?ʓH?m_1~?}]b|?ԕ?y?(]Jw ?Q6?c??_-?L'?l?@L J?}SY"G?y#?ᆪ?d1|?MR?>?V&?*dΑ?Vym? z`?bQV>?h?Y=3@?&'D@?rOJh?隇?XP?_Y?^? /-p?dJz>?R[J`?V?p.?־Qߪ?` U?>S)<? v? ?"?Y]e?u)v?g@ 1?ӗ:G ?Gx8?5~6'?zIFX?Ut?i?v:?ۍ|x?}?ּ؀ x?.]?K2?3?Y*???Ա jn?_CM ?J}#,?༫?2FJf??aI?6h+?쐱Ի?Q"?͕0T^u?'|J7?‡$`?/?Mq?Ѭ$?Ľ#nY?rD Ԧ?Nk&?ᥣI{ ?X_Uj?ۇz?e?/J4$?~y?2V28?]{o}|?';?.a?q)?⶧?얹?pD+&?`LV?ܬ'G?Wbd?1u?KT,?:?9=?浱?Iz0?dt#?%\J ?]{b?eմDB?뵰i@U? bq?#?Ar?4@@?:?І?{?1Rc?١F?ف*?mel?}P?c$?,?rչjm`?LZ?_b-?E J?ȡ ?l=?O?_Zi@?ĵ(?ہU(?DJʧ?ý/RX?Nh?ߤ(:?ZRZQ>? QP?㫗`5?{? ;d?^R?V? (0?9s:?ՠK?ˆ?)?wY?#<:x?:?ZKr ?=*Uh?2[Ɩ?r)?$ C0?L${r?J:?3)?^?엻d?% ѐ?Ǘ0n?Cꋤ?*]c?@+Xl?濗C3?᜜oF?'{?蒽T?h&?ߩ*??C?Af}6x?}R?s'%Gx?ڤ5`?SWG?Q?ux?_Lew?攍fT? Tz9?dr?蟂? *a?^҅? Z?+M?vmG?=Zf)?.DI?Fk?Y(?1?ܢ.) ?t ?D?{+!?5J?zhW?ǘ?>?˛2b?4s?望3a?[|?D[؎?\G!?-3?A _q?ft ?o&6m?U?E]*?҂ftbT?9)?ԍ] ?u2?ߺkiN?vWo?}ɶ?Nqp?Ȋ mB"?Gk?ې۹:?Tx`?Қ?ۛ[.?܅Bmx ?^=JFk?̰?̢l?-Y?FSp,?H ?M[Zrh?{8?ٌJ8>n?1 ;?TL}?l,l?ؚ?Cp4?_6?|c^?G9nH?DaC ?0D?ٕ ?_nn?0II$?=6?f?ѽF:? G;"?!$VC? <6?L?#?b4Tl?)RD?v?xV"?Uq/? ,?Dگ?q.?:<]lp?؈3?ţR;?# ?v *4?YT!?t?1c?lz??u?"?I?:?!2U?R`?YZ 0N?hC+#?ӏҟ?n(? Vy?$aN3?ǜVJ?*?سУ#2*?ۛ!J?i?yipP?ιj8?.*?ѧgd?#?" x?LT?Rv?Wo3?w?ـH?"QZr?XS^?`??YƈF?]g{w?EIt?DKAN?/IMx?dF?ߒ¸S?Mqb?CNcڊ?~?ݵ?rØ\?ƖoL?m* ?fw?3zl8?NOV?[nYy2?G\%?Hj?ܢϚ?>G?1M ?ώ4~0?m&K?<?ޮP-?ݢ?ӎyy`?Ɂo?ܐ7d?*Tx?] ?Z?;?RId?,C?Q?q?ꦤ?<@%?#r@`?A-?4wp?ؘ}q? ?C?N?de?_A?x%T?b?6,2?7?ɬ-0?܃m%&?΄}X?3p"?Nr.?W?WJD?+Y+?He/D?|tcX?7Ф?AH,?i?YM`?Nb~[? u?`?) ?㟍?N?{< ?5`||?9?,{\? ?]L0?ř?V'(q2?^JN?OoA?,֌?ע6 ?ߑvMce?gzq?O f?)bi?_0E?~[?{y?0?ρ?hyL?v!΂(?w(?~,He ?b'm?7?[5x?$<_ Yv?UTCj?fe?+{>;L?} svP?=T L??x?V &?ǬYOP?ffo?މQ?Sn?sq}? F/Z8?1?4?Ď?U-'E?愳@S?fX/!?;?M ɍ?{;?]=߶?\?Ld?%WM?*'L?5HǺ?\8?Cu{0?P NL? 5n?xz?蜋M}?sD,?Ď?Y,a?b~?N:&?:=J?WxSk?^xn`?O??qkCB?G4.?:oJ?ˌlҳ>?AJ?О*'?ch`?x'`@? ?{О?[\ 9'?TF\?7N#0?Ӣ ?rߒ?p?D1?d]?(I?j<+?ZY`t8?& ?.Q:?"?^Ml\?Li?ևr)?>?1H? la? {na?}Ip?a;Sy+?D/? 7?u?D3%?U~\?%Qqr?5#1x?յa۬?n@Ӡ??lF;?I͡.?uA? /? &/J?ѯ 0?/[?CL?5@M?kG2L?\z @?NJhe?J9?8ٺP?oޛ?ĊSG?? 9 h?K?MR>?z ?TFϯ?(,?l?5hE?z`?"$?`?f,QP?,ʵ(?ˌ ?KF?=?|f? ?W?-? $L?=~U'?ғjP?̺tg?+Y팔?VM׉?٨bʪ?*~wK?uT[!?s/(?(Ֆ?ݤ\ȼz?ʅ]9?Se2o`?9U0M?Ll?,,M?rh?ȵ}|?ϐkHx?;z?^?W0?[c0?{dLT?U$G?jQ?Q?-70?4H?@n? ! T?#t?cx?DZ'?߽?6"\?8yVU ??㠎qO?Ꞽ?3H?d'?Vӂ?i٫?C`??;C?Ìy?AH"?' :?"J,S{?"'?3aL~h?>>3"?;?xQ@G1?Ŀm?`?I\Z?; U?X}0?FdZd?Dc!gn?FT?7\Ԯ@??1Oau?*b\^?HN4?? X.&?ڃN?8xx?2sr ?B6?9{P?䂥ZI?4.VW?\N?@?<@2x?Ϙ =qx?Hwu?xt?ً^0?ceP??*3iP?YSQ??ڔU?svLժ?#BIB?Ms?݅˷?Sv)?HU< ?3P/?} Om?hZw3X?CPJ0?nܠe?L q?i?n"BZ?'?u}5? ?ws?G'(?uR?ɝо?@O@ 8?NJm$?-#?;?ߦZ1wU?6U??[] ?Rd?butv?غbF?s8?+c ?Wtp?D?w(?෡4P?bX%?ԣsX?ݙF#n?"?2׋l?%8? bb?nF?i$H?Ԫx?_{7 ?xL?zػ?s?׬? ?"0?dP?Tᴴ?[TE?U.7S?Ֆ8Ě?=ZNhI?Ol?S9g?戎?N ?p#?lN?w06?"?j ?w3??*q?!H?D`?B ?u? UOD?$?)?Q6c}?lp8?l  ?!*M?j?c,?!`:?؍?x/3?RV(?橝}2?%&?DQ?GnvнA?傽{ "?K,?›9x?핀YW?|+i ?^'A?ca&?31[-?b?Ƣ?D+ qw?\ ?? `?A?]<0?)K?+`?/l^?V?ڦ3l?@[Ҭ?g*?2q ?T[6?B6?Ud?aM?Sv'vL?'"ƾ?bnrn?f@?v?$1`?_jڹ?JVG?N sL?B/F?u?Ň=-?/~K9?(G w?Ӈ4?Б?G?e]X?eS.?nxA6?$mFE|?O2׈&?uS0h?Ƚ?ɘNW?V ? ??D7F?ہPd?6.9?nB?-)CM?k1?h^T[?'?nV-h?҂H??ࠀٌ1?%Q ?Ml?H?. >?q "?f:t?Ωኜc?ΡBI0?߄Q?sH?︧_;?%kx?^_?Ph?RF ? +>n?~XM\??ނ?n).?>>ow?Ǎ?I/?_Y? G+?[ ]?%ʩEm?itWd?&jķHS?ݖ@?EJ?8qL?u5?Zuf?ێ?nF1?0B?FN?S$?d[s?GLt@?.?A2H]>?!2v\?T?Ònsh?py?La?zS?&n? Sv9?z?"P?cq?Z8?f? 61?Јhw?ߝg0?R>D? v?[@?Xo?䏵nL?E ?M>?zGP?v_غ?w6j?U{vu>?KL@?P9$?!:?xoߗ?ݶ;Ɩ8?v?3"?rN0?JFƗ?|kp8G?gj?Ҙ{?ΙY?K8? ?`R?s .o?V?F zj?6U6B?"ck?Ιw|?ݠuOV?4-δ?Ⱥ ?z/Q/?!1?R2b?2p?,]?QcN@|?Bc?Џe?p?9'?%xk?27\ױL?}?d?Hv?4>J?w ?8%?ڱ@ 6?}lKW?>Z(z?㋚7?NX<?^lR?ԿTR?Xb8?Ya*?c`r?>S? t?S(?AB?z?ƱI?F?8u ?<'-(?fy?n?8׭?dt?7/{?oSA$?\V?lǼp?ބB?ئp?(&{?)+ ?c?@0?e.?;m.w8j?ߙ8뿸?A$V?Ҁ ?W_?zᯌ?Tp:?"!I{?hv6?~KL?rO1?0 (?ʃY/?% =?[)'6 ?]Ǯ?鬒?4 K?ж ODp?o ?я\?&"?KIF?:| H?{7u?Fwc?@_D? _???P?GT?Pm?&n?/Y?xD?n ?Zbn(?5g? | ?ٳKu4?@IR? S?彪Bt?K?:_?Ƌyop?zxH?Kk?jĢ^?Vsҵ?,=̽?Az5? J9?@ |`?z_;?) ?uJl?g N?τ? 6sF?מ|?]/?A6?Է.1?=4`??{?3?Gx?~ @?lY?Цc>?;9L ?ѭ;[ݲ?NKŴ?k?̢L? v?aԛ?̋p ?+? ?9??bU?-9$/=?DL/?ׁ/N?zA.?$?xS?hG?Z?bQ?} u0?x?d?=VF0?dX?H5Tq??(?Bmmt??`?YJ?? ?S?01?Fx?X2N??S|?&iX?s?M&_p?ݔ 4,Z?HB%?% ?}7ݡE?2Y?ܷߣF?vk?.;8 ?8Y:?^?-ΞI?U*? E?YnƲ?(?ӝ c? ;H?*g?F:?6lJ?ƣ?߱ғG?Ll?2!ic?c ދ?M?C?B[`)?NUP?ᚉm?^?藸!!/?sBt?ԨIA z?N m?űo&T?t?n Bl?y8d?<1lS?ڻKZ?( 4?HP>?@?7D?Fq?cmh*?;Y#?٘G?ۏ h?}XR?V@:?暴ž?| i?$؊?=K%?y>4?)̪X? P~`?o$?W+p+?O;?)?1`k}?ϡ? ?{,z?dh?uX?H?jV8?Fs?ʊ?捯:j?\F=?eBտ?&C,:?؄,*8l?ـJ?bU? l?ɥ'D?XD26?G?N:^@?RCs?7 ?޴ aP??Tlܾb? ,?ZB?<_b?ka?fU`?T?ORX?լ?`?e׉uz?ܕ\?̕'B?곢?WA?鋝}u/? ?uZ%?鴝?dS\?Za$?T3?m3 `?tNP?+JȐ?? bc?MuH? ?KHBҀ?P k?ʶqG?o;[?%z?7B6Px?ڟ?sj觀i?Q2Фx?|?Е&?q t?Ҙ5#I?w??xlgU8? )?1j?鄷Ć?Y?iQ7_?"?א x?l'wJ?9k^?[H@?Ԯ޹4?M٦?>,9?KTx@?,%?A#P?I?&?%6?ΌO"?\?˿GdP??d7M?H@',>?p$j?z?irY>r?|u%?Hr?`-4h ?~BKP?`? ?hjc6?:I?tHVX?#@?D?2s0 ?mU@?JK'\? #@? f?G0?ۺzP7?\:? sh? mx?uJ|?eL?lV?ݩEn?ML2*?ކR?x-?! ?vh$?܇崥?J?Rm ?P0x?,?N?ヌA?^d1J?mUB?ղQ|?l\?cit?0j? 0?q7vUx?&C?mk?1-?E;C1?BeVe?>?8ɶ?:Ȗ@?x8?-?:?嚁?8a?#?߸Q6K?A?M:?DZ?ǁw?0Jh?^A ?m\b?擃 ?M=}= ?-}5P,?`k?1z @?+# ?Ă?ˋh1,?& r?%Qi?׀O5p?}(U?M~Go8$?B?FmB? v?WK@?Ӕ.n?&rX?#ob?np?تh?geŝ?vMY-?̻q~u?掐Oik ?Η:e}&?ѥG;!?š3o?Ș%?kM?3?ՙS?I쮤?Mg?H0D? `F?ft?R?&#?U&?֊V?ձb?ĎgO?1vT)?+!$?fe?ℕ6"$?K?ԫE[@?sgS?=RD`d?F!A?Qk'? ?փe?_?JwW ?ko r?m*?-?ɿ(l}d?%;?p*P?+$S?g"??jI;?|?›?Ǩ|l?p??ܑ;zB?چSi?6Ȳ?ۏ$?Z?ky{u?7D?8ŽE??оM? ?\/}h?|Ba???ℨ@?Ǔ4?Xp ?mWw?=K?mh ?RfvT??#M^?? ?>]?~5?0?̡Lqx?.hI?ޡ?5"/?;36?;Ǒ5?T^,?KlWr?}ja?T84@?䶄`D?5 CV?J??5{A Y?L:?Ҕ?!z ?줼*c~?]N ?تNm&n?m2? "?gx?O?{icġ?B8i?(Gx(?X5 ?"FKg?gEoK?g9?:?:H.N?h>\B?? R?="v??" _3?۲@G $r?֒#H]?ES?o?i^?WM??Nlo?%`R ?Ӧ?La,?ê /?޷P?<;?AJ?<Π?U7?k{hV?tì?Z\`?ؒȃ9B?Q!Τ?ճg@f?#P B?Up0?ga0?T$`?޲#8?ܼ6, 7?ڴ8?]S?|v(?ir?1Ә?8y?46LR,?hg}\?t&p?}6Sf?4!`?,?ͼ,j?^ }(?꟡a? ?`7b?1 ?i p?Ef?I0?蟞Q?X8jM?d?&@?t?l2?LY՝?> < ?!?+T?4?kق(?CS^?A&?"dV4? W?"h<?3zH?A?Ͷ?q'8?/IR?$LO ?IM?K8?̄bw~t?T? /?['<,?ˈ$?+x?p[??%??>?dRo#?Q!+h ??E? NX?W{?ô;#|?0*?踄W(?ˉ?& `?|Ϟh? 4} ?DT?<?f?r =G?An?)%?R`,/?[?'ח?,w臐?Wu?*l=?=}M?ʔ?OC(??1 د? (S@?&8)?:?X?k ?XX?˃ak{p?/HJ>?"gXU??@_.R?T 0?i?*Eو?CŁ?{m?ע}iH?Be?r ?9+*?,Q ?6bx?b/?E)O?@Oh?-cC8?T`?"ם@?^ `?б?;?.?DgՆC?؊>G?ިu?O??zqu?;Rz?nco?iz?o?}B?smSM?ׂVx??T῎&?P ? m,8?ɽ?vЧBx?̀E(?!'x.?Ӥ \?3|1?ڳ}2*?vѩ?}"9?OS?ʅ< ?wB?7{w?p ?w?a]6?_e2?r%\?wo?I),ݶ?ˎ?̱cܠ?4%t]?4Űߖ? ה?э|+??< "?9?̦_?kG8?=Ȁ+?}])?ގe2r?_Nd?JPq7?\/?AQ?W\d;4? eD\?:!X?:̖ ?PLA?ZP ?e4>?A_T?|,1?T9ʂ?1 ?11 ?팋! ?dW$?cB>?CEF)?lZt?2f?Ϟ@?(?0b?$?$,h?ѧC?ђbH?/7?Y\!V?HV~=?rBG?{; ?)?(?.^?Ocpk? "#?)A?ҹD? @3?hC0?FK{\?ޮ]?7C?A0?9@>?xj?Y?7whY?5Oi+?Q<?z?]f]??w3?X?ȇG?#;?q?`f?Γ;ȟ?є?ՙ|v.?_mX?ݩr?۱QM^? $v?pA?ރՊʹ?`a?rC?¹?E0?X(?ٵ?@u ?BܨyN?1r?ۘZd?x?n (?Ki ?EL?[{O[?;P|?E?<= \?új?;$`C?6 ?t8#?hs۩c?нz?'v-$?pUX9?vJj?S)i7?M?{ q?$]?yA6?ocCjX?c0YA?pU?[EN?,[?_Zn?תSŤ>?߮],?ds?\Nsv?-?R]?[)*q?_0=y?tRn?9v?oq^d?~ s\?`4T?hrG[ ??5e?S4?wtt?wS<#H?0/?S=h?O?ԈN%?ړ:?ԟ]Ɲ?3i?~swW? /,?d? Ft?і#S@?xoD,?jSsn?dS?& N/?ꞡ ?c I?t9?GV?LFn?]\?f?VCw?Ĝ~?M}N?VxϨ?̙?@Pɢ??c)l{?E0 `?ݮ,w|??|Y&?څ [?! h?nI^?<?mB˛v?Tr?d1?5y|?Õ:_vp?QG?٭>*?2`h?^q?P}h?j۲f?*˻?v&ʝ?tn?k? ?=(?ԃN?0ȶP?5p5?}N7S?~+D?wO?ܘaj?Ӣ_D ?QJ$8?>?xs?G?(מ6?Q)?"?|?R?)( ?ʬ`Cg?]=V?'Oz?7d~^?oP ?c[O?O ?ʿҀK?J0?` ?m?U?l` ?c&?݁_? ?*`#?q〢fB?uD ?F? ?ǔwnp?l>?kIɓ?l!?짖?[ j?'6?n P?ZWu?P9?^G?(9?ZIT?^?Y?NN]??\џX?Ě% ?@p?H;d",?V>Z?OQ?K?e qe?݋ȷ?9@?̓h?}G(?sg0?hhU?}"?V*#??ى [?cC3i?Zl ?笍 &?̣)y8t?ϸ MK? I? ҈>?̧i0`?u.??wP¬?KX??~xs?@(Sn?qZMA?<ۅ?O7?'#?i>?FUD?'1?BRM[?S]O$?ҳM[R? L*?Xv2?Xes?FSY?KvZD?gMd d?&&?-?^" ?!V0?م++5?)< V?#k?BX?]"?N i??sV{'?r!* ?֛ȯDE?E@F?<?: i??Jk30?ĥE?GJP?6[d?,?J}??ZP?+{W?ˌ>m?e?ǎ1;? qc?-Fb?qa#rp?ե2n?s:?U6 y?^[?JZ ?΍p?`ld?߸> Q?=K?~2?훱?r?5. ?|BEh?@, V? ? y?z6?H-?E,O?$~?z<4G0?'k?e4?鶽\?T]l??65?ɖ6M?ߑ4^?iTĬ?nFg?”4?R vF?l?ά~?Խ"5?wBw?̍?S~??ށn6w?v0ew?FQkv?Eawp?b|@?K:5Jͬ?Y?%l?6c?m̾?Nh*?Bߚ?c)V?ٕ?ʽD?z?v%?zVb?˽88r?슂Bk?#?8S?׼b ߫?ȃGe?,.??Uog?e1o?,"?U{d?npF`?۵z ?쨶TZS?/?H_=?FMa^?MB?䁎@M?Չ#yf? F=?+* ?O|?5>?a8}s?zx?Y(Z?Ա9rc?Zr+T?#VD?Z̋?r;?P ?"?-.*?h?qc? `p?m*c3?5u[Mn?P6aD?eF?z??晴ng?%sh?ںĠ?r ? l*ͬ?-?upv(?63?5AǴ?y z?IZb?ߎcH?O?1Mcd(?̳?aF"?܁Lb?aԊ? `[?X ?W h?0\?fج?xŎt?ǐ,?XN?<6?r,?ܖ S?␌1J?6?c?4'M?v @?xkD ?ʙs?ȁ^D|?깷?ѻH;c?ZS ?.ao?߄{M3F? p%@?QT/?Av?J8P?Ai1F?Gh$?閧L?v?ѻ念?R6|??E?ˇ?DJ ?cb?7Eњ?-{C?_D?k2??D?y?qw?nxE?⍘ ?¡A8?{(N??Db,?˙˚?ё?1QY?WI0=?F]?9?c%K|-?Gu@ '?Y ?6$?бĀ?ɶBz\??صr?ۭ\?XN~?4iƼ?A?QPF;?g6x?!D"?AX ?DQ"?5j?O/J? `،?!x( ?Ov?z>nK?By?#f?Az?f 0?t 8???\c?[6?_aW?>6?r)?9,?qt?_5 ?REH?㦐?iI2? ;?|k?՟0?@D]??gp?䭂5"?'c|6z?{%:$?z^? ?͞i?̬*-?'?YN?E?\k0?Z,??܂J#` ?̶]D?Ȥ]H?L\۾?B`OL?jn??=Ћ?Fq?T"q?:8%r?n)?ҥq}?MXF4x?zQ?c`?ܬS6?I9?S ?%? 6=??g??ۖeO?=?nJoRa?sT?t@-z?W?${?׮+P!?8~f?Mm?6?"|?w逶?VZE?8ky?jd|2?\l?DlG?'?Z}.?Lr?ݵk5?NiPP?ޤ?|X) H?>lҞ8??p"?{V?Б\?6\0C4?Ը?(|?8?hwᲬI?惦(?C\? "?qo?Ѝ*G?N?s?#?Կ쭐? "?Ы:? qrR?>7?k?ʪ?*?YG#l?m%0? F?TҜ.??d!?X ;?[ǰ?Ul?㠠?Vn4?zt?#tً~??W?ۅdj?c5A?5H>?$2N6?h?gp?e?P@?D6?N"p?/ r?%E?IS?<׋p?zO?~@r??0e6o?sZ?%N[+*?[Pb?񇩜|?گiHd?`?bq)s?񜍩N?{z???c.o?1V?{$#?ܧy ?cnz#`?p$?,?C|?h8b ? ?t=?z-6bg9? o?+8-_?2,?Ỹ.?y$d? ?yk?ˆG?x|?MԎ$?n? UC??g?E?ÌX?MT?^w?8.? W;o?c[}?s k`?SD?]h?VPc'?VKܠp?쟊7I=?)iG?(S?ބ׵? z«?ߓbJ?(2n?j?o?ћ oJ?DLv?ث=h?zx@?.g'ְ?0iƣa?Ya=?6%?z?J~Ђ?8F;?[|(P?k٤? ^?k?[f œ?|j?V>,?ЄC?oS L??(ݩ0?ƒ!?T"g&:?ة?ퟗmE?eu?TYg?1760?5uda>?'k?xZcU?I(? I'?m`?U'o??c(?/ևw?~n%$Q?RU֍?fǺ@?04@?gnP?sl?Ɲ% ?@R?$$>?/ ?ܾ?? ?^-4?*?99A?്7Tj?O|J?3?edoV? M?<ȱ?X3?|_?016?\xԦ?Ϛ:2@?ś}շ?9zG[%?1?xoX?ȄAF?5ڠ3z?㪒.?|U!?E|?Eix @?`Y$?شL;?D~?DgEL?ӿ vx? fi?D ?l&C@?8lʦ?u ?,h?_T?Qڻ\>?Yn8???oj?%0L?g][?e~u?+0E?YX?{4?[VES0?Jp?XC?qD?$|)n?eIdw?J̰h?~[!%?(~bּ?? @B?yA!(?J?u ?ԨGLY?R"&)?zaG'?.(yD?a6U?aeʹ?<?,?uNQ=?lSK?!M>8?|$H?om?׉GJ?˵l=I?pwba?o΃?b?*????uK0?s?? >?R?l0+]?? `?y-?ݚ03?0L5?ޅ1?ߩ^?NQ?sWk?k?$H?c+]?̇ 8?0.?pZ4n?.Uy?F0TW?5Ȕ??"H?n=?r@?,ߪ?g-?kDB?Ӕ(?g`I?w]?" ?ab:Ш? ??4QR#?eӨ?9_ ?s?&??Gq|?.E? =CK?>Mj?]D?pB%?<= $_?ZsM?rς?ޖ֎\?sv?jI?K}X?5?Y͈? ?!jPV?lq~?7r ?㘗GLW?218Z?uJz?̅]?#3`?SkB^?;?F'X8м??o ?G?o"?F١?) ?\kW?F`?3l?1g??沠=l?;؛?E%?WKΦ? P ?ڶXV?\85A>? BW`?H$)d?%Y:?1?,a >?$$?ڰEX?l/?ϏH?Un(| D?om&_?Y$ ?߂27h?;b?c'?2&??|٧?HaW?U!?aQ\?(,0?|t6?٦?2+?j}?SR&?Rx?vEN?͜_r}(?:@?X!?dX?՗'?"WeA?3l5W?5?ظl? ȯ?ИO?ZBn?mU?Pvn?{J2?*8?p@ 0[?|-(dV?0`/P?y ?f2?w?éq܈?!,?O/[??0fi?Wv?f0d?q "?'~??>*T79?b`?0R?s䝠?}z=(?6b?=/?0?؞V}$&?erʧ?s0s?eJ?3]F(?8k?%?Վm?P$$?*`?ot`y!n?y\?s]P?bAu ?RQLj?U2`?Tϐ?贆 ?_?Ӽ6?ɦxx?|S[z?N?ʵ?]Q?U?ĖN?ltX?陫)V?I ?1`>W?i>?BY?Uv?̅B v ?8:ߕ?8`?⏓f ?U!?E?js!B?}8?S?% /2|??9.?ԐEÐ?9>'?`Ì^?\^?1)C?\!r ? \{?֎ᶚ?̃$?㟯Yw?4|?@L?Ȥ.)?b?ФK_?HIdV?Dw?맦>?bA+??EV?kR*?:t?pou?筲!?.PdP?in?41[?.YLx?1:J?Π!?r#%k?VDr?~lghp?؀" ?֤_)?Э~&?᫣Љr?WVȺ2?$?S2?ƽPdNP?g?؀e= ?r6? F?R?t:yQ?j<?%9??ZA|w{`?c;\?I8?HL?ZjT?2)7V?ӤTX:?ه=?jT!?;)ȋ?y/x?I]?aE ?BĴ^?ED/?rn ?Ks?P?jt ?VO?>Z0?p o4?ԇ D6?^?ΏH|[?hԂ?Pt?1 ?өf9p?v$7?Fm|!?LX?/V?r?੣ϡq?Sp?E|P~?tZ?fp?z?M쌴?rl?o?Ic?'^vh?NqW?Ghc?:,Y+a?z f<)?Z6dG?HH7sĦ?!It?_9?e8*?)?Ux;T?e?ӊHjT?;?;P|?7?,?/?S f?iԜ?؛&?G"?6?ܤ̂?^?kt;?͈Yel?&97^?Ǖǫ+h?0[P?ٙL8?B5:ek?s}Va?SGE?ۑ`a?Tp|}?*ElE?^? ?e:X?;Y eq?or?P:?>;.& ? x?N?՞;z?ۺQR?@;iVR?6Z?[J?#B?Hl?㕮?!{@!?ԣ8J?Ș?ܧ?͐X?ح=?qp?龔,1 ?DZB? =x&l?ᾰ9RU?@?|>,?k=o?ʭYl?3?.%B?!+?"?D?|ƪ"?E'd4?݀ vl06?-?V?xwO?p1>? /[?Q) ?KoWZ?(?plL?M? r?|??5&?- ?- K)?15 b?>^]?$-t?՗^" ?GP(?7lMV?13?,g?†Ad??h"K.? ߸?M:H? #\?HSt?׹vl?jp?l5F\4?p 6?~ {"?۶ g?q I?2 @?lLt?^$?CK?թR9?u ?b%v?_lG(?/[ ? 04p?Y?"?灰eM?mW,x?nyX?l2?B/!?묾Cʩp?v c?}^?ӌZYDB?\M?\kI?;WsX?z7U3?;z " ?4O?a?#HU,?T=ω?jKB?箋Q]??7D?_w?0ϭ5?뛺?g%M$?W?D 4?? ̄?y1e?1?ZBS?fO/?GjۀH?4 ?T=?3%9I?6=?fJ <?Z"fdI??mhÎ?M?-5?f8?He2T?pj?4h}?f#l?JWa7?R̛O?OU ?P;fZ?l?;?i ?ͪ[h4?7?oy2-?>J8?4 qy ?f0?=L?3 ,?ᩝq ?Zr?eڰ~V?M]]Q?=y&$?:&x4?G@9t:?bI K? =}l?Wj3||&?ӏ!?Ԗ<.???0!e?рI)?38eǠ?̵i,?0qWh?k؄?")]!?L%f ?‡mF?Ol;?"?@g)~?zы?֜8?H}?Iז˔?(Kf??DSsj ?bw ?? ?6T?7.,6?w| `\?n?\;I&??k_??Һ?R?ɒ EL(?7?Pph?`hxIS?#Sy?Z^&_P?DP?[~/?:JZV?]?}S?ጕ4?6.NP?$*g~?FJ*]X?34+FD?|b/?p=Ox?I)?Ż)?lp?5yV3?G ?ӗi}^.?A 5x?prV.?h+?Q?ʛο?2f?b;P?6Д?3,??ߍBYn?T_?*?ElJpv?{oI?6x&L?DJxN?w& ?$fŽR?91?c ?"?eCG?͏fA4??nu?rX?x2Έ?F$?û?CK O?~{?duA?Ÿd?H؛L?E=?o)m ?Uޒ?( Ȕ?m9B?&:J9?h?LUh?Y?rh?\+~?Ѿe?a8?I?m7?Ɨ?˖ߦH?Io?34򵖇?6-?P{?_u?~&QX?Ҵ@\/P?ajRo?ŗZ?( O??[7>a|?l?fG?LIQϽ?>e?=*?{$?P &@?]ut?07? ./? y?O_x?8ܜ?\BP?>n*r?̂?]?d-,@?˜H'?*? u?hCu? ?خBOOJ?P?Kxm|?bHR ?'`?fv~?S啜??>7\?*:?zz?\fiP?tG??P ݫ?0־į?+l4?ܽrhZ?܈@N?;?1D?eG#`? |y ?䟚?Aҟ?dSv?݁q0?xt;?ieA>X?b?e``@?XB^8?8q?x5?Z4|?G?G?^"y?FY ?8n?b8?s?bNɿE? ?} 1?͒ߏZx?ce]?- W?eo^j?>,?j|?0Τ? ?ד?+؁?68?8A?=ݿpt?d`?ϐul?)$;?/I?ۧ@֮?/'?* ݐ?&jW?eL@?T?^`?/h&;V?x֓p?>@?'[?]z?;g-6?x$?9m?C?;3-? ?E6?ܚ?P5\30?V,x?S-s!?+bW?%6Q7xX?|{e?s F)1:?tlp͵\?!&v?T+#?L?%dXx?GJe%??yya?X\?ۜj?ֈ??C*?g?2Өp?׽y??ڗQ^ ?h?vi]?i?Py??|lw?6Ƕ?Έ=?Vf?沰Z?%;?C":?!&OTt?Ҟ7+P? Mk? ?Yj/?`q?M?$^?^E?ם ~~?oq!r\?'ys?ïCD?k?L£?@?" `?GR>?vL?#N?Y ?3kWg?'Q?qs?( ?Ϡ?c&uL?՘@v?Η(2?i+?[?ʻ ?/K?*?{y?πBk4?:2`E?Q u?&QV6?Ptj??P?'i]?ӵ?:, ?Չ>!?7?Fx[p?pT'[?HBq?9a?|uI~?3k:?EDB6?~] ?}1OQ?D|?z'?Km!?Igs?:nH??԰! ?tg8?4b?!-?K\*8?C>m?DӀ?ԝj?_]?ܰ^_y2? qG?9s?<֮?0?hS+[?+ӫG?۱ p?aH0?^es?x?ߚ :?m_\?భ]???FqF?61"Ʈ?\zF?>C#?)7y?=dh)"?RQ1t?x;p?AS0?팄jl;?ᎢaB?eS?? ?:mɰ?+ ?x\]=?}?)Cl?Ŭ6@ ?-}?׿j4?ڢ`Z?B璝?c?O?Zk?⡀-u?H?ɸܠ?h^]?1?yz?|?'W%S?& ?Pz?7?ÃZ?¦?Ӭ9i?@fU?α?P?G](d?EgOT`?Zŷy?Cx`?nf?uI!A?7/X?wQ?|#Ie?t s?:4?K;co?ojI?48%tQ?%3P ?ՊZ?`"i/?g5?7[?r?.?j 5?HLn6?$^+?~&.?~?oCX1?*7 ?~?F?bk>V?~x ?..$?ܡ+?Ӄm[yx?I`G?F?|5F?/m?!7?|Yc?3yU?ƄYh,?g ?"G0?=rw?p+G%?s S\?ت?g!vpX?C3?Q?O~B?.ۣ?d?w?ƌ?Fa?wqm ?I84`?BD@?}rC|?Ԋq?ffZ?Iq.?wx=?d؉?#C?_YY?4Fƣ5? ~n?ƀ:/t?YL[?@?rGB?yN?~~(?H+? "?͇js5?E Ʀ?i+?jI??x?}7?v ?J?Դʝt?9F_)?;/t!?蟽?4?&c8]?J?d61?T$6m ?D~%ZQ?]?4ۥ?[E?6?]x$?hp?йOo?`%?++4 ??$^؇?(p\?& F?,!"?*K($?ԭd?h{&?^0?,L1oo?^m?$d俢? g?hΗ?_?܆l?Ϙ?N?辳hB7?\̕?V `?k(W?vM?? `U?]_CH?ڜ:n?n(df&?/K?k?!c?Jaw?׀ǔ? ~H?8p?aSr?4?Y{P?Dz,s?mm?M*?Uq A?L$ @?ˠI?Fut?d} #?ts?ƃ)ܘ?ΈE4?xÌ-?~Oux?qZU?݉I? t?Hf?fcm^΅?E-Z5`?D߶)?΂8N&?S`?.]Ƣ?f?o'e?K?9)O\??8O,?IE?x˸?9?*YX^?ؐZ|?òl0?$P^P?xSlE?OH?ǽM Kh?u?tb?u?ᱏlJ?߰^?AlG\?nE<,?Jq? ?lX?E?ԧ/>6?▥e?;?5'?/X?_~Bs?ռH?Z?ߍpf+?%Q? yI?߳)8>?aD8X?Cn?84`?vў?!?b?2fG?ԁث؋D?H=?d?pfjvj??縎Z%?Ԙ"?+MH?}id?XJt?ׂ!J?E0h?0)sz?}?4" ?ӧ\r|?!Ӱ?E5S!S?RSq?ꥳnt?i兮?ԍ?/?ʿt?4?~G?BFg??UC%x?ڒ-Ŋ?0?T4n?팈s3?%C$Af?[[?vA?`?;8R?A?8?ոCD?=lrt?H;P#??q&?띍f?-ł?d:V?Kͮ?6$?,)|j? D?lU\?R?y#v?Ԕ&g?ځm?F6`?R,\.?[?ាR"?/|?7?]|?v~;?Y,(>?,AY>?қ '?g.??؟&mG?P?吁g?\?̝ /?Ƹ?g ?d( ?D/?P w_?47uE?p ?h?;`Yr?1c5?6mN/??-?,cb?h:U?H ?Ma?ஆtt?j ?V\4?p?ҜLCGh?$?Qٞv?V?S?+֙T?$0?ϱ@R?}Q:SRP?^~O?y$s?枷?s?`&a ?t=B?%W?26 ?o_Nu? 8?nN?H?ݵT? ]ya/?E ?ۢ Of?ڄ?=$X?szT8?euKD?<]X?dთ?~?@D`?C8g?>.?~l2!K?ߣ|}ݶ?MpÉH?7mr?露? !?Iɷ?յS?;c?`?⡚?ҎCKj? ?i*? 8?1/?ҸS? >[v?vAkz ?p Ml?ʄ{5?06X&?y4(? :H?Z 2=P?";$?\$?s&3?ҳ ~?토ݬ?к9w?qz%#2?ߐb???5WVC??p1Y3?Lވy?a{;ݠ?$6?B ?G?K?艁?+\W?E?[Q^?5V?v?M2\?o :?ze@]?12{V?Ӗ3i?_, ?~ ?_[Dg?|?o2r6?I+ZT?‚u?ppI}?t /?{B?q?4eX?”^T?ƩU^L?P2nF?u?`e^?}*TA?ӹ-?pCx?X@O?$ދ?=R C? -hL?˞?? MRa?L%?#5@?&;㹧? L?}@w?ҁ[?g[JB?C; ?_v??+6?4Qo?,L\?Z?~d?[;=?]l? 7?"?."J?c>?ڬ].?&%W?#[D?0æOH?=z$?>dl?FxH?M`u?eDO{?9H݊?sd[o?Dr?L?,?gS?Ub?1?gL?쨔FH?KHɭ?w˺?u0?̨h?삠?ԗP?V2?ێ(d?ª80?xd?q?>$#?IUa?ҥ$2R?jP?] 8?TK,Y?K@ذ?س? AX?Ș5r?oK\?Ϳ?!,*D?eC?V7?O)BS? VL?\ Y?ȚX?ŹٓE?&9ی?S?+?o?)j?XĜծ?)9KM?~M?Zr?F?6P?"p\m?w n?!']K?W@?Mr_3܅?ͨp?zx?E?6? ?5Y?Kdx?t&?({?Pb`?m?l?u&?]G}"?LO?ܮAk?5h/@?%ܻc?Jh [??~0?U9V ?Ŵ5aH?'?Iӕ?ѕPDh?l|? ߦ4?%o?Ld1@2? e??n[T?ԱlQ?̴}?׸dBH?O?FQ?IPCb?pP,?u9#?֍gm?ߡ`?ٲt@?#1nn?F:?J4X? U?LɎ?Qc2?ڔ>|?j+?yw?QL2?h\?0& j? |b?,?'`?9?fmP?00?qD0?5M?gn݂?S'wŌ?Ll٭?}o?f[$p?  ?Ɇ)*?~?2ŒZ??ؼ*?#?MQ? 4?ʱ?#?cdD?53ּ?ߺA":P?#س?t}w?^;?ؽjm?Ц?8?}?2?#1?]?TX?g_?$G? $ ?ܒ?u p?|bD?QUvʥ)?ᑺӰ?m?2?{=ѿ?`?'l{?#H ?ӿFoT?,*?hè?Ul?PSp ?)|R ?35Υ#? 8_?3?* ?R_N?E''y?x^P,?V8rya?r Zŀm?^?^z?d.} ?:?ծ7?lbz?S?nysi?9+i?98 4j?3H?P!4-+?ᇴF?(_|?:?gº?- 'n?&*?Bz?晥1'?6t?{j?NmsD?l1?Hݴ4?O‰?qK'?ЇSB,p?ލ+?o*?fG? ?ޢGȽ?в*gwB?N:;?SZ?2z?뽖˩?ϰ? `8?NRD?ٞ*9?̤.3x?<(?ᄋ8q?xW[?$56?՟Q?Sl?쵄|F?@ڈ(?u[?V8N ?g^A?d;9p?+9?Cß?Yr ?]?$4?gCR?7L?xj??NM*?<,Ou?L?VNX?դ*r?;Z=?>V ?ޤ$?:;d?欱?=lB?Ր#?CK?l(D?&ʟx? ?6L@? V?{_?&@Ao?vFC?c9?'C途?ϵ$7?ފ?;fCF?t?}X?͹ ?t͌?<ZA?Tc ?c+?f?ʹ-8 ?[lK&@?5,9h?]S?Q?B cv?d?\`?pӲ? fF?$a|?C??LtP?vX?Ͱ?sk[?ή ?e?؈t?~q@Ư?53H?ql?Z@?*>z0?옉=?8Lu?c=ĺ0?ԙK ?b&8J,p?&F}?[?(?Mq6Ū?!APϠ?cFEB?ꫬ(*?I_?/Ba?j??!?nX? Ƹ?`OR5u?7?y0~8N?',ȡ?/1?ኩv)i?I'?Q~U?B@?3XN֑?n ?λ:?s'?srAK?8Т? j?\?%?xQ ?S?E?S<"?ȓ=?:|fq?|dų?{|?M*?&ҹQ?ŭ7?ԝÀL??h??ʜ)?ٌ?gr%n?+)?_J?~"?gZ?{٬4? (9~?.c#?SJԆ?$^?HҰ?6?h̞)??%|r?Pw =?%gC?ЕkB?(0l2?㦉?KmT t?굃>.>?ӂ:Y \?`Kt/?ّ?2(?qȲ?yp:?Fx?F?X h ?Zk}?Ѭ\,?kB >q?Mn?ue?"v?Ե ?Ӌ&j֦?S\͛?U?n>B?ۇW}?rHr7?m?E@g$?n m?VyR?ޒ?(9~?v=So^?0J?W:RH?ŝC?nz%?ǃWV?hob?ٮ[U?2^?4rd?e. ?d<1? +9?մn5?'M|?㯢?j?]w ?>oL;?cٹ ?RC?[Q9B?PgQ?Ҋgݸd?W?@h貙?H?U^?㤺>L?3!߷?naX/?D?j|8?R?/2?]W?ec&?B *?/ ?A -S?w04_?BW?U ?K?zk(c?_pI_?,mt7?1R?ط+T?ޢ8?&_A?[»p?at%? Є?ȨM?"祭`?)T?ʚP#?~Nr?k2FnN?/b?;ޱW?̱|%͂?Z3i?pZ?f}?Sz*)?e7.ܥy?(%c?` ,\?w$?oM` ?!ՠ?1:1?g"?'c ?tZ?яh?ʮYeU?x?ʿ_82$? ?Ѓ"s?̀]1?yA ?Y'03?̈́?GY??۟?֨E?E \?_%HK?Ϲ?Kƹ?G#?P<].9C?|Y?7^?rzш?aJ?i`[#?^G?\tAH? ܼ'?,u?nlp?˖l?sw?Uy?mn8?ӄ%b@?먻)A0?v,&?߲?ıl"?3?sHz??{u? =F?.?6?:`v^?ǺB?Ь\x?Ķ=*?Ig?Uw?4!Cz?1Bȯ?2?t)5?7j?դٸ|?sa\\?8Ftޔ&?0y?}?RP?_S`?ǿg *X?,<^g?DM?P^™?|`,?Kz?شω3?O~?-? &.?Г|o0? ?sX?Iˆe?Y{ֶm? ί?_?ٷ{5*?Uy?TQ8?{ų?Iu?l 3?F?3􇉘??U?i8?߮~?d?"YUF?)b?i /?NcP?O?Q|^?kd1?1?¦ ?3,2?UCD?J6}?p?ɬA??c?@\!?췦?"?i)"?= ?_?9?0 }?#xQ?Q.4?E`?}RR?w9? ?*|?[c ?? "?#XpE?&??c܊P?%/ |?o#?`H?i_3?qj*h?Zi?_zEZ?>TC?8?u?Rł?Ƣ0?o|?D??f/?J?Ah`?D?;?JPC?B-g?U?hX]@? B3+?S?㒉w?†[?r$?f^?ʧX?ۊۨ?S`.?1!}I?Ef,?u9آV!?b9?EV?Ϯ?? e? f?=x.DP?vT9p?Ѡ:?&7k3?n@A?ЛA#kj?ߒ`?~o?-JHh?Bty3?K%g ?|[dLW? 2)#??]?W ?? }?db2?к3bX??X/:?mWyU?鐮*S?, ?;f?Վ?ޢwΞ`?6qkk??^s?S½w?|`\]?H-f?2 K&T? |q?͂O?iG7S?>$?՘U?6?>xZ ?s ?"hoX?K?a̬ ?ʊ'n9`?*y;K?Z?Jm?V1)?'ޙ~?Y8*>D?0b^6?Ϙ@v\?o}8?;!V? R?/;?`l?CƔ?0~n??oRD??Wm|???eE?-K{#?]p?n?u#:?hv-?qȸ?챀? 4?gp?O;]\??YZHt?D\,?E4O\?y?j KG?,:gP?.q_?Sv?tQM@?Mua?l1?gG,?#pה?H^?!LZ) ?I=*?~v Q=?׉|?[> 0`?ԁ9u?W%k#?3k)?$i?ޝ D?LIE?ڰ6?;wj?a@?(W@?3t(?nn*F?H5Q?M x?۟D?7r?愓)m?$:?+%+/? #1?8]?߃%5?gl?`T2?&~?;?>?:WD?%PL 8?:X h??p2V?l% ?b=9/?e'48?d;?ӌiw'?c$3?yv5V?ϱ.-Z?*\^?x?Rm^r(?j?mA?^.?R?[{?ח??9ON?|? ?w?ꦤڮ?޼?}?.3?@?#b?L?J[Ա?.?I)? ͇\ ?b48?Ɉ6?9w:?촜O?"(:8??3ڊ+?lbި?ٯ_?XͶ?,`?s|8? N4?*D?u ?᝞?Fa-m?pm`M?V"`?d?:l?A@7?ݍZ?R@??ѝZuL?OH?C/?dD o!?EM?I1?=Թ?*|?X?o P?J4Ȇ?{?ȒUH?ٯJ8? 10?Έ?>?_mP?^W?BΛI?LC?"q?WBC?pXvY?_?)4?utcM?R?@v6? !R^?{?sK??) ?!1(ܣ?os?I? v?ڋo4?W<3(?ԼezY?i9`?)?A#4?O?efM$?bS?? ꊭ#H?cC@8Z ?zZP?"0}F??.?0{?O&/?ጣS?jk ?[p)?ɕv0?!{͟?lcY`?Ac%?)?L[s?Ͻ_k?8`{ ?D6 ?p]?wQ?Gncr ?*\,?vO'q?,R,?{?ׁPq?a[?qԜ?zs?*wME?3CXY?gl?}ݐ?UVK?[!?O?ū?_Di?Ka0?@?9蓕9??Jۢ? ?ij?ֱA_?8Uǰ?_sp?m^PX?!V5aP?8PX?U 곜T?эt `"?L>L? l?#P?m ?lLZ]?(?GͰ?D)L?*?z?B?ġ)?۸Ɯ?F?器_?%9i?ܥ7N?*/?˃7y?Y|?!;?Fٴ?q?B='Q?[80Pp?佞?h ?z3|?g߈?O)^? 5p?˭?K ?/^6?촑JK?ٲל?t+8l?u̧?g@??lcݦ?:?_n1?~?6?ߟ,?䙩?ǠJ ?Fn?o r?aҝ9??x:?Ρ?Uk? VWf?Һ?M?7~?瑽lK?5?G?@c?cJ?n?SNt?H+?7l?+ ?s*-|?z0I?s?Jأ?+$?Px?}o d?J%h?ӓ?6?"mGQ?꯻TGe?Op\?ᡈ1a?^YW ?e.C`?w7d!?*i nr?`Qr?)?8z?8He?%yU?:ךa#?,?I8&?{؆?UN,?.K?&ۗf?ŷZp?Zv̸?\)i%?B?Eԑ?ʉ s,?;eϗT??+yU#?s97?)**?5|!?ap&H?f۬6?ozq?R|?\ڎ~? [?#?Ӷ:D܈?2(Jp?g0O?<VЎ?&'E?3/p$?ߟ?G|r`"e?kX?*r@NC?:?Yq?C,lbHh?" 0?&E?F?35?qxzu?B#8?iND?IJZ?䓙R?y/P?w?ק[$@?sjk?֖Zp[?=u%9?, ?E[O?&B"?U(!?yxF?,Ҭ)?3?cf ?6<.x? fD?gH?б<&O&?%֬[?C=`?ٸQZ?ޓl-IV?BZ\'?㎘7^$??Wb z?%i?0.GB?]9?jL?l(!v?:?~?>2?綾%*?7?غ\:?ō?' ??o[wz?ׅ2f?2)??'6?^OF?s`??Yuf?8?˥7 &?Ux_?'_ɾ? ?*"? \?qm? X?+? Að?ﮧvl?W)J*?cc?(ng?l3D?w X?dS:? B?C2M?M?;!?<9 ?|(?(,7?K.X?A5 ?{!qX? ?b@?cdt?,[R?qU?M%Z?77?yqq?龵؟<?JVܾ?T?$?՘sHh?/'?vH?aɵ?ض? .?椢)6?̔lf?kY>H?¾P9`?ွG ??R? 9?ִ,? ?WF4?&m ?~+v?44.?틴y?U?,+;tm?ڈ!?K?J6? D?sP?Ti?E.?:T?U"?8V?䐕}?!X*P?.В?F0x?Qq?qq}T?U?ZI S?ńZ?@3?=o?e[r~ ?G(ӑ?Q"?wjʵ?Ѝ$:T?GvJh?׆c??-onH?8ր?t;\+??:~7?ԕP?&W]?R1?n?U2?=?Տ?Ev?F!?gPQ?CS5[?.?D?NY̧@x?2F?`آ?sx{vv?he=?ӢB ?(c ?-Ի?aN@?Ao U?S|? ϋs?ٌ?辳?)S?ڕNxZ?J%=b??P^?,.?hכ?ֱ b???ʎ4?5?P]y?ݜ)K?&k\?; L? u[C?ᕛru?Tw~ ?D^Nߠ? #v;C ??ɐB?T`?9Hm?TQ?y]? G?0]\?{`OsP?Z< ?aM@9?`H?EL%?iz?&F#?mn?&d?9D`?B?jB?/8y?Y-L<@??履,p?ӿ-u? qOu?j#`?$rYo?XX;8|?9?Cj-?Ш?5?ɛҚ?Dz?(ES20? d??ﰾ飬?Yۆ{?[M??E?% dH?߾?O??@۽?+4?y?OG%??S?̃8y,?Œ:!?Rh?( D*\?Įe?ԡ?X?_ ?kBWke?d;a?=?'ig!v?;f?ԥO?愖f?}b@F_?Â"@?/h?ү?wK?S̫z?1??f2=?)W/b?*|W?(?JG?x;d? X? o?^+?A$h?~? ?@c?g*?ƨ@0?}1o2?n4?ӷ`?HWpx?bSb?76 ?YӽI?wE$TAH?8:,?TXȡ?ަKk@?-IM?@?4юG|?qkk?~(Ĵ?ٔ{?_ ?Vj?0 h?y.0P?!pn?DK\?GE?1 ?:yH?wcZ.X?w 暉?+X3?6{?qfJF?z?mt&(?ჰc ?]T?Č,#?ҮD?XT?Zsk0?]˕?q> ' ?fd?+^N?퍑6?|t?F3)[2?Ƨ?훶,Q?`]N?ePV??$b?K'?mH?ﴗ ?B7Dt? z?HOtz?פ>?,?w?v(nP?&>?[D?&@ ?Jo?pb'C?xa?z=k? B?`?_~µ?A?!S30?<?kk?ӝ2U\?ګ/?' ms?m?q?~»?`lVG?ᬐ"K?O2Ј?Y?ԉj4?uo?DC?:`?S? d(?ΗK??"p?hg.?Twٓ?Oh?6?-|?@$b?E4?"C ?b5:?]?<э[N?=5r?ݜa??#|n?dc%.?+L^?L@d?02?Xӹk?~hU?1ib?W~[?n!*?~M@?lX!?Qf?j8qs:? 2?Ӄ]?ߔ_ ?gtbF?ȪYVP?bdE?8v7?䁯*??Y `?1hr??;9$?v {?U#??mP?g8]?Ț/?Ϭh?ѣw,?z/"?T ?bD}?>ؚg ?éJ?:b?2rf}?;h%|0?hCb?&enx? VE?,C)?ظf$?3$(?@Vm&?ݴ?zhZ-?hyr?fR]:?}c`?6>?⮋<Ǐt?s?]"d ?BKy?OCTB?K?˵IV?m?b?bs?%?FK?єm`L?A(??O3?S?p_?<H?j~?u?1 ,WQK?c묛?ӡ.(? ?;?Ƴ$8?ʍJ|{8?Xب?>?k?dO ?~?䕾+O?y7$?aF?gu?w ,?Vl?Y ?豉t?׿mEc\?ܯ?]CM?/?("ӹ ?XO ~?{?<.T??^U?6R??CwBT?͂Q?ԃ@??׹9?HK? @?Xw?H?ӾȂ?U??hp?k:x?(Oo?AgX?%(p?/ay?೸fbl,?Mh?TP?=o?DE8?i֐p?Q?鈷Ş?{e?|1 ?^I?nڎ?)oG?!?뚇pD?3O?b??۰uY4?]o?h5?gb?RFWR? ڝ?PQK?sD+:?1ȺC?vۧ?' ??KP??)dz(R8?b#Mn|?f+P?־D[wL?u'uD?>T?Ii,/j?XӇ?+Fx?ɹ%D?Ǫ?5B?ޑV8?,?HA]@?HA{?<E5 ?V#sT?(.?xy?"s?C ['~?ڎ[T?"F?ѵҊ?i4|r?UB?7#nn?֬x??"{?9 ?v!?N.w?hG@?{?ؒa$?]?G+-?t.^-^?rNт?ПdD?[1/??ĺ\6?ɯ?,$p@?Ⱥg^?IY9\?%⋻F?ӤMA|?0,B?kz`?}2?h?:!k?ķ)4??K?Td)?z? >w?.ƽaIN?7o?>?mOR t?,7 ?ٜy?Ƣj?g4?/?T?J3>)?!?ŷ(?V?ֻ1X{཈? >?ԫy.?|"LB?[#?KT ?'?ƫ9!T?וxe/?C&H?z9j?9YY?^*Tu?J?B}?p?8|?p?p1]Qx?A>M?\L?ӿP? 鐖F?"Qߖ'?&bo?+?[Dx?_*Hm?Vt?֞b~b?◐al?k(8l?Qb? R?PhlX?-{dm??㋖g~?3h?*O ?)%?Ss?~Z?UC?.+PD?;?]e?D0q?1{V?dcTk?aX?DZ ?$Sk?" ?(?V s?۶~I0&?D}b 4?y:M?=G?kb0ł?J>t??RĔ"?j?g.?莇?f+@?@?\=??<(?P4?+Uo?7?``?7Mh?cmű?~ K?#ϙb?Ή3>D?ͱ?H"ٳy,?Sbb?m?D?*@?\?Y(!?Ӻ 8` ?݄h?ߍUC? 1h??~>t??S?U6{?ب{ ?~D?!å9G?٧9s?Q?uE?`?& ?wh?顃vY:?֡VN?CUz? Il?(E?fѼ?ܤB?.ߜD?Tm?Ꮼ{N?踙P??7PL?ɸ\?ĝH?:C?e{(? ? cn?BZt?eY?ͤxF?VR@?ұp?˷A @?եK:?ؼ,,X?_\?d?3?\?[?mj ?T> }? "?BufL?|?XT,?3Д?Π^ ?7?K82?w"?~?1W?֍6?8?ĥ?} ??Q??vCF?.?]bih?o?륝 ?QHM?崎h?K~?+`?Mj?Xjz?jK?ǃ牏?8޹|?q"`?=,O?3m"3`?c IQ?/˰6?_x6?sWV?e+VN?zp???N;0qX?#\jB?hQ-Ol?1Y?V+?Ϛ8D?ИP5qh?KK ?3]"*?>,(dAh?nҵ ?_L?ըE;*?TSp?E3 ?5z???~^~?2>c?,ƔW?*)Y'$?ВB 2?#b&?o4^?+3F*Z? ??a?޿?A(? ֓?w1 ,?L0?? a?p,6W?ٲ7O?>ڛ/{?ṧ+G6?ْjMd?k+?>FS?seW?'ƫ? E. ??;!?zֽP?U]Su?y ?)"$?zb? i?y -?֠p؀?R.?eE?c?y)Z?װ =]Bt?z<?;`g? :<b? MH?10R?QȍX?WOk?RB&$?c?1A?|?s?ޚO??g`\?)c}?s ??p`h?;y:?Ѳ\-4?^oUH?Lki(?g @J?J=ډ?r?ۢ(?vht?kWB?o?B ?C?V?̚jɴ~H?ʵ*?`?xl?bS(?ރQ?5 BB?_N.?XMSk?8\|?RY&?VqU0?S?z?ϺF?wj!??%z?RʪQ?i^h?*f?*n)?7]Lq`?BDnp?,c?Aw?LuP?E~n?ZN?֋^ְ?߭ a?Aο x?ۗ͘e?$?殡  ?utQ?Gs?efװm?&9?"u?_a?0LP?CX?\?΁I`?l%h?[?tYhV?Ry?뷋2?ؠs? ?˚2d?젥s|?=Y?֋:?=2W?LoP?vG2 ?>;a?]c?`5O?SJ?[.PVG?5O?:HL? ?Io˿?8x?19?33x;?ĤX?nZ?p3C`??Pn?4,G??;*?B?PF?x8?;~?Ƒ$L\?*b@?+BX?By=?~)0|h?ޖVW?"- ?m ?'ĉ?:4?R?Q ?Y !?֦Qu[x?Qf?Й?З]H?0mT?7?IQ?š ?ޏ) nx?P&>?E`?M 8?B֙.?K?U?9G?& ̩?>ܡ]?:9??[Y'?%;?? ?đ:d?\V ?*.#*?T26T_?⠃P?jR8?+6Nj5?O?> ?ڳW \? ?K< ?ٲIY?I!:?S䊆?Ŕs ?o덈8?عEJ?,:+K??"Q;?a+Q ?ps?6%? ]|`?X5?Nsh?/!}?I?|5 ?D?ڬt_?Hɠp?A>.?޻S\?lh?/5@?x?UGd?%=va?J?픣?zY?'),?6f)?љy?ub?rS?U@?\ۅ?,?ވih?9r?šn?Aٝ?X??DzuT?N8=X?wcf?t'|#q?:M@6?qC0?xt?|S7??M^X?ٹEZ^?f?ˍ[Zx?],x?FUZ)3?+ ??ߘ?JM U??-:x?̲?o#? Ҙߝ?Мn?۷0?fy0h?xov?޲c?p0?}m??p~?G~;"?0?/?釥K?=?h?p?y!T>?={j?, `? :?K?٫#|?F$?A(F?]7?7?-)?)?c%ۇ?͌ m}?o4&?9?u+?ؕyV?Ŧ?*M0?#<~?Vz?5X]?.?ad`?uhW?EM=G@?}qGjy?3`F2?؅o“?㞲M?H]O4?c6Z?%*?PTz{?87y?&-JD0?\H0?7v?_ ?V?.,:?eWS..?r#?- ?N?RW'-? 4?{$p)?뼃E?bۦs@?]?0? ?ĝR?WSl/?;&?HJ%~?+\?P_?3q;Z?T?m1 ?مќxX?Th?]V?lT?翵 ?^?ފ:?˿y- ?Ҏ?xu?z?4$?J;?pdm?ϫ >?:?j2?T44#?sNz?B=?5v|?㹀a?꺹`?O,b?? ?f5>?UCx?ٶ:S%?'s?xϸ???̗Mt?ީvst?.EZ?Q&?n4ef?J?) ?M_:?;͘?NwG6?>c륒?쮝? 0??\1?u?lr|?_BS?+sҒ?A§?*ٵ?fp"s?IGn?Uu?kK~7?ľd?вlD?IJ_?FJ?Mf$?LȟS?j?p?h3W?N=o?Йcܚ?'?~G?o|d? ?* E_?I R?6Φ?˼0?QwS?n4?ڤx?O ?ȑ*?8@T?5kB?≷WWo?S? p2R?HIe?/p?G ?a ?b?d$i?簅2 Y?zaw?5a?mh&p?fR?h?욁c??]iĶ?תװp?vb?&j4u?I\#ع?'ӧ?VP?0#+?DwU?Ƨ??ң/?hE ?נvk?65ZB?<)s?ڡ|?j?ʶ ?B?ƈz '?U?|v?55?ײt?…:N?%<#?if?ۀG-:?, ƀ??C'jK?7@?iv?Q0?V$?:DM6?C ?獶d?Ai o?( c?UtV? n:?`ҏ?ͦ?]?>?xvMW?Z ?bL?Ӑ0?롇R^g?O3vB?\?Te?? h?*ʊD? |Z4?TС ?a2H?MO?䄹?譾`f?v(?ָ'u4?艸/?[?óU?v=?̣m` ?oԸ?2[K?~p8?~U ?q?耕?E@?QV?)5?C"?ƥ6/x?IϹ/|?gSX&?˰/D?Il$S?g+?ѝ*?⁐vJ~?\g]?sK< ?爥|o?g -T?1$??&?د`?c?Wb?aJP?dCu?fQ6?(7?@Nr? B?ѶXVz?ʭF?g?'F?6?9.v?叱h?tc?ë+w?W-!`?|),?àM?pH?7?tDuC?ߡ]T?.lSE?H_>^?1H?MX?uI?QPJ?Di|~?6qDs[?r,F?-#p(?|)?#9(|W?EYC?U;|a?S?˟S?һtq?ᶚ6?7xĎ?e\j?҇?'e$?tr? -V(?1ڵj?S5?|~?'/fv?H4XI?=6`?If?)r?O7?”wG?h n??o(gE@?ƞOd?Ӽ]?e&'A?~,?Zl_2}?g3I ?d ~?r.Lu?G6?> ?ٕ`?vH?pz?(~?~?}Е?:x2.?z??m ?Ze?>|f*?Ҏ";_?fbb?V?̾D8?lF?N&?_ȃ?xŶH?66Z?KR{??jk?7?H8?]Ab?$#sb?.:?^ ?]#,5?fQ[?w?nNC?u(!?(2x՞?pu?Zl=?sx(?syG2~?1Un?~(Y?"?MYP?Ex*??wh@v?顯?.XKi?U? ?-7?xCx?ʊrq'H?{1&?Ͱ?Of?(a\? Z?ZIx?j7ҩ?t;?f))x?rQR?Iى? eG?\?5Q??8 W?G0?; ?B?ۭƅ?,#a8D?V]?hL?Mol#?N ?d4f?w?V? ƇBf?"\M'?ݻJ`?k(?O?? g?\?֍? N?Y-?G#?z{q/?`&S?1-?T?k^?8?s ? Ch@?`/}2D?c?љ8yw?nPtX?#{'f?w B?OM?V?P?;[`?a?5~ө[w?ҤY?"'EJ? U^??m?jj0n?7?MbO?(y¢?͏@?.s? 8L0?@M;T?Ap?HRi?q 4?&??$c?Ǩ?, ɴ?}7e?޲mͺ?c ?1]??rtX?&Q!?A{?멐I?] s? mt?7M?=7 ?M'?}?:)?tVeF?*h?᭣b'?궔Aa?ث,?ݼ x? Bq?гmf?6`N?Հ?NN9+?Ovݙb?2XhL?})q?_v?$?ߣ@ ?e3P,?qWC?ؚ>?EC?$\?*r|\?5-}?vHO?[ZD,^? 1k?ROz?n.d?~%IbV?L?4?V ?t ?咎i`?LJNv:x?d@?s?{m/??&lhc?]'h?OEC ?d?n6?V ?pYc?m}?+ 5?rU?(Y?:rB?a ?NJ?ƙU?)\?'lia?Y7'&u?qj#?ʴqY p?ҕ;?8?fQL?٫8P?&xT? 9?rgF?(u_?l'%z?4?2?!')=?ra?O:?/?kk '?`K?[|K7?k}R]?ļ?˧}]?iyh?Td ?#DQ`?0=&@?L}b??R?d?k&?Qw?*ݪ Y?o7F?l\ГP?G]l?Vn?cJ?(?}?6j[?_@?:?<ΡF?%z?l? w N??i)?W16??\փ??d?&D?G^A6\?桏*?c`H!? B?耗/?Ґٜ?.?~?V`??R&? ?{l0?@ྯŢ??`h?^Lt@?濍ygi?NZx?푋A?,)d1?ڐ*?Tx?xy8R?JA?.q?hCM'7?쀵{W?">JS?\-A+ ?Oc?sp'S?7+i0E?" -?V|ч?7z j?1P?QĵX?ءTl?&:k???ls?8u?ϐg?p?2:?Ќ쇂?x\?CL?R?1l?srB?a??a?Xrt?Y},?s*"?N0?PV?|9&?-xN?p]*?Y".@?#}0H?seAP?YEɭb?_hf&?˛F=l? ?ę ?/ؚ?WE/I&? ?T?'ȋ?Ž.\?羳;&?^m?7 hz?@lg?숋f?Ưp$t?_pp@s|?<'%?ļ ?sfg̚??4٘?@cɲ?+|?.q ?/B?`ĵ:?կK 0??p?X $?hT<%?WTx?aN;J?0D?ӪMf?a~m*?ɺq?@>k?Vuu6L?_jC?T9>?%C.?@Ʃ?Ao?1m?ү-4?#WVx?$`.?~b?@?턤?5@)-?ttJ?m?]j\j?йbE?FڔE?֪e{v?͑ ?yw?޻JyF?H??:6D?吙}`?|%=?z?IYY?@ux`?ίHy?3k2@?㇙-@?a[ g?̮7?Iɪ?%3?h?5}*?PUKϡ?@R@?܉5?m?VyU?)*?j9rF?]hF/? +*?Q?ϪI5?NF(10H?w ZST?f?9p?rGS?5d?!|p?@6:?**Xz?ξ'l?KFW ?i,-?Pq?4``?N?e?4lu?C?կ6?]0ӎ|W?a뻸?I?4??mx? mG:?x,0?f}ϒ?a̘?caIЄ4?Ÿ?s?Հ:(U?Жޙ `?b7Er?fR?ˬ ?To?0 `7_? 0Ԩ?_pP?::??.n?ο*|O,?Oz?F}r?i|t%? >? f?S,?c?t… ?KңL?#>n?bLT?4( &;??/ b? Ӭ? s?t(?ͼ݄k?s$?B}x\? ּ?Z?-!?y?&7?¾-??w?뷧al?spf?9Oda?Xc!?_^?=pxI?l` ?g?w ?)6o?v)? `?=W*? ?ȏl|x?4?v^&9?=@?+ҩ`?q0?ɫ4-5?쐙|5?`b?߬A?⵹fd?7qyc ?q >.?92 ? *?3?ln?=a?`m?ws? )C^?|7?'?;?H+~|?i`m`?ik—? !?P?⃽AG?ێ??w\ެ?`KO?Fޗ?̑ h?^KY??v(?/X?#l?«w?U?Oce~?F1;?P|)C?>x?h?D>,?4D:?R$4P?j˕,?eiG?2Y?䰭)?<^A?XӋPH?ĥMP?!xl?[j??ѼPx?ܢ¯?=%? sf?!,o?,cNp?ep[P?>?NaB?`?N_5Y0?dGBg8?G?u') l?./ۇ?鿃?sT?ٳ_D?0?i 6?2}?ܐ &?5z1?C0?لr@?K6?뗆[?߮YC͋(?Aݫ+?SoX?ـ'v?uz?f7-l?Le b ?p? C׭?+??,VJ?N?*?"`ͳ? ot?ǰ38?5h$ ?(%;?67-??ϝf?}J?{~?d*9??$?K|et?ʪ8&~?K(Nn??'l??.HgD?eT#C?:V3?Cg@?ɝ0?(3.?׼S*mH?,>?PJӮ#x??Nr%?ity?7;g?ӳs(?0?ɐ8t(?Џ?;'U;?z  ?ՍjN?k~|6?i?m0IX?RZ@?ȗ5?9DpxP?3B`?Gm?m%?Na?}?ZpG5S?`?۰7ڼBd?UO,?Ep?֝܂?)? ?r*ijvt?%‡?G?Ki?} s?aEK?}fh_?r64-?G1}?6I )t?lt/?ҬqT?|;%?S[h?p]d?p(??݀?{w;?F?lC?d ?ӌF?H (?{<:B%t?(%9T?’%?K&@?kYf?UNV?9)?D4/?Lhm*? J"q?*8{u?YU"?{@F?D@q?>9x?ky{̓?7?Zuw3?qh?ƛ ?>!;-?xr?ٟ,7G?=z?&(?דY7$?Yˢg?cyb?#+~?߻*R?*bU?碒%T?UY?a?:g^T?ۆIԯ?ȟ?VBh?ru0gA?${t??H!?6[?ݨ+D?_ ?;c.?gkӃ?be?9( 4?똝ҷj??1S.?U2L?K=&?jl?60??J"skZ?Pd-?a?|IcR? D?¨h?QJ)]*?KA5~>?O3ƀ?%0?Et0?r?ӫs`?Xg?\?F4!oj?.攘x?l(?Z|Af? y?us8?3';?\?.Ewf?e?zQ_?ˇ9*/?105T??q}?5#4?B.!?d ?{0?m1O?V$?_h?%e?`;?՛ο\8?Dٕ8?P*x?毸?巔o?i&?YH?aG q>?;? ?C F%?s4ڏq?wǥ:?t#?S +>?|]a?{LU?IV?kA?bv?*hgfM?`?Le xA?Ң?ӏL?U?6f`B?ckN? v,x?q!b?R?n4?r:`?Ex?۪?۳AL?Òr? 3?B ??)_vh?f | ?Igm$K?D$?`50?v.gw&?l ?y?\8?BEh1?rvm@?=2$?T???d^?k?[E?;׌?~ ?܉`gj?hwi?:y۸?C9?瞖V; ? #?ѣ?#ea?v,?t_-?N°į?ۥG6p?o9H?MT?+?4?S??҅1?*M_?{?(@?PP0n?/T}Ȏ?>s?aUL? (`?"?ob?^$?ԥ6l&?֡Vv?㴖?P6`?D6!?zr8ߞ?^4?QT8?@ O?yP|?d'?Ϗ'?N|+?2@|]?+ݠ?Ú?".L?G ?t0=?[&la?z?X?4#(Kd?P3/0?%Ib?䢬5m?Ϭ1>?T?яtq?jB?`?ԿI ??T t?Қm݄?}v?ə%7K?<@[?YT?ױ?!:?J? ͭX ?.\׭?瑋1?k\?۸ ݖl?X,?|jn??+NҌ?zFNN?8I ?u?S,@?O""?*!?|5?oD$m@?D$P?3k ?z-?o5?OH?컧?c?HZP?O_V?s 5S&?/iD?ǔ5D,?A/y?ѵiS`?~ h?P8u?]?1#~?{2]? r3?⒯;1t?"{4t?S@?LL?0Dhh?R^0?2WvwP?*hն?d?ٹ?92-?6#?-T?²h?v?dxh?nv1 $?@ A?qa9`?<"?՝ ?NfP.?z?ɼ-Xi?b~4?.gc?6d7p?&M?~v=?@ ?٤vm?m }?(h?B*? E?8RǤ\?@nvP?ݽ֑I?}WӲ?r>xL?bV?nge?Ǜ??ق?ෂ&?H(o$i?yZ\?mIe?ڛ9n?t(?@?N ??6LJe? k? }? ?`oȥT?}?fR?ɕRM?O|z H?\?M?j}a?JfJ?pin#?v#L???K>S?iq?pZ ?'Q?B2?ha% ?aA?Ad?8Qp?i悄`?z?z{"? l֜?ڶ>'?_K[?~?njv?hGIi?Տ0Ԁ?2d?ͼq?u垔?堻"?!?t~dH?~TK?nX?N?8j? G)A?j[fHp? }>X? 2&?s_?w?.?F«V=?i\M?DžKߤ@?+x.?t:?mH}M?r#?W? 5?ܨO&?1?#5*O?Yp?c{I[?WI?M?비t?>u?qL,?s?,Ɉ?˰^?˱8[ ?"L?Mw?*#?]ΐi0?=VXwv?5?:&%X?Br?ȪSOx?#eX?-G ?psG? D]i?#?Rpht?KOۥ7?ᎋ#?N/pK?#黛?>8?<?&Zi?E0&ġ?}5[^?d k?%""?wKMD?Jؘ8?C?U`:~?ޢI?ג{jW5?(^"Ie?S(?6 ?㽿q?—oH?*[&݃?ä?X`U?T=?HQ ?煐`??L?_<9?;{FN?[ {?]nD|?Α; ?sy"aa?'wQ?ğM׸?0$`=?(.N@?㑠?,G?焽Fԥ?20Dd?bu0qZ?7#?Rؘ?bY?U{Y`?Hn?pcJ?scO&?ᙉWh?<?R@?=Gݨ?p ?N(?fmG?+?+c]?푄v!?רPKN?f}~?CBfܳ?x)ث:?"{? .?pJT?3F` p?.Cj?kF@?_Rx?O?]% P?끟x{?֥?Get?CDJ?=U?Ww2?4l?Ӗb#?A.H@?h#?C.?]6x?=/ ?ԧz?=?"@ ?/Lb$?`ֈ?ώƹ?,@?:s?Y>?%?"?ލX["?]N?\jZ(?({?s ?2>X?Pl?Zgl?cy#?ޟ?P/?]3 $?jP??Vw>?58?4͓@?4?;Ԗ?^sM?՞\®H?m5K? 5D?Չ t?v7X?ܺ4Bgn?ϟW,p?w:L%?tqQ?=*r?Ϭ7`?QN 2bY?|.<^?mӠ?kP?7PB? W?sj81?"W?;??y?N(?&K?p?4S\Ѱ?%?ĥ P?D.7?e?|F@?4?F8 ?ҧh'?H?.y+?- 2|?VyRI`?[,.?ķ?V|^c'`??|`? Oa?8!+*?e?<Č? uE?冟[I:?Sc?נO?Yʵw?̼e?2 0 ?_0?(j?m` ?eiA?0_1|2? >EF?ȴb?2iʳ`?'?ѠPf? M ?#, n?9_/?a}?y.£"?p ?fW?,L3?@<}?j߉MS?3?b{?ǗT?ǒ4$?$J?X`?f[?gPÈܠ?u?am_?*3d ?0lWzh?Vu?@D?۩?:$@?Џ:kx?^(?7ɠ;??-LN?䂼@?HO?x,?  ?~E?uh݀?Ƹ?p0gK?kVE-?%+K?7ѣXE?m/E?igr?ҵ^?ˍ$?۬e{?dn`-Y?]bk?GSU?f(p?ވ5T?Q?,d?嬦 ?Z _?גO?ĩ?Dz?21:?O{?ы`?+&c,?sjHf?Q?վ`DZ? f$?뀶Q??W)ټ?뜍s?oz&?K?t4' ?M-m?nB?0# ??0:??r4L?9E?)qS?Ǹt̀?l]2 ?5?M@%.?\D?DJk?,8m=?wtv?Ήg3??pL)O ?r?Ʊ? 1?ܸR0?ݕF?oq?wWh?u?_?ך)?{JB?`:e?y?8h\l?tZ&?#@z?AP9ӑ-?ަmy? %|???,6x2x?0x?ԻΌQr?i+?CiE?OU#?vQ?ʷ;T?G?҂??ϰ?]Է ?䐞/?l?Wt<8~?g'|?q?4?߯c)vT?q&?X?~c7a?䳆B?['??,|?DeeKe?iːET@?t8?/}? B,?xrtf?ʳ'?ƿȨ?pе?g\?ۨ2*?<NR?Ý){??+lv?yo|:?iB?Wzd?Fyi ?1;n?&ڢ?$!\?ǃTr?u2i?9F ?/De?|?h{?~TwN?cHwt?ךhi[??5?) sn?X@??O#k?ܫ-pf?F?R?$$?|5?Ϸte,b?ˢ!?|Ver?Ư^(?Q?0B?@)?-7c`?xZ?Ʉ:t?暷?tih?ԧ%i?{h?u'@PЖ?ё_R?ja?? N?oݟ0?cT?Sٻ$? ?mWT?NĈ ? :D%?:? fs'L?:4?&H?W-W0?W٬? }P?P?.?ĊPLM%?hx&.?ْg ?%|%?ӑ6W?"̯?jsYb?p2Y}r?*L?]&?iҍ;4?jh{)~?b1?I0"?Rh?)LN?Ht4 ?CR?A+/?{]?෣٭+?)mu?AL? ?4?X?ߐQDք?6ȆD?l8?;D{?ۻ/b?Ż ?kK??_&R?OJ?O0? tz8?t6V?h\K?H/z?'cA?<@t"?x `?L^*?#?]e$:?퓅?&?? m?Xs~ڐ ?SZ?ȪOT?:Z%q?̵H2?`\\=q?Ó?C?#=:?my?E?<.d?u^?ӀB?+r?3XVY?u@#϶?֒/=?ٰYM?cb??+l?пS?[90@@?T 8?੏o?dQU7?`8'I??pi@?j&1? G= ?Ѧ >?U?#?ǷK?tLt?_x ?;SV{b?SrT,?vB9L?b:u?楯RH?h?X?#?I?"D0?Lu?^ y?#ҕ?ﭫ(:?9|?Ҟ5ں9?˞9bS?U?b ?(?e"T!d?kUS?R5cD? 6+5,?-Ro?⬟[i?;<0?)0?H[?ز?FE ?kDj?h;qn?nl?H `?z_?H @?Q`$? Pj'?_&?  ?/S91?fSY?v?'"@)?f?"I;?cB~ z?\;,?B'?E?'?I7ӥ?~bEm?dWuZ?ߔ] ?'xw}?hy?;̒D?PEV?]^Ҹ?ՙs8?9?j?t^(? :t?^}?]?I\B?eѣ1?}ね?":pU?ڊ#K?$8Fx?b\?q(?|2*'?٦w?.ef?$R=?w; oY?t*?L*k?+ו?vF=?+3?Af?ڀW_P?(54@?C[I5?ӃZ*?䏊%/r ?T ,P?ղ?:F?Pq\?~?ލAp?|mbY?z&8t? ?mJc?Snu.?<p?A?'~?ضE??G?Z?RWlY?pٴ?06S?қx ^?\ h?lkmm?s?*1?CpYT?VjԮ钮@?9~c?ww0?쑴|?j?BG?1O?T5?\@|T?Y1? _?KS?ݾW5? d?Vװ1u9?Cg?Mv?ޠ0? CW?tL@?HUAP?਎{?9?#p`?فNV0?梈1;??X?s?Ӡ1 ?Щ.?ޯnu?t?ء{M$v?cq%?J?||d?*?ϯAN&?z40?EFe ?q?CKQ,K? 8o?8<?Pl?S?C4?hx?cY?mpn?Fg?f}?SNtl?C\$?1!d̻Z?{d?؃lkt?ƌ*D?4-H7?֔WGEb?8ݓ@?uB??-kI? /,?9$j ?B&?qb#?hfpt4?oW?܄ͺ?ֲÀv? I.?ಈ#z?A[Oz?@#۰?;tb??t#?{q?x*0=?a$?Z'?yMZ@?Lp2t?mԝ9?@z0? ^z?ڟEDŽ?KV&?d?c4%J?uҷ?]_?Da?-ݑ(?a ?wuBѰ?IJJ?(?᪲i0?>M64?h擟?ʏfrc?DFiM??!?퀂!?B HBi?[s6?? @?j`e?%d?+?M+ ? iULc?Ƕ$?ԕ8{?`g_%4?S&G @?Ö?K?/aS?eu^j?u;m? i'?SW|j?Zvq??Qh?r3][?Ś6?2Gib?:KMF?|ٖ/?;q1?L?,?*_\8?:[#?80?ީ*?$?@6f?<8?ꊘ~X?8?dk(0?ׁdn.̬?p_!?OB?Bb?t 6B?80?ǀ;U?%پ?/#X?]?]T  ?-F_?Gj?MR? IHM?Y70?|?η?f%_?农7?Q.*?H?}I;z?0C?Q?F B??~?V=p?[g2p?GtH?b?(>?+@߻oH??o̩F?!ؗ?ޅٿ[H?]0?jf?49?(Td@?Ul?]AQ ?쩉C?a)?̒Nl\?*?'oU?@?ȓtK|?Ϥ*4?َ#2?_yw?$? ?@Kw?ҠS?;?ꘁ!'?~?y7.?ՈT?G;o?A5z9?U'r?IXN?i)?$pR?l2^h?ԄC?nu@?|b?U-1qX?˩4r?x3?rd?Ol'?W/W?o})?AC??GR2?kx(?g v?ϼBST?4(B?%??Ӓo8?%ȩ$?'--@?W;?;v?量%?%|C?D9?ӫL?L[ ?ʨ0S?{&?(=#(?oj?(C?I`? [7?8?vq ?AT<?g0?aQ|? ?Zhp?_N!?k?j?IE?X?Z޾~?bXm?ްOT?n^a ?y ?뢦?k A?˲H?1rU?x׾!?\?XN?f?0y?s<?>?7ﮏs?iGd?%n9?$ p-Y?+k悼?M=?gpEV?dw]?.**L? 1+??ؒ0j??#fsX?j}h?H}$w,?80?L(?(I8?5A2?;r5cP?_٠?ͦ>9X?ɡl7?^^V >?]?AOx?6?ǝ*?Ȁ:?b]bT?Cdl? ?l6gV?tyZ](?ح yR?4gk?ҙۤ˕?-v?2,?# ? ~f=?ߟf?.?;@?@%? U? nN?w?->r?Z?{8Mz?ͮ7?\p?T b?-r?(®?,,?? 6?N?C8? [/?@?f * p?&oF0?¤f;L?Qp`?Ai?(?<(Ȋ?/1T{?*?W??C#c?Ȭ`?۠C??@FrsP?-"?fRx?P*]?U?_ ?Cy@?0?萴 ¦?3d?؁?߼z?u?`Ry?Oe?B?Ű?'iJ@? FKr?0JF?)0?CQ3AB?V?REh?!EX?l?tw>?-A ?^i5?> ?_L-?P`?X?"g?K6?6*5?|?]o|?2?LJ?祐( ?iJ(t?ʖ|?f0j"?K#?[ ?nI\?BX?о'2?󦮹?xq?aN?*@?Z@dߑ?gY*? f?\߈?&hQ?V?F?`3-t?Rnh? Jʏ?8g?Is~?vT'?KSLT?2?^>c?ު@? ?,?]m ?[?\?Ĺ3H?|B?]WX?X?YO?cfy_?i<8??ɤٳ(?"?v? .?ܿ?Uý?m?Φ z?qh0?g?g@cd??Q B0?-)48s?NV4? H`?KTI?a"aʨ?T\- ?o4iP?J?[^?&h?E?!xo?$f?b`?ڏB?O<?ݾ ?w?vQ|??uBw'? ?θM.T?3 %f@??KoFV?EP9?E?7d[jT?ҞtY?RG?A+?bN|uw?ԭ>?խۜL?JhD?7o?"א4?`>fJ?})M?X5n??ԪcT{?/r=?KYzW?T^b!?ZCQ?8p#x?앙u%?t*?oJaQ?&?|4F?RV1eX?(e?ǟj0?e 30?ȼS%P?4 Dx2?mk?y ?'k?գG$?z?GM?{9?&BTRF?bm{?G"??) ???]u>?Z?!?&:?׭?Q㳡?ȟ?%>Y@?iB ?oX?1H$~?Ѭ*|?Q ն??.|?3@?Zx?U?A)?g}{?f?\HzW?RX?eG?[U?B6-V?6?n?SNB?Sf?Ʉ?릉\ ?ή?W2?d?U?.= ?4rZN?5d?ċt?!Ƈ? r ?ߕ3v?ҁN)?1NZ?i^??1?BxU?&T?u(? `?q6? t ?{|-D?>κ2?فJD?Jat?&S ?<- ?B?Ck[?&Ⱥ?~Lb?G?μJ*t?4պ?C?b%i(?k?ݑ+x?AXZ?*\,?S.RI? ?cB?աp?3Hy^?F^`z?Pj ?M?ڄ#X?ƴS? ?96*^?Qg??V$?kR:?H\?dd?qWM?滾@?Ҟh?œ0}E?'&?꜋dK?A*?|?L?Tr'?Z@v?fp?i!T?NsO?f@?G\_F?0uϤ?vV.{?~+At?^LW?bp;?h)>J0?O7ڧ?i?*?|ެ?kw?H]?+Vt?u:^ ?dVN?y؁;i? f?ç{?Eǡ?rR3^?a8?b0M?ۈ?q#?ti?؇h d?>?]I^'Ѭ?AWo?ËzBh?R8$_?#1t?7vA?Џچ?.g?O|i?qݜ?|.?d?FB?Z?kߙ ?pEy`?s߁?&oXv⛼?Hj`R? Qj?|-W ?jO?p?7Z?JI?lӵT?RP7xC?d z?:P?B-?o ?%)H?è=$?W$?m?pel?vڒ?ǟ4?[u?ן;?&y^d??62?јF?Vuy?yuH?YJ?$&?X,e ?>*}?zT?ko?C?Ar?@?ދ?u ?xV?ћ]H?MUD?M}?uԪ?FN& ?nArU?@I?E?i?J?I8?䗵֏? ! ?Ya.?^,x? L?4D?=? YH??g ?TT?2>=?nBʉ?O=?jG?,'t?csU ?R/?8^ f?*T|?1j/]? ]?ᏺq?@l?)'?Ε?a?D)?U[&?1Z@?1ْ?n?F:_?wĵ@?]RS"?rZ?wb?bqY}?§8?ƚG??3U,?˚*p?6X ?rC? 7RR,?1?)uA?蝔fyG?ں&?=?!ͯ?ǽZ1?P?q{,?'M?RPߏZ?ݫ?dx? ߣ? ed?]Ul?|&?=?݄ ?x9|?}N.?\?*}R? 0u? ̶bd??6+?q? i4z?؀\?穂N?h?"p?sϣS?čH?Bi0?D@ ??nѨ?4dPb?op?8:>?Vn_?m=?Z?ھg?ʛn?͂d ?!W?{_ ?D?&W]k?m?4?=_X?ۮ Tr?]Yj?<8?C\=?;]?섭zx?g.K\?tN?ɲ#T?EZ?Y#8?>?)W?@C.?\xy5?\$I?꾳?zB׉$?ȱ.?_֭?Ҫ( ?Ič?|Z?Ha= R?LLH6?#-?oJ?V]@?o?6>Q?x6M?챓 t?)?S"D? !IE?^v?ȁV?! {?}`? ? Kj?]5?ʠ/x?ߖuE?ނFy?e ?= ?{7?[?;A<@?r!,$?@̭b@? r8?_y?ҏ{?5?耏](?bt"c?w`?lq|y0?൸ӽ|?ӗ/?Á? 4J?[? >2?Ϭ$ ?Ap ?3H?s0;?ݸY.? iv?L"o?öTH?coh,?W w?u?ּz(?(-$?J'?W{s\?1ЄaZ?м`̒?m?H@ ?L78J?oF?ڊl?Փ~?J/??ڧ)g?ޱFL?OZ?ށ e?锠&?+v?`?R{?jf/?_?i;I?Jc?ֈ!]?u悈?氮Fa?H?0?~ب?o=?glP ?KchH?&I}x?ި9?[a*?:?W?dVָ?.v4?F?س?s}hf??C|?Љ*?eI?Gz?)r?鯲=4?+ ?YD)`?4U{?툟ޓo?u1z?1F?ר33U@?YSa?կI?գ5梐?낎]R?}?ԵED?0=??a媁?8;?2? ?jo&?u? `?g?,m?(aQh?l/J?G\?Ba@؎?wͣ8?ǹ?T[?X:?u7H?lj)?q b?[~j?y ?$9?L@ ;?4}Z ?q]{6?̌Ge?.I?ۀo?#oQ? 6.8?Փj-n?hl?fD?K``߁i?L?܄ /?߁DP>?deb?IA?{Uw?{y?hn\E?1qt.?xj? ?љi$?I ?g{M?ŸCBǬh? O }?5 (???">ݨ?q?: J?op?ʳr?V=k?hؾ\?B݄!?ܦH=D?c3?X?i<6o? VwN?Ӈ؎&?ᔛ+6?7K?`ӜN?އ?052?n?ӞG?ͫAD?8?!T4?e7oU?챵)Vj?؛?g3+?˞?S"('@?QB?QKyQ?_R7?Ɩ ?⁔c ?Y{m?ߠ\7?aCuG?̰e0h?܀6E*?L?v0?Їq7?6m?}?FfqȐ?2R*?O0k ?5?hd?BH\?#\?sUj:? ϩ8??寮T?LX]?}ᣣ?ɖMx?KPY?UOp?-Oz?0^$K?ބ *?i?E4@?چX\?u?ˑٰ?Ƿ*h ?mKY4?o0h? I\?Ât\ ? ?Ќꕐ?T?c&&?CS?v ?~j\z?6??4??9/?|t?O2? `a?)g?mڵ`?5fn?տ!(?I?'t1?ve?ҘF ?ڂK:n?Yty?Ȃ? 1%?\? ?¨WҴ?v\贜?4?᧸(?һK?B[/?ʋx̐?Pzf?6Nu/?iu?K!?'?q?"b^?&9C?>;?l_5?KpP(??ˊ^iX?;y|`?%?n[f? ?Aܸ=?{ ?%J2H?Kɭ^-?K*<2?;??E[ ?W#?R?/8?v@?}ȥ1x?^"ԉ?(Sϩ? @?ͻ?RY?ߪWW7 ?&@!?N&?o[t*? ?e?|@?| ?>?߶(?McJ?4F`?%3Bu?܏R|? PGi,?(kS}$?ԠcY?;DB?+u?Xhj8?g֪ ?$" ?U?or>SP?h~ۈ?AVn?}+朜?Ԑٿ܉?Ck?9J?m%{ ?ejn) ?vaʮ? w+x?끜=?yJ!?s\?&\U?ډ?'l,?p6?i?ﮩ8[?*4?&J?ZtW?d5?dќ?mJ ,?4̶?}鐊?⼍Z?I{ɨp?hܡ?2h ?C?v?ȠX(?-w~t*?-[?#LSs|?-9?K$nx?-M?k|\.?t_H?S[?~?4"? L-?fA?)LO?m3)0?DGҼJ ?K?u&[14?皴=5?x.?$!?oIN8?"??5 68?߄l4?هcw?,2(?7י?@.V?㳽u8?8T?g$)?&b?z?aw?ˬ7?.s?csA?! y?U8m<4?٠lu*?2 ?c*~?ゐwzqu?r$?>`o{?3?i_@f?`8&H8?M?wx?-?$94_?Mҋx?]YLM? ?{qش?w1?!ba_?“\Lؠ? ?j-?ñ,?K?ɰ[X?n4I?A[$F? 5Sx?̊X*?c! ?3U?ڡ;?>?ɱ{Rl?C|a?vp ?͓?@Og?_C?O`H?e.Bq?K٘? a&?*8s1?$O?fN?$uh?&ow\c~?hJ?;6L?ZTt!?w 8? f_?[?s?C?/`?ܔpK?*?cW?e }}?JW#$?0j?toH?ͣ"?} KL?ShG8?bڣ0P?Y-^]?f?عnW? BF?:?8 0?f?)$:?ڜ d?\Fb?ɣK?Z$8?@4L;4?@?G?i߅f ?\ >|?E;/g?05?Xq?о%(j?芠Q?؃TȜ?u?9Xxr?uߙ)??7?M")b?1;t?: ?+~?向i?7lsl?!?Us`?~}WXX?QJp3?$?Z?䶩ԘC?.VP??7S?.?q蚋??p?އZ'?҈HCޫ ?1 ?"LX?U7P>?.?2$C?϶ы?;p?/?胥`? h?PT?0$ ??T0?p?޼8?x)v?/L0V? {?:A?ti4?Ry?,D>9?fS? ^?˰}l?߷)a?Vv?E?7K?A#f?ڽ>?e?O?Ǫc ?5m?9`?􀨌?n?K?wr?#?SWp?›D.?9?X 9x?hY[E?j?b|X?m+Tpp?j`-?T~?x=?@? |N᪵?< !0? PپX?ť-,p?dSNC?ah?ڢD1& t?Y ?3'#m?ǝJA\?#Ǡ?7 `?՛y\?Zctd?g4aX?ԅ~?)a?_K^?)<8?%t)?~8(?*W?fh?"?}#?7m ?O$2?(HD?j ?᩺t?%#̄?˷-?Ɋ鿿d?6.?KԪ,? .0?,8{)? ML?*?莢9Rc?I!?0s?{ۢm??ȊQk?w~?l4lt;\? R'?P8$?-?/pD?q?׸0i?yփ?FuP?Ӿ\?8U4?Ggat?~ox,?Ÿb?$?7ΌrT?ݍ a?-L?}8 ? TEmH?,쇧?CU?°#] ?ƞϳZ?۰9?⋟e3??J?pH ?Kޔ??[ ?- I@?,RxZ?p0?;e??ts?0? 靕?aLc:?:?f?2 l?`+?㈹ `?m?Sl{?(S "N ?JoB?\?F` Z?ʶ2??V8?v? ?f9ۇ?Z콄·?փt?/e'?{/i?,_ (Z?)ܽ?;5?֦h?C@?2pk.?а^2RB?E?n^(?6qE?Ke?ӏS?~ef@?䜠R(?[Ь?)?_{f?a0?荅qD?:~B(?>0k??/g?$/ӫ?J"s; ?ҷ/??Tc?쥟cA?D?-u?G7qF?O{{??j? j?n&,>9?0yNP?{Vo?܋󻌝n?nj?!mAz?w*+p?᠑(0?ՒÔEj?sbX?n*J?J2?^bl?qYW ?7@?pF5?? ,?l ?_(?P.@&?L?z;?9L?hDžb6?4B?E_?Žv%?ܺ-. ?Ѽ$?BgP?z V?ɦg7٬?kvP?Nh?Ą#C?ҦBF?WfO?ڭa?y?$A?D@?͙2c,?oi k?Q-]?yz?/?,w?תּ.@I?`ר?l"Z ??>F< ?0?v(p?`!B?v?a&?%\e ?{؜?b?L,@?gO?݀@¼P?ᷛ ?[9H?}l>R?&.P M?o71?52?Fh?!|?fs& ?fh >?4FA5n?J 0?zA6??Iǥ?|[!V?st?hk=?-=?*syj?ɸ.l?Xǝ?Pu8??/cPd?C,}H?87?7qx =?T{`?j9?|c$?z"?e<?ұ0 ?Ӝ+?ޑ P4?L1?zr?1>=?F8~?ǂ?-%Q?⦱m?>-w?hF1~?G~?L8%@=?̱$?+=8U?-rS ?4>6?С?B?ۏlu?8C3? :U?R?Ա 0,?pN?bA?ؑ>?Ye?}A^L?^M? t?HMK?qJ?j@?]?6Zh?ׁZ?'Uzx?ѯ}?X(?\\?Ɩ;}#?7A? l?ΙL?L!?e9?ޟiM?→g?t?2{H?Ćv9iڜ?|U?鱱VaH?NuF9?1?qYɧ?jC.?ғW ?ϔd?k?俓+j?a)t16?t|?$9T̗?2.?L\,-?2%4p?8JT?jN?MLJM?21%m?t93>?u?-K?"j,??5?ddg!?+z2?V7~"?v?hD?ﵖ??v?.ʌT?C@?(Ԫf?q ?̇ y? ?DDj?d;i?1k͛f?>1?(n6X?4g :?@?U:C?` ?4$?Zk$ÀD?}àI?i^Vd?n?Ԩ|%?L?}?Bh?؛l3?  ZD?;V? Xz? }H8?g?)P?:H?Й\?aA^?n? ?!y&? 7O|?I?pR?ٲIg'f?/p?pOI?nc?XsHu?ٶHZhQ*?apSZ?8S*?ŠT?畵'?%%6?UgK?唐?:ʎr?MKX?g?n>?ȁVRX?p'? ~_?~l?e0 ?>*IŠ?mPa?Q)?.O$-?PA(?a?}R?R4? A? V[?_1M?], :?S/K?6{?U̒0?? _"d?{ey?_?/ 7U?~>?WT ?u_b?cD_P?s>?X ?DpA?g1.?x 9?.H?ؽsb?]5"Qv?xmI?Pפ?Sr?Ⱦ?޳(1Ѥ?H~?è4?'uC?zx?YLU? ?+]@!pz?Ҷ;nR?.4rL?rQ?X+?`$B?٥Od?Րw?پ@:=?y?c,?`!P?/@i0?A ?I>s>? 2t?_rF?mgZ?݂<?0U?? fĊ?Rp ?NO?KT;?ޝ,? ?m%T?v*e~m?-? j?[2?-{@?%}l4?Л)?׹`&j ?糏?:P?FH?e?y+??9[ ?ϓ?b?W~d?jh9?盖Fx?T}.\?}IH~?ѩD?fP?|+?\}5d-?ա|`=?0ܖf?D} ??$a?ʰ$p?Ҍ?6aKXU?.?ch?[!{?΃w;q?ﲝ?2?VkѾ?wQ?W?V?l1̚?uk?U9w?.9d?]$9?m,?䊖{H~?:?₅C?֊V? n?BT?Kb?yr?|~?O2?#i?{A+$?se=H?:m*b?{C?pm?ٍ4K?ҫM? 2B?@?鉏L?cEtd?E{ ?u? 3?˵+%?1;Z?m??hӇ$?G恳?FI1?q4AQ?+f?Z?j?Y?0?owI?۲?"6?\δ?΅? (2*?;]A?{f(?ОD;n?^g?ں?≥cx?4`G?RޞH?*ܝX?@t,?ӹ7 ?m9(g?( =0?]F8?+?*W:r?]Y?i`=?& ?Ե'{z?ўkN?h??NV?2?N N{?He?ߒa?9<'#3?P?xCx ?%zϬ?(o|sm?.?:z&?Mp?oPg?`[?t?V'j?~?畋x?Ko6?F]3?J?q^t?2q1?՛FT?}ߋZ|?7+?3~橃n?秠f` ?" _?/B5*?FR?YT?z}{?0N?h?Le?0^}v?ikFY?y\@?j?9y?n2Luq?j?$+M`?^:3?%3rH?+ePƄ?R4b?ڰ?ImF?K9?eʜ?ጁk?a >?hВ{?׮+?ľf ?eϲ?S. ?{e?є?L!K?u:? ?|0R?uwj8?,;E(?ר~-;?CKf?뢗Ӿ?8Ëso?>t}e+??]>.Q?Ӆz\?G?+T9?G?nt`?qq?Nma?d?ƛ_x$?կ+.ό6?˔*J=t?8)? y?j䉔?yr?#WL?Eb?U~z?եQ,?ҽ?;Mru? ?ذUZ? T?%?Ɗy!?-p?9 ?>o?8PDž?SH?v0?J2?9΋W?XK? I?q]? t=?ǿ6g?|uլM?qM[?,7;?x?vm?=~tpb?tx?6$%E@?˦+w?@=>i?쁗?Q ?_-X?0?Q7 (N?L?O[E4??kr?ਸQC?rp?y3o?ڏ$?׳j?7F~?( ?˞J4?XxI#?V ?:w:e6?A??I8H?ޑ&Q~?Z5^?(Tk?)3?r<4?i^??)2?'"k?݅Q2B?*!?i^?*=?-;?O*?9Ƶ?K?=ɫ ?T?Kgҧ?tE\?y9J?t"?Q?p?"?<_?Fg~?KE?سw&?A$?W?,;?vQT?ҕo?;~wP? 8|?N@?-_>?Q;w? ?\??{;i?ĽK?J^?*V3?IL?Qx ??𮻴??L?ꪇ{?i+RCP?&E1G?롹8?g?҅=?m*w ?Bns?A?!3?xBN?Żd?ѯY{Tz?&?$?Y?U./?đ2P?s:?: (ST?f0? &?Á(?>gg?e*5:6?z$̍?Nkt?p:R?ʽ?k$;B? t?%>?E ? ?hnDs?Du?j"~W?Ҿi?B?JF?lD?^2G?Й\P?/=?֨"p?̒B?F7P?^o?mp1 ??=S?g ?ߵi.?]A ?)?2dH?Mh?o?-v?/4A?P.?wa˩%?>?IG?Z?сYB(?㝁e)?M ?ؼR?ѯh?ѽ.IR?v:M|~?+L#r?G2?Q'?ih??Z-۔?Mɧ.?ڰ?]H?mJޒ8?\G??š?kF԰?}MWj?̊A?uu*|A?㹣/?IZp?zf%,?L ?!0?H|YS?2$?@к?z}?Šۍ4H?la?C!?0*l?y ?a?BH?zN0?H?נw?mQ`?W tte?؈ ;?_ ?LǓl?;U]+? кU?iN?^WT?k?ӳaR?Z29ҋ?ɷ\h?R6?#|_?AA?n1? 4k?4+F?; ?$c2?ZU?_P??ŊZE?dћ1?СRz?HG?elN?8FD?J&?k??Nj!?9Zg?9?? K.g?d&Y?ڦ~8Z?Ұ?blzq?ҵ?S ?ۨc0 ?}s?4\??GM?\[gZ?[Ȉ&h?2Юμ?%X%?ɻ?"V?J??T$? HL?P?핵L?kS3?r?Iֿ?uWI6?̄T;j?soO8!?Nx?4ba5?0ƍES?ط?+@?9Q?%os8?VK?P䰀?ҏ D ?c[=f ?Ȗ?} z1?۝c?m?yVC?Zv?Aȡ`?hG>?r8f ?9?e$,?pe#`?e'? J$?7a&?6?b[Q?3?, ?o?˜?G*b?U{?.7K?Ѝ3T?Dh{p?1-?]2>?‡t?݁x? Y?r'r?Op? +?ekB?v2Py?- ?4(?쟻͠?τL ??ղq$?@??P}>?tv߁?8oǦ?ӳ?a?f̀7??jD??#M)?!7?7%? 0(?3?Z* ?+p?ׄ22UI?૶$X?;-^u?13pt?2.,t?$ VH?W{0b?t`?7sr ?93??%qhz?[?YQV#?[*JH?*1 J??-qխ?9?_? ?q?yak3?=wha?V FS?9S#?~_;N_?ŗ ?*9+I? ؛?/&?hL 0?$`0?.eS?9?ׯݼB?m@?xGڡ?|}@?wbt?YS^?B&P?L9+)?4'?/qE?Ҽlir?dsF?*r_?KY7W?j.?2l`?_?닞C?V&R? -? Bb?ܲ7 W ?SF?ݡW@?U_:7?ڨ4{s?J?A G?BK8?H1 ?5 ?]^e,?wbP?6hw?tNH;?-?ċœ?gM}?ʮjt?ԧ#?\~?W?>'? ?\3k?}?vԂř?kf-?vv*?ټ|B&?͒|?s7V?ϴ6?f}?~?%?w\?JzL?x?%$?X G?b2A?7L?P? uϞ?*1?`Jr?2Z?ҵu9ts? d~ ?JLN?阂JY?iN?J5+H?U2?F;?Jv ?L I?̶_㈈?t? R;?@e?HL'?־oXd?RYWb?+g?t? FIV?#"?M2?łͷD?p/V?|? |?$J?#屹?ՓFaϒ0?lY?,?8whpt?8Cu?B  ?6?o?бVv?h5s?:[ ?v'0?xL?폞 ?ъo?Jz6?U M?n2J ?Hê?9O/C?J?i?%`x?cE0N?84=U?׶<~?枺?ٿ%^?5?'#?ݦ?k?Z?!V?SV*>p?l=f?@2 ?,x?,h=?)ltP?峞?PY>(?# =µ?A P}?uGG?T:?.S(?౗#o?Xjh?/?ZJ?o~t?eѦ?M5? s?:۳? ?ۃخ43t?^??.?7LZ-?h78??'t?;XP?@7b)?΅h?{ 1&?2O8?Bf)?Բ?kYoh?BP?CztY?_UV@?>}h?bB?0R?Օ.4?-|{?ڣVl@?=;MRz?[SP?Uv?zB?e}?/oM?`Q?Q ?+a? @+f?K'?Y&??wE?} ?㺩Ohl? SV?0!\E?t_`??ᨒ?s?H ?R@uj?(nT{? ??V˛?\ ʸ?HLp? 6+?hd?hgw?JTmx?A?tF?*?ї7eVx?L?^r??|wR?8o+?qA?$}a?]Jx?n.,?mt?[]殕?ԵH?ͤKdKd?էҏ2R?VV?(TL?Y?)R2?tR?wJ?ܓt@?Q?-NF?ȾgdH??S?Mk?тǭ?Mc?۱rJ? #JM8c?\B?wZH/?¹P?q==&V?̼&[? 5|?Q}?_ܭ@?_ƺ?g#Xw?SF׵B?q4?Q,^H? M8 ?a88?j|9?تW2? ,A?Q.g?E䦀?b@;}?ی6?g!?!ǩ?CV?‚Ķ?߻#MǺ? pR?C0>QO?!Ur?捏S?tڲ>?r'0x?q??zĠ?P3P-?!'h?ÿ 8?)WS?͈q(9?)&\0?WUw?; ?z](?Ujr? s;$?5 ?g&i1? ?rI?56aZ?ZGP?T.Y\?^/z?.%?쉉 ?(XF?j&y?&? ?f9>?ԣ@J??,|Y?p?}5v?+z98?XCnI?k?  ?º7?ϛ-?]u?NnR?SɄ?]%;(?$_D‘?%x:jH?g0?UW?h%?,0lA?kOki?d%?7,?K?鹁p!?Ln?o? ~h6?뗘2j?.W/ k?U?@*AC?ޞf?F|??)?GM?R>~*?j#?e?};Y{t?UX? T<^x?1??-xb?Yq?>?2%r?^?Ȣ7?d]?3P?۞Ccu?xP?tT?Qd?_4iK?[N/g?hvs\?jΎ\?偬?ߚ|U? 6?kZ?ʀde? (>?(?ҹU?EKSj?N@(?W:?W4?');Z?==0?$KOM?DqU?+# ?ig}g?ӿSߘ?QLx?`\fZ?$^vhK?¥?Lm"h?@a?\0?瀬J?Ifm_? Ms?Ӷ(? sZ?)kAW?燉x?Jf?@zDĐ?Ό|?}H U? \'q?T#8z_w?J#=?FruȀ??냚? _(V,?Io3L?.p2?e4?Cj#?E5?B?,>P? ?Ғ?#=p?⡣+u?J'Ȁ?ܴ":?i??K?ӄrrO6?1%?܊F9?=?ޡn?eAdљv?V@h?:?ٮ8$?yPI?j9a?ڮ3?vf?֞ok?㦏E?z(P?Wɟ{g? RO?0?엔?l1:?z?Ŏƀ+?z:+rp@?Y2-?ͿQbN!(?e?`7v?GUq%?A?^8!?Jw?G?d b?씉?PL'?貇l1m$?Ȝh?jK?ߘáČ?{!? gX.?,?|\?nF-?.7?Qȝ?[ ?Xo' ?.Z?`א_?gH;ݗ? ?x$5?2/? 6Z2:?ؚ*E)?"a0?ZiD?A̳?WȲH??`Ѱ?U+r?[١h2?Ӛ?śf ?Õk?6Ap?J8\?(M*v66?':?˔x>?%1D?,`?͹Ƈ\?P?]?ތW?r?(7'E?]Y}X?ʬ8?7do?z2O?rwI?kɰ? r[?M{?ɌN`?jx?\~4?\\Ei?䏈t,?ԐfC@\? ]ґ?*rN?~mD?'?e$v?  \?b5?}8?R?\?9bš?:6?Vr 0?˕@i?ݞ5$?5?Mq?ߙ{wX?&n6 ?a0.J?>B? ?\-?Q~ ?%,?(2?ooiԼ? O^8?~5? Ez ?*z?+8^t? ?pX”?ʙ)?0LJ?sc?޺Z??oT? ||*F?$0?6ُ{?&b?/?O1? )Ç?ш@?zo?ץeӻf? Yה6?5f4?L@?&Б9??ڜK?׉-wj?f0?g?;3dD?}^u? ? ԧ?g6]?}?/?Ԛ?R?u ?b?v?N?W@L?x?Ɉr!?N`?px?ޥp•??OR?䕨=f?꒕?P!p?Dr5?9;=M? ,R:?r8?=f?aO(?&?*?Bp?G(@?ho?+?3?W?Yx?V?aZ6?=gW?M?ԮV?xQ?w(y?⃚zT?`4?G(GE?n? %+,?嚠)?yX?b 0?o.?lTRl?Q_0 ?X?-ŎDP?螈ft?c**?A]ݯ?=4?ԣo:?K?ӚIf?¯h@?&?8I3??h?33X?ΜWL?Rt?K>?>H?۶i9?mM?2ji3?wǀ?6?KA~\*?㬉JO? 78?n^?޸^G?롢:?#&c?}?}y$?ݫ Ҧ?ӄ{5K\?}sf^i?V L?i 5}#?ҒM?ژ?įČ?0[eT?_,?B$?M?ݚC+?57?FM?WT?嫛?@'c/?l?t*? V+?y??gjl?Yִ?œ.+ե?8Q?6??9?؂ (?%'?zfO?Ѳ/.?xB92?ʚ*?$l ?$ C?ٱ\kѯ? ݍ ?I?܆RJ?lEh?Qj?֛?&A?['>?b0r?"f?!H?06?nJ؅?}4?KX?gÆ0?3eJ?iEt`?^H?H4%?ӱ{x?4?s0? \uG?POh?Ŭs ?̳O ?޶?U?˫xNN@?eQ̹?Ġ?́J?ųUgt?3i"?F0?ᐏV?Ĵ7G,?!0?*̪?]S$O;?\)?C/w?:?Hp?Gn+en?te6?ӈh ?5 ?֥ycv?І4?E;c?(ͮ?ZVZ?ʚ?ỽe"?ܪ>X?[o?z?)?"szX?ǡ2)?ӄG?f"+? ww?$ln:?|쯤?ߧ ^?H]d?,L(?TV,?̪憐? k(o?{lO}&?̜/b6F?0B?/ݯQ?%r#?v8?¥,?{\҄?^b?\(?(a?nCLP?(S?;?L,Lv?(C_?*??ꁣXr??8@}?,26o?>bP?͓p?&g1?ዒR?/Oj?بܤ?֛&?W]?)9?λn?'J@?<$?+G?^ud{O?N+'n?iL&? *~?1o?0x%?  P?̄MG?RM?P}? vB?1V?=h?0?>?i͕?_I?X?n?e'k??I ?|>@?j/7?몷i?0wl:?@"-)?5S(?~7??\Hx?I_?ܦtQ.@?ؙo?.J?[xY?xž?H!h?^~?bϦo\?56s?/W}?ZIm?ӢZ,6?o0d?嚉b??|R? 3+?2~? ٍ?w7?(F~af?G5xy?߃?RF&?> !?~F?ޣOXb?(dg?Л5_-Z?ݞ*?6w-?ə[e]x?b^?c?纔\??|Ly/?֌!;?θj?,?Zݾ(?V`s:?W{"?Ł`f ?? n?Ҽ#~?ۚ̔c?܌D?݅k?÷N?Ö?5?ɴ?A:Fn ?ҩ7M? N?͠2?tl*?߹f *?[Us?ȂsTP?)?ѱ5?A b$?MpuX?|?c?ܶ?m*s6,? ?{\?'V?o^D?g`?Ky;?\r5?Qӝ?0f?L*?rh?Yͅ?żUoa?Nw???z'?ئN?g?`ry#?H x$?]?QկE q?[^C?gj:?ֈ?7 ?爡?*ŸHhC?#:3[?‚RldP?w\uc?@?|(?4{K V?M.?=?[I?Yg=?|=?ڠ} ?Ý]?'=N?Z7?~>k?Re?š#?Ιi?5i`?G`? zO?谂?$?XƓ ?&?:?P?˧W?5X?{:?f ?$4=?epS?v?!=?&u?Ow?SkR?W ?s"=? %#?{@c$"?u?r8y?e Ŕ?B9B?ϟ?À ?ɸq,?p1??{?kܣ#?V t?B;|quZ?B9t?1?YM^0?T(?_"2?O ?r &?F;*o?$7?Sэ?*?хH?Lp>?}@o?ܪK$p?zC?”?;v"?oP? ?o,DD?j2E?Uq?婂_?ŸQ'?e.?³{?)2?^?vV#?Dǥ?Hw?uVN(S?H?o "?\?jv?ށ4AG??ncf`?ҦP?1p]3??خTS?ĨF?&$_?a]?Y ?زݝ<5?]d?_?Ը;"J?9"?? 1?z?AJF@? 4?î?aEY8?fsy?S]]?ҖE?︕h?=?FdZ? ?wVĘ?懀.?۱Y?[Ra?"~y?U d?nH?ޥ|? ?dc"?mx{y?Vf@(?՜ ?-3hL?⽹?%:}c?t?) ?u:?SYȉ?e?Q,*÷"?鮡?m8M?kWͤ?|?늄Ny8?P4?!y?~?=?Ϫ? &?37 ?gv?jb:?!A?VoKl?yp?${l?]H~?Źp*?sil?k9(?0}Dw??"|f?ֳ^gޓ?'@?[5}?ЈЉw?Ş]I?㎋~?拿J?8y?r pF?O(:?{ӼS?q?2 CU??झ??^$g?~.v?34?8?3*?5Y ]?*?NT?s D?ޖMz?!X?ESX`?wQp#?r۞6?5?>vD??M5?9`?Qzi?WO?=?džf?;~?B/1h?g ?E,?ƓDw?v?{Rl?`H?6z=?(r?UFwx?'.6?V?o wp?܆_^.?d?ނ?n-S?| ?ゟcp?L P?R'6(?a z?ҙ?*C.?đ9 ?8&42?Ы? ?AI1?;L(?}©p?JӠ?rmZ?k?Fs8?p?1!~K?: etk?bnp?Z?5A§c?=?pD?ߌA,?홇1?=?;0<]?ˬL?ز?D^ X?h"j$N?`?Pw?@'M?b*?!]?ܗ;k?L)?ۢgtG?ѹw\?Gc,+?5/?kԵ?3x?}?'?ӕ qD?8Ͼ+?o?rut?ڈb[?sD~B?'? V?B't@?]-?e7yL?^xd?eti??p?\B ?4Ň?#FVP?s&?k5.?#?BV% ?P&f?ƍ|!?tOr??ֵ/S҈(?ŜS^?97WT?*&?ߏNw?ڤ(1@?~-ؤ?ip?]m?؄>m?~v3? F!r?$ƬG?e+ah?ϔƚд?[%5n ?j9H?ҕ6@?ȿKxq?pTh"?9T.?'Z?6?ԘX?Iz?i\z)? &?,o-@?͏g]g?TR?(gh?DŽ?-Ғ_?5$?ﳄsZX?/?2זs?z?Mu|?t@)?@@? ?7G?N|?kK˼h?͵tn?Dq`? ?xF?Z?'.k@ ?,j?dH?4w@?PgK|?d8? t?fOT?Ǩ ?:?bX?ܓ*?6q?.0f?/SI*?l^t?^/?w?忣D?D-?KDӄ?fO?דO`??`?/ ڸ?je)?⬭aL%?9 ?/!?%UƝ?fS9?۩v?f4m?uEVh?7?oF?]2tq?>?Ӷn@D?9ZZ ?. : ?Oi?vAUx?H=?œ;zp?ońvT?oCS?ݛtRVU\? p?tyȇ?GsX?:6?f?!ChW?\7 ?.gbw?X?;C?c0s;?Dg*?d9ú?eņd?Y)?ڃ?[g?~* P??N?⺳?/h`H?R+n=?Թʳ?䥅T?~pmn?s@rAp`?笪?F%+?po~+?Y#t??H8?(c"?#?^B?G s]b?4`?Pxp? ɦF?isTW?Z_"M?alp?ԃ?\?+[?qvl?wv?ꇅ&`?d?Ut\@?ޛ?eD~@?{??" /?0Y?Hx?5H%?f?1p?h%PS?)T"?͑?* %J?PzD?죔+?ad?V?RLF)?CԵ?=.&?e܄?Dn? I?|٩?1,7?y{'b?ˋ?ҙjk?e ?꭪U?@$?iUFZv?d?pd?0xR?`4 P9?P?gF?־#b?B6?x5b?ޅR~?0l'?y^Ƴ?J\R? ?ͪP?0?퇄 %?0?pXEP?9?39,?|t$?b '?!?J?I n&?Bh?qX1?e4?@>?ɇ?׎?ղT4?lr`? ŘQ?눾^? @|?ќ#=?/spV ?ݘ?'4Ga?:v͘t?פO?HN?U?Q4B?EWZ?x+5%?ߍ:?X?@z?{LU3?%a*?'{k? {Ӆ?LuD?z'?wË]?n_?Y qL? 9? %e?i?/ܡ[G? [?j?;6b?od?ԀBoA?Jy^?v?&@?Sgov?{6(;?Do?Δ?#TwH?D\˼?ȉڸ;?tjc?iq6?fP܎?(.^?X{?b1?,HeD?yJe?SN?m?@G?<@?Y&x?d?´sD?_?nwdf?}}j;?N ?@@?c?X!H?>t?R ?hق?7@?(9j?-?g?$ "?{o?u|?]@?od5?kߺ?In?8 /'@?&3gc?)?⽀?J%U?:I:6?gcA?ӑTn?/_X?s?=?=ϼP?[E?Ђ9?L;?8j4%D5?hbX?X@?#쫋x?IO?ޡ8M]??Zbߴ?>4e2_?eFq?+f??և+?Ƽft? NeK?.iU]i?n ?\'S?.n?mK?xӂ?P#?;c??r?P?'?,DP?L?+S8=?Ӽ?|3?Е?Y.\9?@U]"?+T?SCr??1bH?F ? 3H?q)jT?,G?cVg?۰_s2?I 7̭???)R?`?W-?A\'1?%s<?yE\N?D!?ݾ ѩ?^??| ?թ~?}Y!Q?i{?`R>U?OK~?4Q?1z9bp?Cg} ?QH?I-&?i?sM? 6P?/1?8e?{3?{b oR?0疱^?%?5=<? M_9p?O?ҢO? ?&M@?r6—?¢?|.??|kc?Ԭt?=G?̗P}(?߱P?检^X?ph?7V?4=P9?zfVL?ډ,N?F<?O?BOk?'db7?Ǵa/?J=*??j{?n?vcUz? ?\ΐ?||e?fi?T\$? S"ڠ?܊M/ y(?J#?* ?r44@?k}8?K5astropy-pyvo-b70558c/pyvo/dal/tests/data/scs/000077500000000000000000000000001510533647000211515ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/scs/result.xml000066400000000000000000003463251510533647000232260ustar00rootroot00000000000000 ROSAT was an orbiting x-ray observatory active in the 1990s. We provide a table of all photons observed during ROSAT's all-sky survey (RASS) as well as images of both survey and pointed observations. For ROSAT data products, see http://www.xray.mpe.mpg.de/cgi-bin/rosat/rosat-survey {'SR0': 0.5, 'DEC0': 2.0, 'RA0': 78.0} Written by DaCHS 1.0.1 SCSRenderer This resource contains data associated with the publication 2000IAUC.7432R...1V. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/rosat.photons A table of x-ray photons detected by ROSAT Right Ascension, equinox 2000.0 Declination, equinox 2000.0 Time the photon was detected, in Julian years. Energy of incoming photon (corrected) [keV] Total positional error, 1{sigma} arcsec Galactic longitude Galactic latitude ROSAT cluster survey exposure time Photon identifier (a combination of observation date and detector coordinates) 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 MzAwOTc4MzkwOTM=
astropy-pyvo-b70558c/pyvo/dal/tests/data/sia/000077500000000000000000000000001510533647000211355ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/sia/dataset.xml000066400000000000000000000071121510533647000233050ustar00rootroot00000000000000 OK Not the access key for the data Access key for the data MIME type of the file served Size of the data in bytes Epoch at midpoint of observation Object being observed, Simbad-resolvable form Approximate center of image, RA Approximate center of image, Dec Synthetic name of the image Lower limit of the bandpass (in BandPass_Unit units) Upper limit of the bandpass (in BandPass_Unit units) The pixel scale on each image axis
This should not be the dataurl http://example.com/querydata/image.fits image/fits 153280 18885.9416782409 Test 288.95078924817 15.0322239971381 Test Observation 3.8e-07 5.2e-07 0.000403806 0.000406123
This should not be the dataurl http://example.com/querydata/image.fits image/fits 153280 Test 288.95078924817 15.0322239971381 Test Observation 3.8e-07 5.2e-07 0.000403806 0.000406123
astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/000077500000000000000000000000001510533647000212175ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/capabilities-basicauth.xml000066400000000000000000000020121510533647000263260ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/auth-v2query astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/capabilities-newformat.xml000066400000000000000000000022571510533647000264000ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/v2query astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/capabilities-priv.xml000066400000000000000000000027241510533647000253550ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/auth-v2query https://example.com/sia/v2query https://example.com/sia/v2query astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/capabilities.xml000066400000000000000000000031611510533647000243730ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/v2query https://example.com/sia/auth-v2query https://example.com/sia/v2query https://example.com/sia/v2query astropy-pyvo-b70558c/pyvo/dal/tests/data/sia2/dataset.xml000066400000000000000000000236761510533647000234040ustar00rootroot00000000000000 calibration level (0,1,2,3) publisher dataset identifier short name for the data colection telescope name instrument name internal dataset identifier type of product timestamp of date the data becomes publicly available RA of central coordinates DEC of central coordinates size of the region covered (~diameter of minimum bounding circle) region bounded by observation typical spatial resolution dimensions (number of pixels) along one spatial axis name of intended target dimensions (number of pixels) along the other spatial axis start time of observation (MJD) end time of observation (MJD) exposure time of observation typical temporal resolution dimensions (number of pixels) along the time axis start spectral coordinate value stop spectral coordinate value typical spectral resolution dimensions (number of pixels) along the energy axis dimensions (number of pixels) along the polarization axis UCD describing the spectral axis polarization states present in the data URL to download the data estimated size of the download UCD describing the observable axis (pixel values) format of the data file(s) primary key timestamp of last modification of the metadata
1 ivo://cadc.nrc.ca/TEST?C190508_1442_SCI/C190508_1442_SCI TEST TEST-1.6m TEST-INSTR TEST-DATASET image 2000-01-01T00:00:00.000 253.0637009871805 69.94853437563255 0.7176383627131244 polygon 252.33774699999995 70.20847500000002 253.83481099999992 70.19265699999998 253.77173199999996 69.68570399999999 252.31050299999995 69.70114700000002 2048 BYW1651+6953 2048 58612.30703703704 58612.30726851852 20.295 0.1 1 1.1699999999999998E-6 1.33E-6 1 https://example.com/datalink?runid=i4oh43a0oky3dyvp&ID=ivo%3A%2F%2Fexample.com%2FTEST%3FC190508_1442_SCI%2FC190508_1442_SCI phot.count application/x-votable+xml;content=datalink f0a8f0ad-be05-4adf-ab3d-8b13c45b79b1 2019-09-19T22:17:07.594
astropy-pyvo-b70558c/pyvo/dal/tests/data/sla/000077500000000000000000000000001510533647000211405ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/sla/dataset.xml000066400000000000000000000261431510533647000233150ustar00rootroot00000000000000 Splatalogue SLAP Service small description identifying the line the name of the line catalog this entry was drawn from the wavelength in the vacuum of the transition originating the line The molecular formula which may include notation of the common quantum state of the transition The molecular formula which may include notation of the common quantum state of the transition Actual chemical name of the species The type of molecular given as an integer identifier sijmu2. Einstein coefficient Lower state energy Upper state energy Lower state energy in Kelvin Upper state energy in Kelvin an integer flag indicating whether this record provides the recommended frequency for this line. The initial and final quantum number states that produces this line Known interstellar species a description of the spectral line
JPL: CH2OHCOCH2OH v29=1 65(10,55)-65( 9,56)JPL0.0026007993198247656115269.3542CH2OHCOCH2OH v29=1Dihydroxyacetone0131.249668558294-4.74809778436645442.7856446.63057178378637.066044526917642.59806942928565(10,55)-65( 9,56)0
JPL: NH2CO2CH3 v=1 9( 4, 6)- 8( 3, 6) EJPL0.00260079801795189115269.4119NH2CO2CH3 v=1Methyl Carbamate017.1236909189353-4.83754596637333130.4111134.256073708445187.631403639604193.16343131112 9( 4, 6)- 8( 3, 6) E0
JPL: NH2CO2CH3 v=1 9( 4, 6)- 8( 3, 6) EJPL0.002600797618590801115269.4296NH2CO2CH3 v=1Methyl Carbamate013.6709044372638-4.84357552571736130.4111134.256074298853187.631403639604193.163432160581 9( 4, 6)- 8( 3, 6) E0
JPL: (CH3)2CO v=0 54(33,21)-54(32,22) EEJPL0.0026007950374124845115269.544(CH3)2CO v=0Acetone15230.27724514961-3.02603304293211835.60529839.4502681148261202.242703660791207.77473767207154(33,21)-54(32,22) EE1
JPL: cis-CH2OHCHO v=1 14( 2,13)-14( 1,14)JPL0.0026007933722844356115269.6178cis-CH2OHCHO v=1Glycolaldehyde021.9273878887541-4.87033282331431231.969235.813980576529333.749727368876339.28176492197714( 2,13)-14( 1,14)0
JPL: C3H8 N/AJPL0.0026007911904735396115269.7145C3H8Propane00.635151099856094-6.7094715707246130.94892134.793903802094188.405202200504193.937244394447N/A0
JPL: C3H8 N/AJPL0.0026007911882172737115269.7146C3H8Propane00.423328037153043-6.97957609404102130.94891134.79389380543188.405187812818193.93723001156N/A0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.002600789356130706115269.7958NH2CO2CH3 v=1Methyl Carbamate05.01323226119246-5.72089974485859279.3062283.15118651397401.85700719681407.38905329251523(20, 3)-24(19, 5) E0
JPL: C3H8 N/AJPL0.0026007859830224104115269.9453C3H8Propane01.69346180808879-6.4646252884359130.94896134.793951500753188.405259751251193.937313021783N/A0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.0026007822579477637115270.1104NH2CO2CH3 v=1Methyl Carbamate05.2265567985742-5.72089656247709279.3063283.151297007897401.857151073676407.38921226771423(20, 3)-24(19, 5) E0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.002600781919510257115270.1254NH2CO2CH3 v=1Methyl Carbamate04.80745869862592-5.72021293919008279.3063283.151297508243401.857151073676407.38921298759623(20, 3)-24(19, 5) E0
JPL: C3H8 N/AJPL0.002600780777848383115270.176C3H8Propane01.05845801182539-6.20136303548129130.94894134.793939196077188.405230975878193.937295318201N/A0
JPL: C2H5OOCH-trans 21( 9,12)-20( 9,11)JPL0.0026007731630344477115270.5135C2H5OOCH-transEthyl formate058.6767590539827-4.6139139381576178.957782.8027104538654113.601864252006119.13394479168221( 9,12)-20( 9,11)0
JPL: C2H5OOCH-trans 21( 9,13)-20( 9,12)JPL0.0026007731630344477115270.5135C2H5OOCH-transEthyl formate058.6767590539827-4.6139139381576178.957782.8027104538654113.601864252006119.13394479168221( 9,13)-20( 9,12)0
JPL: NH2CH2CH2OH v26=1 18( 4,14)-17( 5,13)JPL0.002600759664053141115271.1118NH2CH2CH2OH v26=1Aminoethanol02.00560091486674-6.01486204317833267.4658271.310830411005384.821410750999390.35352000438218( 4,14)-17( 5,13)0
CDMS: CO v=0 1-0CDMS0.0026007576334647012115271.2018CO v=0Carbon Monoxide10.0121216495404374-7.1424633520481903.8450334130820605.53211357267721-01
JPL: CO v=0 1-0JPL0.0026007576334647012115271.2018CO v=0Carbon Monoxide10.0121216495404374-7.1424633520481903.8450334130820605.53211357267721-01
Lovas/NIST: CO v=0 1-0Lovas/NIST0.002600757628952286115271.202CO v=0Carbon Monoxide11-01
SLAIM: CO v=0 1- 0SLAIM0.002600757628952286115271.202CO v=0Carbon Monoxide10.0121242121-7.142360865160203.8450334197533405.532113582275631 1- 01
CDMS: FeCO N=14-13, J=13-12CDMS0.0026007539084712634115271.3669FeCOIron Monocarbonyl0131.500969428903-4.061336764522968.408872.253838920225398.4244375310149103.956559027197N=14-13, J=13-120
CDMS: CH3CHNH2COOH - I 29(11,18)-29( 8,21)CDMS0.002600753536198126115271.3834CH3CHNH2COOH - I&alpha;-Alanine02.70687445003096-6.2029243806737686.4611190.306149470606124.397535405638129.92965769369129(11,18)-29( 8,21)0
astropy-pyvo-b70558c/pyvo/dal/tests/data/ssa/000077500000000000000000000000001510533647000211475ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/ssa/result.xml000066400000000000000000001212031510533647000232060ustar00rootroot00000000000000 DALServer proxy service for JHU spectrum services Degree of match to query parameters URL used to access dataset Content or MIME type of dataset Datamodel name and version Dataset or segment type Number of points SI factor and dimensions SI factor and dimensions SI factor and dimensions Dataset Title Dataset creator Data collection to which dataset belongs Creator's ID for the dataset Data processing/creation date Version of dataset IVOA Dataset ID Instrument name Band as in RSM Coverage.Spectral Original source of the data Dataset creation type Dataset publisher Publisher's ID for the dataset ID Restrictions on data access Target RA and Dec Target name Object class of observed target Target redshift Target variability amplitude (typical) Spatial coordinate frame name Equinox Timescale Spatial Position Aperture angular size Query Metadata Access Metadata Estimated dataset size General Dataset Metadata Dataset Identification Metadata Curation Metadata Target Metadata Coordinate System Metadata Spatial Axis Characterization
1.0http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261170552832application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115923.80+005905.16 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611705528322000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261170552832SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261170552832PUBLIC179.849160 0.984768SDSS J115923.80+005905.16GALAXY0.4519410FK52000TAI179.849160 0.984768
1.0http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407863906304application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115923.80+005905.16 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078639063042001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407863906304SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407863906304PUBLIC179.849160 0.984768SDSS J115923.80+005905.16GALAXY0.4517070FK52000TAI179.849160 0.984768
0.2182701723871967http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261472542720application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115955.84+010937.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614725427202000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261472542720SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261472542720PUBLIC179.982660 1.160404SDSS J115955.84+010937.46GALAXY0.07839570FK52000TAI179.982660 1.160404
0.2182701723871967http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408233005056application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115955.84+010937.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082330050562001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408233005056SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408233005056PUBLIC179.982660 1.160404SDSS J115955.84+010937.46GALAXY0.07828230FK52000TAI179.982660 1.160404
0.2152962973906383http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261447376896application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120029.86+005857.51 SKYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614473768962000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261447376896SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261447376896PUBLIC180.124420 0.982641SDSS J120029.86+005857.51SKY-99990FK52000TAI180.124420 0.982641
0.2152962973906383http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408224616448application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120029.86+005857.51 SKYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082246164482001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408224616448SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408224616448PUBLIC180.124420 0.982641SDSS J120029.86+005857.51SKY-99990FK52000TAI180.124420 0.982641
8.695981252317388E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408262365184application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120008.29+010848.68 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082623651842001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408262365184SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408262365184PUBLIC180.034560 1.146855SDSS J120008.29+010848.68GALAXY3.19604E-050FK52000TAI180.034560 1.146855
8.695981252317388E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261464154112application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120008.29+010848.68 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614641541122000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261464154112SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261464154112PUBLIC180.034560 1.146855SDSS J120008.29+010848.68GALAXY3.05799E-050FK52000TAI180.034560 1.146855
8.497366575010945E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408249782272application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115943.32+010204.53 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082497822722001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408249782272SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408249782272PUBLIC179.930500 1.034592SDSS J115943.32+010204.53GALAXY0.1740460FK52000TAI179.930500 1.034592
8.497366575010945E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261510291456application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115943.32+010204.53 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615102914562000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261510291456SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261510291456PUBLIC179.930500 1.034592SDSS J115943.32+010204.53GALAXY0.1740790FK52000TAI179.930500 1.034592
4.841872020928674E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408237199360application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.57+010207.33 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082371993602001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408237199360SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408237199360PUBLIC180.085720 1.035371SDSS J120020.57+010207.33GALAXY0.1985670FK52000TAI180.085720 1.035371
4.841872020928674E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261455765504application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.57+010207.33 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614557655042000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261455765504SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261455765504PUBLIC180.085720 1.035371SDSS J120020.57+010207.33GALAXY0.1986180FK52000TAI180.085720 1.035371
2.7872930478268343E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261468348416application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115938.50+005726.95 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614683484162000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261468348416SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261468348416PUBLIC179.910430 0.957486SDSS J115938.50+005726.95QSO-0.0002556920FK52000TAI179.910430 0.957486
2.7872930478268343E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408270753792application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115938.50+005726.95 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082707537922001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408270753792SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408270753792PUBLIC179.910430 0.957486SDSS J115938.50+005726.95QSO-0.000136880FK52000TAI179.910430 0.957486
2.367640751371186E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261459959808application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115949.74+010446.07 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614599598082000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261459959808SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261459959808PUBLIC179.957260 1.079465SDSS J115949.74+010446.07GALAXY0.2004570FK52000TAI179.957260 1.079465
2.367640751371186E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408207839232application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115949.74+010446.07 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082078392322001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408207839232SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408207839232PUBLIC179.957260 1.079465SDSS J115949.74+010446.07GALAXY0.2003520FK52000TAI179.957260 1.079465
6.785416172617944E-12http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261162164224application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115904.60+005656.94 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611621642242000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261162164224SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261162164224PUBLIC179.769180 0.949151SDSS J115904.60+005656.94GALAXY0.3960870FK52000TAI179.769180 0.949151
6.785416172617944E-12http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407893266432application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115904.60+005656.94 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078932664322001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407893266432SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407893266432PUBLIC179.769180 0.949151SDSS J115904.60+005656.94GALAXY0.396050FK52000TAI179.769180 0.949151
2.341439286296514E-14http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261501902848application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.85+005628.47 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615019028482000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261501902848SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261501902848PUBLIC179.936880 0.941241SDSS J115944.85+005628.47GALAXY0.1009130FK52000TAI179.936880 0.941241
2.341439286296514E-14http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408212033536application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.85+005628.47 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082120335362001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408212033536SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408212033536PUBLIC179.936880 0.941241SDSS J115944.85+005628.47GALAXY0.1008910FK52000TAI179.936880 0.941241
4.041185654538269E-15http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261187330048application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115928.97+005619.92 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611873300482000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261187330048SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261187330048PUBLIC179.870720 0.938866SDSS J115928.97+005619.92GALAXY0.1638470FK52000TAI179.870720 0.938866
4.041185654538269E-15http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407914237952application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115928.97+005619.92 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434079142379522001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407914237952SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407914237952PUBLIC179.870720 0.938866SDSS J115928.97+005619.92GALAXY0.163890FK52000TAI179.870720 0.938866
8.581980694287659E-16http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408279142400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.81+011207.06 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082791424002001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408279142400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408279142400PUBLIC179.936720 1.201961SDSS J115944.81+011207.06QSO2.000140FK52000TAI179.936720 1.201961
8.581980694287659E-16http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261480931328application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.81+011207.06 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614809313282000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261480931328SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261480931328PUBLIC179.936720 1.201961SDSS J115944.81+011207.06QSO1.030260FK52000TAI179.936720 1.201961
2.0502337296517507E-17http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408258170880application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120044.06+005553.57 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082581708802001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408258170880SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408258170880PUBLIC180.183580 0.931548SDSS J120044.06+005553.57GALAXY0.1255930FK52000TAI180.183580 0.931548
2.0502337296517507E-17http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261506097152application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120044.06+005553.57 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615060971522000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261506097152SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261506097152PUBLIC180.183580 0.931548SDSS J120044.06+005553.57GALAXY0.125550FK52000TAI180.183580 0.931548
1.4806720665249466E-20http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261489319936application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120034.98+005517.51 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614893199362000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261489319936SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261489319936PUBLIC180.145770 0.921530SDSS J120034.98+005517.51GALAXY0.08604780FK52000TAI180.145770 0.921530
1.4806720665249466E-20http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408216227840application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120034.98+005517.51 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082162278402001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408216227840SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408216227840PUBLIC180.145770 0.921530SDSS J120034.98+005517.51GALAXY0.08602110FK52000TAI180.145770 0.921530
1.6300048361147802E-21http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261174747136application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115922.31+010453.49 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611747471362000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261174747136SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261174747136PUBLIC179.842950 1.081526SDSS J115922.31+010453.49GALAXY0.1035140FK52000TAI179.842950 1.081526
1.6300048361147802E-21http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407901655040application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115922.31+010453.49 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434079016550402001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407901655040SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407901655040PUBLIC179.842950 1.081526SDSS J115922.31+010453.49GALAXY0.1035230FK52000TAI179.842950 1.081526
1.33654114566609E-22http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380725178694238208application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.40+004752.35 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#807251786942382082001-03-31 05:48:15Z6.2.5ivo://sdss/dr6/spec/2_5/#80725178694238208SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80725178694238208PUBLIC180.084990 0.797876SDSS J120020.40+004752.35GALAXY0.139860FK52000TAI180.084990 0.797876
1.0976232720625123E-25http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261485125632application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120022.76+004741.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614851256322000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261485125632SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261485125632PUBLIC180.094830 0.794849SDSS J120022.76+004741.46GALAXY0.1398420FK52000TAI180.094830 0.794849
1.0976232720625123E-25http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408220422144application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120022.76+004741.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082204221442001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408220422144SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408220422144PUBLIC180.094830 0.794849SDSS J120022.76+004741.46GALAXY0.1397970FK52000TAI180.094830 0.794849
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407868100608application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078681006082001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407868100608SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407868100608PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07704420FK52000TAI179.825520 0.853781
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261136998400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611369984002000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261136998400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261136998400PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07709410FK52000TAI179.825520 0.853781
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261136998400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611369984006.2.5ivo://sdss/dr6/spec/2_5/#80442261136998400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261136998400PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07709410FK52000TAI179.825520 0.853781
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/000077500000000000000000000000001510533647000211455ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/capabilities.xml000066400000000000000000000273251510533647000243310ustar00rootroot00000000000000 http://dc.zah.uni-heidelberg.de/__system__/tap/run/availability http://dc.zah.uni-heidelberg.de/__system__/tap/run/capabilities http://dc.zah.uni-heidelberg.de/__system__/tap/run/tableMetadata http://dc.zah.uni-heidelberg.de/tap Registry 1.1 Registry 1.0 Obscore-1.1 GloTS 1.0 ADQL 2.0 ADQL 2.0
gavo_simbadpoint(identifier TEXT) -> POINT
gavo_simbadpoint queries simbad for an identifier and returns the corresponding point. Note that identifier can only be a literal, i.e., as simple string rather than a column name. This is because our database cannot query simbad, and we probably wouldn't want to fire off millions of simbad queries anyway; use simbad's own TAP service for this kind of applications.
gavo_match(pattern TEXT, string TEXT) -> INTEGER
gavo_match returns 1 if the POSIX regular expression pattern matches anything in string, 0 otherwise.
ivo_healpix_center(hpxOrder INTEGER, hpxIndex BIGINT) -> POINT
returns a POINT corresponding to the center of the healpix with the given index at the given order.
ivo_string_agg(expression TEXT, delimiter TEXT) -> TEXT
An aggregate function returning all values of expression within a GROUP contcatenated with delimiter
gavo_to_jd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
ivo_interval_overlapsivo_interval_overlaps(l1 NUMERIC, h1 NUMERIC, l2 NUMERIC, h2 NUMERIC) -> INTEGER
The function returns 1 if the interval [l1...h1] overlaps with the interval [l2...h2]. For the purposes of this function, the case l1=h2 or l2=h1 is treated as overlap. The function returns 0 for non-overlapping intervals.
ivo_healpix_index(order INTEGER, ra DOUBLE PRECISION, dec DOUBLE PRECISION) -> BIGINT
Returns the index of the (nest) healpix with order containing the spherical point (ra, dec). An alternative, 2-argument form ivo_healpix_index(order INTEGER, p POINT) -> BIGINT is also available.
ivo_hashlist_has(hashlist TEXT, item TEXT) -> INTEGER
The function takes two strings; the first is a list of words not containing the hash sign (#), concatenated by hash signs, the second is a word not containing the hash sign. It returns 1 if, compared case-insensitively, the second argument is in the list of words coded in the first argument. The behaviour in case the the second argument contains a hash sign is unspecified.
gavo_to_mjd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to modified julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
ivo_nocasematch(value TEXT, pattern TEXT) -> INTEGER
ivo_nocasematch returns 1 if pattern matches value, 0 otherwise. pattern is defined as for the SQL LIKE operator, but the match is performed case-insensitively. This function in effect provides a surrogate for the ILIKE SQL operator that is missing from ADQL. On this site, this is actually implemented using python's and SQL's LOWER, so for everything except ASCII, your milage will vary.
ivo_hasword(haystack TEXT, needle TEXT) -> INTEGER
gavo_hasword returns 1 if needle shows up in haystack, 0 otherwise. This is for "google-like"-searches in text-like fields. In word, you can actually employ a fairly complex query language; see http://www.postgresql.org/docs/8.3/static/textsearch.html for details.
ivo_apply_pm(ra DOUBLE PRECISION, dec DOUBLE PRECISION, pmra DOUBLE PRECISION, pmde DOUBLE PRECISON, epdist DOUBLE PRECISION) -> POINT
Returns a POINT (in the UNDEFINED reference frame) for the position an object at ra/dec with proper motion pmra/pmde has after epdist years. positions must be in degrees, PMs in should be in julian years (i.e., proper motions are expected in degrees/year). pmra is assumed to contain cos(delta). NOTE: This currently is a crappy approximation that does *not* go through the tangential plane. If you use it, let the operators know so we replace it with something real.
BOX
POINT
CIRCLE
POLYGON
REGION
CENTROID
COORD1
COORD2
DISTANCE
CONTAINS
INTERSECTS
AREA
LOWER
OFFSET
CAST
IN_UNIT
UNION
EXCEPT
INTERSECT
text/xml application/x-votable+xml;version=1.4 vodml text/html html application/x-votable+xml;serialization=binary2 votable/b2 application/geo-json geojson application/fits fits text/csv text/csv;header=present csv application/x-votable+xml;serialization=tabledata votable/td application/json json application/x-votable+xml votable text/plain text/tab-separated-values tsv 172800 3600 20000 10000000 100000000
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/examples.htm000066400000000000000000000105671510533647000235060ustar00rootroot00000000000000 Examples queries for HEASARC's TAP service
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/lazy-table1.xml000066400000000000000000000013331510533647000240140ustar00rootroot00000000000000 test.table1Test table 1Lazy Test Table 1utype id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/lazy-table2.xml000066400000000000000000000026231510533647000240200ustar00rootroot00000000000000 test This is a unittest schema test.table2Test table 2Lazy Test Table 2utype id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/obscore-examples.html000066400000000000000000000034741510533647000253130ustar00rootroot00000000000000

ObsCore Examples from Heidelberg

These are examples for ADQL you can run in TAP services carrying an ivoa.obscore table. See ObsCore for the underlying data model.

Finding images by time and place

Suppose you read in an old amateur observer's log there was an unexpected object on the night sky in the cold winter nights of the week between January 12th and 18th, 1903 – and now you would like to see whether there could be an observation of such a thing.

SELECT s_ra, s_dec, t_min FROM ivoa.obscore
  WHERE t_min BETWEEN gavo_to_mjd('1903-01-12')
      AND gavo_to_mjd('1903-01-19')

There is also a shortcut via user defined functions. As an extension to regular ADQL, DaCHS lets you write gavo_simbadpoint('object') and replaces the result with a position obtained from simbad, like this:

SELECT access_url, t_exptime, t_min FROM ivoa.obscore
  WHERE
    t_min BETWEEN gavo_to_mjd('J2416128.5')
      AND gavo_to_mjd('J2416133.5') AND
    1=CONTAINS(gavo_simbadpoint('Aldebaran'),
      CIRCLE('ICRS', s_ra, s_dec, 15))
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/obscore-image.xml000066400000000000000000000632171510533647000244140ustar00rootroot00000000000000 Definition and support code for the ObsCore data model and table. Definition and support code for the ObsCore data model and table. The IVOA-defined obscore table, containing generic metadata for datasets within this datacenter. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/ivoa.ObsCore The IVOA-defined obscore table, containing generic metadata for datasets within this datacenter. The calib_level flag takes the following values: === =========================================================== 0 Raw Instrumental data requiring instrument-specific tools 1 Instrumental data processable with standard tools 2 Calibrated, science-ready data without instrument signature 3 Enhanced data products (e.g., mosaics) === =========================================================== High level scientific classification of the data product, taken from an enumeration Data product specific type Amount of data processing that has been applied to the data Name of a data collection (e.g., project name) this data belongs to Unique identifier for an observation Free-from title of the data set Dataset identifier assigned by the publisher. Dataset identifier assigned by the creator. The URL at which to obtain the data set. MIME type of the resource at access_url Estimated size of data product Object a targeted observation targeted Class of the target object (star, QSO, ...) RA of (center of) observation, ICRS Dec of (center of) observation, ICRS Approximate spatial extent for the region covered by the observation Region covered by the observation, as a polygon Best spatial resolution within the data set Lower bound of times represented in the data set, as MJD Upper bound of times represented in the data set, as MJD Total exporure time Minimal significant time interval along the time axis Minimal wavelength represented within the data set Maximal wavelength represented within the data set Spectral resolving power delta lambda/lamda UCD for the product's observable List of polarization states in the data set Name of the facility at which data was taken Name of the instrument that produced the data Number of elements (typically pixels) along the first spatial axis. Number of elements (typically pixels) along the second spatial axis. Number of elements (typically pixels) along the time axis. Number of elements (typically pixels) along the spectral axis. Number of elements (typically pixels) along the polarization axis. Sampling period in world coordinate units along the spatial axis Nature of the product's spectral axis
image 0 Carte du Ciel potsdam/data/fits/POT032_000002E.fits POT032 000002E 1913-08-26 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000002E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000002E.fits image/fits 435344 3.50857212362378 32.5520448642212 2.62789597705705 Polygon ICRS 5.0880992953 33.8657594349 5.0355038987 31.2453170186 1.9726562093 31.2287678614 1.9153724039 33.8626908007 0.633652077522129 20005.0 20005.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam AO Potsdam, CdC 32-cm refractor 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000002F.fits POT032 000002F 1913-09-27 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000002F.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000002F.fits image/fits 435344 3.53452561567638 32.5695561136923 2.63052286885795 Polygon ICRS 5.103047601 33.886686541 5.0917765511 31.2555119167 2.0160910239 31.2347269366 1.9365250319 33.8649731746 0.634285487467423 20037.0 20037.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam AO Potsdam, CdC 32-cm refractor 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000016E.fits POT032 000016E 1913-11-24 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000016E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000016E.fits image/fits 435344 35.1868032690563 32.4808618261433 2.63817216226016 Polygon ICRS 36.7551716874 33.7680816314 36.6569941298 31.1022837509 33.6625182329 31.1788382583 33.6735378399 33.8410154025 0.63612992526032 20095.0 20095.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000019E.fits POT032 000019E 1914-01-10 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000019E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000019E.fits image/fits 435344 41.9674003552431 32.3991901793566 2.63199545777752 Polygon ICRS 43.548354499 33.6930243809 43.505828469 31.0660423298 40.4307731342 31.0878159639 40.3821684028 33.7082053158 0.634640565840527 20142.0 20142.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000023E.fits POT032 000023E 1913-11-24 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000023E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000023E.fits image/fits 435344 51.013271521456 32.3592083837928 2.63320798636414 Polygon ICRS 52.5854005932 33.6698086892 52.51655884 31.0228597137 49.4658779108 31.0489341391 49.4582217525 33.6839678682 0.63493293710053 20095.0 20095.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000024E.fits POT032 000024E 1914-01-14 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000024E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000024E.fits image/fits 435344 53.2710128701656 32.3315125241693 2.63161394905183 Polygon ICRS 54.8696536533 33.6223099336 54.8100487496 31.0070068952 51.7281712497 31.0126204922 51.6981281808 33.6237338407 0.63454857445322 20146.0 20146.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000037E.fits POT032 000037E 1914-01-23 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000037E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000037E.fits image/fits 435344 82.5782914160801 32.0756686823358 2.63224704496679 Polygon ICRS 84.1734494156 33.3485377418 84.0849626303 30.7322269305 81.0277353071 30.7779028646 81.0221467846 33.3910659111 0.634701229864731 20155.0 20155.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000041E.fits POT032 000041E 1914-01-28 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000041E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000041E.fits image/fits 435344 91.6034594938728 31.9863773920912 2.63157744935597 Polygon ICRS 93.1854668029 33.2901563606 93.1207735598 30.6519541351 90.0617359171 30.6701913547 90.0404624178 33.3040260012 0.63453977345489 20160.0 20160.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000042E.fits POT032 000042E 1914-01-23 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000042E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000042E.fits image/fits 435344 93.8580667241656 31.9665743279601 2.63161025563022 Polygon ICRS 95.4495587754 33.2578595127 95.3683729818 30.6303045167 92.309936229 30.6592222838 92.3026865321 33.2851196812 0.634547683876008 20155.0 20155.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000043E.fits POT032 000043E 1914-01-28 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000043E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000043E.fits image/fits 435344 96.1095474818101 31.929532175206 2.63162090137484 Polygon ICRS 97.6853052447 33.2333654587 97.6308154 30.6046273426 94.5763673352 30.6070206617 94.5432016023 33.2334262897 0.634550250833854 20160.0 20160.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
astropy-pyvo-b70558c/pyvo/dal/tests/data/tap/tables.xml000066400000000000000000000013311510533647000231370ustar00rootroot00000000000000 test This is a unittest schema test.table1
test.table2
astropy-pyvo-b70558c/pyvo/dal/tests/make_testdata.py000066400000000000000000000106021510533647000226270ustar00rootroot00000000000000from pathlib import Path import numpy as np from astropy.table import Table from astropy.io.votable.tree import VOTableFile, Info from astropy.io.fits import ImageHDU def _votablefile(): table = Table([ [23, 42, 1337], [b'Illuminatus', b"Don't panic, and always carry a towel", b'Elite'] ], names=('1', '2')) table['1'].meta['ucd'] = 'foo;bar' table['2'].meta['utype'] = 'foobar' votable_file = VOTableFile.from_table(table) info = Info(name='QUERY_STATUS', value='OK') info.content = 'OK' votable_file.resources[0].infos.append(info) return votable_file def votablefile(): votable_file = _votablefile() return votable_file def votablefile_errorstatus(): votable_file = _votablefile() info = Info(name='QUERY_STATUS', value='ERROR') info.content = 'ERROR' votable_file.resources[0].infos[0] = info return votable_file def votablefile_overflowstatus(): votable_file = _votablefile() info_ok = Info(name='QUERY_STATUS', value='OK') info_overflow = Info(name='QUERY_STATUS', value='OVERFLOW') votable_file.resources[0].infos[0] = info_ok votable_file.resources[0].infos.append(info_overflow) return votable_file def votablefile_missingtable(): votable_file = _votablefile() del votable_file.resources[0].tables[0] return votable_file def votablefile_missingresource(): votable_file = _votablefile() del votable_file.resources[0] return votable_file def votablefile_missingcolumns(): votable_file = _votablefile() del votable_file.resources[0].tables[0].fields[:] return votable_file def votablefile_firstresource(): votable_file = _votablefile() votable_file.resources[0]._type = 'results' return votable_file def votablefile_tableinfo(): votable_file = _votablefile() votable_file.resources[0].tables[0].infos[:] = ( votable_file.resources[0].infos[:]) del votable_file.resources[0].infos[:] return votable_file def votablefile_rootinfo(): votable_file = _votablefile() votable_file.infos[:] = ( votable_file.resources[0].infos[:]) del votable_file.resources[0].infos[:] return votable_file def votablefile_dataset(): table = Table([ [ 'image/fits', 'application/x-votable+xml', 'application/x-votable+xml;content=datalink' ], [ b'http://example.com/querydata/image.fits', b'http://example.com/querydata/votable.xml', b'http://example.com/querydata/votable-datalink.xml' ] ], names=('dataformat', 'dataurl')) table['dataformat'].meta['ucd'] = 'meta.code.mime' table['dataurl'].meta['utype'] = 'Access.Reference' table['dataurl'].meta['ucd'] = 'meta.dataset;meta.ref.url' votable_file = VOTableFile.from_table(table) info = Info(name='QUERY_STATUS', value='OK') info.content = 'OK' votable_file.resources[0].infos.append(info) return votable_file def dataset_fits(): hdu = ImageHDU(np.random.random((256, 256))) return hdu def main(): dirname = Path(__file__).parent / 'data' votablefile().to_xml( str(dirname / 'query/basic.xml'), tabledata_format='tabledata') votablefile_errorstatus().to_xml( str(dirname / 'query/errorstatus.xml'), tabledata_format='tabledata') votablefile_overflowstatus().to_xml( str(dirname / 'query/overflowstatus.xml'), tabledata_format='tabledata') votablefile_missingtable().to_xml( str(dirname / 'query/missingtable.xml'), tabledata_format='tabledata') votablefile_missingresource().to_xml( str(dirname / 'query/missingresource.xml'), tabledata_format='tabledata') votablefile_missingcolumns().to_xml( str(dirname / 'query/missingcolumns.xml'), tabledata_format='tabledata') votablefile_firstresource().to_xml( str(dirname / 'query/firstresource.xml'), tabledata_format='tabledata') votablefile_tableinfo().to_xml( str(dirname / 'query/tableinfo.xml'), tabledata_format='tabledata') votablefile_rootinfo().to_xml( str(dirname / 'query/rootinfo.xml'), tabledata_format='tabledata') votablefile_dataset().to_xml( str(dirname / 'query/dataset.xml'), tabledata_format='tabledata') dataset_fits().writeto( str(dirname / 'querydata/image.fits'), overwrite=True) if __name__ == '__main__': main() astropy-pyvo-b70558c/pyvo/dal/tests/test_adhoc.py000066400000000000000000000202541510533647000221420ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.adhoc """ import datetime from astropy import units as u from astropy.time import Time import pytest from pyvo.dal.adhoc import AxisParamMixin, SodaQuery def test_pos(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() test_obj.pos.add((1, 2, 3) * u.deg) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0'] test_obj.pos.add((1, 2, 3, 4)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0'] # duplicates are ignored test_obj.pos.add((1, 2, 3)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0'] # polygon test_obj.pos.add((1, 2, 3, 4, 5, 6)) assert len(test_obj._pos) == 3 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0', 'POLYGON 1.0 2.0 3.0 4.0 5.0 6.0'] # deletes test_obj.pos.remove((1, 2, 3, 4)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'POLYGON 1.0 2.0 3.0 4.0 5.0 6.0'] # test borders test_obj.pos.discard((1, 2, 3) * u.deg) test_obj.pos.discard((1, 2, 3, 4, 5, 6)) assert (len(test_obj._pos) == 0) test_obj.pos.add((0, 90, 90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 0.0 90.0 90.0'] test_obj.pos.pop() test_obj.pos.add((360, -90, 1)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 360.0 -90.0 1.0'] test_obj.pos.pop() test_obj.pos.add((0, 360, -90, 90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['RANGE 0.0 360.0 -90.0 90.0'] test_obj.pos.pop() test_obj.pos.add((0, 0, 180, 90, 270, -90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['POLYGON 0.0 0.0 180.0 90.0 270.0 -90.0'] # errors test_obj.pos.pop() with pytest.raises(TypeError): test_obj.pos.add(('A', 2, 3)) with pytest.raises(ValueError): test_obj.pos.add((-2, 7, 3)) with pytest.raises(ValueError): test_obj.pos.add((3, 99, 3)) with pytest.raises(ValueError): test_obj.pos.add((2, 7, 91)) with pytest.raises(ValueError): test_obj.pos.add((-1, 7, 3, 4)) with pytest.raises(ValueError): test_obj.pos.add((2, 1, 3, 4)) with pytest.raises(ValueError): test_obj.pos.add((1, 2, 4, 3)) with pytest.raises(ValueError): test_obj.pos.add((-2, 7, 5, 9, 10, 10)) with pytest.raises(ValueError): test_obj.pos.add((2, 99, 5, 9, 10, 10)) with pytest.raises(ValueError): test_obj.pos.add((1, 2, 3, 4, 5, 6, 7)) def test_band(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_band') test_obj.band.add(33) assert 33 in test_obj.band assert test_obj['BAND'] == ['33.0 33.0'] test_obj.band.add((50 * u.meter, 500)) assert 33 in test_obj.band assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['33.0 33.0', '50.0 500.0'] test_obj.band.discard(33) assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['50.0 500.0'] test_obj.band.pop() assert not test_obj.band assert not test_obj['BAND'] test_obj.band.add((float('-inf'), 33)) assert (float('-inf'), 33) in test_obj.band assert test_obj.band.dal == ['-inf 33.0'] test_obj.band.clear() test_obj.band.add((33, float('inf'))) assert (33, float('inf')) in test_obj.band assert test_obj.band.dal == ['33.0 inf'] test_obj.clear() # error cases with pytest.raises(ValueError): test_obj.band.add(()) with pytest.raises(ValueError): test_obj.band.add((1, 2, 3)) with pytest.raises(TypeError): test_obj.band.add(('INVALID', 6)) with pytest.raises(ValueError): test_obj.band.add((3, 1)) def test_time(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_time') now = Time(datetime.datetime.now(tz=datetime.timezone.utc)) test_obj.time.add(now) assert now in test_obj.time assert test_obj['TIME'] == ['{now} {now}'.format(now=now.mjd)] min_time = '2010-01-01T00:00:00.000Z' max_time = '2010-01-01T01:00:00.000Z' test_obj.time.add((min_time, max_time)) assert now in test_obj.time assert (min_time, max_time) in test_obj.time assert test_obj['TIME'] == ['{now} {now}'.format(now=now.mjd), '{min} {max}'.format(min=Time(min_time).mjd, max=Time(max_time).mjd)] test_obj.time.discard(now) assert (min_time, max_time) in test_obj.time assert test_obj['TIME'] == ['{min} {max}'.format(min=Time(min_time).mjd, max=Time(max_time).mjd)] test_obj.time.pop() assert not test_obj.time assert not test_obj['TIME'] # error cases with pytest.raises(ValueError): test_obj.time.add([]) with pytest.raises(ValueError): test_obj.time.add([now, min_time, max_time]) with pytest.raises(ValueError): test_obj.time.add(['INVALID']) with pytest.raises(ValueError): test_obj.time.add([max_time, min_time]) def test_pol(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_pol') test_obj.pol.add('YY') assert 'YY' in test_obj.pol assert test_obj['POL'] == ['YY'] test_obj.pol.add('POLI') assert 'YY' in test_obj.pol assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['YY', 'POLI'] # test duplicate test_obj.pol.add('POLI') assert 'YY' in test_obj.pol assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['YY', 'POLI'] test_obj.pol.remove('YY') assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['POLI'] test_obj.pol.pop() assert not test_obj._pol assert not test_obj['POL'] # error cases with pytest.raises(ValueError): test_obj.pol.add(None) with pytest.raises(ValueError): test_obj.pol.add(['INVALID']) def test_soda_query(): test_obj = SodaQuery(baseurl='some/url') test_obj.circle = (2, 3, 5) assert test_obj._circle == (2, 3, 5) assert test_obj['CIRCLE'] == '2.0 3.0 5.0' assert test_obj._circle assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_range') test_obj.range = (8, 9, 3, 4) * u.deg assert test_obj['POS'] == 'RANGE 8.0 9.0 3.0 4.0' assert test_obj._range is not None assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_circle') test_obj.polygon = (1, 2, 3, 4, 5, 6) assert test_obj['POLYGON'] == '1.0 2.0 3.0 4.0 5.0 6.0' assert test_obj._polygon assert not hasattr(test_obj, '_range') assert not hasattr(test_obj, '_circle') del test_obj.polygon assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_circle') assert not hasattr(test_obj, '_range') # error cases with pytest.raises(TypeError): test_obj.circle = ('A', 1, 2) with pytest.raises(ValueError): test_obj.circle = (1, 1, 2, 2) with pytest.raises(ValueError): test_obj.circle = (-1, 1, 2) with pytest.raises(ValueError): test_obj.circle = (1, 99, 2) with pytest.raises(ValueError): test_obj.circle = (1, 1, 91) with pytest.raises(ValueError): test_obj.range = (1, 2, 3) with pytest.raises(ValueError): test_obj.range = (2, 1, 3, 4) with pytest.raises(ValueError): test_obj.range = (1, 2, 4, 3) with pytest.raises(ValueError): test_obj.range = (-1, 2, 3, 4) with pytest.raises(ValueError): test_obj.range = (2, 1000, 3, 4) with pytest.raises(ValueError): test_obj.range = (1, 1, -91, 4) with pytest.raises(ValueError): test_obj.range = (1, 1, 3, 92) with pytest.raises(ValueError): test_obj.polygon = (1, 2, 3, 4) with pytest.raises(ValueError): test_obj.polygon = (2, 1, 3, 4, 5, 6, 7) astropy-pyvo-b70558c/pyvo/dal/tests/test_datalink.py000066400000000000000000000321101510533647000226450ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from functools import partial import re import pytest import pyvo as vo from pyvo.dal.adhoc import DatalinkResults, DALServiceError from pyvo.dal.sia2 import SIA2Results from pyvo.dal.tap import TAPResults from pyvo.utils import testing, vocabularies from pyvo.dal.sia import search from astropy.utils.data import get_pkg_data_contents, get_pkg_data_filename get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture() def ssa_datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink-ssa.xml') with mocker.register_uri( 'GET', 'http://example.com/ssa_datalink', content=callback ) as matcher: yield matcher sia_re = re.compile("http://example.com/sia.*") @pytest.fixture() def register_mocks(mocker): with mocker.register_uri( "GET", "http://example.com/querydata/image.fits", content=get_pkg_data_contents("data/querydata/image.fits"), ) as matcher: yield matcher @pytest.fixture() def sia(mocker): with mocker.register_uri( "GET", sia_re, content=get_pkg_data_contents("data/sia/dataset.xml") ) as matcher: yield matcher @pytest.fixture() def datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink.xml') with mocker.register_uri( 'POST', 'http://example.com/datalink', content=callback ) as matcher: yield matcher @pytest.fixture() def datalink_product(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink.xml') with mocker.register_uri( 'GET', 'http://example.com/datalink.xml', content=callback ) as matcher: yield matcher @pytest.fixture() def obscore_datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink-obscore.xml') with mocker.register_uri( 'GET', 'http://example.com/obscore', content=callback ) as matcher: yield matcher @pytest.fixture() def res_datalink(mocker): first_batch = True def callback(request, context): nonlocal first_batch if first_batch: first_batch = False return get_pkg_data_contents('data/datalink/cutout1.xml') else: return get_pkg_data_contents('data/datalink/cutout2.xml') with mocker.register_uri( 'POST', 'https://example.com/obscore-datalink', content=callback ) as matcher: yield matcher @pytest.fixture() def proc(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc.xml') with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def datalink_vocabulary(mocker): # astropy download_file (which get_vocabluary uses) does not use # requests, so we can't mock this as we can mock the others. We # replace the entire function for a while dl_voc_uri = 'http://www.ivoa.net/rdf/datalink/core' def fake_download_file(src_url, *args, **kwargs): assert src_url == dl_voc_uri return get_pkg_data_filename('data/datalink/datalink.desise') real_download_file = vocabularies.download_file try: vocabularies.download_file = fake_download_file yield finally: vocabularies.download_file = real_download_file @pytest.mark.usefixtures('ssa_datalink', 'datalink') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_datalink(): results = vo.spectrumsearch( 'http://example.com/ssa_datalink', (30, 30)) for preserve_order in [False, True]: dl_res = set(results.iter_datalinks(preserve_order=preserve_order)) assert len(dl_res) == 1 datalinks = dl_res.pop() assert datalinks.original_row["accsize"] == 100800 assert 4 == len(datalinks) for dl in datalinks: assert dl.semantics in ['#this', '#preview', '#progenitor', '#proc'] @pytest.mark.usefixtures('obscore_datalink', 'res_datalink') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_datalink_batch(): results = vo.dal.imagesearch( 'http://example.com/obscore', (30, 30)) for preserve_order in [False, True]: dls = list(results.iter_datalinks(preserve_order=preserve_order)) assert len(dls) == 3 assert dls[0].original_row["obs_collection"] == "MACHO" @pytest.mark.usefixtures('proc', 'datalink_vocabulary') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") class TestSemanticsRetrieval: def test_access_with_string(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') assert datalinks.original_row is None res = [r["access_url"] for r in datalinks.bysemantics("#this")] assert len(res) == 1 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") def test_access_with_list(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics(["#this", "#preview-image"])] assert len(res) == 2 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("20100927.comb_avg.0001.fits.fz?preview=True") def test_access_with_expansion(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics(["#this", "#preview"])] assert len(res) == 3 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("20100927.comb_avg.0001.fits.fz?preview=True") assert res[2].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") def test_access_without_expansion(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics( ["#this", "#preview"], include_narrower=False)] assert len(res) == 2 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") def test_with_full_url(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics("urn:example:rdf/dlext#oracle")] assert len(res) == 1 assert res[0].endswith("when-will-it-be-back") def test_all_mixed(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics([ "urn:example:rdf/dlext#oracle", 'http://www.ivoa.net/rdf/datalink/core#preview', '#this', 'non-existing-term'])] assert len(res) == 4 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("comb_avg.0001.fits.fz?preview=True") assert res[2].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") assert res[3].endswith("when-will-it-be-back") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") @pytest.mark.usefixtures('datalink_product', 'datalink_vocabulary') class TestIterDatalinksProducts: """Tests for producing datalinks from tables containing links to datalink documents. """ def test_no_access_format(self): res = testing.create_dalresults([ {"name": "access_url", "datatype": "char", "arraysize": "*", "utype": "obscore:access.reference"}], [("http://foo.bar/baz.jpeg",)], resultsClass=TAPResults) assert list(res.iter_datalinks()) == [] def test_obscore_utype(self): res = testing.create_dalresults([ {"name": "data_product", "datatype": "char", "arraysize": "*", "utype": "obscore:access.reference"}, {"name": "content_type", "datatype": "char", "arraysize": "*", "utype": "obscore:access.format"},], [("http://example.com/datalink.xml", "application/x-votable+xml;content=datalink")], resultsClass=TAPResults) links = list(res.iter_datalinks()) assert len(links) == 1 assert (next(links[0].bysemantics("#this"))["access_url"] == "http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt") def test_sia2_record(self): res = testing.create_dalresults([ {"name": "access_url", "datatype": "char", "arraysize": "*", "utype": "obscore:access.reference"}, {"name": "access_format", "datatype": "char", "arraysize": "*", "utype": "obscore:access.format"},], [("http://example.com/datalink.xml", "application/x-votable+xml;content=datalink")], resultsClass=SIA2Results) links = list(res.iter_datalinks()) assert len(links) == 1 assert (next(links[0].bysemantics("#this"))["access_url"] == "http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt") def test_sia1_record(self): res = testing.create_dalresults([ {"name": "product", "datatype": "char", "arraysize": "*", "ucd": "VOX:Image_AccessReference"}, {"name": "mime", "datatype": "char", "arraysize": "*", "ucd": "VOX:Image_Format"},], [("http://example.com/datalink.xml", "application/x-votable+xml;content=datalink")], resultsClass=TAPResults) links = list(res.iter_datalinks()) assert len(links) == 1 assert (next(links[0].bysemantics("#this"))["access_url"] == "http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt") def test_ssap_record(self): res = testing.create_dalresults([ {"name": "product", "datatype": "char", "arraysize": "*", "utype": "ssa:access.reference"}, {"name": "mime", "datatype": "char", "arraysize": "*", "utype": "ssa:access.format"},], [("http://example.com/datalink.xml", "application/x-votable+xml;content=datalink")], resultsClass=TAPResults) links = list(res.iter_datalinks()) assert len(links) == 1 assert (next(links[0].bysemantics("#this"))["access_url"] == "http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt") def test_generic_record(self): # The meta.code.mime and meta.ref.url UCDs are perhaps too # generic. To ensure a somewhat predictable behaviour, # we at least make sure we pick the first of possibly multiple # pairs (not that this would preclude arbitrary amounts of # chaos). res = testing.create_dalresults([ {"name": "access_url", "datatype": "char", "arraysize": "*", "ucd": "meta.ref.url"}, {"name": "access_format", "datatype": "char", "arraysize": "*", "utype": "meta.code.mime"}, {"name": "alt_access_url", "datatype": "char", "arraysize": "*", "ucd": "meta.ref.url"}, {"name": "alt_access_format", "datatype": "char", "arraysize": "*", "utype": "meta.code.mime"},], [("http://example.com/datalink.xml", "application/x-votable+xml;content=datalink", "http://example.com/bad-pick.xml", "application/x-votable+xml;content=datalink",)], resultsClass=TAPResults) links = list(res.iter_datalinks()) assert len(links) == 1 assert (next(links[0].bysemantics("#this"))["access_url"] == "http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt") @pytest.mark.usefixtures("sia", "register_mocks") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_no_datalink(): # for issue #328 getdatalink() exits messily when there isn't a datalink results = search("http://example.com/sia", pos=(288, 15), format="all") result = results[0] with pytest.raises(DALServiceError, match="No datalink found for record."): result.getdatalink() astropy-pyvo-b70558c/pyvo/dal/tests/test_mimetype.py000066400000000000000000000031501510533647000227110ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.mimetype """ from functools import partial import pytest import requests_mock from astropy.utils.data import get_pkg_data_contents from pyvo.dal.mimetype import mime_object_maker mime_url = 'https://someurl.com/' get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') try: from PIL import Image # noqa: F401 HAS_PILLOW = True except ImportError: HAS_PILLOW = False @pytest.fixture() def mime(mocker): def callback(request, context): if 'mime-text' in request.url: return b'Text content' elif 'image' in request.url: return get_pkg_data_contents('data/mimetype/ivoa_logo.jpg') elif 'fits' in request.url: return get_pkg_data_contents('data/mimetype/test.fits') with mocker.register_uri( 'GET', requests_mock.ANY, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('mime') @pytest.mark.skipif('not HAS_PILLOW') def test_mime_object_maker(): assert 'Text content' == mime_object_maker(mime_url + 'mime-text', 'text/csv') img = mime_object_maker(mime_url + 'image', 'image/jpeg') assert img assert 'JPEG' == img.format fits = mime_object_maker(mime_url + 'fits', 'application/fits') assert 2 == len(fits) # error cases with pytest.raises(ValueError): mime_object_maker(None, "not/a/mime/type") with pytest.raises(ValueError): mime_object_maker(None, None) astropy-pyvo-b70558c/pyvo/dal/tests/test_monkeypatch.py000066400000000000000000000006031510533647000234020ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from astropy.table import Table from astropy.utils.data import get_pkg_data_filename def test_monkeypatch(): Table.read(get_pkg_data_filename("data/monkeypatch.xml")) import pyvo # noqa: F401 Table.read(get_pkg_data_filename("data/monkeypatch.xml")) astropy-pyvo-b70558c/pyvo/dal/tests/test_params.py000066400000000000000000000163371510533647000223560ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from functools import partial from urllib.parse import parse_qsl from pyvo.dal.adhoc import DatalinkResults from pyvo.dal.params import find_param_by_keyword, get_converter, AbstractDalQueryParam, IntervalQueryParam from pyvo.dal.exceptions import DALServiceError import pytest import numpy as np import astropy.units as u from astropy.utils.data import get_pkg_data_contents, get_pkg_data_fileobj get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') get_pkg_data_fileobj = partial( get_pkg_data_fileobj, package=__package__, encoding='binary') @pytest.fixture() def proc(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc.xml') with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_ds(mocker): def callback(request, context): return b'' with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_units(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc_units.xml') with mocker.register_uri( 'GET', 'http://example.com/proc_units', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_units_ds(mocker): def callback(request, context): data = dict(parse_qsl(request.query)) if 'band' in data: assert data['band'] == ( '6.000000000000001e-07 8.000000000000001e-06') return b'' with mocker.register_uri( 'GET', 'http://example.com/proc_units_ds', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_inf(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc_inf.xml') with mocker.register_uri( 'GET', 'http://example.com/proc_inf', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_inf_ds(mocker): def callback(request, context): data = dict(parse_qsl(request.query)) if 'band' in data: assert data['band'] == ( '6.000000000000001e-07 +Inf') return b'' with mocker.register_uri( 'GET', 'http://example.com/proc_inf_ds', content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('proc') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_find_param_by_keyword(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_lower = find_param_by_keyword('polygon', input_params) polygon_upper = find_param_by_keyword('POLYGON', input_params) circle_lower = find_param_by_keyword('circle', input_params) circle_upper = find_param_by_keyword('CIRCLE', input_params) assert polygon_lower == polygon_upper assert circle_lower == circle_upper @pytest.mark.usefixtures('proc') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_serialize(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_conv = get_converter( find_param_by_keyword('polygon', input_params)) circle_conv = get_converter( find_param_by_keyword('circle', input_params)) scale_conv = get_converter( find_param_by_keyword('scale', input_params)) kind_conv = get_converter( find_param_by_keyword('kind', input_params)) assert polygon_conv.serialize((1, 2, 3)) == "1 2 3" assert polygon_conv.serialize(np.array((1, 2, 3))) == "1 2 3" assert circle_conv.serialize((1.1, 2.2, 3.3)) == "1.1 2.2 3.3" assert circle_conv.serialize(np.array((1.1, 2.2, 3.3))) == "1.1 2.2 3.3" assert scale_conv.serialize(1) == "1" assert kind_conv.serialize("DATA") == "DATA" @pytest.mark.usefixtures('proc') @pytest.mark.usefixtures('proc_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_serialize_exceptions(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_conv = get_converter( find_param_by_keyword('polygon', input_params)) circle_conv = get_converter( find_param_by_keyword('circle', input_params)) band_conv = get_converter( find_param_by_keyword('band', input_params)) with pytest.raises(DALServiceError): polygon_conv.serialize((1, 2, 3, 4)) with pytest.raises(DALServiceError): circle_conv.serialize((1, 2, 3, 4)) with pytest.raises(DALServiceError): band_conv.serialize((1, 2, 3)) @pytest.mark.usefixtures('proc_units') @pytest.mark.usefixtures('proc_units_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_units(): datalink = DatalinkResults.from_result_url('http://example.com/proc_units') proc_dl = datalink[0] proc_dl.process(band=(6000 * u.Angstrom, 80000 * u.Angstrom)) @pytest.mark.usefixtures('proc_inf') @pytest.mark.usefixtures('proc_inf_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_inf(): datalink = DatalinkResults.from_result_url('http://example.com/proc_inf') proc_dl = datalink[0] proc_dl.process(band=(6000, +np.inf) * u.Angstrom) def test_dal_query_param(): class Test(AbstractDalQueryParam): def get_dal_format(self, item): return str(item) # check test_obs behaves like a set but also holds the dal representation test_obs = Test() test_obs.add(1) assert 1 in test_obs assert test_obs.dal == ['1'] test_obs.add(2) test_obs.add(3) assert len(test_obs) == 3 assert test_obs.dal == ['1', '2', '3'] assert {2, 3} < test_obs assert {1, 2, 3, 4} > test_obs test_obs.clear() assert len(test_obs) == 0 assert len(test_obs.dal) == 0 def test_dal_format(): iqp = IntervalQueryParam(unit=u.m, equivalencies=u.spectral()) assert '1.0 1.0' == iqp.get_dal_format(1) assert '1.0 2.0' == iqp.get_dal_format((1, 2)) assert '1.0 2.0' == iqp.get_dal_format((100 * u.cm, 200 * u.cm)) assert '1.0 2.0' == iqp.get_dal_format((100, 200) * u.cm) assert '0.14989622900000002 1.0' == iqp.get_dal_format((100 * u.cm, 2 * u.GHz)) assert '14.9896229 29.9792458' == iqp.get_dal_format((0.01, 0.02) * u.GHz) # Quantity intervals are corrected in terms of min and max .. assert '1.0 2.0' == iqp.get_dal_format((2, 1) * u.m) # But unitless intervals are not with pytest.raises(ValueError): iqp.get_dal_format((2, 1)) astropy-pyvo-b70558c/pyvo/dal/tests/test_query.py000066400000000000000000000507061510533647000222360ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.query """ import warnings from functools import partial from contextlib import ExitStack from io import BytesIO from os import listdir import pytest import numpy as np import platform from pyvo.dal.query import DALService, DALQuery, DALResults, Record, Upload from pyvo.dal.exceptions import DALServiceError, DALQueryError, DALFormatError, DALOverflowWarning from pyvo.version import version from astropy.table import Table, QTable from astropy.io.votable import parse as votableparse from astropy.io.votable.tree import VOTableFile try: # Workaround astropy deprecation, remove try/except once >=6.0 is required from astropy.io.votable.tree import TableElement except ImportError: from astropy.io.votable.tree import Table as TableElement from astropy.io.fits import HDUList from astropy.utils.data import get_pkg_data_contents, get_pkg_data_filename get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture() def register_mocks(mocker): with ExitStack() as stack: matchers = [ stack.enter_context(mocker.register_uri( 'GET', '//example.com/query/basic', content=get_pkg_data_contents('data/query/basic.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingtable', content=get_pkg_data_contents('data/query/missingtable.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingresource', content=get_pkg_data_contents('data/query/missingresource.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingcolumns', content=get_pkg_data_contents('data/query/missingcolumns.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/firstresource', content=get_pkg_data_contents('data/query/firstresource.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/rootinfo', content=get_pkg_data_contents('data/query/rootinfo.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/tableinfo', content=get_pkg_data_contents('data/query/tableinfo.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/dataset', content=get_pkg_data_contents('data/query/dataset.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/querydata/image.fits', content=get_pkg_data_contents('data/querydata/image.fits') )), # mocker.register_uri( # 'GET', 'http://example.com/querydata/votable.xml', # content=get_pkg_data_contents('data/querydata/votable.xml') # ), # mocker.register_uri( # 'GET', 'http://example.com/querydata/votable-datalink.xml', # content=get_pkg_data_contents('data/querydata/votable-datalink.xml') # ), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/nonexistant', text='Not Found', status_code=404 )), stack.enter_context(mocker.register_uri( 'GET', '//example.com/query/errornous', text='Error', status_code=500 )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/errorstatus', content=get_pkg_data_contents('data/query/errorstatus.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/overflowstatus', content=get_pkg_data_contents('data/query/overflowstatus.xml') )), ] def verbosetest_callback(request, context): assert 'VERBOSE' in request.qs and '1' in request.qs['VERBOSE'] return get_pkg_data_contents('data/query/basic.xml') matchers.append(stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/verbosetest', content=verbosetest_callback ))) def useragent_callback(request, context): assert 'User-Agent' in request.headers assert request.headers['User-Agent'] == 'pyVO/{} Python/{} ({})'.format( version, platform.python_version(), platform.system()) return get_pkg_data_contents('data/query/basic.xml') matchers.append(stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/useragent', content=useragent_callback ))) yield matchers def _test_results(results): """Regression test result columns for correctnes""" assert len(results) == 3 assert results['1', 0] == 23 assert results['1', 1] == 42 assert results['1', 2] == 1337 truth = 'Illuminatus' assert results['2', 0] == truth truth = "Don't panic, and always carry a towel" assert results['2', 1] == truth truth = 'Elite' assert results['2', 2] == truth def _test_records(records): """ Regression test dal records for correctness""" assert len(records) == 3 assert all([isinstance(record, Record) for record in records]) assert records[0]['1'] == 23 truth = 'Illuminatus' assert records[0]['2'] == truth assert records[1]['1'] == 42 truth = "Don't panic, and always carry a towel" assert records[1]['2'] == truth assert records[2]['1'] == 1337 truth = 'Elite' assert records[2]['2'] == truth @pytest.fixture def url(): return "http://example.com/query/basic" @pytest.fixture def description(): return "An example service." @pytest.fixture def basic_service(url, description): return DALService(url, capability_description=description) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.usefixtures('register_mocks') class TestDALService: def test_init(self, url, description, basic_service): """Test if baseurl and description are passed correctly""" assert basic_service.baseurl == url assert basic_service.capability_description == "An example service." def test__repr__(self, basic_service): assert str(basic_service) == (f"DALService(baseurl : '{basic_service.baseurl}'," f" description : '{basic_service.capability_description}')") def test_search(self): """ Test (in conjunction with mocker) that parameters arrive serverside, while also ensuring data consistency """ service = DALService('http://example.com/query/verbosetest') dalresults = service.search(VERBOSE=1) _test_results(dalresults) _test_records(dalresults) def test_useragent(self): service = DALService('http://example.com/query/useragent') service.search() def test_http_exception_404(self): service = DALService('http://example.com/query/nonexistant') try: service.search() except DALServiceError as exc: assert exc.code == 404 else: assert False def test_http_exception_500(self): service = DALService('http://example.com/query/errornous') try: service.search() except DALServiceError as exc: assert exc.code == 500 else: assert False def test_query_exception(self): service = DALService('http://example.com/query/errorstatus') with pytest.raises(DALQueryError): service.search() def test_query_warning(self): service = DALService('http://example.com/query/overflowstatus') with pytest.warns(DALOverflowWarning): service.search() @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W53") def test_format_exception(self): with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingtable') service.search() with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingresource') service.search() with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingcolumns') service.search() @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.usefixtures('register_mocks') class TestDALQuery: def test_url(self): queries = ( DALQuery('http://example.com/query/basic'), DALQuery(b'http://example.com/query/basic'), ) assert all( q.queryurl == 'http://example.com/query/basic' for q in queries ) def test_params(self): query = DALQuery( 'http://example.com/query/basic', verbose=1, foo='BAR') assert query['VERBOSE'] == 1 assert query['FOO'] == 'BAR' def test_execute(self): query = DALQuery('http://example.com/query/basic') dalresults = query.execute() assert dalresults.queryurl == 'http://example.com/query/basic' _test_results(dalresults) _test_records(dalresults) def test_execute_raw(self): query = DALQuery('http://example.com/query/basic') raw = query.execute_raw() assert raw.startswith(b'') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06') @pytest.mark.usefixtures('register_mocks') class TestDALResults: def test_init(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.queryurl == 'http://example.com/query/basic' assert isinstance(dalresults.votable, VOTableFile) assert isinstance(dalresults.resultstable, TableElement) assert dalresults.fieldnames == ('1', '2') assert ( dalresults.fielddescs[0].name, dalresults.fielddescs[1].name ) == ('1', '2') assert dalresults.status == ('OK', 'OK') def test_from_result_url(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.status == ('OK', 'OK') def test_init_errorstatus(self): with pytest.raises(DALQueryError): DALResults.from_result_url('http://example.com/query/errorstatus') def test_init_overflowstatus(self): with pytest.warns(DALOverflowWarning): DALResults.from_result_url('http://example.com/query/overflowstatus') def test_init_missingtable(self): with pytest.raises(DALFormatError): DALResults.from_result_url('http://example.com/query/missingtable') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W53') def test_init_missingresource(self): with pytest.raises(DALFormatError): DALResults.from_result_url( 'http://example.com/query/missingresource') def test_init_missingcolumns(self): with pytest.raises(DALFormatError): DALResults.from_result_url( 'http://example.com/query/missingcolumns') def test_init_firstresource(self): dalresults = DALResults.from_result_url( 'http://example.com/query/firstresource') assert dalresults.status == ('OK', 'OK') def test_init_tableinfo(self): dalresults = DALResults.from_result_url( 'http://example.com/query/tableinfo') assert dalresults.status == ('OK', 'OK') def test_init_rootinfo(self): dalresults = DALResults.from_result_url( 'http://example.com/query/rootinfo') assert dalresults.status == ('OK', 'OK') def test_repr(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert repr(dalresults)[0:26] == "" def test_iter(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') records = list(iter(dalresults)) _test_results(dalresults) _test_records(records) def test_dataconsistency(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults['1'], np.ndarray) assert isinstance(dalresults['2'], np.ndarray) _test_results(dalresults) _test_records(dalresults) def test_table_conversion(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults.to_table(), Table) assert isinstance(dalresults.to_qtable(), QTable) assert len(dalresults) == len(dalresults.to_table()) assert len(dalresults) == len(dalresults.to_qtable()) def test_id_over_name(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults['_1'], np.ndarray) assert isinstance(dalresults['_2'], np.ndarray) table = dalresults.to_table() with pytest.raises(KeyError): assert table['_1'] with pytest.raises(KeyError): assert table['_2'] def test_nosuchcolumn(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') with pytest.raises(KeyError): dalresults['nosuchcolumn'] with pytest.raises(KeyError): dalresults.getdesc('nosuchcolumn') def test_columnaliases(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.fieldname_with_ucd('foo') == '1' assert dalresults.fieldname_with_ucd('bar') == '1' assert dalresults.fieldname_with_utype('foobar') == '2' assert dalresults.fieldname_with_ucd('baz') is None assert dalresults.fieldname_with_utype('foobaz') is None def test_check_overflow_warning_no_maxrec(self): with pytest.warns(DALOverflowWarning): dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"): dalresults.check_overflow_warning() def test_check_overflow_warning_exact_match(self): with pytest.warns(DALOverflowWarning): dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') with warnings.catch_warnings(): warnings.simplefilter("error") dalresults.check_overflow_warning(client_set_maxrec=3) def test_check_overflow_warning_service_truncation(self): with pytest.warns(DALOverflowWarning): dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"): dalresults.check_overflow_warning(client_set_maxrec=1000) def test_check_overflow_warning_uses_stored_maxrec(self): with pytest.warns(DALOverflowWarning): dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') dalresults._client_set_maxrec = 1000 with pytest.warns(DALOverflowWarning, match="Results truncated at 3 records by service limits"): dalresults.check_overflow_warning() def test_check_overflow_warning_parameter_overrides_stored(self): with pytest.warns(DALOverflowWarning): dalresults = DALResults.from_result_url('http://example.com/query/overflowstatus') dalresults._client_set_maxrec = 1000 with warnings.catch_warnings(): warnings.simplefilter("error") dalresults.check_overflow_warning(client_set_maxrec=3) def test_check_overflow_warning_no_overflow_status(self): dalresults = DALResults.from_result_url('http://example.com/query/basic') with warnings.catch_warnings(): warnings.simplefilter("error") dalresults.check_overflow_warning(client_set_maxrec=1000) def test_handle_overflow_warning_default_behavior(self): votable = votableparse(BytesIO(get_pkg_data_contents('data/query/overflowstatus.xml'))) with pytest.warns(DALOverflowWarning, match="Result set limited by user- or server-supplied MAXREC parameter."): _ = DALResults(votable, url='http://test.com') def test_stored_client_set_maxrec_initialization(self): votable = votableparse(BytesIO(get_pkg_data_contents('data/query/basic.xml'))) result = DALResults(votable, url='http://test.com', client_set_maxrec=100) assert result._client_set_maxrec == 100 result2 = DALResults(votable, url='http://test.com') assert result2._client_set_maxrec is None @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06') @pytest.mark.usefixtures('register_mocks') class TestRecord: def test_itemaccess(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record['1'] == 23 truth = 'Illuminatus' assert record['2'] == truth assert record['_1'] == 23 assert record['_2'] == truth def test_nosuchcolumn(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] with pytest.raises(KeyError): record['nosuchcolumn'] def test_iter(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] record = list(iter(record)) assert record[0] == '1' assert record[1] == '2' def test_len(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert len(record) == 2 def test_repr(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] truth = 'Illuminatus' assert repr(record) == repr(('23', truth)) def test_get(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.get('2', decode=True) == 'Illuminatus' def test_columnaliases(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.getbyucd('foo') == 23 assert record.getbyucd('bar') == 23 truth = 'Illuminatus' assert record.getbyutype('foobar') == truth record.getbyucd('baz') is None record.getbyutype('foobaz') is None def test_datasets(self): records = DALResults.from_result_url( 'http://example.com/query/dataset') record = records[0] assert record.getdataurl() == 'http://example.com/querydata/image.fits' dataset = record.getdataset() HDUList.fromstring(dataset.read()) def test_nodataset(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.getdataurl() is None with pytest.raises(KeyError): record.getdataset().read() def test_cachedataset(self, tmpdir): tmpdir = str(tmpdir) record = DALResults.from_result_url( 'http://example.com/query/dataset')[0] record.cachedataset(dir=tmpdir) assert "dataset.dat" in listdir(tmpdir) class TestUpload: bytesio = BytesIO(get_pkg_data_contents('data/query/dataset.xml', encoding='binary')) filename = get_pkg_data_filename('data/query/dataset.xml') astropy_table = Table.read(filename) records = DALResults(votableparse(filename)) @pytest.mark.parametrize('content', (bytesio, filename, astropy_table, records)) def test_upload(self, content): upload = Upload('up', content) fileobj = upload.fileobj() assert fileobj fileobj.close() def test_upload_nonfileobj(self): upload = Upload('up', 'some text that is not a resource') with pytest.raises(ValueError): upload.fileobj() astropy-pyvo-b70558c/pyvo/dal/tests/test_scs.py000066400000000000000000000026351510533647000216570ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.scs """ from functools import partial import re import pytest from pyvo.dal.scs import search, SCSService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') scs_re = re.compile('http://example.com/scs.*') @pytest.fixture() def scs(mocker): def callback(request, context): return get_pkg_data_contents('data/scs/result.xml') with mocker.register_uri( 'GET', scs_re, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('scs') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('http://example.com/scs', pos=(78, 2), radius=0.5) assert len(results) == 1273 class TestSCSService: def test_init(self): service = SCSService('http://example.com/scs', capability_description="SCS") assert service.baseurl == 'http://example.com/scs' assert service.capability_description == "SCS" @pytest.mark.usefixtures('scs') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(self): service = SCSService('http://example.com/scs') results = service.search(pos=(78, 2), radius=0.5) assert len(results) == 1273 astropy-pyvo-b70558c/pyvo/dal/tests/test_sia.py000066400000000000000000000074771510533647000216540ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia """ from functools import partial import os import re import pytest from pyvo.dal.sia import search, SIAService, SIAQuery from astropy.io.fits import HDUList from astropy.coordinates import SkyCoord from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sia_re = re.compile('http://example.com/sia.*') @pytest.fixture() def register_mocks(mocker): with mocker.register_uri( 'GET', 'http://example.com/querydata/image.fits', content=get_pkg_data_contents('data/querydata/image.fits') ) as matcher: yield matcher @pytest.fixture() def sia(mocker): with mocker.register_uri( 'GET', sia_re, content=get_pkg_data_contents('data/sia/dataset.xml') ) as matcher: yield matcher def _test_result(result): assert result.getdataurl() == 'http://example.com/querydata/image.fits' assert isinstance(result.getdataobj(), HDUList) assert result.filesize == 153280 @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.parametrize("position", ((288, 15), SkyCoord(288, 15, unit="deg"))) @pytest.mark.parametrize("format", ("IMAGE/JPEG", "all")) def test_search(position, format): results = search('http://example.com/sia', pos=position, format=format) result = results[0] _test_result(result) class TestSIAService: @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(self): description = "A SIA service." url = 'http://example.com/sia' service = SIAService(url, capability_description=description) assert service.baseurl == url assert service.capability_description == description results = service.search(pos=(288, 15)) result = results[0] _test_result(result) assert results[1].dateobs is None @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_formatter(self): service = SIAQuery('http://example.com/sia') service.format = "image" assert service["FORMAT"] == "image" service.format = "all" assert service["FORMAT"] == "ALL" service.format = "Graphic-png" assert service["FORMAT"] == "GRAPHIC-png" service.format = "Unsupported" assert service["FORMAT"] == "Unsupported" @pytest.mark.usefixtures('sia') class TestNameMaking: def test_slash_replace(self): res = SIAService('http://example.com/sia').search(pos=(288, 15)) res["imageTitle"][0] = "ogsa/dai output" assert res[0].make_dataset_filename() == os.path.join(".", "ogsa_dai_output.fits") def test_default_for_broken_media_type(self): res = SIAService('http://example.com/sia').search(pos=(288, 15)) res["mime"][0] = "application/x-youcannotknowthis" assert res[0].make_dataset_filename() == os.path.join(".", "Test_Observation.dat") def test_default_media_type_adaption(self): res = SIAService('http://example.com/sia').search(pos=(288, 15)) res["mime"][0] = "image/png" assert res[0].make_dataset_filename() == os.path.join(".", "Test_Observation.png") astropy-pyvo-b70558c/pyvo/dal/tests/test_sia2.py000066400000000000000000000236351510533647000217300ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia """ from functools import partial from pathlib import Path import re import requests_mock import pytest from pyvo.dal.sia2 import search, SIA2Service, SIA2Query, SIAService, SIAQuery from pyvo.dal.exceptions import DALServiceError import astropy.units as u from astropy.coordinates import SkyCoord from astropy.utils.data import get_pkg_data_contents from astropy.utils.exceptions import AstropyDeprecationWarning get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sia_re = re.compile('https://example.com/sia/v2query*') capabilities_url = 'https://example.com/sia/capabilities' @pytest.fixture() def sia(mocker): with mocker.register_uri( 'GET', sia_re, content=get_pkg_data_contents('data/sia2/dataset.xml') ) as matcher: yield matcher @pytest.fixture() def capabilities(mocker): with mocker.register_uri( 'GET', capabilities_url, content=get_pkg_data_contents('data/sia2/capabilities.xml') ) as matcher: yield matcher def _test_result(record): assert record.obs_collection == 'TEST' assert record.obs_id == 'TEST-DATASET' assert record.instrument_name == 'TEST-INSTR' assert record.facility_name == 'TEST-1.6m' @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('https://example.com/sia', pos=(33.3 * u.deg, 4.2 * u.deg, 0.0166 * u.deg)) result = results[0] _test_result(result) class TestSIA2Service: def test_capabilities(self): # this tests the SIA2 capabilities with various combinations: with requests_mock.Mocker() as cm: cm.get('https://example.com/sia/capabilities', content=get_pkg_data_contents('data/sia2/capabilities.xml')) cm.get('https://example.com/sia-basicauth/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-basicauth.xml')) cm.get('https://example.com/sia-newformat/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-newformat.xml')) cm.get('https://example.com/sia-priv/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-priv.xml')), cm.get('https://example.com/sia/myquery/capabilities', content=get_pkg_data_contents('data/sia2/capabilities.xml')) # multiple interfaces with single security method each and # anonymous access. service = SIA2Service('https://example.com/sia') assert service.query_ep == 'https://example.com/sia/v2query' # one interface with multiple security methods service = SIA2Service('https://example.com/sia-newformat') assert service.query_ep == 'https://example.com/sia/v2query' # multiple interfaces with single security method each (no anon) service = SIA2Service('https://example.com/sia-priv') assert service.query_ep == 'https://example.com/sia/v2query' # any access point will be valid even when it contains query params service = SIA2Service('https://example.com/sia/myquery?param=1') assert service.query_ep == 'https://example.com/sia/v2query' # capabilities checking is bypassed all together with the # check_baseurl=False flag service = SIA2Service('https://example.com/sia/myquery?param=1&', check_baseurl=False) assert service.query_ep == 'https://example.com/sia/myquery?param=1' POSITIONS = [(2, 4, 0.0166 * u.deg), (12, 12.5, 34, 36), (12.0 * u.deg, 34.0 * u.deg, 14.0 * u.deg, 35.0 * u.deg, 14.0 * u.deg, 36.0 * u.deg, 12.0 * u.deg, 35.0 * u.deg), (SkyCoord(2, 4, unit='deg'), 0.166 * u.deg)] ERR_POSITIONS = [(SkyCoord(2, 4, unit='deg'), "radius should be provided in the pos tuple"), ((1, 2), "a 2-length pos should be a coordinate and a radius")] @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") @pytest.mark.parametrize("position", POSITIONS) def test_search_scalar(self, position): service = SIA2Service('https://example.com/sia') results = service.search(pos=position) result = results[0] _test_result(result) @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') @pytest.mark.parametrize(("position", "expected_errmsg"), ERR_POSITIONS) def test_search_scalar_errors(self, position, expected_errmsg): service = SIA2Service('https://example.com/sia') with pytest.raises(ValueError, match=expected_errmsg): service.search(pos=position) @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') def test_search_vector(self, pos=POSITIONS): service = SIA2Service('https://example.com/sia') results = service.search(pos=pos) result = results[0] _test_result(result) @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') def test_search_deprecation(self, pos=POSITIONS): # test the deprecation with pytest.warns(AstropyDeprecationWarning): deprecated_service = SIAService('https://example.com/sia') deprecated_results = deprecated_service.search(pos=pos) result = deprecated_results[0] _test_result(result) class TestSIA2Query(): def test_query(self): query = SIA2Query('someurl') query.field_of_view.add((10, 20)) assert query['FOV'] == ['10.0 20.0'] query.field_of_view.add((1 * u.rad, 60)) assert query['FOV'] == ['10.0 20.0', '57.29577951308232 60.0'] query.spatial_resolution.add((1 * u.arcsec, 2)) assert query['SPATRES'] == ['1.0 2.0'] query.spectral_resolving_power.add((3, 5)) assert query['SPECRP'] == ['3 5'] query.exptime.add((25, 50)) assert query['EXPTIME'] == ['25.0 50.0'] query.timeres.add((1, 3)) assert query['TIMERES'] == ['1.0 3.0'] query.publisher_did.add('ID1') query.publisher_did.add('ID2') assert query['ID'] == ['ID1', 'ID2'] query.facility.add('TEL1') assert query['FACILITY'] == ['TEL1'] query.collection.add('ABC') query.collection.add('EFG') assert query['COLLECTION'] == ['ABC', 'EFG'] query.instrument.add('INST1') assert query['INSTRUMENT'] == ['INST1'] query.data_type.add('TYPEA') assert query['DPTYPE'] == ['TYPEA'] query.calib_level.add(0) query.calib_level.add(1) assert query['CALIB'] == ['0', '1'] query.target_name.add('TARGET1') assert query['TARGET'] == ['TARGET1'] query.res_format.add('pdf') assert query['FORMAT'] == ['pdf'] query.maxrec = 1000 assert query['MAXREC'] == '1000' query = SIA2Query('someurl', custom_param=23) assert query['custom_param'] == ['23'] query['custom_param'].append('-Inf 0') assert query['custom_param'] == ['23', '-Inf 0'] query = SIA2Query('someurl', custom_param=[('-Inf', 0), (2, '+Inf')]) assert query['custom_param'] == ['-Inf 0', '2 +Inf'] with pytest.warns(AstropyDeprecationWarning): deprecated_query = SIAQuery('someurl') deprecated_query.field_of_view.add((10, 20)) assert deprecated_query['FOV'] == ['10.0 20.0'] def test_variable_deprecation(): # Test this while we are in the deprecation period, as the variable is durectly # used at least by astroquery.alma with pytest.warns(AstropyDeprecationWarning): from pyvo.dal.sia2 import SIA_PARAMETERS_DESC assert SIA_PARAMETERS_DESC def test_none_standardid_capability(): """Test that SIA2Service handles capabilities with None standardID.""" # Mock a capabilities response with a None standardID with requests_mock.Mocker() as m: # Mock the capabilities endpoint m.get('http://example.com/sia/capabilities', content=b''' http://example.com/sia/query http://example.com/sia/query ''') # This should not raise an AttributeError sia2_service = SIA2Service('http://example.com/sia') # Basic verification that the service was created successfully assert sia2_service is not None assert sia2_service.query_ep is not None def test_url_is_not_sia2(): # with capabilities from an other service type, we raise an error with open(Path(__file__).parent / "data/tap/capabilities.xml", "rb") as f: with requests_mock.Mocker() as mocker: mocker.get("http://example.com/sia/capabilities", content=f.read()) with pytest.raises(DALServiceError, match="This URL does not seem to correspond to an " "SIA2 service."): SIA2Service('http://example.com/sia') astropy-pyvo-b70558c/pyvo/dal/tests/test_sia2_remote.py000066400000000000000000000221511510533647000232730ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia2 against remote services """ import pytest import astropy.units as u from astropy.utils.exceptions import AstropyDeprecationWarning from pyvo.dal.sia2 import search, SIA2Service from pyvo.dal.adhoc import DatalinkResults from pyvo import regsearch CADC_SIA_URL = 'https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/sia' @pytest.mark.remote_data class TestSIACadc(): # Tests the SIA2 client against the CADC SIA service def test_service(self): cadc = SIA2Service(baseurl=CADC_SIA_URL) with pytest.raises(AstropyDeprecationWarning): assert cadc.availability with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.available with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.notes with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.notes[0] == 'service is accepting queries' assert cadc.capabilities @pytest.mark.xfail(reason="https://github.com/astropy/pyvo/issues/361") @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_datalink_batch(self): # Maximum batch size in CADC SIA is around 25 # Test whether multiple batches can be retrieved results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 10), maxrec=55) ids = [] for i in results.iter_datalinks(): assert i.to_table()[0]['ID'] not in ids ids.append(i.to_table()[0]['ID']) assert len(ids) == 55 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_pos(self): results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 0.001)) assert len(results) > 10 # check that results are datalink assert isinstance(results[0].getdataobj(), DatalinkResults) # limit results to 5 to expedite tests results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 0.001), maxrec=5) assert len(results) == 5 # check attributes of a record record = results[0] record.dataproduct_type record.dataproduct_subtype record.calib_level # TARGET INFO record.target_name record.target_class # DATA DESCRIPTION record.obs_id record.obs_title record.obs_collection record.obs_create_date record.obs_creator_name record.obs_creator_did # CURATION INFORMATION record.obs_release_date record.obs_publisher_did record.publisher_id record.bib_reference record.data_rights # ACCESS INFORMATION record.access_url record.access_format record.access_estsize # SPATIAL CHARACTERISATION record.s_ra record.s_dec record.s_fov record.s_region record.s_resolution record.s_xel1 record.s_xel2 record.s_ucd record.s_unit record.s_resolution_min record.s_resolution_max record.s_calib_status record.s_stat_error record.s_pixel_scale # TIME CHARACTERISATION record.t_xel record.t_ref_pos record.t_min record.t_max record.t_exptime record.t_resolution record.t_calib_status record.t_stat_error # SPECTRAL CHARACTERISATION record.em_xel record.em_ucd record.em_unit record.em_calib_status record.em_min record.em_max record.em_res_power record.em_res_power_min record.em_res_power_max record.em_resolution record.em_stat_error # OBSERVABLE AXIS record.o_ucd record.o_unit record.o_calib_status record.o_stat_error # POLARIZATION CHARACTERISATION record.pol_xel record.pol_states # PROVENANCE record.instrument_name record.facility_name record.proposal_id @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_band(self): results = search(CADC_SIA_URL, band=(0.0002, 0.0003), maxrec=5) # TODO - correctness assert len(results) == 5 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_time(self): results = search(CADC_SIA_URL, time=('2002-01-01T00:00:00.00', '2002-01-02T00:00:00.00'), maxrec=5) assert len(results) == 5 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_pol(self): results = search(CADC_SIA_URL, pol=['YY', 'U'], maxrec=5) assert len(results) == 5 for rr in results: assert 'YY' in rr.pol_states or 'U' in rr.pol_states @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_fov(self): results = search(CADC_SIA_URL, field_of_view=(10, 20), collection='TESS', maxrec=5) assert len(results) == 5 # how to test values @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_spatial_res(self): results = search(CADC_SIA_URL, spatial_resolution=(1, 2), collection='MACHO', maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.arcsec <= rr.s_resolution <= 2 * u.arcsec @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_spec_resp(self): results = search(CADC_SIA_URL, spectral_resolving_power=(1, 2), collection='NEOSSAT', maxrec=5) assert len(results) == 5 for rr in results: assert 1 <= rr.em_res_power <= 2 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_exptime(self): results = search(CADC_SIA_URL, exptime=(1, 2), collection='GEMINI', maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.second <= rr.t_exptime <= 2 * u.second @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_timeres(self): results = search(CADC_SIA_URL, timeres=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.second <= rr.t_resolution <= 2 * u.second def test_publisher_did(self): ids = ['ivo://cadc.nrc.ca/CFHT?447231/447231o', 'ivo://cadc.nrc.ca/CFHT?447232/447232o'] results = search(CADC_SIA_URL, publisher_did=ids) assert len(results) == 2 assert results[0].obs_publisher_did in ids assert results[1].obs_publisher_did in ids @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_facility(self): results = search(CADC_SIA_URL, facility='JCMT', maxrec=5) assert len(results) == 5 for rr in results: assert rr.facility_name == 'JCMT' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_collection(self): results = search(CADC_SIA_URL, collection='IRIS', maxrec=5) assert len(results) == 5 for rr in results: assert rr.obs_collection == 'IRIS' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_instrument(self): results = search(CADC_SIA_URL, instrument='IRAS', maxrec=5) assert len(results) == 5 for rr in results: assert rr.instrument_name == 'IRAS' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_dataproduct_type(self): results = search(CADC_SIA_URL, data_type='image', maxrec=5) assert len(results) == 5 for rr in results: assert rr.dataproduct_type == 'image' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_target_name(self): results = search(CADC_SIA_URL, target_name='BarnardStar', collection='NEOSSAT', maxrec=5) assert len(results) == 5 for rr in results: assert rr.target_name == 'BarnardStar' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_res_format(self): results = search( CADC_SIA_URL, res_format='application/x-votable+xml;content=datalink', maxrec=5) assert len(results) == 5 for rr in results: assert rr.access_format == \ 'application/x-votable+xml;content=datalink' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_reg_sia2(self): image_services = regsearch(servicetype='sia2') irsa_seip = \ [s for s in image_services if 'irsa' in s.ivoid and 'seip' in s.ivoid][0] result = irsa_seip.search(pos=(31.8425, 77.4846, 0.1), maxrec=1) assert len(result) == 1 astropy-pyvo-b70558c/pyvo/dal/tests/test_sla.py000066400000000000000000000032051510533647000216400ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sla """ from functools import partial import re import pytest from pyvo.dal.sla import search, SLAService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sla_re = re.compile('http://example.com/sla.*') @pytest.fixture() def sla(mocker): with mocker.register_uri( 'GET', sla_re, content=get_pkg_data_contents('data/sla/dataset.xml') ) as matcher: yield matcher @pytest.mark.usefixtures('sla') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(): results = search('http://example.com/sla', wavelength=(7.6e-6, 1.e-5)) assert len(results) == 21 class TestSLAService: def test_service_init(self): url = "http://test" description = "A SLA service." service = SLAService(url, capability_description=description) assert service.baseurl == url assert service.capability_description == description assert str(service) == f"SLAService(baseurl : '{url}', description : '{description}')" @pytest.mark.usefixtures('sla') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(self): service = SLAService('http://example.com/sla') results = service.search(wavelength=(7.6e-6, 1.e-5)) assert len(results) == 21 astropy-pyvo-b70558c/pyvo/dal/tests/test_ssa.py000066400000000000000000000025221510533647000216500ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.ssa """ from functools import partial import re import pytest from pyvo.dal.ssa import search, SSAService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') ssa_re = re.compile('http://example.com/ssa.*') @pytest.fixture() def ssa(mocker): def callback(request, context): return get_pkg_data_contents('data/ssa/result.xml') with mocker.register_uri( 'GET', ssa_re, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('ssa') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") def test_search(): results = search('http://example.com/ssa', pos=(0.0, 0.0), diameter=1.0) assert len(results) == 36 class TestSSAService: @pytest.mark.usefixtures('ssa') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") def test_search(self): service = SSAService('http://example.com/ssa') assert str(service) == "SSAService(baseurl : 'http://example.com/ssa', description : 'None')" results = service.search(pos=(0.0, 0.0), diameter=1.0) assert len(results) == 36 assert results[35].dateobs is None astropy-pyvo-b70558c/pyvo/dal/tests/test_tap.py000066400000000000000000001746451510533647000216660ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.tap """ import warnings from functools import partial from contextlib import ExitStack import datetime import re from io import BytesIO, StringIO from unittest.mock import Mock from urllib.parse import parse_qsl import tempfile import pytest import requests import requests_mock from pyvo.dal.tap import escape, search, AsyncTAPJob, TAPService from pyvo.dal import DALQueryError, DALServiceError, DALOverflowWarning from pyvo.io.uws import JobFile from pyvo.io.uws.tree import Parameter, Result, ErrorSummary, Message from pyvo.io.vosi.exceptions import VOSIError from pyvo.utils import prototype from astropy.time import Time, TimeDelta from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') job_re_path_full = re.compile('^http://example.com/tap/async/([0-9]+)') job_re_path = re.compile('^/tap/async/([0-9]+)') job_re_phase_full = re.compile('^http://example.com/tap/async/([0-9]+)/phase') job_re_parameters_full = re.compile( '^http://example.com/tap/async/([0-9]+)/parameters') job_re_result_full = re.compile( '^http://example.com/tap/async/([0-9]+)/results/result') def _test_image_results(results): assert len(results) == 10 @pytest.fixture() def sync_fixture(mocker): def callback(request, context): return get_pkg_data_contents('data/tap/obscore-image.xml') with mocker.register_uri( 'POST', 'http://example.com/tap/sync', content=callback ) as matcher: yield matcher @pytest.fixture() def create_fixture(mocker): def match_request(request): data = request.text.read() if b'VOSITable' in data: assert request.headers['Content-Type'] == 'text/xml', 'Wrong file format' elif b'VOTable' in data: assert request.headers['Content-Type'] == \ 'application/x-votable+xml', 'Wrong file format' else: assert False, 'BUG' return True with mocker.register_uri( 'PUT', 'https://example.com/tap/tables/abc', additional_matcher=match_request, status_code=201 ) as matcher: yield matcher @pytest.fixture() def delete_fixture(mocker): with mocker.register_uri( 'DELETE', 'https://example.com/tap/tables/abc', status_code=200, ) as matcher: yield matcher @pytest.fixture() def load_fixture(mocker): def match_request(request): data = request.text.read() if b',' in data: assert request.headers['Content-Type'] == 'text/csv', 'Wrong file format' elif b'\t' in data: assert request.headers['Content-Type'] == \ 'text/tab-separated-values', 'Wrong file format' elif b'FITSTable' in data: assert request.headers['Content-Type'] == \ 'application/fits', 'Wrong file format' else: assert False, 'BUG' return True with mocker.register_uri( 'POST', 'https://example.com/tap/load/abc', additional_matcher=match_request, status_code=200, ) as matcher: yield matcher def get_index_job(phase): return """ v3njuz4k1ebpdb5q user {} 2021-10-29T17:34:19.638 2021-10-28T17:34:19.638 14400 2021-11-04T17:34:19.638 article cadcauthtest1.pyvoTestTable true """.format(phase).encode('utf-8') @pytest.fixture() def overflow_fixture(mocker): """Mock TAP service that returns overflow status with exactly 10 records""" def callback(request, context): votable_content = ''' Result truncated due to MAXREC ''' + ''.join(f'' for i in range(10)) + '''
{i}test{i}
''' return votable_content.encode('utf-8') with mocker.register_uri( 'POST', 'http://example.com/tap/sync', content=callback ) as matcher: yield matcher class MockAsyncTAPServer: def __init__(self): self._jobs = dict() def validator(self, request): data = dict(parse_qsl(request.body)) if 'QUERY' in data: if "select" not in data['QUERY'].casefold(): raise DALQueryError("Missing select") def use(self, mocker): with ExitStack() as stack: matchers = { 'create': stack.enter_context(mocker.register_uri( 'POST', 'http://example.com/tap/async', content=self.create )), 'job': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_path_full, content=self.job )), 'phase': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_phase_full, content=self.phase )), 'parameters': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_parameters_full, content=self.parameters )), 'result': stack.enter_context(mocker.register_uri( 'GET', job_re_result_full, content=self.result )), 'get_job': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/async/111', content=self.get_job )), 'get_job_list': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/async', content=self.get_job_list )) } yield matchers def create(self, request, context): if request.method == 'GET': return self.get_job_list(request, context) self.validator(request) newid = max(list(self._jobs.keys()) or [0]) + 1 data = dict(parse_qsl(request.body)) job = JobFile() job.version = "1.1" job.jobid = newid if 'test_erroneus_submit.non_existent' in request.text: job.phase = 'ERROR' job._errorsummary = ErrorSummary() job.errorsummary.message = Message() job.errorsummary.message.content =\ 'test_erroneus_submit.non_existent not found' else: job.phase = 'PENDING' job.quote = Time.now() + TimeDelta(1, format='sec') job.creationtime = Time.now() job.executionduration = TimeDelta(3600, format='sec') job.destruction = Time.now() + TimeDelta(3600, format='sec') for key, value in data.items(): param = Parameter(id=key) param.content = value job.parameters.append(param) context.status_code = 303 context.reason = 'See other' context.headers['Location'] = ( f'http://example.com/tap/async/{newid}') self._jobs[newid] = job def job(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) if request.method == 'GET': job = self._jobs[jobid] io = BytesIO() job.to_xml(io) return io.getvalue() elif request.method == 'POST': data = dict(parse_qsl(request.body)) action = data.get('ACTION') if action == 'DELETE': del self._jobs[jobid] def phase(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) if request.method == 'GET': phase = self._jobs[jobid].phase return phase elif request.method == 'POST': newphase = request.body.split('=')[-1] job = self._jobs[jobid] result = get_pkg_data_contents('data/tap/obscore-image.xml') if newphase == 'RUN': newphase = 'COMPLETED' result = Result(**{ 'id': 'result', 'size': len(result), 'mime-type': 'application/x-votable+xml', 'xlink:href': ( 'http://example.com/tap/async/{}/results/result' ).format(jobid) }) try: job.results[0] = result except (IndexError, TypeError): job.results.append(result) job.phase = newphase def parameters(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) job = self._jobs[jobid] if request.method == 'GET': pass elif request.method == 'POST': data = dict(parse_qsl(request.body)) if 'QUERY' in data: assert data['QUERY'] == 'SELECT TOP 42 * FROM ivoa.obsCore' for param in job.parameters: if param.id_.lower() == 'query': param.content = data['QUERY'] if 'UPLOAD' in data: for param in job.parameters: if param.id_.lower() == 'upload': uploads1 = {data[0]: data[1] for data in [ data.split(',') for data in data['UPLOAD'].split(';') ]} uploads2 = {data[0]: data[1] for data in [ data.split(',') for data in param.content.split(';') ]} uploads1.update(uploads2) param.content = ';'.join([ f'{key}={value}' for key, value in uploads1.items() ]) def result(self, request, context): self.validator(request) return get_pkg_data_contents('data/tap/obscore-image.xml') def get_job(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) job = JobFile() job.jobid = jobid job.phase = 'EXECUTING' job.ownerid = '222' job.creationtime = Time.now() io = BytesIO() job.to_xml(io) return io.getvalue() def _get_jobref_rep(self, jobid, phase, runid, ownerid, creation_time): doc = (' \n' ' {}\n' ' {}\n' ' {}\n' ' {}\n' ' \n') return doc.format(jobid, phase, runid, ownerid, creation_time) def get_job_list(self, request, context): self.validator(request) fields = parse_qsl(request.query) phases = [] last = None after = None for arg, val in fields: if arg == 'PHASE': phases.append(val) elif arg == 'LAST': last = int(val) elif arg == 'AFTER': after = val doc = '\n' +\ '\n' if phases: doc += self._get_jobref_rep('abc1', 'EXECUTING', 'def1', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc2', 'EXECUTING', 'def2', '21', '2018-12-20T00:23:15.79') if after: doc += self._get_jobref_rep('abc3', 'EXECUTING', 'def3', '21', '2018-12-20T00:23:15.79') if last: doc += self._get_jobref_rep('abc4', 'EXECUTING', 'def4', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc5', 'EXECUTING', 'def5', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc6', 'EXECUTING', 'def6', '21', '2018-12-20T00:23:15.79') doc += '' return doc.encode('UTF-8') @pytest.fixture() def async_fixture(mocker): mock_server = MockAsyncTAPServer() yield from mock_server.use(mocker) @pytest.fixture() def async_fixture_with_timeout(mocker): mock_server = MockAsyncTAPServer() yield from mock_server.use(mocker) @pytest.fixture() def tables(mocker): def callback_tables(request, context): return get_pkg_data_contents('data/tap/tables.xml') def callback_table1(request, context): return get_pkg_data_contents('data/tap/lazy-table1.xml') def callback_table2(request, context): return get_pkg_data_contents('data/tap/lazy-table2.xml') with ExitStack() as stack: matchers = { 'tables': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables', content=callback_tables )), 'table1': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables/test.table1', content=callback_table1 )), 'table2': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables/test.table2', content=callback_table2 )), } yield matchers @pytest.fixture() def examples(mocker): def callback_examplesXHTML(request, context): uri = f"://{request.netloc}{request.path}" if uri == '://example.com/tap/examples': return get_pkg_data_contents('data/tap/examples.htm') elif uri == '://example.org/obscore-examples.html': return get_pkg_data_contents('data/tap/obscore-examples.html') else: assert False, f"Unexpected examples URI: {uri}" with mocker.register_uri( 'GET', requests_mock.ANY, content=callback_examplesXHTML ) as matcher: yield matcher @pytest.fixture() def capabilities(mocker): def callback(request, context): return get_pkg_data_contents('data/tap/capabilities.xml') with mocker.register_uri( 'GET', 'http://example.com/tap/capabilities', content=callback ) as matcher: yield matcher @pytest.fixture() def tapservice(capabilities): """ preferably use this fixture when you need a generic TAP service; it saves a bit of parsing overhead. (but of course make sure you don't modify it). """ return TAPService('http://example.com/tap') def test_escape(): query = 'SELECT * FROM ivoa.obscore WHERE dataproduct_type = {}' query = query.format(escape("'image'")) assert query == ( "SELECT * FROM ivoa.obscore WHERE dataproduct_type = ''image''") @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('http://example.com/tap', "SELECT * FROM ivoa.obscore") _test_image_results(results) class TestTAPService: def test_init(self): url = 'http://example.com/tap' description = "An example TAP service." service = TAPService(url, capability_description=description) assert service.baseurl == url assert service.capability_description == description assert str(service) == f"""TAPService(baseurl : '{url}', description : '{description}')""" def _test_tables(self, table1, table2): assert table1.description == 'Lazy Test Table 1' assert table1.title == 'Test table 1' assert table2.description == 'Lazy Test Table 2' assert table2.title == 'Test table 2' @pytest.mark.usefixtures('tables') def test_tables(self): service = TAPService('http://example.com/tap') vositables = service.tables assert list(vositables.keys()) == ['test.table1', 'test.table2'] assert "test.table1" in vositables assert "any.random.stuff" not in vositables table1, table2 = list(vositables) self._test_tables(table1, table2) def _test_examples(self, parsed_examples): assert len(parsed_examples) == 6 assert "SELECT * FROM rosmaster" in parsed_examples[0]['QUERY'] # the last query is from the continuation assert parsed_examples[-1]['QUERY'].startswith( "\nSELECT access_url, t_exptime, t_min FROM ivoa.obscore") @pytest.mark.usefixtures('examples') def test_examples(self): service = TAPService('http://example.com/tap') service_examples = service.examples self._test_examples(service_examples) @pytest.mark.usefixtures('capabilities') def test_maxrec(self): service = TAPService('http://example.com/tap') assert service.maxrec == 20000 @pytest.mark.usefixtures('capabilities') def test_hardlimit(self): service = TAPService('http://example.com/tap') assert service.hardlimit == 10000000 @pytest.mark.usefixtures('capabilities') def test_upload_methods(self): service = TAPService('http://example.com/tap') upload_methods = service.upload_methods assert upload_methods[0].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-https') assert upload_methods[1].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-ftp') assert upload_methods[2].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-inline') assert upload_methods[3].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-http') @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_run_sync(self): service = TAPService('http://example.com/tap') results = service.run_sync("SELECT * FROM ivoa.obscore") _test_image_results(results) @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(self): service = TAPService('http://example.com/tap') results = service.search("SELECT * FROM ivoa.obscore") _test_image_results(results) @pytest.mark.usefixtures('async_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_run_async(self, monkeypatch): service = TAPService('http://example.com/tap') mock_delete = Mock() monkeypatch.setattr(AsyncTAPJob, "delete", mock_delete) results = service.run_async("SELECT * FROM ivoa.obscore", delete=False) _test_image_results(results) # make sure that delete was not called mock_delete.assert_not_called() @pytest.mark.usefixtures('async_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_run_async_on_error(self, monkeypatch): service = TAPService('http://example.com/tap') mock_delete = Mock() monkeypatch.setattr(AsyncTAPJob, "delete", mock_delete) with pytest.raises(DALQueryError): service.run_async("bad query", delete=True) # make sure that the job is deleted even with a bad query mock_delete.assert_called_once() @pytest.mark.usefixtures('async_fixture') def test_submit_job(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") assert job.url == 'http://example.com/tap/async/' + job.job_id assert job.phase == 'PENDING' assert job.execution_duration == TimeDelta(3600, format='sec') assert isinstance(job.destruction, Time) assert isinstance(job.quote, Time) assert job.query == "SELECT * FROM ivoa.obscore" job.run() job.wait() job.delete() @pytest.mark.usefixtures('async_fixture') def test_erroneus_submit_job(self): service = TAPService('http://example.com/tap') job = service.submit_job( "SELECT * FROM test_erroneus_submit.non_existent") with pytest.raises(DALQueryError) as e: job.raise_if_error() assert 'test_erroneus_submit.non_existent not found' in str(e) @pytest.mark.usefixtures('async_fixture') def test_submit_job_case(self): """Test using mixed case in the QUERY parameter to a job. DALI requires that query parameter names be case-insensitive, and some TAP servers reflect the input case into the job record, so the TAP client has to be prepared for any case for the QUERY parameter name. """ service = TAPService('http://example.com/tap') # This has to be tested manually, bypassing the normal client layer, # in order to force a mixed-case parameter name. response = service._session.post( "http://example.com/tap/async", data={ "REQUEST": "doQuery", "LANG": "ADQL", "quERy": "SELECT * FROM ivoa.obscore", } ) response.raw.read = partial(response.raw.read, decode_content=True) job = AsyncTAPJob(response.url, session=service._session) assert job.url == 'http://example.com/tap/async/' + job.job_id assert job.query == "SELECT * FROM ivoa.obscore" @pytest.mark.usefixtures('async_fixture') def test_modify_job(self): service = TAPService('http://example.com/tap') job = service.submit_job( "SELECT * FROM ivoa.obscore", uploads={ 'one': 'http://example.com/uploads/one' }) job.query = "SELECT TOP 42 * FROM ivoa.obsCore" job.upload(two='http://example.com/uploads/two') for parameter in job._job.parameters: if parameter.id_ == 'query': assert parameter.content == 'SELECT TOP 42 * FROM ivoa.obsCore' break elif parameter.id_ == 'upload': assert ( 'one=http://example.com/uploads/one' in parameter.content) assert ( 'two=http://example.com/uploads/two' in parameter.content) @pytest.mark.usefixtures('async_fixture') def test_get_job(self): service = TAPService('http://example.com/tap') job = service.get_job('111') assert job.jobid == '111' assert job.phase == 'EXECUTING' assert job.ownerid == '222' @pytest.mark.usefixtures('async_fixture') @pytest.mark.remote_data def test_get_job_list(self): service = TAPService('http://example.com/tap') # server returns: # - 3 jobs for last atribute # - 2 jobs for phase attribute # - 1 job for after attribute # Tests consists in counting the cumulative number of jobs as per # above rules after = datetime.datetime.now(tz=datetime.timezone.utc) assert len(service.get_job_list()) == 0 assert len(service.get_job_list(last=3)) == 3 assert len(service.get_job_list(after='2018-04-25T17:46:01Z')) == 1 assert len(service.get_job_list(phases=['EXECUTING'])) == 2 assert len(service.get_job_list(after=after, phases=['EXECUTING'])) == 3 assert len(service.get_job_list(after='2018-04-25T17:46:01.123Z', last=3)) == 4 assert len(service.get_job_list(phases=['EXECUTING'], last=3)) == 5 assert len(service.get_job_list(phases=['EXECUTING'], last=3, after=datetime.datetime.now(tz=datetime.timezone.utc))) == 6 @pytest.mark.usefixtures('create_fixture') def test_create_table(self): prototype.activate_features('cadc-tb-upload') try: buffer = BytesIO(b'table definition in VOSITable format') service = TAPService('https://example.com/tap') service.create_table(name='abc', definition=buffer) tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'table definition in VOTable format here') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.create_table('abc', definition=f, format='VOTable') with pytest.raises(ValueError): service.create_table('abc', definition=buffer, format='Unknown') with pytest.raises(ValueError): service.create_table('abc', definition=None, format='VOSITable') finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures('delete_fixture') def test_remove_table(self): prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') service.remove_table(name='abc') finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures('load_fixture') def test_load_table(self): # csv content in buffer prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') table_content = BytesIO(b'article,count\nart1,1\nart2,2\nart3,3') service.load_table(name='abc', source=table_content, format='csv') # tsv content in file tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'article\tcount\nart1\t1\nart2\t2\nart3\t3') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.load_table('abc', source=f, format='tsv') # FITSTable content in file tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'FITSTable content here') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.load_table('abc', source=f, format='FITSTable') with pytest.raises(ValueError): service.load_table('abc', source=table_content, format='Unknown') with pytest.raises(ValueError): service.load_table('abc', source=None, format='tsv') finally: prototype.deactivate_features('cadc-tb-upload') def test_create_index(self): prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') def match_request_text(request): # check details of index are present return 'table=abc&index=col1&unique=true' in request.text with requests_mock.Mocker() as rm: # mock initial post to table-update and the subsequent calls to # get, run and check status of the job rm.post('https://example.com/tap/table-update', additional_matcher=match_request_text, status_code=303, headers={'Location': 'https://example.com/tap/uws'}) rm.get('https://example.com/tap/uws', [{'content': get_index_job("PENDING")}, {'content': get_index_job("COMPLETED")}]) rm.post('https://example.com/tap/uws/phase', status_code=200) # finally the call service.create_index(table_name='abc', column_name='col1', unique=True) # test wrong return status code with requests_mock.Mocker() as rm: # mock initial post to table-update and the subsequent calls to # get, run and check status of the job rm.post('https://example.com/tap/table-update', additional_matcher=match_request_text, status_code=200, # NOT EXPECTED! headers={'Location': 'https://example.com/tap/uws'}) with pytest.raises(RuntimeError): service.create_index(table_name='abc', column_name='col1', unique=True) finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures('async_fixture') def test_job_no_result(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") with pytest.raises(DALServiceError) as excinfo: job.fetch_result() assert "No result URI available" in str(excinfo.value) job.delete() @pytest.mark.usefixtures('async_fixture') def test_fetch_result_network_error(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', exc=requests.exceptions.ConnectTimeout ) with pytest.raises(DALServiceError) as excinfo: job.fetch_result() assert "Unknown service error" in str(excinfo.value) job.delete() @pytest.mark.usefixtures('async_fixture') def test_job_no_result_uri(self): status_response = ''' 1 COMPLETED ''' service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) job._update() with pytest.raises(DALServiceError) as excinfo: job.fetch_result() assert "No result URI available" in str(excinfo.value) job.delete() @pytest.mark.usefixtures('async_fixture') def test_job_with_empty_error(self): error_response = ''' 1 ERROR ''' service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=error_response) job._update() with pytest.raises(DALQueryError) as excinfo: job.fetch_result() assert "" in str(excinfo.value) @pytest.mark.usefixtures('async_fixture') def test_endpoint_503_with_retry_after(self): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.get('http://example.com/tap/capabilities', status_code=503, headers={'Retry-After': '30'}, text='Service temporarily unavailable') rm.get('http://example.com/capabilities', status_code=404) with pytest.raises(DALServiceError) as excinfo: service._get_endpoint('capabilities') error_msg = str(excinfo.value) assert "503 Service Unavailable (Retry-After: 30)" in error_msg assert "404 Not Found" in error_msg @pytest.mark.usefixtures('async_fixture') def test_endpoint_case_insensitive_retry_after(self): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.get('http://example.com/tap/capabilities', status_code=503, headers={'RETRY-AFTER': '60'}, text='Service temporarily unavailable') rm.get('http://example.com/capabilities', status_code=404) with pytest.raises(DALServiceError) as excinfo: service._get_endpoint('capabilities') error_msg = str(excinfo.value) assert "503 Service Unavailable (Retry-After: 60)" in error_msg @pytest.mark.usefixtures('async_fixture') def test_endpoint_stops_on_server_error(self): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: first_url = rm.get('http://example.com/tap/capabilities', status_code=500, text='Internal Server Error') second_url = rm.get('http://example.com/capabilities', text='Success') with pytest.raises(DALServiceError) as excinfo: service._get_endpoint('capabilities') assert first_url.call_count == 1 assert second_url.call_count == 0 error_msg = str(excinfo.value) assert "HTTP Code: 500" in error_msg @pytest.mark.usefixtures('async_fixture') def test_job_result_with_non_standard_id(self): """Test fix for Github issue https://github.com/astropy/pyvo/issues/670""" status_response = ''' 1 COMPLETED ''' service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) job._update() result = job.result assert result is not None assert result.id_ == "main" assert result.href.endswith("results/result") @pytest.mark.usefixtures('async_fixture') def test_job_result_fallback_to_id_based_lookup(self): status_response = ''' 1 COMPLETED ''' service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) job._update() result = job.result assert result is not None assert result.id_ == "result" assert not result.href.endswith("results/result") @pytest.mark.usefixtures('async_fixture') def test_job_result_multiple_results(self): """Test that standard URL structure is preferred when multiple results exist.""" status_response = ''' 1 COMPLETED ''' # Check that we return the /results/result URL if it exists service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) job._update() result = job.result assert result is not None assert result.id_ == "main" assert result.href.endswith("results/result") @pytest.mark.usefixtures('async_fixture') def test_job_result_empty_href(self): status_response = ''' 1 COMPLETED ''' service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) job._update() result = job.result assert result is None @pytest.mark.usefixtures('async_fixture') def test_job_result_handles_attribute_error(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job._job = None result = job.result assert result is None @pytest.mark.usefixtures('async_fixture') def test_job_result_handles_malformed_results(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") mock_result = Mock() del mock_result.href mock_job = Mock() mock_job.results = [mock_result] job._job = mock_job result = job.result assert result is None @pytest.mark.usefixtures('async_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_fetch_result_retry_connection_error(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) call_count = 0 def response_callback(request, context): nonlocal call_count call_count += 1 if call_count <= 2: raise requests.exceptions.ConnectionError() else: return get_pkg_data_contents('data/tap/obscore-image.xml') rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', content=response_callback ) result = job.fetch_result(max_retries=2) assert len(result) == 10 assert call_count == 3 job.delete() @pytest.mark.usefixtures('async_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_fetch_result_retry_timeout_error(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) call_count = 0 def response_callback(request, context): nonlocal call_count call_count += 1 if call_count == 1: raise requests.exceptions.Timeout() else: return get_pkg_data_contents('data/tap/obscore-image.xml') rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', content=response_callback ) result = job.fetch_result(max_retries=1) assert len(result) == 10 assert call_count == 2 job.delete() @pytest.mark.usefixtures('async_fixture') def test_fetch_result_no_retry_on_http_error(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', status_code=404 ) with pytest.raises(DALServiceError): job.fetch_result(max_retries=2) job.delete() @pytest.mark.usefixtures('async_fixture') def test_fetch_result_retry_exhausted(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', exc=requests.exceptions.ConnectionError() ) with pytest.raises(DALServiceError): job.fetch_result(max_retries=2) job.delete() @pytest.mark.usefixtures('async_fixture') def test_fetch_result_default_no_retry(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") job.run() job.wait() status_response = ''' 1 COMPLETED ''' with requests_mock.Mocker() as rm: rm.get(f'http://example.com/tap/async/{job.job_id}', text=status_response) rm.get( f'http://example.com/tap/async/{job.job_id}/results/result', exc=requests.exceptions.ConnectionError() ) with pytest.raises(DALServiceError): job.fetch_result() job.delete() @pytest.mark.usefixtures("tapservice") class TestTAPCapabilities: def test_no_tap_cap(self): svc = TAPService('http://example.com/tap') svc.capabilities = [] with pytest.raises(DALServiceError) as excinfo: svc.get_tap_capability() assert str(excinfo.value) == ("Invalid TAP service:" " Does not expose a tr:TableAccess capability") def test_no_adql(self): svc = TAPService('http://example.com/tap') svc.get_tap_capability()._languages = [] with pytest.raises(VOSIError) as excinfo: svc.get_tap_capability().get_adql() assert str(excinfo.value) == ("Invalid TAP service:" " Does not declare an ADQL language") def test_get_adql(self, tapservice): assert tapservice.get_tap_capability().get_adql().name == "ADQL" def test_missing_featurelist(self, tapservice): assert ( tapservice.get_tap_capability().get_adql().get_feature_list("fump") == []) def test_get_featurelist(self, tapservice): features = tapservice.get_tap_capability().get_adql().get_feature_list( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo") assert {f.form for f in features} == { 'CENTROID', 'CONTAINS', 'COORD1', 'POLYGON', 'INTERSECTS', 'COORD2', 'BOX', 'AREA', 'DISTANCE', 'REGION', 'CIRCLE', 'POINT'} def test_get_missing_feature(self, tapservice): assert tapservice.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo", "Garage") is None def test_get_feature(self, tapservice): feature = tapservice.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo", "AREA") assert feature.form == "AREA" assert feature.description is None def test_missing_udf(self, tapservice): assert tapservice.get_tap_capability().get_adql().get_udf("duff function") is None def test_get_udf(self, tapservice): func = tapservice.get_tap_capability().get_adql().get_udf("IVO_hasword") # case insensitive! assert func.form == "ivo_hasword(haystack TEXT, needle TEXT) -> INTEGER" def test_get_endpoint_candidates(): # Directly instantiate the TAPService with a known base URL svc = TAPService("http://astroweb.projects.phys.ucl.ac.uk:8000/tap") # Check if the correct endpoint candidates are generated expected_urls = [ "http://astroweb.projects.phys.ucl.ac.uk:8000/tap/capabilities", "http://astroweb.projects.phys.ucl.ac.uk:8000/capabilities" ] assert svc._get_endpoint_candidates("capabilities") == expected_urls def test_timeout_error(): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', exc=requests.Timeout("Request timed out") ) rm.register_uri( 'GET', 'http://example.com/capabilities', exc=requests.Timeout("Request timed out") ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Request timed out" in error_message def test_generic_request_exception(): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', exc=requests.RequestException("Some request error") ) rm.register_uri( 'GET', 'http://example.com/capabilities', exc=requests.RequestException("Some request error") ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Some request error" in error_message def test_unexpected_exception(): service = TAPService('http://example.com/tap') class CustomException(Exception): pass with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', exc=CustomException("Unexpected error occurred") ) rm.register_uri( 'GET', 'http://example.com/capabilities', exc=CustomException("Unexpected error occurred") ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message def test_tap_service_initialization_error(): with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', status_code=404 ) rm.register_uri( 'GET', 'http://example.com/capabilities', status_code=404 ) service = TAPService('http://example.com/tap') with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message assert "404" in error_message def test_endpoint_connection_errors(): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', status_code=404 ) rm.register_uri( 'GET', 'http://example.com/capabilities', status_code=404 ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message assert "404" in error_message assert "The service URL is incorrect" in error_message def test_invalid_tap_url(): service = TAPService('not-a-url') with requests_mock.Mocker() as rm: rm.register_uri( 'GET', requests_mock.ANY, exc=requests.exceptions.ConnectionError( "Failed to establish connection") ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message assert "Connection failed" in error_message def test_http_error_responses(): error_codes = { 403: "Forbidden", 500: "Internal Server Error", 502: "Bad Gateway", 503: "Service Unavailable" } for code, reason in error_codes.items(): with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', status_code=code, reason=reason ) rm.register_uri( 'GET', 'http://example.com/capabilities', status_code=code, reason=reason ) service = TAPService('http://example.com/tap') with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message assert f"{code} {reason}" in error_message def test_network_error(): service = TAPService('http://example.com/tap') with requests_mock.Mocker() as rm: rm.register_uri( 'GET', 'http://example.com/tap/capabilities', exc=requests.exceptions.ConnectionError( "Failed to establish connection") ) rm.register_uri( 'GET', 'http://example.com/capabilities', exc=requests.exceptions.ConnectionError( "Failed to establish connection") ) with pytest.raises(DALServiceError) as excinfo: _ = service.capabilities error_message = str(excinfo.value) assert "Unable to access the capabilities endpoint at:" in error_message assert "Connection failed" in error_message @pytest.mark.remote_data @pytest.mark.parametrize('stream_type', [BytesIO, StringIO]) def test_tap_upload_remote(stream_type): tmp_table = (''' external URI for the physical artifact
2013.1.01365.S
''') if stream_type == BytesIO: tmp_table = tmp_table.encode() query = ('select top 3 proposal_id from ivoa.ObsCore oc join ' 'TAP_UPLOAD.proj_codes pc on oc.proposal_id=pc.prop_id') service = TAPService('https://almascience.nrao.edu/tap') res = service.search(query, uploads={'proj_codes': stream_type(tmp_table)}) assert len(res) == 3 for row in res: assert row['proposal_id'] == '2013.1.01365.S' @pytest.mark.usefixtures('overflow_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") class TestTAPOverflowWarnings: """Test TAP overflow warning behavior""" def test_sync_no_maxrec_overflow_warning(self): service = TAPService('http://example.com/tap') with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"): results = service.run_sync("SELECT * FROM ivoa.obscore") assert len(results) == 10 def test_sync_maxrec_exact_match_no_warning(self): service = TAPService('http://example.com/tap') with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") _ = service.run_sync("SELECT * FROM ivoa.obscore", maxrec=10) overflow_warnings = [warning for warning in w if issubclass(warning.category, DALOverflowWarning)] assert len(overflow_warnings) == 0, f"Unexpected overflow warnings: {overflow_warnings}" def test_sync_maxrec_service_truncation_warning(self): service = TAPService('http://example.com/tap') with pytest.warns(DALOverflowWarning, match="Results truncated at " "10 records by service limits.*you requested maxrec=100"): results = service.run_sync("SELECT * FROM ivoa.obscore", maxrec=100) assert len(results) == 10 def test_search_function_no_maxrec(self): with pytest.warns(DALOverflowWarning, match="Results truncated due to server limits"): results = search('http://example.com/tap', "SELECT * FROM ivoa.obscore") assert len(results) == 10 def test_search_function_with_maxrec(self): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") _ = search('http://example.com/tap', "SELECT * FROM " "ivoa.obscore", maxrec=10) overflow_warnings = [warning for warning in w if issubclass(warning.category, DALOverflowWarning)] assert len(overflow_warnings) == 0, f"Unexpected overflow warnings: {overflow_warnings}" def test_tapresults_suppresses_construction_warning(self): from pyvo.dal.tap import TAPResults from astropy.io.votable import parse as votableparse import requests response = requests.post('http://example.com/tap/sync', data={'QUERY': 'SELECT * FROM test'}) votable = votableparse(BytesIO(response.content)) with warnings.catch_warnings(): warnings.simplefilter("error") result = TAPResults(votable, url='http://test.com', session=None) assert len(result) == 10 def test_tap_create_query_maxrec_tracking(self): service = TAPService('http://example.com/tap') query = service.create_query("SELECT * FROM ivoa.obscore", maxrec=10) assert query._client_set_maxrec == 10 assert query["MAXREC"] == 10 query2 = service.create_query("SELECT * FROM ivoa.obscore") assert query2._client_set_maxrec is None assert "MAXREC" not in query2 def test_edge_case_maxrec_zero(self): service = TAPService('http://example.com/tap') query = service.create_query("SELECT * FROM ivoa.obscore", maxrec=0) assert query._client_set_maxrec == 0 assert "MAXREC" not in query @pytest.mark.usefixtures('async_fixture') class TestTAPAsyncOverflowWarnings: """Test async TAP overflow warnings""" def test_async_job_stores_maxrec(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore", maxrec=5) assert job._client_set_maxrec == 5 def test_async_job_manual_creation_no_maxrec(self): job_response = ''' 123 PENDING 2025-07-29T17:34:19.638 2025-07-28T17:34:19.638 14400 2025-07-04T17:34:19.638 ''' with requests_mock.Mocker() as rm: rm.get('http://example.com/tap/async/123', text=job_response) job = AsyncTAPJob('http://example.com/tap/async/123') assert job._client_set_maxrec is None assert job.job_id == '123' assert job.phase == 'PENDING' def test_tap_integration_with_existing_tests(): service = TAPService('http://example.com/tap') query = service.create_query("SELECT * FROM ivoa.obscore") assert query is not None assert hasattr(query, '_client_set_maxrec') astropy-pyvo-b70558c/pyvo/dal/vosi.py000066400000000000000000000220711510533647000176420ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ VOSI classes and mixins """ from itertools import chain import requests from urllib.parse import urlparse from astropy.utils.decorators import lazyproperty, deprecated from .exceptions import DALServiceError from ..io import vosi from ..utils.url import url_sibling from ..utils.decorators import stream_decode_content, response_decode_content from ..utils.http import use_session __all__ = ['CapabilityMixin', 'VOSITables'] class EndpointMixin: def _get_endpoint_candidates(self, endpoint): """Construct endpoint URLs from base URL and endpoint""" urlcomp = urlparse(self.baseurl) # Include the port number if present netloc = urlcomp.hostname if urlcomp.port: netloc += f':{urlcomp.port}' curated_baseurl = f'{urlcomp.scheme}://{netloc}{urlcomp.path}' if not endpoint: raise AttributeError('endpoint required') return [f'{curated_baseurl}/{endpoint}', url_sibling(curated_baseurl, endpoint)] @staticmethod def _build_error_message(endpoint, attempted_urls): """Build error message from attempted URLs and their failures""" return ( f"Unable to access the {endpoint} endpoint at:\n" f"{chr(10).join(attempted_urls)}\n\n" f"This could mean:\n" f"1. The service URL is incorrect\n" f"2. The service is temporarily unavailable\n" f"3. The service doesn't support this protocol\n" f"4. If a 503 was encountered, retry after the suggested delay.\n" ) @staticmethod def _get_response_body(response): """Extract response body for error messagess""" max_length = 500 # Truncate if too long (500 chars), to avoid overwhelming the user if response is None or not response.headers.get("Content-Type", "").startswith("text"): return "" return f" Response body: {response.text[:max_length]}" def _handle_http_error(self, error, url, attempted_urls): """Handle HTTP errors and update attempted_urls list""" response_body = self._get_response_body(error.response) if error.response.status_code == 503: # Handle Retry-After header, if present retry_after = None for header in error.response.headers: if header.lower() == 'retry-after': retry_after = error.response.headers[header] break if retry_after: attempted_urls.append( f"- {url}: 503 Service Unavailable (Retry-After: " f"{retry_after}){response_body}") return True elif error.response.status_code == 404: attempted_urls.append(f"- {url}: 404 Not Found") return True status_code = error.response.status_code attempted_urls.append( f"- {url}: HTTP Code: {status_code} " f"{error.response.reason}{response_body}") return False def _get_endpoint(self, endpoint): """Attempt to connect to service endpoint""" attempted_urls = [] try: candidates = self._get_endpoint_candidates(endpoint) except (ValueError, AttributeError) as e: raise DALServiceError( f"Cannot construct endpoint URL from base '{self.baseurl}' and endpoint '{endpoint}'. " f"{type(e).__name__}: {str(e)}" ) from e for ep_url in candidates: try: response = self._session.get(ep_url, stream=True) response.raise_for_status() return response.raw except requests.HTTPError as e: if not self._handle_http_error(e, ep_url, attempted_urls): break except requests.ConnectionError: attempted_urls.append( f"- {ep_url}: Connection failed " f"(Possible causes: incorrect URL, DNS issue, or service is down)") break except requests.Timeout: attempted_urls.append( f"- {ep_url}: Request timed out (Service may be overloaded or slow)") except (requests.RequestException, Exception) as e: attempted_urls.append(f"- {ep_url}: {str(e)}") break raise DALServiceError( self._build_error_message(endpoint, attempted_urls)) @deprecated(since="1.5") class AvailabilityMixin(EndpointMixin): """ Mixing for VOSI availability """ @deprecated(since="1.5") @stream_decode_content def _availability(self): """ Service Availability as a :py:class:`~pyvo.io.vosi.availability.Availability` object """ return self._get_endpoint('availability') @lazyproperty @deprecated(since="1.5") def availability(self): return vosi.parse_availability(self._availability().read) @property @deprecated(since="1.5") def available(self): """ True if the service is available, False otherwise """ return self.availability.available @property @deprecated(since="1.5") def up_since(self): """ datetime the service was started """ return self.availability.upsince class CapabilityMixin(EndpointMixin): """ Mixing for VOSI capability """ @stream_decode_content def _capabilities(self): """ Retrieve the raw capabilities document from the service. """ return self._get_endpoint('capabilities') @lazyproperty def capabilities(self): return vosi.parse_capabilities(self._capabilities().read) class TablesMixin(CapabilityMixin): """ Mixin for VOSI tables """ @stream_decode_content def _tables(self): try: interfaces = next( _ for _ in self.capabilities if _.standardid.startswith( 'ivo://ivoa.net/std/VOSI#tables') ).interfaces accessurls = chain.from_iterable(_.accessurls for _ in interfaces) tables_urls = (_.value for _ in accessurls) except StopIteration: tables_urls = [ f'{self.baseurl}/tables', url_sibling(self.baseurl, 'tables') ] for tables_url in tables_urls: try: response = self._session.get(tables_url, stream=True) response.raise_for_status() break except requests.RequestException: continue else: raise DALServiceError("No working tables endpoint provided") return response.raw @lazyproperty def tables(self): return VOSITables(vosi.parse_tables(self._tables().read)) class VOSITables: """ This class encapsulates access to the VOSITables using a given Endpoint. Access to table names is like accessing dictionary keys. using iterator syntax or `keys()` """ def __init__(self, vosi_tables, endpoint_url, *, session=None): self._vosi_tables = vosi_tables self._endpoint_url = endpoint_url self._cache = {} self._session = use_session(session) def __len__(self): return self._vosi_tables.ntables def __getitem__(self, key): return self._get_table(key) def __iter__(self): for tablename in self.keys(): yield self._get_table(tablename) def __contains__(self, tablename): return tablename in self.keys() def _get_table(self, name): if name in self._cache: return self._cache[name] table = self._vosi_tables.get_table_by_name(name) if not table.columns and not table.foreignkeys: tables_url = f'{self._endpoint_url}/{name}' response = self._get_table_file(tables_url) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, tables_url) table = vosi.parse_tables(response.raw.read).get_first_table() self._cache[name] = table return table @response_decode_content def _get_table_file(self, tables_url): return self._session.get(tables_url, stream=True) def keys(self): """ Iterates over the keys (table names). """ for table in self._vosi_tables.iter_tables(): yield table.name def values(self): """ Iterates over the values (tables). Gathers missing values from endpoint if necessary. """ for name in self.keys(): yield self._get_table(name) def items(self): """ Iterates over keys and values (table names and tables). Gathers missing values from endpoint if necessary. """ for name in self.keys(): yield (name, self._get_table(name)) def describe(self): for table in self: table.describe() astropy-pyvo-b70558c/pyvo/dam/000077500000000000000000000000001510533647000163075ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/dam/__init__.py000066400000000000000000000001661510533647000204230ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .obscore import * __all__ = ["ObsCoreMetadata"] astropy-pyvo-b70558c/pyvo/dam/obscore.py000066400000000000000000000056001510533647000203160ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for representing data in ObsCore format """ __all__ = ['ObsCoreMetadata', 'POLARIZATION_STATES', 'CALIBRATION_LEVELS'] # to be moved to ObsCore POLARIZATION_STATES = ['I', 'Q', 'U', 'V', 'RR', 'LL', 'RL', 'LR', 'XX', 'YY', 'XY', 'YX', 'POLI', 'POLA'] CALIBRATION_LEVELS = [0, 1, 2, 3, 4] class ObsCoreMetadata(): """ Representation of an ObsCore observation TBD setters to do validation and unit check. """ def __init__(self): # OBSERVATION INFO self.dataproduct_type = None self.dataproduct_subtype = None self.calib_level = None # TARGET INFO self.target_name = None self.target_class = None # DATA DESCRIPTION self.obs_id = None self.obs_title = None self.obs_collection = None self.obs_create_date = None self.obs_creator_name = None self.obs_creator_did = None # CURATION INFORMATION self.obs_release_date = None self.obs_publisher_did = None self.publisher_id = None self.bib_reference = None self.data_rights = None # ACCESS INFORMATION self.access_url = None self.access_format = None self.access_estsize = None # SPATIAL CHARACTERISATION self.s_ra = None self.s_dec = None self.s_fov = None self.s_region = None self.s_resolution = None self.s_xel1 = None self.s_xel2 = None self.s_ucd = None self.s_unit = None self.s_resolution_min = None self.s_resolution_max = None self.s_calib_status = None self.s_stat_error = None self.s_pixel_scale = None # TIME CHARACTERISATION self.t_xel = None self.t_ref_pos = None self.t_min = None self.t_max = None self.t_exptime = None self.t_resolution = None self.t_calib_status = None self.t_stat_error = None # SPECTRAL CHARACTERISATION self.em_xel = None self.em_ucd = None self.em_unit = None self.em_calib_status = None self.em_min = None self.em_max = None self.em_res_power = None self.em_res_power_min = None self.em_res_power_max = None self.em_resolution = None self.em_stat_error = None # OBSERVABLE AXIS self.o_ucd = None self.o_unit = None self.o_calib_status = None self.o_stat_error = None # POLARIZATION CHARACTERISATION self.pol_xel = None self.pol_states = None # PROVENANCE self.instrument_name = None self.facility_name = None self.proposal_id = None astropy-pyvo-b70558c/pyvo/discover/000077500000000000000000000000001510533647000173645ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/discover/__init__.py000066400000000000000000000011071510533647000214740ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Various functions dealing with global data disovery. """ import warnings from pyvo.utils.prototype import PrototypeWarning # if you remove this warning, also remove the ignorere in test_imagediscovery. warnings.warn("pyvo.discover's API is still under design in pyVO 1.6 and" " may change without prior notice. Feedback to the authors is most" " welcome.", PrototypeWarning) from .image import images_globally, ImageDiscoverer, ImageFound __all__ = ['images_globally', "ImageDiscoverer", "ImageFound"] astropy-pyvo-b70558c/pyvo/discover/image.py000066400000000000000000000601571510533647000210310ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Global image discovery. This module is intended to cover the much-requested use case "give me all images with properties x, y, and z, where these properties for now is the location in space, time, and spectrum; sensitivity clearly would be great, but that needs standard work. The code here looks for data in SIA1, SIA2, and Obscore services for now. """ # TODOs: # * Evaluate and log Overflow conditions in the three protocols # * do more (s_region!) on the basis of the WCS parts in SIA1 # * In obscore, we probably should make the dataproduct type constraint # configurable (this should really also work for cubes) # * Obscore query generation *might* want some extra logic for inclusive, # (as in OR whatever IS NULL) -- but is that a good idea? # * It would be nice if we preserved datalink availability and perhaps # even let people do automatic cutouts to the RoI. import functools import threading import requests from astropy import units as u from astropy import table from astropy import time from ..dam import obscore from .. import dal from .. import registry from ..registry import regtap # imports for type hints from typing import Callable, Optional from collections.abc import Generator from astropy.units import quantity __all__ = ["ImageFound", "ImageDiscoverer", "images_globally"] # We should probably have a general way to set query timeouts in pyVO. # For now, we don't, but for global discovery there's really no way # around them. So, let me hack something here. class SessionWithTimeout(requests.Session): def __init__(self, *args, default_timeout=None, **kwargs): self.default_timeout = default_timeout super().__init__(*args, **kwargs) def request(self, *args, **kwargs): if "timeout" not in kwargs: kwargs["timeout"] = self.default_timeout return super().request(*args, **kwargs) class Queriable: """A facade for a queriable service. We keep these rather than work on `pyvo.registry.RegistryResource`-s directly because the latter actually live in VOTables which are very hard to manipulate. They are constructed with a resource record. """ def __init__(self, res_rec): self.res_rec = res_rec self.ivoid = res_rec.ivoid self.title = self.res_rec.res_title def __str__(self): return f"<{self.ivoid}>" def __repr__(self): return str(self) @functools.cache def obscore_column_names(): """returns the names of obscore columns. For lack of alternatives, I pull them out of ObsCoreMetadata for now. """ return [name for name in dir(obscore.ObsCoreMetadata()) if not name.startswith("_")] class ImageFound(obscore.ObsCoreMetadata): """Obscore metadata for a found record. The mandatory obscore fields are kept as attributes. We are pulling these out from the various VOTables that we retrieve because we need to do some manipulation of them that is simpler to do if they are proper Python objects. This also keeps track of the service the record came from, which is available from the origin_service attribute. """ attr_names = set(obscore_column_names()) def __init__(self, origin_service, obscore_record): self.origin_service = origin_service for k, v in obscore_record.items(): if k not in self.attr_names: raise TypeError( f"ImageFound does not accept {k} keys") setattr(self, k, v) @classmethod def from_obscore_recs(cls, origin_service, obscore_result): if not obscore_result: return ap_table = obscore_result.to_table() our_keys = [n for n in ap_table.colnames if n in cls.attr_names] for row in obscore_result: yield cls( origin_service, dict(zip(our_keys, (row[n] for n in our_keys)))) @classmethod def from_sia1_recs(cls, origin_service, sia1_result, filter_func): for rec in sia1_result: if not filter_func(rec): continue mapped = { "dataproduct_type": "image" if rec.naxes == 2 else "cube", "access_url": rec.acref, "em_max": None if rec.bandpass_hilimit is None else rec.bandpass_hilimit.to(u.m).value, "em_min": None if rec.bandpass_lolimit is None else rec.bandpass_lolimit.to(u.m).value, # Sigh. Try to guess exposure time? "t_min": None if rec.dateobs is None else rec.dateobs.mjd, "t_max": None if rec.dateobs is None else rec.dateobs.mjd, "access_estsize": None if rec.filesize is None else rec.filesize/1024, "access_format": rec.format, "instrument_name": rec.instr, "s_xel1": rec.naxis[0].to(u.pix).value, "s_xel2": rec.naxis[1].to(u.pix).value, "s_ra": rec.pos.icrs.ra.to(u.deg).value, "s_dec": rec.pos.icrs.dec.to(u.deg).value, "obs_title": rec.title, } yield cls(origin_service, mapped) def _clean_for(records: list[Queriable], ivoids_to_remove: set[str]): """returns the Queriables in records the ivoids of which are not in ivoids_to_remove. """ return [r for r in records if r.ivoid not in ivoids_to_remove] class ImageDiscoverer: """A management class for VO global image discovery. This encapsulates all of constraints, service lists, results, and diagnostics. This probably should not be considered API but rather as an implementation detail of discover_images. The normal usage is do call discover_services(), which will locate all VO services that may have relevant data. Alternatively, call set_services(registry_results) with some result of a registry.search() call. ImageDiscoverer will then pick capabilities it can use out of the resource records. Records without usable capabilities are silently ignored. Then call query_services to execute the discovery query on these services. See images_globally for a discussion of its constructor parameters. """ # Constraint defaults # a float in metres spectrum = None # MJD floats time_min = time_max = None # a center as a 2-tuple in ICRS degrees center = None # a radius as a float in degrees radius = None def __init__(self, *, space=None, spectrum=None, time=None, inclusive=False, watcher=None, timeout=20): self.session = SessionWithTimeout(default_timeout=timeout) if space: self.center = (space[0], space[1]) self.radius = space[2] if spectrum is not None: self.spectrum = spectrum.to(u.m, equivalencies=u.spectral()).value if time is not None: if isinstance(time, tuple): self.time_min, self.time_max = time[0].mjd, time[1].mjd else: self.time_min = self.time_max = time.mjd self.inclusive = inclusive self.already_queried, self.failed_services = 0, 0 self.results: list[obscore.ObsCoreMetadata] = [] self.watcher = watcher self.log_messages: list[str] = [] self.known_access_urls: set[str] = set() self._service_list_lock = threading.Lock() with self._service_list_lock: # only write to these while holding the lock self.sia1_recs, self.sia2_recs, self.obscore_recs = [], [], [] def _info(self, message: str) -> None: """sends message to our watcher (if there is any) """ if self.watcher is not None: self.watcher(self, message) def _log(self, message: str) -> None: """logs message. This will also do whatever _info does. """ self.log_messages.append(message) self._info(message) def _purge_redundant_services(self): """removes services querying data already covered by more capable services from our current services lists. """ def ids(recs): return {r.ivoid for r in recs} self.sia1_recs = _clean_for(self.sia1_recs, ids(self.sia2_recs) | ids(self.obscore_recs)) self.sia2_recs = _clean_for(self.sia2_recs, ids(self.obscore_recs)) # In addition, now throw out all services that have an # IsServedBy relationship to another service we will also # query. That's particularly valuable if there are large # obscore services covering data from many SIA1 services. ids_present = table.Table([ table.Column( name="id", data=list( sorted(ids(self.sia1_recs) | ids(self.sia2_recs) | ids(self.obscore_recs))), description="ivoids of candiate services", meta={"ucd": "meta.ref.ivoid"}),]) if len(ids_present) == 0: return services_for = regtap.get_RegTAP_service().run_sync( """SELECT ivoid, related_id FROM rr.relationship JOIN tap_upload.ids AS leftids ON (ivoid=leftids.id) JOIN tap_upload.ids AS rightids ON (related_id=rightids.id) WHERE relationship_type='isservedby' """, uploads={'ids': ids_present}) for rec in services_for: self._log(f"Skipping {rec['ivoid']} because" f" it is served by {rec['related_id']}") collections_to_remove = {r["ivoid"] for r in services_for} self.sia1_recs = _clean_for(self.sia1_recs, collections_to_remove) self.sia2_recs = _clean_for(self.sia2_recs, collections_to_remove) self.obscore_recs = _clean_for(self.obscore_recs, collections_to_remove) def _discover_obscore_services(self, *constraints): # For obscore, we currently have a defunct discovery pattern # ("obscore" in the Datamodel constraint). There is obscore-new, # which fixes the problem, but until that's adopted by all the # obscore services, we have to try both and the pick the # more suitable version. # Once we move obscore-new to obscore, remove this function # and put # self.obscore_recs = [Queriable(r) for r in registry.search( # registry.Datamodel("obscore"), *constraints)] # back into discover_services. obscore_services = registry.search( registry.Datamodel("obscore_new"), *constraints) tap_services_with_obscore = registry.search( registry.Datamodel("obscore"), *constraints) new_style_access_urls = set() for rec in obscore_services: new_style_access_urls |= { i.access_url for i in rec.list_interfaces("tap")} for tap_rec in tap_services_with_obscore: access_urls = { i.baseurl for i in rec.list_services("tap")} if new_style_access_urls.isdisjoint(access_urls): obscore_services.append(obscore_services) return [Queriable(r) for r in obscore_services] def discover_services(self): """fills the X_recs attributes with resources declaring coverage for our constraints. X at this point is sia1, sia2, and obscore. It tries to filter out as many duplicates (i.e., services operating on the same data collections) as it can. The order of preference is Obscore, SIA2, SIA. """ constraints = [] if self.center is not None: constraints.append( registry.Spatial(list(self.center)+[self.radius], inclusive=self.inclusive)) if self.spectrum is not None: constraints.append( registry.Spectral(self.spectrum*u.m, inclusive=self.inclusive)) if self.time_min is not None or self.time_max is not None: constraints.append( registry.Temporal( (self.time_min, self.time_max), inclusive=self.inclusive)) with self._service_list_lock: self.sia1_recs = [Queriable(r) for r in registry.search( registry.Servicetype("sia"), *constraints)] self._info(f"Found {len(self.sia1_recs)} SIA1 service(s)") self.sia2_recs = [Queriable(r) for r in registry.search( registry.Servicetype("sia2"), *constraints)] self._info(f"Found {len(self.sia2_recs)} SIA2 service(s)") self.obscore_recs = self._discover_obscore_services(*constraints) self._info("Found {} Obscore service(s)".format( len(self.obscore_recs))) self._purge_redundant_services() def set_services(self, registry_results: registry.RegistryResults, purge_redundant: bool = True) -> None: """as an alternative to discover_services, this sets the services to be queried to the result of a custom registry query. This will pick the "most capabable" interface from each record and ignore records without image discovery capabilities. If you set purge_redundant to false, this will not attempt to eliminate services that are alternative interfaces to services that are already called. There are very few situations in which you would want to do that, mostly related to debugging. """ with self._service_list_lock: for rsc in registry_results: if "tap" in rsc.access_modes(): # TODO: we ought to ensure there's an obscore # table on this; but by the time this sees users, # I hope to have fixed obscore discovery. self.obscore_recs.append(Queriable(rsc)) elif "sia2" in rsc.access_modes(): self.sia2_recs.append(Queriable(rsc)) elif "sia" in rsc.access_modes(): self.sia1_recs.append(Queriable(rsc)) # else ignore this record if purge_redundant: self._purge_redundant_services() def reset_services(self): """clears the queues of services to query. This is the only supported way to interrupt operations once query_services has been called. This will not interrupt the query currently running. There is currently no way to do that. """ with self._service_list_lock: self.sia1_recs, self.sia2_recs, self.obscore_recs = [], [], [] self._log("Cancelling queries with {} service(s) queried" .format(self.already_queried)) def _add_records(self, recgen: Generator[ImageFound, None, None]) -> int: """adds records from regen to the global results. This will skip datasets the access urls of which we have already seen and will return the number of datasets actually added. """ n_added = 0 for obscore_record in recgen: if obscore_record.access_url in self.known_access_urls: continue self.known_access_urls.add(obscore_record.access_url) self.results.append(obscore_record) n_added += 1 return n_added def _query_one_sia1(self, rec: Queriable): """runs our query against a SIA1 capability of rec. Since SIA1 cannot do spectral and temporal constraints itself, we do them client-side provided sufficient metadata is present. If metadata is missing, we keep or discard accoding to self.inclusive. """ def non_spatial_filter(sia1_rec): if self.spectrum and not self.inclusive and ( sia1_rec.bandpass_hilimit is not None and sia1_rec.bandpass_lolimit is not None): if not (sia1_rec.bandpass_lolimit <= self.spectrum*u.m <= sia1_rec.bandpass_hilimit): return False # Regrettably, the exposure time is not part of SIA1 standard # metadata. We fudge things a bit; this should not # increase false positives very badly. if (self.time_min is not None or self.time_max is not None) \ and not self.inclusive and sia1_rec.dateobs: if not (self.time_min-0.1 < sia1_rec.dateobs.mjd < self.time_max+0.1): return False return True self._info(f"Querying SIA1 {rec.title}...") svc = rec.res_rec.get_service("sia", session=self.session, lax=True) n_found = self._add_records( ImageFound.from_sia1_recs( rec.ivoid, svc.search( pos=self.center, size=self.radius, intersect='overlaps'), non_spatial_filter)) self._log(f"SIA1 {rec.title} {n_found} records") def _query_sia1(self): """runs the SIA1 part of our discovery. This will be a no-op without a space constraint due to limitations of SIA1. """ if self.sia1_recs and self.center is None: self._log("SIA1 service(s) skipped due to missing space" " constraint") return # we don't do a for loop here because we want to react to changes # in self.sia1_recs while self.sia1_recs: rec = self.sia1_recs.pop() try: self._query_one_sia1(rec) except Exception as msg: self._log(f"SIA1 {rec.ivoid} skipped: {msg}") self.failed_services += 1 self.already_queried += 1 def _query_one_sia2(self, rec: Queriable): """runs our query against a SIA2 capability of rec. """ self._info(f"Querying SIA2 {rec.title}...") svc = rec.res_rec.get_service("sia2", session=self.session, lax=True) constraints = {} if self.center is not None: constraints["pos"] = self.center+(self.radius,) if self.spectrum is not None: constraints["band"] = self.spectrum if self.time_min is not None or self.time_max is not None: constraints["time"] = ( time.Time(self.time_min, format="mjd"), time.Time(self.time_max, format="mjd")) n_found = self._add_records( ImageFound.from_obscore_recs( rec.ivoid, svc.search(**constraints))) self._log(f"SIA2 {rec.title}: {n_found} records") def _query_sia2(self): """runs the SIA2 part of our discovery. """ while self.sia2_recs: rec = self.sia2_recs.pop() try: self._query_one_sia2(rec) except Exception as msg: self._log(f"SIA2 {rec.ivoid} skipped: {msg}") self.failed_services += 1 self.already_queried += 1 def _query_one_obscore(self, rec: Queriable, where_clause: str): """runs our query against a Obscore capability of rec. """ self._info(f"Querying Obscore {rec.title}...") svc = rec.res_rec.get_service("tap", session=self.session, lax=True) n_found = self._add_records( ImageFound.from_obscore_recs( rec.ivoid, svc.run_sync("select * from ivoa.obscore "+where_clause))) self._log(f"Obscore {rec.title}: {n_found} records") def _query_obscore(self): """runs the Obscore part of our discovery. """ where_parts = ["dataproduct_type='image'"] if self.center is not None: where_parts.append( "(1=contains(point('ICRS', s_ra, s_dec)," " circle('ICRS', {}, {}, {}))".format( self.center[0], self.center[1], self.radius) + " or 1=intersects(circle({}, {}, {}), s_region))".format( self.center[0], self.center[1], self.radius)) if self.spectrum is not None: where_parts.append( f"(em_min<={self.spectrum} AND em_max>={self.spectrum})") if self.time_min is not None or self.time_max is not None: where_parts.append( "({h1}>={l2} AND {h2}>={l1}" " AND {l1}<={h1} AND {l2}<={h2})".format( l1="t_min", h1="t_max", l2=self.time_min, h2=self.time_max)) where_clause = "WHERE "+(" AND ".join(where_parts)) while self.obscore_recs: rec = self.obscore_recs.pop() try: self._query_one_obscore(rec, where_clause) except Exception as msg: self._log("Obscore {} skipped: {}".format( rec.res_rec['ivoid'], msg)) self.failed_services += 1 self.already_queried += 1 def get_query_stats(self): """returns a tuple of n(total to query), n(already queried) """ total_to_query = len(self.obscore_recs)\ + len(self.sia1_recs)\ + len(self.sia2_recs) return total_to_query, self.already_queried, self.failed_services def query_services(self): """queries the discovered image services according to our constraints. This creates and fills the results and the log_messages attributes. """ if (not self.sia1_recs and not self.sia2_recs and not self.obscore_recs): raise dal.DALQueryError("No services to query. Unless" " you overrode service selection, you will have to" " loosen your constraints.") self._query_obscore() self._query_sia2() self._query_sia1() def images_globally( *, space: Optional[tuple[float, float, float]] = None, spectrum: Optional[quantity.Quantity] = None, time: Optional[time.Time] = None, inclusive: bool = False, watcher: Optional[Callable[['ImageDiscoverer', str], None]] = None, timeout: float = 20, services: Optional[registry.RegistryResults] = None)\ -> tuple[list[obscore.ObsCoreMetadata], list[str]]: """returns a collection of ObsCoreMetadata-s matching certain constraints and a list of log lines. Parameters ---------- space : An optional tuple of ra, dec, and the search radius, all in degrees; images returned must intersect a spherical circle described in this way. spectrum : An astropy quantity convertible to a (vacuum) wavelength; images must cover this point in the (electromagnetic) spectrum. time : An astropy time that must be in the observation time of the image (if it declares a time). inclusive : Set to True to incluse services that do not declare their STC coverage. As of 2024, it may be advisable to do that as many relevant archives do not do that. watcher : A callable that will be called with the ImageDiscoverer instance and a string perhaps suitable for displaying to a human. services : An optional `~pyvo.registry.RegistryResults` instance to override automatic services detection. When an image has insufficient metadata to evaluate a constraint, it is excluded; this mimics the behaviour of SQL engines that consider comparisons with NULL-s false. Returns ------- discovered_images, log_lines The images found are returned in a list of `pyvo.discover.ImageFound` instances. log_lines is a list of strings reporting which services were queried with which result (and possibly more). So far, this is not considered machine-readable. """ discoverer = ImageDiscoverer( space=space, spectrum=spectrum, time=time, inclusive=inclusive, watcher=watcher, timeout=timeout) if services is None: discoverer.discover_services() else: discoverer.set_services(services) discoverer.query_services() return discoverer.results, discoverer.log_messages astropy-pyvo-b70558c/pyvo/discover/tests/000077500000000000000000000000001510533647000205265ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/discover/tests/test_imagediscovery.py000066400000000000000000000231671510533647000251620ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.discover.image """ import weakref import pytest from astropy import coordinates from astropy import time from astropy import units as u from pyvo import dal from pyvo import registry from pyvo.discover import image from pyvo import discover class TestImageFound: def testBadAttrName(self): with pytest.raises(TypeError): image.ImageFound("ivo://x-fake", {"invented": None}) class FakeQueriable: """a scaffolding class to record queries built by the various discovery methods. It's a stand-in for all of discover.Queriable, the resource record, and the services. All query functions just make their search arguments available in search_args and search_kwargs attributes here and otherwise return whatever you pass in as return_this. """ def __init__(self, return_this=[]): self.title = "Fake scaffold service" self.ivoid = "ivo://x-invalid/pyvo-fake" self.return_this = return_this self.res_rec = weakref.proxy(self) def get_service(self, service_type, **kwargs): return self def search(self, *args, **kwargs): self.search_args = args self.search_kwargs = kwargs return self.return_this run_sync = search class FakeSIARec: """a convenience class to halfway easily create SIA1 records sufficient for testing. """ defaults = { "bandpass_lolimit": 5e-7*u.m, "bandpass_hilimit": 7e-7*u.m, "dateobs": time.Time("1970-10-13T14:00:00"), "filesize": 20, "naxis": (10*u.pix, 20*u.pix), "naxes": 2, "pos": coordinates.SkyCoord(0*u.deg, 0*u.deg)} def __init__(self, **kwargs): for key, value in self.defaults.items(): setattr(self, key, value) for key, value in kwargs.items(): setattr(self, key, value) def __getattr__(self, name): return None class TestTimeCondition: def test_sia1(self): # this primarily tests for local filtering queriable = FakeQueriable([ FakeSIARec(), FakeSIARec(dateobs=time.Time("1970-10-14"))]) d = discover.ImageDiscoverer( space=(0, 0, 1), time=( time.Time("1970-10-13T13:00:00"), time.Time("1970-10-13T17:00:00"))) d._query_one_sia1(queriable) assert len(d.results) == 1 assert abs(d.results[0].t_min-40872.583333333336) < 1e-10 assert queriable.search_kwargs == { 'pos': (0, 0), 'size': 1, 'intersect': 'overlaps'} def test_sia1_nointerval(self): queriable = FakeQueriable([ FakeSIARec(), FakeSIARec(dateobs=time.Time("1970-10-14"))]) d = discover.ImageDiscoverer( space=(0, 0, 1), time=time.Time("1970-10-13T13:00:02")) d._query_one_sia1(queriable) assert len(d.results) == 1 def test_sia2(self): queriable = FakeQueriable() d = discover.ImageDiscoverer( time=( time.Time("1970-10-13T13:00:00"), time.Time("1970-10-13T17:00:00"))) d._query_one_sia2(queriable) assert set(queriable.search_kwargs) == {"time"} assert abs(queriable.search_kwargs["time"][0].utc.value - 40872.54166667) < 1e-8 def test_sia2_nointerval(self): queriable = FakeQueriable() d = discover.ImageDiscoverer( time=time.Time("1970-10-13T13:00:00")) d._query_one_sia2(queriable) assert set(queriable.search_kwargs) == {"time"} assert abs(queriable.search_kwargs["time"][0].utc.value - 40872.54166667) < 1e-8 assert abs(queriable.search_kwargs["time"][1].utc.value - 40872.54166667) < 1e-8 def test_obscore(self): queriable = FakeQueriable() d = discover.ImageDiscoverer( time=(time.Time("1970-10-13T13:00:00"), time.Time("1970-10-13T17:00:00"))) d.obscore_recs = [queriable] d._query_obscore() assert set(queriable.search_kwargs) == set() assert queriable.search_args[0] == ( "select * from ivoa.obscore WHERE dataproduct_type='image'" " AND (t_max>=40872.541666666664 AND 40872.708333333336>=t_min" " AND t_min<=t_max AND 40872.541666666664<=40872.708333333336)") def test_obscore_nointerval(self): queriable = FakeQueriable() d = discover.ImageDiscoverer( time=( time.Time("1970-10-13T13:00:00"))) d.obscore_recs = [queriable] d._query_obscore() assert set(queriable.search_kwargs) == set() assert queriable.search_args[0] == ( "select * from ivoa.obscore WHERE dataproduct_type='image'" " AND (t_max>=40872.541666666664 AND 40872.541666666664>=t_min" " AND t_min<=t_max AND 40872.541666666664<=40872.541666666664)") class TestSpaceCondition: def test_sia1_fails_without_space(self): queriable = FakeQueriable() di = discover.ImageDiscoverer( time=( time.Time("1970-10-13T13:00:00"))) di.sia1_recs = [queriable] di.query_services() assert di.log_messages == [ 'SIA1 service(s) skipped due to missing space constraint'] def test_sia2(self): queriable = FakeQueriable() di = discover.ImageDiscoverer(space=(30, 21, 1)) di.sia2_recs = [queriable] di.query_services() assert queriable.search_kwargs == {'pos': (30, 21, 1)} def test_obscore(self): queriable = FakeQueriable() di = discover.ImageDiscoverer(space=(30, 21, 1)) di.obscore_recs = [queriable] di.query_services() assert queriable.search_args == ( "select * from ivoa.obscore WHERE dataproduct_type='image'" " AND (1=contains(point('ICRS', s_ra, s_dec)," " circle('ICRS', 30, 21, 1))" " or 1=intersects(circle(30, 21, 1), s_region))",) def test_no_services_selected(): with pytest.raises(dal.DALQueryError) as excinfo: image.ImageDiscoverer().query_services() assert "No services to query." in str(excinfo.value) # Tests requiring remote data below this line @pytest.mark.remote_data def test_single_sia1(): results, log = discover.images_globally( space=(116, -29, 0.1), time=time.Time(56383.105520834, format="mjd"), services=registry.search( registry.Ivoid("ivo://org.gavo.dc/bgds/q/sia"))) im = results[0] assert im.s_xel1 == 10800. assert isinstance(im.em_min, float) assert abs(im.s_dec+29) < 2 assert im.instrument_name == 'Robotic Bochum Twin Telescope (RoBoTT)' assert "BGDS GDS_" in im.obs_title assert "dc.g-vo.org/getproduct/bgds/data" in im.access_url @pytest.mark.remote_data def test_cone_and_spectral_point(): # This should really return just a few services. If this # starts hitting more services, see how we can throw them out # again, perhaps using a time constraint. watcher_msgs = [] def test_watcher(disco, msg): watcher_msgs.append(msg) images, logs = discover.images_globally( space=(134, 11, 0.1), spectrum=600*u.eV, time=(time.Time('1990-01-01'), time.Time('1999-12-31')), watcher=test_watcher) assert len(logs) < 10, ("Too many services in test_cone_and_spectral." " Try constraining the discovery phase more tightly to" " keep this test economical") skip_msg = ("Skipping ivo://org.gavo.dc/__system__/siap2/sitewide because" " it is served by ivo://org.gavo.dc/__system__/obscore/obscore") assert skip_msg in logs assert skip_msg in watcher_msgs assert len(images) >= 8 assert "RASS" in {im.obs_collection for im in images} @pytest.mark.remote_data def test_servedby_elision(): d = discover.ImageDiscoverer(space=(3, 1, 0.2)) # siap2/sitewide has isservedby to tap d.set_services( registry.search( registry.Ivoid( "ivo://org.gavo.dc/tap", "ivo://org.gavo.dc/__system__/siap2/sitewide"))) assert d.sia2_recs == [] assert len(d.obscore_recs) == 1 assert repr(d.obscore_recs[0]) == "" assert ('Skipping ivo://org.gavo.dc/__system__/siap2/sitewide' ' because it is served by ivo://org.gavo.dc/tap' in d.log_messages) @pytest.mark.remote_data def test_access_url_elision(): with pytest.warns(): di = discover.ImageDiscoverer( time=time.Time("1910-07-15", scale="utc"), spectrum=400*u.nm) di.set_services( registry.search( registry.Ivoid( "ivo://org.gavo.dc/tap", "ivo://org.gavo.dc/__system__/siap2/sitewide")), # that's important here: we *want* to query the same stuff twice purge_redundant=False) di.query_services() # make sure we found anything at all assert di.results assert len([1 for lm in di.log_messages if "skipped" in lm]) == 0 # make sure there are no duplicate records access_urls = [im.access_url for im in di.results] assert len(access_urls) == len(set(access_urls)) @pytest.mark.remote_data def test_cancelling(): def query_killer(disco, msg): if msg.startswith("Querying") and di.already_queried > 0: disco.reset_services() di = discover.ImageDiscoverer( time=time.Time("1980-07-15", scale="utc"), spectrum=400*u.nm, timeout=1, watcher=query_killer) di.set_services(registry.search(servicetype="sia2")) di.query_services() assert ('Cancelling queries with 1 service(s) queried' in di.log_messages) astropy-pyvo-b70558c/pyvo/io/000077500000000000000000000000001510533647000161555ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/__init__.py000066400000000000000000000001001510533647000202550ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/io/uws/000077500000000000000000000000001510533647000167735ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/uws/__init__.py000066400000000000000000000002421510533647000211020ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ['parse_job', 'parse_job_list', 'JobFile'] from .endpoint import * from .tree import * astropy-pyvo-b70558c/pyvo/io/uws/endpoint.py000066400000000000000000000102031510533647000211610ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains a contains the high-level functions to read the various VOSI Endpoints. """ from astropy.utils.xml.writer import XMLWriter from astropy.io.votable.util import convert_to_writable_filelike from ...utils.xml.elements import xmlattribute, parse_for_object from .tree import JobSummary, Jobs __all__ = ["parse_job", "parse_job_list", "JobFile"] def parse_job_list( source, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses a job xml file (or file-like object), and returns a `~pyvo.io.uws.tree.Jobs` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- `~pyvo.io.uws.tree.Jobs` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ return parse_for_object(source, Jobs, pedantic, filename, _debug_python_based_parser).joblist def parse_job( source, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses a job xml file (or file-like object), and returns a `~pyvo.io.uws.endpoint.JobFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- `~pyvo.io.uws.endpoint.JobFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ return parse_for_object(source, JobFile, pedantic, filename, _debug_python_based_parser) class JobFile(JobSummary): """ availability element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self._version = None @xmlattribute def version(self): return self._version @version.setter def version(self, version): self._version = version def parse(self, iterator, config): for start, tag, data, pos in iterator: if start and tag == 'xml': pass elif start and tag == 'job': # version was not required in v1.0, so default to that. self._version = data.get('version', '1.0') break return super().parse(iterator, config) def to_xml(self, fd): with convert_to_writable_filelike(fd) as _fd: w = XMLWriter(_fd) xml_header = ( '\n' '\n' ) w.write(xml_header) super().to_xml(w) astropy-pyvo-b70558c/pyvo/io/uws/tests/000077500000000000000000000000001510533647000201355ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/uws/tests/__init__.py000066400000000000000000000000001510533647000222340ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/uws/tests/data/000077500000000000000000000000001510533647000210465ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-error.xml000066400000000000000000000024711510533647000234750ustar00rootroot00000000000000 1337 ERROR 2018-01-01T02:00:00Z 2018-01-01T00:00:00Z 2018-01-01T00:05:00Z 2018-01-01T02:00:00Z 7200 2018-02-01T00:00:00Z ADQL SELECT 'test' doQuery We have problem astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-implicit-v1.0.xml000066400000000000000000000015301510533647000246330ustar00rootroot00000000000000 1576511540079_32840 COMPLETED 2019-12-16T10:52:20 2019-12-17T10:52:20 600 2019-12-17T10:52:20 ADQL-2.0 tap doQuery SELECT ra, dec FROM table astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-duplicate-elements.xml000066400000000000000000000015051510533647000270760ustar00rootroot00000000000000 myjobid COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table initial processing completed astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-empty-jobinfo.xml000066400000000000000000000014141510533647000260730ustar00rootroot00000000000000 myjobid COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-namespace-elements.xml000066400000000000000000000016631510533647000270650ustar00rootroot00000000000000 myjobid COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table 50 75 no collision astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-nested-jobinfo.xml000066400000000000000000000016051510533647000262210ustar00rootroot00000000000000 jobid123 COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table 1500 100 astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-simple-jobinfo.xml000066400000000000000000000016111510533647000262250ustar00rootroot00000000000000 test123 COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table 100 1 1 astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job-with-typed-jobinfo.xml000066400000000000000000000015721510533647000260670ustar00rootroot00000000000000 myid123 COMPLETED 2025-06-04T00:00:00Z 2025-06-04T00:05:00Z 2025-06-04T02:00:00Z 7200 2025-06-04T00:00:00Z SELECT * FROM table 100 3.14 pyvo astropy-pyvo-b70558c/pyvo/io/uws/tests/data/job.xml000066400000000000000000000024561510533647000223510ustar00rootroot00000000000000 1337 COMPLETED 2018-01-01T02:00:00Z 2018-01-01T00:00:00Z 2018-01-01T00:05:00Z 2018-01-01T02:00:00Z 7200 2018-02-01T00:00:00Z ADQL SELECT 'test' doQuery astropy-pyvo-b70558c/pyvo/io/uws/tests/test_job.py000066400000000000000000000152541510533647000223270ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.uws """ import pytest import pyvo.io.uws as uws from pyvo.io.uws.tree import ExtensibleUWSElement from astropy.utils.data import get_pkg_data_filename class TestJob: def test_job(self): job = uws.parse_job(get_pkg_data_filename( "data/job.xml")) assert job.jobid == '1337' assert job.version == '1.1' job = uws.parse_job(get_pkg_data_filename( "data/job-implicit-v1.0.xml")) assert job.version == '1.0' def test_error_job(self): job = uws.parse_job(get_pkg_data_filename( "data/job-error.xml")) assert job.jobid == '1337' assert job.version == '1.1' assert not job.errorsummary.has_detail assert job.errorsummary.type_ == 'fatal' assert job.errorsummary.message.content == 'We have problem' def test_simple_jobinfo(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-simple-jobinfo.xml")) assert job.jobinfo is not None assert 'tapQueryInfo' in job.jobinfo assert job.jobinfo['tapQueryInfo'] is not None tap_info = job.jobinfo['tapQueryInfo'] assert 'pct_complete' in tap_info assert 'chunks_processed' in tap_info assert 'total_chunks' in tap_info assert tap_info['pct_complete'].value == 100 assert tap_info['chunks_processed'].value == 1 assert tap_info['total_chunks'].value == 1 assert tap_info['pct_complete'].text == "100" keys = list(job.jobinfo.keys()) assert 'tapQueryInfo' in keys def test_jobinfo_multiple_access_patterns(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-simple-jobinfo.xml")) assert job.jobinfo is not None tap_info1 = job.jobinfo['tapQueryInfo'] tap_info2 = job.jobinfo.get('tapQueryInfo') assert tap_info1 is tap_info2 assert tap_info1 is not None def test_jobinfo_text_content_and_types(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-typed-jobinfo.xml")) assert job.jobinfo is not None int_elem = job.jobinfo['integer_value'] assert int_elem.value == 100 assert isinstance(int_elem.value, int) assert int_elem.text == "100" float_elem = job.jobinfo['float_value'] assert float_elem.value == 3.14 assert isinstance(float_elem.value, float) assert float_elem.text == "3.14" string_elem = job.jobinfo['string_value'] assert string_elem.value == "pyvo" assert isinstance(string_elem.value, str) assert string_elem.text == "pyvo" empty_elem = job.jobinfo['empty_value'] assert empty_elem.value is None assert empty_elem.text is None def test_jobinfo_get_methods(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-simple-jobinfo.xml")) jobinfo = job.jobinfo assert jobinfo.get('nonexistent') is None assert jobinfo.get('nonexistent', 'default') == 'default' tap_info = jobinfo.get('tapQueryInfo') assert tap_info is not None assert 'tapQueryInfo' in jobinfo assert 'nonexistent' not in jobinfo tap_info = jobinfo['tapQueryInfo'] assert tap_info is not None with pytest.raises(KeyError): _ = jobinfo['nonexistent'] def test_jobinfo_edge_cases(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-simple-jobinfo.xml")) jobinfo = job.jobinfo str_repr = str(jobinfo) assert 'jobInfo' in str_repr or len(str_repr) > 0 repr_str = repr(jobinfo) assert 'ExtensibleUWSElement' in repr_str assert 'elements=' in repr_str def test_no_jobinfo(self): job = uws.parse_job(get_pkg_data_filename( "data/job.xml")) assert job.jobinfo is None def test_nested_jobinfo_access(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-nested-jobinfo.xml")) assert job.jobinfo is not None query_info = job.jobinfo['queryInfo'] assert query_info is not None metrics = query_info['metrics'] assert metrics is not None assert metrics['execution_time'].value == 1500 assert metrics['rows_returned'].value == 100 def test_jobinfo_overwrite_behavior(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-duplicate-elements.xml")) assert job.jobinfo is not None status = job.jobinfo.get('status') assert status is not None assert status.value == "completed" def test_extensible_element_creation(self): element = ExtensibleUWSElement(config={}, pos=(1, 1), _name='test') assert element._name == 'test' assert len(element._elements) == 0 assert 'test' in str(element) assert 'elements=0' in repr(element) assert 'nonexistent' not in element assert element.get('nonexistent') is None assert list(element.keys()) == [] def test_jobinfo_namespace_elements(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-namespace-elements.xml")) assert job.jobinfo is not None progress = job.jobinfo.get('progress') assert progress is not None # Assert that the value is the last one of the elements with the same name assert progress.value == 75 unique_elem = job.jobinfo.get('uniqueElement') assert unique_elem is not None keys = job.jobinfo.keys() assert len(keys) > 0 def test_multiple_elements_same_name(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-duplicate-elements.xml")) assert job.jobinfo is not None _ = [key for key in job.jobinfo.keys() if 'status' in key] status = job.jobinfo['status'] assert status.value == "completed" def test_empty_jobinfo(self): job = uws.parse_job(get_pkg_data_filename( "data/job-with-empty-jobinfo.xml")) assert job.jobinfo is not None assert len(job.jobinfo.keys()) == 0 assert len(job.jobinfo._elements) == 0 def test_jobinfo_numeric_content_conversion(self): element = ExtensibleUWSElement(config={}, pos=(1, 1), _name='test') element.content = 100 assert not isinstance(element.content, str) assert element.content == 100 element.parse(iter([]), {}) assert element.text == "100" assert element.value == 100 assert isinstance(element.text, str) astropy-pyvo-b70558c/pyvo/io/uws/tree.py000066400000000000000000000373261510533647000203170ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VOResource standard. """ from functools import partial from astropy.utils.collections import HomogeneousList from astropy.time import Time, TimeDelta from ...utils.xml.elements import ( xmlattribute, xmlelement, Element, ContentMixin) uwselement = partial(xmlelement, ns='uws') def XSInDate(val): if not val: return None try: return Time(val, format='iso') except ValueError: pass try: return Time(val, format='isot') except ValueError: pass raise ValueError(f'Cannot parse datetime {val}') InDuration = partial(TimeDelta, format='sec') XSOutDate = partial(Time, out_subfmt='date') __all__ = [ 'UWSElement', 'Reference', 'JobSummary', 'Parameters', 'Parameter', 'Results', 'Result', 'ExtensibleUWSElement', 'Jobs', 'JobInfo'] def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) class UWSElement(Element): def __init__(self, config=None, pos=None, _name='', _ns='uws', **kwargs): super().__init__(config, pos, _name, 'uws', **kwargs) class Reference(UWSElement): """standard xlink references""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.type = kwargs.get('xlink:type') self.href = kwargs.get('xlink:href') @xmlattribute(name='xlink:type') def type(self): """the type of the result""" return self._type @type.setter def type(self, type_): self._type = type_ @xmlattribute(name='xlink:href') def href(self): """the url the result can be retrieved""" return self._href @href.setter def href(self, href): self._href = href class JobSummary(Element): def __init__(self, config=None, pos=None, _name='job', **kwargs): super().__init__(config, pos, _name, **kwargs) self.jobid = kwargs.get('id') self._runid = None self._ownerid = None self._phase = None self._quote = None self._creationtime = None self._starttime = None self._endtime = None self._executionduration = None self._destruction = None self._parameters = Parameters() self._results = Results() self._errorsummary = None self._message = None self._jobinfo = None @uwselement(name='jobId', plain=True) def jobid(self): """ The identifier for the job """ return self._jobid @jobid.setter def jobid(self, jobid): self._jobid = jobid @uwselement(name='runId', plain=True) def runid(self): """client supplied identifier""" return self._runid @runid.setter def runid(self, runid): self._runid = runid @uwselement(name='ownerId', plain=True) def ownerid(self): """the owner (creator) of the job""" return self._ownerid @ownerid.setter def ownerid(self, ownerid): self._ownerid = ownerid @uwselement(plain=True) def phase(self): """the execution phase""" return self._phase @phase.setter def phase(self, phase): self._phase = phase @uwselement(plain=True) def quote(self): """estimated completion time""" return self._quote @quote.setter def quote(self, quote): self._quote = XSInDate(quote) @quote.formatter def quote(self): try: return str(XSOutDate(self._quote)) except ValueError: return None @uwselement(name='creationTime', plain=True) def creationtime(self): """The instant at which the job was created.""" return self._creationtime @creationtime.setter def creationtime(self, creationtime): self._creationtime = XSInDate(creationtime) @creationtime.formatter def creationtime(self): try: return str(XSOutDate(self._creationtime)) except ValueError: return None @uwselement(name='startTime', plain=True) def starttime(self): """The instant at which the job started execution.""" return self._starttime @starttime.setter def starttime(self, starttime): self._starttime = XSInDate(starttime) @starttime.formatter def starttime(self): try: return str(XSOutDate(self._starttime)) except ValueError: return None @uwselement(name='endTime', plain=True) def endtime(self): """The instant at which the job finished execution""" return self._endtime @endtime.setter def endtime(self, endtime): self._endtime = XSInDate(endtime) @endtime.formatter def endtime(self): try: return str(XSOutDate(self._endtime)) except ValueError: return None @uwselement(name='executionDuration', plain=True) def executionduration(self): """ The duration (in seconds) for which the job should be allowed to run - a value of 0 is intended to mean unlimited """ return self._executionduration @executionduration.setter def executionduration(self, executionduration): if not isinstance(executionduration, TimeDelta): executionduration = InDuration(float(executionduration)) self._executionduration = executionduration @executionduration.formatter def executionduration(self): if self.executionduration: return str(int(self._executionduration.value)) @uwselement(plain=True) def destruction(self): """The time at which the whole job will be destroyed""" return self._destruction @destruction.setter def destruction(self, destruction): self._destruction = XSInDate(destruction) @destruction.formatter def destruction(self): try: return str(XSOutDate(self._destruction)) except ValueError: return None @uwselement def parameters(self): """The parameters to the job""" return self._parameters @parameters.adder def parameters(self, iterator, tag, data, config, pos): parameters = Parameters(config, pos, 'parameters', **data) parameters.parse(iterator, config) self._parameters = parameters @uwselement def results(self): """The results for the job""" return self._results @results.adder def results(self, iterator, tag, data, config, pos): results = Results(config, pos, 'results', **data) results.parse(iterator, config) self._results = results @uwselement(name='errorSummary', plain=True) def errorsummary(self): """The error summary of the job.""" return self._errorsummary @errorsummary.adder def errorsummary(self, iterator, tag, data, config, pos): res = ErrorSummary(config, pos, 'errorSummary', **data) res.parse(iterator, config) self._errorsummary = res @uwselement(name='jobInfo', plain=True) # ← Add plain=True def jobinfo(self): """Implementation-specific job information""" return self._jobinfo @jobinfo.adder def jobinfo(self, iterator, tag, data, config, pos): jobinfo = JobInfo(config, pos, 'jobInfo', **data) jobinfo.parse(iterator, config) self._jobinfo = jobinfo class Jobs(HomogeneousList, UWSElement): """A parsed representation of the joblist endpoint. """ def __init__(self, config=None, pos=None, _name='jobs', **kwargs): HomogeneousList.__init__(self, JobSummary) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement def jobs(self): return self @jobs.adder def jobs(self, iterator, tag, data, config, pos): return @uwselement(name='jobref') def joblist(self): return self @joblist.adder def joblist(self, iterator, tag, data, config, pos): job = JobSummary(config, pos, 'jobref', **data) job.parse(iterator, config) self.append(job) class Parameters(UWSElement, HomogeneousList): """ Parameters element of a job """ def __init__(self, config=None, pos=None, _name='parameters', **kwargs): """ """ # Note: Above is a load-bearing empty comment. # Do not remove, or else the Sphinx build may fail (see PR #193). HomogeneousList.__init__(self, Parameter) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement(name='parameter') def parameters(self): return self @parameters.adder def parameters(self, iterator, tag, data, config, pos): parameter = Parameter(config, pos, 'parameter', **data) parameter.parse(iterator, config) self.append(parameter) class Parameter(ContentMixin, UWSElement): def __init__(self, config=None, pos=None, _name='parameter', **kwargs): super().__init__(config, pos, _name, **kwargs) self.byreference = _convert_boolean(kwargs.get('byReference')) self.id_ = kwargs.get('id') @xmlattribute def byreference(self): """ if this attribute is true then the content of the parameter represents a URL to retrieve the actual parameter value. """ return self._byreference @byreference.setter def byreference(self, byreference): self._byreference = byreference @xmlattribute(name='id') def id_(self): """the identifier for the parameter""" return self._id @id_.setter def id_(self, id_): self._id = id_ class Results(UWSElement, HomogeneousList): """ """ def __init__(self, config=None, pos=None, _name='results', **kwargs): HomogeneousList.__init__(self, Result) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement(name='result') def results(self): return self @results.adder def results(self, iterator, tag, data, config, pos): result = Result(config, pos, 'result', **data) result.parse(iterator, config) self.append(result) class Result(Reference, UWSElement): """A reference to a UWS result.""" def __init__(self, config=None, pos=None, _name='result', **kwargs): super().__init__(config, pos, _name, **kwargs) self.id_ = kwargs.get('id') self.size = int(kwargs.get('size') or 0) self.mimetype = kwargs.get('mime-type') @xmlattribute(name='id') def id_(self): """the identifier for the result""" return self._id @id_.setter def id_(self, id_): self._id = id_ @xmlattribute def size(self): """the size of the result""" return self._size @size.setter def size(self, size): self._size = size @xmlattribute def mimetype(self): """the mimetype of the result""" return self._mimetype @mimetype.setter def mimetype(self, mimetype): self._mimetype = mimetype class ErrorSummary(UWSElement): """A UWS Error summary.""" def __init__(self, config=None, pos=None, _name='errorSummary', **kwargs): super().__init__(config, pos, _name, **kwargs) self.type_ = kwargs.get('type') self.has_detail = _convert_boolean(kwargs.get('hasDetail')) self.message = None @xmlattribute(name='type') def type_(self): """the type of the error""" return self._type @type_.setter def type_(self, type_): self._type = type_ @xmlattribute def has_detail(self): """whether error has details""" return self._has_detail @has_detail.setter def has_detail(self, has_detail): self._has_detail = has_detail @uwselement(name='message') def message(self): """The error message""" return self._message @message.setter def message(self, message): self._message = message class Message(ContentMixin, UWSElement): """The actual UWS Error message.""" def __init__(self, config=None, pos=None, _name='message', **kwargs): super().__init__(config, pos, _name, **kwargs) class ExtensibleUWSElement(ContentMixin, UWSElement): """ UWS Element that can handle arbitrary child elements. """ def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__(config, pos, _name, **kwargs) self._elements = {} self._text_content = None self._name = _name def _add_unknown_tag(self, iterator, tag, data, config, pos): """Handle unknown tags without generating warnings Parameters ---------- iterator : iterator The iterator that provides the XML elements. tag : str The tag name of the unknown element. data : dict Additional data associated. config : dict Configuration options. pos : tuple The position of the element in the XML document (line, column). Returns ------- ExtensibleUWSElement object """ element = ExtensibleUWSElement(config, pos, tag, **data) element.parse(iterator, config) # Last element with the same tag wins self._elements[tag] = element return element def parse(self, iterator, config): """Override parse to capture text content for leaf elements""" super().parse(iterator, config) # Capture text content from ContentMixin if hasattr(self, 'content') and self.content is not None: if isinstance(self.content, str): self._text_content = self.content.strip() else: self._text_content = str(self.content).strip() if self.content else None @property def text(self): """Get the text content of this element""" if self._text_content is not None and self._text_content.strip(): return self._text_content if hasattr(self, 'content') and self.content is not None: content_str = str(self.content).strip() return content_str if content_str else None return None @property def value(self): """Get the text content converted to appropriate type""" text = self.text if not text: return None # Try to convert to int, float, if not leave as string try: return int(text) except ValueError: try: return float(text) except ValueError: return text def get(self, name, default=None): """Get element by name (supports both local names and full namespaced names)""" return self._elements.get(name, default) def keys(self): """Return all available keys (both local and namespaced)""" return list(self._elements.keys()) def __contains__(self, name): """Support 'in' operator""" return name in self._elements def __getitem__(self, name): """Dict-like access""" if name not in self._elements: raise KeyError(f"Element '{name}' not found") return self._elements[name] def __str__(self): if self._text_content: return self._text_content return f"<{self._name} with {len(set(self._elements.values()))} children>" def __repr__(self): unique_elements = len(set(self._elements.values())) return f"ExtensibleUWSElement(name='{self._name}', elements={unique_elements})" class JobInfo(ExtensibleUWSElement): """JobInfo element that can contain arbitrary elements.""" def __init__(self, config=None, pos=None, _name='jobInfo', **kwargs): super().__init__(config, pos, _name, **kwargs) astropy-pyvo-b70558c/pyvo/io/vosi/000077500000000000000000000000001510533647000171355ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/vosi/__init__.py000066400000000000000000000002131510533647000212420ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .endpoint import parse_tables, parse_capabilities, parse_availability astropy-pyvo-b70558c/pyvo/io/vosi/availability.py000066400000000000000000000034171510533647000221660ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.collections import HomogeneousList from ...utils.xml.elements import xmlelement, Element from .exceptions import W32, W33, W34, W35 __all__ = ["Availability"] ###################################################################### # FACTORY FUNCTIONS def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) ###################################################################### # ELEMENT CLASSES class Availability(Element): def __init__(self, config=None, pos=None, _name='availability', **kwargs): super().__init__(config, pos, _name, **kwargs) self._available = None self._upsince = None self._downat = None self._backat = None self._notes = HomogeneousList(str) @xmlelement(plain=True, multiple_exc=W32) def available(self): return self._available @available.setter def available(self, available): self._available = _convert_boolean(available) @xmlelement(name='upSince', plain=True, multiple_exc=W33) def upsince(self): return self._upsince @upsince.setter def upsince(self, upsince): self._upsince = upsince @xmlelement(name='downAt', plain=True, multiple_exc=W34) def downat(self): return self._downat @downat.setter def downat(self, downat): self._downat = downat @xmlelement(name='backAt', plain=True, multiple_exc=W35) def backat(self): return self._backat @backat.setter def backat(self, backat): self._backat = backat @xmlelement(name='note', plain=True) def notes(self): return self._notes astropy-pyvo-b70558c/pyvo/io/vosi/endpoint.py000066400000000000000000000300531510533647000213300ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains a contains the high-level functions to read the various VOSI Endpoints. """ from astropy.utils.xml import iterparser from astropy.utils.collections import HomogeneousList from astropy.io.votable.exceptions import vo_raise, vo_warn from astropy.io.votable.util import version_compare from ...utils.xml.elements import xmlattribute, xmlelement, Element from . import voresource as vr from . import vodataservice as vs from . import availability as av from .exceptions import W15, W16, E07, E10 __all__ = [ "parse_tables", "parse_capabilities", "parse_availability", "TablesFile", "CapabilitiesFile", "AvailabilityFile"] def _pedantic_settings(pedantic): """ Controls the pedantic parser settings. Based on the bool passed in to pedantic, create a config to be passed to astropy parsing to raise exceptions or ignore them on pedantic errors. Parameters ---------- pedantic : bool When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Returns ------- A dict containing 'verify' configuration settings. """ if pedantic: return {'verify': 'exception'} else: return {'verify': 'warn'} def parse_tables(source, *, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a tableset xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.TablesFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. _debug_python_based_parser : bool, optional If `True`, use the Python-based parser. This is useful for debugging purposes. Defaults to False. Returns ------- tables_file : `~pyvo.io.vosi.endpoint.TablesFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return TablesFile( config=config, pos=(1, 1)).parse(iterator, config) def parse_capabilities(source, *, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a capabilities xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.CapabilitiesFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a capabilities xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. _debug_python_based_parser : bool, optional If `True`, use the Python-based parser. This is useful for debugging purposes. Defaults to False. Returns ------- capabilities_file : `~pyvo.io.vosi.endpoint.CapabilitiesFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return CapabilitiesFile( config=config, pos=(1, 1)).parse(iterator, config) def parse_availability(source, *, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a availability xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.AvailabilityFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a availability xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. _debug_python_based_parser : bool, optional If `True`, use the Python-based parser. This is useful for debugging purposes. Defaults to False. Returns ------- availability_file : `~pyvo.io.vosi.endpoint.AvailabilityFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return AvailabilityFile( config=config, pos=(1, 1)).parse(iterator, config) class TablesFile(Element): """ TABLESET/TABLE element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, *, config=None, pos=None, version="1.1"): Element.__init__(self, config, pos) self._tableset = None self._table = None self._ntables = None version = str(version) if version not in ("1.0", "1.1"): raise ValueError("'version' should be one of '1.0' or '1.1'") config['version'] = version self._version = version def __repr__(self): if self.table: return repr(self.table) elif self.tableset: return repr(self.tableset) else: return super().__repr__() @xmlattribute def version(self): """ The version of the TableSet specification that the file uses. """ return self._version @version.setter def version(self, version): version = str(version) if version not in ('1.0', '1.1'): raise ValueError( "pyvo.io.vosi.tables only supports VOSI versions 1.0 and 1.1") self._version = version @xmlelement def tableset(self): """ The tableset. Must be a `~pyvo.io.vosi.vodataservice.TableSet` object. """ return self._tableset @tableset.setter def tableset(self, tableset): self._tableset = tableset @tableset.adder def tableset(self, iterator, tag, data, config, pos): tableset = vs.TableSet(config, pos, 'tableset', **data) tableset.parse(iterator, config) self._tableset = tableset @xmlelement(cls=vs.VODataServiceTable) def table(self): """ The `~pyvo.io.vosi.vodataservice.VODataServiceTable` root element if present. """ return self._table @table.setter def table(self, table): self._table = table @property def ntables(self): """ The number of tables in the file. """ return self._ntables def parse(self, iterator, config): super().parse(iterator, config) if self.tableset is None and self.table is None: vo_raise(E07, config=config, pos=self._pos) self._version = config['version'] if config['version'] not in ('1.0', '1.1'): vo_warn(W15, config=config, pos=self._pos) if self.table: if version_compare(config['version'], '1.1') < 0: vo_warn(W16, config=config, pos=self._pos) self._ntables = 1 else: self._ntables = sum( len(schema.tables) for schema in self.tableset.schemas) return self def iter_tables(self): """ Iterates over all tables in the VOSITables file in a "flat" way, ignoring the schemas. """ if self.table: yield self.table else: for schema in self.tableset.schemas: yield from schema.tables def get_first_table(self): """ When you parse table metadata for a single table here is only one table in the file, and that's all you need. This method returns that first table. """ for table in self.iter_tables(): return table raise IndexError("No table found in VOSITables file.") def get_table_by_name(self, name): """ Looks up a table element by the given name. """ for table in self.iter_tables(): if table.name == name: return table raise KeyError(f"No table with name {name} found") class CapabilitiesFile(Element, HomogeneousList): """ capabilities element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, *, config=None, pos=None, _name='capabilities', **kwargs): Element.__init__(self, config=config, pos=pos, **kwargs) HomogeneousList.__init__(self, vr.Capability) @xmlelement(name='capability') def capabilities(self): """List of `~pyvo.io.vosi.voresource.Capability` objects""" return self @capabilities.adder def capabilities(self, iterator, tag, data, config, pos): capability = vr.Capability(config, pos, 'capability', **data) capability.parse(iterator, config) self.append(capability) def parse(self, iterator, config): for start, tag, data, pos in iterator: if start: if tag == "xml": pass elif tag == "capabilities": break else: vo_raise(E10, config=config, pos=pos) super().parse(iterator, config) return self class AvailabilityFile(av.Availability): """ availability element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def parse(self, iterator, config): for start, tag, data, pos in iterator: if start: if tag == 'xml': pass elif tag == 'availability': break super().parse(iterator, config) return self astropy-pyvo-b70558c/pyvo/io/vosi/exceptions.py000066400000000000000000000340541510533647000216760ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ .. _warnings: Warnings -------- .. note:: Most of the following warnings indicate violations of the VOSI specification. They should be reported to the authors of the tools that produced the VOSI file. To control the warnings emitted, use the standard Python :mod:`warnings` module. Most of these are of the type `VOSISpecWarning`. {warnings} .. _exceptions: Exceptions ---------- .. note:: This is a list of many of the fatal exceptions emitted by vosi.endpoint when the file does not conform to spec. Other exceptions may be raised due to unforeseen cases or bugs in vosi.endpoint itself. {exceptions} """ from astropy.utils.exceptions import AstropyWarning from ...utils.xml.exceptions import XMLWarning __all__ = ["VOSIWarning"] __all__ += [f"W{i:0>2}" for i in range(1, 36)] __all__ += [f"E{i:0>2}" for i in range(1, 10)] class VOSIWarning(AstropyWarning): """ The base class of all VOSI warnings and exceptions. Handles the formatting of the message with a warning or exception code, filename, line and column number. """ class W01(VOSIWarning, XMLWarning): """ The attribute must be a valid URI as defined in `RFC 2396 `_. """ message_template = "'{}' is not a valid URI" default_args = ('x',) class W02(VOSIWarning, XMLWarning): """ The attribute must be any of the accepted types in the VOSI spec. """ message_template = ( "'{}' is not a valid datatype according to the VOSI spec") default_args = ('x',) class W03(VOSIWarning, XMLWarning): """ The attribute must be an positive integer. """ message_template = "Size must be positive" class W04(VOSIWarning, XMLWarning): """ The attribute must have one of the recognized values 'indexed', 'primary', 'nullable'. """ message_template = "'{}' is not a recognized flag" default_args = ('x',) class W05(VOSIWarning, XMLWarning): """ A ``name`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one name element" default_args = ('x',) class W06(VOSIWarning, XMLWarning): """ A ``description`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one description element" default_args = ('x',) class W07(VOSIWarning, XMLWarning): """ A ``unit`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one unit element" default_args = ('x',) class W08(VOSIWarning, XMLWarning): """ A ``ucd`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one ucd element" default_args = ('x',) class W09(VOSIWarning, XMLWarning): """ A ``utype`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one utype element" default_args = ('x',) class W10(VOSIWarning, XMLWarning): """ A ``fromColumn`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one fromColumn element" default_args = ('x',) class W11(VOSIWarning, XMLWarning): """ A ``targetColumn`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one targetColumn element" default_args = ('x',) class W12(VOSIWarning, XMLWarning): """ A ``targetTable`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one targetTable element" default_args = ('x',) class W13(VOSIWarning, XMLWarning): """ A ``title`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one title element" default_args = ('x',) class W14(VOSIWarning, XMLWarning): """ The tableset element must contain at least one schema element. """ message_template = ( "tableset element must contain at least one schema element.") class W15(VOSIWarning, XMLWarning): """ Unknown issues may arise using ``dal`` with VOSITables files from a version other than 1.0 or 1.1 """ message_template = ( 'pyvo.dal is designed for VOSITables version 1.0, and 1.1, but ' + 'this file is {}') default_args = ('x',) class W16(VOSIWarning, XMLWarning): """ The table element is not a valid root element in VOSI before version 1.1 """ message_template = ( "The element table is not a valid root element in VOSI below v1.1") class W17(VOSIWarning, XMLWarning): """ A ``queryType`` element can only appear once within the ParamHTTP element. According to the schema, it may only occur once (`1.1 `__, """ message_template = ( "ParamHTTP element contains more than one ParamHTTP element") class W18(VOSIWarning, XMLWarning): """ The QueryType element must not occur more than two times. """ message_template = ( "The QueryType element must not occur more than two times.") class W19(VOSIWarning, XMLWarning): """ TAP Capabilities must not have an ivo-id other than ivo://ivoa.net/std/TAP """ message_template = ( "TAP Capabilities must not have an ivo-id other than " "ivo://ivoa.net/std/TAP" ) class W20(VOSIWarning, XMLWarning): """ TAP Capabilties must have at least one `language` element. """ message_template = ( "TAP Capabilties must have at least one `language` element.") class W21(VOSIWarning, XMLWarning): """ TAP Capabilties must have at least one outputFormat element. """ message_template = ( "TAP Capabilties must have at least one `outputFormat` element.") class W22(VOSIWarning, XMLWarning): """ The `retentionPeriod` element must not occur more than once. """ message_template = ( "The retentionPeriod element must not occur more than once") class W23(VOSIWarning, XMLWarning): """ The `executionDuration` element must not occur more than once. """ message_template = ( "The executionDuration element must not occur more than once") class W24(VOSIWarning, XMLWarning): """ The `outputLimit` element must not occur more than once. """ message_template = ( "The outputLimit element must not occur more than once") class W25(VOSIWarning, XMLWarning): """ The `uploadLimit` element must not occur more than once. """ message_template = ( "The uploadLimit element must not occur more than once") class W26(VOSIWarning, XMLWarning): """ The ivo-id attribute is mandatory. """ message_template = "The ivo-id attribute is mandatory" class W27(VOSIWarning, XMLWarning): """ The `form` element must not occur more than once. """ message_template = "The form element must not occur more than once" class W28(VOSIWarning, XMLWarning): """ The `mime` element must not occur more than once. """ message_template = "The mime element must not occur more than once" class W29(VOSIWarning, XMLWarning): """ The `default` element must not occur more than once. """ message_template = "The default element must not occur more than once" class W30(VOSIWarning, XMLWarning): """ The `hard` element must not occur more than once. """ message_template = "The hard element must not occur more than once" class W31(VOSIWarning, XMLWarning): """ The content of the `DataLimit` element must be byte or row """ message_template = ( "The content of the DataLimit element must be byte or row") class W32(VOSIWarning, XMLWarning): """ The `available` element must not occur more than once. """ message_template = "The available element must not occur more than once" class W33(VOSIWarning, XMLWarning): """ The `upSince` element must not occur more than once. """ message_template = "The upSince element must not occur more than once" class W34(VOSIWarning, XMLWarning): """ The `downAt` element must not occur more than once. """ message_template = "The downAt element must not occur more than once" class W35(VOSIWarning, XMLWarning): """ The `backAt` element must not occur more than once. """ message_template = "The backAt element must not occur more than once" class W36(VOSIWarning, XMLWarning): """ The `resultType` element must not occur more than once. """ message_template = "The resultType element must not occur more than once" class W37(VOSIWarning, XMLWarning): """ The `dataType` element must not occur more than once. """ message_template = "The dataType element must not occur more than once" class E01(VOSIWarning, XMLWarning, ValueError): r""" The attribute must be a valid arraysize according to the VOTable standard. From the VOTable 1.2 spec: A table cell can contain an array of a given primitive type, with a fixed or variable number of elements; the array may even be multidimensional. For instance, the position of a point in a 3D space can be defined by the following:: and each cell corresponding to that definition must contain exactly 3 numbers. An asterisk (\*) may be appended to indicate a variable number of elements in the array, as in:: where it is specified that each cell corresponding to that definition contains 0 to 100 integer numbers. The number may be omitted to specify an unbounded array (in practice up to =~2×10⁹ elements). A table cell can also contain a multidimensional array of a given primitive type. This is specified by a sequence of dimensions separated by the ``x`` character, with the first dimension changing fastest; as in the case of a simple array, the last dimension may be variable in length. As an example, the following definition declares a table cell which may contain a set of up to 10 images, each of 64×64 bytes:: **References**: `1.1 `__, `1.2 `__ """ message_template = "Invalid arraysize attribute '{}'" default_args = ('x',) class E02(VOSIWarning, XMLWarning, ValueError): """ The `FKColumn` element must have a `fromColumn`. """ message_template = "fkColumn element is missing a fromColumn" class E03(VOSIWarning, XMLWarning, ValueError): """ The element must have a `targetColumn`. """ message_template = "The element is missing a targetColumn" class E04(VOSIWarning, XMLWarning, ValueError): """ The element must have a `targetTable`. """ message_template = "The element is missing a targetTable" class E05(VOSIWarning, XMLWarning, ValueError): """ The element must contain at least one `fkColumn`. """ message_template = "The element contains no `fkColumn`" class E06(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``name`` element. """ message_template = "The {} element must have a name element" default_args = ('x',) class E07(VOSIWarning, XMLWarning, ValueError): """ Raised either when the file doesn't appear to be XML, or the root element is not tableset or table. """ message_template = "File does not appear to be a VOSITables file" class E08(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``version`` element. """ message_template = "The {} element must have a version element" default_args = ('x',) class E09(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``form`` element. """ message_template = "The {} element must have a form element" default_args = ('x',) class E10(VOSIWarning, XMLWarning, ValueError): """ Raised when then file doesn't appear to be valid capabilities xml """ message_template = "File does not appear to be a VOSICapabilities file" class VOSIError(Exception): """ Raised for non-XML VOSI errors """ pass astropy-pyvo-b70558c/pyvo/io/vosi/tapregext.py000066400000000000000000000416771510533647000215310ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.collections import HomogeneousList from textwrap import indent from astropy.io.votable.exceptions import vo_raise, warn_or_raise from ...utils.xml.elements import ( Element, ContentMixin, xmlelement, xmlattribute) from . import voresource as vr from .exceptions import ( W05, W06, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30, W31, E06, E08, E09, VOSIError) __all__ = [ "TAPCapRestriction", "TableAccess", "DataModelType", "Language", "Version", "LanguageFeatureList", "LanguageFeature", "OutputFormat", "UploadMethod", "TimeLimits", "DataLimits", "DataLimit"] INDENT = 4 * " " ###################################################################### # ELEMENT CLASSES class DataModelType(ContentMixin, Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id', None) if ivo_id is None: warn_or_raise(W26, W26, config=config, pos=pos) self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.content) def describe(self): """ Prints out a human readable description """ print(f"Datamodel {self.content}") print(indent(self.ivo_id, INDENT)) print() @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the data model.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class OutputFormat(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.mime = None self._aliases = HomogeneousList(str) self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.mime) def describe(self): """ Prints out a human readable description """ print(f'Output format {self.mime}') if self.aliases: print(indent('Also available as {}'.format(', '.join(self.aliases)), INDENT)) print() @xmlelement(plain=True, multiple_exc=W28) def mime(self): return self._mime @mime.setter def mime(self, mime): self._mime = mime @xmlelement(name='alias') def aliases(self): return self._aliases class UploadMethod(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.ivo_id = ivo_id def __repr__(self): return f'' def describe(self): """ Prints out a human readable description """ print("Upload method supported") print(indent(self.ivo_id, INDENT)) print() @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the upload model.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class TimeLimits(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self._default = None self._hard = None def __repr__(self): return ''.format( self.default, self.hard) @xmlelement(plain=True, multiple_exc=W29) def default(self): return self._default @default.setter def default(self, default): self._default = int(default) @xmlelement(plain=True, multiple_exc=W30) def hard(self): return self._hard @hard.setter def hard(self, hard): self._hard = int(hard) class LanguageFeature(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.form = None self.description = None @xmlelement(plain=True, multiple_exc=W27) def form(self): return self._form @form.setter def form(self, form): self._form = form @xmlelement(plain=True, multiple_exc=W06) def description(self): return self._description @description.setter def description(self, description): self._description = description def parse(self, iterator, config): super().parse(iterator, config) if not self.form: vo_raise(E09, self._element_name, config=config, pos=self._pos) class LanguageFeatureList(Element, HomogeneousList): def __init__( self, config=None, pos=None, _name='languageFeatures', **kwargs ): Element.__init__(self, config, pos, _name, **kwargs) HomogeneousList.__init__(self, LanguageFeature) self.type = kwargs.get('type') self._features = HomogeneousList(LanguageFeature) @xmlattribute def type(self): return self._type @type.setter def type(self, type_): self._type = type_ @xmlelement(name='feature', cls=LanguageFeature) def features(self): return self class Version(ContentMixin, Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.content) @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the version.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class Language(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.name = None self._versions = HomogeneousList(Version) self.description = None self._languagefeaturelists = HomogeneousList(LanguageFeatureList) def __repr__(self): return f'{self.name}' def describe(self): """ Prints out a human readable description """ print(f"Language {self.name}") for languagefeaturelist in self.languagefeaturelists: print(indent(languagefeaturelist.type, INDENT)) for feature in languagefeaturelist: print(indent(feature.form, 2 * INDENT)) if feature.description: print(indent(feature.description, 3 * INDENT)) print() print() def get_feature_list(self, ivoid): """ returns a list of features groupd with the features id ivoid. Parameters ---------- ivoid : the ivoid of a TAPRegExt feature list. It is compared case-insensitively against the service's ivoids. Returns ------- A (possibly empty) list of `~pyvo.io.vosi.tapregext.LanguageFeature` elements """ ivoid = ivoid.lower() for features in self.languagefeaturelists: if features.type.lower() == ivoid: return features return [] def get_feature(self, ivoid, form): """ returns the `~pyvo.io.vosi.tapregext.LanguageFeature` with ivoid and form if present. We return None rather than raising an error because we expect the normal pattern of usage here will be "if feature is present", and with None-s this is simpler to write than with exceptions. Since it's hard to predict the form of UDFs, for those rather use the get_udf method. ivoid (regrettably) has to be compared case-insensitively; form is compared case-sensitively. Parameters ---------- ivoid : str The IVOA identifier of the feature group the form is in form : str The form of the feature requested Returns ------- A `~pyvo.io.vosi.tapregext.LanguageFeature` or None. """ for feature in self.get_feature_list(ivoid): if feature.form == form: return feature return None def get_udf(self, function_name): """ returns a `~pyvo.io.vosi.tapregext.LanguageFeature` corresponding to an ADQL user defined function on the server, on None if the UDF is not available. This is a bit heuristic in that it tries to parse the form, which is specified only so-so. Parameters ---------- function_name : str A function name. This is matched against the server's function names case-insensitively, as guided by ADQL's case insensitivity. Returns: A `~pyvo.io.vosi.tapregext.LanguageFeature` instance or None. """ function_name = function_name.lower() for udf in self.get_feature_list( "ivo://ivoa.net/std/TAPRegExt#features-udf"): this_name = udf.form.split("(")[0].strip() if this_name.lower() == function_name: return udf return None @xmlelement(plain=True, multiple_exc=W05) def name(self): return self._name @name.setter def name(self, name): self._name = name @xmlelement(name='version', cls=Version) def versions(self): return self._versions @xmlelement(plain=True, multiple_exc=W06) def description(self): return self._description @description.setter def description(self, description): self._description = description @xmlelement(name='languageFeatures', cls=LanguageFeatureList) def languagefeaturelists(self): return self._languagefeaturelists def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._element_name, config=config, pos=self._pos) if not self.versions: vo_raise(E08, self._element_name, config=config, pos=self._pos) class DataLimit(ContentMixin, Element): def __init__(self, unit=None, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.unit = unit def __str__(self): return f"{self.unit}:{self.content}" @xmlattribute def unit(self): return self._unit @unit.setter def unit(self, unit): self._unit = unit @property def content(self): return self._content @content.setter def content(self, content): self._content = int(content) def parse(self, iterator, config): super().parse(iterator, config) if self.unit not in ('byte', 'row'): warn_or_raise(W31, W31, config=config, pos=self._pos) class DataLimits(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.default = None self.hard = None def __repr__(self): parts = [] if self.default is not None: parts.append("default={self.default}") if self.hard is not None: parts.append("hard={self.hard}") return ''.format(" ".join(parts)) @xmlelement(cls=DataLimit, multiple_exc=W29) def default(self): return self._default @default.setter def default(self, default): self._default = default @xmlelement(cls=DataLimit, multiple_exc=W30) def hard(self): return self._hard @hard.setter def hard(self, hard): self._hard = hard class TAPCapRestriction(vr.Capability): def __init__( self, config=None, pos=None, _name='capability', standardID=None, **kwargs ): if standardID != 'ivo://ivoa.net/std/TAP': warn_or_raise(W19, W19, config=config, pos=pos) super().__init__( config, pos, _name, standardID='ivo://ivoa.net/std/TAP', **kwargs) @vr.Capability.register_xsi_type('tr:TableAccess') class TableAccess(TAPCapRestriction): def __init__(self, config=None, pos=None, _name='capability', **kwargs): super().__init__(config, pos, _name, **kwargs) self._datamodels = HomogeneousList(DataModelType) self._languages = HomogeneousList(Language) self._outputformats = HomogeneousList(OutputFormat) self._uploadmethods = HomogeneousList(UploadMethod) self.retentionperiod = None self.executionduration = None self.outputlimit = None self.uploadlimit = None def describe(self): """ Prints out a human readable description """ super().describe() for datamodel in self.datamodels: datamodel.describe() for language in self.languages: language.describe() for outputformat in self.outputformats: outputformat.describe() for uploadmethod in self.uploadmethods: uploadmethod.describe() if self.retentionperiod: print("Time a job is kept (in seconds)") print(indent(f"Default {self.retentionperiod.default}", INDENT)) if self.retentionperiod.hard: print(indent(f"Maximum {self.retentionperiod.hard}", INDENT)) print() if self.executionduration: print("Maximal run time of a job") print(indent(f"Default {self.executionduration.default}", INDENT)) if self.executionduration.hard: print(indent(f"Maximum {self.executionduration.hard}", INDENT)) print() if self.outputlimit: print("Maximum size of resultsets") print(indent("Default {} {}".format( self.outputlimit.default.content, self.outputlimit.default.unit), INDENT) ) if self.outputlimit.hard: print(indent("Maximum {} {}".format( self.outputlimit.hard.content, self.outputlimit.hard.unit), INDENT) ) print() if self.uploadlimit and self.uploadlimit.hard: print("Maximal size of uploads") print(indent("Maximum {} {}".format( self.uploadlimit.hard.content, self.uploadlimit.hard.unit), INDENT)) print() def get_adql(self): """ returns the (first) ADQL language element on this service. ADQL support is mandatory for IVOA TAP, so in general you can rely on this being present. """ for lang in self.languages: if lang.name == "ADQL": return lang raise VOSIError( "Invalid TAP service: Does not declare an ADQL language") @xmlelement(name='dataModel', cls=DataModelType) def datamodels(self): """Identifier of IVOA-approved data model supported by the service.""" return self._datamodels @xmlelement(name='language', cls=Language) def languages(self): """Languages supported by the service.""" return self._languages @xmlelement(name='outputFormat', cls=OutputFormat) def outputformats(self): """Output formats supported by the service.""" return self._outputformats @xmlelement(name='uploadMethod', cls=UploadMethod) def uploadmethods(self): """ Upload methods supported by the service. The absence of upload methods indicates that the service does not support uploads at all. """ return self._uploadmethods @xmlelement(name='retentionPeriod', cls=TimeLimits, multiple_exc=W22) def retentionperiod(self): """Limits on the time between job creation and destruction time.""" return self._retentionperiod @retentionperiod.setter def retentionperiod(self, retentionperiod): self._retentionperiod = retentionperiod @xmlelement(name='executionDuration', cls=TimeLimits, multiple_exc=W23) def executionduration(self): """Limits on executionDuration.""" return self._executionduration @executionduration.setter def executionduration(self, executionduration): self._executionduration = executionduration @xmlelement(name='outputLimit', cls=DataLimits, multiple_exc=W24) def outputlimit(self): """Limits on the size of data returned.""" return self._outputlimit @outputlimit.setter def outputlimit(self, outputlimit): self._outputlimit = outputlimit @xmlelement(name='uploadLimit', cls=DataLimits, multiple_exc=W25) def uploadlimit(self): return self._uploadlimit @uploadlimit.setter def uploadlimit(self, uploadlimit, cls=DataLimits): self._uploadlimit = uploadlimit def parse(self, iterator, config): super().parse(iterator, config) if not self.languages: warn_or_raise(W20, W20, config=config, pos=self._pos) if not self.outputformats: warn_or_raise(W21, W21, config=config, pos=self._pos) astropy-pyvo-b70558c/pyvo/io/vosi/tests/000077500000000000000000000000001510533647000202775ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/vosi/tests/__init__.py000066400000000000000000000001001510533647000223770ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/000077500000000000000000000000001510533647000212105ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/availability.xml000066400000000000000000000011451510533647000244050ustar00rootroot00000000000000 true 2000-00-00T00:00:00Z 2666-00-00T00:00:00Z 2666-23-23T13:37:00Z foo bar astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/capabilities.xml000066400000000000000000000077671510533647000244040ustar00rootroot00000000000000 http://example.org/tap/availability https://example.org/tap/availability http://example.org/tap/capabilities https://example.org/tap/capabilities http://example.org/tap/tables https://example.org/tap/tables http://example.org/tap https://example.org/tap https://paris.example.org/tap QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL Obscore-1.1 Registry 1.0 GloTS 1.0 Obscore-1.0 ADQL 2.0 ADQL 2.0
form 1
description 1
form 2
description 2
BOX
POINT
text/xml text/html html 172800 3600 2000 10000000 100000000
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/capabilities/000077500000000000000000000000001510533647000236415ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/capabilities/minimal-tapregext.xml000066400000000000000000000036241510533647000300170ustar00rootroot00000000000000 http://example.org/tap/capabilities http://example.org/tap/tables http://example.org/tap ADQL 2.0 ADQL 2.0 text/xml astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/capabilities/multiple_capa_descriptions.xml000066400000000000000000000022101510533647000317630ustar00rootroot00000000000000 one two astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables.xml000066400000000000000000000026561510533647000232150ustar00rootroot00000000000000 test This is a unittest schema test.allTest tableAll test data in one tableutype30 id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/000077500000000000000000000000001510533647000224625ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/datatypes_tap.xml000066400000000000000000000107421510533647000260520ustar00rootroot00000000000000 test test.tap _boolean BOOLEAN _smallint SMALLINT _integer INTEGER _bigint BIGINT _real REAL _double DOUBLE _timestamp TIMESTAMP _char CHAR _varchar VARCHAR _binary BINARY _varbinary VARBINARY _point POINT _region REGION _clob CLOB _blob BLOB
test.taptype _boolean BOOLEAN _smallint SMALLINT _integer INTEGER _bigint BIGINT _real REAL _double DOUBLE _timestamp TIMESTAMP _char CHAR _varchar VARCHAR _binary BINARY _varbinary VARBINARY _point POINT _region REGION _clob CLOB _blob BLOB
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/datatypes_votable.xml000066400000000000000000000075501510533647000267250ustar00rootroot00000000000000 test test.votable _boolean boolean _bit bit _unsignedBytes unsignedByte _short short _int int _long long _char char _unicodeChar unicodeChar _float float _double double _floatComplex floatComplex _doubleComplex doubleComplex
test.votable _boolean boolean _bit bit _unsignedBytes unsignedByte _short short _int int _long long _char char _unicodeChar unicodeChar _float float _double double _floatComplex floatComplex _doubleComplex doubleComplex
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_datatypes.xml000066400000000000000000000014141510533647000304720ustar00rootroot00000000000000 test test datatype int INTEGER
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_descriptions.xml000066400000000000000000000013471510533647000312070ustar00rootroot00000000000000 test ucd test one two
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_names.xml000066400000000000000000000012621510533647000276000ustar00rootroot00000000000000 test test one two
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_ucds.xml000066400000000000000000000013071510533647000274330ustar00rootroot00000000000000 test ucd test one two
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_units.xml000066400000000000000000000013141510533647000276350ustar00rootroot00000000000000 test test unit one two
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_column_utypes.xml000066400000000000000000000013211510533647000300220ustar00rootroot00000000000000 test test utype one two
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_foreignkey_descriptions.xml000066400000000000000000000020111510533647000320410ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn desc1 desc2 fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_foreignkey_utypes.xml000066400000000000000000000017631510533647000307010ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn utype1 utype2 fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_fromcolumns.xml000066400000000000000000000017451510533647000274720ustar00rootroot00000000000000 test test foreigntable fromcolumn fromcolumn targetcolumn fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_schema_descriptions.xml000066400000000000000000000012011510533647000311370ustar00rootroot00000000000000 descriptiontest one two astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_schema_names.xml000066400000000000000000000011041510533647000275360ustar00rootroot00000000000000 one two astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_schema_titles.xml000066400000000000000000000011431510533647000277420ustar00rootroot00000000000000 titletest one two astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_schema_utypes.xml000066400000000000000000000011431510533647000277670ustar00rootroot00000000000000 utypetest one two astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_table_descriptions.xml000066400000000000000000000012621510533647000307750ustar00rootroot00000000000000 test descriptiononetwo
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_table_names.xml000066400000000000000000000011671510533647000273760ustar00rootroot00000000000000 test onetwo
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_table_titles.xml000066400000000000000000000012241510533647000275710ustar00rootroot00000000000000 test titleonetwo
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_table_utypes.xml000066400000000000000000000012241510533647000276160ustar00rootroot00000000000000 test utypeonetwo
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_targetcolumns.xml000066400000000000000000000017531510533647000300140ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn targetcolumn fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/multiple_targettables.xml000066400000000000000000000017471510533647000276110ustar00rootroot00000000000000 test test foreigntable foreigntable fromcolumn targetcolumn fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_fromcolumn.xml000066400000000000000000000015141510533647000260620ustar00rootroot00000000000000 test test foreigntable fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_schema_name.xml000066400000000000000000000010321510533647000261340ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_schemas.xml000066400000000000000000000010031510533647000253150ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_table_description.xml000066400000000000000000000011501510533647000273670ustar00rootroot00000000000000 test description
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_table_name.xml000066400000000000000000000011041510533647000257630ustar00rootroot00000000000000 test
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/no_targetcolumn.xml000066400000000000000000000016031510533647000264040ustar00rootroot00000000000000 test test foreigntable fromcolumn fromcolumn int
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/single_table_description.xml000066400000000000000000000012601510533647000302360ustar00rootroot00000000000000 test descriptionA test table with a single description
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/sizenegative.xml000066400000000000000000000013621510533647000257030ustar00rootroot00000000000000 test test.sizezero ZERO INTEGER
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/wrong_arraysize.xml000066400000000000000000000014071510533647000264330ustar00rootroot00000000000000 test test.wrong_arraysize wrongarraysize INTEGER
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/wrong_datatypes_tap.xml000066400000000000000000000013501510533647000272610ustar00rootroot00000000000000 test test.wrong_tap WRONG WRONG
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/wrong_datatypes_votable.xml000066400000000000000000000013601510533647000301320ustar00rootroot00000000000000 test test.wrong_votable wrong wrong
astropy-pyvo-b70558c/pyvo/io/vosi/tests/data/tables/wrong_flag.xml000066400000000000000000000014241510533647000253320ustar00rootroot00000000000000 test test.wrong_flag wrongflag INTEGER prmary
astropy-pyvo-b70558c/pyvo/io/vosi/tests/test_availability.py000066400000000000000000000012431510533647000243620ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import pyvo.io.vosi as vosi from astropy.utils.data import get_pkg_data_filename class TestAvailability: def test_availability(self): availability = vosi.parse_availability(get_pkg_data_filename( "data/availability.xml")) assert availability.available assert availability.upsince == "2000-00-00T00:00:00Z" assert availability.downat == "2666-00-00T00:00:00Z" assert availability.backat == "2666-23-23T13:37:00Z" assert "foo" in availability.notes assert "bar" in availability.notes astropy-pyvo-b70558c/pyvo/io/vosi/tests/test_capabilities.py000066400000000000000000000225071510533647000243470ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import contextlib import io from operator import eq as equals import pytest import pyvo.io.vosi as vosi import pyvo.io.vosi.vodataservice as vs import pyvo.io.vosi.tapregext as tr from pyvo.io.vosi.exceptions import W06 from astropy.utils.data import get_pkg_data_filename @pytest.fixture(name='parsed_caps') def _parsed_caps(): return vosi.parse_capabilities(get_pkg_data_filename( "data/capabilities.xml")) @pytest.fixture(name='parsed_minimal_tapregext') def _minimal_tapregext(): return vosi.parse_capabilities(get_pkg_data_filename( "data/capabilities/minimal-tapregext.xml")) @pytest.mark.usefixtures("parsed_caps") class TestCapabilities: def test_availability(self, parsed_caps): assert equals( parsed_caps[0].standardid, "ivo://ivoa.net/std/VOSI#availability") assert isinstance(parsed_caps[0].interfaces[0], vs.ParamHTTP) assert parsed_caps[0].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[0].interfaces[0].accessurls[0].content, "http://example.org/tap/availability") def test_capendpoint(self, parsed_caps): assert equals( parsed_caps[1].standardid, "ivo://ivoa.net/std/VOSI#capabilities") assert isinstance(parsed_caps[1].interfaces[0], vs.ParamHTTP) assert parsed_caps[1].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[1].interfaces[0].accessurls[0].content, "http://example.org/tap/capabilities") def test_tablesendpoint(self, parsed_caps): assert parsed_caps[2].standardid == "ivo://ivoa.net/std/VOSI#tables" assert isinstance(parsed_caps[2].interfaces[0], vs.ParamHTTP) assert parsed_caps[2].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[2].interfaces[0].accessurls[0].content, "http://example.org/tap/tables") def test_type_parsed(self, parsed_caps): assert isinstance(parsed_caps[3], tr.TableAccess) def test_stdid_parsed(self, parsed_caps): assert parsed_caps[3].standardid == "ivo://ivoa.net/std/TAP" def test_dm_parsed(self, parsed_caps): assert equals( parsed_caps[3].datamodels[0].ivo_id, "ivo://ivoa.net/std/ObsCore#table-1.1") assert parsed_caps[3].datamodels[0].content == "Obscore-1.1" assert equals( parsed_caps[3].datamodels[1].ivo_id, "ivo://ivoa.net/std/RegTAP#1.0") assert parsed_caps[3].datamodels[1].content == "Registry 1.0" def test_language_parsed(self, parsed_caps): assert parsed_caps[3].languages[0].name == "ADQL" assert equals( parsed_caps[3].languages[0].versions[0].ivo_id, "ivo://ivoa.net/std/ADQL#v2.0") assert parsed_caps[3].languages[0].versions[0].content == "2.0" assert parsed_caps[3].languages[0].description == "ADQL 2.0" def test_udfs(self, parsed_caps): assert equals( parsed_caps[3].languages[0].languagefeaturelists[0].type, "ivo://ivoa.net/std/TAPRegExt#features-udf") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][0].form, "form 1") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][ 0].description, "description 1") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][1].form, "form 2") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0].features[ 1].description, "description 2") def test_adqlgeos(self, parsed_caps): assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].type, "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo") assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].features[ 0].form, "BOX") assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].features[ 1].form, "POINT") def test_outputformats(self, parsed_caps): assert equals( parsed_caps[3].outputformats[0].ivo_id, "ivo://ivoa.net/std/TAPRegExt#output-votable-binary") assert parsed_caps[3].outputformats[0].mime == "text/xml" assert parsed_caps[3].outputformats[1].ivo_id is None assert parsed_caps[3].outputformats[1].mime == "text/html" def test_uploadmethods(self, parsed_caps): assert equals( parsed_caps[3].uploadmethods[0].ivo_id, "ivo://ivoa.net/std/TAPRegExt#upload-https") assert equals( parsed_caps[3].uploadmethods[1].ivo_id, "ivo://ivoa.net/std/TAPRegExt#upload-inline") def test_temporal_limits(self, parsed_caps): assert parsed_caps[3].retentionperiod.default == 172800 assert parsed_caps[3].executionduration.default == 3600 def test_spatial_limits(self, parsed_caps): assert parsed_caps[3].outputlimit.default.unit == "row" assert parsed_caps[3].outputlimit.default.content == 2000 assert parsed_caps[3].outputlimit.hard.unit == "row" assert parsed_caps[3].outputlimit.hard.content == 10000000 assert parsed_caps[3].uploadlimit.hard.unit == "byte" assert parsed_caps[3].uploadlimit.hard.content == 100000000 def test_multiple_capa_descriptions(self): with pytest.warns(W06): vosi.parse_capabilities(get_pkg_data_filename( 'data/capabilities/multiple_capa_descriptions.xml')) with pytest.raises(W06): vosi.parse_capabilities(get_pkg_data_filename( 'data/capabilities/multiple_capa_descriptions.xml'), pedantic=True) @pytest.mark.usefixtures("parsed_minimal_tapregext") class TestMinimalTAPRegExt: def test_standard_id(self, parsed_minimal_tapregext): assert isinstance(parsed_minimal_tapregext[2], tr.TableAccess) def test_limits(self, parsed_minimal_tapregext): assert parsed_minimal_tapregext[2]._uploadlimit is None assert parsed_minimal_tapregext[2]._outputlimit is None def test_adql(self, parsed_minimal_tapregext): assert parsed_minimal_tapregext[2].get_adql().name == "ADQL" def test_udfs(self, parsed_minimal_tapregext): assert parsed_minimal_tapregext[2].get_adql().languagefeaturelists == [] def test_uploadmethods(self, parsed_minimal_tapregext): assert parsed_minimal_tapregext[2].uploadmethods == [] def test_describe(self, parsed_minimal_tapregext): with io.StringIO() as buf, contextlib.redirect_stdout(buf): parsed_minimal_tapregext[2].describe() output = buf.getvalue() assert "http://example.org/tap" in output assert "Output format text/xml" in output assert "Maximal size" not in output @pytest.mark.usefixtures("parsed_caps") class TestInterface: def test_interface_parsed(self, parsed_caps): assert parsed_caps[3].interfaces[0].accessurls[0].use == "base" assert equals( parsed_caps[3].interfaces[0].accessurls[0].content, "http://example.org/tap") def test_mirrors_parsed(self, parsed_caps): assert len(parsed_caps[3].interfaces[0].mirrorurls) == 2 def test_mirrors_have_titles(self, parsed_caps): assert ([m.title for m in parsed_caps[3].interfaces[0].mirrorurls] == ["https version", "Paris mirror"]) def test_mirrors_have_urls(self, parsed_caps): assert ([m.content for m in parsed_caps[3].interfaces[0].mirrorurls] == ['https://example.org/tap', 'https://paris.example.org/tap']) def test_testquerystring_parsed(self, parsed_caps): assert (parsed_caps[3].interfaces[0].testquerystring.content == 'QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL') @pytest.fixture(name='cap_with_free_prefix') def _cap_with_free_prefix(recwarn): caps = vosi.parse_capabilities(io.BytesIO(b""" https://archive.eso.org/tap_obs ObsCore-1.1 ADQL 2.0 ADQL-2.0 """)) return recwarn, caps # this is a test for when people ignore the canonical prefixes for # registry documents. class TestFreePrefixes: def test_parses_without_warning(self, cap_with_free_prefix): warnings, _ = cap_with_free_prefix assert len(warnings) == 0 def test_parses_as_tapregext(self, cap_with_free_prefix): _, cap = cap_with_free_prefix assert isinstance(cap[0], tr.TableAccess) astropy-pyvo-b70558c/pyvo/io/vosi/tests/test_tables.py000066400000000000000000000344551510533647000231750ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import contextlib import io import pytest import pyvo.io.vosi as vosi import pyvo.io.vosi.vodataservice as vs from pyvo.io.vosi.exceptions import ( W02, W03, W04, W05, W06, W07, W08, W09, W10, W11, W12, W13, W14, W37) from pyvo.io.vosi.exceptions import E01, E02, E03, E06 from astropy.utils.data import get_pkg_data_filename class TestTables: def test_all(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables.xml")) table = next(tablesfile.iter_tables()) assert table.name == "test.all" assert table.title == "Test table" assert table.description == "All test data in one table" assert table.utype == "utype" assert table.nrows == 30 col = table.columns[0] fkc = table.foreignkeys[0] assert col.name == "id" assert col.description == "Primary key" assert col.unit == "unit" assert col.ucd == "meta.id;meta.main" assert col.utype == "utype" assert isinstance(col.datatype, vs.TAPType) assert str(col.datatype) == "VARCHAR" assert col.datatype.arraysize == "*" assert col.datatype.delim == ";" assert col.datatype.size == "42" assert col.datatype.content == "VARCHAR" assert "indexed" in col.flags assert "primary" in col.flags assert fkc.targettable == "test.foreigntable" assert fkc.fkcolumns[0].fromcolumn == "testkey" assert fkc.fkcolumns[0].targetcolumn == "testkey" assert fkc.description == "Test foreigner" assert fkc.utype == "utype" def _test_datatypes_votable(self, cols): assert cols[0].datatype.content == 'boolean' assert cols[1].datatype.content == 'bit' assert cols[2].datatype.content == 'unsignedByte' assert cols[3].datatype.content == 'short' assert cols[4].datatype.content == 'int' assert cols[5].datatype.content == 'long' assert cols[6].datatype.content == 'char' assert cols[7].datatype.content == 'unicodeChar' assert cols[8].datatype.content == 'float' assert cols[9].datatype.content == 'double' assert cols[10].datatype.content == 'floatComplex' assert cols[11].datatype.content == 'doubleComplex' def test_datatypes_votable(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables/datatypes_votable.xml")) votable, votabletype = tuple(tablesfile.iter_tables()) self._test_datatypes_votable(votable.columns) self._test_datatypes_votable(votabletype.columns) def _test_datatypes_tap(self, cols): assert cols[0].datatype.content == 'BOOLEAN' assert cols[1].datatype.content == 'SMALLINT' assert cols[2].datatype.content == 'INTEGER' assert cols[3].datatype.content == 'BIGINT' assert cols[4].datatype.content == 'REAL' assert cols[5].datatype.content == 'DOUBLE' assert cols[6].datatype.content == 'TIMESTAMP' assert cols[7].datatype.content == 'CHAR' assert cols[8].datatype.content == 'VARCHAR' assert cols[9].datatype.content == 'BINARY' assert cols[10].datatype.content == 'VARBINARY' assert cols[11].datatype.content == 'POINT' assert cols[12].datatype.content == 'REGION' assert cols[13].datatype.content == 'CLOB' assert cols[14].datatype.content == 'BLOB' def test_datatypes_tap(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables/datatypes_tap.xml")) tap, taptype = tuple(tablesfile.iter_tables()) self._test_datatypes_tap(tap.columns) self._test_datatypes_tap(taptype.columns) def test_wrong_datatypes_tap(self): with pytest.warns(W02): vosi.parse_tables( get_pkg_data_filename("data/tables/wrong_datatypes_tap.xml")) def test_wrong_datatypes_votable(self): with pytest.warns(W02): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_datatypes_votable.xml")) def test_no_schemas(self): with pytest.warns(W14): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schemas.xml")) with pytest.raises(W14): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schemas.xml"), pedantic=True) def test_no_schema_name(self): with pytest.raises(E06): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schema_name.xml")) def test_multiple_schema_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_schema_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_schema_names.xml"), pedantic=True) def test_multiple_schema_titles(self): with pytest.warns(W13): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_titles.xml")) with pytest.raises(W13): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_titles.xml"), pedantic=True) def test_multiple_schema_descriptions(self): with pytest.warns(W06): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_descriptions.xml"), pedantic=True) def test_multiple_schema_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_utypes.xml"), pedantic=True) def test_no_table_name(self): with pytest.raises(E06): vosi.parse_tables( get_pkg_data_filename("data/tables/no_table_name.xml")) def test_multiple_table_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_table_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_table_names.xml"), pedantic=True) def test_multiple_table_titles(self): with pytest.warns(W13): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_titles.xml")) with pytest.raises(W13): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_titles.xml"), pedantic=True) def test_multiple_table_descriptions(self): with pytest.warns(W06): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_descriptions.xml"), pedantic=True) def test_multiple_table_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_utypes.xml"), pedantic=True) def test_multiple_column_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_column_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_column_names.xml"), pedantic=True) def test_multiple_column_descriptions(self): with pytest.warns(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_descriptions.xml"), pedantic=True) def test_multiple_column_units(self): with pytest.warns(W07): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_units.xml")) with pytest.raises(W07): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_units.xml"), pedantic=True) def test_multiple_column_ucds(self): with pytest.warns(W08): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_ucds.xml")) with pytest.raises(W08): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_ucds.xml"), pedantic=True) def test_multiple_column_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_utypes.xml"), pedantic=True) def test_multiple_column_datatypes(self): with pytest.warns(W37): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_datatypes.xml")) with pytest.raises(W37): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_datatypes.xml"), pedantic=True) def test_tap_size(self): with pytest.warns(W03): vosi.parse_tables(get_pkg_data_filename( "data/tables/sizenegative.xml")) with pytest.raises(W03): vosi.parse_tables(get_pkg_data_filename( "data/tables/sizenegative.xml"), pedantic=True) @pytest.mark.xfail def test_wrong_flag(self): with pytest.warns(W04): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_flag.xml")) with pytest.raises(W04): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_flag.xml"), pedantic=True) def test_multiple_fromcolumns(self): with pytest.warns(W10): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_fromcolumns.xml")) with pytest.raises(W10): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_fromcolumns.xml"), pedantic=True) def test_missing_fromcolumn(self): with pytest.raises(E02): vosi.parse_tables(get_pkg_data_filename( "data/tables/no_fromcolumn.xml")) def test_multiple_targetcolumns(self): with pytest.warns(W11): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targetcolumns.xml")) with pytest.raises(W11): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targetcolumns.xml"), pedantic=True) def test_missing_targetcolumn(self): with pytest.raises(E03): vosi.parse_tables(get_pkg_data_filename( "data/tables/no_targetcolumn.xml")) def test_multiple_targettables(self): with pytest.warns(W12): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targettables.xml")) with pytest.raises(W12): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targettables.xml"), pedantic=True) def test_multiple_foreignkey_descriptions(self): with pytest.warns(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_descriptions.xml"), pedantic=True) def test_multiple_foreignkey_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_foreignkey_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_utypes.xml"), pedantic=True) def test_wrong_arraysize(self): with pytest.raises(E01): vosi.parse_tables( get_pkg_data_filename( "data/tables/wrong_arraysize.xml")) def test_no_table_description(self): """Test handling of describing tables with no description """ tableset = vosi.parse_tables( get_pkg_data_filename( "data/tables/no_table_description.xml")) nodesc_table = tableset.get_first_table() assert nodesc_table.description is None with io.StringIO() as buf, contextlib.redirect_stdout(buf): nodesc_table.describe() output = buf.getvalue() assert 'No description' in output def test_single_table_description(self): """Test describing a table with a single description """ tableset = vosi.parse_tables( get_pkg_data_filename( "data/tables/single_table_description.xml")) onedesc_table = tableset.get_first_table() describe_string = 'A test table with a single description' assert describe_string in onedesc_table.description with io.StringIO() as buf, contextlib.redirect_stdout(buf): onedesc_table.describe() output = buf.getvalue() assert describe_string in output astropy-pyvo-b70558c/pyvo/io/vosi/vodataservice.py000066400000000000000000000755321510533647000223620ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VODataService standard There are different ways of handling the various xml tags. * Elements with complex content * Elements with simple content and attributes * Elements with simple content without attributes Elements with complex content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element`. Elements with simple content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element` defining a ``value`` property. """ import re from astropy.utils import deprecated from astropy.utils.collections import HomogeneousList from textwrap import indent from astropy.utils.xml import check as xml_check from astropy.io.votable.exceptions import vo_raise, vo_warn, warn_or_raise from ...utils.xml.elements import ( xmlattribute, xmlelement, Element, ElementWithXSIType, ContentMixin) from . import voresource as vr from .exceptions import ( W01, W02, W03, W04, W05, W06, W07, W08, W09, W10, W11, W12, W13, W14, W17, W18, W36, W37, E01, E02, E03, E04, E05, E06) __all__ = [ "TableSet", "TableSchema", "ParamHTTP", "VODataServiceTable", "BaseParam", "TableParam", "InputParam", "DataType", "SimpleDataType", "TableDataType", "VOTableType", "TAPDataType", "TAPType", "FKColumn", "ForeignKey"] ###################################################################### # FACTORY FUNCTIONS def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) ###################################################################### # ATTRIBUTE CHECKERS def check_anyuri(uri, config=None, pos=None): """ Raises a `~pyvo.io.vosi.tables.exceptions.VOSITablesWarning` if *uri* is not a valid URI. As defined in RFC 2396. """ if uri is not None and not xml_check.check_anyuri(uri): warn_or_raise(W01, W01, uri, config=config, pos=pos) return False return True def check_datatype_flag(data, config=None, pos=None): """ Checks if the datatype flag is valid """ if data not in ('indexed', 'primary', 'nullable'): warn_or_raise(W04, W04, data, config=config, pos=pos) return False return True ###################################################################### # ELEMENT CLASSES class TableSet(Element, HomogeneousList): """ TableSet element as described in http://www.ivoa.net/xml/VODataService/v1.1 The set of tables hosted by a resource. """ def __init__( self, config=None, pos=None, _name='tableset', version='1.1', **kwargs ): HomogeneousList.__init__(self, TableSchema) Element.__init__(self, config, pos, _name, **kwargs) self._version = version def __repr__(self): return '... {} schemas ...'.format( len(self)) @xmlattribute def version(self): """The version of the standard""" return self._version @version.setter def version(self, version): self._config['version'] = version self._version = version @xmlelement(name='schema') def schemas(self): """ A list of schemas. Must contain only `~pyvo.io.vosi.vodataservice.TableSchema` objects. A named description of a set of logically related tables. The name given by the "name" child element must be unique within this TableSet instance. If there is only one schema in this set and/or there's no locally appropriate name to provide, the name can be set to "default". This aggregation does not need to map to an actual database, catalog, or schema, though the publisher may choose to aggregate along such designations, or particular service protocol may recommend it. """ return self @schemas.adder def schemas(self, iterator, tag, data, config, pos): schema = TableSchema(config, pos, 'schema', **data) schema.parse(iterator, config) self.append(schema) def parse(self, iterator, config): super().parse(iterator, config) if not self.schemas: warn_or_raise(W14, W14, config=config, pos=self._pos) class TableSchema(Element, HomogeneousList): """ TableSchema element as described in http://www.ivoa.net/xml/VODataService/v1.1 A detailed description of a logically-related set of tables. """ def __init__(self, config=None, pos=None, _name='schema', **kwargs): HomogeneousList.__init__(self, VODataServiceTable) Element.__init__(self, config, pos, _name, **kwargs) self._name = None self._title = None self._description = None self._utype = None def __repr__(self): return '... {} tables ...'.format( self.name, len(self.tables)) @xmlelement(plain=True, multiple_exc=W05) def name(self): """ A name for the set of tables. This is used to uniquely identify the table set among several table sets. If a title is not present, this name can be used for display purposes. If there is no appropriate logical name associated with this set, the name should be explicitly set to "default". """ return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W13) def title(self): """ a descriptive, human-interpretable name for the table set. This is used for display purposes. There is no requirement regarding uniqueness. It is useful when there are multiple schemas in the context (e.g. within a tableset; otherwise, the resource title could be used instead). """ return self._title @title.setter def title(self, title): self._title = title @xmlelement(plain=True, multiple_exc=W06) def description(self): """ A free text description of the tableset that should explain in general how all of the tables are related. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this schema as a whole represent. The format defined in the VOTable standard is strongly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype @xmlelement(name='table') def tables(self): """ A list of tables in the schema. Must contain only `~pyvo.io.vosi.vodataservice.VODataServiceTable` objects. A description of one of the tables that makes up the set. The table names for the table should be unique. """ return self @tables.adder def tables(self, iterator, tag, data, config, pos): table = VODataServiceTable(config, pos, 'table', **data) table.parse(iterator, config) self.append(table) def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) @vr.Interface.register_xsi_type('vs:ParamHTTP') class ParamHTTP(vr.Interface): """ ParamHTTP element as described in http://www.ivoa.net/xml/VODataService/v1.1 A service invoked via an HTTP Query (either Get or Post) with a set of arguments consisting of keyword name-value pairs. Note that the URL for help with this service can be put into the Service/ReferenceURL element. """ def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._querytypes = HomogeneousList(str) self._resulttype = None @xmlelement(name='queryType', multiple_exc=W17) def querytypes(self): """ The type of HTTP request, either GET or POST. The service may indicate support for both GET and POST by providing 2 queryType elements, one with GET and one with POST. """ return self._querytypes @xmlelement(name='resultType', multiple_exc=W36) def resulttype(self): """The MIME type of a document returned in the HTTP response.""" return self._resulttype @resulttype.setter def resulttype(self, resulttype): self._resulttype = resulttype def parse(self, iterator, config): super().parse(iterator, config) if len(self.querytypes) > 2: warn_or_raise(W18, W18, config=config, pos=self._pos) class VODataServiceTable(Element): """ Table element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__( self, config=None, pos=None, _name='table', version='1.1', **kwargs ): super().__init__(config, pos, _name, **kwargs) self._name = None self._title = None self._description = None self._utype = None self._type = kwargs.get("type") self._version = version self._nrows = None self._columns = HomogeneousList(TableParam) self._foreignkeys = HomogeneousList(ForeignKey) def __repr__(self): return '... {} columns ...'.format( self.name, len(self.columns)) def describe(self): print(self.name) if self.description is not None: print(indent(self.description, 4 * " ")) else: print('No description') print() @xmlelement(plain=True, multiple_exc=W05) def name(self): """ the fully qualified name of the table. This name should include all catalog or schema prefixes needed to sufficiently uniquely distinguish it in a query. In general, the format of the qualified name may depend on the context; however, when the table is intended to be queryable via ADQL, then the catalog and schema qualifiers are delimited from the table name with dots (.). """ return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W13) def title(self): """ a descriptive, human-interpretable name for the table. This is used for display purposes. There is no requirement regarding uniqueness. """ return self._title @title.setter def title(self, title): self._title = title @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of the table's contents """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this table represent. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype @xmlelement(plain=True, multiple_exc=W09) def nrows(self): """ the approximate number of rows in the table. This is None if the data provider failed to provide this information. """ return self._nrows @nrows.setter def nrows(self, nrows): self._nrows = int(nrows) @xmlattribute def type(self): """ a name for the role this table plays. Recognized values include "output", indicating this table is output from a query; "base_table", indicating a table whose records represent the main subjects of its schema; and "view", indicating that the table represents a useful combination or subset of other tables. Other values are allowed. """ return self._type @type.setter def type(self, type_): self._type = type_ @xmlattribute def version(self): """The version of the standard""" return self._version @version.setter def version(self, version): self._config['version'] = version self._version = version @xmlelement(name='column') def columns(self): """ A list of columns in the table. Must contain only `~pyvo.io.vosi.vodataservice.TableParam` objects. A description of a table column. """ return self._columns @columns.adder def columns(self, iterator, tag, data, config, pos): column = TableParam(config, pos, 'column', **data) column.parse(iterator, config) self.columns.append(column) @xmlelement(name='foreignKey') def foreignkeys(self): """ A list of columns in the table. Must contain only `~pyvo.io.vosi.vodataservice.ForeignKey` objects a description of a foreign keys, one or more columns from the current table that can be used to join with another table. """ return self._foreignkeys @foreignkeys.adder def foreignkeys(self, iterator, tag, data, config, pos): foreignkey = ForeignKey(config, pos, 'foreignKey', **data) foreignkey.parse(iterator, config) self.foreignkeys.append(foreignkey) def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) @deprecated("1.5", alternative="VODataServiceTable") class Table(VODataServiceTable): pass class BaseParam(Element): """ BaseParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 a description of a parameter that places no restriction on the parameter's data type. As the parameter's data type is usually important, schemas normally employ a sub-class of this type (e.g. Param), rather than this type directly. """ def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._name = None self._description = None self._unit = None self._ucd = None self._utype = None def __repr__(self): return f'' @xmlelement(plain=True, multiple_exc=W05) def name(self): """the name of the element""" return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of the element's contents """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W07) def unit(self): """the unit associated with all values in the element""" return self._unit @unit.setter def unit(self, unit): self._unit = unit @xmlelement(plain=True, multiple_exc=W08) def ucd(self): """ the name of a unified content descriptor that describes the scientific content of the element. There are no requirements for compliance with any particular UCD standard. The format of the UCD can be used to distinguish between UCD1, UCD1+, and SIA-UCD. See http://www.ivoa.net/Documents/latest/UCDlist.html for the latest IVOA standard set. """ return self._ucd @ucd.setter def ucd(self, ucd): self._ucd = ucd @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this element represent. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype class TableParam(BaseParam): """ TableParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 A description of a table parameter having a fixed data type. The allowed data type names match those supported by VOTable. """ @classmethod def from_field(cls, field): """ Create a instance from a `~astropy.io.votable.tree.Field` instance. """ instance = cls() instance.name = field.name instance.description = field.description instance.unit = field.unit instance.ucd = field.ucd instance.utype = field.utype datatype = VOTableType(arraysize=field.arraysize) datatype.value = field.datatype instance.datatype = datatype return instance def __init__(self, config=None, pos=None, _name='', std=None, **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._datatype = None self._flags = HomogeneousList(str) self._std = _convert_boolean(std) @xmlelement(name='dataType') def datatype(self): """The type of data contained in the element""" return self._datatype @datatype.setter def datatype(self, datatype): if datatype is not None and not isinstance(datatype, TableDataType): raise ValueError("datatype must be an TableDataType object") self._datatype = datatype @datatype.adder def datatype(self, iterator, tag, data, config, pos): datatype = TableDataType(config, pos, 'dataType', **data) datatype.parse(iterator, config) if self.datatype: warn_or_raise( W37, args=self._Element__name, config=config, pos=pos) self.datatype = datatype @xmlelement(name='flag') def flags(self): """ A list of flags. Must contain only `str` objects. a keyword representing traits of the column. Recognized values include "indexed", "primary", and "nullable". """ return self._flags @xmlattribute def std(self): """ If true, the meaning and use of this parameter is reserved and defined by a standard model. If false, it represents a database-specific parameter that effectively extends beyond the standard. If not provided, then the value is unknown. """ return self._std @std.setter def std(self, std): self._std = std def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) class InputParam(BaseParam): """ InputParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 A description of a service or function parameter having a fixed data type. """ def __init__( self, config=None, pos=None, _name='', use="optional", std="1", **kwargs): BaseParam.__init__(self, config, pos, _name, **kwargs) self._datatype = None self._use = use self._std = _convert_boolean(std, True) @xmlelement(name='dataType') def datatype(self): """The type of data contained in the element""" return self._datatype @datatype.setter def datatype(self, datatype): if datatype is not None and not isinstance(datatype, SimpleDataType): raise ValueError("datatype must be an SimpleDataType object") self._datatype = datatype @xmlattribute def use(self): """ An indication of whether this parameter is required to be provided for the application or service to work properly. Allowed values are "required" and "optional". """ return self._use @use.setter def use(self, use): self._use = use @xmlattribute def std(self): """ If true, the meaning and behavior of this parameter is reserved and defined by a standard interface. If false, it represents an implementation-specific parameter that effectively extends the behavior of the service or application. """ return self._std @std.setter def std(self, std): self._std = std def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) class DataType(ContentMixin, ElementWithXSIType): """ DataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 A type (in the computer language sense) associated with a parameter with an arbitrary name. This XML type is used as a parent for defining data types with a restricted set of names. """ def __init__( self, config=None, pos=None, _name='dataType', arraysize=None, delim=None, extendedType=None, extendedSchema=None, **kwargs ): super().__init__( config=config, pos=pos, _name=_name, **kwargs) if arraysize is None: arraysize = "1" if delim is None: delim = " " self.arraysize = arraysize self._delim = delim self._extendedtype = extendedType self.extendedschema = extendedSchema def __repr__(self): return '{}'.format( self.arraysize, self.content) @xmlattribute def arraysize(self): """Specifies the size of the dataType""" return self._arraysize @arraysize.setter def arraysize(self, arraysize): if all(( arraysize is not None, not re.match(r"^([0-9]+x)*[0-9]*[*]?(s\W)?$", arraysize) )): vo_raise(E01, arraysize, self._config, self._pos) self._arraysize = arraysize @xmlattribute def delim(self): """ the string that is used to delimit elements of an array value when arraysize is not "1". Unless specifically disallowed by the context, applications should allow optional spaces to appear in an actual data value before and after the delimiter (e.g. "1, 5" when delim=","). the default is " "; i.e. the values are delimited by spaces. """ return self._delim @delim.setter def delim(self, delim): self._delim = delim @xmlattribute(name='extendedType') def extendedtype(self): """ The data value represented by this type can be interpreted as of a custom type identified by the value of this attribute. If an application does not recognize this extendedType, it should attempt to handle value assuming the type given by the element's value. string is a recommended default type. This element may make use of the extendedSchema attribute and/or any arbitrary (qualified) attribute to refine the identification of the type. """ @extendedtype.setter def extendedtype(self, extendedtype): self._extendedtype = extendedtype @xmlattribute(name='extendedSchema') def extendedschema(self): """ An identifier for the schema that the value given by the extended attribute is drawn from. This attribute is normally ignored if the extendedType attribute is not present. """ return self._extendedschema @extendedschema.setter def extendedschema(self, extendedschema): if extendedschema is not None: check_anyuri(extendedschema, self._config, self._pos) self._extendedschema = extendedschema class SimpleDataType(DataType): """ SimpleDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 A data type restricted to a small set of names which is imprecise as to the format of the individual values. This set is intended for describing simple input parameters to a service or function. """ def _content_check(self, value): if value is not None: valid_values = { 'integer', 'real', 'complex', 'boolean', 'char', 'string'} if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class TableDataType(DataType): """ TableDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 an abstract parent for a class of data types that can be used to specify the data type of a table column. Subtypes must be decorated with ``register_xsi_type('ns:name')``. """ @TableDataType.register_xsi_type('vs:VOTable') @TableDataType.register_xsi_type('vs:VOTableType') class VOTableType(TableDataType): """ VOTableType element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def _content_check(self, value): if value is not None: valid_values = ( 'boolean', 'bit', 'unsignedByte', 'short', 'int', 'long', 'char', 'unicodeChar', 'float', 'double', 'floatComplex', 'doubleComplex') if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class TAPDataType(TableDataType): """ TAPDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 an abstract parent for the specific data types supported by the Table Access Protocol. """ def __init__( self, config=None, pos=None, _name='dataType', size=None, **kwargs ): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self.size = size @xmlattribute def size(self): """ the length of the fixed-length value. This corresponds to the size Column attribute in the TAP_SCHEMA and can be used with data types that are defined with a length (CHAR, BINARY). """ return self._size @size.setter def size(self, size): if size is not None and int(size) < 0: size = 0 warn_or_raise(W03, W03, config=self._config, pos=self._pos) self._size = size @TableDataType.register_xsi_type('vs:TAP') @TableDataType.register_xsi_type('vs:TAPType') class TAPType(TAPDataType): """ TAPType element as described in http://www.ivoa.net/xml/VODataService/v1.1 a data type supported explicitly by the Table Access Protocol (v1.0). """ def _content_check(self, value): if value is not None: valid_values = ( 'BOOLEAN', 'SMALLINT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'TIMESTAMP', 'CHAR', 'VARCHAR', 'BINARY', 'VARBINARY', 'POINT', 'REGION', 'CLOB', 'BLOB') if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class FKColumn(Element): """ FKColumn element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__(self, config=None, pos=None, _name='fkColumn', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._fromcolumn = None self._targetcolumn = None def __repr__(self): return '...'.format( self.fromcolumn, self.targetcolumn) @xmlelement(name='fromColumn', plain=True, multiple_exc=W10) def fromcolumn(self): """ The unqualified name of the column from the current table. """ return self._fromcolumn @fromcolumn.setter def fromcolumn(self, fromcolumn): self._fromcolumn = fromcolumn @xmlelement(name='targetColumn', plain=True, multiple_exc=W11) def targetcolumn(self): """ The unqualified name of the column from the target table. """ return self._targetcolumn @targetcolumn.setter def targetcolumn(self, targetcolumn): self._targetcolumn = targetcolumn def parse(self, iterator, config): super().parse(iterator, config) if self.fromcolumn is None: vo_raise(E02, config=config, pos=self._pos) if self.targetcolumn is None: vo_raise(E03, config=config, pos=self._pos) class ForeignKey(Element): """ ForeignKey element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__(self, config=None, pos=None, _name='foreignKey', **kwargs): Element.__init__(self, config, pos, _name, **kwargs) self._targettable = None self._fkcolumns = HomogeneousList(FKColumn) self._description = None self._utype = None def __repr__(self): return '...'.format( self.targettable) @xmlelement(name='targetTable', plain=True, multiple_exc=W12) def targettable(self): """ the fully-qualified name (including catalog and schema, as applicable) of the table that can be joined with the table containing this foreign key. """ return self._targettable @targettable.setter def targettable(self, targettable): self._targettable = targettable @xmlelement(name='fkColumn') def fkcolumns(self): """ A list of foreign key columns. Must contain only `~pyvo.io.vosi.vodataservice.FKColumn` objects. a pair of column names, one from this table and one from the target table that should be used to join the tables in a query. """ return self._fkcolumns @fkcolumns.adder def fkcolumns(self, iterator, tag, data, config, pos): fkcolumn = FKColumn(config, pos, 'fkColumn', **data) fkcolumn.parse(iterator, config) self.fkcolumns.append(fkcolumn) @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of what this key points to and what the relationship means. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the association enabled by this key represents. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype def parse(self, iterator, config): super().parse(iterator, config) if not self.targettable: vo_raise(E04, config=config, pos=self._pos) if not self.fkcolumns: vo_raise(E05, config=config, pos=self._pos) astropy-pyvo-b70558c/pyvo/io/vosi/voresource.py000066400000000000000000000360431510533647000217110ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VOResource standard. There are different ways of handling the various xml tags. * Elements with complex content * Elements with simple content and attributes * Elements with simple content without attributes Elements with complex content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element`. Elements with simple content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element` defining a ``value`` property. """ from astropy.utils.collections import HomogeneousList from textwrap import indent from ...utils.xml.elements import ( Element, ElementWithXSIType, ContentMixin, xmlattribute, xmlelement) from .exceptions import W06 __all__ = [ "ValidationLevel", "Capability", "Interface", "AccessURL", "SecurityMethod", "WebBrowser", "WebService", "MirrorURL"] ###################################################################### # ELEMENT CLASSES class ValidationLevel(ContentMixin, Element): """ ValidationLevel element as described in http://www.ivoa.net/xml/VOResource/v1.0 the allowed values for describing the resource descriptions and interfaces. See the RM (v1.1, section 4) for more guidance on the use of these values. Possible values: 0: The resource has a description that is stored in a registry. This level does not imply a compliant description. 1: In addition to meeting the level 0 definition, the resource description conforms syntactically to this standard and to the encoding scheme used. 2: In addition to meeting the level 1 definition, the resource description refers to an existing resource that has demonstrated to be functionally compliant. When the resource is a service, it is consider to exist and functionally compliant if use of the service accessURL responds without error when used as intended by the resource. If the service is a standard one, it must also demonstrate the response is syntactically compliant with the service standard in order to be considered functionally compliant. If the resource is not a service, then the ReferenceURL must be shown to return a document without error. 3: In addition to meeting the level 2 definition, the resource description has been inspected by a human and judged to comply semantically to this standard as well as meeting any additional minimum quality criteria (e.g., providing values for important but non-required metadata) set by the human inspector. 4: In addition to meeting the level 3 definition, the resource description meets additional quality criteria set by the human inspector and is therefore considered an excellent description of the resource. Consequently, the resource is expected to be operate well as part of a VO application or research study. """ def __init__( self, config=None, pos=None, _name='validationLevel', validatedBy=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._validatedby = validatedBy def __repr__(self): return '{}'.format( self.validatedby, self.content) @xmlattribute def validatedby(self): """ The IVOA ID of the registry or organisation that assigned the validation level. """ return self._validatedby @validatedby.setter def validatedby(self, validatedby): self._validatedby = validatedby class AccessURL(ContentMixin, Element): """ AccessURL element as described in http://www.ivoa.net/xml/VOResource/v1.0 The URL (or base URL) that a client uses to access the service. How this URL is to be interpreted and used depends on the specific Interface subclass """ def __init__( self, config=None, pos=None, _name='accessURL', use=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._use = use def __repr__(self): return '{}'.format( self.use, self.content) @xmlattribute def use(self): """ A flag indicating whether this should be interpreted as a base URL, a full URL, or a URL to a directory that will produce a listing of files. Possible values: full: Assume a full URL--that is, one that can be invoked directly without alteration. This usually returns a single document or file. base: Assume a base URL--that is, one requiring an extra portion to be appended before being invoked. dir: Assume URL points to a directory that will return a listing of files. """ return self._use @use.setter def use(self, use): self._use = use class MirrorURL(ContentMixin, Element): """ A URL available as a mirror of an access URL. These come with a human-readable title intended to aid in mirror selection. """ def __init__( self, config=None, pos=None, _name='accessURL', title=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._title = title @xmlattribute def title(self): """ A human-readable title for the mirror. """ return self._title class SecurityMethod(ContentMixin, Element): """ SecurityMethod element as described in http://www.ivoa.net/xml/VOResource/v1.0 A description of a security mechanism. this type only allows one to refer to the mechanism via a URI. Derived types would allow for more metadata. """ def __init__( self, config=None, pos=None, _name='securityMethod', standardID=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._standardid = standardID def __repr__(self): return '{}'.format( self.standardid, self.content) @xmlattribute(name='standardID') def standardid(self): """ A URI identifier for a standard security mechanism. """ return self._standardid @standardid.setter def standardid(self, standardid): self._standardid = standardid class Interface(ElementWithXSIType): """ Interface element as described in http://www.ivoa.net/xml/VOResource/v1.0 A description of a service interface. Since this type is abstract, one must use an Interface subclassto describe an actual interface. Additional interface subtypes (beyond WebService and WebBrowser) are defined in the VODataService schema. """ def __init__( self, config=None, pos=None, _name='interface', version='1.0', role=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._xsi_type = kwargs.get('xsi:type') self._version = version self._role = role self._resulttype = None self._testquerystring = None self._accessurls = HomogeneousList(AccessURL) self._securitymethods = HomogeneousList(SecurityMethod) self._mirrorurls = HomogeneousList(MirrorURL) def __repr__(self): return '...'.format( self.role) def describe(self): """ Prints out a human readable description """ print(f'Interface {self._xsi_type}') accessurls = '\n'.join( accessurl.content for accessurl in self.accessurls) print(indent(accessurls, 4 * " ")) print() @xmlattribute def version(self): """ The version of a standard interface specification that this interface complies with. When the interface is provided in the context of a Capability element, then the standard being refered to is the one identified by the Capability's standardID element. If the standardID is not provided, the meaning of this attribute is undefined. """ return self._version @version.setter def version(self, version): self._version = version @xmlattribute def role(self): """ A tag name the identifies the role the interface plays in the particular capability. If the value is equal to "std" or begins with "std:", then the interface refers to a standard interface defined by the standard referred to by the capability's standardID attribute. For an interface complying with some registered standard (i.e. has a legal standardID), the role can be match against interface roles enumerated in standard resource record. The interface descriptions in the standard record can provide default descriptions so that such details need not be repeated here. """ return self._role @role.setter def role(self, role): self._role = role @xmlelement(name='accessURL', cls=AccessURL) def accessurls(self): """ A list of access urls in the interface. Must contain only `AccessURL` objects. """ return self._accessurls @xmlelement(name='mirrorURL', cls=MirrorURL) def mirrorurls(self): """ mirror(s) for this access URL. """ return self._mirrorurls @xmlelement(name='securityMethod', cls=SecurityMethod) def securitymethods(self): """ the mechanism the client must employ to gain secure access to the service. when more than one method is listed, each one must be employed to gain access. """ return self._securitymethods @xmlelement(name='testQueryString') def testquerystring(self): """ a string to be used in an interface-specific way to obtain a non-empty result from the service. """ return self._testquerystring @testquerystring.setter def testquerystring(self, testquerystring): self._testquerystring = testquerystring @xmlelement def resulttype(self): """ The MIME type of a document returned in the HTTP response. """ return self._resulttype @resulttype.setter def resulttype(self, resulttype): self._resulttype = resulttype class Capability(ElementWithXSIType): """ Capability element as described in http://www.ivoa.net/xml/VOResource/v1.0 a description of what the service does (in terms of context-specific behavior), and how to use it (in terms of an interface) """ def __init__( self, config=None, pos=None, _name='capability', standardID=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._description = None self._standardid = standardID self._validationlevels = HomogeneousList(ValidationLevel) self._interfaces = HomogeneousList(Interface) def __repr__(self): return ( '' '... {} validationLevels, {} interfaces ...' '' ).format( self.standardid, len(self.validationlevels), len(self.interfaces)) def describe(self): """ Prints out a human readable description """ print(f"Capability {self.standardid}") print() if self.description: print(self.description) print() for interface in self.interfaces: interface.describe() @xmlelement(plain=True, multiple_exc=W06) def description(self): """ A human-readable description of what this capability provides as part of the over-all service Use of this optional element is especially encouraged when this capability is non-standard and is one of several capabilities listed. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(name='validationLevel', cls=ValidationLevel) def validationlevels(self): """ A numeric grade describing the quality of the capability description and interface, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research study. """ return self._validationlevels @xmlelement(name='interface', cls=Interface) def interfaces(self): """ a description of how to call the service to access this capability Since the Interface type is abstract, one must describe the interface using a subclass of Interface, denoting it via xsi:type. Multiple occurances can describe different interfaces to the logically same capability--i.e. data or functionality. That is, the inputs accepted and the output provides should be logically the same. For example, a WebBrowser interface given in addition to a WebService interface would simply provide an interactive, human-targeted interface to the underlying WebService interface. """ return self._interfaces @xmlattribute(name='standardID') def standardid(self): """ A URI identifier for a standard service. This provides a unique way to refer to a service specification standard, such as a Simple Image Access service. The use of an IVOA identifier here implies that a VOResource description of the standard is registered and accessible. """ return self._standardid @standardid.setter def standardid(self, standardid): self._standardid = standardid @Interface.register_xsi_type('vr:WebBrowser') class WebBrowser(Interface): """ WebBrowser element as described in http://www.ivoa.net/xml/VOResource/v1.0 A (form-based) interface intended to be accesed interactively by a user via a web browser. The accessURL represents the URL of the web form itself. """ @Interface.register_xsi_type('vr:WebService') class WebService(Interface): """ WebService element as described in http://www.ivoa.net/xml/VOResource/v1.0 A Web Service that is describable by a WSDL document. The accessURL element gives the Web Service's endpoint URL. """ def __init__(self, config=None, pos=None, _name='interface', **kwargs): super().__init__(config, pos, _name, **kwargs) self._wsdlurls = HomogeneousList(str) @xmlelement(name='wsdlURL') def wsdlurls(self): """ The location of the WSDL that describes this Web Service. If not provided, the location is assumed to be the accessURL with "?wsdl" appended. Multiple occurances should represent mirror copies of the same WSDL file. """ return self._wsdlurls astropy-pyvo-b70558c/pyvo/mivot/000077500000000000000000000000001510533647000167045ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/__init__.py000066400000000000000000000001001510533647000210040ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/mivot/features/000077500000000000000000000000001510533647000205225ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/features/__init__.py000066400000000000000000000000001510533647000226210ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/features/sky_coord_builder.py000066400000000000000000000235461510533647000246100ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Utility transforming MIVOT annotation into SkyCoord instances """ import numbers from astropy.coordinates import SkyCoord from astropy import units as u from astropy.coordinates import ICRS, Galactic, FK4, FK5 from astropy.time.core import Time from pyvo.mivot.glossary import SkyCoordMapping from pyvo.mivot.utils.exceptions import NoMatchingDMTypeError, MappingError class SkyCoordBuilder: ''' Utility generating SkyCoord instances from MIVOT annotations - SkyCoord instances can only be built from model classes containing the minimal set of required parameters (a position). - In this implementation, only the mango:EpochPosition class is supported since it contains the information required to compute the epoch propagation which is a major use-case ''' def __init__(self, mivot_instance): ''' Constructor parameters ----------- mivot_instance: dict or MivotInstance Python object generated from the MIVOT block as either a Pyhon object or a dict ''' self._mivot_instance_dict = mivot_instance.to_dict() self._map_coord_names = None def build_sky_coord(self): """ Build a SkyCoord instance from the MivotInstance dictionary. The operation requires the dictionary to have ``mango:EpochPosition`` as dmtype. This instance can be either the root of the dictionary or it can be one of the Mango properties if the root object is a mango:MangoObject instance This is a public method which could be extended to support other dmtypes. returns ------- SkyCoord Instance built by the method raises ------ NoMatchingDMTypeError if the SkyCoord instance cannot be built. """ if self._mivot_instance_dict and self._mivot_instance_dict["dmtype"] == "mango:MangoObject": property_dock = self._mivot_instance_dict["propertyDock"] for mango_property in property_dock: if mango_property["dmtype"] == "mango:EpochPosition": self._mivot_instance_dict = mango_property return self._build_sky_coord_from_mango() raise NoMatchingDMTypeError( "No INSTANCE with dmtype='mango:EpochPosition' has been found:" " in the property dock of the MangoObject, " "cannot build a SkyCoord from annotations") elif self._mivot_instance_dict and self._mivot_instance_dict["dmtype"] == "mango:EpochPosition": return self._build_sky_coord_from_mango() raise NoMatchingDMTypeError( "No INSTANCE with dmtype='mango:EpochPosition' has been found:" " cannot build a SkyCoord from annotations") def _get_time_instance(self, hk_field, besselian=False): """ Format a date expressed in year as [scale]year - Exception possibly risen by Astropy are not caught parameters ---------- hk_field: dict MIVOT instance attribute besselian: boolean besselian time scale is used if True, otherwise Julian (default) returns ------- Time instance or None raise ----- MappingError: if the Time instance cannot be built for some reason """ # Process complex type "mango:DateTime if hk_field['dmtype'] == "mango:DateTime": representation = hk_field['representation']['value'] timestamp = hk_field['dateTime']['value'] # Process simple attribute else: representation = hk_field.get("unit") timestamp = hk_field.get("value") if not representation or not timestamp: raise MappingError(f"Cannot interpret field {hk_field} " f"as a {('besselian' if besselian else 'julian')} timestamp") time_instance = self. _build_time_instance(timestamp, representation, besselian) if not time_instance: raise MappingError(f"Cannot build a Time instance from {hk_field}") return time_instance def _build_time_instance(self, timestamp, representation, besselian=False): """ Build a Time instance matching the input parameters. - Returns None if the parameters do not allow any Time setup - Exception possibly risen by Astropy are not caught at this level parameters ---------- timestamp: string or number The timestamp must comply with the given representation representation: string year, iso, ... (See MANGO primitive types derived from ivoa:timeStamp) besselian: boolean (optional) Flag telling to use the besselain calendar. We assume it to only be relevant for FK5 frame returns ------- Time instance or None """ if representation in ("year", "yr", "y"): # it the timestamp is numeric, we infer its format from the besselian flag if isinstance(timestamp, numbers.Number): return Time(f"{('B' if besselian else 'J')}{timestamp}", format=("byear_str" if besselian else "jyear_str")) if besselian: if timestamp.startswith("B"): return Time(f"{timestamp}", format="byear_str") elif timestamp.startswith("J"): # a besselain year cannot be given as "Jxxxx" return None elif timestamp.isnumeric(): # we force the string representation not to break the test assertions return Time(f"B{timestamp}", format="byear_str") else: if timestamp.startswith("J"): return Time(f"{timestamp}", format="jyear_str") elif timestamp.startswith("B"): # a julian year cannot be given as "Bxxxx" return None elif timestamp.isnumeric(): # we force the string representation not to break the test assertions return Time(f"J{timestamp}", format="jyear_str") # no case matches return None # in the following cases, the calendar (B or J) is given by the besselian flag # We force to use the string representation to avoid breaking unit tests. elif representation in ("mjd", "jd", "iso"): time = Time(f"{timestamp}", format=representation) return (Time(time.byear_str) if besselian else time) return None def _get_space_frame(self): """ Build an astropy space frame instance from the MIVOT annotations. - Equinox are supported for FK4/5 - Reference location is not supported returns ------- FK2, FK5, ICRS or Galactic Astropy space frame instance """ coo_sys = self._mivot_instance_dict["spaceSys"]["frame"] equinox = None frame = coo_sys["spaceRefFrame"]["value"].lower() if frame == 'fk4': self._map_coord_names = SkyCoordMapping.default_params if "equinox" in coo_sys: equinox = self._get_time_instance(coo_sys["equinox"], True) # by FK4 takes obstime=equinox by default return FK4(equinox=equinox) return FK4() if frame == 'fk5': self._map_coord_names = SkyCoordMapping.default_params if "equinox" in coo_sys: equinox = self._get_time_instance(coo_sys["equinox"]) return FK5(equinox=equinox) return FK5() if frame == 'galactic': self._map_coord_names = SkyCoordMapping.galactic_params return Galactic() self._map_coord_names = SkyCoordMapping.default_params return ICRS() def _build_sky_coord_from_mango(self): """ Build a SkyCoord instance from the ``mango:EpochPosition instance``. - The epoch (obstime) is meant to be given in year. - ICRS frame is taken by default - The cos-delta correction is meant to be applied. The case ``mango:pmCosDeltApplied = False`` is not supported yet returns ------- SkyCoord instance built by the method """ kwargs = {} kwargs["frame"] = self._get_space_frame() for mango_role, skycoord_field in self._map_coord_names.items(): # ignore not mapped parameters if mango_role not in self._mivot_instance_dict: continue hk_field = self._mivot_instance_dict[mango_role] if mango_role == "obsDate": besselian = isinstance(kwargs["frame"], FK4) fobstime = self._get_time_instance(hk_field, besselian=besselian) # FK4 class has an obstime attribute which must be set at instanciation time if besselian: kwargs["frame"] = FK4(equinox=kwargs["frame"].equinox, obstime=fobstime) # This is not the case for any other space frames else: kwargs[skycoord_field] = fobstime # ignore not set parameters elif (hk_value := hk_field["value"]) is not None: # Convert the parallax (mango) into a distance if skycoord_field == "distance": kwargs[skycoord_field] = ( (hk_value * u.Unit(hk_field["unit"])).to(u.parsec, equivalencies=u.parallax())) elif "unit" in hk_field and hk_field["unit"]: kwargs[skycoord_field] = hk_value * u.Unit(hk_field["unit"]) else: kwargs[skycoord_field] = hk_value return SkyCoord(**kwargs) astropy-pyvo-b70558c/pyvo/mivot/features/static_reference_resolver.py000066400000000000000000000050751510533647000263310ustar00rootroot00000000000000""" Class used to resolve each static REFERENCE found in mivot_block. """ from copy import deepcopy from pyvo.mivot.utils.exceptions import MivotError from pyvo.mivot.utils.xpath_utils import XPath from pyvo.utils.prototype import prototype_feature @prototype_feature('MIVOT') class StaticReferenceResolver: """ Namespace for the function processing the static REFERENCEs """ @staticmethod def resolve(annotation_seeker, templates_ref, mivot_block): """ Resolve all static REFERENCEs found in the mivot_block. The referenced objects are first searched in GLOBALS and then in the templates_ref table. REFERENCE elements are replaced with the referenced objects set with the roles of the REFERENCEs. Works even if REFERENCE tags are numbered by the former processing. Parameters ---------- annotation_seeker : AnnotationSeeker Utility to extract desired elements from the mapping block. templates_ref : str Identifier of the table where the mivot_block comes from. mivot_block : xml.etree.ElementTree The XML element object. Returns ------- int The number of references resolved. Raises ------ MappingError If the reference cannot be resolved. NotImplementedError If the reference is dynamic. """ resolved_refs = 0 for ele in XPath.x_path_startwith(mivot_block, './/REFERENCE_'): dmref = ele.get("dmref") # If we have no @dmref in REFERENCE, we consider this is a ref based on a keys if dmref is None: raise NotImplementedError("Dynamic reference not implemented") target = annotation_seeker.get_globals_instance_by_dmid(dmref) if target is None and templates_ref is not None: target = annotation_seeker.get_templates_instance_by_dmid(templates_ref, dmref) if target is None: raise MivotError(f"Cannot resolve reference={dmref}") target_copy = deepcopy(target) # If the reference is within a collection: no role if ele.get('dmrole'): target_copy.attrib["dmrole"] = ele.get('dmrole') parent_map = {c: p for p in mivot_block.iter() for c in p} parent = parent_map[ele] # Insert the referenced object parent.append(target_copy) # Drop the reference parent.remove(ele) resolved_refs += 1 return resolved_refs astropy-pyvo-b70558c/pyvo/mivot/glossary.py000066400000000000000000000166641510533647000211360ustar00rootroot00000000000000""" Glossary for the MIVOT package - Model related words (hard-coded for now) - URLs """ __all__ = ["Url", "IvoaType", "Roles", "CoordSystems", "ModelPrefix", "VodmlUrl", "EpochPositionAutoMapping"] class Url: """ Service URL(s) that are used by the API """ #: Filter Profile Service URL (SVO) FPS = ( "http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID=" ) class IvoaType: """ Primitive VODML types """ #: primitive type for strings string = "ivoa:string" #: primitive type for reals real = "ivoa:real" #: primitive type for real quantity (real + unit) RealQuantity = "ivoa:RealQuantity" #: primitive type for booleans bool = "ivoa:boolean" #: primitive type for a point in time datetime = "ivoa:datatime" class Roles: """ Accepted roles for all implemented classes; correspond to the last path element of the ``dmroles`` as defined in VODML (VODML-ID) """ # Roles of the EpochPosition class that are supported # Do not change the ordering: it used below to access fields EpochPosition = [ "longitude", "latitude", "parallax", "radialVelocity", "pmLongitude", "pmLatitude", "obsDate", ] #: Roles of the EpochPositionCorrelations class that are supported EpochPositionCorrelations = [ "longitudeParallax", "latitudeParallax", "pmLongitudeParallax", "pmLatitudeParallax", "longitudeLatitude", "pmLongitudePmLatitude", "latitudePmLatitude", "latitudePmLongitude", "longitudePmLatitude", "longitudePmLongitude", "isCovariance", ] #: Roles of the EpochPositionErrors class that are supported EpochPositionErrors = [ "parallax", "radialVelocity", "position", "properMotion", ] #: Roles of the PErrorSym1D class that is supported PErrorSym1D = [ "sigma" ] #: Roles of the PErrorAsym1D class that are supported PErrorAsym1D = [ "low", "high" ] #: Roles of the PErrorSym2D class that are supported PErrorSym2D = [ "sigma1", "sigma2" ] #: Roles of the PErrorSym2D class that are supported PErrorEllipse = [ "semiMajorAxis", "semiMinorAxis", "angle" ] #: Roles of the PhotometricProperty class that are supported PhotometricProperty = ["value", "error" ] #: Roles of the Color class that are supported Color = ["value", "error", "definition" ] #: Roles of the QueryOrigin class that are supported QueryOrigin = ["publisher", "server_software", "service_protocol", "request", "request_date", "query", "contact", "ivoid" ] #: Roles of the DataOrigin class that are supported DataOrigin = ["ivoid", "reference_url", "resource_version", "creators", "cites", "is_derived_from", "original_date", "rights", "rights_uri", "articles" ] #: Roles of the Article class that are supported Article = ["identifier", "editor" ] class CoordSystems: """ Supported values for the coordinate system parameters (space and time) """ #: see IVOA `refframe `_ vocabulary space_frames = ["eq_FK4", "FK4", "eq_FK5", "FK5", "ICRS", "GALACTIC", "SUPER_GALACTIC", "ECLIPTIC"] #: see IVOA `refposition `_ vocabulary ref_positions = ["BARYCENTER", "GEOCENTER", "TOPOCENTER"] #: see IVOA `timescale `_ vocabulary time_frames = ["TAI", "TT", "TDT", "ET", "IAT", "UT1", "UTC", "GMT", "GPS", "TCG", "TCB", "TBD", "LOCAL"] #: supported time formats (could be replaced witha vocabulary later on) time_formats = ["byear", "cxcsec", "decimalyear", "fits", "gps", "iso", "timestamp", "jyear", "year", "jd", "mjd"] class ModelPrefix: """ Model names as defined in VODML """ #: `VODML `_ primitive types ivoa = "ivoa" #: VODML prefix of the MANGO model mango = "mango" #: VODML prefix of the `Photometry Data Model #: `_ Phot = "Phot" #: VODML prefix of the Astronomical `Astronomical Coordinates and Coordinate Systems #: `_ datamodel coords = "coords" #: VODML prefix of the `Astronomical Measurements Model #: `_ meas = "meas" class VodmlUrl: """ VODML URLs of the supported models. Names of the class attributes match the `ModelPrefix` fields. """ #: VODML URL of the `VODML #: `_ primitive types ivoa = "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml" #: VODML URL of the MANGO model mango = "https://raw.githubusercontent.com/ivoa-std/MANGO/refs/heads/wd-v1.0/vo-dml/mango.vo-dml.xml" #: VODML URL of the `Photometry Data Model `_ Phot = "https://ivoa.net/xml/VODML/Phot-v1.vodml.xml" #: VODML URL of the `Astronomical Coordinates and Coordinate Systems #: `_ datamodel coords = "https://ivoa.net/xml/VODML/Coords-v1.vo-dml.xml" #: VODML URL of the `Astronomical Measurements Model #: `_ meas = "https://ivoa.net/xml/VODML/Meas-v1.vo-dml.xml" class EpochPositionAutoMapping: """ Expected UCDs for identifying FIELD to be mapped to EpochPosition attributes. - UCD-s of the associated errors are derived from them - list items must have an exact match - Single values are evaluated as starting with """ #: UCD-s accepted to map the longitude longitude = ["POS_EQ_RA_MAIN", "pos.eq.ra;meta.main"] #: UCD-s accepted to map the latitude latitude = ["POS_EQ_DEC_MAIN", "pos.eq.dec;meta.main"] #: UCD-s accepted to map the proper motion longitude pmLongitude = ["pos.pm;pos.eq.ra"] #: UCD-s accepted to map the proper motion latitude pmLatitude = ["pos.pm;pos.eq.dec"] #: UCD-s accepted to map the obsDate obsDate = ["time.epoch;obs;stat.mean", "time.epoch;obs"] #: UCD-s accepted to map the parallax parallax = ["pos.parallax.trig"] #: first word of UCD-s accepted to map the radial velocity radialVelocity = "spect.dopplerVeloc.opt" class SkyCoordMapping: """ Mapping of the MANGO:EpochPosition parameters to the SkyCoord parameters """ default_params = { Roles.EpochPosition[0]: 'ra', Roles.EpochPosition[1]: 'dec', Roles.EpochPosition[2]: 'distance', Roles.EpochPosition[3]: 'radial_velocity', Roles.EpochPosition[4]: 'pm_ra_cosdec', Roles.EpochPosition[5]: 'pm_dec', Roles.EpochPosition[6]: 'obstime'} galactic_params = { Roles.EpochPosition[0]: 'l', Roles.EpochPosition[1]: 'b', Roles.EpochPosition[2]: 'distance', Roles.EpochPosition[3]: 'radial_velocity', Roles.EpochPosition[4]: 'pm_l_cosb', Roles.EpochPosition[5]: 'pm_b', Roles.EpochPosition[6]: 'obstime'} astropy-pyvo-b70558c/pyvo/mivot/seekers/000077500000000000000000000000001510533647000203455ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/seekers/__init__.py000066400000000000000000000001461510533647000224570ustar00rootroot00000000000000""" ``seekers`` package contains utilities for retrieving components of VOTales or of Mivot blocks """astropy-pyvo-b70558c/pyvo/mivot/seekers/annotation_seeker.py000066400000000000000000000363501510533647000244360ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Utilities for extracting sub-blocks from a MIVOT mapping block. """ import logging from pyvo.mivot.utils.exceptions import MivotError, MappingError from pyvo.mivot.utils.vocabulary import Att, Ele from pyvo.mivot.utils.vocabulary import Constant from pyvo.mivot.utils.xpath_utils import XPath from pyvo.utils.prototype import prototype_feature @prototype_feature('MIVOT') class AnnotationSeeker: """ This class provides tools for extracting mapping sub-blocks commonly used by other stake-holders. All functions using the mapping employ this class to obtain XML elements. To simplify the job for other tools, the XML namespace is removed from the mapping block. Attributes: ---------- _xml_block (~`xml.etree.ElementTree.Element`): Full mapping block. _globals_block (~`xml.etree.ElementTree.Element` or None): GLOBALS block. _templates_blocks (dict): Templates dictionary where keys are tableref and values are XML-TEMPLATES. """ def __init__(self, xml_block): """ Initializes the AnnotationSeeker. - Split the mapping as elements of interest. - Remove the name_spaces. - Append numbers to JOIN/REFERENCE. Parameters ---------- xml_block (~`xml.etree.ElementTree.Element`): XML mapping block """ self._xml_block = xml_block self._globals_block = None self._templates_blocks = {} self._find_globals_block() self._find_templates_blocks() self._rename_ref_and_join() def _find_globals_block(self): """ Extract the GLOBALS element from the XML mapping block and store its reference. """ for child in self._xml_block: if self._name_match(child.tag, Ele.GLOBALS): logging.debug("Found " + Ele.GLOBALS) self._globals_block = child def _find_templates_blocks(self): """ Search the TEMPLATES elements from the XML mapping block and store its reference. This method iterates through the children of the XML mapping block, identifies TEMPLATES blocks, and associates them with their respective @tableref values in the _templates_blocks dictionary. Returns ------- dict: TEMPLATES tablerefs and their mapping blocks {'tableref': mapping_block, ...} Raises ------ MivotElementNotFound If a TEMPLATES block is found without a @tableref attribute, and there are already other TEMPLATES blocks. """ for child in self._xml_block: if self._name_match(child.tag, Ele.TEMPLATES): tableref = child.get(Att.tableref) if tableref is not None: logging.debug("Found " + Ele.TEMPLATES + " %s", tableref) self._templates_blocks[tableref] = child elif not self._templates_blocks: logging.debug("Found " + Ele.TEMPLATES + " without " + Att.tableref) self._templates_blocks["DEFAULT"] = child else: raise MivotError(Ele.TEMPLATES + " without " + Att.tableref + " must be unique") def _rename_ref_and_join(self): """ Tag specified elements with a numerical suffix to make them unique. This is necessary to avid confusions when resolving cross references between elements The elements that are renamed are: - JOIN - REFERENCE """ cpt = 1 for tag in ['REFERENCE', 'JOIN']: xpath = './/' + tag for ele in XPath.x_path(self._xml_block, xpath): ele.tag = tag + '_' + str(cpt) cpt += 1 @staticmethod def _name_match(name, expected): """ Return true if name matches expected whatever the namespace Parameters ---------- name (str): Name to compare expected (str): Expected name for the comparison Returns ------- bool """ if type(name).__name__ == 'cython_function_or_method': return False return name.endswith(expected) """ Properties """ @property def globals_block(self): """ GLOBALS getter """ return self._globals_block def get_globals_collections(self): """ Return a list of all GLOBALS/COLLECTION elements. These collections have no @dmroles but often @dmids. They have particular roles - Used by references (e.g., filter definition) - Used as head of the mapped model (e.g., [Cube instance]) Returns ------- list: GLOBALS/COLLECTION elements """ return XPath.x_path(self._globals_block, ".//COLLECTION") def get_models(self): """ Get the declared MODELs and their URLs. Returns ------- dict: MODELs and their URLs {'model': [url], ...} """ models_found = {} eset = XPath.x_path(self._xml_block, ".//" + Ele.MODEL) for ele in eset: models_found[ele.get("name")] = ele.get("url") return models_found def get_templates_tableref(self): """ Get all @tableref of the mapping. Returns ------- list: @tableref found in the mapping """ return self._templates_blocks.keys() def get_templates(self): """ Return a list of TEMPLATES @tableref. Returns ------- [string] tablerefs of all TEMPLATES elements """ templates_found = [] eset = XPath.x_path(self._xml_block, ".//" + Ele.TEMPLATES) for ele in eset: tableref = ele.get("tableref") if tableref is None: tableref = Constant.FIRST_TABLE templates_found.append(tableref) return templates_found def get_templates_block(self, tableref): """ Return the TEMPLATES mapping block of the table identified @tableref. If tableref is None or equals to Constant.FIRST_TABLE, return the first TEMPLATES. Parameters ---------- tableref (str): @tableref of the searched TEMPLATES Returns ------- XML element: matching TEMPLATES block or None """ # one table: name forced to DEFAULT or take the first if tableref is None or tableref == Constant.FIRST_TABLE: for _, tmpl in self._templates_blocks.items(): return tmpl if tableref not in self._templates_blocks: raise MivotError( "No TEMPLATES with tableref=" + tableref) return self._templates_blocks[tableref] """ INSTANCE """ def get_instance_dmtypes(self): """ Get @dmtypes of all mapped instances Returns ------- dict: @dmtypes of all mapped instances {GLOBALS: [], TEMPLATES: {}} """ dmtypes_found = {Ele.GLOBALS: [], Ele.TEMPLATES: {}} eset = XPath.x_path(self._globals_block, ".//" + Ele.INSTANCE) for ele in eset: dmtypes_found[Ele.GLOBALS].append(ele.get(Att.dmtype)) for tableref, block in self._templates_blocks.items(): dmtypes_found[Ele.TEMPLATES][tableref] = [] eset = XPath.x_path(block, ".//" + Ele.INSTANCE) for ele in eset: dmtypes_found[Ele.TEMPLATES][tableref].append(ele.get(Att.dmtype)) return dmtypes_found def get_instance_by_dmtype(self, dmtype_pattern): """ Get all the mapped instances that have a @dmtype containing dmtype_pattern Parameters ---------- dmtype_pattern (str): @dmtype looked for Returns ------- dict: @dmtypes of all mapped instances {'dmtype': [instance], ...} """ instance_found = {Ele.GLOBALS: [], Ele.TEMPLATES: {}} eset = XPath.x_path_contains(self._globals_block, ".//" + Ele.INSTANCE, Att.dmtype, dmtype_pattern ) instance_found[Ele.GLOBALS] = eset for (tableref, block) in self._templates_blocks.items(): instance_found[Ele.TEMPLATES][tableref] = XPath.x_path_contains(block, './/' + Ele.INSTANCE, Att.dmtype, dmtype_pattern) return instance_found """ GLOBALS INSTANCES """ def get_globals_instances(self): """ Return the list of all GLOBALS/INSTANCE elements. These collections have no @dmroles but often @dmids. They have particular roles when: - Used by references (e.g., filter definition) - Used as head of the mapped model (e.g., Cube instance) Returns ------- list: GLOBALS/INSTANCE elements """ return XPath.x_path(self._globals_block, "./" + Ele.INSTANCE) def get_globals_instance_dmids(self): """ Get a list of @dmid for GLOBALS/INSTANCE. Returns ------- list: @dmid of GLOBALS/INSTANCE """ dmids_found = [] eset = XPath.x_path(self._globals_block, ".//" + Ele.INSTANCE + "[@" + Att.dmid + "]") for ele in eset: dmids_found.append(ele.get(Att.dmid)) return dmids_found def get_globals_instance_by_dmid(self, dmid): """ Get the GLOBALS/INSTANCE with @dmid=dmid. Parameters ---------- dmid (str): @dmid of the searched GLOBALS/INSTANCE Returns ------- dict: `~xml.etree.ElementTree.Element` """ eset = XPath.x_path(self._globals_block, (".//" + Ele.INSTANCE + "[@" + Att.dmid + "='" + dmid + "']") ) for ele in eset: return ele return None def get_globals_instance_dmtypes(self): """ Get the list the @dmtype GLOBALS/INSTANCE. Returns ------- list: @dmtype of GLOBALS/INSTANCE """ dmtypes_found = [] for inst in self.get_globals_instances(): dmtypes_found.append(inst.get(Att.dmtype)) return dmtypes_found def get_templates_instance_by_dmid(self, tableref, dmid): """ Get the TEMPLATES/INSTANCE with @dmid=dmid and TEMPLATES@tableref=tableref. Parameters ---------- tableref (str): @tableref of the searched TEMPLATES dmid (str): @dmid of the searched TEMPLATES/INSTANCE Returns ------- dict: ~`xml.etree.ElementTree.Element` """ templates_block = self.get_templates_block(tableref) if templates_block is None: return None eset = XPath.x_path(templates_block, ".//" + Ele.INSTANCE + "[@" + Att.dmid + "='" + dmid + "']") for ele in eset: return ele return None def get_globals_instance_from_collection(self, sourceref, pk_value): """ Get the GLOBALS/COLLECTION[@dmid=sourceref]/INSTANCE/PRIMARY_KEY[@value='pk_value']. Parameters ---------- sourceref (str): @dmid of the searched GLOBALS/COLLECTION pk_value: (str): @value of the searched GLOBALS/COLLECTION/INSTANCE/PRIMARY_KEY Returns ------- dict: ~`xml.etree.ElementTree.Element` """ einst = XPath.x_path( self._globals_block, ".//" + Ele.COLLECTION + "[@" + Att.dmid + "='" + sourceref + "']/" + Ele.INSTANCE + "/" + Att.primarykey + "[@" + Att.value + "='" + pk_value + "']") parent_map = {c: p for p in self._globals_block.iter() for c in p} for inst in einst: return parent_map[inst] return None """ GLOBALS COLLECTION """ def get_globals_collection(self, dmid): """ Get the GLOBALS/COLLECTION with @dmid=dmid. Parameters ---------- dmid (str):@dmid of the searched GLOBALS/COLLECTION Returns ------- dict: `xml.etree.ElementTree.Element` """ eset = XPath.x_path(self._globals_block, ".//" + Ele.GLOBALS + "/" + Ele.COLLECTION + "[@" + Att.dmid + "='" + dmid + "']") for ele in eset: return ele return None def get_globals_collection_dmids(self): """ Get the list of all the @dmid of GLOBALS/COLLECTION. Returns ------- list: @dmid of GLOBALS/COLLECTION """ dmids_found = [] eset = XPath.x_path(self._globals_block, ".//" + Ele.COLLECTION + "[@" + Att.dmid + "]") for ele in eset: dmids_found.append(ele.get(Att.dmid)) return dmids_found def get_globals_collection_dmtypes(self): """ Get the list of the @dmtype of GLOBALS/COLLECTION/INSTANCE. Used for collections of static objects. Returns ------- list: @dmtype of GLOBALS/COLLECTION/INSTANCE """ eles = XPath.x_path(self._globals_block, ".//" + Ele.COLLECTION + "/" + Ele.INSTANCE) dmtypes_found = [] for inst in eles: dmtype = inst.get(Att.dmtype) if dmtype not in dmtypes_found: dmtypes_found.append(dmtype) return dmtypes_found def get_collection_item_by_primarykey(self, coll_dmid, key_value): """ Get the GLOBALS/COLLECTION/INSTANCE with COLLECTION@dmid=dmid and INSTANCE with a PRIMARY_key which @value matches key_value. An exception is raised if there is less or more than one element matching the criteria. The 2 parameters match the dynamic REFERENCE definition. Parameters ---------- coll_dmid (str): @dmid of the searched GLOBALS/COLLECTION key_value (str): @value of the searched GLOBALS/COLLECTION/INSTANCE/PRIMARY_KEY Returns ------- dict: ~`xml.etree.ElementTree.Element` Raises ------ MivotElementNotFound: If no element matches the criteria. MappingError: If more than one element matches the criteria. """ eset = XPath.x_path(self._globals_block, ".//" + Ele.COLLECTION + "[@" + Att.dmid + "='" + coll_dmid + "']/" + Ele.INSTANCE + "/" + Att.primarykey + "[@" + Att.value + "='" + key_value + "']") if len(eset) == 0: message = (f"{Ele.INSTANCE} with {Att.primarykey} = {key_value} in " f"{Ele.COLLECTION} {Att.dmid} {key_value} not found" ) raise MivotError(message) if len(eset) > 1: message = ( f"More than one {Ele.INSTANCE} with {Att.primarykey}" f" = {key_value} found in {Ele.COLLECTION} " f"{Att.dmid} {key_value}" ) raise MappingError(message) logging.debug(Ele.INSTANCE + " with " + Att.primarykey + "=%s found in " + Ele.COLLECTION + " " + Att.dmid + "=%s", key_value, coll_dmid) parent_map = {c: p for p in self._globals_block.iter() for c in p} return parent_map[eset[0]] astropy-pyvo-b70558c/pyvo/mivot/seekers/resource_seeker.py000066400000000000000000000074521510533647000241140ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Class that provides multiple getters on VOTable RESOURCE elements. """ from pyvo.mivot.utils.vocabulary import Constant from pyvo.utils.prototype import prototype_feature @prototype_feature('MIVOT') class ResourceSeeker: """ This class provides multiple getters on resource tables. Some methods are simple wrappers for external tools in order to have all the search functions on RESOURCE gathered in within a single namespace. """ def __init__(self, resource): """ Constructor Parameters ---------- resource (astropy.votable.Resource): The resource object to be queried. """ self._resource = resource def get_table_ids(self): """ Return the list of table ids. Only resource children are considered. The @ID is first searched and then the @name, and finally 'AnonymousTable' is taken. Returns ------- list of str: table ids. """ ids_found = [] for table in self._resource.tables: if table.ID is not None: ids_found.append(table.ID) elif table.name is not None: ids_found.append(table.name) else: ids_found.append(Constant.ANONYMOUS_TABLE) return ids_found def get_table(self, table_name_or_id): """ Return the table matching table_name first by ID and then by name. Parameters ---------- table_name_or_id (str): Name or id of the table to get. Returns ------- ~astropy.votable.table: table matching the table_name. """ if table_name_or_id == Constant.FIRST_TABLE: return self._resource.tables[0] for table in self._resource.tables: if (table_name_or_id is None or table.name == table_name_or_id or table.ID == table_name_or_id): return table return None def get_params(self): """ Return the VOTable PARAMS. Returns ------- ~astropy.votable.Resource.params: VOTable PARAMS. """ return self._resource.params def get_id_index_mapping(self, table_name): """ Build an index binding column number with field id. Parameters ---------- table_name (str): Name of the table. Returns ------- dict: dictionary mapping field id to column number: {name: {ID, ref, indx}...} """ column_index = {} table = self.get_table(table_name) indx = 0 for field in table.fields: field_desc = {} if field.ID is not None: field_desc["ID"] = field.ID if field.ref is not None: field_desc["ref"] = field.ref field_desc["indx"] = indx if "ID" not in field_desc: field_desc["ID"] = field.name column_index[field.name] = field_desc indx += 1 return column_index def get_id_unit_mapping(self, table_name): """ Build an index binding field unit with field id. Parameters ---------- table_name (str): Name of the table. Returns ------- dict: A dictionary mapping field id to field unit {ID1: unit, name1: unit, ref1: unit ...}. """ unit_index = {} table = self.get_table(table_name) for field in table.fields: unit = field.unit if field.ID is not None: unit_index[field.ID] = unit elif field.name is not None: unit_index[field.name] = unit elif field.ref is not None: unit_index[field.ref] = unit return unit_index astropy-pyvo-b70558c/pyvo/mivot/seekers/table_iterator.py000066400000000000000000000032301510533647000237150ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Iterator for table rows. """ from pyvo.utils.prototype import prototype_feature @prototype_feature('MIVOT') class TableIterator: """ Simple wrapper iterating over table rows. Some methods are simple wrappers for external tools in order to have all the search functions on TABLE gathered in within a single namespace. """ def __init__(self, name, data_table): """ Constructor of the TableIterator class. Parameters ---------- name (str): Table name (not really used). data_table (~numpy.ndarray): Numpy table returned by `~astropy.votable`. """ self.name = name self.data_table = data_table self.last_row = None self.iter = None # not used yet self.row_filter = None def get_next_row(self): """ Return the next Numpy row or None. The end of table exception usually returned by Numpy is trapped. """ # The iterator is set at the first iteration if self.iter is None: self.iter = iter(self.data_table) try: while True: row = next(self.iter) if row is not None: if self.row_filter is None or self.row_filter.row_match(row): self.last_row = row return row else: return None except StopIteration: return None def rewind(self): """ Set the pointer on the table-top, destroys the iterator actually. """ self.iter = None astropy-pyvo-b70558c/pyvo/mivot/tests/000077500000000000000000000000001510533647000200465ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/tests/__init__.py000066400000000000000000000076571510533647000221760ustar00rootroot00000000000000""" Utility class to check that generated XML match reference elements. Only used by the tests """ from pyvo.utils import activate_features # Activate MIVOT for all tests activate_features('MIVOT') try: from defusedxml import ElementTree as etree except ImportError: from xml.etree import ElementTree as etree from pyvo.mivot.utils.xml_utils import XmlUtils class XMLOutputChecker: """ This class is used to compare XML outputs, ignoring whitespace differences. """ def check_output(self, want, got): """ Compare two XML outputs, ignoring whitespace differences. Parameters ---------- want : str The expected XML output. got : str The actual XML output. Returns ------- bool True if the two XML outputs are equal, False otherwise. """ return (self._format_xml(want.strip()) == self._format_xml(got.strip())) def output_difference(self, want, got): """ Return a string describing the differences between two XML outputs. Parameters ---------- want : str The expected XML output. got : str The actual XML output. Returns ------- str A string describing the differences between the two XML outputs. """ return f"Diff:\n{self._format_xml(want)}\nvs.\n{self._format_xml(got)}" def _format_xml(self, xml_str): """ Format an XML string. Parameters ---------- xml_str : str The XML string to format. Returns ------- str The formatted XML string. """ return "\n".join(line.strip() for line in xml_str.splitlines()) @staticmethod def xmltree_to_file(xmltree, file_path): """ Write an XML tree to a file. Parameters ---------- xmltree : ~`xml.etree.ElementTree.Element` The XML tree to write to the file. file_path : str The path to the output file. """ with open(file_path, 'w') as output: output.write(XmlUtils.pretty_string(xmltree)) @staticmethod def xmltree_from_file(file_path): """ Parse an XML tree from a file. Parameters ---------- file_path : str The path to the XML file. Returns ------- ~`xml.etree.ElementTree.Element` The parsed XML tree. """ return etree.parse(file_path) @staticmethod def assertXmltreeEquals(xmltree1, xmltree2): """ Assert that two XML trees are equal. Parameters ---------- xmltree1 : ~`xml.etree.ElementTree.Element` The first XML tree for comparison. xmltree2 : ~`xml.etree.ElementTree.Element` The second XML tree for comparison. """ xml_str1 = etree.tostring(xmltree1).decode("utf-8") xml_str2 = etree.tostring(xmltree2).decode("utf-8") checker = XMLOutputChecker() assert checker.check_output(xml_str1, xml_str2, 0), f"XML trees differ:\n{xml_str1}\n---\n{xml_str2}" @staticmethod def assertXmltreeEqualsFile(xmltree1, xmltree2_file): """ Assert that an XML tree is equal to the content of a file. Parameters ---------- xmltree1 : ~`xml.etree.ElementTree.Element` The XML tree for comparison. xmltree2_file : str The path to the file containing the second XML tree. """ xmltree2 = XMLOutputChecker.xmltree_from_file(xmltree2_file).getroot() xml_str1 = etree.tostring(xmltree1).decode("utf-8").strip() xml_str2 = etree.tostring(xmltree2).decode("utf-8").strip() checker = XMLOutputChecker() assert checker.check_output(xml_str1, xml_str2), f"XML trees differ:\n{xml_str1}\n---\n{xml_str2}" astropy-pyvo-b70558c/pyvo/mivot/tests/conftest.py000066400000000000000000000012661510533647000222520ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins astropy-pyvo-b70558c/pyvo/mivot/tests/data/000077500000000000000000000000001510533647000207575ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/tests/data/filter_gaia_grp.xml000066400000000000000000000063731510533647000246300ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/filter_gaia_grvs.xml000066400000000000000000000063771510533647000250250ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/000077500000000000000000000000001510533647000227155ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/annotation_seeker.0.1.xml000066400000000000000000000122651510533647000274520ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/annotation_seeker.0.2.xml000066400000000000000000000040751510533647000274530ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/annotation_seeker.0.4.xml000066400000000000000000000012211510533647000274430ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/globals_models.json000066400000000000000000000010431510533647000265740ustar00rootroot00000000000000{"coords": "https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml", "cube": "https://volute.g-vo.org/svn/trunk/projects/dm/Cube/vo-dml/Cube-1.0.vo-dml.xml", "ds": "https://volute.g-vo.org/svn/trunk/projects/dm/DatasetMetadata/vo-dml/DatasetMetadata-1.0.vo-dml.xml", "ivoa": "https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml", "mango": "file:/Users/sao/Documents/IVOA/GitHub/ivoa-dm-examples/tmp/Mango-v1.0.vo-dml.xml", "meas": "https://www.ivoa.net/xml/Meas/20200908/Meas-v1.0.vo-dml.xml"}astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/instance_dmtypes.json000066400000000000000000000015171510533647000271650ustar00rootroot00000000000000{ "GLOBALS": [ "coords:TimeSys", "coords:TimeFrame", "coords:StdRefLocation", "coords:SpaceSys", "coords:SpaceFrame", "mango:coordinates.PhotometryCoordSys", "mango:coordinates.PhotFilter", "mango:coordinates.PhotometryCoordSys", "mango:coordinates.PhotFilter", "mango:coordinates.PhotometryCoordSys", "mango:coordinates.PhotFilter", "ds:experiment.ObsDataset", "ds:experiment.Target" ], "TEMPLATES": { "_PKTable": [ "cube:SparseCube" ], "Results": [ "cube:NDPoint", "cube:Observable", "meas:Time", "coords:MJD", "cube:Observable", "meas:GenericMeasure", "coords:PhysicalCoordinate", "cube:Observable", "meas:GenericMeasure", "coords:PhysicalCoordinate", "meas:Error", "meas:Symmetrical" ] } }astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/mango_object.xml000066400000000000000000000453751510533647000261040ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/multiple_templates.xml000066400000000000000000000004741510533647000273550ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/static_reference_resolved.xml000066400000000000000000000013471510533647000306540ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/test_header_extraction.xml000066400000000000000000000165731510533647000302020ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/test_mivot_frames.xml000066400000000000000000000026041510533647000271730ustar00rootroot00000000000000 Generated by pyvo.mivot.writer astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/test_mivot_photcal.xml000066400000000000000000000074441510533647000273570ustar00rootroot00000000000000 Generated by pyvo.mivot.writer astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/test_mivot_writer.json000066400000000000000000000020041510533647000273750ustar00rootroot00000000000000{ "dmtype": "mango:EpochPosition", "longitude": { "value": 52.2340018, "unit": "deg" }, "latitude": { "value": 59.8937333, "unit": "deg" }, "errors": { "dmtype": "mango:EpochPositionErrors", "dmrole": "mango:EpochPosition.errors", "position": { "dmtype": "mango:error.ErrorCorrMatrix", "dmrole": "mango:EpochPositionErrors.position", "sigma1": { "value": 6.0, "unit": "arcsec" }, "sigma2": { "value": 6.0, "unit": "arcsec" } } }, "spaceSys": { "dmtype": "coords:SpaceSys", "dmid": "_spacesys_icrs", "dmrole": "mango:EpochPosition.spaceSys", "frame": { "dmtype": "coords:SpaceFrame", "dmrole": "coords:PhysicalCoordSys.frame", "spaceRefFrame": { "value": "ICRS" }, "refPosition": { "dmtype": "coords:StdRefLocation", "dmrole": "coords:SpaceFrame.refPosition", "position": { "value": "BARYCENTER" } } } } }astropy-pyvo-b70558c/pyvo/mivot/tests/data/reference/test_mivot_writer.xml000066400000000000000000000033431510533647000272330ustar00rootroot00000000000000 Mivot writer unit test astropy-pyvo-b70558c/pyvo/mivot/tests/data/simbad-cone-mivot.xml000066400000000000000000000225021510533647000250170ustar00rootroot00000000000000 IVOID of the service specification Used access protocol and version Data publisher HTTP request URL Query execution date Publisher email address Successful query Distance (in degrees) between this object and the cone-search's target. Main identifier for an object Right ascension Declination Object type Coordinate error major axis Coordinate error minor axis Coordinate error angle Proper motion in RA Proper motion in DEC Proper motion error major axis Proper motion error minor axis Proper motion error angle Parallax Radial Velocity Angular size major axis Angular size minor axis Galaxy ellipse angle MK spectral type Morphological type
1.0409523503457668E-5 NAME Barnard's Star c 269.45207695861900 4.69336496657667 Planet 17 57 48.4984700683 +04 41 36.113879675 0.0262 0.0290 90 -801.551 10362.394 0.032 0.036 90 546.9759 1
Truncated result
astropy-pyvo-b70558c/pyvo/mivot/tests/data/static_reference.xml000066400000000000000000000011131510533647000250020ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.header_extraction.1.xml000066400000000000000000001115131510533647000263100ustar00rootroot00000000000000 VizieR Astronomical Server vizier.cds.unistra.fr Date: 2024-03-21T15:16:08 [V7.33.3] Explanations and Statistics of UCDs: See LINK below In case of problem, please report to: cds-question@unistra.fr IVOID of the protocol through which the data was retrieved Query execution date Full request URL (POST) Email or URL to contact publisher Software version Data centre that produced the VOTable Gaia DR3 Part 1. Main source (Gaia Collaboration, 2022) IVOID of underlying data collection First author or institution Bibcode of the dataset Year of the article publication Dataset landing page Dataset identifier that can be used for citation Date of first publication in the data centre Licence URI hand-made mapping Gaia DR3 source catalog (1811709771 sources) Right ascension (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account Declination (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account Unique source designation (unique across all Data Releases) (designation) Right ascension (ICRS) at Ep=2016.0 (ra) Declination (ICRS) at Ep=2016.0 (dec) Unique source identifier (unique within a particular Data Release) (source_id) Standard error of right ascension (ra_error) Standard error of declination (dec_error) ? Parallax (parallax) ? Standard error of parallax (parallax_error) ? Total proper motion (pm) ? Proper motion in right ascension direction, pmRA*cosDE (pmra) ? Standard error of proper motion in right ascension direction (pmra_error) ? Proper motion in declination direction (pmdec) ? Standard error of proper motion in declination direction (pmdec_error) [-1/1] Correlation between right ascension and declination (ra_dec_corr) [-1/1]? Correlation between right ascension and parallax (ra_parallax_corr) [-1/1]? Correlation between right ascension and proper motion in right ascension (ra_pmra_corr) [-1/1]? Correlation between right ascension and proper motion in declination (ra_pmdec_corr) [-1/1]? Correlation between declination and parallax (dec_parallax_corr) [-1/1]? Correlation between declination and proper motion in right ascension (dec_pmra_corr) [-1/1]? Correlation between declination and proper motion in declination (dec_pmdec_corr) [-1/1]? Correlation between parallax and proper motion in right ascension (parallax_pmra_corr) [-1/1]? Correlation between parallax and proper motion in declination (parallax_pmdec_corr) [-1/1]? Correlation between proper motion in right ascension and proper motion in declination (pmra_pmdec_corr) ? Radial velocity (radial_velocity) ? Radial velocity error (radial_velocity_error) ? Spectral line broadening parameter (vbroad) ? Integrated Grvs magnitude (grvs_mag) [0/1] Flag indicating the availability of additional information in the QSO candidates table (in_qso_candidates) [0/1] Flag indicating the availability of additional information in the galaxy candidates table (in_galaxy_candidates) [0/7] Flag indicating the availability of additional information in the various Non-Single Star tables (non_single_star) [0/1] Flag indicating the availability of mean BP/RP spectrum in continuous representation for this source (has_xp_continuous) [0/1] Flag indicating the availability of mean BP/RP spectrum in sampled form for this source (has_xp_sampled) [0/1] Flag indicating the availability of mean RVS spectrum for this source (has_rvs) [0/1] Flag indicating the availability of epoch photometry for this source (has_epoch_photometry) [0/1] Flag indicating the availability of epoch radial velocity for this source (has_epoch_rv) [0/1] Flag indicating the availability of GSP-Phot MCMC samples for this source (has_mcmc_gspphot) [0/1] Flag indicating the availability of MSC MCMC samples for this source (has_mcmc_msc) [0/1] Flag indicating that the source is present in the Gaia Andromeda Photometric Survey (GAPS) (in_andromeda_survey) ? Effective temperature from GSP-Phot Aeneas best library using BP/RP spectra (teff_gspphot) ? Surface gravity from GSP-Phot Aeneas best library using BP/RP spectra (logg_gspphot) ? Iron abundance from GSP-Phot Aeneas best library using BP/RP spectra (mh_gspphot) ? Distance from GSP-Phot Aeneas best library using BP/RP spectra (distance_gspphot) ? Monochromatic extinction A_0 at 547.7nm from GSP-Phot Aeneas best library using BP/RP spectra (azero_gspphot) ? HIP cross-id number, van Leeuwen, Cat. I/311 (hip_original_ext_source_id) ? PS1 cross-id name, Chambers et al., Cat. II/349 (ps1_original_ext_source_id) ? SDSS name, Albareti et al., 2017ApJS..233...25A (sdss13_ext_source_id) ? SkyMapperDR2 cross-id name, Onken et al., 2019PASA...36...33O (skym2_original_ext_source_id) Tycho-2 cross-id name, Hog et al., Cat. I/259 (tyc2_original_ext_source_id) URAT1 name, Zacharias et al., Cat. I/329 (urat1_original_ext_source_id) ALLWISE cross-id name, Cutri et al., Cat. II/328 (allwise_original_ext_source_id) ? APASS9 identification, Henden et al., Cat. II/336 (apass9_original_ext_source_id) GSC2.3 cross-id name, Lasker et al., Cat. I/305 (gsc23_original_ext_source_id) RAVE DR5 cross-id name, Kunder et al., Cat. III/279 (rave5_original_ext_source_id) 2MASS cross-id name, Cutri et al., Cat. II/246 (twomass_original_ext_source_id) RAVE DR6 cross-id name, Steinmetz et al., Cat. III/283 (rave6_original_ext_source_id) Barycentric right ascension (ICRS) at Ep=2000.0 (added by CDS) (ra2000) Barycentric declination (ICRS) at Ep=2000.0 (added by CDS) (dec2000)
307.7911702004070 +20.4311044212247 Gaia DR3 1816065554621487360 307.79115807079 +20.43108005561 1816065554621487360 0.0511 0.0477 0.4319 0.0691 6.049 -2.557 0.064 -5.482 0.067 0.1212 0.1337 -0.4109 -0.0072 0.0069 -0.0085 -0.2983 -0.2603 -0.0251 0.2688 0 0 0 1 0 0 0 0 1 1 0 4696.2 4.1928 -0.7942 2428.5532 0.0380 132513077911637877 URAT1-553569515 N2PD017546 20310987+2025519 307.79117020041 20.43110442122
307.7958370989668 +20.4325479549513 Gaia DR3 1816065558924798592 307.79582391363 +20.43253083107 1816065558924798592 0.3203 0.2898 -0.5553 0.4195 4.751 -2.780 0.395 -3.853 0.426 0.3740 0.1981 -0.3253 -0.0486 0.0632 -0.0608 -0.1303 -0.2989 -0.0876 0.4716 0 0 0 0 0 0 0 0 0 0 0 132513077957999584 N2PD017483 307.79583709897 20.43254795495
307.7973736604703 +20.4355882715396 Gaia DR3 1816065558920966528 307.79737366047 +20.43558827154 1816065558920966528 6.0643 2.1063 0.7514 0 0 0 0 0 0 0 0 0 0 0 132523077973693273 307.79737366047 20.43558827154
307.7885745651585 +20.4301331822264 Gaia DR3 1816065558924801792 307.78856137441 +20.43011713943 1816065558924801792 0.1196 0.1127 0.0910 0.1671 4.557 -2.781 0.153 -3.610 0.165 0.1448 0.1580 -0.3400 0.0468 0.0149 0.0299 -0.2083 -0.2178 0.0615 0.3266 0 0 0 0 0 0 0 0 1 0 0 5071.5 4.6975 -0.7109 2923.6248 0.0090 132513077885716709 URAT1-553569502 N2PD017266 307.78857456516 20.43013318223
307.7897816527446 +20.4315208963567 Gaia DR3 1816065554622254464 307.78977228741 +20.43150439876 1816065554622254464 0.3688 0.3801 -0.6243 0.5059 4.205 -1.975 0.444 -3.712 0.498 0.2553 0.0889 -0.3890 -0.0365 -0.0773 -0.0307 -0.2998 -0.2452 0.0025 0.3249 0 0 0 0 0 0 0 0 0 0 0 132513077897708356 N2PD096440 307.78978165274 20.43152089636
matching records value="truncated result (maxtup=5)"
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.header_extraction.2.xml000066400000000000000000000121141510533647000263060ustar00rootroot00000000000000 VizieR Astronomical Server cds:8082 Date: 2025-04-19T10:08:58 [V7.5] Explanations and Statistics of UCDs: See LINK below In case of problem, please report to: cds-question@unistra.fr IVOID of the protocol through which the data was retrieved Query execution date Full request URL Email or URL to contact publisher Software version Data centre that produced the VOTable Constraint -out.meta=dhuM 1400-MHz Sky Survey, Maps Covering Dec -5 to +82 (Condon+ 1985-86) IVOID of underlying data collection First author or institution Article or Data origin sources Editor name (article) Year of the article publication Dataset landing page Date of first publication in the data centre Licence URI List of the map centers Distance from center (000.0-08.5)[FK4/B1950] Record number assigned by the VizieR team. Should Not be used for identification. [datatype=int] Observation date Right Ascension (B1950) of map center Declination (B1950) of map center Name of FITS file in "maps" subdirectory Associated Data web page Downbload the FITS file Right ascension (ICRS) (computed by VizieR, not part of the original data) Declination (ICRS) (computed by VizieR, not part of the original data)
0.0011983-10-06000.0-08.5f001.fitfitsFITS000.6405-08.2216
matching records
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.header_extraction.xml000066400000000000000000003212711510533647000261550ustar00rootroot00000000000000 VizieR Astronomical Server cdsarc.cds.unistra.fr Date: 2025-04-07T12:06:32 [V7.4.6] Explanations and Statistics of UCDs: See LINK below In case of problem, please report to: cds-question@unistra.fr IVOID of the protocol through which the data was retrieved Query execution date Full request URL Email or URL to contact publisher Software version Data centre that produced the VOTable Constraint -out.meta=dhuM URAT1 Catalog (Zacharias+ 2015) IVOID of underlying data collection First author or institution Article or Data origin sources Editor name (article) Year of the article publication Dataset landing page Date of first publication in the data centre Licence URI URAT1 catalog Distance from center (052.2670800+59.9402700)[ICRS], at Epoch of catalog (Epoch) URAT1 recommended identifier (ZZZ-NNNNNN) (13) [datatype=char] Right ascension on ICRS, at "Epoch" (1) Declination on ICRS, at "Epoch" (1) Position error per coordinate, from scatter (2) Position error per coordinate, from model (2) (nst) Total number of sets the star is in (3) (nsu) Number of sets used for mean position (3) (epoc) Mean URAT observation epoch (1) ?(mmag) mean URAT model fit magnitude (4) ?(sigp) URAT photometry error (5) (nsm) Number of sets used for URAT magnitude (3) (ref) largest reference star flag (6) (nit) Total number of images (observations) (niu) Number of images used for mean position (ngt) Total number of 1st order grating observations (ngu) Number of 1st order grating positions used ?(pmr) Proper motion RA*cosDec (from 2MASS) (7) ?(pmd) Proper motion in Declination (7) ?(pme) Proper motion error per coordinate (8) [1/11] Match flag URAT with 2MASS (9) [1/11] Match flag URAT with APASS (9) [-] "-" if there is no match with GSC2.4 (14) ?(id2) unique 2MASS star identification number ?(jmag) 2MASS J-band magnitude ?(ejmag) Error on Jmag [0,58]? J-band quality-confusion flag (10) ?(hmag) 2MASS H-band magnitude ?(ehmag) Error on H-band magnitude (10) [0,58]? H-band quality-confusion flag (10) ?(kmag) 2MASS Ks-band magnitude ?(ekmag) Error on Ks-band magnitude (10) [0,58]? Ks-band quality-confusion flag (10) (ann) Number of APASS observation nights (12) (ano) Number of APASS observations (12) ?(abm) APASS B-band magnitude (11) ?(ebm) Error on Bmag ?(avm) APASS V-band magnitude ?(evm) Error on Vmag ?(agm) APASS g-band magnitude ?(egm) Error on gmag ?(arm) APASS r-band magnitude ?(erm) Error on rmag ?(aim) APASS i-band magnitude ?(eim) Error on imag
0.049402750-146023052.2340018+59.89373339613132013.41815.3400.0131397474001.5-12.35.91575880868113.7130.028513.3400.034513.1010.03451417.6320.20416.1640.00116.6900.00115.7500.001
0.031797750-146062052.2504219+59.90958812221662013.53717.7660.01660222200-2.66.06.011175880876815.7940.0842515.0010.0922515.0280.1472700
0.033292750-146084052.2609042+59.90712222124662013.46718.1540.08560191800-9.8-4.46.011175880875015.2470.0882514.9950.1244614.3650.1142600
0.036592750-146098052.2686898+59.90368643123662013.37618.1430.022601413001111-1-1-100
0.045475750-146164052.3107322+59.90040423417662013.53517.8460.061602222000.7-5.25.811149436904815.6100.061514.9460.078514.6000.086500
0.043982750-146213052.3322281+59.91079898362332013.78018.3460.22930330050.4-17.37.311149436899515.6380.069514.8860.083514.6750.100500
0.048035750-146232052.3403269+59.90928616613132013.49813.6790.0121318080005.2-3.95.51349436900712.5220.023512.1890.023512.0860.026531214.1730.03314.3470.01813.8750.03313.5780.069
0.046409750-146245052.3456364+59.91568781212772013.43417.1220.02970414100-1.01.05.611149436896614.2090.030513.2700.028513.0290.030500
0.020939750-146093052.2669561+59.91933082510432013.3528.6020.02040126001111-1-1-100
0.020870750-146099052.2690619+59.919423921014322013.8468.5270.0373064001111--1-1-100
0.014874750-146123052.2879544+59.92969366329662013.37217.4890.08760141300-1.5-4.16.411149436889514.560215.0470.0852514.6530.0872500
0.032417750-146132052.2943525+59.91087443424662013.72118.1790.07660151500-2.2-1.65.811149436899315.3250.0482514.5760.0672514.1950.063500
0.040484750-146134052.2959558+59.90246144727552013.61118.4510.04250101000-4.412.26.011149436904216.1700.0972515.4450.116615.653200
0.027692750-146136052.2969233+59.91696333630552013.37418.3500.08250151500-2.14.46.011149436895915.7720.0702515.0150.0872514.8030.1042500
0.021100750-146138052.2987561+59.9263650151135222013.5860022002.9-26.411.271149436891616.7820.1652715.8950.1752715.4330.1772700
0.035679750-146155052.3057528+59.91031362821662013.47017.9400.06960181800-3.44.15.811149436900115.5340.0552515.0400.092514.7000.0922500
0.025295750-146156052.3059601+59.92413365436552013.15818.4640.08650770065.57.86.511149436892616.4050.1192616.0050.2042715.5640.2012700
0.026930750-146163052.3105422+59.9244261176176112013.77700110037.430.513.111149436892516.3200.1092515.4860.1232615.0380.1182600
0.039970750-146183052.3189791+59.90991974433662013.69018.5120.0556099002.44.66.011149436900315.7200.0642514.8900.075514.7470.090500
0.019092750-146152052.3047506+59.937367211151442013.12618.4030.107407600-5.8-16.28.011149436883416.8100.1932715.9920.1992715.6400.2142700
0.020072750-146153052.3053716+59.93436036445442013.29618.2910.23730870030.6-20.36.871149436885916.2120.0992515.3390.1202615.1790.1482600
0.021880750-146159052.3091815+59.93444502941552013.63118.0170.2123012900-0.8-6.36.011149436885816.4700.1372615.4060.1162615.3390.1602700
0.025664750-146175052.3168392+59.94639501619662013.43917.8440.03360252500-6.64.55.711149436875715.5010.0552514.8110.0612514.5160.0752500
0.025290750-146176052.3172297+59.93735395431552013.66018.4860.095501414002.3-11.56.211149436883516.4750.1292615.7860.1622715.7490.2372800
0.029091750-146182052.3184422+59.92670116643552013.73418.7280.0825010900-10.414.26.611149436891515.8980.0812515.1750.1002514.6220.0872500
0.028688750-146189052.3212203+59.9496400198118222013.35218.51110320013.4-2.512.311149436873416.2890.1042515.8450.1812715.5220.1832700
0.027290750-146191052.3213516+59.94267362623662013.46317.9790.03860232300-1.24.95.811149436877816.1190.0882515.6750.1542615.1130.1272600
0.031386750-146206052.3296428+59.93855443045442013.69019.3490.26930760011.434.96.011149436882615.9280.0822515.4110.1282614.9860.1182600
0.032830750-146209052.3306025+59.93219751210882013.47116.9380.029804343004.66.65.611149436887514.6430.035513.9530.043513.7480.053500
0.034542750-146221052.3358481+59.93772141316662013.53617.9290.049602323006.1-2.85.611149436883315.4490.0482514.8520.081514.8870.1082500
0.035569750-146224052.3371944+59.94592179911112013.41416.3690.0211105858004.48.25.611149436875913.9310.027513.1730.031512.9020.030500
0.036228750-146228052.3391817+59.94313394430442013.75418.7790.116408800-0.525.16.011149436877516.5060.1242615.9400.1832715.4360.1692700
0.036259750-146229052.3394681+59.94031311616772013.53217.7390.057703029007.13.25.6111132435807015.4990.0742514.9780.0854514.7760.1162600
0.038744750-146237052.3423429+59.931354210043222013.71118.6480.069204300-7.9-27.17.31113620183816.3830.1082516.137215.707200
0.039405750-146243052.3454028+59.94398115462332013.55618.8580.100304400-29.7-18.76.811149436877017.1470.2222816.2100.2282815.8680.2532800
0.042539750-146247052.3487833+59.92868867272112013.70319.272102200-1.0-14.77.311149436890416.4450.1202615.4980.129615.5500.1872700
0.041644750-146250052.3500932+59.938012214910102013.41816.6200.0161005555000.64.15.611149436882914.1080.029513.2930.033513.0550.033500
0.042891750-146255052.3523933+59.94397035551442013.67118.9550.029405500-8.5-23.46.911175880899016.8180.1752715.7220.149716.916200
0.047900750-145933052.1905831+59.91154862519662013.48017.9600.078601717009.3-22.16.011175880877916.1220.094515.6790.140715.3150.192700
0.046752750-145959052.2012094+59.90716331923662013.48918.1350.025601717009.7-10.66.011175880875116.0030.1064515.1160.0934515.1720.1784700
0.041776750-145979052.2127270+59.908595815093322013.90118.9670.24430320020.3-19.410.311175880875816.6440.1452715.921216.740200
0.026421750-145988052.2177525+59.93092256613132013.46214.2240.0151318383003.1-1.25.811175880887211.3320.022510.2770.02859.9640.022500
0.041472750-145990052.2200053+59.90616253125662013.58217.8250.04960191700-4.4-5.96.111175880872715.9120.1022515.3140.1652715.0150.1532600
0.042576750-145996052.2219553+59.90419696613132013.42914.6260.0121317876000.4-5.35.91175880872112.1620.023511.3200.028511.0500.02352416.8450.08915.0270.00114.5100.085
0.022141750-146002052.2247150+59.93396062227662013.53818.8140.16540222100-11.321.95.811149436886516.9120.2112716.3010.2672815.7390.2392800
0.023557750-146018052.2316656+59.92477501212772013.44517.2860.026704242008.1-9.75.911175880883515.1380.0552514.5630.0622514.2520.0782500
0.026209750-146029052.2360836+59.91915815585332013.44618.5330.3762033001111-1-1-100
0.023308750-146033052.2379494+59.92209817613132013.47214.9230.0121357776001.8-7.65.811175880882313.2700.026512.9040.033512.7350.033500
0.030926750-146041052.2399071+59.91250332818662013.53917.3990.02860232300-5.2-8.86.071175880878315.6170.0862514.9710.1022514.8550.1332600
0.032283750-146043052.2403975+59.9108858134107222013.39718.9640.064202200-12.17.010.011149436899416.7450.1622715.915215.971200
0.030627750-146045052.2415433+59.91244506169222013.33517.4450.0242033001111-1-1-100
0.031026750-146049052.2452325+59.9112394190190112013.77718.776101100-67.7-117.314.711175880878016.4330.1352615.8730.1792715.5140.2362800
0.027683750-146051052.2461478+59.91465112722662013.46318.1590.04560232200-8.5-7.66.111114664206015.8360.0792515.4720.1132615.322200
0.020736750-146056052.2481158+59.92183949981332013.20919.0480.0692055001111-1-1-100
0.049510750-145875052.1683236+59.93824784140442013.65318.5490.04040550019.45.96.5711-75880892616.1270.107515.2560.107614.8710.128600
0.048590750-145887052.1702414+59.937453612571222012.99113.9210.0342033001111-1-1-100
0.048229750-145889052.1709828+59.94330221112662013.52617.4160.043603535009.46.15.911175880898315.7380.076515.6770.144715.3930.205800
0.048752750-145890052.1713750+59.93143501211882013.50317.1100.028804141005.9-4.95.9111127328005015.4230.053514.7920.072514.4040.097500
0.046061750-145913052.1803169+59.92503941114662013.45617.6750.044603232003.39.65.911175880883615.8140.079515.6610.135716.738200
0.044014750-145914052.1811616+59.94952036613132013.46114.8180.0111377773008.59.05.81175880902813.4860.028513.0860.037512.9520.039552516.6380.00015.4420.03615.8980.06215.0660.05014.8450.058
0.042768750-145917052.1823739+59.94567004030662013.64918.5730.019601111001111-1-1-100
0.043524750-145922052.1850706+59.925911411097222013.38718.2890.16520220013.6-56.49.611175880883916.1440.1092515.7710.157715.066200
0.042216750-145925052.1868361+59.95320064742442013.66419.1010.224406600-8.7-9.46.211149436811116.6100.1292616.058215.7840.1972800
0.040142750-145927052.1869408+59.94029612020662013.48917.9940.042602020005.95.06.0111128056707915.7000.1042515.1680.1144614.530200
0.041106750-145929052.1880718+59.95140642526662013.58018.3540.12660171700-7.84.66.111175880903716.2940.1252615.425216.062200
0.038411750-145935052.1906267+59.94327311614662013.47017.5460.0576038380012.24.55.911175880898015.6030.0682515.1470.108514.9370.1432600
0.039597750-145938052.1918033+59.95238221310992013.40716.8870.023904747000.19.35.911175880904315.3200.050515.0510.092514.6950.113600
0.043102750-145939052.1918572+59.91936196613132013.43413.9030.01113180800019.3-6.85.91175880881112.6430.024512.2590.030512.0790.027552215.4810.02314.4680.03814.9560.05214.1260.05413.8760.081
0.038533750-145940052.1921275+59.94896391719662013.44518.0560.02460242400-0.43.16.011143241923415.6010.0712514.6970.069514.5280.119600
0.036671750-145951052.1958144+59.94868582523442013.52916.8940.1534010100010.2-0.86.0311123921700915.8160.0842515.2730.1062614.235200
0.036190750-145953052.1972169+59.949513311910102013.38916.0940.0431005858000.80.45.931175880902914.2660.0492513.8910.0492513.702200
0.037715750-145955052.1991867+59.92398144238552013.48018.5110.052508800-11.30.16.511175880883016.6850.1562716.0970.1922815.2840.2052700
0.039428750-145956052.2004306+59.96126331913662013.37817.5890.03060353500-4.54.66.011175880909815.7080.0692515.1040.095515.1690.1712700
0.035618750-145957052.2005153+59.92775929157442013.62518.8360.1524055001111-1-1-100
0.036617750-145960052.2016304+59.95659642733552013.73818.6470.02350101000-1.6-4.86.111175880906416.4540.1312616.182215.441200
0.032441750-145963052.2027467+59.944026110911112013.40816.4410.0211105656004.8-3.15.911175880898914.7830.0312514.1920.048513.8990.060500
0.032197750-145964052.2031066+59.93715333889222013.79517.7980.4002043001111-1-1-100
0.034542750-145965052.2041036+59.95435834636662013.54718.4400.08660111100-0.9-8.56.511175880905616.0880.0992515.6450.1622714.9450.1352600
0.031211750-145968052.2057602+59.945826710712122013.43315.8130.0161256564006.8-0.15.91175880899713.2980.024512.4990.030512.2300.03151216.8000.18515.7270.001
0.030966750-145969052.2062669+59.93471535137662013.50118.4110.07160121000-16.210.06.611175880889616.4600.1392616.101215.822200
0.025791750-145987052.2168179+59.93468473442662013.59819.5070.0872087001.49.36.111149436800116.9400.1842716.2200.2212815.9370.2512800
0.023239750-145993052.2206928+59.93989422722662013.55317.7560.082602321001111-1-1-100
0.020864750-146004052.2255104+59.938965698132222013.42218.2360.1092073001111-1-1-100
0.015445750-146030052.2362992+59.94118508282332013.05517.5600.3482033001111-1-1-100
0.016676750-146052052.2466444+59.92710723737662013.49218.2990.070601512002.5-4.46.411175880884515.9880.0982515.2900.1152615.2650.2052700
0.014724750-146055052.2480839+59.9290353251410102013.32916.8400.1781003434001111-1-1-100
0.017463750-146063052.2508794+59.92480813023662013.41817.8950.0656022200015.02.96.211175880883415.8580.0782515.3500.1132615.405200
0.011856750-146070052.2539083+59.930420012911112013.45115.5830.06911034340010.9-12.05.911175880886913.217212.899212.9790.0272500
0.018206750-146085052.2617168+59.92226315232332013.51118.2560.0903088004.9-23.26.611175880882515.9330.0942515.1370.1042514.9190.1422600
0.017484750-146087052.2641631+59.95769284428552013.31018.1900.1675012110025.9-20.76.511141378138116.5340.1882715.235215.2000.1882700
0.020866750-146089052.2647878+59.919435314218222013.7128.5660.4472033001111-1-1-100
0.020918750-146090052.2656714+59.91936392514332013.9648.5290.0103044001111-1-1-100
0.000082750-146094052.2672150+59.940316189772013.5985.5310.4937939373737-7.00.35.511112507147412.7120.23082.6030.19272.4130.236800
0.008436750-146100052.2690918+59.9318944169662013.55114.4930.108601717001111--1-1-100
0.016946750-146102052.2694767+59.92336676613132013.47512.0220.016136747400-1.2-5.55.511131867191311.0800.102510.7210.018510.6380.023552213.0250.00612.2290.02912.6440.04612.0370.04211.8670.035
0.011438750-146119052.2840414+59.93261332813662013.23816.1770.08665212100-3.14.95.811149436887313.029212.158211.9410.0262500
0.024388750-146121052.2867748+59.96257563555552013.73219.1020.248408700-17.4-14.26.611175880910716.5860.1422715.7210.1352715.898200
0.029304750-146122052.2874027+59.96775081414772013.39217.5750.02170353500-1.5-0.85.611149436863214.9700.0382514.0580.045513.7780.043500
0.032470750-146126052.2895014+59.97073817713132013.45115.5720.015135717100-3.95.75.611149436860612.9320.023512.0940.024511.7890.026500
0.035009750-146131052.2930547+59.97277443025552013.66318.1540.04850131300-2.411.25.811149436859415.7450.0622515.0730.087514.7730.0972500
0.025769750-146135052.2968034+59.96130641314772013.54117.4570.03870353500-4.715.75.611149436866515.7050.0692515.1280.0922514.9360.1042500
0.018310750-146144052.3010061+59.9470925171171112013.01800110016.3108.213.511149436875416.6550.1722716.3810.3062815.683200
0.039074750-146146052.3013697+59.975370899992013.44016.5480.01690555500-5.1-0.35.611149436857414.5970.034513.8930.038513.6350.044500
0.024343750-146148052.3014987+59.95745972834662013.50418.6090.12760121100-12.98.95.9111124102617716.2980.1072515.6770.1572715.1290.1292600
0.040880750-146154052.3057421+59.97627784032552013.60818.6060.092501211005.60.56.011149436857216.4900.1232615.9500.1862715.720200
0.037715750-146158052.3090078+59.97160287364442013.57617.5740.1534044002.30.07.211149436860016.4490.1272615.5800.1422614.590200
0.038783750-146161052.3094356+59.9727425151010102013.40417.1300.018100474700-7.3-0.35.611149436859314.8620.0482514.1040.0562513.813200
0.024688750-146162052.3105325+59.95192974244552013.71518.9870.1505088001111-1-1-100
0.030470750-146169052.3139508+59.95970119855332013.71817.8630.0463055001111-1-1-100
0.030474750-146170052.3152114+59.95891752119662013.51417.7100.04460212000-12.724.95.711149436868415.651215.3290.1262614.747200
0.026707750-146171052.3155167+59.95144222415662013.48717.8170.024602929005.412.35.711149436872715.7350.0592515.1100.0842514.9050.1052500
0.040398750-146172052.3165128+59.97219942219662013.56717.6770.060602423000.3-4.55.711149436859515.8780.0882515.2390.1052514.8480.1162600
0.039734750-146178052.3177702+59.97084312218662013.52917.7200.036602323004.10.85.711149436860415.6140.0602515.1710.0962514.6660.0932500
0.035145750-146181052.3183368+59.96427896638552013.65818.9340.14050660013.3-18.76.511149436865017.0160.1892716.0040.2022715.5610.1922700
0.030971750-146192052.3214386+59.95503925940442013.66518.8590.140408800-38.7-8.56.411149436870416.5270.1282615.808215.318200
0.031681750-146196052.3242203+59.95386426788322013.52218.45510430010.131.07.71149436871216.8340.1632716.109216.36621217.5710.49717.4000.001
0.034861750-146200052.3259394+59.95888581310772013.43717.0250.02770444400-0.75.05.611149436868315.1080.0452514.6900.061514.3880.073500
0.038755750-146214052.3325711+59.96092141315662013.63117.6690.03760282800-4.9-4.05.511149436866615.7060.0652515.4140.111615.0170.1192600
0.037790750-145973052.2101897+59.9651028128128112013.94017.431101100-141.7-195.710.111149436864217.639216.408215.3540.1622700
0.036356750-145977052.2118531+59.963871732952222013.72418.2462033001111-1-1-100
0.037767750-145978052.2124958+59.9663347127127112014.02518.290101100135.3100.210.011141378138217.639216.408215.3540.1622700
0.039744750-145999052.2231803+59.97338364871222013.71918.7881033001111-1-1-100
0.021407750-146003052.2249117+59.94376002223662013.32818.4980.114602524005.7-11.16.111175880898716.2330.1282615.1530.0992515.0160.1452600
0.035145750-146007052.2259831+59.968761112911112013.46316.6470.019110565600-0.10.95.911175880914214.5590.038513.8740.048513.5210.047500
0.020072750-146017052.2308514+59.94885251412772013.31116.9150.057793736003.2-6.06.011145188442715.0570.0492514.6720.0682514.6180.1052500
0.040894750-146028052.2356869+59.97802364132552013.59118.7140.023401010009.5-59.46.411175880921016.1640.1162615.5710.1484615.0510.1572700
0.019714750-146040052.2396922+59.95443063365222013.12318.8810.0342044001111-1-1-100
0.018988750-146046052.2440811+59.95536568613132013.48915.1660.0211357574002.81.55.811175880906013.7540.0242513.3090.0382513.1340.0372500
0.033557750-146047052.2444797+59.97186223125662013.68918.1990.03460151400-3.9-0.16.111175880916616.2300.1082615.7390.1602715.2840.1952700
0.021717750-146048052.2449492+59.95894611620662013.45418.0520.045602220009.812.96.011175880908016.2050.1102615.2610.1092615.2620.1922700
0.028204750-146053052.2467506+59.96657281310882013.49316.8650.02280454500-7.6-8.75.911141235956514.6790.0352514.0720.046513.7390.0582500
0.020219750-146057052.2481875+59.95813923022662013.37417.1490.053602020003.0-5.56.211175880907615.6600.0712515.0690.0892514.9240.1362600
0.021223750-146058052.2487220+59.95939831213992013.44916.8980.022903939006.2-0.45.911175880908515.0570.0612514.4420.0732514.1730.0782500
0.036035750-146068052.2522172+59.97552862628662013.45618.2780.09360121200-13.5-17.96.211175880919016.3100.1212615.5630.1492615.146200
0.043256750-146072052.2552314+59.98311723340442013.50019.1850.090407700-4.343.06.11113611798616.7260.1492615.8190.164717.251200
0.048555750-146073052.2552783+59.98846421717662013.40818.0410.086602020003.0-8.16.011175880924915.7970.075515.1340.095515.0150.1472600
0.033418750-146074052.2553883+59.97317176613132013.44414.1990.013131878700-1.70.25.91175880917711.5100.023510.5870.028510.3000.02252416.3720.10514.8750.00113.9220.126
0.043717750-146081052.2604243+59.98386036434662013.57118.6740.1686010100014.97.26.811185023945416.5020.1502615.8850.185715.5700.2562800
0.032100750-146083052.2608539+59.97221817575112013.70318.74910220043.218.97.511149436859616.7380.1722715.350215.920200
0.049321750-146091052.2657139+59.98958613248332013.67818.6510.04630770016.49.46.111149436850716.3310.1102615.7790.169715.7880.2422800
0.046491750-146105052.2716081+59.98670534343112013.95714.54610110046.1-146.86.111149436852216.6420.1442615.5820.133615.5720.1952700
0.049329750-146106052.2721311+59.98953441821662013.53718.1940.049601818009.1-3.56.011175880925415.8630.085514.9950.093515.2630.187700
0.032008750-146112052.2786911+59.9717461111010102013.39416.8320.022100525200-0.1-15.75.911175880916514.6580.0402513.9660.046513.7580.053500
0.039682750-146114052.2798206+59.97943569910102013.40616.5290.0141005353001.7-4.85.911175880921214.6400.041514.2210.0654514.0330.073500
0.043201750-146118052.2824372+59.98278141516662013.44917.4560.057603232002.3-4.95.911175880922415.4400.056514.5480.059514.4000.088500
0.044158750-146139052.2990233+59.98143083432552013.38118.5730.032509900-15.1-8.76.311175880921616.2170.1072615.254215.527200
0.048261750-146145052.3010575+59.98543444035442013.53018.4130.150407700-0.21.76.411175880923816.0850.095515.2230.103515.0250.156700
0.048163750-146151052.3045872+59.98462284736442013.59318.4520.086408800-6.1-2.96.511175880923216.5320.1322616.1400.208815.399200
0.049987750-145880052.1690739+59.94972695795222013.72919.0040.135202200-2.617.17.51149436809516.8490.1492616.663216.31921318.1560.001
0.049581750-145881052.1691200+59.94741615728662013.44518.2950.183609900-34.35.36.311149436807716.4910.1132615.9310.1562715.4540.159700
0.049017750-145885052.1698575+59.94588061613772013.35317.3700.031703434008.23.16.011175880899815.3090.0512514.6050.061514.3200.080500
0.047321750-145896052.1741953+59.94894031215662013.48817.4520.021603232007.03.05.911124987388815.4650.056515.0340.081514.7090.105600
0.048312750-145911052.1788664+59.95983338431332013.75218.7290.02530550013.36.96.711149436867216.7040.1562716.664215.5850.2082700
0.044746750-145926052.1869258+59.96004811918662013.60817.9390.044602525006.3-22.05.911175880909316.0410.0992515.3070.1032615.2910.1952700
0.046378750-145941052.1924456+59.96773831622662013.33018.0740.02560171700-8.0-13.46.011175880913515.8630.0822515.6100.1442615.0840.1632700
0.043355750-145946052.1941594+59.96364561010992013.40416.8250.027904747007.8-12.25.911175880911614.9360.0512514.3230.0622514.098200
0.042651750-145947052.1943544+59.96247223640442013.64917.4350.06540770028.3-0.76.411175880910616.2590.1092615.7170.1562714.895200
0.049788750-145949052.1947844+59.97445721313662013.39117.5940.030603030005.0-1.05.911175880918315.5410.061514.8910.076514.7220.1102600
0.042324750-145972052.2100333+59.97150362425552013.62718.3660.022501414003.613.76.011175880916416.1210.1022515.5330.134615.2310.1712700
0.049180750-145974052.2106543+59.98052921715662013.44117.7420.038602626000.1-11.26.011175880921415.5870.067514.9620.080514.6350.107600
0.044311750-145980052.2130069+59.97535081215662013.52817.6560.032602929000.4-0.55.911175880918715.0000.050514.0540.053513.7160.052500
0.045071750-145986052.2162192+59.97745972724662013.64518.3000.0716014140016.3-5.86.011175880920116.3110.1202615.7140.1574715.2810.1754700
0.042351750-146005052.2255808+59.977175399992013.43416.8130.02190505000-0.9-5.45.911175880919814.3690.0344513.5290.038513.3270.0444500
0.047728750-146010052.2278934+59.98378063632552013.53718.4900.088509900-12.9-30.66.311143874706116.3700.1162616.1090.219814.798200
0.044271750-146011052.2288256+59.98018538613132013.45015.1870.0121377979000.5-3.35.91175880921313.5460.028513.0070.031512.8600.031551217.3250.00816.0130.16216.3790.00815.4650.00815.0060.148
0.048283750-146022052.2335583+59.98554331412772013.40317.2950.019703939006.4-5.75.911175880923915.1030.051514.4200.057514.2170.083500
0.043104750-146240052.3436564+59.95995533433552013.69518.5470.02150101000-11.13.35.911149436867116.1500.0882515.1160.089514.7370.087500
0.048970750-146249052.3500103+59.96622816613132013.50812.5200.011131888800-1.92.85.51164559043011.2160.021511.0050.018510.8680.023572413.8220.02612.9920.03513.4190.05312.7050.04412.4080.043
0.049442750-146260052.3547244+59.96304008659422013.72418.5720.385404200-8.3-23.27.311149436865716.7490.1712716.2580.2612815.404200
0.047279750-146261052.3548542+59.95768589696112013.70318.7111011006.48.18.5111131865095716.6690.1412615.8070.161715.970200
0.046354750-146207052.3297766+59.97437892420662013.50517.7210.031602222000.58.25.711149436858316.1100.0842515.3270.111615.4650.1792700
0.049237750-146216052.3331683+59.97673476050552013.59618.7470.046507700-29.216.26.611149436857016.6280.1412615.6710.151615.278200
0.043484750-146217052.3335950+59.96823006613132013.50311.9330.0121379090221.712.95.51149436862910.6920.0214510.2940.0184510.1940.023572413.4540.01512.4380.02712.9260.04412.1110.04911.8340.048
0.047142750-146242052.3441606+59.96734119910102013.40516.3040.021100636300-4.67.35.611149436863414.4570.037514.0130.044513.8020.052500
matching records
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.instance_multiple.xml000066400000000000000000000304541510533647000262040ustar00rootroot00000000000000 Automatically annotated by XTAPDB the flux of the energy band number 1 of the ep camera in sc the flux of the energy band number 2 of the ep camera in sc the flux of the energy band number 3 of the ep camera in sc
0.0 0.1 0.2
1.0 2.1 3.2
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.mango_annoter.xml000066400000000000000000000732701510533647000253170ustar00rootroot00000000000000 VizieR Astronomical Server vizier.cds.unistra.fr Date: 2024-03-21T15:16:08 [V7.33.3] Explanations and Statistics of UCDs: See LINK below In case of problem, please report to: cds-question@unistra.fr IVOID of the protocol through which the data was retrieved Query execution date Full request URL (POST) Email or URL to contact publisher Software version Data centre that produced the VOTable Gaia DR3 Part 1. Main source (Gaia Collaboration, 2022) IVOID of underlying data collection First author or institution Bibcode of the dataset Year of the article publication Dataset landing page Dataset identifier that can be used for citation Date of first publication in the data centre Licence URI Gaia DR3 source catalog (1811709771 sources) Right ascension (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account Declination (FK5, Equinox=J2000.0) at Epoch=J2000, proper motions taken into account Unique source designation (unique across all Data Releases) (designation) Right ascension (ICRS) at Ep=2016.0 (ra) Declination (ICRS) at Ep=2016.0 (dec) Unique source identifier (unique within a particular Data Release) (source_id) Standard error of right ascension (ra_error) Standard error of declination (dec_error) ? Parallax (parallax) ? Standard error of parallax (parallax_error) ? Total proper motion (pm) ? Proper motion in right ascension direction, pmRA*cosDE (pmra) ? Standard error of proper motion in right ascension direction (pmra_error) ? Proper motion in declination direction (pmdec) ? Standard error of proper motion in declination direction (pmdec_error) [-1/1] Correlation between right ascension and declination (ra_dec_corr) [-1/1]? Correlation between right ascension and parallax (ra_parallax_corr) [-1/1]? Correlation between right ascension and proper motion in right ascension (ra_pmra_corr) [-1/1]? Correlation between right ascension and proper motion in declination (ra_pmdec_corr) [-1/1]? Correlation between declination and parallax (dec_parallax_corr) [-1/1]? Correlation between declination and proper motion in right ascension (dec_pmra_corr) [-1/1]? Correlation between declination and proper motion in declination (dec_pmdec_corr) [-1/1]? Correlation between parallax and proper motion in right ascension (parallax_pmra_corr) [-1/1]? Correlation between parallax and proper motion in declination (parallax_pmdec_corr) [-1/1]? Correlation between proper motion in right ascension and proper motion in declination (pmra_pmdec_corr) ? Radial velocity (radial_velocity) ? Radial velocity error (radial_velocity_error) ? Spectral line broadening parameter (vbroad) ? Integrated Grvs magnitude (grvs_mag) [0/1] Flag indicating the availability of additional information in the QSO candidates table (in_qso_candidates) [0/1] Flag indicating the availability of additional information in the galaxy candidates table (in_galaxy_candidates) [0/7] Flag indicating the availability of additional information in the various Non-Single Star tables (non_single_star) [0/1] Flag indicating the availability of mean BP/RP spectrum in continuous representation for this source (has_xp_continuous) [0/1] Flag indicating the availability of mean BP/RP spectrum in sampled form for this source (has_xp_sampled) [0/1] Flag indicating the availability of mean RVS spectrum for this source (has_rvs) [0/1] Flag indicating the availability of epoch photometry for this source (has_epoch_photometry) [0/1] Flag indicating the availability of epoch radial velocity for this source (has_epoch_rv) [0/1] Flag indicating the availability of GSP-Phot MCMC samples for this source (has_mcmc_gspphot) [0/1] Flag indicating the availability of MSC MCMC samples for this source (has_mcmc_msc) [0/1] Flag indicating that the source is present in the Gaia Andromeda Photometric Survey (GAPS) (in_andromeda_survey) ? Effective temperature from GSP-Phot Aeneas best library using BP/RP spectra (teff_gspphot) ? Surface gravity from GSP-Phot Aeneas best library using BP/RP spectra (logg_gspphot) ? Iron abundance from GSP-Phot Aeneas best library using BP/RP spectra (mh_gspphot) ? Distance from GSP-Phot Aeneas best library using BP/RP spectra (distance_gspphot) ? Monochromatic extinction A_0 at 547.7nm from GSP-Phot Aeneas best library using BP/RP spectra (azero_gspphot) ? HIP cross-id number, van Leeuwen, Cat. I/311 (hip_original_ext_source_id) ? PS1 cross-id name, Chambers et al., Cat. II/349 (ps1_original_ext_source_id) ? SDSS name, Albareti et al., 2017ApJS..233...25A (sdss13_ext_source_id) ? SkyMapperDR2 cross-id name, Onken et al., 2019PASA...36...33O (skym2_original_ext_source_id) Tycho-2 cross-id name, Hog et al., Cat. I/259 (tyc2_original_ext_source_id) URAT1 name, Zacharias et al., Cat. I/329 (urat1_original_ext_source_id) ALLWISE cross-id name, Cutri et al., Cat. II/328 (allwise_original_ext_source_id) ? APASS9 identification, Henden et al., Cat. II/336 (apass9_original_ext_source_id) GSC2.3 cross-id name, Lasker et al., Cat. I/305 (gsc23_original_ext_source_id) RAVE DR5 cross-id name, Kunder et al., Cat. III/279 (rave5_original_ext_source_id) 2MASS cross-id name, Cutri et al., Cat. II/246 (twomass_original_ext_source_id) RAVE DR6 cross-id name, Steinmetz et al., Cat. III/283 (rave6_original_ext_source_id) Barycentric right ascension (ICRS) at Ep=2000.0 (added by CDS) (ra2000) Barycentric declination (ICRS) at Ep=2000.0 (added by CDS) (dec2000)
307.7911702004070 +20.4311044212247 Gaia DR3 1816065554621487360 307.79115807079 +20.43108005561 1816065554621487360 0.0511 0.0477 0.4319 0.0691 6.049 -2.557 0.064 -5.482 0.067 0.1212 0.1337 -0.4109 -0.0072 0.0069 -0.0085 -0.2983 -0.2603 -0.0251 0.2688 0 0 0 1 0 0 0 0 1 1 0 4696.2 4.1928 -0.7942 2428.5532 0.0380 132513077911637877 URAT1-553569515 N2PD017546 20310987+2025519 307.79117020041 20.43110442122
307.7958370989668 +20.4325479549513 Gaia DR3 1816065558924798592 307.79582391363 +20.43253083107 1816065558924798592 0.3203 0.2898 -0.5553 0.4195 4.751 -2.780 0.395 -3.853 0.426 0.3740 0.1981 -0.3253 -0.0486 0.0632 -0.0608 -0.1303 -0.2989 -0.0876 0.4716 0 0 0 0 0 0 0 0 0 0 0 132513077957999584 N2PD017483 307.79583709897 20.43254795495
307.7973736604703 +20.4355882715396 Gaia DR3 1816065558920966528 307.79737366047 +20.43558827154 1816065558920966528 6.0643 2.1063 0.7514 0 0 0 0 0 0 0 0 0 0 0 132523077973693273 307.79737366047 20.43558827154
307.7885745651585 +20.4301331822264 Gaia DR3 1816065558924801792 307.78856137441 +20.43011713943 1816065558924801792 0.1196 0.1127 0.0910 0.1671 4.557 -2.781 0.153 -3.610 0.165 0.1448 0.1580 -0.3400 0.0468 0.0149 0.0299 -0.2083 -0.2178 0.0615 0.3266 0 0 0 0 0 0 0 0 1 0 0 5071.5 4.6975 -0.7109 2923.6248 0.0090 132513077885716709 URAT1-553569502 N2PD017266 307.78857456516 20.43013318223
307.7897816527446 +20.4315208963567 Gaia DR3 1816065554622254464 307.78977228741 +20.43150439876 1816065554622254464 0.3688 0.3801 -0.6243 0.5059 4.205 -1.975 0.444 -3.712 0.498 0.2553 0.0889 -0.3890 -0.0365 -0.0773 -0.0307 -0.2998 -0.2452 0.0025 0.3249 0 0 0 0 0 0 0 0 0 0 0 132513077897708356 N2PD096440 307.78978165274 20.43152089636
matching records value="truncated result (maxtup=5)"
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.mivot_viewer.first_instance.xml000066400000000000000000000021421510533647000302070ustar00rootroot00000000000000 Prototype for covariance errors hand-made mapping
SourceName
OtherSourceName
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.mivot_viewer.no_mivot.xml000066400000000000000000000272241510533647000270360ustar00rootroot00000000000000 URAT1 Catalog (Zacharias+ 2015) IVOID of underlying data collection URAT1 catalog Distance from center (052.2670800+59.9402700)[ICRS], at Epoch of catalog (Epoch) URAT1 recommended identifier (ZZZ-NNNNNN) (13) [datatype=char] Right ascension on ICRS, at "Epoch" (1) Declination on ICRS, at "Epoch" (1) Position error per coordinate, from scatter (2) Position error per coordinate, from model (2) (nst) Total number of sets the star is in (3) (nsu) Number of sets used for mean position (3) (epoc) Mean URAT observation epoch (1) ?(mmag) mean URAT model fit magnitude (4) ?(sigp) URAT photometry error (5) (nsm) Number of sets used for URAT magnitude (3) (ref) largest reference star flag (6) (nit) Total number of images (observations) (niu) Number of images used for mean position (ngt) Total number of 1st order grating observations (ngu) Number of 1st order grating positions used ?(pmr) Proper motion RA*cosDec (from 2MASS) (7) ?(pmd) Proper motion in Declination (7) ?(pme) Proper motion error per coordinate (8) [1/11] Match flag URAT with 2MASS (9) [1/11] Match flag URAT with APASS (9) [-] "-" if there is no match with GSC2.4 (14) ?(id2) unique 2MASS star identification number ?(jmag) 2MASS J-band magnitude ?(ejmag) Error on Jmag [0,58]? J-band quality-confusion flag (10) ?(hmag) 2MASS H-band magnitude ?(ehmag) Error on H-band magnitude (10) [0,58]? H-band quality-confusion flag (10) ?(kmag) 2MASS Ks-band magnitude ?(ekmag) Error on Ks-band magnitude (10) [0,58]? Ks-band quality-confusion flag (10) (ann) Number of APASS observation nights (12) (ano) Number of APASS observations (12) ?(abm) APASS B-band magnitude (11) ?(ebm) Error on Bmag ?(avm) APASS V-band magnitude ?(evm) Error on Vmag ?(agm) APASS g-band magnitude ?(egm) Error on gmag ?(arm) APASS r-band magnitude ?(erm) Error on rmag ?(aim) APASS i-band magnitude ?(eim) Error on imag
0.049402 750-146023 52.2340018 59.8937333 9 6 13 13 2013.418 15.340 0.013 13 9 74 74 0 0 1.5 -12.3 5.9 1 5 758808681 13.713 0.028 5 13.340 0.034 5 13.101 0.034 5 1 4 17.632 0.204 16.164 0.001 16.690 0.001 15.750 0.001
0.049402 750-146023 58.2340018 69.8937333 9 6 13 13 2013.418 15.340 0.013 13 9 74 74 0 0 1.5 -12.3 5.9 1 5 758808681 13.713 0.028 5 13.340 0.034 5 13.101 0.034 5 1 4 17.632 0.204 16.164 0.001 16.690 0.001 15.750 0.001
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.mivot_viewer.xml000066400000000000000000000431651510533647000252070ustar00rootroot00000000000000 Epoch photometry. This table contains light curve data points. Each entry is a photometric flux-time pair for a given object, band and time. It follows the (evolving) VO convention for time series as much as possible. At the time of writing, a VO recommendation has not yet been released. Source ID Photometric Band
5813181197970338560 G
5813181197970338560 BP
5813181197970338560 RP
Source Id. A unique single numerical identifier of the source obtained from GaiaSource. Transit unique identifier. For a given object, a transit comprises the different Gaia observations (SM, AF, BP, RP and RVS) obtained for each focal plane crossing. Photometric band. Values: G (per-transit combined SM-AF flux), BP (blue photometer integrated flux) and RP (red photometer integrated flux). Different times are defined for each band. For G, it is the field-of-view transit averaged observation time. For BP and RP, it is the observation time of the BP CCD transit. The units are Barycentric JD (in TCB) in days -2,455,197.5, computed as follows. First the observation time is converted from On-board Mission Time (OBMT) into Julian date in TCB (Temps Coordonnee Barycentrique). Next a correction is applied for the light-travel time to the Solar system barycentre, resulting in Barycentric Julian Date (BJD). Finally, an offset of 2,455,197.5 days is applied (corresponding to a reference time $T_0$ at 2010-01-01T00:00:00) to have a conveniently small numerical value. Although the centroiding time accuracy of the individual CCD observations is (much) below 1~ms (e.g. in BP and RP), the G band observation time is averaged over typically 9 CCD observations taken in a time range of about 44sec. Vega magnitude, computed from the flux applying the zero-point defined in ExtPhotZeroPoint. Band flux value for the transit. For G band, it is a combination of individual SM-AF CCD fluxes. For BP and RP bands, it is an integrated CCD flux. Flux error. If the flux has been rejected or is unavailable, this error will be set to null. Band flux divided by its error. If the flux has been rejected or is unavailable, this field will be set to null. Rejected by DPAC photometry processing. Rejected by DPAC variability processing (or variability analysis). All Gaia data processed by the Data Processing and Analysis Consortium comes tagged with a solution identifier. This is a numeric field attached to each table row that can be used to unequivocally identify the version of all the subsystems that where used in the generation of the data as well as the input data used. It is mainly for internal DPAC use but is included in the published data releases to enable end users to examine the provenance of processed data products. To decode a given solution ID visit https://gaia.esac.esa.int/decoder/solnDecoder.jsp
5813181197970338560 17091923342681275 G 1705.9437360200984 15.216574774452164 15442.456273273616 44.151258712309364 349.76254 F F 4097 369295551293819386
5813181197970338560 17096015648964756 G 1706.0177100217386 14.767336693604877 23356.70699319823 33.53035403015752 696.584 F F 4194817 369295551293819386
5813181197970338560 19103616164443503 G 1742.3215763366886 15.278342999137502 14588.447956240941 15.054069973748831 969.07 F F 1 369295551293819386
astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.photcal_SDSS.xml000066400000000000000000000061131510533647000247460ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/tests/data/test.photcal_error.xml000066400000000000000000000004501510533647000253210ustar00rootroot00000000000000 PhotCalID not found: zaada astropy-pyvo-b70558c/pyvo/mivot/tests/test_annotation_seeker.py000066400000000000000000000125221510533647000251710ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Test for mivot.seekers.annotation_seeker.py """ import pytest try: from defusedxml import ElementTree as etree except ImportError: from xml.etree import ElementTree as etree from astropy.utils.data import get_pkg_data_filename from pyvo.mivot.seekers.annotation_seeker import AnnotationSeeker from pyvo.mivot.utils.dict_utils import DictUtils from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer import MivotViewer from pyvo.mivot.tests import XMLOutputChecker @pytest.fixture def a_seeker(): m_viewer = MivotViewer( get_pkg_data_filename("data/test.mivot_viewer.xml"), tableref="Results" ) return AnnotationSeeker(m_viewer._mapping_block) @pytest.fixture def a_multiple_seeker(): m_viewer = MivotViewer( get_pkg_data_filename("data/test.instance_multiple.xml"), tableref="Results") return AnnotationSeeker(m_viewer._mapping_block) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_multiple_templates(): """ Try to create an AnnotationSeeker with a mapping_block containing multiple TEMPLATES. """ mapping_block = XMLOutputChecker.xmltree_from_file( get_pkg_data_filename("data/reference/multiple_templates.xml")) with pytest.raises(Exception, match="TEMPLATES without tableref must be unique"): AnnotationSeeker(mapping_block.getroot()) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_all_reverts(a_seeker): # Checks the GLOBALS block given by the AnnotationSeeker # by comparing it to the content of the file test.0.1.xml XMLOutputChecker.assertXmltreeEqualsFile(a_seeker.globals_block, get_pkg_data_filename("data/reference/annotation_seeker.0.1.xml")) # Checks the TEMPLATES block given by the AnnotationSeeker # by comparing it to the content of the file test.0.2.xml XMLOutputChecker.assertXmltreeEqualsFile(a_seeker.get_templates_block("Results"), get_pkg_data_filename("data/reference/annotation_seeker.0.2.xml")) # Checks the list of all the tableref found by the AnnotationSeeker assert list(a_seeker.get_templates_tableref()) == ['_PKTable', 'Results'] # a_seeker should have only 2 COLLECTIONS in GLOBALS: _CoordinateSystems and _Datasets assert len(a_seeker.get_globals_collections()) == 2 # a_seeker should have only 1 INSTANCES in GLOBALS: _tg1 assert len(a_seeker.get_globals_instances()) == 1 assert a_seeker.get_globals_instance_dmtypes() == ['ds:experiment.Target'] assert (a_seeker.get_globals_instance_dmids() == ['_timesys', '_spacesys1', '_photsys_G', '_photsys_RP', '_photsys_BP', '_ds1', '_tg1']) assert a_seeker.get_globals_collection_dmids() == ['_CoordinateSystems', '_Datasets'] selection = a_seeker.get_instance_by_dmtype("coords") assert len(selection["GLOBALS"]) == 5 for ele in selection["GLOBALS"]: assert ele.get("dmtype").startswith("coords") assert len(selection["TEMPLATES"]["_PKTable"]) == 0 assert len(selection["TEMPLATES"]["Results"]) == 3 for _, table_sel in selection["TEMPLATES"].items(): for ele in table_sel: assert ele.get("dmtype").startswith("coords") with pytest.raises(Exception, match="INSTANCE with PRIMARY_KEY = wrong_key_value " "in COLLECTION dmid wrong_key_value not found"): a_seeker.get_collection_item_by_primarykey("_Datasets", "wrong_key_value") pksel = a_seeker.get_collection_item_by_primarykey("_Datasets", "5813181197970338560") XMLOutputChecker.assertXmltreeEqualsFile(pksel, get_pkg_data_filename("data/reference/annotation_seeker.0.4.xml")) with (pytest.raises(Exception, match="More than one INSTANCE with " "PRIMARY_KEY = G found in COLLECTION dmid G")): double_key = etree.fromstring("""""") a_seeker.get_collection_item_by_primarykey("_CoordinateSystems", "G" ).append(double_key) a_seeker.get_collection_item_by_primarykey("_CoordinateSystems", "G") with pytest.raises(Exception, match="INSTANCE with PRIMARY_KEY = wrong_key " "in COLLECTION dmid wrong_key not found"): a_seeker.get_collection_item_by_primarykey("_CoordinateSystems", "wrong_key") assert a_seeker.get_instance_dmtypes() == DictUtils.read_dict_from_file( get_pkg_data_filename("data/reference/instance_dmtypes.json")) assert a_seeker.get_templates_instance_by_dmid("Results", "wrong_dmid") is None assert a_seeker.get_templates_instance_by_dmid("Results", "_ts_data").get("dmtype") == "cube:NDPoint" assert a_seeker.get_globals_instance_from_collection( "_CoordinateSystems", "ICRS").get("dmtype") == "coords:SpaceSys" assert a_seeker.get_globals_instance_from_collection("wrong_dmid", "ICRS") is None @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_multiple_seeker(a_multiple_seeker): assert (a_multiple_seeker.get_instance_dmtypes()["TEMPLATES"] == {"Results": ["mango:Brightness", "mango:Brightness", "mango:Brightness"]}) astropy-pyvo-b70558c/pyvo/mivot/tests/test_header_mapper.py000066400000000000000000000066231510533647000242620ustar00rootroot00000000000000""" This module contains test cases for validating the generation of mapping dictionaries that allow to extract Mivot instances from INFO located in the VOTable header """ import pytest from astropy.io.votable import parse from astropy.utils.data import get_pkg_data_filename from pyvo.utils import activate_features from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.writer.header_mapper import HeaderMapper # Enable MIVOT-specific features in the pyvo library activate_features("MIVOT") data_origin_mapping = { "service_protocol": "ivo://ivoa.net/std/ConeSearch/v1.03", "request_date": "2025-04-07T12:06:32", "request": ("https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch" "/I/329/urat1?RA=52.26708&DEC=59.94027&SR=0.05"), "contact": "cds-question@unistra.fr", "server_software": "7.4.6", "publisher": "CDS", "dataOrigin": [{ "ivoid": "ivo://cds.vizier/i/329", "creators": ["Zacharias N."], "cites": "bibcode:2015AJ....150..101Z", "original_date": "2015", "reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/329", "rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html", "articles": [{ "editor": "Astronomical Journal (AAS)" }] }] } coosys_mappings = [{"spaceRefFrame": "ICRS", "epoch": "2345"}] timesys_mappings = [{"timescale": "TCB"}] @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_all(): """ checks that the mapping dictionaries extracte from the VOTable match the expected ones """ votable = parse(get_pkg_data_filename("data/test.header_extraction.xml")) builder = HeaderMapper(votable) assert builder.extract_origin_mapping() == data_origin_mapping assert builder.extract_coosys_mapping() == coosys_mappings assert builder.extract_timesys_mapping() == timesys_mappings @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_field_extraction(): """ checks that the epochPosition mapping dictionaries extracted from the VOTable columns match the expected ones """ votable = parse(get_pkg_data_filename("data/test.header_extraction.1.xml")) builder = HeaderMapper(votable) mapping, error_mapping = builder.extract_epochposition_mapping() assert mapping == {"longitude": "RA_ICRS", "latitude": "DE_ICRS", "parallax": "Plx", "pmLongitude": "pmRA", "pmLatitude": "pmDE", "radialVelocity": "RV"} assert error_mapping == {"position": {"class": "PErrorSym2D", "sigma1": "e_RA_ICRS", "sigma2": "e_DE_ICRS"}, "parallax": {"class": "PErrorSym1D", "sigma": "e_Plx"}, "properMotion": {"class": "PErrorSym2D", "sigma1": "e_pmRA", "sigma2": "e_pmDE"}, "radialVelocity": {"class": "PErrorSym1D", "sigma": "e_RV"}} votable = parse(get_pkg_data_filename("data/test.header_extraction.2.xml")) builder = HeaderMapper(votable) mapping, error_mapping = builder.extract_epochposition_mapping() assert mapping == {"obsDate": {"dateTime": "ObsDate", "representation": "iso"}, "longitude": "RAB1950", "latitude": "DEB1950" } astropy-pyvo-b70558c/pyvo/mivot/tests/test_mango_annoter.py000066400000000000000000000204461510533647000243140ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This module contains test cases for validating the functionality of MivotInstance, MivotAnnotations, and related components in the pyvo.mivot package. These tests ensure that the classes behave as expected, including error handling and XML generation for data models. """ import os import pytest from unittest.mock import patch from astropy.io.votable import parse from astropy.utils.data import get_pkg_data_contents, get_pkg_data_filename from pyvo.utils import activate_features from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.utils.xml_utils import XmlUtils from pyvo.mivot.writer.instances_from_models import InstancesFromModels # Enable MIVOT-specific features in the pyvo library activate_features("MIVOT") # File paths for test data votable_path = os.path.realpath( os.path.join(__file__, "..", "data", "test.mivot_viewer.no_mivot.xml") ) data_path = os.path.realpath( os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") ) @pytest.fixture() def mocked_fps_grvs(mocker): def callback(request, context): return bytes(get_pkg_data_contents('data/filter_gaia_grvs.xml'), "utf8") with mocker.register_uri( 'GET', 'http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID=GAIA/GAIA3.Grvs/AB', content=callback ) as matcher: yield matcher @pytest.fixture() def mocked_fps_grp(mocker): def callback(request, context): return bytes(get_pkg_data_contents('data/filter_gaia_grp.xml'), "utf8") with mocker.register_uri( 'GET', 'http://svo2.cab.inta-csic.es/svo/theory/fps/fpsmivot.php?PhotCalID=GAIA/GAIA3.Grp/AB', content=callback ) as matcher: yield matcher @pytest.fixture def mocked_fps(): with patch('requests.get') as mock_get: yield mock_get @pytest.mark.filterwarnings("ignore:root:::") def add_color(builder): filter_ids = {"high": "GAIA/GAIA3.Grp/AB", "low": "GAIA/GAIA3.Grvs/AB"} mapping = {"value": 8.76, "definition": "ColorIndex", "error": {"class": "PErrorAsym1D", "low": 1, "high": 3} } semantics = {"description": "very nice color", "uri": "vocabulary#term", "label": "term"} builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics) @pytest.mark.filterwarnings("ignore:root:::") def add_photometry(builder): photcal_id = "GAIA/GAIA3.Grvs/AB" mapping = {"value": "GRVSmag", "error": {"class": "PErrorAsym1D", "low": 1, "high": 3} } semantics = {"description": "Grvs magnitude", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude", "label": "magnitude"} builder.add_mango_brightness(photcal_id=photcal_id, mapping=mapping, semantics=semantics) def add_epoch_positon(builder): frames = {"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None}, "timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}} mapping = {"longitude": "_RAJ2000", "latitude": "_DEJ2000", "pmLongitude": "pmRA", "pmLatitude": "pmDE", "parallax": "Plx", "radialVelocity": "RV", "obsDate": {"representation": "mjd", "dateTime": 579887.6}, "correlations": {"isCovariance": True, "longitudeLatitude": "RADEcor", "latitudePmLongitude": "DEpmRAcor", "latitudePmLatitude": "DEpmDEcor", "longitudePmLongitude": "RApmRAcor", "longitudePmLatitude": "RApmDEcor", "longitudeParallax": "RAPlxcor", "latitudeParallax": "DEPlxcor", "pmLongitudeParallax": "PlxpmRAcor", "pmLatitudeParallax": "PlxpmDEcor", }, "errors": {"position": {"class": "PErrorSym2D", "sigma1": "e_RA_ICRS", "sigma2": "e_DE_ICRS"}, "properMotion": {"class": "PErrorSym2D", "sigma1": "e_pmRA", "sigma2": "e_pmDE"}, "parallax": {"class": "PErrorSym1D", "sigma": "e_Plx"}, "radialVelocity": {"class": "PErrorSym1D", "sigma": "e_RV"} } } semantics = {"description": "6 parameters position", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location", "label": "Astronomical location"} builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics) @pytest.mark.usefixtures("mocked_fps_grvs", "mocked_fps_grp") @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_all_properties(): votable_filename = get_pkg_data_filename("data/test.mango_annoter.xml") votable = parse(votable_filename) builder = InstancesFromModels(votable, dmid="DR3Name") # pylint: disable=E501 builder.add_query_origin( {"service_protocol": "ASU", "request_date": "2024-03-21T15:16:08", "request": ("https://vizier.cds.unistra.fr/viz-bin/votable?-oc.form=dec&-out.max=5&" "-out.add=_r&-out.add=_RAJ,_DEJ&-sort=_r&-c.eq=J2000&-c.r= 2&" "-c.u=arcmin&-c.geom=r&-source=I/355/gaiadr3&-order=I&" "-out.orig=standard&-out=DR3Name&-out=RA_ICRS&-out=DE_ICRS&" "-out=Source&-out=e_RA_ICRS&-out=e_DE_ICRS&-out=Plx&" "-out=e_Plx&-out=PM&-out=pmRA&-out=e_pmRA&-out=pmDE&" "-out=e_pmDE&-out=RADEcor&-out=RAPlxcor&-out=RApmRAcor&" "-out=RApmDEcor&-out=DEPlxcor&-out=DEpmRAcor&-out=DEpmDEcor&" "-out=PlxpmRAcor&-out=PlxpmDEcor&-out=pmRApmDEcor&-out=RV&" "-out=e_RV&-out=Vbroad&-out=GRVSmag&-out=QSO&-out=Gal&-out=NSS&" "-out=XPcont&-out=XPsamp&-out=RVS&-out=EpochPh&-out=EpochRV&" "-out=MCMCGSP&-out=MCMCMSC&-out=And&-out=Teff&-out=logg&" "-out=[Fe/H]&-out=Dist&-out=A0&-out=HIP&-out=PS1&-out=SDSS13&" "-out=SKYM2&-out=TYC2&-out=URAT1&-out=AllWISE&-out=APASS9&" "-out=GSC23&-out=RAVE5&-out=2MASS&-out=RAVE6&-out=RAJ2000&" "-out=DEJ2000&"), "contact": "cds-question@unistra.fr", "server_software": "7.33.3", "publisher": "CDS", "dataOrigin": [{"ivoid": "ivo://cds.vizier/i/355", "creators": ["Gaia collaboration"], "cites": "bibcode:2022yCat.1355....0G, ""doi:10.26093/cds/vizier.1355", "original_date": "2022", "reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/355", "rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html", "articles": [{"identifier": "doi:10.1051/0004-6361/202039657e]", "editor": "A&A"}] }] }) add_color(builder) add_photometry(builder) add_epoch_positon(builder) builder.pack_into_votable(schema_check=False) assert XmlUtils.strip_xml(builder._annotation.mivot_block) == ( XmlUtils.strip_xml(get_pkg_data_contents("data/reference/mango_object.xml")) ) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_extraction_from_votable_header(): """ test that the automatic mapping extraction is well connected with the annoter """ votable_filename = get_pkg_data_filename("data/test.header_extraction.1.xml") votable = parse(votable_filename) builder = InstancesFromModels(votable, dmid="URAT1") builder.extract_frames() builder.extract_data_origin() epoch_position_mapping = builder.extract_epoch_position_parameters() builder.add_mango_epoch_position(**epoch_position_mapping) builder.pack_into_votable(schema_check=True) assert XmlUtils.strip_xml(builder._annotation.mivot_block) == ( XmlUtils.strip_xml(get_pkg_data_contents("data/reference/test_header_extraction.xml")) ) astropy-pyvo-b70558c/pyvo/mivot/tests/test_mivot_instance.py000066400000000000000000000111371510533647000245040ustar00rootroot00000000000000''' Test the class generation from a dict.x Created on 19 févr. 2024 @author: michel ''' import pytest from astropy.table import Table from pyvo.mivot.viewer.mivot_instance import MivotInstance fake_hk_dict = { "dmtype": "EpochPosition", "longitude": { "dmtype": "RealQuantity", "value": 52.2340018, "unit": "deg", "ref": "RAICRS" }, "latitude": { "dmtype": "RealQuantity", "value": 59.8937333, "unit": "deg", "ref": "DEICRS" } } fake_dict = { "dmtype": "EpochPosition", "longitude": { "dmtype": "RealQuantity", "value": 52.2340018, "unit": "deg", }, "latitude": { "dmtype": "RealQuantity", "value": 59.8937333, "unit": "deg", } } test_dict = { "tableref": "Results", "root_object": { "dmid": "_ts_data", "dmrole": "", "dmtype": "cube:NDPoint", "observable": [ { "dmtype": "cube:Observable", "dependent": {"dmtype": "ivoa:boolean", "value": True}, "measure": { "dmrole": "cube:MeasurementAxis.measure", "dmtype": "meas:Time", "coord": { "dmrole": "meas:Time.coord", "dmtype": "coords:MJD", "date": {"dmtype": "ivoa:real", "value": 1705.9437360200984}, }, }, }, { "dmtype": "cube:Observable", "dependent": {"dmtype": "ivoa:boolean", "value": True}, "measure": { "dmrole": "cube:MeasurementAxis.measure", "dmtype": "meas:GenericMeasure", "coord": { "dmrole": "meas:GenericMeasure.coord", "dmtype": "coords:PhysicalCoordinate", "cval": {"dmtype": "ivoa:RealQuantity", "value": 15.216575}, }, }, }, { "dmtype": "cube:Observable", "dependent": {"dmtype": "ivoa:boolean", "value": True}, "measure": { "dmrole": "cube:MeasurementAxis.measure", "dmtype": "meas:GenericMeasure", "coord": { "dmrole": "meas:GenericMeasure.coord", "dmtype": "coords:PhysicalCoordinate", "cval": {"dmtype": "ivoa:RealQuantity", "value": 15442.456}, }, "error": { "dmrole": "meas:Measure.error", "dmtype": "meas:Error", "statError": { "dmrole": "meas:Error.statError", "dmtype": "meas:Symmetrical", "radius": {"dmtype": "ivoa:RealQuantity", "value": 44.15126}, }, }, }, }, ], }, } def test_mivot_instance_constructor(): """Test the class generation from a dict.""" mivot_object = MivotInstance(**fake_hk_dict) assert mivot_object.longitude.value == 52.2340018 assert mivot_object.longitude.unit == "deg" assert mivot_object.latitude.value == 59.8937333 assert mivot_object.latitude.unit == "deg" assert mivot_object.longitude.dmtype == "RealQuantity" assert mivot_object.dmtype == "EpochPosition" def test_mivot_instance_update(): """Test the class generation from a dict followed by an update""" mivot_object = MivotInstance(**fake_hk_dict) t = Table() t["RAICRS"] = [67.87] t["DEICRS"] = [-89.87] mivot_object.update(t[0]) assert mivot_object.longitude.value == 67.87 assert mivot_object.latitude.value == -89.87 def test_mivot_instance_update_wrong_columns(): """Test the class generation from a dict followed by an update with wrong columns.""" mivot_object = MivotInstance(**fake_hk_dict) t = Table() t["RAICRSXX"] = [67.87] t["DEICRS"] = [-89.87] with pytest.raises(KeyError, match="RAICRS"): mivot_object.update(t[0]) def test_mivot_instance_display_dict(): """Test the class generation from a dict and rebuild the dict from the instance.""" mivot_object = MivotInstance(**fake_hk_dict) assert mivot_object.to_hk_dict() == fake_hk_dict assert mivot_object.to_dict() == fake_dict astropy-pyvo-b70558c/pyvo/mivot/tests/test_mivot_viewer.py000066400000000000000000000272571510533647000242130ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Test for mivot.viewer.mivot_viewer.py """ import os import pytest import re from astropy.utils.data import get_pkg_data_filename from pyvo.mivot.utils.vocabulary import Constant from pyvo.mivot.utils.dict_utils import DictUtils from pyvo.mivot.utils.exceptions import MappingError from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer import MivotViewer from astropy import version as astropy_version dm_raw_instances = [ { "dmrole": "", "dmtype": "mango:Brightness", "value": { "dmtype": "ivoa:RealQuantity", "value": None, "unit": None, "ref": "SC_EP_1_FLUX", }, }, { "dmrole": "", "dmtype": "mango:Brightness", "value": { "dmtype": "ivoa:RealQuantity", "value": None, "unit": None, "ref": "SC_EP_2_FLUX", }, }, { "dmrole": "", "dmtype": "mango:Brightness", "value": { "dmtype": "ivoa:RealQuantity", "value": None, "unit": None, "ref": "SC_EP_3_FLUX", }, }, ] globals_photcal = { "dmid": "CoordSystem_XMM_EB1_id", "dmtype": "Phot:PhotCal", "identifier": { "dmtype": "ivoa:string", "value": "XMM/EPIC/EB1", "unit": None, "ref": None, }, "magnitudeSystem": { "dmrole": "Phot:PhotCal.magnitudeSystem", "dmtype": "Phot:MagnitudeSystem", "type": { "dmtype": "Phot:TypeOfMagSystem", "value": "XMM", "unit": None, "ref": None, }, "referenceSpectrum": { "dmtype": "ivoa:anyURI", "value": "https://xmm-tools.cosmos.esa.int/external" "/xmm_user_support/documentation/sas_usg/USG/SASUSG.html", "unit": None, "ref": None, }, }, "photometryFilter": { "dmid": "CoordSystem_XMM_FILTER_EB1_id", "dmtype": "Phot:PhotometryFilter", "dmrole": "Phot:PhotCal.photometryFilter", "identifier": { "dmtype": "ivoa:string", "value": "XMM/EPIC/EB1", "unit": None, "ref": None, }, "name": { "dmtype": "ivoa:string", "value": "XMM EPIC EB1", "unit": None, "ref": None, }, "description": { "dmtype": "ivoa:string", "value": "Soft", "unit": None, "ref": None, }, "bandName": { "dmtype": "ivoa:string", "value": "EB1", "unit": None, "ref": None, }, "spectralLocation": { "dmrole": "Phot:PhotometryFilter.spectralLocation", "dmtype": "Phot:SpectralLocation", "ucd": { "dmtype": "Phot:UCD", "value": "em.wl.effective", "unit": None, "ref": None, }, "unitexpression": { "dmtype": "ivoa:Unit", "value": "keV", "unit": None, "ref": None, }, "value": {"dmtype": "ivoa:real", "value": 0.35, "unit": None, "ref": None}, }, "bandwidth": { "dmrole": "Phot:PhotometryFilter.bandwidth", "dmtype": "Phot:Bandwidth", "ucd": { "dmtype": "Phot:UCD", "value": "instr.bandwidth;stat.fwhm", "unit": None, "ref": None, }, "unitexpression": { "dmtype": "ivoa:Unit", "value": "keV", "unit": None, "ref": None, }, "extent": {"dmtype": "ivoa:real", "value": 0.3, "unit": None, "ref": None}, "start": {"dmtype": "ivoa:real", "value": 0.2, "unit": None, "ref": None}, "stop": {"dmtype": "ivoa:real", "value": 0.5, "unit": None, "ref": None}, }, "transmissionCurve": { "dmrole": "Phot:PhotometryFilter.transmissionCurve", "dmtype": "Phot:TransmissionCurve", "access": { "dmrole": "Phot:TransmissionCurve.access", "dmtype": "Phot:Access", "reference": { "dmtype": "ivoa:anyURI", "value": "https://xmm-tools.cosmos.esa.int/external/xmm_user_support" "/documentation/sas_usg/USG/SASUSG.html", "unit": None, "ref": None, }, "format": { "dmtype": "ivoa:string", "value": "text/html", "unit": None, "ref": None, }, }, }, }, } @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_get_first_instance_dmtype(path_to_first_instance): """ Test the function get_first_instance_dmtype() which is used to find the first INSTANCE/COLLECTION in TEMPLATES. """ m_viewer = MivotViewer(votable_path=path_to_first_instance) assert m_viewer.get_dm_instance_dmtypes("one_instance")[0] == "one_instance" assert m_viewer.get_dm_instance_dmtypes("some_instances")[0] == "first" with pytest.raises(Exception, match="Can't find INSTANCE in TEMPLATES"): m_viewer.get_dm_instance_dmtypes("empty") with pytest.raises(Exception, match="No TEMPLATES with tableref=not_existing_tableref"): m_viewer.get_dm_instance_dmtypes("not_existing_tableref") @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_table_ref(m_viewer): """ Test if the mivot_viewer can find each table_ref and connect to the right table_ref. Test if the mivot_viewer can find each models. """ assert m_viewer._mapped_tables == ['_PKTable', 'Results'] with pytest.raises(Exception, match=re.escape(r"The table first_table doesn't match with any mapped_table " r"(['_PKTable', 'Results']) encountered in TEMPLATES")): m_viewer._connect_table("wrong_tableref") assert m_viewer.connected_table_ref == Constant.FIRST_TABLE assert (m_viewer.get_models() == {'mango': 'file:/Users/sao/Documents/IVOA/GitHub/ivoa-dm-examples/tmp/Mango-v1.0.vo-dml.xml', 'cube': 'https://volute.g-vo.org/svn/trunk/projects/dm/Cube/vo-dml/Cube-1.0.vo-dml.xml', 'ds': 'https://volute.g-vo.org/svn/trunk/projects/dm/' 'DatasetMetadata/vo-dml/DatasetMetadata-1.0.vo-dml.xml', 'meas': 'https://www.ivoa.net/xml/Meas/20200908/Meas-v1.0.vo-dml.xml', 'coords': 'https://www.ivoa.net/xml/STC/20200908/Coords-v1.0.vo-dml.xml', 'ivoa': 'https://www.ivoa.net/xml/VODML/IVOA-v1.vo-dml.xml'}) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_global_getters(m_viewer): """ Test each getter for TEMPLATES of the model_viewer. """ assert m_viewer.get_table_ids() == ['_PKTable', 'Results'] assert m_viewer.get_models() == DictUtils.read_dict_from_file( get_pkg_data_filename("data/reference/globals_models.json")) m_viewer._connect_table('_PKTable') row = m_viewer.next_table_row() assert row[0] == '5813181197970338560' assert row[1] == 'G' row = m_viewer.next_table_row() assert row[0] == '5813181197970338560' assert row[1] == 'BP' m_viewer.rewind() row = m_viewer.next_table_row() assert row[0] == '5813181197970338560' assert row[1] == 'G' @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_no_mivot(path_no_mivot): """ Test the viewer behavior when there is no mapping """ m_viewer = MivotViewer(path_no_mivot) assert m_viewer.get_table_ids() is None assert m_viewer.get_models() is None with pytest.raises(MappingError): m_viewer._connect_table('_PKTable') with pytest.raises(MappingError): m_viewer._connect_table() assert m_viewer.next_table_row() is None @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_instance_mutiple_in_templates(path_to_multiple_instance): """ Test case with a TEMPLATES containing multiple instances """ m_viewer = MivotViewer(votable_path=path_to_multiple_instance) instance_dict = [] # test the DM instances children of TEMPLATES before their values are set for dmi in m_viewer.dm_instances: instance_dict.append(dmi.to_hk_dict()) assert instance_dict == dm_raw_instances # test the DM instances children of TEMPLATES set with the values of the first row m_viewer.next_row_view() row_values = [] for dmi in m_viewer.dm_instances: row_values.append(dmi.value.value) assert row_values == pytest.approx([0.0, 0.1, 0.2], rel=1e-3) # test the DM instances children of TEMPLATES set with the values of the second row m_viewer.next_row_view() row_values = [] for dmi in m_viewer.dm_instances: row_values.append(dmi.value.value) assert row_values == pytest.approx([1.0, 2.1, 3.2], rel=1e-3) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_globals_instances(path_to_multiple_instance): """ Test case for the GLOBALS instance access as MivotInstances """ m_viewer = MivotViewer(votable_path=path_to_multiple_instance) instance_dict = [] photcals = 0 photfilters = 0 # test the DM instances children of TEMPLATES before their values are set for dmi in m_viewer.dm_globals_instances: if dmi.dmtype == "Phot:PhotCal": photcals += 1 elif dmi.dmtype == "Phot:PhotometryFilter": photfilters += 1 else: assert False, f"Unexpected dmtype {dmi.dmtype} in GLOBALS " instance_dict.append(dmi.to_hk_dict()) assert photcals == 3 assert photfilters == 3 # just check the first one assert instance_dict[0] == globals_photcal def test_check_version(path_to_viewer): if not check_astropy_version(): with pytest.raises(Exception, match=f"Astropy version {astropy_version.version} " f"is below the required version 6.0 for the use of MIVOT."): MivotViewer(votable_path=path_to_viewer) if astropy_version.version is None: assert not check_astropy_version() elif astropy_version.version < '6.0': assert not check_astropy_version() else: assert check_astropy_version() is True @pytest.fixture def m_viewer(): if not check_astropy_version(): pytest.skip("MIVOT test skipped because of the astropy version.") votable_name = "test.mivot_viewer.xml" votable_path = get_pkg_data_filename(os.path.join("data", votable_name)) return MivotViewer(votable_path=votable_path) @pytest.fixture def path_to_viewer(): if not check_astropy_version(): pytest.skip("MIVOT test skipped because of the astropy version.") votable_name = "test.mivot_viewer.xml" return get_pkg_data_filename(os.path.join("data", votable_name)) @pytest.fixture def path_to_multiple_instance(): votable_name = "test.instance_multiple.xml" return get_pkg_data_filename(os.path.join("data", votable_name)) @pytest.fixture def path_to_first_instance(): votable_name = "test.mivot_viewer.first_instance.xml" return get_pkg_data_filename(os.path.join("data", votable_name)) @pytest.fixture def path_no_mivot(): votable_name = "test.mivot_viewer.no_mivot.xml" return get_pkg_data_filename(os.path.join("data", votable_name)) astropy-pyvo-b70558c/pyvo/mivot/tests/test_mivot_writer.py000066400000000000000000000056621510533647000242220ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This module contains test cases for validating the functionality of MivotInstance, MivotAnnotations, and related components in the pyvo.mivot package. These tests ensure that the classes behave as expected, including error handling and XML generation for data models. """ import os import pytest from pyvo.utils import activate_features from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.utils.exceptions import MappingError from pyvo.mivot.utils.xml_utils import XmlUtils from pyvo.mivot.writer.annotations import MivotAnnotations from pyvo.mivot.writer.instance import MivotInstance # Enable MIVOT-specific features in the pyvo library activate_features("MIVOT") # File paths for test data votable_path = os.path.realpath( os.path.join(__file__, "..", "data", "test.mivot_viewer.no_mivot.xml") ) data_path = os.path.realpath( os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") ) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_MivotInstance(): """ Test the MivotInstance class for various operations including attribute addition, reference addition, and XML generation. Verifies that invalid operations raise the expected MappingError. """ with pytest.raises(MappingError): MivotInstance("") instance1 = MivotInstance("model:type.inst", dmid="id1") with pytest.raises(MappingError): instance1.add_attribute( dmrole="model:type.inst.role1", value="value1", unit="m/s" ) with pytest.raises(MappingError): instance1.add_attribute( dmtype="model:type.att1", dmrole="model:type.inst.role1" ) with pytest.raises(MappingError): instance1.add_reference(dmref="dmreference") with pytest.raises(MappingError): instance1.add_reference(dmrole="model:type.inst.role2") with pytest.raises(MappingError): instance1.add_instance("azerty") instance1.add_reference(dmrole="model:type.inst.role2", dmref="dmreference") instance1.add_attribute( dmtype="model:type.att1", dmrole="model:type.inst.role1", value="*value1", unit="m/s", ) assert XmlUtils.strip_xml(instance1.xml_string()) == ( "" + "" + "" + "" ) @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_MivotAnnotations(): """ Test the MivotAnnotations class for template and global instance addition. Verifies that invalid operations raise the expected MappingError. """ mb = MivotAnnotations() with pytest.raises(MappingError): mb.add_templates(12) with pytest.raises(MappingError): mb.add_globals(12) astropy-pyvo-b70558c/pyvo/mivot/tests/test_resource_seeker.py000066400000000000000000000051511510533647000246460ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Test for mivot.seekers.resource_seeker.py """ import pytest from astropy.io.votable import parse from astropy.utils.data import get_pkg_data_filename from pyvo.mivot.seekers.resource_seeker import ResourceSeeker from pyvo.mivot.version_checker import check_astropy_version @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_id_table(rseeker): """ Checks the IDs of tables found by the RessourceSeeker, checks the IDs of the field of the table concerned. """ assert rseeker.get_table_ids() == ['_PKTable', 'Results'] assert (rseeker.get_id_index_mapping('_PKTable') == {'pksrcid': {'ID': '_pksrcid', 'indx': 0}, 'pkband': {'ID': '_pkband', 'indx': 1} } ) assert (rseeker.get_id_index_mapping('Results') == {'source_id': {'ID': '_srcid', 'indx': 0}, 'transit_id': {'ID': 'transit_id', 'indx': 1}, 'band': {'ID': '_band', 'indx': 2}, 'time': {'ID': '_obstime', 'indx': 3}, 'mag': {'ID': '_mag', 'indx': 4}, 'flux': {'ID': '_flux', 'indx': 5}, 'flux_error': {'ID': '_fluxerr', 'indx': 6}, 'flux_over_error': {'ID': 'flux_over_error', 'indx': 7}, 'rejected_by_photometry': {'ID': 'rejected_by_photometry', 'indx': 8}, 'rejected_by_variability': {'ID': 'rejected_by_variability', 'indx': 9}, 'other_flags': {'ID': 'other_flags', 'indx': 10}, 'solution_id': {'ID': 'solution_id', 'indx': 11} } ) table = rseeker.get_table('_PKTable') for field in table.fields: field.ID = None assert (rseeker.get_id_index_mapping('_PKTable') == {'pksrcid': {'indx': 0, 'ID': 'pksrcid'}, 'pkband': {'indx': 1, 'ID': 'pkband'} } ) for table in rseeker._resource.tables: table.ID = None assert rseeker.get_table_ids() == ['AnonymousTable', 'AnonymousTable'] for table in rseeker._resource.tables: table.name = "any_name" assert rseeker.get_table_ids() == ['any_name', 'any_name'] assert (rseeker.get_id_index_mapping('any_name') == {'pksrcid': {'indx': 0, 'ID': 'pksrcid'}, 'pkband': {'indx': 1, 'ID': 'pkband'}} ) @pytest.fixture def rseeker(): votable_path = get_pkg_data_filename("data/test.mivot_viewer.xml") votable = parse(votable_path) for resource in votable.resources: return ResourceSeeker(resource) astropy-pyvo-b70558c/pyvo/mivot/tests/test_sky_coord_builder.py000066400000000000000000000232451510533647000251670ustar00rootroot00000000000000''' The first service in operation that annotates query responses in the fly is Vizier https://cds/viz-bin/mivotconesearch/VizierParams Data are mapped on the mango:EpochPropagtion class as it is implemented in the current code. This test case is based on 2 VOTables: Both tests check the generation of SkyCoord instances from the MivotInstances built for the output of this service. ''' import pytest from copy import deepcopy from astropy.utils.data import get_pkg_data_filename from astropy import units as u from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer.mivot_instance import MivotInstance from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder from pyvo.mivot.utils.exceptions import NoMatchingDMTypeError from pyvo.mivot.viewer.mivot_viewer import MivotViewer from pyvo.utils import activate_features # Enable MIVOT-specific features in the pyvo library activate_features("MIVOT") # annotations generated by Vizier as given to the MivotInstance vizier_dict = { "dmtype": "mango:EpochPosition", "longitude": { "dmtype": "ivoa:RealQuantity", "value": 52.26722684, "unit": "deg", "ref": "RAICRS", }, "latitude": { "dmtype": "ivoa:RealQuantity", "value": 59.94033461, "unit": "deg", "ref": "DEICRS", }, "pmLongitude": { "dmtype": "ivoa:RealQuantity", "value": -0.82, "unit": "mas/yr", "ref": "pmRA", }, "pmLatitude": { "dmtype": "ivoa:RealQuantity", "value": -1.85, "unit": "mas/yr", "ref": "pmDE", }, "obsDate": { "dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "yr", "ref": None, }, "spaceSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "coords:Coordinate.coordSys", "frame": { "dmtype": "coords:SpaceFrame", "dmrole": "coords:PhysicalCoordSys.frame", "spaceRefFrame": { "dmtype": "ivoa:string", "value": "ICRS" }, }, }, } # The same edited by hand (parallax added and FK5 + Equinox frame) vizier_equin_dict = { "dmtype": "mango:EpochPosition", "longitude": { "dmtype": "ivoa:RealQuantity", "value": 52.26722684, "unit": "deg", "ref": "RAICRS", }, "latitude": { "dmtype": "ivoa:RealQuantity", "value": 59.94033461, "unit": "deg", "ref": "DEICRS", }, "pmLongitude": { "dmtype": "ivoa:RealQuantity", "value": -0.82, "unit": "mas/yr", "ref": "pmRA", }, "pmLatitude": { "dmtype": "ivoa:RealQuantity", "value": -1.85, "unit": "mas/yr", "ref": "pmDE", }, "parallax": { "dmtype": "ivoa:RealQuantity", "value": 0.6, "unit": "mas", "ref": "parallax", }, "obsDate": { "dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "yr", "ref": None, }, "spaceSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "coords:Coordinate.coordSys", "frame": { "dmtype": "coords:SpaceFrame", "dmrole": "coords:PhysicalCoordSys.frame", "spaceRefFrame": { "dmtype": "ivoa:string", "value": "FK5" }, "equinox": { "dmtype": "coords:SpaceFrame.equinox", "value": "2012", "unit": "yr", } } }, } # The same edited mapped on a dummy class vizier_dummy_type = { "dmtype": "mango:DumyType", "longitude": { "dmtype": "ivoa:RealQuantity", "value": 52.26722684, "unit": "deg", "ref": "RAICRS", }, "latitude": { "dmtype": "ivoa:RealQuantity", "value": 59.94033461, "unit": "deg", "ref": "DEICRS", }, "pmLongitude": { "dmtype": "ivoa:RealQuantity", "value": -0.82, "unit": "mas/yr", "ref": "pmRA", }, "pmLatitude": { "dmtype": "ivoa:RealQuantity", "value": -1.85, "unit": "mas/yr", "ref": "pmDE", }, "parallax": { "dmtype": "ivoa:RealQuantity", "value": 0.6, "unit": "mas", "ref": "parallax", }, "obsDate": { "dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "yr", "ref": None, }, "coordSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "coords:Coordinate.coordSys", "spaceRefFrame": { "dmtype": "coords:SpaceFrame.spaceRefFrame", "value": "FK5", "unit": None, "ref": None, }, "equinox": { "dmtype": "coords:SpaceFrame.equinox", "value": "2012", "unit": "yr", }, }, } def test_no_matching_mapping(): """ Test that a NoMatchingDMTypeError is raised not mapped on mango:EpochPosition """ with pytest.raises(NoMatchingDMTypeError): mivot_instance = MivotInstance(**vizier_dummy_type) scb = SkyCoordBuilder(mivot_instance) scb.build_sky_coord() @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_vizier_output(): """ Test the SkyCoord issued from the Vizier response """ mivot_instance = MivotInstance(**vizier_dict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") scoo = mivot_instance.get_SkyCoord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") vizier_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "Galactic" mivot_instance = MivotInstance(**vizier_dict) scoo = mivot_instance.get_SkyCoord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") vizier_dict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "QWERTY" mivot_instance = MivotInstance(**vizier_dict) scoo = mivot_instance.get_SkyCoord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_vizier_output_with_equinox_and_parallax(): """Test the SkyCoord issued from the modified Vizier response * (parallax added and FK5 + Equinox frame) """ mivot_instance = MivotInstance(**vizier_equin_dict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") mydict = deepcopy(vizier_equin_dict) mydict["spaceSys"]["frame"]["spaceRefFrame"]["value"] = "FK4" mivot_instance = MivotInstance(**mydict) scoo = mivot_instance.get_SkyCoord() assert (str(scoo).replace("\n", "").replace(" ", "") == "") @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_simad_cs_output(): """Test the SkyCoord issued from a Simbad SCS response """ filename = get_pkg_data_filename('data/simbad-cone-mivot.xml') m_viewer = MivotViewer(filename, resolve_ref=True) mivot_instance = m_viewer.dm_instance scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.ra.degree == pytest.approx(269.45207696) assert scoo.dec.degree == pytest.approx(4.69336497) assert scoo.distance.pc == pytest.approx(1.82823411) x = scoo.pm_ra_cosdec.value y = (-801.551 * u.mas/u.yr).value assert x == pytest.approx(y) x = scoo.pm_dec.value y = (10362.394 * u.mas/u.yr).value assert x == pytest.approx(y) assert str(scoo.obstime) == "J2000.000" def test_time_representation(): """ Test various time representations Inconsistent values are not tested since there are detected by ``astropy.core.Time`` """ # work with a copy to not alter other test functions mydict = deepcopy(vizier_equin_dict) mydict["obsDate"]["unit"] = "mjd" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.obstime.jyear_str == "J1864.331" mydict["obsDate"]["unit"] = "jd" mydict["obsDate"]["value"] = "2460937.36" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.obstime.jyear_str == "J2025.715" mydict = deepcopy(vizier_equin_dict) mydict["obsDate"]["unit"] = "iso" mydict["obsDate"]["dmtype"] = "ivoa:string" mydict["obsDate"]["value"] = "2025-05-03" mivot_instance = MivotInstance(**mydict) scb = SkyCoordBuilder(mivot_instance) scoo = scb.build_sky_coord() assert scoo.obstime.jyear_str == "J2025.335" astropy-pyvo-b70558c/pyvo/mivot/tests/test_static_reference.py000066400000000000000000000022231510533647000247630ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Test for mivot.features.static_reference_resolver.py """ import pytest from astropy.utils.data import get_pkg_data_filename from pyvo.mivot.seekers.annotation_seeker import AnnotationSeeker from pyvo.mivot.features.static_reference_resolver import StaticReferenceResolver from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer import MivotViewer from . import XMLOutputChecker @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_static_reference_resolve(a_seeker, instance): StaticReferenceResolver.resolve(a_seeker, None, instance) XMLOutputChecker.assertXmltreeEqualsFile( instance.getroot(), get_pkg_data_filename("data/reference/static_reference_resolved.xml") ) @pytest.fixture def instance(): return XMLOutputChecker.xmltree_from_file( get_pkg_data_filename("data/static_reference.xml")) @pytest.fixture def a_seeker(): m_viewer = MivotViewer( get_pkg_data_filename("data/test.mivot_viewer.xml"), tableref="Results") return AnnotationSeeker(m_viewer._mapping_block) astropy-pyvo-b70558c/pyvo/mivot/tests/test_user_api.py000066400000000000000000000365631510533647000233030ustar00rootroot00000000000000""" Check the API exposed to the user works properly This unit test can also be used as a code example Created on 26 Feb. 2024 @author: michel """ import os import pytest from urllib.request import urlretrieve import astropy.units as u from astropy.coordinates import SkyCoord from pyvo.dal.scs import SCSService from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer import MivotViewer from astropy.io.votable import parse ref_ra = [ 0.04827189, 0.16283175, 0.29222255, 0.42674592, 359.5190115, 359.94372764, ] ref_dec = [ -0.36042119, 0.22293899, -0.07592034, -0.21749947, -0.1281483, -0.28005255, ] ref_pmdec = [ -11.67, -3.09, -73.28, -114.08, -19.05, -25.43 ] ref_pmra = [ 61.75, 39.02, 54.94, 20.73, -45.19, -5.14 ] @pytest.fixture def data_path(): return os.path.dirname(os.path.realpath(__file__)) @pytest.fixture def data_sample_url(): return "https://raw.githubusercontent.com/ivoa/dm-usecases/main/pyvo-ci-sample/" @pytest.fixture def vizier_url(): return "https://vizier.cds.unistra.fr/viz-bin/conesearch/V1.5/I/239/hip_main?RA=0&DEC=0&;SR=0.5" @pytest.fixture def delt_coo(): """acceptable delta for coordinate value comparisons""" return 0.0000001 @pytest.fixture def path_to_votable(data_path, data_sample_url): votable_name = "vizier_for_user_api.xml" votable_path = os.path.join(data_path, "data", votable_name) urlretrieve(data_sample_url + votable_name, votable_path) yield votable_path os.remove(votable_path) @pytest.fixture def path_to_full_mapped_votable(data_path, data_sample_url): votable_name = "gaia_epoch_propagation_full.xml" votable_path = os.path.join(data_path, "data", votable_name) urlretrieve(data_sample_url + votable_name, votable_path) yield votable_path os.remove(votable_path) @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_mivot_viewer_next(path_to_votable): """ Check that ten MivotViewer iterating over the data rows provides the expected values """ mivot_viewer = MivotViewer(path_to_votable, resolve_ref=True) mivot_instance = mivot_viewer.dm_instance assert mivot_instance.dmtype == "mango:EpochPosition" assert mivot_instance.coordSys.spaceRefFrame.value == "ICRS" ra = [] dec = [] pmra = [] pmdec = [] while mivot_viewer.next_row_view(): ra.append(mivot_instance.longitude.value) dec.append(mivot_instance.latitude.value) pmra.append(mivot_instance.pmLongitude.value) pmdec.append(mivot_instance.pmLatitude.value) assert ra == ref_ra assert dec == ref_dec assert pmra == ref_pmra assert pmdec == ref_pmdec @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_mivot_tablerow_next(path_to_votable): """ Check that the MIVOT interpreter can be applied to a classical table readout. - The MivotViewer is initialized on the first data row (behind the stage) to be able to build a MivoInstance that will provide a model view on the data - The data table is read in a classical way - The MivotInstance is updated with data row providing so a model view on it """ votable = parse(path_to_votable) table = votable.resources[0].tables[0] mivot_viewer = MivotViewer(votable, resolve_ref=True) mivot_instance = mivot_viewer.dm_instance assert mivot_instance.dmtype == "mango:EpochPosition" assert mivot_instance.coordSys.spaceRefFrame.value == "ICRS" ra = [] dec = [] pmra = [] pmdec = [] for rec in table.array: mivot_instance.update(rec) ra.append(mivot_instance.longitude.value) dec.append(mivot_instance.latitude.value) pmra.append(mivot_instance.pmLongitude.value) pmdec.append(mivot_instance.pmLatitude.value) assert ra == ref_ra assert dec == ref_dec assert pmra == ref_pmra assert pmdec == ref_pmdec @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_external_iterator(path_to_votable, delt_coo): """Checks that the values returned by MIVOT are the same as those read by Astropy - The MivotViewer is initialized on the first data row (behind the stage) to be able to build a MivoInstance that will provide a model view on the data - The data table is read in a classical way - The MivotInstance is updated with data row providing so a model view on it - The attribute values are then checked against the table data """ # parse the VOTable outside of the viewer votable = parse(path_to_votable) table = votable.resources[0].tables[0] # init the viewer mivot_viewer = MivotViewer(votable) mivot_instance = mivot_viewer.dm_instance for rec in table.array: mivot_instance.update(rec) assert rec["RAICRS"] == mivot_instance.longitude.value assert rec["DEICRS"] == mivot_instance.latitude.value assert rec["pmRA"] == mivot_instance.pmLongitude.value assert rec["pmDE"] == mivot_instance.pmLatitude.value @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_with_withstatement(path_to_votable): """check that the values read by a MivotViewer used with a with statement are those expected """ read_ra = [] read_dec = [] read_pmra = [] read_pmdec = [] with MivotViewer(path_to_votable) as mivot_viewer: mivot_object = mivot_viewer.dm_instance while mivot_viewer.next_row_view(): read_ra.append(mivot_object.longitude.value) read_dec.append(mivot_object.latitude.value) read_pmra.append(mivot_object.pmLongitude.value) read_pmdec.append(mivot_object.pmLatitude.value) assert read_ra == ref_ra assert read_dec == ref_dec assert read_pmra == ref_pmra assert read_pmdec == ref_pmdec @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_with_dict(path_to_votable): """check that the MIVOT object user dictionary match the read data""" with MivotViewer(path_to_votable, resolve_ref=True) as mivot_viewer: mivot_object = mivot_viewer.dm_instance # let"s focus on the last data row while mivot_viewer.next_row_view(): pass # check the slim (user friendly) dictionary assert mivot_object.to_dict() == { "dmtype": "mango:EpochPosition", "longitude": {"dmtype": "ivoa:RealQuantity", "value": 359.94372764, "unit": "deg"}, "latitude": {"dmtype": "ivoa:RealQuantity", "value": -0.28005255, "unit": "deg"}, "pmLongitude": {"dmtype": "ivoa:RealQuantity", "value": -5.14, "unit": "mas/yr"}, "pmLatitude": {"dmtype": "ivoa:RealQuantity", "value": -25.43, "unit": "mas/yr"}, "epoch": {"dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "year"}, "coordSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "coords:Coordinate.coordSys", "spaceRefFrame": {"dmtype": "coords:SpaceFrame", "value": "ICRS"}, }, } # check the whole dictionary assert mivot_object.to_hk_dict() == { "dmtype": "mango:EpochPosition", "longitude": { "dmtype": "ivoa:RealQuantity", "value": 359.94372764, "unit": "deg", "ref": "RAICRS", }, "latitude": { "dmtype": "ivoa:RealQuantity", "value": -0.28005255, "unit": "deg", "ref": "DEICRS", }, "pmLongitude": { "dmtype": "ivoa:RealQuantity", "value": -5.14, "unit": "mas/yr", "ref": "pmRA", }, "pmLatitude": { "dmtype": "ivoa:RealQuantity", "value": -25.43, "unit": "mas/yr", "ref": "pmDE", }, "epoch": { "dmtype": "ivoa:RealQuantity", "value": 1991.25, "unit": "year", "ref": None, }, "coordSys": { "dmtype": "coords:SpaceSys", "dmid": "SpaceFrame_ICRS", "dmrole": "coords:Coordinate.coordSys", "spaceRefFrame": { "dmtype": "coords:SpaceFrame", "value": "ICRS", "unit": None, "ref": None, }, }, } @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_with_full_dict(path_to_full_mapped_votable): """check that the MIVOT object user dictionary match the read data - The data sample used here maps the whole EpochPosition model except TimeSys """ with MivotViewer(path_to_full_mapped_votable, resolve_ref=True) as mivot_viewer: mivot_object = mivot_viewer.dm_instance # let"s focus on the second data row while mivot_viewer.next_row_view(): # check the slim (user friendly) dictionary assert mivot_object.to_dict() == { "dmtype": "mango:EpochPosition", "longitude": {"dmtype": "ivoa:RealQuantity", "value": 307.79115807079, "unit": "deg"}, "latitude": {"dmtype": "ivoa:RealQuantity", "value": 20.43108005561, "unit": "deg"}, "parallax": {"dmtype": "ivoa:RealQuantity", "value": 0.4319, "unit": "mas"}, "radialVelocity": {"dmtype": "ivoa:RealQuantity", "value": None, "unit": "km/s"}, "pmLongitude": {"dmtype": "ivoa:RealQuantity", "value": -2.557, "unit": "mas/yr"}, "pmLatitude": {"dmtype": "ivoa:RealQuantity", "value": -5.482, "unit": "mas/yr"}, "epoch": {"dmtype": "coords:Epoch", "value": "2016.5"}, "pmCosDeltApplied": {"dmtype": "ivoa:boolean", "value": True}, "errors": { "dmrole": "mango:EpochPosition.errors", "dmtype": "mango:EpochPositionErrors", "parallax": { "dmrole": "mango:EpochPositionErrors.parallax", "dmtype": "mango:ErrorTypes.PropertyError1D", "sigma": {"dmtype": "ivoa:real", "value": 0.06909999996423721, "unit": "mas"}, }, "radialVelocity": { "dmrole": "mango:EpochPositionErrors.radialVelocity", "dmtype": "mango:ErrorTypes.PropertyError1D", "sigma": {"dmtype": "ivoa:real", "value": None, "unit": "km/s"}, }, "position": { "dmrole": "mango:EpochPositionErrors.position", "dmtype": "mango:ErrorTypes.ErrorMatrix", "sigma1": {"dmtype": "ivoa:real", "value": 0.0511, "unit": "mas"}, "sigma2": {"dmtype": "ivoa:real", "value": 0.0477, "unit": "mas"}, }, "properMotion": { "dmrole": "mango:EpochPositionErrors.properMotion", "dmtype": "mango:ErrorTypes.ErrorMatrix", "sigma1": {"dmtype": "ivoa:real", "value": 0.06400000303983688, "unit": "mas/yr"}, "sigma2": {"dmtype": "ivoa:real", "value": 0.06700000166893005, "unit": "mas/yr"}, }, }, "correlations": { "dmrole": "mango:EpochPosition.correlations", "dmtype": "mango:EpochPositionCorrelations", "positionPm": { "dmrole": "mango:EpochPositionCorrelations.positionPm", "dmtype": "mango:Correlation22", "isCovariance": {"dmtype": "ivoa:boolean", "value": True}, "a2b1": {"dmtype": "ivoa:real", "value": -0.0085}, "a2b2": {"dmtype": "ivoa:real", "value": -0.2983}, "a1b1": {"dmtype": "ivoa:real", "value": -0.4109}, "a1b2": {"dmtype": "ivoa:real", "value": -0.0072}, }, "parallaxPm": { "dmrole": "mango:EpochPositionCorrelations.parallaxPm", "dmtype": "mango:Correlation12", "isCovariance": {"dmtype": "ivoa:boolean", "value": True}, "a1b1": {"dmtype": "ivoa:real", "value": -0.2603}, "a1b2": {"dmtype": "ivoa:real", "value": -0.0251}, }, "positionParallax": { "dmrole": "mango:EpochPositionCorrelations.positionParallax", "dmtype": "mango:Correlation21", "isCovariance": {"dmtype": "ivoa:boolean", "value": True}, "a2b1": {"dmtype": "ivoa:real", "value": 0.0069}, "a1b1": {"dmtype": "ivoa:real", "value": 0.1337}, }, "positionPosition": { "dmrole": "mango:EpochPositionCorrelations.positionPosition", "dmtype": "mango:Correlation22", "isCovariance": {"dmtype": "ivoa:boolean", "value": True}, "a2b1": {"dmtype": "ivoa:real", "value": 0.1212}, "a1b2": {"dmtype": "ivoa:real", "value": 0.1212}, }, "properMotionPm": { "dmrole": "mango:EpochPositionCorrelations.properMotionPm", "dmtype": "mango:Correlation22", "isCovariance": {"dmtype": "ivoa:boolean", "value": True}, "a2b1": {"dmtype": "ivoa:real", "value": 0.2688}, "a1b2": {"dmtype": "ivoa:real", "value": 0.2688}, }, }, "coordSys": { "dmid": "_spacesys_icrs", "dmrole": "mango:EpochPosition.coordSys", "dmtype": "coords:SpaceSys", "frame": { "dmrole": "coords:PhysicalCoordSys.frame", "dmtype": "coords:SpaceFrame", "spaceRefFrame": {"dmtype": "ivoa:string", "value": "ICRS"}, }, }, } break @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_cone_search(vizier_url): """ Test that data returned by Vizier CS (1 row) and read through MIVOt are those expected """ scs_srv = SCSService(vizier_url) m_viewer = MivotViewer( scs_srv.search( pos=SkyCoord(ra=52.26708 * u.degree, dec=59.94027 * u.degree, frame="icrs"), radius=0.05, ), resolve_ref=True ) mivot_instance = m_viewer.dm_instance assert mivot_instance.dmtype == "mango:EpochPosition" assert mivot_instance.spaceSys.frame.spaceRefFrame.value == "ICRS" ra = [] dec = [] pmra = [] pmdec = [] while m_viewer.next_row_view(): ra.append(mivot_instance.longitude.value) dec.append(mivot_instance.latitude.value) pmra.append(mivot_instance.pmLongitude.value) pmdec.append(mivot_instance.pmLatitude.value) assert ra == [52.26722684] assert dec == [59.94033461] assert pmra == [-0.82] assert pmdec == [-1.85] astropy-pyvo-b70558c/pyvo/mivot/tests/test_vizier_cs.py000066400000000000000000000110201510533647000234460ustar00rootroot00000000000000''' The first service in operation the annotates query responses in the fly is Vizier https://cds/viz-bin/mivotconesearch/VizierParams Data are mapped o the EPochPropagtion model as it is implemented in the current code. This test case is based on 2 VOTables: - The Vizier native (vizier_cs_withname.xml) where all ATTRIBUTE@ref are based on FIELD@name even when a field has an ID. - The patched vizier (vizier_cs_withid.xml) where all ATTRIBUTE@ref are based on FIELD@ID or FIELD@name if it exists. The test checks that: - The position fields can be retrieved through the mapping. - Both cases give the same results A third test checks the case where a referenced column does not exist. Created on 26 janv. 2024 @author: michel ''' import os import pytest from urllib.request import urlretrieve from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer import MivotViewer from pyvo.mivot.utils.exceptions import MivotError @pytest.fixture def data_path(): return os.path.dirname(os.path.realpath(__file__)) @pytest.fixture def data_sample_url(): return "https://raw.githubusercontent.com/ivoa/dm-usecases/main/pyvo-ci-sample/" @pytest.fixture def delt_coo(): """ acceptable delta for coordinate value comparisons """ return 0.0000005 @pytest.fixture def path_to_withname(data_path, data_sample_url): votable_name = "vizier_cs_withname.xml" votable_path = os.path.join(data_path, "data", votable_name) urlretrieve(data_sample_url + votable_name, votable_path) yield votable_path os.remove(votable_path) @pytest.fixture def path_to_withid(data_path, data_sample_url): votable_name = "vizier_cs_withid.xml" votable_path = os.path.join(data_path, "data", votable_name) urlretrieve(data_sample_url + votable_name, votable_path) yield votable_path os.remove(votable_path) @pytest.fixture def path_to_badref(data_path, data_sample_url): votable_name = "vizier_cs_badref.xml" votable_path = os.path.join(data_path, "data", votable_name) urlretrieve(data_sample_url + votable_name, votable_path) yield votable_path os.remove(votable_path) @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_with_name(path_to_withname, delt_coo): """ Test that the epoch propagation works with all FIELDs referenced by name or by ID """ m_viewer = MivotViewer(votable_path=path_to_withname, resolve_ref=True) m_viewer.next_row_view() mivot_object = m_viewer.dm_instance assert abs(mivot_object.longitude.value - 52.2340018) < delt_coo assert abs(mivot_object.latitude.value - 59.8937333) < delt_coo assert abs(mivot_object.pmLongitude.value - 1.5) < delt_coo assert abs(mivot_object.pmLatitude.value - -12.30000019) < delt_coo assert str(mivot_object.epoch.value) == '2013.418' assert str(mivot_object.coordSys.spaceRefFrame.value) == 'ICRS' m_viewer.next_row_view() assert abs(mivot_object.longitude.value - 32.2340018) < delt_coo assert abs(mivot_object.latitude.value - 49.8937333) < delt_coo assert abs(mivot_object.pmLongitude.value - 1.5) < delt_coo assert abs(mivot_object.pmLatitude.value - -12.30000019) < delt_coo assert str(mivot_object.epoch.value) == '2013.418' assert str(mivot_object.coordSys.spaceRefFrame.value) == 'ICRS' @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_with_id(path_to_withid, delt_coo): """ Test that the epoch propagation works with all FIELDs referenced by name or by ID """ # Test with all FILELDs referenced by names m_viewer = MivotViewer(votable_path=path_to_withid) m_viewer.next_row_view() mivot_instance = m_viewer.dm_instance assert abs(mivot_instance.longitude.value - 52.2340018) < delt_coo assert abs(mivot_instance.latitude.value - 59.8937333) < delt_coo assert abs(mivot_instance.pmLongitude.value - 1.5) < delt_coo assert abs(mivot_instance.pmLatitude.value - -12.30000019) < delt_coo assert str(mivot_instance.epoch.value) == '2013.418' @pytest.mark.remote_data @pytest.mark.skipif(not check_astropy_version(), reason="need astropy 6+") def test_bad_ref(path_to_badref, delt_coo): """ Test that the epoch propagation works with all FIELDs referenced by name or by ID """ # Test with all FILELDs referenced by names with (pytest.raises(MivotError, match="Attribute mango:EpochPosition.epoch can not be set.*")): MivotViewer(votable_path=path_to_badref) astropy-pyvo-b70558c/pyvo/mivot/utils/000077500000000000000000000000001510533647000200445ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/utils/__init__.py000066400000000000000000000000431510533647000221520ustar00rootroot00000000000000from .mivot_utils import MivotUtilsastropy-pyvo-b70558c/pyvo/mivot/utils/dict_utils.py000066400000000000000000000045511510533647000225660ustar00rootroot00000000000000""" Utility class to process dictionary. """ import json import logging from pyvo.mivot.utils.exceptions import MivotError from pyvo.mivot.utils.json_encoder import MivotJsonEncoder class DictUtils: """ Static class implementing convenient operations on dictionaries. """ @staticmethod def add_array_element(dictionnary, key): """ Add a [] as key element if not exist Parameters: ----------- dictionnary: dict dictionnary to be updated key: string key to be added Returns: -------- Bool True if the key element has been added """ if key not in dictionnary: dictionnary[key] = [] return True return False @staticmethod def read_dict_from_file(filename, fatal=False): """ Read a dictionary from a file and raise an exception if something goes wrong. Parameters: - filename (str): The filename. Any file that can be processed by json.load is accepted - fatal (bool): Triggers a system exit if True. Returns: - dict: The dictionary extracted from the file. Raises: - DataFormatException: If the file has an incorrect format. """ try: logging.debug("Reading json from %s", filename) from collections import OrderedDict with open(filename) as file: return json.load(file, object_pairs_hook=OrderedDict) except Exception as exception: if fatal: raise MivotError(f"reading {filename}") else: logging.error(f"{exception} reading {filename}") @staticmethod def _get_pretty_json(dictionary): """ Return a pretty string representation of the dictionary. Parameters: - dictionary (dict): The dictionary. Returns: - str: A pretty string representation of the dictionary. """ return json.dumps(dictionary, indent=2, cls=MivotJsonEncoder) @staticmethod def print_pretty_json(dictionary): """ Print out a pretty string representation of the dictionary. Parameters: - dictionary (dict): The dictionary. """ print(DictUtils._get_pretty_json(dictionary)) astropy-pyvo-b70558c/pyvo/mivot/utils/exceptions.py000066400000000000000000000024041510533647000225770ustar00rootroot00000000000000""" MIVOT Exceptions 3 exception classes - AstropyVersionException that prevent to use the package - MappingError if the annotation cannot be processed (e.g. no MIVOT block) but the VOtable parsing can continue - MivotError in any other case (block the processing) """ class MivotError(Exception): """ The annotation block is there but something went wrong with its processing """ class MappingError(Exception): """ Exception raised if a Resource or MIVOT element can't be mapped for one of these reasons: - It doesn't match with any Resource/MIVOT element expected. - It matches with too many Resource/MIVOT elements than expected. This exception is trapped by the Viewer so that the processing can continue by ignoring the annotations """ class NoMatchingDMTypeError(TypeError): """ Exception thrown when some PyVO code misses MIVOT element: - When trying to build a SkyCoord while there is no position in the annotations - is mapped to a model unknown to the PyVO code. This exception is never caught by the mivot package. It must be handled by the calling code. """ class AstropyVersionException(Exception): """ Exception raised if the version of astropy is not compatible with MIVOT. """ astropy-pyvo-b70558c/pyvo/mivot/utils/json_encoder.py000066400000000000000000000015411510533647000230670ustar00rootroot00000000000000import json import numpy class MivotJsonEncoder(json.JSONEncoder): """ Custom JSON encoder for NumPy data types. This class extends the default JSONEncoder to handle NumPy integers, floating-point numbers, and arrays during JSON encoding. """ def default(self, obj): """ Serialize NumPy data types to their Python equivalents for JSON encoding. Parameters ---------- obj : Any The object to be encoded. Returns ------- Any The serialized version of the object. """ if isinstance(obj, numpy.integer): return int(obj) elif isinstance(obj, numpy.floating): return float(obj) elif isinstance(obj, numpy.ndarray): return obj.tolist() else: return super().default(obj) astropy-pyvo-b70558c/pyvo/mivot/utils/mivot_utils.py000066400000000000000000000262241510533647000230020ustar00rootroot00000000000000''' Utilities handling various operations on Mivot instances ''' import numpy from pyvo.mivot.utils.exceptions import MappingError from pyvo.mivot.glossary import IvoaType, Roles, ModelPrefix class MivotUtils: """ Some utilities making easier the transformation of Mivot elements into dictionary components. These dictionaries are used to generate ``MivotInstance`` objects """ @staticmethod def _valid_mapped_dmroles(mapped_roles, class_name): """ Check that the given mapped roles of the given class, are in the `pyvo.mivot.glossary.Roles`, which reflects the model. Parameters ---------- mapped_roles: dict Dictionary of the matches between the dmroles (dict keys) and the column identifiers (dict values). This mapping is a user input. class_name: str Name of the class to which the mapping applies Returns ------- dict The dictionary of valid matches between the dmroles (dict keys) and the column identifiers (dict values). Raises ------ MappingError If the class ``class_name`` is not supported or if some mapped role does not exist for the glossary """ # Check that the class is in the glossary if not hasattr(Roles, class_name): raise MappingError(f"Unknown or unimplemented class {class_name}") # get the list of supported roles dmroles = getattr(Roles, class_name) real_mapping = [] for mapped_role, column in mapped_roles: # 'class' is a reserved word, not a role if mapped_role == "class" or isinstance(column, dict) or isinstance(column, list): continue found = False for leaf in dmroles: dmrole = f"{ModelPrefix.mango}:{class_name}.{leaf}" if dmrole.lower().endswith("." + mapped_role.lower()): real_mapping.append((dmrole, column)) found = True break if not found: raise MappingError(f"Class {ModelPrefix.mango}:{class_name} " f"has no {mapped_role} attribute." f"Supported roles are {dmroles}") return real_mapping @staticmethod def xml_to_dict(element): """ Recursively create a nested dictionary from the XML tree structure, preserving the hierarchy. The processing of elements depends on the tag: - For INSTANCE, a new dictionary is created. - For COLLECTION, a list is created. - For ATTRIBUTE, a leaf structure is created in the tree structure with dmtype, dmrole, value, unit, and ref keys. Parameters ---------- element : `xml.etree.ElementTree.Element` The XML element to convert to a dictionary Returns ------- dict The nested dictionary representing the XML tree structure. """ dict_result = {} for key, value in element.attrib.items(): dict_result[key] = value for child in element: dmrole = child.get("dmrole") if child.tag == "ATTRIBUTE": dict_result[dmrole] = MivotUtils._attribute_to_dict(child) elif child.tag == "INSTANCE": # INSTANCE is recursively well managed by the function _to_dict dict_result[dmrole] = MivotUtils.xml_to_dict(child) elif child.tag == "COLLECTION": dict_result[dmrole] = MivotUtils._collection_to_dict(child) return dict_result @staticmethod def _attribute_to_dict(child): """ Convert an ATTRIBUTE (XML) element to a dictionary. ATTRIBUTE being always a leaf, the conversion is not recursive. Parameters ---------- child : `xml.etree.ElementTree.Element` ATTRIBUTE XML element to convert. Returns ------- dict: A dictionary representing the ATTRIBUTE element with keys: 'dmtype', 'dmrole', 'value', 'unit', and 'ref'. """ attribute = {} if child.get("dmtype") is not None: attribute["dmtype"] = child.get("dmtype") if child.get("value") is not None: attribute["value"] = MivotUtils.cast_type_value(child.get("value"), child.get("dmtype")) else: attribute["value"] = None if child.get("unit") is not None: attribute["unit"] = child.get("unit") else: attribute["unit"] = None if child.get("ref") is not None: attribute["ref"] = child.get("ref") else: attribute["ref"] = None return attribute @staticmethod def _collection_to_dict(child): """ Convert a COLLECTION element (child) to a list of dictionaries. Parameters ---------- child : `xml.etree.ElementTree.Element` COLLECTION XML element to convert Returns ------- list({}) list of dictionaries representing the COLLECTION items """ collection_items = [] for child_coll in child: collection_items.append(MivotUtils.xml_to_dict(child_coll)) return collection_items @staticmethod def cast_type_value(value, dmtype): """ Cast value to the Python type matching dmtype. Parameters ---------- value : str value to cast dmtype : str model dmtype Returns ------- Union[bool, float, str, None] The casted value or None """ lower_dmtype = dmtype.lower() # empty strings cannot be casted if "string" not in lower_dmtype and value == "": return None if numpy.issubdtype(type(value), numpy.floating): return float(value) if isinstance(value, str): lower_value = value.lower() else: lower_value = value if "bool" in lower_dmtype: if value == "1" or lower_value == "true" or lower_value: return True else: return False elif lower_value in ('notset', 'noset', 'null', 'none', 'nan') or value is None: return None elif (isinstance(value, numpy.ndarray) or isinstance(value, numpy.ma.core.MaskedConstant) or value == '--'): return None elif "real" in lower_dmtype or "double" in lower_dmtype or "float" in lower_dmtype: return float(value) else: return value @staticmethod def format_dmid(dmid): """ Replace characters that could confuse XPath queries with '_'. This is not required by the MIVOT schema but this makes this API more robust Returns ------- str formatted dmid """ if dmid is not None: return dmid.replace("/", "_").replace(".", "_").replace("-", "_") return "" @staticmethod def get_field_attributes(table, column_id): """ Parameters ---------- table : astropy.table Table (from parsed VOTable) of the mapped data column_id : str Identifier of the table column from which we want to get the unit Returns ------- unit, ref, literal """ ref, literal = MivotUtils.get_ref_or_literal(column_id) if literal: return None, None, literal else: try: field = table.get_field_by_id_or_name(ref) return str(field.unit), column_id, None except KeyError as keyerror: raise MappingError(f"Cannot find any field identified by {column_id}") from keyerror @staticmethod def get_ref_or_literal(value_or_ref): """ Check if value_or_ref must be interpreted as a column reference or a literal. Returns ------- (ref, literal) """ if not value_or_ref: raise MappingError("An attribute cannot be set with a None value") elif isinstance(value_or_ref, str): return ((None, value_or_ref.replace("*", "")) if value_or_ref.startswith("*") else (value_or_ref, None)) else: return (None, value_or_ref) @staticmethod def as_literal(identifier): """ Make sure the identifier will be interpreted as a literal (* prefix). Literal are either non string values or strings starting with a * Parameters ---------- identifier: str column identifier or literal value Returns ------- str identifier prefixes with a * """ if isinstance(identifier, str) and not identifier.startswith("*"): return "*" + identifier return identifier @staticmethod def populate_instance(property_instance, class_name, mapping, table, dmtype, as_literals=False, package=None): """ This function inserts in the property_instance all expected attributes. - The structure of the class is supposed to be flat (only ATTRIBUTEs). - All attributes are meant to have the same dmtype. - The mapping is checked against the `pyvo.mivot.glossary.Roles`. Parameters ---------- property_instance : `pyvo.mivot.writer.instance.MivotInstance` Mivot instance to populate with attributes class_name : str Name of the property_instance class (dmtype). Used to get all the attribute roles (given by the model) of the class mapping : dict Dictionary associating model roles with their values. table : astropy.table Table (from parsed VOTable) of the mapped data dmtype : string common dmtype of object attributes as_literal : boolean, optional (default isTrue) If True, all attribute are set with literal values (@value="...") package : str, optional (default as None) Package name possibly prefixing dmroles """ mapped_roles = MivotUtils._valid_mapped_dmroles(mapping.items(), class_name) pkg = f"{package}." if package else "" for dmrole, column in mapped_roles: # minimal reserved characters escaping if isinstance(column, str): column = column.replace("&", "&") # force column references to be processed as literals if requested if as_literals: column = MivotUtils.as_literal(column) unit, _, _ = MivotUtils.get_field_attributes(table, column) if isinstance(column, bool): r_dmtype = IvoaType.bool else: r_dmtype = dmtype r_dmrole = dmrole.replace(":", f":{pkg}") property_instance.add_attribute(dmtype=r_dmtype, dmrole=r_dmrole, value=column, unit=unit) astropy-pyvo-b70558c/pyvo/mivot/utils/vocabulary.py000066400000000000000000000031531510533647000225670ustar00rootroot00000000000000""" MIVOT vocabulary and regular expressions. """ import re from astropy import units as u from pyvo.utils import prototype_feature class Constant: """ Class used to set constant to identify XML attributes added to the MIVOT ATTRIBUTES """ FIRST_TABLE = "first_table" FIELD_UNIT = "field_unit" COL_INDEX = "col_index" NOT_SET = "NotSet" ANONYMOUS_TABLE = "AnonymousTable" # Regexp pattern to check no valid mapping is present NoMapping = re.compile(r".REPORT\s+status=['\"]KO") unit_mapping = { "deg": u.degree, "rad": u.radian, "hourangle": u.hourangle, "arcsec": u.arcsec, "mas": u.mas, "pc": u.pc, "km": u.km, "m": u.m, "mas/yr": u.mas / u.yr, "mas/y": u.mas / u.yr, "km/s": u.km / u.s, } @prototype_feature('MIVOT') class Ele: """ Constant used to identify MIVOT Element """ namespace = "" VODML = namespace + "VODML" MODEL = namespace + "MODEL" GLOBALS = namespace + "GLOBALS" TEMPLATES = namespace + "TEMPLATES" INSTANCE = namespace + "INSTANCE" ATTRIBUTE = namespace + "ATTRIBUTE" COLLECTION = namespace + "COLLECTION" JOIN = namespace + "JOIN" REFERENCE = namespace + "REFERENCE" WHERE = namespace + "WHERE" NOROLE = "NOROLE" @prototype_feature('MIVOT') class Att: """ Constant used to identify attributes in MIVOT Element """ dmrole = "dmrole" dmtype = "dmtype" dmid = "dmid" name = "name" value = "value" dmref = "dmref" tableref = "tableref" sourceref = "sourceref" ref = "ref" primarykey = "PRIMARY_KEY" foreignkey = "foreignkey" astropy-pyvo-b70558c/pyvo/mivot/utils/xml_utils.py000066400000000000000000000121551510533647000224420ustar00rootroot00000000000000""" Utility class to process XML. """ import re from pyvo.mivot.utils.xpath_utils import XPath import xml.etree.ElementTree as ET from xml.dom import minidom from pyvo.mivot.utils.vocabulary import Constant from pyvo.mivot.utils.vocabulary import Att from pyvo.mivot.utils.exceptions import MivotError class XmlUtils: """ Static class implementing convenient operations on XML """ @staticmethod def pretty_print(xmltree, *, lshift=""): """ Pretty print an XML tree Parameters ---------- xmltree: (~`xml.etree.ElementTree.Element`) XML tree to pretty print lshift : str, optional, default "" Sequence to be inserted at the beginning of each line Usually a space sequence """ print(XmlUtils.pretty_string(xmltree, lshift=lshift)) @staticmethod def pretty_string(xmltree, *, lshift="", clean_namespace=True): """ Return a pretty string representation of an XML tree (as Etree or string) Parameters ---------- xmltree (~`xml.etree.ElementTree.Element`) or string: XML tree to convert to a pretty string lshift : str, optional, default "" Sequence to be inserted at the beginning of each line Usually a space sequence clean_namespace : boolean, optional, default True Default namespace (ns0) removed from element names if True Returns ------- str: The pretty string representation of the XML tree. """ if isinstance(xmltree, str): root = xmltree else: if hasattr(xmltree, 'getroot'): root = ET.tostring(xmltree.getroot(), encoding='unicode') else: root = ET.tostring(xmltree, encoding='unicode') root = root.replace("\n", "") reparsed = minidom.parseString(root) pretty_string = re.sub(r" +\n", "", reparsed.toprettyxml(indent=" ")) pretty_string = pretty_string.replace("\n", "") \ .replace("\n\n", "\n") \ .replace("<", f"{lshift}<") if clean_namespace: return pretty_string.replace("ns0:", "") else: return pretty_string @staticmethod def strip_xml(xml_string): """ Strip unnecessary whitespace and newline characters from an XML string. Used by unit tests to compare xml strings Parameters: - xml_string (str): The XML string to strip. Returns: - str: The stripped XML string. """ return ( xml_string.replace("\n", "").replace(" ", "").replace("'", "").replace('"', "") ) @staticmethod def add_column_indices(mapping_block, index_map): """ Add column ranks to attributes having a ref. Using ranks allows identifying columns even when NumPy arrays have been serialized as []. Parameters ---------- mapping_block : ~`xml.etree.ElementTree.Element` The XML mapping block. index_map : dict A dictionary mapping ref values to column indices. """ for ele in XPath.x_path(mapping_block, ".//ATTRIBUTE"): attr_ref = ele.get(Att.ref) if attr_ref is not None and attr_ref != Constant.NOT_SET: field_desc = None if attr_ref in index_map: field_desc = index_map[attr_ref] else: for _, value in index_map.items(): if value["ID"] == attr_ref: field_desc = value break if not field_desc: if not ele.get(Att.value): raise MivotError( f"Attribute {ele.get(Att.dmrole)} can not be set:" f" references a non existing column: {attr_ref} " f"and has no default value") else: ele.attrib.pop(Att.ref, None) if field_desc: ele.attrib[Constant.COL_INDEX] = str(field_desc["indx"]) if field_desc["ID"] != attr_ref: ele.set(Att.ref, field_desc["ID"]) @staticmethod def add_column_units(mapping_block, unit_map): """ Add field units to attributes having a ref. Used for performing unit conversions. Parameters ---------- mapping_block : ~`xml.etree.ElementTree.Element` The XML mapping block. unit_map : dict A dictionary mapping ref values to units. """ for ele in XPath.x_path(mapping_block, ".//ATTRIBUTE"): ref = ele.get(Att.ref) if ref is not None and ref != Constant.NOT_SET: unit = None if ref in unit_map: unit = unit_map[ref].__str__() else: unit = "" ele.attrib[Constant.FIELD_UNIT] = unit astropy-pyvo-b70558c/pyvo/mivot/utils/xpath_utils.py000066400000000000000000000064121510533647000227650ustar00rootroot00000000000000""" Utility class performing XPath queries on XML trees. """ class XPath: """ Static class use to perform XPath queries on XML trees. """ @staticmethod def x_path(etree, path): """ Return all the elements of the XML tree that match the given XPath query. Parameters ---------- etree : ~`xml.etree.ElementTree.Element` The XML tree to query. path : str The XPath query to perform. Returns ------- list The list of all the elements of the XML tree that match the given XPath query. """ return etree.findall(path) @staticmethod def x_path_contains(etree, path, key, value): """ Return all the elements of the XML tree that match the given XPath query with a given attribute containing a given value. Example of a path: ".//INSTANCE[contains(@dmtype,'dmtype_pattern')]" Parameters ---------- etree : ~`xml.etree.ElementTree.Element` The XML tree to query. path : str The XPath query to perform. key : str The attribute to look for. value : str The value to look for. Returns ------- list The list of all the elements of the XML tree that match the given XPath query with a given attribute containing a given value. """ result = [] xset = etree.findall(path) for ele in xset: if value in ele.get(key): result.append(ele) return result @staticmethod def x_path_startwith(etree, path): """ Return all the elements of the XML tree that match the given XPath query with a given Tag starting with a given value. Example of a path: ".//*[starts-with(name(), 'REFERENCE_')]" This function is only used in the static reference resolver to find all the REFERENCEs and JOINs. It adds a counter to the path to find all the REFERENCEs and JOINs. Parameters ---------- etree : `xml.etree.ElementTree.Element` The XML tree to query. path : str The XPath query to perform. Returns ------- list The list of all the elements of the XML tree that match the given XPath query. """ return {elem for elem in etree.iter() if elem.tag.startswith("REFERENCE_")} @staticmethod def select_elements_by_atttribute(etree, element, attribute, attribute_value): """ Select all xml elements named 'element' having @attribute=attribute_value Parameters ---------- etree : `xml.etree.ElementTree.Element` The XML tree to query. element: str name of the searched elements attribute: str name of the element attribute used for the selection attribute_value: str attribute value of the selected elements Returns ------- list The list of all the elements of the XML tree that match the given XPath query. """ return XPath.x_path(etree, f'.//{element}[@{attribute}="{attribute_value}"]' ) astropy-pyvo-b70558c/pyvo/mivot/version_checker.py000066400000000000000000000006621510533647000224330ustar00rootroot00000000000000from astropy import version as astropy_version def check_astropy_version(): """ Check if the installed version of astropy is compatible with MIVOT. """ if not astropy_version.version: return False if astropy_version.version < "6.0": print(f"Astropy version {astropy_version.version} is below " f"the required version 6.0 for the use of MIVOT.") return False return True astropy-pyvo-b70558c/pyvo/mivot/viewer/000077500000000000000000000000001510533647000202055ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/viewer/__init__.py000066400000000000000000000002201510533647000223100ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .mivot_instance import MivotInstance from .mivot_viewer import MivotViewer astropy-pyvo-b70558c/pyvo/mivot/viewer/mivot_instance.py000066400000000000000000000214671510533647000236130ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ MivotInstance is the root of the Python generated classes. Instances of MivotInstance are built from a dictionary issued from the XML view of the mapped model. This dictionary is used to extend the object with all components (classes, attributes, collections) necessary to reproduce the structure of the mapped model. Instances of this class are built by `pyvo.mivot.viewer.mivot_viewer`. Although attribute values can be changed by users, this class is first meant to provide a convenient access the mapped VOTable data """ from pyvo.utils.prototype import prototype_feature from pyvo.mivot.utils.mivot_utils import MivotUtils from pyvo.mivot.utils.dict_utils import DictUtils from pyvo.mivot.features.sky_coord_builder import SkyCoordBuilder # list of model leaf parameters that must be hidden for the final user hk_parameters = ["ref"] @prototype_feature('MIVOT') class MivotInstance: """ MivotInstance holds the dictionary (__dict__) similar with the mapped model structure where the references have been resolved. The dictionary keeps the hierarchy of the XML : "key" : {not a leaf} means key is the dmtype of an INSTANCE "key" : {leaf} means key is the dmrole of an ATTRIBUTE "key" : "value" means key is an element of ATTRIBUTE "key" : [] means key is the dmtype of a COLLECTION """ def __init__(self, **instance_dict): """ Constructor of the MIVOT class. Parameters ---------- kwargs (dict): Dictionary of the XML object. """ self._create_class(**instance_dict) def __str__(self): """ return a human readable representation of object """ return f"" def __repr__(self): """ return a human readable (json) unambigous representation of object """ return DictUtils._get_pretty_json(self.to_dict()) def to_hk_dict(self): """ return a human readable (dict) representation of object with a few housekeeping data such as column references. This might be used to apply the mapping out of the MivotViewer context """ return self._get_class_dict(self, slim=False) def to_dict(self): """ return a human readable (dict) representation of object """ return self._get_class_dict(self, slim=True) def _create_class(self, **kwargs): """ Recursively initialize the MIVOT class with the dictionary of the XML object got in MivotViewer. For the unit of the ATTRIBUTE, we add the Astropy unit or the Astropy time equivalence by comparing the value of the unit with values in time.TIME_FORMATS.keys() which is the list of time formats. We do the same with the unit_mapping dictionary, which is the list of Astropy units. Parameters ---------- kwargs (dict): Dictionary of the XML object. """ for key, value in kwargs.items(): if isinstance(value, list): # COLLECTION setattr(self, self._remove_model_name(key), []) for item in value: getattr(self, self._remove_model_name(key)).append(MivotInstance(**item)) elif isinstance(value, dict): # INSTANCE if not self._is_leaf(**value): setattr(self, self._remove_model_name(key), MivotInstance(**value)) if self._is_leaf(**value): setattr(self, self._remove_model_name(key), MivotInstance(**value)) else: # ATTRIBUTE if key == 'value': # We cast the value read in the row setattr(self, self._remove_model_name(key), MivotUtils.cast_type_value(value, getattr(self, 'dmtype'))) elif key not in ["dmtype", "dmrole"]: setattr(self, self._remove_model_name(key), self._remove_model_name(value)) else: setattr(self, self._remove_model_name(key), value) if key == 'unit': # We convert the unit to astropy unit or to astropy time format if possible # The first Vizier implementation used mas/year for the mapped pm unit: let's correct it value = value.replace("year", "yr") if value else None def update(self, row, ref=None): """ Update the MIVOT class with the new data row. For each leaf of the MIVOT class, we update the value with the new data row. Parameters ---------- row (astropy.table.row.Row): The new data row. ref (str, optional):The reference of the data row, default is None. """ for key, value in vars(self).items(): if isinstance(value, list): for item in value: item.update(row=row) elif isinstance(value, MivotInstance): if isinstance(vars(value), dict): if 'value' not in vars(value): value.update(row=row) if 'ref' in vars(value): value.update(row=row, ref=getattr(value, 'ref')) else: if key == 'value' and ref is not None and ref != 'null': setattr(self, self._remove_model_name(key), MivotUtils.cast_type_value(row[ref], getattr(self, 'dmtype'))) def get_SkyCoord(self): """ returns ------- - a SkyCoord instance or None """ return SkyCoordBuilder(self).build_sky_coord() @staticmethod def _remove_model_name(value): """ Return the last element of a model path built like model:a.b.c Parameters ---------- value (str): The string to process. """ if value: next_index_underscore = value.rfind(".") return value[next_index_underscore + 1:] return value def _is_leaf(self, **kwargs): """ Check if the dictionary is an ATTRIBUTE. Parameters ---------- **kwargs (dict): The dictionary to check. Returns ------- bool: True if the dictionary is an ATTRIBUTE, False otherwise. """ if isinstance(kwargs, dict): for _, value in kwargs.items(): if isinstance(value, dict): return False return True def _get_class_dict(self, obj, classkey=None, slim=False, with_dmtypes=True): """ Recursively displays a serializable dictionary. This function is only used for debugging purposes. Parameters ---------- obj (dict or object): The dictionary or object to display. classkey (str, optional): The key to use for the object's class name in the dictionary, default is None. slim (bool, optional): if true, only @values and @units (if not empty) are attached to model leaves. @dmtype and @ref attributes are ignored with_dmtypes (boolean, optional) : if true dmtypes are added to the primitive types (model leaves) Returns ------- dict or object The serializable dictionary representation of the input. """ # This case is likely not to occur because MIVOT does not support dictionaries if isinstance(obj, dict): data = {} for (k, v) in obj.items(): data[k] = self._get_class_dict(v, classkey, slim=slim) return data elif hasattr(obj, "_ast"): return self._get_class_dict(obj._ast()) elif hasattr(obj, "__iter__") and not isinstance(obj, str): return [self._get_class_dict(v, classkey, slim=slim) for v in obj] elif hasattr(obj, "__dict__"): data = {key: obj._get_class_dict(value, classkey, slim=slim) for key, value in obj.__dict__.items() if not callable(value) and not key.startswith('_')} # remove the house keeping parameters if slim is True: # data is atomic value (e.g. float): the type be hidden if with_dmtypes is False and ("ref" in data or "value" in data): data.pop("dmtype", None) # remove unit when not set if "unit" in data and not data["unit"]: data.pop("unit", None) for hk_parameter in hk_parameters: data.pop(hk_parameter, None) if classkey is not None and hasattr(obj, "__class__"): data[classkey] = obj.__class__.__name__ return data else: return obj astropy-pyvo-b70558c/pyvo/mivot/viewer/mivot_viewer.py000066400000000000000000000451301510533647000233010ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ MivotViewer implements the user API for accessing mapped data. - extracts the annotation block from the VOTable - builds an XML view of the mapped model - updates the leaves of the XML view with the values read in the data rows - builds Python instances providing access to the mapped values by object attributes. The code below shows a typical use of `MivotViewer .. code-block:: python with MivotViewer(path_to_votable) as mivot_viewer: print(f"mapped class id {mivot_instance.dmtype}") print(f"space frame is {mivot_instance.Coordinate_coordSys.spaceRefFrame.value}") mivot_object = mivot_viewer.dm_instance while mivot_viewer.next_row_view(): print(f"latitude={mivot_object.latitude.value}") print(f"longitude={mivot_object.longitude.value}") See `tests/test_user_api.py`to get different examples of the API usage. """ import logging from copy import deepcopy from astropy import version from astropy.io.votable import parse from astropy.io.votable.tree import VOTableFile from pyvo.dal import DALResults from pyvo.mivot.utils.vocabulary import Ele, Att from pyvo.mivot.utils.vocabulary import Constant, NoMapping from pyvo.mivot.utils.exceptions import (MappingError, MivotError, AstropyVersionException) from pyvo.mivot.utils.xml_utils import XmlUtils from pyvo.mivot.utils.xpath_utils import XPath from pyvo.mivot.seekers.annotation_seeker import AnnotationSeeker from pyvo.mivot.seekers.resource_seeker import ResourceSeeker from pyvo.mivot.seekers.table_iterator import TableIterator from pyvo.mivot.features.static_reference_resolver import StaticReferenceResolver from pyvo.mivot.version_checker import check_astropy_version from pyvo.mivot.viewer.mivot_instance import MivotInstance from pyvo.utils.prototype import prototype_feature from pyvo.mivot.utils.mivot_utils import MivotUtils # Use defusedxml only if already present in order to avoid a new depency. try: from defusedxml import ElementTree as etree except ImportError: from xml.etree import ElementTree as etree @prototype_feature('MIVOT') class MivotViewer: """ MivotViewer is a PyVO table wrapper aiming at providing a model view on VOTable data read with usual tools. """ def __init__(self, votable_path, tableref=None, resolve_ref=False): """ Constructor of the MivotViewer class. Parameters ---------- votable_path : str, DALResults, VOTableFile Reference of the VOTable from which Astropy will extracts the annotation block tableref : str, optional Used to identify the table to process. If not specified, the first table is taken by default. resolve_ref : boolean Ask for references between MIVOT instances to be resolved (referenced instances are copied into the host object). This is usually used to copy the coordinates systems into the object that uses them. Parameters ---------- resolve_ref : bool, optional If True, replace the REFERENCE elements with a copy of the objects they refer to. e.g. copy the space coordinates system, usually located in the GLOBALS block, in the position objects Default is False. """ if not check_astropy_version(): raise AstropyVersionException(f"Astropy version {version.version} " f"is below the required version 6.0 for the use of MIVOT.") if isinstance(votable_path, DALResults): self._parsed_votable = votable_path.votable elif isinstance(votable_path, VOTableFile): self._parsed_votable = votable_path else: self._parsed_votable = parse(votable_path) self._table_iterator = None self._connected_table = None self._connected_tableref = None self._current_data_row = None # when the search object is in GLOBALS self._globals_instance = None self._last_row = None self._templates = None self._resource = None self._annotation_seeker = None self._mapping_block = None self._mapped_tables = [] self._resource_seeker = None self._dm_instances = [] self._dm_globals_instances = [] self._resolve_ref = resolve_ref try: self._set_resource() self._set_mapping_block() self._resource_seeker = ResourceSeeker(self._resource) self._set_mapped_tables() self._connect_table(tableref) self._init_instances() self._init_globals_instances() except MappingError as mnf: logging.error(str(mnf)) def __enter__(self): """ with statement implementation """ return self def __exit__(self, exc_type, exc_value, traceback): """ with statement implementation """ logging.info("MivotViewer closing..") def close(self): """ with statement implementation """ logging.info("MivotViewer is closed") @property def votable(self): """ returns the Astropy parsed votable """ return self._parsed_votable @property def annotation_seeker(self): """ Return an API to search various components in the XML mapping block. """ return self._annotation_seeker @property def resource_seeker(self): """ Return an API to search various components in the VOTabel resource. """ return self._resource_seeker @property def connected_table(self): """ getter for the identifier the astropy.table instance the viewer is connected to """ return self._connected_table @property def connected_table_ref(self): """ getter for the identifier the table the viewer is connected to """ return self._connected_tableref @property def dm_instance(self): """ returns ------- MivotInstance: The Python object (MivotInstance) built from the XML view of the first 'TEMPLATES' child, with the attribute values set according to the values of the current read data row. """ dm_instances = self._dm_instances return self.dm_instances[0] if dm_instances else None @property def dm_instances(self): """ Returns ------- [MivotInstance]: The list of Python objects (MivotInstance) built from the XML views of the TEMPLATES children, whose attribute values are set from the values of the current read data row. """ return self._dm_instances @property def dm_globals_instances(self): """ Returns ------- [MivotInstance]: The list of Python objects (MivotInstance) built from the XML views of the GLOBALS children, whose attribute values are set from the values of the current read data row. This method allows to retrieve the GLOBALS (coordinates systems usually) even when the viewer is in ``resolve_ref=False`` mode or if the reference to the coordinates systems have not been setup in the objects representing the mapped data. """ return self._dm_globals_instances @property def table_row(self): """ getter for the current astropy.table.array row """ return self._current_data_row def next_row_view(self): """ jump to the next table row and update the MivotInstance instance with the row values Returns ------- [MivotInstance] List of updated instances or None it he able end has been reached """ self.next_table_row() if self._current_data_row is None: return None self._init_instances() for dm_instance in self._dm_instances: dm_instance.update(self._current_data_row) return self._dm_instances def get_table_ids(self): """ Return a list of the table located just below self._resource. """ if self.resource_seeker is None: return None return self.resource_seeker.get_table_ids() def get_models(self): """ Get a dictionary of models and their URLs. Returns ------- dict: Model names and a lists of their URLs. The format is {'model': [url], ...}. """ if self._annotation_seeker is None: return None return self._annotation_seeker.get_models() def next_table_row(self): """ Iterate once on the table row Returns: numpy row: the current table row of None if the end of the table has been reached """ if self._table_iterator is None: return None self._current_data_row = self._table_iterator.get_next_row() return self._current_data_row def rewind(self): """ Rewind the table iterator on the table the veizer is connected with. """ if self._table_iterator: self._table_iterator.rewind() def _get_templates_child_instances(self, tableref=None): """ Returns ------- [`xml.etree.ElementTree.Element`] List of all INSTANCES elements children of the current TEMPLATES block """ if self._annotation_seeker is None: return None templates_block = self._annotation_seeker.get_templates_block(tableref) return XPath.x_path(templates_block, ".//" + Ele.INSTANCE) def get_dm_instance_dmtypes(self, tableref): """ Return the dmtypes of the INSTANCEs children of the TEMPLATES block mapping the data table identified by tableref. Parameters ---------- tableref : str or None Identifier of the data table. Returns ------- [string] list of dmtypes Raises ------ MivotError if no INSTANCE can be found """ dmtypes = [] templates_block = self._annotation_seeker.get_templates_block(tableref) instances = XPath.x_path(templates_block, ".//" + Ele.INSTANCE) for instance in instances: dmtypes.append(instance.get(Att.dmtype)) if not dmtypes: raise MivotError("Can't find " + Ele.INSTANCE + " in " + Ele.TEMPLATES) return dmtypes def _connect_table(self, tableref=None): """ Iterate over the table identified by tableref. Required to browse table data. Connect to the first table if tableref is None. Parameters ---------- tableref : str or None, optional Identifier of the table. If None, connects to the first table. """ if not self._resource_seeker: raise MappingError("No mapping block found") stableref = tableref if tableref is None: stableref = "" self._connected_tableref = Constant.FIRST_TABLE logging.debug("Since " + Ele.TEMPLATES + "@table_ref is None, " "the mapping will be applied to the first table." ) elif tableref not in self._mapped_tables: raise MappingError(f"The table {self._connected_tableref} doesn't match with any " f"mapped_table ({self._mapped_tables}) encountered in " + Ele.TEMPLATES ) else: self._connected_tableref = tableref self._connected_table = self._resource_seeker.get_table(tableref) if self.connected_table is None: raise MivotError(f"Cannot find table {stableref} in VOTable") logging.debug("table %s found in VOTable", stableref) self._templates = deepcopy(self.annotation_seeker.get_templates_block(tableref)) if self._templates is None: raise MivotError("Cannot find " + Ele.TEMPLATES + f" {stableref} ") logging.debug(Ele.TEMPLATES + " %s found ", stableref) self._table_iterator = TableIterator(self._connected_tableref, self.connected_table.to_table()) self._squash_join_and_references() self._set_column_indices() self._set_column_units() def _get_model_view(self, xml_instance): """ Return an XML model view of the last read row. - References are possibly resolved here. - ``ATTRIBUTE@value`` are set with actual data row values Returns ------- `xml.etree.ElementTree.Element` XML model view of the last read row. """ templates_copy = deepcopy(xml_instance) if self._resolve_ref is True: while StaticReferenceResolver.resolve(self._annotation_seeker, self._connected_tableref, templates_copy) > 0: pass # Make sure the instances of the resolved references # have both indexes and unit attribute XmlUtils.add_column_indices(templates_copy, self._resource_seeker .get_id_index_mapping(self._connected_tableref)) XmlUtils.add_column_units(templates_copy, self._resource_seeker .get_id_unit_mapping(self._connected_tableref)) for ele in XPath.x_path(templates_copy, ".//ATTRIBUTE"): ref = ele.get(Att.ref) if ref is not None and ref != Constant.NOT_SET and Constant.COL_INDEX in ele.attrib: index = ele.attrib[Constant.COL_INDEX] ele.attrib[Att.value] = str(self._current_data_row[int(index)]) return templates_copy def _init_instances(self): """ Read the first table row and build all MivotInstances (_dm_instances attribute) from it. The table row iterator in rewind at the end to make sure we won't lost the first data row. """ if not self._dm_instances: self.next_table_row() xml_instances = self._get_templates_child_instances(self.connected_table_ref) self._dm_instances = [] for xml_instance in xml_instances: self._dm_instances.append( MivotInstance( **MivotUtils.xml_to_dict(self._get_model_view(xml_instance)) )) self.rewind() def _init_globals_instances(self): """ Build one MivotInstance for each GLOBALS/INSTANCE. Internal references are always resolved Globals MivotInstance are stored in the _dm_globals_instances list """ if not self._dm_globals_instances: globals_copy = deepcopy(self._annotation_seeker.globals_block) while StaticReferenceResolver.resolve(self._annotation_seeker, None, globals_copy) > 0: pass for ele in XPath.x_path(globals_copy, "./" + Ele.INSTANCE): self._dm_globals_instances.append( MivotInstance( **MivotUtils.xml_to_dict(ele) )) def _set_mapped_tables(self): """ Set the _mapped_tables list with the TEMPLATES tablerefs. """ if not self.resource_seeker: self._mapped_tables = [] else: self._mapped_tables = self._annotation_seeker.get_templates() def _set_resource(self): """ select the first resource with @type=results The annotations, if there are, are supposed to be there. The case of multiple 'results' annotated is not taken into account yest """ if len(self._parsed_votable.resources) < 1: raise MivotError("No resource detected in the VOTable") rnb = 0 for res in self._parsed_votable.resources: if res.type.lower() == "results": logging.info("Resource %s selected", rnb) self._resource = self._parsed_votable.resources[rnb] return rnb += 1 raise MivotError("No resource @type='results'detected in the VOTable") def _set_mapping_block(self): """ Set the mapping block found in the resource and set the annotation_seeker """ if NoMapping.search(self._resource.mivot_block.content): raise MappingError("Mivot block is not found") # The namespace should be removed self._mapping_block = ( etree.fromstring(self._resource.mivot_block.content .replace('xmlns="http://www.ivoa.net/xml/mivot"', '') .replace("xmlns='http://www.ivoa.net/xml/mivot'", ''))) self._annotation_seeker = AnnotationSeeker(self._mapping_block) logging.info("Mapping block found") def _squash_join_and_references(self): """ Remove both JOINs and REFERENCEs from the templates and store them in to be resolved later on. This prevents the model view of being polluted with elements that are not in the model """ for ele in XPath.x_path_startwith(self._templates, ".//REFERENCE_"): if ele.get("sourceref") is not None: self._dyn_references = {ele.tag: deepcopy(ele)} for child in list(ele): ele.remove(child) for ele in XPath.x_path_startwith(self._templates, ".//JOIN_"): self._joins = {ele.tag: deepcopy(ele)} for child in list(ele): ele.remove(child) def _set_column_indices(self): """ Add column ranks to attribute having a ref. Using ranks allow identifying columns even when numpy raw have been serialised as [] """ index_map = self._resource_seeker.get_id_index_mapping(self._connected_tableref) XmlUtils.add_column_indices(self._templates, index_map) def _set_column_units(self): """ Add field unit to attribute having a ref. Used for performing unit conversions """ unit_map = self._resource_seeker.get_id_unit_mapping(self._connected_tableref) XmlUtils.add_column_units(self._templates, unit_map) astropy-pyvo-b70558c/pyvo/mivot/writer/000077500000000000000000000000001510533647000202205ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/mivot/writer/__init__.py000066400000000000000000000004351510533647000223330ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .annotations import MivotAnnotations from .instance import MivotInstance from .instances_from_models import InstancesFromModels from .header_mapper import HeaderMapper from .mango_object import MangoObject, Property astropy-pyvo-b70558c/pyvo/mivot/writer/annotations.py000066400000000000000000000303001510533647000231230ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ MivotAnnotations: A utility module to build and manage MIVOT annotations. """ import os import logging try: import xmlschema except ImportError: xmlschema = None # Use defusedxml only if already present in order to avoid a new dependency. try: from defusedxml import ElementTree as etree except ImportError: from xml.etree import ElementTree as etree from astropy.io.votable.tree import VOTableFile, Resource try: from astropy.io.votable.tree import MivotBlock except ImportError: pass from astropy.io.votable import parse from astropy import version from pyvo.utils.prototype import prototype_feature from pyvo.mivot.utils.xml_utils import XmlUtils from pyvo.mivot.utils.exceptions import MappingError, AstropyVersionException from pyvo.mivot.writer.instance import MivotInstance from pyvo.mivot.version_checker import check_astropy_version __all__ = ["MivotAnnotations"] @prototype_feature("MIVOT") class MivotAnnotations: """ This module provides a class to construct, validate, and insert MIVOT blocks into VOTable files. The MIVOT block, represented as an XML structure, is used for data model annotations in the IVOA ecosystem. The main features are: - Construct the MIVOT block step-by-step with various components. - Validate the MIVOT block against the MIVOT XML schema (if ``xmlschema`` is installed). - Embed the MIVOT block into an existing VOTable file. The MIVOT block is constructed as a string to maintain compatibility with the Astropy API. Attributes ---------- suggested_space_frames: string array, class attribute A warning is emitted if a frame not in this list is used to build a space frame. This list matches https://www.ivoa.net/rdf/refframe/2022-02-22/refframe.html. suggested_ref_positions: string array, class attribute A warning is emitted if a reference position not in this list is used to build a space or a time frame. suggested_time_frames: string array, class attribute A warning is emitted if a frame not in this list is used to build a space frame. This list matches https://www.ivoa.net/rdf/timescale/2019-03-15/timescale.html. """ def __init__(self): """ """ # Dictionary containing models with their names as keys and URLs as values self._models = {} # str: Indicates the success status of the annotation process.s self._report_status = True # str: message associated with the report, used in the REPORT block. self._report_message = "Generated by pyvo.mivot.writer" # list(str or MivotInstance): GLOBALS blocks to be included in the MIVOT block. self._globals = [] # list(str or MivotInstance): TEMPLATES blocks to be included in the MIVOT block. self._templates = [] # str: An optional ID for the TEMPLATES block. self._templates_id = "" # str: list of the dmid of the INSTANCE stored in the GLOBALS self._dmids = [] # str: Complete MIVOT block as a string self._mivot_block = "" @property def mivot_block(self): """ Getter for the whole MIVOT block. Returns ------- str Complete MIVOT block as a string. """ return self._mivot_block def _get_report(self): """ Generate the component of the MIVOT block. Returns ------- str The block as a string, indicating the success or failure of the process. """ if self._report_status: return f'{self._report_message}' else: return f'{self._report_message}' def _get_models(self): """ Generate the components of the MIVOT block. Returns ------- str The components as a formatted string. """ models_block = "" for key, value in self._models.items(): if value: models_block += f'\n' else: models_block += f'\n' return models_block def _get_globals(self): """ Generate the component of the MIVOT block. Returns ------- str The block as a formatted string. """ globals_block = "\n" for glob in self._globals: globals_block += f"{glob}\n" globals_block += "\n" return globals_block def _get_templates(self): """ Generate the component of the MIVOT block. Returns ------- str The block as a formatted string, or an empty string if no templates are defined. """ if not self._templates: return "" if not self._templates_id: templates_block = "\n" else: templates_block = f'\n' for templates in self._templates: templates_block += f"{templates}\n" templates_block += "\n" return templates_block def build_mivot_block(self, *, templates_id=None, schema_check=True): """ Build a complete MIVOT block from the declared components and validates it against the MIVOT XML schema. Parameters ---------- templates_id : str, optional The ID to associate with the block. Defaults to None. schema_check : boolean, optional (default True) Skip the XSD validation if False (use to make test working in local mode). Raises ------ Any exceptions raised during XML validation are not caught and must be handled by the caller. """ if templates_id: self._templates_id = templates_id self._mivot_block = '\n' self._mivot_block += self._get_report() self._mivot_block += "\n" self._mivot_block += self._get_models() self._mivot_block += "\n" self._mivot_block += self._get_globals() self._mivot_block += "\n" self._mivot_block += self._get_templates() self._mivot_block += "\n" self._mivot_block += "\n" self._mivot_block = self.mivot_block.replace("\n\n", "\n") self._mivot_block = XmlUtils.pretty_string(self._mivot_block) if schema_check: self.check_xml() def add_templates(self, templates_instance): """ Add an element to the block. Parameters ---------- templates_instance : str or MivotInstance The element to be added. Raises ------ MappingError If ``templates_instance`` is neither a string nor an instance of `MivotInstance`. """ if isinstance(templates_instance, MivotInstance): self._templates.append(templates_instance.xml_string()) if templates_instance.dmid is not None: self._dmids.append(templates_instance.dmid) elif isinstance(templates_instance, str): self._templates.append(templates_instance) else: raise MappingError( "Instance added to templates must be a string or MivotInstance." ) def add_globals(self, globals_instance): """ Add an block to the block. Parameters ---------- globals_instance : str or MivotInstance The block to be added. Raises ------ MappingError If ``globals_instance`` is neither a string nor an instance of `MivotInstance`. """ if isinstance(globals_instance, MivotInstance): self._globals.append(globals_instance.xml_string()) if globals_instance.dmid is not None: self._dmids.append(globals_instance.dmid) elif isinstance(globals_instance, str): self._globals.append(globals_instance) else: raise MappingError( "Instance added to globals must be a string or MivotInstance." ) def add_model(self, model_name, *, vodml_url=None): """ Add a element to the MIVOT block. Parameters ---------- model_name : str The short name of the model. vodml_url : str, optional The URL of the VO-DML file associated with the model. """ self._models[model_name] = vodml_url def set_report(self, status, message): """ Set the element of the MIVOT block. Parameters ---------- status : bool The status of the annotation process. True for success, False for failure. message : str The message associated with the REPORT. Notes ----- If ``status`` is False, all components of the MIVOT block except MODEL and REPORT are cleared. """ self._report_status = status self._report_message = message if not status: self._globals = [] self._templates = [] def check_xml(self): """ Validate the MIVOT block against the MIVOT XML schema v1.0. Raises ------ MappingError If the validation fails. Notes ----- The schema (mivot 1.0) is loaded from a local file to avoid dependency on a remote service. """ # put here just to improve the test coverage root = etree.fromstring(self._mivot_block) mivot_block = XmlUtils.pretty_string(root, clean_namespace=False) if not xmlschema: logging.warning( "XML validation skipped: no XML schema validator found. " + "Please install it (e.g., pip install xmlschema)." ) return schema = xmlschema.XMLSchema11(os.path.dirname(__file__) + "/mivot-v1.xsd") try: schema.validate(mivot_block) except Exception as excep: raise MappingError(f"Validation failed: {excep}") from excep def insert_into_votable(self, votable_file, override=False): """ Insert the MIVOT block into a VOTable. Parameters ---------- votable_file : str or VOTableFile The VOTable to be annotated, either as a file path or a ``VOTableFile`` instance. override : bool If True, overrides any existing annotations in the VOTable. Raises ------ MappingError If a mapping block already exists and ``override`` is False. """ if not check_astropy_version(): raise AstropyVersionException(f"Astropy version {version.version} " "is below the required version 6.0 for the use of MIVOT.") if isinstance(votable_file, str): votable = parse(votable_file) elif isinstance(votable_file, VOTableFile): votable = votable_file else: raise MappingError( "votable_file must be a file path string or a VOTableFile instance, " "not a {type(votable_file)}." ) for resource in votable.resources: if resource.type == "results": for subresource in resource.resources: if subresource.type == "meta": if not override: raise MappingError( "A type='meta' resource already exists in the first 'result' resource." ) else: logging.info("Overriding existing type='meta' resource.") break mivot_resource = Resource() mivot_resource.type = "meta" mivot_resource.mivot_block = MivotBlock(self._mivot_block) resource.resources.append(mivot_resource) astropy-pyvo-b70558c/pyvo/mivot/writer/header_mapper.py000066400000000000000000000276201510533647000233750ustar00rootroot00000000000000""" ``HeaderMapper`` class source """ import logging from pyvo.mivot.glossary import Roles, EpochPositionAutoMapping from pyvo.mivot.utils.dict_utils import DictUtils class HeaderMapper: """ This utility class generates dictionaries from header elements of a VOTable. These dictionaries are used as input parameters by `pyvo.mivot.writer.InstancesFromModels` to create MIVOT instances that are placed in the GLOBALS block or in the TEMPLATES. In the current implementation, the following elements can be extracted: - COOSYS -> coords:SpaceSys - TIMESYS - coords:TimeSys - INFO -> mango:QueryOrigin - FIELD -> mango:EpochPosition """ def __init__(self, votable): """ Constructor parameters: Parameters ---------- votable : astropy.io.votable.tree.VOTableFile parsed votable from which INFO element are processed """ self._votable = votable def _check_votable_head_element(self, parameter): """ Check that the parameter is a valid value. .. note:: Vizier uses the ``UNKNOWN`` word to tag not set values """ return parameter is not None and parameter != "UNKNOWN" def _extract_query_origin(self): """ Create a mapping dictionary from ``INFO`` elements found in the VOTable header. This dictionary is used to populate the ``mango:QueryOrigin`` attributes Returns ------- dict Dictionary that is part of the input parameter for :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin` """ mapping = {} for info in self._votable.infos: if info.name in Roles.QueryOrigin: mapping[info.name] = info.value return mapping def _extract_data_origin(self, resource): """ Create a mapping dictionary from ``INFO`` elements found in the header of the first VOTable resource. This dictionary is used to populate the ``mango:QueryOrigin`` part of the ``mango:QueryOrigin`` instance. Returns ------- dict Dictionary that is part of the input parameter for :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin` """ mapping = {} article = None for info in resource.infos: if info.name in Roles.DataOrigin or info.name == "creator": if DictUtils.add_array_element(mapping, "dataOrigin"): article = {} if info.name != "creator": article[info.name] = info.value else: DictUtils.add_array_element(article, "creators") article["creators"].append(info.value) for info in resource.infos: art_ref = {} if info.name in Roles.Article: DictUtils.add_array_element(article, "articles") art_ref[info.name] = info.value if art_ref: article["articles"].append(art_ref) mapping["dataOrigin"].append(article) return mapping def extract_origin_mapping(self): """ Create a mapping dictionary from all VOTable ``INFO`` elements. This dictionary is used to build a ``mango:QueryOrigin`` INSTANCE - INFO elements located in the VOTable header are used to build the ``mango:QueryOrigin`` part which scope is the whole VOtable by construction (one query -> one VOTable) - INFO elements located in the resource header are used to build the ``mango:DataOrigin`` part which scope is the data located in this resource. Returns ------- dict Dictionary that can be used as input parameter for :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_query_origin` """ mapping = self._extract_query_origin() for resource in self._votable.resources: mapping = {**mapping, **self._extract_data_origin(resource)} return mapping def extract_coosys_mapping(self): """ Create a mapping dictionary for each ``COOSYS`` element found in the first VOTable resource. Returns ------- [dict] Array of dictionaries which items can be used as input parameter for :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_simple_space_frame` """ mappings = [] for resource in self._votable.resources: for coordinate_system in resource.coordinate_systems: mapping = {} if not self._check_votable_head_element(coordinate_system.system): logging.warning(f"Not valid COOSYS found: ignored in MIVOT: {coordinate_system}") continue mapping["spaceRefFrame"] = coordinate_system.system if self._check_votable_head_element(coordinate_system.equinox): mapping["equinox"] = coordinate_system.equinox if self._check_votable_head_element(coordinate_system.epoch): mapping["epoch"] = coordinate_system.epoch mappings.append(mapping) return mappings def extract_timesys_mapping(self): """ Create a mapping dictionary for each ``TIMESYS`` element found in the first VOTable resource. .. note:: the ``origin`` attribute is not supported yet Returns ------- [dict] Array of dictionaries which items can be used as input parameter for :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_simple_time_frame` """ mappings = [] for resource in self._votable.resources: for time_system in resource.time_systems: mapping = {} if not self._check_votable_head_element(time_system.timescale): logging.warning(f"Not valid TIMESYS found: ignored in MIVOT: {time_system}") continue mapping["timescale"] = time_system.timescale if self._check_votable_head_element(time_system.refposition): mapping["refPosition"] = time_system.refposition mappings.append(mapping) return mappings def extract_epochposition_mapping(self): """ Analyze the FIELD UCD-s to infer a data mapping to the EpochPosition class. This mapping covers the 6 parameters with the Epoch and their errors. The correlation part is not covered since there is no specific UCD for this. The UCD-s accepted for each parameter are defined in `pyvo.mivot.glossary`. The error classes are hard-coded as the most likely types. - PErrorSym2D for 2D parameters - PErrorSym1D for 1D parameters Returns ------- (dict, dict) A mapping proposal for the EpochPosiion + errors that can be used as input parameter by :py:meth:`pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position`. """ def _check_ucd(mapping_entry, ucd, mapping): """ Inner function checking that mapping_entry matches with ucd according to `pyvo.mivot.glossary` """ if mapping_entry in mapping: return False dict_entry = getattr(EpochPositionAutoMapping, mapping_entry) if isinstance(dict_entry, list): return ucd in dict_entry else: return ucd and ucd.startswith(dict_entry) def _check_obs_date(field): """ check if the field can be interpreted as a value date time This algorithm is a bit specific for Vizier CS """ xtype = field.xtype unit = field.unit representation = None if xtype == "timestamp" or unit == "'Y:M:D'" or unit == "'Y-M-D'": representation = "iso" # let's assume that dates expressed as days are MJD elif xtype == "mjd" or unit == "d": representation = "mjd" if representation is None and unit == "year": representation = "year" if representation is not None: field_ref = field.ID if field.ID is not None else field.name return {"dateTime": field_ref, "representation": representation} return None table = self._votable.get_first_table() fields = table.fields mapping = {} error_mapping = {} for field in fields: ucd = field.ucd for mapping_entry in Roles.EpochPosition: if _check_ucd(mapping_entry, ucd, mapping) is True: if mapping_entry == "obsDate": if (obs_date_mapping := _check_obs_date(field)) is not None: mapping[mapping_entry] = obs_date_mapping else: mapping[mapping_entry] = field.ID if field.ID is not None else field.name # Once we got a parameter mapping, we look for its associated error # This nested loop makes sure we never have error without value for err_field in fields: err_ucd = err_field.ucd # We assume the error UCDs are the same the these of the # related quantities but prefixed with "stat.error;" and without "meta.main" qualifier if err_ucd == ("stat.error;" + ucd.replace(";meta.main", "")): param_mapping = err_field.ID if err_field.ID is not None else err_field.name if mapping_entry == "parallax": error_mapping[mapping_entry] = {"class": "PErrorSym1D", "sigma": param_mapping} elif mapping_entry == "radialVelocity": error_mapping[mapping_entry] = {"class": "PErrorSym1D", "sigma": param_mapping} elif mapping_entry == "longitude": if "position" in error_mapping: error_mapping["position"]["sigma1"] = param_mapping else: error_mapping["position"] = {"class": "PErrorSym2D", "sigma1": param_mapping} elif mapping_entry == "latitude": if "position" in error_mapping: error_mapping["position"]["sigma2"] = param_mapping else: error_mapping["position"] = {"class": "PErrorSym2D", "sigma2": param_mapping} elif mapping_entry == "pmLongitude": if "properMotion" in error_mapping: error_mapping["properMotion"]["sigma1"] = param_mapping else: error_mapping["properMotion"] = {"class": "PErrorSym2D", "sigma1": param_mapping} elif mapping_entry == "pmLatitude": if "properMotion" in error_mapping: error_mapping["properMotion"]["sigma2"] = param_mapping else: error_mapping["properMotion"] = {"class": "PErrorSym2D", "sigma2": param_mapping} return mapping, error_mapping astropy-pyvo-b70558c/pyvo/mivot/writer/instance.py000066400000000000000000000141501510533647000223770ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ MivotInstance is a simple API for building MIVOT instances step by step. A MIVOT instance is a MIVOT serialisation of an object whose attributes are set with column values or literals. A MIVOT instance can contain ATTRIBUTEs elements, COLLECTIONs of elements, or other INSTANCEs. The MIVOT INSTANCE structure is defined by the data model on which the data is mapped. """ from pyvo.utils.prototype import prototype_feature from pyvo.mivot.utils.exceptions import MappingError from pyvo.mivot.utils.mivot_utils import MivotUtils __all__ = ["MivotInstance"] @prototype_feature("MIVOT") class MivotInstance: """ API for building elements of MIVOT annotation step by step. This class provides methods for incremental construction of a MIVOT instance. It builds elements that can contain , , and . Support for elements is not yet implemented. The main features are: - Model-agnostic: The implementation is independent of any specific data model. - Syntax validation: Ensures basic MIVOT syntax rules are followed. - Context-agnostic: Ignores context-dependent syntax rules. attributes ---------- _dmtype : string Instance type (class VO-DML ID) _dmrole : string Role played by the instance in the context where it is used (given by the VO-DML serialization of the model) _dmid : string Free identifier of the instance """ def __init__(self, dmtype, *, dmrole=None, dmid=None): """ Parameters ---------- dmtype : str dmtype of the INSTANCE (mandatory) dmrole : str, optional dmrole of the INSTANCE dmid : str, optional dmid of the INSTANCE Raises ------ MappingError If ``dmtype`` is not provided """ if not dmtype: raise MappingError("Cannot build an instance without dmtype") self._dmtype = dmtype self._dmrole = dmrole self._dmid = MivotUtils.format_dmid(dmid) self._content = [] @property def dmid(self): return self._dmid def add_attribute(self, dmtype=None, dmrole=None, *, value=None, unit=None): """ Add an element to the instance. Parameters ---------- dmtype : str dmtype of the ATTRIBUTE (mandatory) dmrole : str dmrole of the ATTRIBUTE (mandatory) value : str or numerical, optional ID of the column to set the attribute value. If ref is a string starting with a * or is numerical, it is considered as a value (* stripped) as a ref otherwise unit : str, optional Unit of the attribute Raises ------ MappingError If ``dmtype`` or ``dmrole`` is not provided, or if both ``ref`` and ``value`` are not defined """ if not dmtype: raise MappingError("Cannot add an attribute without dmtype") if not dmrole: raise MappingError("Cannot add an attribute without dmrole") ref, literal = MivotUtils.get_ref_or_literal(value) if not ref and not value: raise MappingError("Cannot add an attribute without ref or value") xml_string = f' element to the instance. Parameters ---------- dmrole : str dmrole of the REFERENCE (mandatory) dmref : str dmref of the REFERENCE (mandatory) Raises ------ MappingError If ``dmrole`` or ``dmref`` is not provided """ if not dmref: raise MappingError("Cannot add a reference without dmref") if not dmrole: raise MappingError("Cannot add a reference without dmrole") xml_string = f'' self._content.append(xml_string) def add_instance(self, mivot_instance): """ Add a nested element to the instance. Parameters ---------- mivot_instance : MivotInstance INSTANCE to be added Raises ------ MappingError If ``mivot_instance`` is not of type ``MivotInstance`` """ if not isinstance(mivot_instance, MivotInstance): raise MappingError("Instance added must be of type MivotInstance") self._content.append(mivot_instance.xml_string()) def add_collection(self, dmrole, mivot_instances): """ to be documented """ dm_att = "" if dmrole: dm_att = f"dmrole=\"{dmrole}\"" self._content.append(f'') for mivot_instance in mivot_instances: if isinstance(mivot_instance, MivotInstance): self._content.append(mivot_instance.xml_string()) else: self._content.append(mivot_instance) self._content.append("\n") self._content.append("") def xml_string(self): """ Build and serialize the element as a string. Returns ------- str The string representation of the element """ xml_string = f'" in fps_reponse: message = re.search(r'(.*)', fps_reponse).group(1) raise MappingError(f"FPS service error: {message}") # set the identifiers that will be used for both PhotCal and PhotFilter cal_id = MivotUtils.format_dmid(filter_name) photcal_id = f"_photcal_{cal_id}" filter_id = f"_photfilter_{cal_id}" # skip if the same dmid is already recorded if cal_id in self._annotation._dmids: logging.warning("An instance with dmid=%s has already been stored in GLOBALS: skip", cal_id) return photcal_id, filter_id self._annotation._dmids.append(cal_id) logging.info("%s PhotCal can be referred with dmref='%s'", cal_id, photcal_id) # parse the PhotCal and extract the PhotFilter node photcal_block = etree.fromstring(fps_reponse) filter_block = XPath.x_path_contains(photcal_block, ".//" + Ele.INSTANCE, Att.dmtype, "Phot:photometryFilter")[0] # Tune the Photcal to be placed as a GLOBALS child (no role but an id) # and remove the PhotFilter node which will be shifted at the GLOBALS level del photcal_block.attrib["dmrole"] photcal_block.set("dmid", photcal_id) photcal_block.remove(filter_block) # Tune the PhotFilter to be placed as GLOBALS child (no role but an id) filter_role = filter_block.get("dmrole") del filter_block.attrib["dmrole"] filter_block.set("dmid", filter_id) # Append a REFERENCE on the PhotFilter node to the PhotCal block reference = Element("REFERENCE") reference.set("dmrole", filter_role) reference.set("dmref", filter_id) photcal_block.append(reference) self._annotation.add_model("ivoa", vodml_url=VodmlUrl.ivoa) self._annotation.add_model("Phot", vodml_url=VodmlUrl.Phot) # fix some FPS tweaks photcal_block_string = XmlUtils.pretty_string(photcal_block, lshift=" ") photcal_block_string = photcal_block_string.replace( "", "") filter_block_string = XmlUtils.pretty_string(filter_block, lshift=" ") filter_block_string = filter_block_string.replace("Phot:photometryFilter", "Phot:PhotometryFilter") filter_block_string = filter_block_string.replace("Phot:PhotCal.photometryFilter.bandwidth", "Phot:PhotometryFilter.bandwidth") self._annotation.add_globals(photcal_block_string) self._annotation.add_globals(filter_block_string) return photcal_id, filter_id def add_simple_space_frame(self, spaceRefFrame="ICRS", refPosition="BARYCENTER", equinox=None, epoch=None): """ Adds a SpaceSys instance to the GLOBALS block as defined in the Coordinates data model V1.0 (https://ivoa.net/documents/Coords/20221004/index.html). Notes: - This function implements only the most commonly used features. Custom reference positions for TOPOCENTER frames are not supported. However, methods for implementing the missing features can be derived from this code. - A warning is emitted if either ``spaceRefFrame`` or ``refPosition`` have unexpected values. - No error is raised if the parameter values are inconsistent. - The ``dmid`` of the time frame is built from ``spaceRefFrame``, ``refrefPosition_position`` and ``equinox``. Parameters ---------- spaceRefFrame : str, optional, default "ICRS" The reference frame for the space frame. refPosition : str, optional, default "BARYCENTER" The reference position for the space frame. equinox : str, optional, default None The equinox for the reference frame, if applicable. epoch : str, optional, default None The epoch for the reference location, if applicable. Returns ------- str The actual dmid of the time frame INSTANCE """ # build the dmid dmid = f"_spaceframe_{spaceRefFrame.replace('*', '')}" if equinox: dmid += f"_{equinox.replace('*', '')}" if refPosition: dmid += f"_{refPosition.replace('*', '')}" # skip if the same dmid is already recorded if dmid in self._annotation._dmids: logging.warning("A spaceSys instance with dmid=%s has already been stored in GLOBALS: skip", dmid) return dmid logging.info("spaceSys %s can be referred with dmref='%s'", spaceRefFrame, dmid) self._annotation._dmids.append(dmid) # add (or overwrite) used models self._annotation.add_model(ModelPrefix.ivoa, vodml_url=VodmlUrl.ivoa) self._annotation.add_model(ModelPrefix.coords, vodml_url=VodmlUrl.coords) # check whether ref_frame and ref_position are set with appropriate values self._check_value_consistency(spaceRefFrame, CoordSystems.space_frames) self._check_value_consistency(refPosition, CoordSystems.ref_positions) # Build the SpaceSys instance component by component space_system_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:SpaceSys", dmid=dmid) # let's start with the space frame space_frame_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:SpaceFrame", dmrole=f"{ModelPrefix.coords}:PhysicalCoordSys.frame") space_frame_instance.add_attribute(dmtype=IvoaType.string, dmrole=f"{ModelPrefix.coords}:SpaceFrame.spaceRefFrame", value=MivotUtils.as_literal(spaceRefFrame)) if equinox is not None: space_frame_instance.add_attribute(dmtype=f"{ModelPrefix.coords}:Epoch", dmrole=f"{ModelPrefix.coords}:SpaceFrame.equinox", value=MivotUtils.as_literal(equinox)) # then let's build the reference position. # The RefLocation type depends on the presence of an epoch (see coords DM) if epoch is not None: ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:CustomRefLocation", dmrole=f"{ModelPrefix.coords}:SpaceFrame.refPosition") ref_position_instance.add_attribute(dmtype=IvoaType.string, dmrole=f"{ModelPrefix.coords}:CustomRefLocation.position", value=MivotUtils.as_literal(refPosition)) ref_position_instance.add_attribute(dmtype="coords:Epoch", dmrole=f"{ModelPrefix.coords}:CustomRefLocation.epoch", value=MivotUtils.as_literal(epoch)) else: ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:StdRefLocation", dmrole=f"{ModelPrefix.coords}:SpaceFrame.refPosition") ref_position_instance.add_attribute(dmtype=IvoaType.string, dmrole=f"{ModelPrefix.coords}:StdRefLocation.position", value=MivotUtils.as_literal(refPosition)) # and pack everything space_frame_instance.add_instance(ref_position_instance) space_system_instance.add_instance(space_frame_instance) # add the SpaceSys instance to the GLOBALS block self._annotation.add_globals(space_system_instance) return dmid def add_simple_time_frame(self, timescale="TCB", *, refPosition="BARYCENTER"): """ Adds a TimeSys instance to the GLOBALS block as defined in the Coordinates data model V1.0 (https://ivoa.net/documents/Coords/20221004/index.html). Notes: - This function implements only the most commonly used features. *Custom reference directions* are not supported. However, methods for implementing missing features can be derived from this code. - A warning is emitted if either ``timescale`` or ``refPosition`` have unexpected values. - No error is raised if the parameter values are inconsistent. - The ``dmid`` of the time rame is built from ``timescale`` and ``refPosition``. Parameters ---------- timescale : str, optional, default "TCB" The reference frame for the time frame. refPosition : str, optional, default "BARYCENTER" The reference position for the time frame. Returns ------- str The actual dmid of the time frame INSTANCE """ # buikd the dmid dmid = f"_timeframe_{timescale.replace('*', '')}" if refPosition: dmid += f"_{refPosition.replace('*', '')}" dmid = MivotUtils.format_dmid(dmid) # skip if the same dmid is already recorded if dmid in self._annotation._dmids: logging.warning("An timeSys instance with dmid=%s has already been stored in GLOBALS: skip", dmid) return dmid logging.info("timeSys %s can be referred with dmref='%s'", timescale, dmid) self._annotation._dmids.append(dmid) # add (or overwrite) used models self._annotation.add_model(ModelPrefix.ivoa, vodml_url=VodmlUrl.ivoa) self._annotation.add_model(ModelPrefix.coords, vodml_url=VodmlUrl.coords) # check whether ref_frame and ref_position are set with appropriate values self._check_value_consistency(timescale, CoordSystems.time_frames) self._check_value_consistency(refPosition, CoordSystems.ref_positions) # Build the TimeSys instance component by component time_sys_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:TimeSys", dmid=dmid) # Let's start with the time frame time_frame_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:TimeFrame", dmrole=f"{ModelPrefix.coords}:PhysicalCoordSys.frame") time_frame_instance.add_attribute(dmtype=IvoaType.string, dmrole=f"{ModelPrefix.coords}:TimeFrame.timescale", value=MivotUtils.as_literal(timescale)) # Then let's build the reference position ref_position_instance = MivotInstance(dmtype=f"{ModelPrefix.coords}:StdRefLocation", dmrole=f"{ModelPrefix.coords}:TimeFrame.refPosition") ref_position_instance.add_attribute(dmtype=IvoaType.string, dmrole=f"{ModelPrefix.coords}:StdRefLocation.position", value=MivotUtils.as_literal(refPosition)) # pack everything time_frame_instance.add_instance(ref_position_instance) time_sys_instance.add_instance(time_frame_instance) # add the TimeSys instance to the GLOBALS block self._annotation.add_globals(time_sys_instance) return dmid def add_mango_brightness(self, *, photcal_id=None, mapping={}, semantics={}): """ Add a Mango ``Brightness`` instance to the current `MangoObject` with the specified photometric calibration, using the mapping parameter to associate VOtable data with it. This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic. Parameters ---------- photcal_id : string, optional (default is None) Filter profile service (http://svo2.cab.inta-csic.es/theory/fps/} identifier of the desired photometric calibration. It is made of the filter identifier followed by the photometric system (e.g. GAIA/GAIA3.Grvs/AB) mapping : dict, optional (default to an empty dictionary ({}) A dictionary defining the mapping of values. It includes: - mapping of the brightness value - one separate block for the error specification semantics : dict, optional (default to an empty dictionary ({}) A dictionary specifying semantic details to be added to the Mango property. Returns ------- `Property` The Mango property Notes ----- The mapping example below maps the data of the GaiaD3 table that can be found in the test suite. Notice that the (fake) error bounds are given as literalS. .. code-block:: python photcal_id="GAIA/GAIA3.Grvs/AB" mapping={"value": "GRVSmag", "error": { "class": "PErrorAsym1D", "low": 1, "high": 3} } semantics={"description": "Grvs magnitude", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#magnitude", "label": "magnitude"} builder = InstancesFromModels(votable, dmid="DR3Name") builder.add_mango_magnitude(photcal_id=photcal_id, mapping=mapping, semantics=semantics) """ # make sure MANGO is mentioned in self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango) # Add the photometric calibration instance in photcal_id, _ = self.add_photcal(photcal_id) # Add the brightness property to the MANGO instance return self._mango_instance.add_brightness_property(photcal_id, mapping, semantics=semantics) def add_mango_color(self, *, filter_ids={}, mapping={}, semantics={}): """ Add a Mango ``Color`` instance to the current `MangoObject` with the specified low and high filters, using the mapping parameter to associate VOtable data with it. This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic. Parameters ---------- filter_ids : string, optional (default to an empty dictionary {}) Filter profile service (http://svo2.cab.inta-csic.es/theory/fps/} identifiers of the high and low photometric calibrations that contain the desired filters. Identifiers are made of the filter identifier followed by the photometric system (e.g. GAIA/GAIA3.Grvs/AB). mapping : dict, optional (default to an empty dictionary ({}) A dictionary defining the mapping of values. It includes: - The mapping of the color value and the color definition (ColorIndex or HardnessRatio). - One separate block for the error specification. semantics : dict, optional (default to an empty dictionary {}) A dictionary specifying semantic details to be added to the Mango property. Returns ------- `Property` The Mango property Notes ----- The mapping example below maps the data of the GaiaD3 table that can be found in the test suite. The (fake) color value is given as a literal, it does not refer to any table column. .. code-block:: python filter_ids={"low": "GAIA/GAIA3.Grp/AB", "high": "GAIA/GAIA3.Grvs/AB"} mapping={"value": 0.08, "definition": "ColorIndex", "error": { "class": "PErrorAsym1D", "low": 0.01, "high": 0.02} } semantics={"description": "Fake color index", "uri": "http://astrothesaurus.org/uat/1553", "label": "Spectral index"} builder = InstancesFromModels(votable, dmid="DR3Name") builder.add_mango_color(filter_ids=filter_ids, mapping=mapping, semantics=semantics) """ self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango) filter_low_name = filter_ids["low"] filter_high_name = filter_ids["high"] _, filter_low_id = self.add_photcal(filter_low_name) _, filter_high_id = self.add_photcal(filter_high_name) return self._mango_instance.add_color_instance(filter_low_id, filter_high_id, mapping, semantics=semantics) def add_mango_epoch_position(self, *, frames={}, mapping={}, semantics={}): """ Add a Mango ``EpochPosition`` instance to the current `MangoObject` with the specified frames and semantics, using the mapping parameter to associate VOtable data with it. This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic. Parameters ---------- frames : dict, optional (default to an empty dictionary {}) A dictionary specifying the frames (space and time coordinate systems) to be used. Frames parameters are global, they cannot refer to table columns. If a frame description contains the "dmid" key, that value will be used as an identifier an already installed frame (e.g. with ``extract_frames()``). Otherwise the content of the frame description is meant to be used in input parameter for ``add_simple_(space)time_frame()`` mapping : dict, optional (default to an empty dictionary {}) A dictionary defining the mapping of values. It includes: - A flat list for position parameters. - Two separate blocks for correlations and error specifications. semantics : dict, optional (default to an empty dictionary {}) A dictionary specifying semantic details to be added to the Mango property. Returns ------- `Property` The Mango property Notes ----- The mapping example below maps the data of the GaiaD3 table that can be found in the test suite. .. code-block:: python frames={"spaceSys": {"spaceRefFrame": "ICRS", "refPosition": 'BARYCENTER', "equinox": None}, "timeSys": {"timescale": "TCB", "refPosition": 'BARYCENTER'}} mapping={"longitude": "_RAJ2000", "latitude": "_DEJ2000", "pmLongitude": "pmRA", "pmLatitude": "pmDE", "parallax": "Plx", "radialVelocity": "RV", "correlations": {"isCovariance": True, "longitudeLatitude": "RADEcor", "latitudePmLongitude": "DEpmRAcor", "latitudePmLatitude": "DEpmDEcor", "longitudePmLongitude": "RApmRAcor", "longitudePmLatitude": "RApmDEcor", "longitudeParallax": "RAPlxcor", "latitudeParallax": "DEPlxcor", "pmLongitudeParallax": "PlxpmRAcor", "pmLatitudeParallax": "PlxpmDEcor", }, "errors": { "position": { "class": "PErrorSym2D", "sigma1": "e_RA_ICRS", "sigma2": "e_DE_ICRS"}, "properMotion": { "class": "PErrorSym2D", "sigma1": "e_pmRA", "sigma2": "e_pmDE"}, "parallax": { "class": "PErrorSym1D", "sigma": "e_Plx"}, "radialVelocity": { "class": "PErrorSym1D", "sigma": "e_RV"} } } semantics={"description": "6 parameters position", "uri": "https://www.ivoa.net/rdf/uat/2024-06-25/uat.html#astronomical-location", "label": "Astronomical location"} builder = InstancesFromModels(votable, dmid="DR3Name") builder.add_mango_epoch_position(frames=frames, mapping=mapping, semantics=semantics) """ self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango) space_frame_id = "" if "spaceSys" in frames and frames["spaceSys"]: if "dmid" in frames["spaceSys"]: space_frame_id = frames["spaceSys"]["dmid"] else: space_frame_id = self.add_simple_space_frame(*frames["spaceSys"]) time_frame_id = "" if "timeSys" in frames and frames["timeSys"]: if "dmid" in frames["timeSys"]: time_frame_id = frames["timeSys"]["dmid"] else: time_frame_id = self.add_simple_time_frame(**frames["timeSys"]) return self._mango_instance.add_epoch_position(space_frame_id, time_frame_id, mapping, semantics) def add_query_origin(self, mapping={}): """ Add the Mango ``QueryOrigin`` instance to the current `MangoObject`. This method acts as a front-end for `pyvo.mivot.writer.MangoObject` logic. Parameters ---------- mapping : dict, optional (default to an empty dictionary {}) A dictionary defining the QueryOrigin fields. Mapped fields are global, they cannot refer to table columns Returns ------- `MivotInstance` Notes ----- The partial mapping example below maps a fake QueryOrigin. A complete (long) example based on GaiaD3 can be found in the test suite. .. code-block:: python builder = InstancesFromModels(votable, dmid="DR3Name") builder.add_query_origin( { "service_protocol": "ivo://ivoa.net/std/ConeSearch/v1.03", "request_date": "2025-04-07T12:06:32", "request": ( "https://cdsarc.cds.unistra.fr/beta/viz-bin/mivotconesearch" "/I/329/urat1?RA=52.26708&DEC=59.94027&SR=0.05" ), "contact": "cds-question@unistra.fr", "server_software": "7.4.6", "publisher": "CDS", "dataOrigin": [ { "ivoid": "ivo://cds.vizier/i/329", "creators": ["Zacharias N."], "cites": "bibcode:2015AJ....150..101Z", "original_date": "2015", "reference_url": "https://cdsarc.cds.unistra.fr/viz-bin/cat/I/329", "rights_uri": "https://cds.unistra.fr/vizier-org/licences_vizier.html", "articles": [{"editor": "Astronomical Journal (AAS)"}], } ], } ) """ self._annotation.add_model(ModelPrefix.mango, vodml_url=VodmlUrl.mango) query_origin_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.QueryOrigin", dmid="_origin") MivotUtils.populate_instance(query_origin_instance, "QueryOrigin", mapping, self._table, IvoaType.string, as_literals=True, package="origin") if "dataOrigin" in mapping: origins = [] data_origin_mappings = mapping["dataOrigin"] for data_origin_mapping in data_origin_mappings: data_origin_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.DataOrigin") MivotUtils.populate_instance(data_origin_instance, "DataOrigin", data_origin_mapping, self._table, IvoaType.string, as_literals=True, package="origin") if "articles" in data_origin_mapping: articles = [] for art_mapping in data_origin_mapping["articles"]: art_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:origin.Article") MivotUtils.populate_instance(art_instance, "Article", art_mapping, self._table, IvoaType.string, as_literals=True, package="origin") articles.append(art_instance) data_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.DataOrigin.articles", articles) if "creators" in data_origin_mapping: creators = [] for art_mapping in data_origin_mapping["creators"]: creators.append(f"") data_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.DataOrigin.creators", creators) origins.append(data_origin_instance) query_origin_instance.add_collection(f"{ModelPrefix.mango}:origin.QueryOrigin.dataOrigin", origins) self._annotation.add_globals(query_origin_instance) self._annotation._dmids.append("_origin") return query_origin_instance def pack_into_votable(self, *, report_msg="", sparse=False, schema_check=True): """ Pack all mapped objects in the annotation block and put it in the VOTable. Parameters ---------- report_msg : string, optional (default to an empty string) Content of the REPORT Mivot tag sparse : boolean, optional (default to False) If True, all properties are added in a independent way to the the TEMPLATES. They are packed in a MangoObject otherwise. schema_check : boolean, optional (default to True) If True the MIVOT block is validated against its schema. This may test failing due to remote accesses. """ self._annotation.set_report(True, report_msg) if sparse is True: for prop in self._mango_instance.mango_properties: # Add each individual property to the TEMPLATES block self._annotation.add_templates(prop.xml_string()) else: # Pack the MangoObject and put it in the TEMPLATES self._annotation.add_templates(self._mango_instance.get_mango_object( with_origin=("_origin" in self._annotation._dmids))) self._annotation.build_mivot_block(schema_check=schema_check) self._annotation.insert_into_votable(self._votable, override=True) astropy-pyvo-b70558c/pyvo/mivot/writer/mango_object.py000066400000000000000000000333441510533647000232300ustar00rootroot00000000000000''' Created on 22 Jan 2025 @author: laurentmichel ''' from pyvo.mivot.utils.exceptions import MappingError from pyvo.mivot.utils.mivot_utils import MivotUtils from pyvo.mivot.writer.instance import MivotInstance from pyvo.mivot.glossary import ( IvoaType, ModelPrefix, Roles, CoordSystems) class Property(MivotInstance): """ Class representing one property of a MangoInstance. MangoInstance property instances are `pyvo.mivot.writer.MivotInstance` augmented with a semantics block. """ def __init__(self, dmtype=None, *, dmrole=None, dmid=None, semantics={}): """ Parameters ---------- dmtype : str dmtype of the INSTANCE (mandatory) dmrole : str, optional (default as None) dmrole of the INSTANCE dmid : str, optional (default as None) dmid of the INSTANCE semantics : dict, optional (default as {}) Mapping of the semantic block (supported key: descripton, uri, label) Raises ------ MappingError If ``dmtype`` is not provided """ super().__init__(dmtype, dmrole=dmrole, dmid=dmid) if "description" in semantics: # we assume the description as always being a literal self.add_attribute(dmtype="ivoa:string", dmrole="mango:Property.description", value=f"*{semantics['description']}") if "uri" in semantics or "label" in semantics: semantics_instance = MivotInstance(dmtype="mango:VocabularyTerm", dmrole="mango:Property.semantics") if "uri" in semantics: semantics_instance.add_attribute(dmtype="ivoa:string", dmrole="mango:VocabularyTerm.uri", value=f"*{semantics['uri']}") if "label" in semantics: semantics_instance.add_attribute(dmtype="ivoa:string", dmrole="mango:VocabularyTerm.label", value=f"*{semantics['label']}") self.add_instance(semantics_instance) class MangoObject(object): """ This class handles all the components of a MangoObject (properties, origin, instance identifier). It is meant to be used by `pyvo.mivot.writer.InstancesFromModels` but not by end users. - There is one specific method for each supported property (EpochPosition, photometry and QueryOrigin). - The internal structure of the classes is hard-coded in the class logic. """ def __init__(self, table, *, dmid=None): ''' Constructor parameters: parameters ---------- table : astropy.io.votable.tree.TableElement VOTable table which data is mapped on Mango dmid: stringn optional (default as None) Reference of the column to be used as a MangoObject identifier ''' self._table = table self._properties = [] self._dmid = dmid def _get_error_instance(self, class_name, dmrole, mapping): """ Private method building and returning an Mango error instance Returns: MivotInstance """ prop_err_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:error.{class_name}", dmrole=dmrole) MivotUtils.populate_instance(prop_err_instance, class_name, mapping, self._table, IvoaType.RealQuantity, package="error") return prop_err_instance def _add_epoch_position_correlations(self, **correlations): """ Private method building and returning the correlation block of the EpocPosition object. Returns ------- `Property` The EpochPosition correlations component """ epc_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:EpochPositionCorrelations", dmrole=f"{ModelPrefix.mango}:EpochPosition.correlations") MivotUtils.populate_instance(epc_instance, "EpochPositionCorrelations", correlations, self._table, IvoaType.real) return epc_instance def _add_epoch_position_errors(self, **errors): """ Private method building and returning the error block of the EpocPosition object. Returns ------- `Property` The EpochPosition error instance """ err_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:EpochPositionErrors", dmrole=f"{ModelPrefix.mango}:EpochPosition.errors") for role, mapping in errors.items(): error_class = mapping["class"] if (role in Roles.EpochPositionErrors and error_class in ["PErrorSym2D", "PErrorSym1D", "PErrorAsym1D"]): err_instance.add_instance( self._get_error_instance(error_class, f"{ModelPrefix.mango}:EpochPositionErrors.{role}", mapping)) return err_instance def _add_epoch_position_epoch(self, **mapping): """ Private method building and returning the observation date (DateTime) of the EpohPosition. Parameters ---------- mapping: dict(representation, datetime) Mapping of the DateTime fields Returns ------- `Property` The EpochPosition observation date instance """ datetime_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:DateTime", dmrole=f"{ModelPrefix.mango}:EpochPosition.obsDate") representation = mapping.get("representation") value = mapping["dateTime"] if representation not in CoordSystems.time_formats: raise MappingError(f"epoch representation {representation} not supported. " f"Take on of {CoordSystems.time_formats}") datetime_instance.add_attribute(IvoaType.string, f"{ModelPrefix.mango}:DateTime.representation", value=MivotUtils.as_literal(representation)) datetime_instance.add_attribute(IvoaType.datetime, f"{ModelPrefix.mango}:DateTime.dateTime", value=value) return datetime_instance def add_epoch_position(self, space_frame_id, time_frame_id, mapping, semantics): """ Add an ``EpochPosition`` instance to the properties of the current ``MangoObject``. Both mapping and semantics arguments inherit from `pyvo.mivot.writer.InstancesFromModels.add_mango_epoch_position`. Parameters ---------- space_frame_id : string Identifier (dmid) of space system INSTANCE located in the GLOBALS time_frame_id : string Identifier (dmid) of time system INSTANCE located in the GLOBALS mapping : dict Mapping of the EpochPosition fields semantics : dict Mapping of the MangoObject property Returns ------- `Property` The EpochPosition instance """ ep_instance = Property(dmtype=f"{ModelPrefix.mango}:EpochPosition", semantics=semantics) MivotUtils.populate_instance(ep_instance, "EpochPosition", mapping, self._table, IvoaType.RealQuantity) if "obsDate" in mapping: ep_instance.add_instance(self._add_epoch_position_epoch(**mapping["obsDate"])) if "correlations" in mapping: ep_instance.add_instance(self._add_epoch_position_correlations(**mapping["correlations"])) if "errors" in mapping: ep_instance.add_instance(self._add_epoch_position_errors(**mapping["errors"])) if space_frame_id: ep_instance.add_reference(dmrole=f"{ModelPrefix.mango}:EpochPosition.spaceSys", dmref=space_frame_id) if time_frame_id: ep_instance.add_reference(dmrole=f"{ModelPrefix.mango}:EpochPosition.timeSys", dmref=time_frame_id) self._properties.append(ep_instance) return ep_instance def add_brightness_property(self, filter_id, mapping, semantics={}): """ Add a ``Brightness`` instance to the properties of the current ``MangoObject``. Both mapping and semantics arguments inherit from `pyvo.mivot.writer.InstancesFromModels.add_mango_brightness`. Parameters ---------- filter_id : string Identifier (dmid) of the PhotCal INSTANCE located in the GLOBALS mapping : dict Mapping of the EpochPosition fields semantics : dict Mapping of the MangoObject property Returns ------- `Property` The Brightness instance """ # create the MIVOT instance mapping the MANGO property mag_instance = Property(dmtype=f"{ModelPrefix.mango}:Brightness", semantics=semantics) # set MANGO property attribute MivotUtils.populate_instance(mag_instance, "PhotometricProperty", mapping, self._table, IvoaType.RealQuantity) # build the error instance if it is mapped if "error" in mapping: error_mapping = mapping["error"] error_class = error_mapping["class"] mag_instance.add_instance( self._get_error_instance(error_class, f"{ModelPrefix.mango}:PhotometricProperty.error", error_mapping)) # add MIVOT reference to the photometric calibration instance mag_instance.add_reference(dmrole=f"{ModelPrefix.mango}:Brightness.photCal", dmref=filter_id) self._properties.append(mag_instance) return mag_instance def add_color_instance(self, filter_low_id, filter_high_id, mapping, semantics={}): """ Add an ``Color`` instance to the properties of the current ``MangoObject``. Both mapping and semantics arguments inherit from `pyvo.mivot.writer.InstancesFromModels.add_mango_color`. Parameters ---------- filter_low_id : string Identifier (dmid) of the low energy Photfilter INSTANCE located in the GLOBALS filter_high_id : string Identifier (dmid) of the high energy Photfilter INSTANCE located in the GLOBALS mapping : dict Mapping of the EpochPosition fields semantics : dict Mapping of the MangoObject property Returns ------- `Property` The Color instance """ error_mapping = mapping["error"] mag_instance = Property(dmtype=f"{ModelPrefix.mango}:Color", semantics=semantics) coldef_instance = MivotInstance(dmtype=f"{ModelPrefix.mango}:ColorDef", dmrole=f"{ModelPrefix.mango}:Color.colorDef") mapped_roles = MivotUtils._valid_mapped_dmroles(mapping.items(), "Color") def_found = False for dmrole, column in mapped_roles: if dmrole.endswith("definition"): def_found = True coldef_instance.add_attribute(dmtype="mango:ColorDefinition", dmrole="mango:ColorDef.definition", value=f"*{column}") if not def_found: raise MappingError("Missing color definition") mapping.pop("definition") MivotUtils.populate_instance(mag_instance, "PhotometricProperty", mapping, self._table, IvoaType.RealQuantity) coldef_instance.add_reference(dmrole=f"{ModelPrefix.mango}:ColorDef.low", dmref=filter_low_id) coldef_instance.add_reference(dmrole=f"{ModelPrefix.mango}:ColorDef.high", dmref=filter_high_id) error_class = error_mapping["class"] mag_instance.add_instance(self._get_error_instance(error_class, f"{ModelPrefix.mango}:PhotometricProperty.error", error_mapping)) mag_instance.add_instance(coldef_instance) self._properties.append(mag_instance) return mag_instance def get_mango_object(self, with_origin=False): """ Make and return the XML serialization of the MangoObject. Parameters ---------- with_origin : bool Ask for adding a reference (_origin) to the query origin possibly located in the GLOBALS Returns ------- string The XML serialization of the MangoObject """ mango_object = MivotInstance(dmtype="mango:MangoObject", dmid=self._dmid) if self._dmid: ref, value = MivotUtils.get_ref_or_literal(self._dmid) att_value = ref if ref else value mango_object.add_attribute(dmrole="mango:MangoObject.identifier", dmtype=IvoaType.string, value=att_value) if with_origin: mango_object.add_reference("mango:MangoObject.queryOrigin", "_origin") m_properties = [] for prop in self._properties: m_properties.append(prop.xml_string()) mango_object.add_collection("mango:MangoObject.propertyDock", m_properties) return mango_object astropy-pyvo-b70558c/pyvo/mivot/writer/mivot-v1.xsd000066400000000000000000000313341510533647000224260ustar00rootroot00000000000000 astropy-pyvo-b70558c/pyvo/mivot/writer/v1.1.xsd000066400000000000000000000416761510533647000214430ustar00rootroot00000000000000 Deprecated in Version 1.1 astropy-pyvo-b70558c/pyvo/mivot/writer/v1.2.xsd000066400000000000000000000520761510533647000214400ustar00rootroot00000000000000 VOTable1.2 is meant to serialize tabular documents in the context of Virtual Observatory applications. This schema corresponds to the VOTable document available from http://www.ivoa.net/Documents/latest/VOT.html Accept UCD1+ Accept also old UCD1 (but not / + %) including SIAP convention (with :) content-role was previsouly restricted as: ]]>; is now a name token. Deprecated in Version 1.2 Deprecated in Version 1.1 Added in Version 1.2: INFO for diagnostics The 'encoding' attribute is added here to avoid problems of code generators which do not properly interpret the TR/TD structures. 'encoding' was chosen because it appears in appendix A.5 The ID attribute is added here to the TR tag to avoid problems of code generators which do not properly interpret the TR/TD structures Added in Version 1.2: INFO for diagnostics Added in Version 1.2: INFO for diagnostics in several places astropy-pyvo-b70558c/pyvo/mivot/writer/v1.3.xsd000066400000000000000000000575351510533647000214460ustar00rootroot00000000000000 VOTable is meant to serialize tabular documents in the context of Virtual Observatory applications. This schema corresponds to the VOTable document available from http://www.ivoa.net/Documents/latest/VOT.html Accept UCD1+ Accept also old UCD1 (but not / + %) including SIAP convention (with :) content-role was previsouly restricted as: ]]>; is now a token. Values for this attribute must be taken from the IVOA refframe vocabulary, http://www.ivoa.net/rdf/refframe The reference position SHOULD be taken from the IVOA refposition vocabulary (http://www.ivoa.net/rdf/refposition). This is a time origin of a time coordinate, given as a Julian Date for the the time scale and reference point defined. It is usually given as a floating point literal; for convenience, the magic strings “MJD-origin” (standing for 2400000.5) and “JD-origin” (standing for 0) are also allowed. The time origin is the offset or the time coordinate to Julian Date. The timeorigin attribute MUST be given unless the time's representation contains a year of a calendar era, in which case it MUST NOT be present. This is the time scale used. Values SHOULD be taken from the IVOA timescale vocabulary (http://www.ivoa.net/rdf/timescale). The reference position SHOULD be taken from the IVOA refposition vocabulary (http://www.ivoa.net/rdf/refposition). Deprecated in Version 1.1 Added in Version 1.2: INFO for diagnostics The 'encoding' attribute is added here to avoid problems of code generators which do not properly interpret the TR/TD structures. 'encoding' was chosen because it appears in appendix A.5 The ID attribute is added here to the TR tag to avoid problems of code generators which do not properly interpret the TR/TD structures Added in Version 1.2: INFO for diagnostics Added in Version 1.2: INFO for diagnostics in several places astropy-pyvo-b70558c/pyvo/registry/000077500000000000000000000000001510533647000174165ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/registry/__init__.py000066400000000000000000000015711510533647000215330ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ a package for interacting with registries. The regtap module supports access to the IVOA Registries """ from .regtap import (search, ivoid2service, get_RegTAP_query, choose_RegTAP_service, RegistryResults, RegistryResource) from .rtcons import (Constraint, SubqueriedConstraint, Freetext, Author, Servicetype, Waveband, Datamodel, Ivoid, UCD, UAT, Spatial, Spectral, Temporal, RegTAPFeatureMissing) __all__ = ["search", "get_RegTAP_query", "Constraint", "SubqueriedConstraint", "Freetext", "Author", "Servicetype", "Waveband", "Datamodel", "Ivoid", "UCD", "UAT", "Spatial", "Spectral", "Temporal", "choose_RegTAP_service", "RegTAPFeatureMissing", "RegistryResults", "RegistryResource",] astropy-pyvo-b70558c/pyvo/registry/regtap.py000066400000000000000000001270411510533647000212570ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ a module for basic VO Registry interactions. A VO registry is a database of VO resources--data collections and services--that are available for VO applications. Typically, it is aware of the resources from all over the world. A registry can find relevant data collections and services through search queries--typically, subject-based. The registry responds with a list of records describing matching resources. With a record in hand, the application can use the information in the record to access the resource directly. Most often, the resource is a data service that can be queried for individual datasets of interest. This module provides basic, low-level access to the RegTAP Registries using standardized TAP-based services. """ import functools import itertools import os import textwrap import warnings from astropy import table from astropy.utils.decorators import deprecated from astropy.utils.exceptions import AstropyDeprecationWarning import numpy from . import rtcons from ..dal import scs, sia, sia2, ssa, sla, tap, query as dalq from ..io.vosi import vodataservice from ..utils.formatting import para_format_desc __all__ = ["search", "get_RegTAP_query", "Interface", "RegistryResource", "RegistryResults", "ivoid2service"] REGISTRY_BASEURL = os.environ.get("IVOA_REGISTRY", "http://reg.g-vo.org/tap" ).rstrip("/") # ADQL only has string_agg, where we need string arrays. We fake arrays # by joining elements with a token separator that we think shouldn't # turn up in the things joined. Of course, people could create # resources that break us; let's assume there's nothing be gained # from that ever. TOKEN_SEP = ":::py VO sep:::" def shorten_stdid(s): """removes leading ivo://ivoa.net/std/ from s if present. We're using this to make the display and naming of standard ivoids less ugly in several places. Nones remain Nones. """ if s and s.startswith("ivo://ivoa.net/std/"): return s[19:] return s def expand_stdid(s): """returns s if it already looks like a URI, and it prepends ivo://ivoa.net/std otherwise. This is the (approximate) reverse of shorten_stdid. """ if s is None or "://" in s: return s return "ivo://ivoa.net/std/" + s def regularize_SIA2_id(standard_id): """returns standard_id with SIA2 standard ids modified to what they should have been. Regrettably, SIA2 uses the same standardID as SIA1; sure, they use different fragments, but that doesn't really help with the logic we have here. To make up for that, we replace them with the sia2 ids they should have had on input. This function assumes lowercased ids as they come from RegTAP services. """ if standard_id.startswith("ivo://ivoa.net/std/sia#query-2"): return "ivo://ivoa.net/std/sia2" elif standard_id.startswith("ivo://ivoa.net/std/sia#query-aux-2"): # query-aux-2 is mentioned in discovering data collections, # which isn't really the place to define this. But then # it's endorsed, and SIA2 doesn't say anything about it. return "ivo://ivoa.net/std/sia2#aux" else: return standard_id @functools.lru_cache(1) def get_RegTAP_service(): """ a lazily created TAP service offering the RegTAP services. Always get the TAP service there using this function to avoid re-creating the server and profit from caching of capabilties, tables, etc. To switch to a different RegTAP service, use :py:func:`choose_RegTAP_service`. """ return tap.TAPService(REGISTRY_BASEURL) def choose_RegTAP_service(access_url): """ changes the RegTAP service used by :py:func:`search` to the one at access_url. By default, pyVO uses whatever is given in the environment variable ``IVOA_REGISTRY``, defaulting to GAVO's TAP service. In order to change the service used on the fly, always use this function in order to clear caches that need clearing. Parameters ---------- access_url : str The TAP access URL of the new RegTAP endpoints. To find alternate endpoints, try ``regsearch(datamodel='regtap')`` and look at ``.get_interface("tap").access_url`` of the results. """ global REGISTRY_BASEURL get_RegTAP_service.cache_clear() REGISTRY_BASEURL = access_url def get_RegTAP_query(*constraints: rtcons.Constraint, includeaux=False, service=None, **kwargs): """returns SQL for a RegTAP query for constraints and keywords. This function's parameters are as for search; this is basically a wrapper for rtcons.build_regtap_query maintaining the legacy keyword-based interface. """ # we don't document the service parameter -- it's probably not useful # to users and is just the conscequence of having retrofitted service # sensing into the API. if service is None: service = get_RegTAP_service() constraints = list(constraints) + rtcons.keywords_to_constraints(kwargs) # maintain legacy includeaux by locating any Servicetype constraints # and replacing them with ones that includes auxiliaries. if includeaux: for index, constraint in enumerate(constraints): if isinstance(constraint, rtcons.Servicetype): constraints[index] = constraint.include_auxiliary_services() return rtcons.build_regtap_query(constraints, service) def search(*constraints: rtcons.Constraint, includeaux: bool = False, maxrec: int = None, **kwargs): """ execute a simple query to the RegTAP registry. The function accepts query constraints either as Constraint objects passed in as positional arguments or as their associated keywords. For what constraints are available, see :ref:`registry-basic-interface`. The values of keyword arguments may be tuples or lists when the associated Constraint objects take multiple arguments. All constraints, whether passed in directly or via keywords, are evaluated as a conjunction (i.e., in an AND clause). Parameters ---------- *constraints : `~pyvo.registry.Constraint` instances The constraints (keywords to match, positions to cover, ...) that the returned records need to satisfy. The accepted constraints are: - keywords: one or more freetext words, mached in the title, description or subject of the resource. - servicetype: constrain to one of tap, ssa, sia, conesearch (or full ivoids for other service types). This is the constraint you want to use for service discovery. - ucd: constrain by one or more UCD patterns; resources match when they serve columns having a matching UCD (e.g., phot.mag;em.ir.% for "any infrared magnitude"). - waveband: one or more terms from the vocabulary at http://www.ivoa.net/rdf/messenger giving the rough spectral location of the resource. - author: an author ("creator"). This is a single SQL pattern, and given the sloppy practices in the VO for how to write author names, you should probably generously use wildcards. - datamodel: one of obscore, epntap, or regtap: only return TAP services having tables of this kind. - ivoid: exactly match a single IVOA identifier (that is, in effect, the primary key in the VO). - spatial: match resources covering a certain geometry (point, circle, polygon, or MOC). RegTAP 1.2 Extension. - spectral: match resources covering a certain part of the spectrum (usually, but not limited to, the electromagnetic spectrum). RegTAP 1.2 Extension - temporal: match resources covering a some point or interval in time. RegTAP 1.2 Extension Multiple constraints are combined conjunctively ("AND"). includeaux : bool Flag for whether to include auxiliary capabilities in results. This may result in duplicate capabilities being returned, especially if the servicetype is not specified. maxrec : int Overrides the RegTAP server's default limit on the number of rows to return. You may need to use this if you want to retrieve more than a few thousand matches. The server may also have a hard limit that ``maxrec`` cannot override. Note that truncated search results are not reproducible. **kwargs : strings, mostly shorthands for ``constraints``; see the documentation of a specific constraint for what keyword it uses and what literal it expects. Returns ------- ~pyvo.registry.RegistryResults` a container holding a table of matching resource (e.g. services) See Also -------- RegistryResults """ service = get_RegTAP_service() query = RegistryQuery( service.baseurl, get_RegTAP_query(*constraints, includeaux=includeaux, service=service, **kwargs), maxrec=maxrec) return query.execute() class RegistryQuery(tap.TAPQuery): def execute(self): """ submit the query and return the results as a RegistryResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return RegistryResults(self.execute_votable(), url=self.queryurl) class RegistryResults(dalq.DALResults): """ an iterable set of results from a registry query. Each record is returned as RegistryResults You can iterate over these, or access them by (numeric) index; note, however, that these indexes will not be stable across different executions and thus should only be used in interactive sessions. Alternatively, you can use short names as indexes; there *might* be clashes for these, as they are not unique VO-wide. Where this matters, you need to use full ivoids as index. """ def getrecord(self, index): """ return all the attributes of a resource record with the given index as SimpleResource instance (a dictionary-like object). Parameters ---------- index : int the zero-based index of the record """ return RegistryResource(self, index) def get_summary(self): """ returns a brief overview of the matched results as an astropy table. This is mainly intended for interactive use, where people would like to inspect the matches in, perhaps, notebooks. """ return table.Table([ list(range(len(self))), [r.short_name for r in self], [r.res_title for r in self], [r.res_description for r in self], [", ".join(sorted(r.access_modes())) for r in self]], names=("index", "short_name", "title", "description", "interfaces"), descriptions=( "Index to access the resource within self", "Short name", "Resource title", "Resource description", "Access modes offered")) @functools.cache def _get_ivo_index(self): return {r.ivoid: index for index, r in enumerate(self)} @functools.cache def _get_short_name_index(self): return {r.short_name: index for index, r in enumerate(self)} def __getitem__(self, item): """ returns a record by numeric index, short names, or ivoid. This will raise an IndexError or a KeyError when item does not match a record returned. """ if isinstance(item, int): return self.getrecord(item) elif isinstance(item, str): if item.startswith("ivo://"): return self.getrecord(self._get_ivo_index()[item]) else: return self.getrecord(self._get_short_name_index()[item]) else: raise IndexError(f"No resource matching {item}") class _BrowserService: """A pseudo-service class just opening a web browser for browser-based services. """ def __init__(self, access_url, capability_description=None): self.baseurl = access_url self.capability_description = capability_description def __repr__(self): return f"BrowserService(baseurl : '{self.baseurl}', description : '{self.capability_description}')" def search(self): import webbrowser webbrowser.open(self.baseurl, 2) class Interface: """ a service interface. These consist of an access URL, a standard id for the capability (typically the ivoid of an IVOA standard, or None for free services), an interface type (something like vs:paramhttp or vr:webbrowser) and an indication if the interface is the "standard" interface of the capability. Such interfaces can be turned into services using the ``to_service`` method if pyvo knows how to talk to the interface. Note that the constructor arguments are assumed to be normalised as in regtap (e.g., lowercased for the standardIDs). """ service_for_standardid = { "ivo://ivoa.net/std/conesearch": scs.SCSService, "ivo://ivoa.net/std/sia": sia.SIAService, "ivo://ivoa.net/std/sia2": sia2.SIA2Service, "ivo://ivoa.net/std/ssa": ssa.SSAService, "ivo://ivoa.net/std/sla": sla.SLAService, "ivo://ivoa.net/std/tap": tap.TAPService} def __init__(self, access_url, *, standard_id=None, intf_type=None, intf_role=None, capability_description=None): self.access_url = access_url self.standard_id = standard_id self.type = intf_type self.role = intf_role self.capability_description = capability_description self.is_standard = self.role == "std" if self.standard_id is not None: self.is_vosi = self.standard_id.startswith("ivo://ivoa.net/std/vosi") else: self.is_vosi = False # a service is user visible if it has a corresponding service class if self.standard_id is not None and self.standard_id != "": service_type = self.standard_id.split("#")[0] # remove possible suffixes/standard keys self.is_user_visible = service_type in self.service_for_standardid # or if it is a webpage else: self.is_user_visible = self.type == "vr:webbrowser" def __repr__(self): if not self.standard_id or self.standard_id is None: return (f"Interface(type={self.type!r}, " f"description={self.capability_description!r}, " f"url={self.access_url!r})") return (f"Interface(type={self.standard_id.rsplit('/')[-1]!r}, " f"description={self.capability_description!r}, " f"url={self.access_url!r})") def to_service(self, *, session=None): if self.type == "vr:webbrowser": return _BrowserService(self.access_url, self.capability_description) if self.standard_id is None or not self.is_standard: raise ValueError("This is not a standard interface. PyVO" " cannot speak to it.") service_class = self.service_for_standardid.get( self.standard_id.split("#")[0]) if service_class is None: raise ValueError("PyVO has no support for interfaces with" f" standard id {self.standard_id}.") if service_class == sia2.SIA2Service: return service_class(self.access_url, capability_description=self.capability_description, check_baseurl=False, session=session) else: return service_class( self.access_url, capability_description=self.capability_description, session=session) def supports(self, standard_id): """returns true if we believe the interface should be able to talk standard_id. At this point, we naively check if the interfaces's standard_id has standard_id as a prefix. At this point, we cut off standard_id fragments for this purpose. This works for all current DAL standards but would, for instance, not work for VOSI. Hence, this may need further logic if we wanted to extend our service generation to VOSI or, perhaps, VOSpace. Parameters ---------- standard_id : str The ivoid of a standard. """ if not self.standard_id: return False standard_id = regularize_SIA2_id(standard_id) return self.standard_id.split("#")[0] == standard_id.split("#")[0] class RegistryResource(dalq.Record): """ a dictionary for the resource metadata returned in one record of a registry query. A SimpleResource acts as a dictionary, so in general, all attributes can be accessed by name via the [] operator, and the attribute names can by returned via the keys() function. For convenience, it also stores key values as properties; these include: """ _service = None # the following attribute is used by datasearch._build_regtap_query # to figure build the select clause; it is maintained here # because this class knows what it expects to get. # # Each item is either a plain string for a column name, or # a 2-tuple for an as clause; all plain strings are used # used in the group by, and so it is assumed they are # 1:1 to ivoid. expected_columns = [ "ivoid", "res_type", "short_name", "res_title", "content_level", "res_description", "reference_url", "creator_seq", "created", "updated", "rights", "content_type", "source_format", "source_value", "region_of_regard", "waveband", (f"\n ivo_string_agg(COALESCE(access_url, ''), '{TOKEN_SEP}')", "access_urls"), (f"\n ivo_string_agg(COALESCE(standard_id, ''), '{TOKEN_SEP}')", "standard_ids"), (f"\n ivo_string_agg(COALESCE(intf_type, ''), '{TOKEN_SEP}')", "intf_types"), (f"\n ivo_string_agg(COALESCE(intf_role, ''), '{TOKEN_SEP}')", "intf_roles"), (f"\n ivo_string_agg(COALESCE(cap_description, ''), '{TOKEN_SEP}')", "cap_descriptions")] def __init__(self, results, index, *, session=None): dalq.Record.__init__(self, results, index, session=session) self._mapping["access_urls" ] = self._parse_pseudo_array(self._mapping["access_urls"]) self._mapping["standard_ids"] = [ regularize_SIA2_id(id) for id in self._parse_pseudo_array(self._mapping["standard_ids"])] self._mapping["intf_types" ] = self._parse_pseudo_array(self._mapping["intf_types"]) self._mapping["intf_roles" ] = self._parse_pseudo_array(self._mapping["intf_roles"]) self._mapping["cap_descriptions" ] = self._parse_pseudo_array(self._mapping["cap_descriptions"]) self.interfaces = [Interface(props[0], standard_id=props[1], intf_type=props[2], intf_role=props[3], capability_description=props[4]) for props in itertools.zip_longest( self["access_urls"], self["standard_ids"], self["intf_types"], self["intf_roles"], self["cap_descriptions"])] @staticmethod def _parse_pseudo_array(literal): """ parses RegTAP pseudo-arrays into lists. Parameters ---------- literal : str the result of an ivo_string_agg call with TOKEN_SEP Returns ------- A list of strings corresponding to the orginal, database-side aggregate. """ if not literal: # As VOTable, we don't distinguish between None and "" return [] return literal.split(TOKEN_SEP) @property def ivoid(self): """ the IVOA identifier for the resource. """ return self.get("ivoid", decode=True) @property def res_type(self): """ the resource types that characterize this resource. """ return self.get("res_type", decode=True) @property def short_name(self): """ the short name for the resource """ return self.get("short_name", decode=True) @property def res_title(self): """ the title of the resource """ return self.get("res_title", default=None, decode=True) @property def content_levels(self): """ a list of content level labels that describe the intended audience for this resource. """ return self.get("content_level", default="", decode=True).split("#") @property def res_description(self): """ the textual description of the resource. """ return self.get("res_description", decode=True) @property def reference_url(self): """ URL pointing to a human-readable document describing this resource. """ return self.get("reference_url", decode=True) @property def creators(self): """ The creator(s) of the resource in the order given by the resource record author """ return self.get("creator_seq", default="", decode=True).split(";") @property def created(self): """Date of creation of the resource.""" return self.get("created", decode=True) @property def updated(self): """Date of last modification of the resource.""" return self.get("updated", decode=True) @property def rights(self): """A statement of usage conditions for the content of the resource. This information is often incomplete in the registry, you might get more information at the ``reference_url``. """ return self.get("rights", decode=True) @property def content_types(self): """ list of natures or genres of the content of the resource. """ return self.get("content_type", decode=True).split("#") @property def source_format(self): """ The format of source_value. """ return self.get("source_format", decode=True) @property def source_value(self): """ The bibliographic source for this resource (typically a bibcode or a DOI). """ return self.get("source_value", decode=True) @property def region_of_regard(self): """ numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be "blurred" in order to get an appropriate match. """ # we get NULLs as NaNs here val = self["region_of_regard"] if numpy.isnan(val): return None return val @property def waveband(self): """ a list of names of the wavebands that the resource provides data for """ return self.get("waveband", default="", decode=True).split("#") @property def access_url(self): """ the URL that can be used to access the service resource. """ # some services declare some data models using multiple # identifiers; in this case, we'll get the same access URL # multiple times in here. Be cool about that situation: access_urls = list(sorted(set(self["access_urls"]))) if len(access_urls) == 0: raise dalq.DALQueryError( f"The resource {self.ivoid} has no queriable interfaces.") elif len(access_urls) > 1: warnings.warn(AstropyDeprecationWarning( f"The resource {self.ivoid} has multiple capabilities. " " You should explicitly pick one using get_service. " " Returning some access_url now, but this behaviour " " may change in the future.")) return access_urls[0] @property def standard_id(self): """ the IVOA standard identifier """ standard_ids = list(set(self["standard_ids"])) if len(standard_ids) == 1: return standard_ids[0] else: raise dalq.DALQueryError( "This resource supports several standards ({})." " Use get_service or restrict your query using Servicetype." .format(", ".join(sorted(self.access_modes())))) def access_modes(self): """ returns a set of interface identifiers available on this resource. For standard interfaces, get_service will return a service suitable for querying if you pass in an identifier from this list as the service_type. This will ignore VOSI (infrastructure) services. """ return {shorten_stdid(intf.standard_id) or "web" for intf in self.interfaces if (intf.standard_id or intf.type == "vr:webbrowser") and not intf.is_vosi} def get_interface(self, *, service_type: str, lax: bool = False, std_only: bool = False, keyword: str = None): """returns a regtap.Interface class for service_type. The meaning of the parameters is as for get_service. This method does not return services, though, so you can use it to obtain access URLs and such for interfaces that pyVO does not (directly) support. Parameters ---------- service_type : str If you leave out ``service_type``, this will return a service for "the" standard interface of the resource. If a resource has multiple standard capabilities (e.g., both TAP and SSAP endpoints), this will raise a DALQueryError. Otherwise, a service of the given service type will be returned. Pass in an ivoid of a standard or one of the shorthands from rtcons.SERVICE_TYPE_MAP, or "web" for a web page (the "service" for this will be an object opening a web browser when you call its query method). lax : bool If there are multiple capabilities for service_type, the function choose the first matching capability by default Pass lax=False to instead raise a DALQueryError. std_only : bool Only return interfaces declared as "std". This is what you want when you want to construct pyVO service objects later. This parameter is ignored for the "web" service type. Returns ------- `~pyvo.registry.regtap.Interface` """ if service_type == "web": # this works very much differently in the Registry # than do the proper services candidates = [intf for intf in self.interfaces if intf.type == "vr:webbrowser"] else: service_type = expand_stdid( rtcons.SERVICE_TYPE_MAP.get( service_type, service_type)) candidates = [intf for intf in self.interfaces if ((not std_only) or intf.is_standard) and not intf.is_vosi and ((not service_type) or intf.supports(service_type))] if not candidates: raise ValueError("No matching interface.") if keyword is not None: candidates = [candidate for candidate in candidates if candidate.capability_description is not None and keyword in candidate.capability_description] if len(candidates) > 1 and not lax: raise ValueError("Multiple matching interfaces found. If you know that they are all equivalent, " "use 'lax=True'.\n" "Otherwise, the search can be constrained by specifying a 'service_type' " "or a 'keyword' to be found in the service description." " You might also want to see all the available services" " with `pyvo.registry.regtap.RegistryResource.list_interfaces()`.") return candidates[0] def get_service(self, service_type: str = None, *, lax: bool = False, keyword: str = None, session: object = None): """ return an appropriate DALService subclass for this resource that can be used to search the resource using service_type. Raise a ValueError if the service_type is not offered on the resource (or no standard service is offered). With lax=False, also raise a ValueError if multiple interfaces exist for the given service_type. VOSI (infrastructure) services are always ignored here. A magic service_type "web" can be passed in to get non-standard, browser-based interfaces. The service in this case is an object that opens a web browser if its query() method is called. Parameters ---------- service_type : str If you leave out ``service_type``, this will return a service for "the" standard interface of the resource. If a resource has multiple standard capabilities (e.g., both TAP and SSAP endpoints), this will raise a DALQueryError. Otherwise, a service of the given service type will be returned. Pass in an ivoid of a standard or one of the shorthands from rtcons.SERVICE_TYPE_MAP, or "web" for a web page (the "service" for this will be an object opening a web browser when you call its query method). lax : bool If there are multiple capabilities for service_type, the function choose the first matching capability by default Pass lax=False to instead raise a DALQueryError. keyword : str A keyword that should be in the service description. Some resources have multiple capabilities ("services") of the same type (see list_interfaces for a discussion). get_service will usually raise a ValueError in that case. Passing a ``keyword`` that uniquely identifies the capability to query will make get_service predictably return the desired service. Use list_interfaces to find such a unique description fragment. session : object optional requests session to use to communicate with the service constructed. Returns ------- `pyvo.dal.DALService` For standard service types, a specific DAL service instance (e.g., a `pyvo.dal.tap.TAPService` when requesting ``tap`` services) is returned. For ``web`` services, what is returned is an opaque service object that has a ``search()`` method simply opening a web browser on the access URL. See Also -------- list_interfaces : return a list with all the available services. """ return self.get_interface(service_type=service_type, lax=lax, std_only=True, keyword=keyword).to_service(session=session) @property def service(self): """ return a service for this resource. This will in general only work if the registry query has constrained the service type; otherwise, many resources will have multiple capabilities. Use get_service instead in such cases. See Also -------- get_service : select a service of a specific service type. list_interfaces : return a list with all the available interfaces to services. """ if self._service is not None: return self._service self._service = self.get_service(service_type=None, lax=True) return self._service def list_interfaces(self, service_type: str = None): """List the interfaces to services available for this registry record. The interface objects in the list can then be used to instantiate the service with the `~pyvo.registry.regtap.Interface.to_service` method. Parameters ---------- service_type : str, optional Restricts the list to interfaces corresponding to services of this service type. The list of possible values can be printed with ``pyvo.registry.rtcons.SERVICE_TYPE_MAP.keys()``. Returns ------- list A list of `~pyvo.registry.regtap.Interface` objects. See Also -------- get_service : when you already know that there is only one service of a specific service type. """ interfaces = [interface for interface in self.interfaces if interface.is_user_visible] if service_type is not None: service_type = expand_stdid(rtcons.SERVICE_TYPE_MAP.get(service_type, service_type)) interfaces = [interface for interface in interfaces if interface.is_standard and interface.supports(service_type)] return sorted(interfaces, key=lambda interface: interface.access_url) def search(self, *args, **keys): """ assuming this resource refers to a searchable service, execute a search against the resource. This is equivalent to: .. code:: python self.to_service().search(*args, **keys) The arguments provided should be appropriate for the service that the DAL service type would expect. See the documentation for the appropriate service type: ============ =========================================== Service type Use the argument syntax for ============ =========================================== catalog :py:meth:`pyvo.dal.scs.SCSService.search` sia :py:meth:`pyvo.dal.sia.SIAService.search` sia2 :py:meth:`pyvo.dal.sia2.SIA2Service.search` ssa :py:meth:`pyvo.dal.ssa.SSAService.search` line :py:meth:`pyvo.dal.sla.SLAService.search` database *not yet supported* ============ =========================================== Raises ------ DALServiceError if the resource does not describe a searchable service. """ try: return self.service.search(*args, **keys) except ValueError: # I blindly assume the ValueError comes out of get_interface. # But then that's likely enough. raise dalq.DALServiceError( f"Resource {self.ivoid} is not a searchable service") def describe(self, *, verbose=False, width=78, file=None): """ Print a summary description of this resource. Parameters ---------- verbose : bool If false (default), only user-oriented information is printed. If true, additional information -- reference url, reference to the related article, and alternative identifier (often a DOI) -- will be printed if available. width : int Format the description with given character-width. file : writable file-like object If provided, write information to this output stream. Otherwise, it is written to standard out. """ print(para_format_desc(self.res_title), file=file) print("Short Name: " + self.short_name, file=file) print("IVOA Identifier: " + self.ivoid, file=file) print("Access modes: " + ", ".join(sorted(self.access_modes())), file=file) for interface in self.list_interfaces(): if interface.is_user_visible: if interface.type == "vr:webbrowser": print(f"- webpage: {interface.access_url}", file=file) else: service_item = (f"- {interface.standard_id.rsplit('/')[-1]}: " f"{interface.access_url}") if interface.capability_description: service_item += f", description: {interface.capability_description}" print(textwrap.fill(service_item, width, subsequent_indent=" "), file=file) if self.res_description: print(file=file) print(para_format_desc(self.res_description), file=file) print(file=file) if self.waveband: val = (str(v) for v in self.waveband) print( para_format_desc("Waveband Coverage: " + ", ".join(val)), file=file) if verbose: if self.source_value: print(f"\nSource: {self.source_value}", file=file) if self.creators: # if any creator has a name longer than 70 characters, we # truncate it. creators = [f"{creator[:70]}..." if len(creator) > 70 else creator for creator in self.creators] nmax_authors = 5 if len(creators) <= nmax_authors: print(f"Authors: {', '.join(creators)}", file=file) else: print(f"Authors: {', '.join(creators[:nmax_authors])} et al.\n" "See creators attribute for the complete list of authors.", file=file) alt_identifiers = self.get_alt_identifiers() if alt_identifiers: print( "Alternative identifier(s): {}".format( ", ".join(alt_identifiers)), file=file) if self.reference_url: print("More info: " + self.reference_url, file=file) def get_contact(self): """ return contact information for this resource in a string. Use this to report bugs or unexpected downtime. """ res = get_RegTAP_service().run_sync(""" SELECT role_name, email, telephone FROM rr.res_role WHERE base_role='contact' AND ivoid={}""".format( rtcons.make_sql_literal(self.ivoid))) contacts = [] for row in res: contact = row["role_name"] if row["telephone"]: contact += f" ({row['telephone']})" if row["email"]: contact += f" <{row['email']}>" contacts.append(contact) return "\n".join(contacts) def get_alt_identifiers(self): """return a sequence of non-ivoid identifiers for the resource. This is typically used to provide a DOI for the resource. """ res = get_RegTAP_service().run_sync(""" SELECT alt_identifier FROM rr.alt_identifier WHERE ivoid={}""".format(rtcons.make_sql_literal(self.ivoid))) return [r["alt_identifier"] for r in res] def _build_vosi_column(self, column_row): """ return a io.vosi.vodataservice.Column element for a query result from get_tables. """ res = vodataservice.TableParam() for att_name in ["name", "ucd", "unit", "utype"]: setattr(res, att_name, column_row[att_name]) res.description = column_row["column_description"] # TODO: be more careful with the type; this isn't necessarily a # VOTable type (regrettably) res.datatype = vodataservice.VOTableType( arraysize=column_row["arraysize"], extendedType=column_row["extended_type"]) res.datatype.content = column_row["datatype"] return res def _build_vosi_table(self, table_row, columns): """ return a io.vosi.vodataservice.VODataServiceTable element for a query result from get_tables. """ res = vodataservice.VODataServiceTable() res.name = table_row["table_name"] res.title = table_row["table_title"] res.description = table_row["table_description"] res.utype = table_row["table_utype"] res._columns = [ self._build_vosi_column(row) for row in columns] res.origin = self return res def get_tables(self, *, table_limit=20): """ return the structure of the tables underlying the service. This returns a dict with table names as keys and vodataservice.VODataServiceTable objects as values (pretty much what tables returns for a TAP service). The table instances will have an ``origin`` attribute pointing back to the registry record. Note that not only TAP services can (and do) define table structures. The meaning of non-TAP tables is not always as clear. Also note that resources do not need to define tables at all. You will receive an empty dictionary if they don't. """ svc = get_RegTAP_service() tables = svc.run_sync( """SELECT table_name, table_description, table_index, table_title, table_utype FROM rr.res_table WHERE ivoid={}""".format( rtcons.make_sql_literal(self.ivoid))) if len(tables) > table_limit: raise dalq.DALQueryError(f"Resource {self.ivoid} reports" f" {len(tables)} tables. Pass a higher table_limit" " to see them all.") res = {} for table_row in tables: columns = svc.run_sync( """ SELECT name, ucd, unit, utype, datatype, arraysize, extended_type, column_description FROM rr.table_column WHERE ivoid={} AND table_index={}""".format( rtcons.make_sql_literal(self.ivoid), rtcons.make_sql_literal(table_row["table_index"]))) res[table_row["table_name"]] = self._build_vosi_table( table_row, columns) return res @deprecated("1.5", "ivoid2service does not work in the presence of" " multiple capabilities. Use" " registry.search(ivoid=...)[0].get_service('capname') instead.") def ivoid2service(ivoid, servicetype=None): """ return service(s) for a given IVOID. The servicetype option specifies the kind of service requested (conesearch, sia, sia2, ssa, slap, or tap). By default, if none is given, a list of all matching services is returned. """ constraints = [rtcons.Ivoid(ivoid)] if servicetype is not None: constraints.append(rtcons.Servicetype(servicetype)) resources = search(*constraints) if len(resources) == 0: if servicetype: raise dalq.DALQueryError(f"No resource {ivoid} with" f" {servicetype} capability.") else: raise dalq.DALQueryError(f"No resource {ivoid}") # We're grouping by ivoid in search, so if there's a result # there is only one. resource = resources[0] return resource.get_service(servicetype, lax=True) astropy-pyvo-b70558c/pyvo/registry/rtcons.py000066400000000000000000001267301510533647000213110ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Constraints for doing registry searches. The Constraint class encapsulates a query fragment in a RegTAP query, e.g., a keyword, a sky location, an author name, a class of services. They are used either directly as arguments to registry.search, or by passing keyword arguments into registry.search. The mapping from keyword arguments to constraint classes happens through the _keyword attribute in Constraint-derived classes. """ import datetime import warnings from astropy import units as u from astropy import constants from astropy.coordinates import SkyCoord from astropy.utils.exceptions import AstropyDeprecationWarning import numpy from ..dal import query as dalq from ..utils import vocabularies from .import regtap # Classes from this module are exposed at the higher level namespace, not listing them here __all__ = ["build_regtap_query"] # a mapping of service type shorthands to the ivoids of the # corresponding standards. This is mostly to keep legacy APIs. # In the future, preferably rely on shorten_stdid and expand_stdid # from regtap. SERVICE_TYPE_MAP = {k: "ivo://ivoa.net/std/" + v for k, v in [ ("image", "sia"), ("sia", "sia"), ("sia1", "sia"), # SIA2 is irregular # funky scheme used by SIA2 without breaking everything else ("spectrum", "ssa"), ("ssap", "ssa"), ("ssa", "ssa"), ("scs", "conesearch"), ("conesearch", "conesearch"), ("line", "slap"), ("slap", "slap"), ("table", "tap"), ("tap", "tap"), ]} class RegTAPFeatureMissing(dalq.DALQueryError): """ Raised when the current RegTAP server does not support a feature needed for a constraint. This could be that it is missing some ADQL feature indispensible to write that constraint, or because it is missing a table or column. To recover, choose another RegTAP service. Search constraining ``datamodel="regtap"``, and then use `pyvo.registry.choose_RegTAP_service` with a TAP access URL discovered in this way. """ class _AsIs(str): """a sentinel class make `make_sql_literal` not escape a string. """ def make_sql_literal(value): """makes a SQL literal from a python value. This is not suitable as a device to ward against SQL injections; in what we produce, callers could produce arbitrary SQL anyway. The point of this function is to minimize surprises when building constraints. Parameters ---------- value : object Conceptually, the function should produces SQL literals for anything that might reasonably add up in a registry query. In reality, a ValueError will be raised for anything we do not know about. Returns ------- str A SQL literal. """ if isinstance(value, _AsIs): return value if isinstance(value, str): return "'{}'".format(value.replace("'", "''")) elif isinstance(value, bytes): return "'{}'".format(value.decode("ascii").replace("'", "''")) elif isinstance(value, (int, numpy.integer, float, numpy.floating)): return f'{value}' elif isinstance(value, datetime.datetime): return f"'{value.isoformat()}'" elif isinstance(value, set): return '('+", ".join(make_sql_literal(s) for s in sorted(value))+')' else: raise ValueError("Cannot format {} as a SQL literal" .format(repr(value))) def format_function_call(func_name, args): """make an ADQL literal for a function call with arguments. Parameters ---------- func_name : str the name of the function to call. args : sequence of anything python values for the arguments for the function. Returns ------- str ADQL ready for inclusion into a query. """ return "{}({})".format( func_name, ", ".join(make_sql_literal(a) for a in args)) class Constraint: """an abstract base class for data discovery contraints. These, essentially, are configurable RegTAP query fragments, consisting of a where clause, parameters for filling that, and possibly additional tables. Users construct concrete constraints with whatever they would like to constrain things with. To implement a new constraint, in the constructor set ``_condition`` to a string with {}-type replacement fields (assume all parameters are strings), and define ``fillers`` to be a dictionary with values for the _condition template. Don't worry about SQL-serialising the values, Constraint takes care of that. If you need your Constraint to be "lazy" (cf. Servicetype), it's ok to overrride get_search_condition without an upcall to Constraint. If your constraints need extra tables, give them in a list in _extra_tables. For the legacy x_search with keywords, define a _keyword attribute containing the name of the parameter that should generate such a constraint. When pickung up such keywords, sequence values will in general be unpacked and turned into sequences of constraints. Constraints that want to the all arguments in the constructor can set takes_sequence to True. """ # TODO: _extra_tables is only used in the legacy leg of # the fulltext constraint any more, and there it's wrong, too. # Let's do away with extra_tables and tell people to use # SubqueriedConstraint whenever they think they need it. _extra_tables = [] _condition = None _fillers = None _keyword = None takes_sequence = False def get_search_condition(self, service): """ Formats this constraint to an ADQL fragment. This takes the service the constraint is being executed on as an argument because constraints may be written differently depending on the service's features or refuse to run altogether. Parameters ---------- service : `~pyvo.dal.TAPService` The RegTAP service the query is supposed to be run on (that is relevant because we adapt to the features available on given services). Returns ------- str A string ready for inclusion into a WHERE clause. """ if self._condition is None: raise NotImplementedError("{} is an abstract Constraint" .format(self.__class__.__name__)) return self._condition.format(**self._get_sql_literals()) def _get_sql_literals(self): """ returns self._fillers as a dictionary of properly SQL-escaped literals. """ if self._fillers: return {k: make_sql_literal(v) for k, v in self._fillers.items()} return {} class SubqueriedConstraint(Constraint): """ An (abstract) constraint for when the constraint is over a table other than rr.resource. We need to be careful with these, because they will in general have 1:n relationships to rr.resource, and these will lead to duplicated interfaces if we just do the NATURAL JOIN we normally do. Instead, we have to resort to ivoid in (subquery) conditions. In particular, extra_tables will always be empty for these. To configure those, give the table to query in _subquery_table and _condition, _fillers, and _keyword as usual. The rest is taken care of by get_search_condition. """ _subquery_table = None def get_search_condition(self, service): if self._condition is None or self._subquery_table is None: raise NotImplementedError("{} is an abstract Constraint" .format(self.__class__.__name__)) return ("ivoid IN (SELECT DISTINCT ivoid FROM {subquery_table}" " WHERE {condition})".format( subquery_table=self._subquery_table, condition=self._condition.format(**self._get_sql_literals()))) class Freetext(Constraint): """ A constraint using plain text to match against title, description, subjects, and person names. """ _keyword = "keywords" def __init__(self, *words: str): """ Parameters ---------- *words : str One or more string arguments. It is recommended to pass multiple words in multiple strings arguments. You can pass in phrases (i.e., multiple words separated by space), but behaviour might then vary quite significantly between different registries. Examples -------- >>> from pyvo import registry >>> registry.Freetext("Gamma", "Ray", "Burst") # doctest: +IGNORE_OUTPUT """ self.words = words def get_search_condition(self, service): # cross-table ORs kill the query planner. We therefore # write the constraint as an IN condition on a UNION # of subqueries if we can (i.e., the service has UNION); # It may look as if this has to be really slow, but in fact it's almost # always a lot faster than direct ORs. if service.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adql-sets", "UNION"): return self._get_union_condition(service) else: self._extra_tables = ["rr.res_subject"] return self._get_or_condition(service) def _get_union_condition(self, service): base_queries = [ "SELECT DISTINCT ivoid FROM rr.resource WHERE" " 1=ivo_hasword(res_description, {{{parname}}})", "SELECT DISTINCT ivoid FROM rr.resource WHERE" " 1=ivo_hasword(res_title, {{{parname}}})", "SELECT DISTINCT ivoid FROM rr.res_subject WHERE" " rr.res_subject.res_subject ILIKE {{{parpatname}}}"] self._fillers, subqueries = {}, [] for index, word in enumerate(self.words): parname = f"fulltext{index}" parpatname = f"fulltextpar{index}" self._fillers[parname] = word self._fillers[parpatname] = '%' + word + '%' args = locals() subqueries.append(" UNION ALL ".join( q.format(**args) for q in base_queries)) self._condition = " AND ".join( f"ivoid IN ({part})" for part in subqueries) return super().get_search_condition(service) def _get_or_condition(self, service): base_queries = [ " 1=ivo_hasword(res_description, {{{parname}}})", " 1=ivo_hasword(res_title, {{{parname}}})", " rr.res_subject.res_subject ILIKE {{{parpatname}}}"] self._fillers, conditions = {}, [] for index, word in enumerate(self.words): parname = f"fulltext{index}" parpatname = f"fulltextpar{index}" self._fillers[parname] = word self._fillers[parpatname] = '%' + word + '%' args = locals() conditions.append(" OR ".join( q.format(**args) for q in base_queries)) self._condition = " AND ".join(f"({part})" for part in conditions) return super().get_search_condition(service) class Author(SubqueriedConstraint): """ A constraint for creators (“authors”) of a resource; you can use SQL patterns here. The match is case-sensitive. """ _keyword = "author" _subquery_table = "rr.res_role" def __init__(self, name: str): """ Parameters ---------- name : str Note that regrettably there are no guarantees as to how authors are written in the VO. This means that you will generally have to write things like ``%Hubble%`` (% being “zero or more characters” in SQL) here. """ self._condition = "role_name LIKE {auth} AND base_role='creator'" self._fillers = {"auth": name} class Servicetype(Constraint): """ A constraint for for the availability of a certain kind of service on the result. The constraint normally is a custom keyword, one of: * ``sia``, ``sia1`` (SIAP version 1 services; prefer ``sia1`` for symmetry, although ``sia`` will be kept as the official IVOA short name for SIA1) * ``sia2`` (SIAP version 2 services) * ``ssa``, ``ssap`` (synonymous for SSAP services) * ``scs``, ``conesearch`` (synonymous for cone search services, prefer ``scs``) * ``line`` (for SLAP services) * ``tap``, ``table`` (synonymous for TAP services, prefer ``tap``) * ``image`` (a deprecated alias for sia) * ``spectrum`` (a deprecated alias for ssap) You can also pass in the standards' ivoid (which generally looks like ``ivo://ivoa.net/std/`` (except for SIA2) and have to be URIs with a scheme part in any case); note, however, that for standards pyVO does not know about it will not build service instances for you. Multiple service types can be passed in; a match in that case is for records having any of the service types passed in. Contrary to what the names of the service types may suggest, “image” and "spectrum" do *not* select all resources serving images or spectra. For instance, at the moment you would have to search for images served in three different ways: SIAv1, SIAv2, and ObsTAP. Against that, “image” only selects SIAv1, and "spectrum" similarly omits ObsTAP. A global discovery module is under active development to provide global dataset discovery. When it is ready, these two misleading options may be removed. The match is literal (i.e., no patterns are allowed); this means that you will not receive records that only have auxiliary services, which is what you want when enumerating all services of a certain type in the VO. In data discovery, you can use ``Servicetype(...).include_auxiliary_services()`` or use registry.search's ``includeaux`` parameter; but, really, there is little point using this constraint in data discovery in the first place. """ _keyword = "servicetype" def __init__(self, *stds): """ Parameters ---------- *stds : str one or more standards identifiers. The constraint will match records that have any of them. """ self.stdids = set() self.extra_fragments = [] for std in stds: if std in ('image', 'spectrum'): warnings.warn(AstropyDeprecationWarning( f"The '{std}' servicetype is deprecated. See" " https://pyvo.readthedocs.io/en/latest/api/pyvo.registry" ".Servicetype.html#pyvo.registry.Servicetype" " for more information.")) if std in SERVICE_TYPE_MAP: self.stdids.add(SERVICE_TYPE_MAP[std]) elif "://" in std: self.stdids.add(std) elif std == 'sia2': self.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'") elif std == 'hats': self.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/hats#hats-%'") elif std == 'hips': self.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/hips#hipslist-%'") else: raise dalq.DALQueryError("Service type {} is neither a full" " standard URI nor one of the bespoke identifiers" " {}, sia2, hats, hips".format(std, ", ".join(SERVICE_TYPE_MAP))) def clone(self): """returns a copy of this servicetype constraint. """ new_constraint = Servicetype() new_constraint.stdids = set(self.stdids) new_constraint.extra_fragments = self.extra_fragments[:] return new_constraint def get_search_condition(self, service): # we sort the stdids to make it easy for tests (and it's # virtually free for the small sets we have here). fragments = [] std_ids = ", ".join(make_sql_literal(s) for s in sorted(self.stdids)) if std_ids: fragments.append(f"standard_id IN ({std_ids})") return " OR ".join(fragments + self.extra_fragments) def include_auxiliary_services(self): """returns a Servicetype constraint that has self's service types but includes the associated auxiliary services. This is a convenience to maintain registry.search's signature. """ expanded = self.clone() expanded.stdids |= { std + '#aux' for std in expanded.stdids} if "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'" in expanded.extra_fragments: expanded.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/sia#query-aux-2.%'") return expanded class Waveband(Constraint): """ A constraint on messenger particles. This builds a constraint against rr.resource.waveband, i.e., a verbal indication of the messenger particle, coming from the IVOA vocabulary http://www.ivoa.net/rdf/messenger. The :py:class:`pyvo.registry.Spectral` constraint enables selections by particle energy, but few resources actually give the necessary metadata (in 2021). Multiple wavebands can be given (and are effectively combined with OR). """ _keyword = "waveband" _legal_terms = None def __init__(self, *bands): """ Parameters ---------- *bands : strings One or more of the terms given in http://www.ivoa.net/rdf/messenger. The constraint matches when a resource declares at least one of the messengers listed. """ if self.__class__._legal_terms is None: self.__class__._legal_terms = {w.lower() for w in vocabularies.get_vocabulary("messenger")["terms"]} bands = [band.lower() for band in bands] for band in bands: if band not in self._legal_terms: raise dalq.DALQueryError( f"Waveband {band} is not in the IVOA messenger" " vocabulary http://www.ivoa.net/rdf/messenger.") self.bands = list(bands) self._condition = " OR ".join( "1 = ivo_hashlist_has(rr.resource.waveband, {})".format( make_sql_literal(band)) for band in self.bands) class Datamodel(SubqueriedConstraint): """ A constraint on the adherence to a data model. This constraint only lets resources pass that declare support for one of several well-known data models; the SQL produced depends on the data model identifier. Records returned with these constraints will have a TAP (or auxiliary TAP) capability. Known data models at this point include: * obscore -- generic observational data * epntap -- solar system data * regtap -- the VO registry. * obscore-new -- the table-based new-style obscore discovery. Don't use in code built to last: this will become normal obscore when we have migrated the VO. DM names are matched case-insensitively here mainly for historical reasons. """ _keyword = "datamodel" # if you add to this list, you have to define a method # _make__constraint. _known_dms = {"obscore", "epntap", "regtap", "obscore_new"} def __init__(self, dmname): """ Parameters ---------- dmname : string A well-known name; currently one of obscore, epntap, and regtap. """ dmname = dmname.lower() if dmname not in self._known_dms: raise dalq.DALQueryError("Unknown data model id {}. Known are: {}." .format(dmname, ", ".join(sorted(self._known_dms)))) self._subquery_table, self._condition = getattr( self, f"_make_{dmname}_constraint")() def _make_obscore_constraint(self): # There was a bit of chaos with the DM ids for Obscore. # Be lenient here obscore_pat = 'ivo://ivoa.net/std/obscore%' return "rr.res_detail", ( "detail_xpath = '/capability/dataModel/@ivo-id'" f" AND 1 = ivo_nocasematch(detail_value, '{obscore_pat}')") def _make_obscore_new_constraint(self): return "rr.res_table NATURAL JOIN rr.resource", ( "table_utype LIKE 'ivo://ivoa.net/std/obscore#table-1.%'" # Only use catalogresource-typed records to keep out # full TAP services that may have the table in their # tablesets. " AND res_type = 'vs:catalogresource'") def _make_epntap_constraint(self): # we include legacy, pre-IVOA utypes for matches; lowercase # any new identifiers (utypes case-fold). return "rr.res_table", " OR ".join( f"table_utype LIKE '{pat}'" for pat in ['ivo://vopdc.obspm/std/epncore#schema-2.%', 'ivo://ivoa.net/std/epntap#table-2.%']) def _make_regtap_constraint(self): regtap_pat = 'ivo://ivoa.net/std/RegTAP#1.%' return "rr.res_detail", ( "detail_xpath = '/capability/dataModel/@ivo-id'" f" AND 1 = ivo_nocasematch(detail_value, '{regtap_pat}')") class Ivoid(Constraint): """ A constraint selecting a single resource by its IVOA identifier. """ _keyword = "ivoid" def __init__(self, ivoid, *more_ivoids): """ Parameters ---------- ivoid : string One or more string arguments. The IVOA identifier of the resource to match. As RegTAP requires lowercasing ivoids on ingestion, the constraint lowercases the ivoid passed in, too. more_ivoids : strings You can pass in multiple ivoids to match. As usual, they are combined by an or. """ self.ivoids = [id.lower() for id in (ivoid,) + more_ivoids] def get_search_condition(self, service): return " OR ".join( f"ivoid={make_sql_literal(id)}" for id in self.ivoids) class UCD(SubqueriedConstraint): """ A constraint selecting resources having tables with columns having UCDs matching a SQL pattern (% as wildcard). """ _keyword = "ucd" _subquery_table = "rr.table_column" def __init__(self, *patterns): """ Parameters ---------- patterns : strings SQL patterns (i.e., ``%`` is 0 or more characters) for UCDs. The constraint will match when a resource has at least one column matching one of the patterns. """ self._condition = " OR ".join( f"ucd LIKE {{ucd{i}}}" for i in range(len(patterns))) self._fillers = {f"ucd{index}": pattern for index, pattern in enumerate(patterns)} class UAT(SubqueriedConstraint): """ A constraint selecting resources having UAT keywords as subjects. The UAT (Unified Astronomy Thesaurus) is a hierarchical system of concepts in astronomy. In the VO, its concept identifiers are dashed strings, something like ``x-ray-transient-sources``. The full list of identifiers is available from http://www.ivoa.net/rdf/uat. Note that not all data providers properly use UAT keywords in their subjects even in 2025 (they should, though), and their keyword assignments may not always be optimal. Consider doing free text searches if UAT-based results are disappointing, and then telling the respective data providers about missing keywords. """ _keyword = "uat" _subquery_table = "rr.res_subject" _condition = "res_subject in {query_terms}" _uat = None @classmethod def _expand(cls, term, level, direction): """ Recursively expand term in the uat. This returns a set of concepts that are ``level`` levels wider or narrower (depending on the value of ``direction``) than term. This function assumes the _uat class attribute has been filled before; that is the case once a constraint has been constructed. Parameters ---------- term: str the start term level: int expand this many levels direction: str either ``wider`` to expand towards more general concepts or ``narrower`` to expand toward more specialised concepts. """ result = {term} new_concepts = cls._uat[term][direction] if level: for concept in new_concepts: result |= cls._expand(concept, level-1, direction) return result def __init__(self, uat_keyword, *, expand_up=0, expand_down=0): """ Parameters ---------- uat_keyword: str An identifier from http://www.ivoa.net/rdf/uat, i.e., a string like type-ib-supernovae. Note that these are always all-lowercase. expand_up: int In addition to the concept itself, also include expand_up levels of parent concepts (this is probably rarely makes sense beyond 1). expand_down: int In addition to the concept itself, also include expand_down levels of more specialised concepts (this is usually a good idea; having more than 10 here for now is equivalent to infinity). """ if self.__class__._uat is None: self.__class__._uat = vocabularies.get_vocabulary("uat")["terms"] if uat_keyword not in self._uat: raise dalq.DALQueryError( f"{uat_keyword} does not identify an IVOA uat" " concept (see http://www.ivoa.net/rdf/uat).") query_terms = {uat_keyword} if expand_up: query_terms |= self._expand(uat_keyword, expand_up, "wider") if expand_down: query_terms |= self._expand(uat_keyword, expand_down, "narrower") self._fillers = {"query_terms": query_terms} class Spatial(SubqueriedConstraint): """ A RegTAP constraint selecting resources covering a geometry in space. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Also note that not all data providers give spatial coverage for their resources. To find resources having data for RA/Dec 347.38/8.6772:: >>> from pyvo import registry >>> resources = registry.Spatial((347.38, 8.6772)) To find resources claiming to have data for a spherical circle 2 degrees around that point:: >>> resources = registry.Spatial((347.38, 8.6772, 2)) To find resources claiming to have data for a polygon described by the vertices (23, -40), (26, -39), (25, -43) in ICRS RA/Dec:: >>> resources = registry.Spatial([23, -40, 26, -39, 25, -43]) To find resources claiming to cover a MOC_, pass an ASCII MOC:: >>> resources = registry.Spatial("0/1-3 3/") .. _MOC: https://www.ivoa.net/documents/MOC/ To find resources which coverage is enclosed in a region, >>> enclosed = registry.Spatial("0/0-11", intersect="enclosed") To find resources which coverage intersects a region, >>> overlaps = registry.Spatial("0/0-11", intersect="overlaps") When you already have an astropy SkyCoord:: >>> from astropy.coordinates import SkyCoord >>> resources = registry.Spatial(SkyCoord("23d +3d")) SkyCoords also work as circle centers (plain floats for the radius are interpreted in degrees):: >>> resources = registry.Spatial((SkyCoord("23d +3d"), 3)) Or you can provide the radius angle as an Astropy Quantity: >>> resources = registry.Spatial((SkyCoord("23d +3d"), 1*u.rad)) """ _keyword = "spatial" _subquery_table = "rr.stc_spatial" takes_sequence = True def __init__(self, geom_spec, *, order=6, intersect="covers", inclusive=False): """ Parameters ---------- geom_spec : object For now, this is DALI-style: a 2-sequence is interpreted as a DALI point, a 3-sequence as a DALI circle, a 2n sequence as a DALI polygon. Additionally, strings are interpreted as ASCII MOCs, SkyCoords as points, and a pair of a SkyCoord and a float or Quantity as a circle. Other types (proper geometries or MOCPy objects) might be supported in the future. order : int, optional Non-MOC geometries are converted to MOCs before comparing them to the resource coverage. By default, this constraint uses order 6, which corresponds to about a degree of resolution and is what RegTAP recommends as a sane default for the order actually used for the coverages in the database. intersect : str, optional Allows to specify the connection between the resource coverage and the *geom_spec*. The possible values are 'covers' for services that completely cover the *geom_spec* region, 'enclosed' for services completely enclosed in the region and 'overlaps' for services which coverage intersect the region. inclusive : bool, optional Normally, this constraint will remove all resources that do not declare their spatial coverage. Pass inclusive=True to retain these. """ self.inclusive = inclusive def tomoc(s): return _AsIs(f"MOC({order}, {s})") if isinstance(geom_spec, str): geom = _AsIs("MOC({})".format( make_sql_literal(geom_spec))) elif isinstance(geom_spec, SkyCoord): geom = tomoc(format_function_call("POINT", (geom_spec.ra.value, geom_spec.dec.value))) elif len(geom_spec) == 2: if isinstance(geom_spec[0], SkyCoord): # If radius given is astropy quantity, then convert to degrees if isinstance(geom_spec[1], u.Quantity): if geom_spec[1].unit.physical_type != 'angle': raise ValueError("Radius quantity is not of type angle.") radius = geom_spec[1].to(u.deg).value else: radius = geom_spec[1] geom = tomoc(format_function_call("CIRCLE", [geom_spec[0].ra.value, geom_spec[0].dec.value, radius])) else: geom = tomoc(format_function_call("POINT", geom_spec)) elif len(geom_spec) == 3: geom = tomoc(format_function_call("CIRCLE", geom_spec)) elif len(geom_spec) % 2 == 0: geom = tomoc(format_function_call("POLYGON", geom_spec)) else: raise ValueError("This constraint needs DALI-style geometries.") if intersect == "covers": self._condition = f"1 = CONTAINS({geom}, coverage)" elif intersect == "enclosed": self._condition = f"1 = CONTAINS(coverage, {geom})" elif intersect == "overlaps": self._condition = f"1 = INTERSECTS(coverage, {geom})" else: raise ValueError("'intersect' should be one of 'covers', 'enclosed', or 'overlaps' " f"but its current value is '{intersect}'.") def get_search_condition(self, service): # we *could* make this a bit less demanding on the server # if we MOC-ified the geometries locally -- but then we'd # have to depend on pymoc, and that's too high a price for # something as esoteric as a server that understands # MOC-based geometries but does not have a MOC function. if not service.get_tap_capability().get_adql().get_feature( "ivo://org.gavo.dc/std/exts#extra-adql-keywords", "MOC"): raise RegTAPFeatureMissing( "Current RegTAP service does not support MOC.") # We should compare case-insensitively here, but then we don't # with delimited identifiers -- in the end, that would have to # be handled in dal.vosi.VOSITables. if "rr.stc_spatial" not in service.tables: raise RegTAPFeatureMissing( "stc_spatial missing on current RegTAP service") cond = super().get_search_condition(service) if self.inclusive: return cond[:-1]+" OR coverage IS NULL)" else: return cond class Spectral(SubqueriedConstraint): """ A RegTAP constraint on the spectral coverage of resources. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Worse, not too many resources bother declaring this at this point. For robustness, it might be preferable to use the `Waveband` constraint for the time being.. This constraint accepts quantities, i.e., values with units, and will convert them to RegTAP's representation (which is Joule of particle energy) if it can. This ought to work for wavelengths, frequencies, and energies. Plain numbers are interpreted as particle energies in Joule. RegTAP uses the observer frame at the solar system barycenter, but it is probably wise to use constraints suitably relaxed such that frame and reference position (within reason) do not matter. To find resources covering the messenger particle energy 5 eV:: >>> from astropy import units as u >>> from pyvo import registry >>> resources = registry.Spectral(5*u.eV) To find resources overlapping the band between 5000 and 6000 Ångström:: >>> resources = registry.Spectral((5000*u.Angstrom, 6000*u.Angstrom)) To find resources having data in the FM band:: >>> resources = registry.Spectral((88*u.MHz, 102*u.MHz)) """ _keyword = "spectral" _subquery_table = "rr.stc_spectral" takes_sequence = True def __init__(self, spec, *, inclusive=False): """ Parameters ---------- spec : astropy.Quantity or a 2-tuple of astropy.Quantity-s A spectral point or interval to cover. This must be a wavelength, a frequency, or an energy, or a pair of such quantities, in which case the argument is interpreted as an interval. All resources *overlapping* the interval are returned. Plain floats are interpreted as messenger energy in Joule. inclusive : bool, optional Normally, this constraint will remove all resources that do not declare their spectral coverage. Pass inclusive=True to retain these. """ self.inclusive = inclusive if isinstance(spec, tuple): self._fillers = { "spec_lo": self._to_joule(spec[0]), "spec_hi": self._to_joule(spec[1])} self._condition = ("1 = ivo_interval_overlaps(" "spectral_start, spectral_end, {spec_lo}, {spec_hi})") else: self._fillers = { "spec": self._to_joule(spec)} self._condition = "{spec} BETWEEN spectral_start AND spectral_end" def _to_joule(self, quant): """returns a spectral quantity as a float in joule. A plain float is returned as-is. """ if isinstance(quant, (float, int)): return quant try: # is it an energy? return quant.to(u.Joule).value except u.UnitConversionError: pass # try next try: # is it a wavelength? return (constants.h * constants.c / quant.to(u.m)).value except u.UnitConversionError: pass # try next try: # is it a frequency? return (constants.h * quant.to(u.Hz)).value except u.UnitConversionError: pass # fall through to give up raise ValueError(f"Cannot make a spectral quantity out of {quant}") def get_search_condition(self, service): if "rr.stc_spectral" not in service.tables: raise RegTAPFeatureMissing( "stc_spectral missing on current RegTAP service") cond = super().get_search_condition(service) if self.inclusive: return (f"({cond}) OR NOT EXISTS(" "SELECT 1 FROM rr.stc_spectral AS inner_s WHERE" " inner_s.ivoid=rr.resource.ivoid)") else: return cond class Temporal(SubqueriedConstraint): """ A RegTAP constraint on the temporal coverage of resources. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Worse, not too many resources bother declaring this at this point. Until this changes, you will probably have a lot of false negatives (i.e., resources that should match but do not because they are not declaring their time coverage) if you use this constraint. This constraint accepts astropy Time instances or pairs of Times when specifying intervals. Plain numbers will be interpreted as MJD. RegTAP uses TDB times at the solar system barycenter, and it is probably wise to relax constraints such that such details do not matter. This constraint does not attempt any conversions of time scales or reference positions. To find resources claiming to have data for Jan 10, 2022:: >>> from pyvo import registry >>> from astropy.time import Time >>> resources = registry.Temporal(Time('2022-01-10')) To find resources claiming to have data for some time between MJD 54130 and 54200:: >>> resources = registry.Temporal((54130, 54200)) """ _keyword = "temporal" _subquery_table = "rr.stc_temporal" takes_sequence = True def __init__(self, times, *, inclusive=False): """ Parameters ---------- spec : astropy.Time or a 2-tuple of astropy.Time-s A point in time or time interval to cover. Plain numbers are interpreted as MJD. All resources *overlapping* the interval are returned. inclusive : bool, optional Normally, this constraint will remove all resources that do not declare their temproal coverage. Pass inclusive=True to retain these. """ self.inclusive = inclusive if isinstance(times, tuple): self._fillers = { "time_lo": self._to_mjd(times[0]), "time_hi": self._to_mjd(times[1])} self._condition = ("1 = ivo_interval_overlaps(" "time_start, time_end, {time_lo}, {time_hi})") else: self._fillers = { "time": self._to_mjd(times)} self._condition = "{time} BETWEEN time_start AND time_end" def _to_mjd(self, quant): """returns a time specification in MJD. Times not corresponding to a single point in time are rejected. A plain float is returned as-is. """ if isinstance(quant, (float, int)): return quant val = quant.to_value('mjd') if not isinstance(val, numpy.number): raise ValueError("RegTAP time constraints must be made from" " single time instants.") return val def get_search_condition(self, service): if "rr.stc_temporal" not in service.tables: raise RegTAPFeatureMissing( "stc_temporal missing on current RegTAP service") cond = super().get_search_condition(service) if self.inclusive: return (f"({cond}) OR NOT EXISTS(" "SELECT 1 FROM rr.stc_temporal AS inner_t WHERE" " inner_t.ivoid=rr.resource.ivoid)") else: return cond # NOTE: If you add new Contraint-s, don't forget to add them in # registry.__init__, in docs/registry/index.rst and in the docstring # of regtap.query. def build_regtap_query(constraints, service): """returns a RegTAP query ready for submission from a list of Constraint instances. Parameters ---------- constraints : sequence of `~pyvo.registry.Constraint`-s A sequence of constraints for a RegTAP query. All of them will become part of a conjunction (i.e., all of them have to be satisfied for a record to match). service : `~pyvo.dal.TAPService` The RegTAP service the query is supposed to be run on (that is relevant because we adapt to the features available on given services). Returns ------- str An ADQL literal ready for submission to a RegTAP service. """ if not constraints: raise dalq.DALQueryError( "No search parameters passed to registry search") serialized, extra_tables = [], set() for constraint in constraints: if isinstance(constraint, str): constraint = Freetext(constraint) serialized.append( "(" + constraint.get_search_condition(service) + ")") extra_tables |= set(constraint._extra_tables) joined_tables = ["rr.resource", "rr.capability", "rr.interface" ] + list(sorted(extra_tables)) # see comment in regtap.RegistryResource for the following # oddity select_clause, plain_columns = [], [] for col_desc in regtap.RegistryResource.expected_columns: if isinstance(col_desc, str): select_clause.append(col_desc) plain_columns.append(col_desc) else: select_clause.append("{} AS {}".format(*col_desc)) fragments = ["SELECT", ", ".join(select_clause), "FROM", "\nNATURAL LEFT OUTER JOIN ".join(joined_tables), "WHERE", "\n AND ".join(serialized), "GROUP BY", ", ".join(plain_columns)] return "\n".join(fragments) def keywords_to_constraints(keywords): """returns constraints expressed as keywords as Constraint instances. Parameters ---------- keywords : dict regsearch arguments as a kwargs-style dictionary. Returns ------- sequence of `Constraint`-s Raises ------ DALQueryError if an unknown keyword is encountered. """ constraints = [] for keyword, value in keywords.items(): if keyword not in _KEYWORD_TO_CONSTRAINT: raise TypeError(f"{keyword} is not a valid registry" " constraint keyword. Use one of {}.".format( ", ".join(sorted(_KEYWORD_TO_CONSTRAINT)))) constraint_class = _KEYWORD_TO_CONSTRAINT[keyword] if (isinstance(value, (tuple, list)) and not constraint_class.takes_sequence): constraints.append(constraint_class(*value)) else: constraints.append(constraint_class(value)) return constraints def _make_constraint_map(): """returns a map of _keyword to constraint classes. This is used in module initialisation. """ keyword_to_constraint = {} for att_name, obj in globals().items(): if (isinstance(obj, type) and issubclass(obj, Constraint) and obj._keyword): keyword_to_constraint[obj._keyword] = obj return keyword_to_constraint _KEYWORD_TO_CONSTRAINT = _make_constraint_map() astropy-pyvo-b70558c/pyvo/registry/tests/000077500000000000000000000000001510533647000205605ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/registry/tests/__init__.py000066400000000000000000000001001510533647000226600ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/registry/tests/commonfixtures.py000066400000000000000000000041171510533647000242170ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Common fixtures for pyVO registry tests """ import pytest from astropy.utils.data import ( get_pkg_data_filename, import_file_to_cache) # We need to populate the vocabulary cache with our test data; # we cannot use requests_mock here because a.u.data uses urllib. from astropy.utils.data import _get_download_cache_loc, _url_to_dirname # noqa: F401 @pytest.fixture() def messenger_vocabulary(mocker): """the IVOA messenger vocabulary in astropy's cache. Should we clean up after ourselves? """ import_file_to_cache( 'http://www.ivoa.net/rdf/messenger', get_pkg_data_filename( 'data/messenger.desise', package=__package__)) # We need an object standing in for TAP services for query generation. # It would perhaps be nice to pull up a real TAPService instance from # capabilities and tables, but that's a non-trivial amount of XML. # Let's see how far we get with faking it. class _FakeLanguage: """ a stand-in for vosi.tapregext.Language for rtcons.Constrants. """ def __init__(self, features): self.features = features def get_feature(self, type, form): return (type, form) in self.features class _FakeTAPService: """ A stand-in for a TAP service intended for rtcons.Constraints. features is a set of (type, form) tuples for now. tables is a dict with table names as keys (let's worry about the values later). """ def __init__(self, features, tables): self.tables = tables adql_lang = _FakeLanguage(features) class _: def get_adql(otherself): return adql_lang self.tap_cap = _() def get_tap_capability(self): return self.tap_cap FAKE_GAVO = _FakeTAPService({ ("ivo://ivoa.net/std/TAPRegExt#features-adql-sets", "UNION"), ("ivo://org.gavo.dc/std/exts#extra-adql-keywords", "MOC"), }, { "rr.stc_spatial": None, "rr.stc_spectral": None, "rr.stc_temporal": None, }) FAKE_PLAIN = _FakeTAPService(frozenset(), {}) astropy-pyvo-b70558c/pyvo/registry/tests/conftest.py000066400000000000000000000012661510533647000227640ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins astropy-pyvo-b70558c/pyvo/registry/tests/data/000077500000000000000000000000001510533647000214715ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/registry/tests/data/README000066400000000000000000000001421510533647000223460ustar00rootroot00000000000000Instructions for how to update these data items should be found in the fixtures in test_regtap.py astropy-pyvo-b70558c/pyvo/registry/tests/data/capabilities.xml000066400000000000000000000504011510533647000246440ustar00rootroot00000000000000 http://dc.zah.uni-heidelberg.de/taphttps://dc.zah.uni-heidelberg.de/tapGloTS 1.0Registry 1.1Obscore-1.1ADQL2.02.1The Astronomical Data Query Language is the standard IVOA dialect of SQL; it contains a very general SELECT statement as well as some extensions for spherical geometry and higher mathematics.
gavo_apply_pm(ra DOUBLE PRECISION, dec DOUBLE PRECISION, pmra DOUBLE PRECISION, pmde DOUBLE PRECISION, epdist DOUBLE PRECISION) -> POINT
Returns a POINT (in the UNDEFINED reference frame) for the position an object at ra/dec with proper motion pmra/pmde has after epdist years. positions must be in degrees, PMs in should be in julian years (i.e., proper motions are expected in degrees/year). pmra is assumed to contain cos(delta). This function goes through the tangential plane. Since it does not have information on distance and radial velocity, it cannot reconstruct the true space motion, and hence its results will degrade over time. This function should not be used in new queries; use ivo_epoch_prop instead.
gavo_getauthority(ivoid TEXT) -> TEXT
returns the authority part of an ivoid (or, more generally a URI). So, ivo://org.gavo.dc/foo/bar#baz becomes org.gavo.dc. The behaviour for anything that's not a full URI is undefined.
gavo_ipix(long REAL, lat REAL) -> BIGINT
gavo_ipix returns the q3c ipix for a long/lat pair (it simply wraps the 13c_ang2ipix function). This is probably only relevant when you play tricks with indices or PPMXL ids.
gavo_match(pattern TEXT, string TEXT) -> INTEGER
gavo_match returns 1 if the POSIX regular expression pattern matches anything in string, 0 otherwise.
gavo_mocintersect(moc1 MOC, moc2 MOC) -> MOC
returns the intersection of two MOCs.
gavo_mocunion(moc1 MOC, moc2 MOC) -> MOC
returns the union of two MOCs.
gavo_specconv(expr DOUBLE PRECISION, dest_unit TEXT) -> DOUBLE PRECISION
returns the spectral value expr converted to dest_unit. expr has to be in either energy, wavelength, or frequency, and dest_unit must be a VOUnit giving another spectral unit (e.g., MHz, keV, nm, or Angstrom). This is intended to let users express spectral constraints in their preferred unit independently of the choice of unit in the database. Examples:: gavo_specconv(obscore.em_min, "keV") > 300 gavo_specconv(obscore.em_max, "MHz") > 30 gavo_specconv(spectral_start, "Angstrom") > 4000 There is a variant of gavo_specconv accepting expr's unit in a third argument.
gavo_specconv(expr NUMERIC, expr_unit TEXT, dest_unit TEXT) -> NUMERIC
returns expr assumed to be in expr_unit expressed in dest_unit. This is a variant of the two-argument gavo_specconv for when the unit of expr is not known to the ADQL translator, either because it because it is a literal or because it does not look like a spectral unit. Examples:: gavo_specconv(656, 'nm', 'J') BETWEEN spectral_start AND spectral_end gavo_specconv(arccos(phi)*incidence, 'Hz', 'eV') Clearly, overriding known units is likely to yield bad results; the translator therefore warns if an existing unit is overridden with a different unit.
gavo_vocmatch(vocname TEXT, term TEXT, matchagainst TEXT) -> INTEGER
returns 1 if matchagainst is term or narrower in the IVOA vocabulary vocname, 0 otherwise. This is intended for semantic querying. For instance, gavo_vocmatch('datalink/core', 'calibration', semantics) would be 1 if semantics is any of calibration, bias, dark, or flat. For RDF-flavoured vocabularies (strict trees), term will expand to the entire branch rooted in term. For SKOS-flavoured vocabularies (where narrower is not transitive), only directly narrower terms will be included. Both the term and the vocabulary name must be string literals (i.e., constants). matchagainst can be any string-valued expression.
ivo_epoch_prop(ra DOUBLE PRECISION, dec DOUBLE PRECISION, parallax DOUBLE PRECISION, pmra DOUBLE PRECISION, pmdec DOUBLE PRECISION, radial_velocity DOUBLE PRECISION, ref_epoch DOUBLE PRECISION, out_epoch DOUBLE PRECISION) -> DOUBLE PRECISION[6]
Returns a 6-vector of (ra, dec, parallax, pmra, pmdec, rv) at out_epoch for these quantities at ref_epoch. Essentially, it will apply the proper motion under the assumption of linear motion. Despite the name of the positional parameters, this is not restricted to equatorial systems, as long as positions and proper motions are expressed in the same reference frames. Units on input and output are degrees for ra and dec, mas for parallax, mas/yr for pmra and pmdec, and km/s for the radial velocity. ref_epoch and out_epoch are given in Julian years. parallax, pmra, pmdec, and radial_velocity may be None and will enter the computations as 0 then, except in the case of parallax, which will be some small value. When abs(parallax) is smaller or equal to that small value, parallax and radial velocity will be NULL on output. In daily use, you probably want to use the ivo_epoch_prop_pos functions.
ivo_epoch_prop_pos(ra DOUBLE PRECISION, dec DOUBLE PRECISION, parallax DOUBLE PRECISION, pmra DOUBLE PRECISION, pmdec DOUBLE PRECISION, radial_velocity DOUBLE PRECISION, ref_epoch DOUBLE PRECISION, out_epoch DOUBLE PRECISION) -> POINT
Returns a POINT giving the position at out_epoch for an object with the six parameters at ref_epoch. Essentially, it will apply the proper motion under the assumption of linear motion. Despite the name of the positional parameters, this is not restricted to equatorial systems, as long as positions and proper motions are expressed in the same reference frames. Units on input are degrees for ra and dec, mas for parallax, mas/yr for pmra and pmdec, and km/s for the radial velocity. ref_epoch and out_epoch are given in Julian years. parallax, pmra, pmdec, and radial_velocity may be None and will enter the computations as 0 then, except in the case of parallax, which will be some small value.
ivo_epoch_prop_pos(ra DOUBLE PRECISION, dec DOUBLE PRECISION, pmra DOUBLE PRECISION, pmdec DOUBLE PRECISION, ref_epoch DOUBLE PRECISION, out_epoch DOUBLE PRECISION) -> POINT
A variant of ivo_epoch_prop_pos that behave as if parallax and radial_velocity were both passed as NULL.
ivo_geom_transform(from_sys TEXT, to_sys TEXT, geo GEOMETRY) -> GEOMETRY
The function transforms ADQL geometries between various reference systems. geo can be a POINT, a CIRCLE, or a POLYGON, and the function will return a geometry of the same type. In the current implementation, from_sys and to_sys must be literal strings (i.e., they cannot be computed through expressions or be taken from database columns). All transforms are just simple rotations, which is only a rough approximation to the actual relationships between reference systems (in particular between FK4 and ICRS-based ones). Note that, in particular, the epoch is not changed (i.e., no proper motions are applied). We currently support the following reference frames: ICRS, FK5 (which is treated as ICRS), FK4 (for B1950. without epoch-dependent corrections), GALACTIC. Reference frame names are case-sensitive.
ivo_hashlist_has(hashlist TEXT, item TEXT) -> INTEGER
The function takes two strings; the first is a list of words not containing the hash sign (#), concatenated by hash signs, the second is a word not containing the hash sign. It returns 1 if, compared case-insensitively, the second argument is in the list of words coded in the first argument. The behaviour in case the the second argument contains a hash sign is unspecified.
ivo_hasword(haystack TEXT, needle TEXT) -> INTEGER
gavo_hasword returns 1 if needle shows up in haystack, 0 otherwise. This is for "google-like"-searches in text-like fields. In word, you can actually employ a fairly complex query language; see https://www.postgresql.org/docs/current/textsearch.html for details.
ivo_healpix_center(hpxOrder INTEGER, hpxIndex BIGINT) -> POINT
returns a POINT corresponding to the center of the healpix with the given index at the given order.
ivo_healpix_index(order INTEGER, ra DOUBLE PRECISION, dec DOUBLE PRECISION) -> BIGINT
Returns the index of the (nest) healpix with order containing the spherical point (ra, dec). An alternative, 2-argument form ivo_healpix_index(order INTEGER, p POINT) -> BIGINT is also available.
ivo_histogram(val REAL, lower REAL, upper REAL, nbins INTEGER) -> INTEGER[]
The aggregate function returns a histogram of val with nbins+2 elements. Assuming 0-based arrays, result[0] contains the number of underflows (i.e., val<lower), result[nbins+1] the number of overflows. Elements 1..nbins are the counts in nbins bins of width (upper-lower)/nbins. Clients will have to convert back to physical units using some external communication, there currently is no (meta-) data as lower and upper in the TAP response.
ivo_interval_has(val NUMERIC, iv INTERVAL) -> INTEGER
The function returns 1 if the interval iv contains val, 0 otherwise. The lower limit is always included in iv, behaviour on the upper limit is column-specific.
ivo_interval_overlaps(l1 NUMERIC, h1 NUMERIC, l2 NUMERIC, h2 NUMERIC) -> INTEGER
The function returns 1 if the interval [l1...h1] overlaps with the interval [l2...h2]. For the purposes of this function, the case l1=h2 or l2=h1 is treated as overlap. The function returns 0 for non-overlapping intervals.
ivo_nocasematch(value TEXT, pattern TEXT) -> INTEGER
ivo_nocasematch returns 1 if pattern matches value, 0 otherwise. pattern is defined as for the SQL LIKE operator, but the match is performed case-insensitively. This function in effect provides a surrogate for the ILIKE SQL operator that is missing from ADQL. On this site, this is actually implemented using python's and SQL's LOWER, so for everything except ASCII, your mileage will vary.
ivo_normal_random(mu REAL, sigma REAL) -> REAL
The function returns a random number drawn from a normal distribution with mean mu and width sigma. Implementation note: Right now, the Gaussian is approximated by summing up and scaling ten calls to random. This, hence, is not very precise or fast. It might work for some use cases, and we will provide a better implementation if this proves inadequate.
ivo_simbadpoint(identifier TEXT) -> POINT
gavo_simbadpoint queries simbad for an identifier and returns the corresponding point. Note that identifier can only be a literal, i.e., as simple string rather than a column name. This is because our database cannot query simbad, and we probably wouldn't want to fire off millions of simbad queries anyway; use simbad's own TAP service for this kind of application.
ivo_string_agg(expression TEXT, delimiter TEXT) -> TEXT
An aggregate function returning all values of expression within a GROUP contcatenated with delimiter
ivo_to_jd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
ivo_to_mjd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to modified julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
BOX
POINT
CIRCLE
POLYGON
REGION
CENTROID
COORD1
COORD2
DISTANCE
CONTAINS
INTERSECTS
AREA
LOWER
ILIKE
OFFSET
CAST
IN_UNIT
WITH
TABLESAMPLE
Written after a table reference, TABLESAMPLE(10) will make the database only use 10% of the rows; these are `somewhat random' in that the system will use random blocks. This should be good enough when just testing queries (and much better than using TOP n).
MOC
A geometry function creating MOCs. It either takes a string argument with an ASCII MOC ('4/13 17-18 8/3002'), or an order and another geometry.
COALESCE
This is the standard SQL COALESCE for providing defaults in case of NULL values.
VECTORMATH
You can compute with vectors here. See https://wiki.ivoa.net/twiki/bin/view/IVOA/ADQLVectorMath for an overview of the functions and operators available.
CASE
The SQL92 CASE expression
UNION
EXCEPT
INTERSECT
text/tab-separated-valuestsvtext/plaintxttext/csvcsv_baretext/csv;header=presentcsvapplication/jsonjsonapplication/geo+jsongeojsonapplication/x-votable+xmlvotableapplication/x-votable+xml;serialization=BINARY2votable/b2votableb2application/x-votable+xml;serialization=TABLEDATAtext/xmlvotable/tdvotabletdapplication/x-votable+xml;serialization=TABLEDATA;version=1.1text/xmlvotabletd1.1application/x-votable+xml;version=1.1text/xmlvotable1.1application/x-votable+xml;serialization=TABLEDATA;version=1.2text/xmlvotabletd1.2application/x-votable+xml;serialization=TABLEDATA;version=1.5vodmlapplication/x-votable+xml;version=1.5vodmlbtext/htmlhtmlapplication/fitsfits17280072002000016000000100000000
http://dc.zah.uni-heidelberg.de/__system__/tap/run/availabilityhttps://dc.zah.uni-heidelberg.de/__system__/tap/run/availabilityhttp://dc.zah.uni-heidelberg.de/__system__/tap/run/capabilitieshttps://dc.zah.uni-heidelberg.de/__system__/tap/run/capabilitieshttp://dc.zah.uni-heidelberg.de/__system__/tap/run/tableMetadatahttps://dc.zah.uni-heidelberg.de/__system__/tap/run/tableMetadatahttp://dc.zah.uni-heidelberg.de/__system__/tap/run/exampleshttps://dc.zah.uni-heidelberg.de/__system__/tap/run/examples
astropy-pyvo-b70558c/pyvo/registry/tests/data/messenger.desise000066400000000000000000000047401510533647000246640ustar00rootroot00000000000000{ "uri": "http://www.ivoa.net/rdf/messenger", "flavour": "RDF Class", "terms": { "Photon": { "label": "Photon", "description": " Carrier particles of the electromagnetic interaction", "preliminary": "", "wider": [], "narrower": [ "Radio", "Millimeter", "Infrared", "Optical", "UV", "X-ray", "Gamma-ray", "EUV" ] }, "Radio": { "label": "Radio", "description": " Photon with a wavelength longer than 10 mm (or \u03bd<30 GHz)", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Millimeter": { "label": "Millimeter", "description": " Photon with a wavelength between 0.1 mm and 10 mm (or 30 GHz<=\u03bd<300 GHz)", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Infrared": { "label": "Infrared", "description": " Photon with a wavelength between 1 \u00b5m and 100 \u00b5m", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Optical": { "label": "Optical", "description": " Photon with a wavelength between 300 nm and 1000 nm", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "UV": { "label": "Ultraviolet", "description": " Photon with a wavelength between 100 nm and 300 nm", "preliminary": "", "wider": [ "Photon" ], "narrower": [ "EUV" ] }, "EUV": { "label": "Extreme UV", "description": " Photon with an energy between 12 eV and 120 eV", "preliminary": "", "wider": [ "UV" ], "narrower": [] }, "X-ray": { "label": "X-Ray", "description": " Photon with an energy between 120 eV and 120 keV", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Gamma-ray": { "label": "Gamma Ray", "description": " Photon with an energy above 120 keV", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Neutrino": { "label": "Neutrino", "description": " This term comprises all generations of neutrinos (electron, \u00b5, \u03c4), and particles as well as antiparticles.", "preliminary": "", "wider": [], "narrower": [] } } }astropy-pyvo-b70558c/pyvo/registry/tests/data/multi-interface.xml000066400000000000000000000311461510533647000253100ustar00rootroot00000000000000 ADQL query translated to local SQL (for debugging)Original ADQL queryQuery successfulSoftware that produced this VOTableBase URI of the serverAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceOriginating VO resourceData centre that has delivered the dataContact optionMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceName of a person or entity that produced a contributing resource The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_level. The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_type. The allowed values for waveband include: Radio, Millimeter, Infrared, Optical, UV, EUV, X-ray, Gamma-ray.Unambiguous reference to the resource conforming to the IVOA standard for identifiers.Resource type (something like vg:authority, vs:catalogservice, etc).A short name or abbreviation given to something, for presentation in space-constrained fields (up to 16 characters).The full name given to the resource.A hash-separated list of content levels specifying the intended audience.An account of the nature of the resource.URL pointing to a human-readable document describing this resource.The creator(s) of the resource in the order given by the resource record author, separated by semicolons.The UTC date and time this resource metadata description was created.The UTC date this resource metadata description was last updated.A statement of usage conditions (license, attribution, embargo, etc).A hash-separated list of natures or genres of the content of the resource.The format of source_value. This, in particular, can be ``bibcode''.A bibliographic reference from which the present resource is derived or extracted.A single numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be ``blurred'' in order to get an appropriate match.A hash-separated list of regions of the electro-magnetic spectrum that the resource's spectral coverage overlaps with.An identifier for the resource or an entity related to the resource in URI form.AAAAIml2bzovL29yZy5nYXZvLmRjL2ZsYXNoaGVyb3MvcS9zc2EAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQRmxhc2gvSGVyb3MgU1NBUAAAABAARgBsAGEAcwBoAC8ASABlAHIAbwBzACAAUwBTAEEAUAAAAAAAAAEVAFMAcABlAGMAdAByAGEAIABmAHIAbwBtACAAdABoAGUAIABGAGwAYQBzAGgAIABhAG4AZAAgAEgAZQByAG8AcwAgAEUAYwBoAGUAbABsAGUAIABzAHAAZQBjAHQAcgBvAGcAcgBhAHAAaABzACAAZABlAHYAZQBsAG8AcABlAGQAIABhAHQACgBMAGEAbgBkAGUAcwBzAHQAZQByAG4AdwBhAHIAdABlACAASABlAGkAZABlAGwAYgBlAHIAZwAgAGEAbgBkACAAbQBvAHUAbgB0AGUAZAAgAGEAdAAgAEwAYQAgAFMAaQBsAGwAYQAgAGEAbgBkACAAdgBhAHIAaQBvAHUAcwAgAG8AdABoAGUAcgAKAG8AYgBzAGUAcgB2AGEAdABvAHIAaQBlAHMALgAgAFQAaABlACAAZABhAHQAYQAgAG0AbwBzAHQAbAB5ACAAYwBvAG4AdABhAGkAbgBzACAAcwBwAGUAYwB0AHIAYQAgAG8AZgAgAE8AQgAgAHMAdABhAHIAcwAuACAASABlAHIAbwBzACAAdwBhAHMACgB0AGgAZQAgAG4AYQBtAGUAIABvAGYAIAB0AGgAZQAgAGkAbgBzAHQAcgB1AG0AZQBuAHQAIABhAGYAdABlAHIAIABGAGwAYQBzAGgAIABnAG8AdAAgAGEAIABzAGUAYwBvAG4AZAAgAGMAaABhAG4AbgBlAGwAIABpAG4AIAAxADkAOQA1AC4AAAA1aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc3NhL2luZm8AAAArAFcAbwBsAGYALAAgAEIALgA7ACAASwBhAHUAZgBlAHIALAAgAEEALgA7ACAATQBhAG4AZABlAGwALAAgAEgALgA7ACAAUwB0AGEAaABsACwAIABPAC4AAAATMjAxMi0wNC0wM1QxMzozMDowMAAAABMyMDIzLTExLTA5VDIwOjUzOjE1AAAAg0lmIHlvdSB1c2UgdGhpcyBkYXRhLCBwbGVhc2UgYWNrbm93bGVkZ2U6ICJUaGlzIHJlc2VhcmNoIG1hZGUgdXNlIG9mCkZsYXNoL0hlcm9zIGRhdGEgc2VydmVkIHRocm91Z2ggdGhlIEdBVk8gZGF0YSBjZW50ZXIiLiBUaGFua3MuAAAAAAAAAAdiaWJjb2RlAAAAEwAxADkAOQA2AEEAJgBBAC4ALgAuADMAMQAyAC4ALgA1ADMAOQBTf8AAAAAAAAdvcHRpY2FsAAACDGh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmxhc2hoZXJvcy9xL3NkbC9kbG1ldGE6OjpweSBWTyBzZXA6OjpodHRwOi8vZGMuemFoLnVuaS1oZWlkZWxiZXJnLmRlL2ZsYXNoaGVyb3MvcS9zZGwvZGxnZXQ6OjpweSBWTyBzZXA6OjpodHRwOi8vZGMuemFoLnVuaS1oZWlkZWxiZXJnLmRlL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmxhc2hoZXJvcy9xL3NzYS90YWJsZU1ldGFkYXRhOjo6cHkgVk8gc2VwOjo6aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc3NhL2NhcGFiaWxpdGllczo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmxhc2hoZXJvcy9xL3NzYS9hdmFpbGFiaWxpdHk6OjpweSBWTyBzZXA6OjpodHRwOi8vZGMuemFoLnVuaS1oZWlkZWxiZXJnLmRlL2ZsYXNoaGVyb3MvcS93ZWIvZm9ybTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmhzc2E/AAABRGl2bzovL2l2b2EubmV0L3N0ZC9kYXRhbGluayNsaW5rcy0xLjE6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvc29kYSNzeW5jLTEuMDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3Zvc2kjdGFibGVzOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3Zvc2kjY2FwYWJpbGl0aWVzOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3Zvc2kjYXZhaWxhYmlsaXR5Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3NzYQAAAMp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwAAAAfnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZAAAAGk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAjZG9pOjEwLjIxOTM4L29VdXFham5XbW9mUDJ3NzUuQWNjOGc=
astropy-pyvo-b70558c/pyvo/registry/tests/data/regtap.xml000066400000000000000000007524541510533647000235160ustar00rootroot00000000000000 ADQL query translated to local SQL (for debugging)Original ADQL queryQuery successfulSoftware that produced this VOTableBase URI of the serverAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceOriginating VO resourceData centre that has delivered the dataContact optionMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceName of a person or entity that produced a contributing resource The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_level. The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_type. The allowed values for waveband include: Radio, Millimeter, Infrared, Optical, UV, EUV, X-ray, Gamma-ray.Unambiguous reference to the resource conforming to the IVOA standard for identifiers.Resource type (something like vg:authority, vs:catalogservice, etc).A short name or abbreviation given to something, for presentation in space-constrained fields (up to 16 characters).The full name given to the resource.A hash-separated list of content levels specifying the intended audience.An account of the nature of the resource.URL pointing to a human-readable document describing this resource.The creator(s) of the resource in the order given by the resource record author, separated by semicolons.The UTC date and time this resource metadata description was created.The UTC date this resource metadata description was last updated.A statement of usage conditions (license, attribution, embargo, etc).A hash-separated list of natures or genres of the content of the resource.The format of source_value. This, in particular, can be ``bibcode''.A bibliographic reference from which the present resource is derived or extracted.A single numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be ``blurred'' in order to get an appropriate match.A hash-separated list of regions of the electro-magnetic spectrum that the resource's spectral coverage overlaps with.An identifier for the resource or an entity related to the resource in URI form.AAAAHml2bzovL2Nkcy52aXppZXIvai9hK2EvNDkyLzkyMwAAABF2czpjYXRhbG9nc2VydmljZQAAAA1KL0ErQS80OTIvOTIzAAAAMQBQAHUAbABzAGEAcgAgAFQAaQBtAGkAbgBnACAAZgBvAHIAIABGAGUAcgBtAGkAIABHAGEAbQBtAGEALQByAGEAeQAgAFMAcABhAGMAZQAgAFQAZQBsAGUAcwBjAG8AcABlAAAACHJlc2VhcmNoAAADhABXAGUAIABkAGUAcwBjAHIAaQBiAGUAIABhACAAYwBvAG0AcAByAGUAaABlAG4AcwBpAHYAZQAgAHAAdQBsAHMAYQByACAAbQBvAG4AaQB0AG8AcgBpAG4AZwAgAGMAYQBtAHAAYQBpAGcAbgAgAGYAbwByACAAdABoAGUAIABMAGEAcgBnAGUAIABBAHIAZQBhACAAVABlAGwAZQBzAGMAbwBwAGUAIAAoAEwAQQBUACkAIABvAG4AIAB0AGgAZQAgAEYAZQByAG0AaQAgAEcAYQBtAG0AYQAtAHIAYQB5ACAAUwBwAGEAYwBlACAAVABlAGwAZQBzAGMAbwBwAGUAIAAoAGYAbwByAG0AZQByAGwAeQAgAEcATABBAFMAVAApAC4AIABUAGgAZQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAYQBuAGQAIABzAHQAdQBkAHkAIABvAGYAIABwAHUAbABzAGEAcgBzACAAaQBuACAAZwBhAG0AbQBhACAAcgBhAHkAcwAgAGcAaQB2AGUAIABpAG4AcwBpAGcAaAB0AHMAIABpAG4AdABvACAAdABoAGUAIABwAG8AcAB1AGwAYQB0AGkAbwBuAHMAIABvAGYAIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgBzACAAYQBuAGQAIABzAHUAcABlAHIAbgBvAHYAYQAgAHIAYQB0AGUAcwAgAGkAbgAgAHQAaABlACAARwBhAGwAYQB4AHkALAAgAGkAbgB0AG8AIABwAGEAcgB0AGkAYwBsAGUAIABhAGMAYwBlAGwAZQByAGEAdABpAG8AbgAgAG0AZQBjAGgAYQBuAGkAcwBtAHMAIABpAG4AIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgAgAG0AYQBnAG4AZQB0AG8AcwBwAGgAZQByAGUAcwAsACAAYQBuAGQAIABpAG4AdABvACAAdABoAGUAIAAiAGUAbgBnAGkAbgBlAHMAIgAgAGQAcgBpAHYAaQBuAGcAIABwAHUAbABzAGEAcgAgAHcAaQBuAGQAIABuAGUAYgB1AGwAYQBlAC4AIABMAEEAVAAnAHMAIAB1AG4AcAByAGUAYwBlAGQAZQBuAHQAZQBkACAAcwBlAG4AcwBpAHQAaQB2AGkAdAB5ACAAYgBlAHQAdwBlAGUAbgAgADIAMABNAGUAVgAgAGEAbgBkACAAMwAwADAARwBlAFYAIAB0AG8AZwBlAHQAaABlAHIAIAB3AGkAdABoACAAaQB0AHMAIAAyAC4ANABzAHIAIABmAGkAZQBsAGQALQBvAGYALQB2AGkAZQB3ACAAbQBhAGsAZQBzACAAZABlAHQAZQBjAHQAaQBvAG4AIABvAGYAIABtAGEAbgB5ACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHUAbABzAGEAcgBzACAAbABpAGsAZQBsAHkALAAgAGoAdQBzAHQAaQBmAHkAaQBuAGcAIAB0AGgAZQAgAG0AbwBuAGkAdABvAHIAaQBuAGcAIABvAGYAIABvAHYAZQByACAAdAB3AG8AIABoAHUAbgBkAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABsAGEAcgBnAGUAIABzAHAAaQBuAC0AZABvAHcAbgAgAHAAbwB3AGUAcgBzAC4AIABUAG8AIABzAGUAYQByAGMAaAAgAGYAbwByACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHUAbABzAGEAdABpAG8AbgBzACAAZgByAG8AbQAgAG0AbwBzAHQAIABvAGYAIAB0AGgAZQBzAGUAIABwAHUAbABzAGEAcgBzACAAcgBlAHEAdQBpAHIAZQBzACAAYQAgAHMAZQB0ACAAbwBmACAAcABoAGEAcwBlAC0AYwBvAG4AbgBlAGMAdABlAGQAIAB0AGkAbQBpAG4AZwAgAHMAbwBsAHUAdABpAG8AbgBzACAAcwBwAGEAbgBuAGkAbgBnACAAYQAgAHkAZQBhAHIAIABvAHIAIABtAG8AcgBlACAAdABvACAAcAByAG8AcABlAHIAbAB5ACAAYQBsAGkAZwBuACAAdABoAGUAIABzAHAAYQByAHMAZQAgAHAAaABvAHQAbwBuACAAYQByAHIAaQB2AGEAbAAgAHQAaQBtAGUAcwAuACAAVwBlACAAZABlAHMAYwByAGkAYgBlACAAdABoAGUAIABjAGgAbwBpAGMAZQAgAG8AZgAgAHAAdQBsAHMAYQByAHMAIABhAG4AZAAgAHQAaABlACAAaQBuAHMAdAByAHUAbQBlAG4AdABzACAAaQBuAHYAbwBsAHYAZQBkACAAaQBuACAAdABoAGUAIABjAGEAbQBwAGEAaQBnAG4ALgAAADdodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0ErQS80OTIvOTIzAAABeQBTAG0AaQB0AGgAIABEAC4AQQAuADsAIABHAHUAaQBsAGwAZQBtAG8AdAAgAEwALgA7ACAAQwBhAG0AaQBsAG8AIABGAC4AOwAgAEMAbwBnAG4AYQByAGQAIABJAC4AOwAgAEQAdQBtAG8AcgBhACAARAAuADsAIABFAHMAcABpAG4AbwB6AGEAIABDAC4AOwAgAEYAcgBlAGkAcgBlACAAUAAuAEMALgBDAC4AOwAgAEcAbwB0AHQAaABlAGwAZgAgAEUALgBWAC4AOwAgAEgAYQByAGQAaQBuAGcAIABBAC4ASwAuADsAIABIAG8AYgBiAHMAIABHAC4AQgAuADsAIABKAG8AaABuAHMAdABvAG4AIABTAC4AOwAgAEsAYQBzAHAAaQAgAFYALgBNAC4AOwAgAEsAcgBhAG0AZQByACAATQAuADsAIABMAGkAdgBpAG4AZwBzAHQAbwBuAGUAIABNAC4AQQAuADsAIABMAHkAbgBlACAAQQAuAEcALgA7ACAATQBhAG4AYwBoAGUAcwB0AGUAcgAgAFIALgBOAC4AOwAgAE0AYQByAHMAaABhAGwAbAAgAEYALgBFAC4AOwAgAE0AYwBMAGEAdQBnAGgAbABpAG4AIABNAC4AQQAuADsAIABOAG8AdQB0AHMAbwBzACAAQQAuADsAIABSAGEAbgBzAG8AbQAgAFMALgBNAC4AOwAgAFIAbwBiAGUAcgB0AHMAIABNAC4AUwAuAEUALgA7ACAAUgBvAG0AYQBuAGkAIABSAC4AVwAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABUAGgAZQB1AHIAZQBhAHUAIABHAC4AOwAgAFQAaABvAG0AcABzAG8AbgAgAEQALgBKAC4AOwAgAFQAaABvAHIAcwBlAHQAdAAgAFMALgBFAC4AOwAgAFcAYQBuAGcAIABOAC4AOwAgAFcAZQBsAHQAZQB2AHIAZQBkAGUAIABQAC4AAAATMjAwOS0wMS0xN1QxNzowMzo1OQAAABMyMDIyLTEwLTEwVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAwADgAQQAmAEEALgAuAC4ANAA5ADIALgAuADkAMgAzAFN/wAAAAAAAD2dhbW1hLXJheSNyYWRpbwAAASRodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS80OTIvOTIzL3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQStBLzQ5Mi85MjM6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0ErQS80OTIvOTIzAAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADNzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAB8Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNDkyLzkyMy90YWJsZTEgKEZFUk1JIHB1bHNhciBjYW5kaWRhdGVzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4zNDkyMDkyMwAAAB1pdm86Ly9jZHMudml6aWVyL2ovYSthLzYxMi9hMQAAABF2czpjYXRhbG9nc2VydmljZQAAAAxKL0ErQS82MTIvQTEAAAAeAEgALgBFAC4AUwAuAFMALgAgAEcAYQBsAGEAYwB0AGkAYwAgAFAAbABhAG4AZQAgAFMAdQByAHYAZQB5AAAACHJlc2VhcmNoAAAIKgBXAGUAIABwAHIAZQBzAGUAbgB0ACAAdABoAGUAIAByAGUAcwB1AGwAdABzACAAbwBmACAAdABoAGUAIABtAG8AcwB0ACAAYwBvAG0AcAByAGUAaABlAG4AcwBpAHYAZQAgAHMAdQByAHYAZQB5ACAAbwBmACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAIABpAG4AIAB2AGUAcgB5ACAAaABpAGcAaAAtAGUAbgBlAHIAZwB5ACAAKABWAEgARQApACAAZwBhAG0AbQBhAC0AcgBhAHkAcwAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIABhACAAcAB1AGIAbABpAGMAIAByAGUAbABlAGEAcwBlACAAbwBmACAARwBhAGwAYQBjAHQAaQBjACAAcwBrAHkAIABtAGEAcABzACwAIABhACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgAFYASABFACAAcwBvAHUAcgBjAGUAcwAsACAAYQBuAGQAIAB0AGgAZQAgAGQAaQBzAGMAbwB2AGUAcgB5ACAAbwBmACAAMQA2ACAAbgBlAHcAIABzAG8AdQByAGMAZQBzACAAbwBmACAAVgBIAEUAIABnAGEAbQBtAGEALQByAGEAeQBzAC4AIABUAGgAZQAgAEgAaQBnAGgAIABFAG4AZQByAGcAeQAgAFMAcABlAGMAdAByAG8AcwBjAG8AcABpAGMAIABTAHkAcwB0AGUAbQAgACgASAAuAEUALgBTAC4AUwAuACkAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAIABzAHUAcgB2AGUAeQAgACgASABHAFAAUwApACAAdwBhAHMAIABhACAAZABlAGMAYQBkAGUALQBsAG8AbgBnACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuACAAcAByAG8AZwByAGEAbQAgAGMAYQByAHIAaQBlAGQAIABvAHUAdAAgAGIAeQAgAHQAaABlACAASAAuAEUALgBTAC4AUwAuACAASQAgAGEAcgByAGEAeQAgAG8AZgAgAEMAaABlAHIAZQBuAGsAbwB2ACAAdABlAGwAZQBzAGMAbwBwAGUAcwAgAGkAbgAgAE4AYQBtAGkAYgBpAGEAIABmAHIAbwBtACAAMgAwADAANAAgAHQAbwAgADIAMAAxADMALgAgAFQAaABlACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABhAG0AbwB1AG4AdAAgAHQAbwAgAG4AZQBhAHIAbAB5ACAAMgA3ADAAMAAgAGgAIABvAGYAIABxAHUAYQBsAGkAdAB5AC0AcwBlAGwAZQBjAHQAZQBkACAAZABhAHQAYQAsACAAYwBvAHYAZQByAGkAbgBnACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAIABhAHQAIABsAG8AbgBnAGkAdAB1AGQAZQBzACAAZgByAG8AbQAgAGwAPQAyADUAMAB7AGQAZQBnAH0AIAB0AG8AIAA2ADUAewBkAGUAZwB9ACAAYQBuAGQAIABsAGEAdABpAHQAdQBkAGUAcwAgAHwAYgB8ADwAPQAzAC4AIABJAG4AIABhAGQAZABpAHQAaQBvAG4AIAB0AG8AIAB0AGgAZQAgAHUAbgBwAHIAZQBjAGUAZABlAG4AdABlAGQAIABzAHAAYQB0AGkAYQBsACAAYwBvAHYAZQByAGEAZwBlACwAIAB0AGgAZQAgAEgARwBQAFMAIABhAGwAcwBvACAAZgBlAGEAdAB1AHIAZQBzACAAYQAgAHIAZQBsAGEAdABpAHYAZQBsAHkAIABoAGkAZwBoACAAYQBuAGcAdQBsAGEAcgAgAHIAZQBzAG8AbAB1AHQAaQBvAG4AIAAoADAALgAwADgAewBkAGUAZwB9AH4ANQAtAGEAcgBjAG0AaQBuACAAbQBlAGEAbgAgAHAAbwBpAG4AdAAgAHMAcAByAGUAYQBkACAAZgB1AG4AYwB0AGkAbwBuACAANgA4ACUAIABjAG8AbgB0AGEAaQBuAG0AZQBuAHQAIAByAGEAZABpAHUAcwApACwAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIAAoADEALgA1ACUAIABDAHIAYQBiACAAZgBsAHUAeAAgAGYAbwByACAAcABvAGkAbgB0AC0AbABpAGsAZQAgAHMAbwB1AHIAYwBlAHMAKQAsACAAYQBuAGQAIABlAG4AZQByAGcAeQAgAHIAYQBuAGcAZQAgACgAMAAuADIALQAxADAAMABUAGUAVgApAC4AIABXAGUAIABjAG8AbgBzAHQAcgB1AGMAdABlAGQAIABhACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgAFYASABFACAAZwBhAG0AbQBhAC0AcgBhAHkAIABzAG8AdQByAGMAZQBzACAAZgByAG8AbQAgAHQAaABlACAASABHAFAAUwAgAGQAYQB0AGEAIABzAGUAdAAgAHcAaQB0AGgAIABhACAAcwB5AHMAdABlAG0AYQB0AGkAYwAgAHAAcgBvAGMAZQBkAHUAcgBlACAAZgBvAHIAIABiAG8AdABoACAAcwBvAHUAcgBjAGUAIABkAGUAdABlAGMAdABpAG8AbgAgAGEAbgBkACAAYwBoAGEAcgBhAGMAdABlAHIAaQB6AGEAdABpAG8AbgAgAG8AZgAgAG0AbwByAHAAaABvAGwAbwBnAHkAIABhAG4AZAAgAHMAcABlAGMAdAByAHUAbQAuACAAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABpAHMAIABsAGkAawBlAGwAaQBoAG8AbwBkAC0AIABiAGEAcwBlAGQAIABtAGUAdABoAG8AZAAgAGkAbgAgAGQAZQB0AGEAaQBsACwAIABpAG4AYwBsAHUAZABpAG4AZwAgAHQAaABlACAAaQBuAHQAcgBvAGQAdQBjAHQAaQBvAG4AIABvAGYAIABhACAAbQBvAGQAZQBsACAAYwBvAG0AcABvAG4AZQBuAHQAIAB0AG8AIABhAGMAYwBvAHUAbgB0ACAAZgBvAHIAIAB1AG4AcgBlAHMAbwBsAHYAZQBkACwAIABsAGEAcgBnAGUALQBzAGMAYQBsAGUAIABlAG0AaQBzAHMAaQBvAG4AIABhAGwAbwBuAGcAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAuACAASQBuACAAdABvAHQAYQBsACwAIAB0AGgAZQAgAHIAZQBzAHUAbAB0AGkAbgBnACAASABHAFAAUwAgAGMAYQB0AGEAbABvAGcAIABjAG8AbgB0AGEAaQBuAHMAIAA3ADgAIABWAEgARQAgAHMAbwB1AHIAYwBlAHMALAAgAG8AZgAgAHcAaABpAGMAaAAgADEANAAgAGEAcgBlACAAbgBvAHQAIAByAGUAYQBuAGEAbAB5AHoAZQBkACAAaABlAHIAZQAsACAAZgBvAHIAIABlAHgAYQBtAHAAbABlACwAIABkAHUAZQAgAHQAbwAgAHQAaABlAGkAcgAgAGMAbwBtAHAAbABlAHgAIABtAG8AcgBwAGgAbwBsAG8AZwB5ACwAIABuAGEAbQBlAGwAeQAgAHMAaABlAGwAbAAtAGwAaQBrAGUAIABzAG8AdQByAGMAZQBzACAAYQBuAGQAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAGMAZQBuAHQAZQByACAAcgBlAGcAaQBvAG4ALgAgAFcAaABlAHIAZQAgAHAAbwBzAHMAaQBiAGwAZQAsACAAdwBlACAAcAByAG8AdgBpAGQAZQAgAGEAIABmAGkAcgBtACAAaQBkAGUAbgB0AGkAZgBpAGMAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABWAEgARQAgAHMAbwB1AHIAYwBlACAAbwByACAAcABsAGEAdQBzAGkAYgBsAGUAIABhAHMAcwBvAGMAaQBhAHQAaQBvAG4AcwAgAHcAaQB0AGgAIABzAG8AdQByAGMAZQBzACAAaQBuACAAbwB0AGgAZQByACAAYQBzAHQAcgBvAG4AbwBtAGkAYwBhAGwAIABjAGEAdABhAGwAbwBnAHMALgAgAFcAZQAgAGEAbABzAG8AIABzAHQAdQBkAGkAZQBkACAAdABoAGUAIABjAGgAYQByAGEAYwB0AGUAcgBpAHMAdABpAGMAcwAgAG8AZgAgAHQAaABlACAAVgBIAEUAIABzAG8AdQByAGMAZQBzACAAdwBpAHQAaAAgAHMAbwB1AHIAYwBlACAAcABhAHIAYQBtAGUAdABlAHIAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgBzAC4AIAAxADYAIABuAGUAdwAgAHMAbwB1AHIAYwBlAHMAIAB3AGUAcgBlACAAcAByAGUAdgBpAG8AdQBzAGwAeQAgAHUAbgBrAG4AbwB3AG4AIABvAHIAIAB1AG4AcAB1AGIAbABpAHMAaABlAGQALAAgAGEAbgBkACAAdwBlACAAaQBuAGQAaQB2AGkAZAB1AGEAbABsAHkAIABkAGkAcwBjAHUAcwBzACAAdABoAGUAaQByACAAaQBkAGUAbgB0AGkAZgBpAGMAYQB0AGkAbwBuAHMAIABvAHIAIABwAG8AcwBzAGkAYgBsAGUAIABhAHMAcwBvAGMAaQBhAHQAaQBvAG4AcwAuACAAVwBlACAAZgBpAHIAbQBsAHkAIABpAGQAZQBuAHQAaQBmAGkAZQBkACAAMwAxACAAcwBvAHUAcgBjAGUAcwAgAGEAcwAgAHAAdQBsAHMAYQByACAAdwBpAG4AZAAgAG4AZQBiAHUAbABhAGUAIAAoAFAAVwBOAGUAKQAsACAAcwB1AHAAZQByAG4AbwB2AGEAIAByAGUAbQBuAGEAbgB0AHMAIAAoAFMATgBSAHMAKQAsACAAYwBvAG0AcABvAHMAaQB0AGUAIABTAE4AUgBzACwAIABvAHIAIABnAGEAbQBtAGEALQByAGEAeQAgAGIAaQBuAGEAcgBpAGUAcwAuACAAQQBtAG8AbgBnACAAdABoAGUAIAA0ADcAIABzAG8AdQByAGMAZQBzACAAbgBvAHQAIAB5AGUAdAAgAGkAZABlAG4AdABpAGYAaQBlAGQALAAgAG0AbwBzAHQAIABvAGYAIAB0AGgAZQBtACAAKAAzADYAKQAgAGgAYQB2AGUAIABwAG8AcwBzAGkAYgBsAGUAIABhAHMAcwBvAGMAaQBhAHQAaQBvAG4AcwAgAHcAaQB0AGgAIABjAGEAdABhAGwAbwBnAGUAZAAgAG8AYgBqAGUAYwB0AHMALAAgAG4AbwB0AGEAYgBsAHkAIABQAFcATgBlACAAYQBuAGQAIABlAG4AZQByAGcAZQB0AGkAYwAgAHAAdQBsAHMAYQByAHMAIAB0AGgAYQB0ACAAYwBvAHUAbABkACAAcABvAHcAZQByACAAVgBIAEUAIABQAFcATgBlAC4AAAA2aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BK0EvNjEyL0ExAAAMrABIAC4ARQAuAFMALgBTAC4AIABDAG8AbABsAGEAYgBvAHIAYQB0AGkAbwBuADsAIABBAGIAZABhAGwAbABhACAASAAuADsAIABBAGIAcgBhAG0AbwB3AHMAawBpACAAQQAuADsAIABBAGgAYQByAG8AbgBpAGEAbgAgAEYALgA7ACAAQQBpAHQAIABCAGUAbgBrAGgAYQBsAGkAIABGAC4AOwAgAEEAbgBnAHUAZQBuAGUAcgAgAEUALgBPAC4AOwAgAEEAcgBhAGsAYQB3AGEAIABNAC4AOwAgAEEAcgByAGkAZQB0AGEAIABNAC4AOwAgAEEAdQBiAGUAcgB0ACAAUAAuADsAIABCAGEAYwBrAGUAcwAgAE0ALgA7ACAAQgBhAGwAegBlAHIAIABBAC4AOwAgAEIAYQByAG4AYQByAGQAIABNAC4AOwAgAEIAZQBjAGgAZQByAGkAbgBpACAAWQAuADsAIABCAGUAYwBrAGUAcgAgAFQAagB1AHMAIABKAC4AOwAgAEIAZQByAGcAZQAgAEQALgA7ACAAQgBlAHIAbgBoAGEAcgBkACAAUwAuADsAIABCAGUAcgBuAGwAbwBlAGgAcgAgAEsALgA7ACAAQgBsAGEAYwBrAHcAZQBsAGwAIABSAC4AOwAgAEIAbwBlAHQAdABjAGgAZQByACAATQAuADsAIABCAG8AaQBzAHMAbwBuACAAQwAuADsAIABCAG8AbABtAG8AbgB0ACAASgAuADsAIABCAG8AbgBuAGUAZgBvAHkAIABTAC4AOwAgAEIAbwByAGQAYQBzACAAUAAuADsAIABCAHIAZQBnAGUAbwBuACAASgAuADsAIABCAHIAdQBuACAARgAuADsAIABCAHIAdQBuACAAUAAuADsAIABCAHIAeQBhAG4AIABNAC4AOwAgAEIAdQBlAGMAaABlAGwAZQAgAE0ALgA7ACAAQgB1AGwAaQBrACAAVAAuADsAIABDAGEAcABhAHMAcwBvACAATQAuADsAIABDAGEAcgByAGkAZwBhAG4AIABTAC4AOwAgAEMAYQByAG8AZgBmACAAUwAuADsAIABDAGEAcgBvAHMAaQAgAEEALgA7ACAAQwBhAHMAYQBuAG8AdgBhACAAUwAuADsAIABDAGUAcgByAHUAdABpACAATQAuADsAIABDAGgAYQBrAHIAYQBiAG8AcgB0AHkAIABOAC4AOwAgAEMAaABhAHYAZQBzACAAUgAuAEMALgBHAC4AOwAgAEMAaABlAG4AIABBAC4AOwAgAEMAaABlAHYAYQBsAGkAZQByACAASgAuADsAIABDAG8AbABhAGYAcgBhAG4AYwBlAHMAYwBvACAAUwAuADsAIABDAG8AbgBkAG8AbgAgAEIALgA7ACAAQwBvAG4AcgBhAGQAIABKAC4AOwAgAEQAYQB2AGkAZABzACAASQAuAEQALgA7ACAARABlAGMAbwBjAGsAIABKAC4AOwAgAEQAZQBpAGwAIABDAC4AOwAgAEQAZQB2AGkAbgAgAEoALgA7ACAAZABlAFcAaQBsAHQAIABQAC4AOwAgAEQAaQByAHMAbwBuACAATAAuADsAIABEAGoAYQBuAG4AYQB0AGkALQBBAHQAYQBpACAAQQAuADsAIABEAG8AbQBhAGkAbgBrAG8AIABXAC4AOwAgAEQAbwBuAGEAdABoACAAQQAuADsAIABEAHIAdQByAHkAIABMAC4ATwAnAEMALgA7ACAARAB1AHQAcwBvAG4AIABLAC4AOwAgAEQAeQBrAHMAIABKAC4AOwAgAEUAZAB3AGEAcgBkAHMAIABUAC4AOwAgAEUAZwBiAGUAcgB0AHMAIABLAC4AOwAgAEUAZwBlAHIAIABQAC4AOwAgAEUAbQBlAHIAeQAgAEcALgA7ACAARQByAG4AZQBuAHcAZQBpAG4AIABKAC4ALQBQAC4AOwAgAEUAcwBjAGgAYgBhAGMAaAAgAFMALgA7ACAARgBhAHIAbgBpAGUAcgAgAEMALgA7ACAARgBlAGcAYQBuACAAUwAuADsAIABGAGUAcgBuAGEAbgBkAGUAcwAgAE0ALgBWAC4AOwAgAEYAaQBhAHMAcwBvAG4AIABBAC4AOwAgAEYAbwBuAHQAYQBpAG4AZQAgAEcALgA7ACAARgBvAGUAcgBzAHQAZQByACAAQQAuADsAIABGAHUAbgBrACAAUwAuADsAIABGAHUAZQBzAHMAbABpAG4AZwAgAE0ALgA7ACAARwBhAGIAaQBjAGkAIABTAC4AOwAgAEcAYQBsAGwAYQBuAHQAIABZAC4AQQAuADsAIABHAGEAcgByAGkAZwBvAHUAeAAgAFQALgA7ACAARwBhAHMAdAAgAEgALgA7ACAARwBhAHQAZQAgAEYALgA7ACAARwBpAGEAdgBpAHQAdABvACAARwAuADsAIABHAGkAZQBiAGUAbABzACAAQgAuADsAIABHAGwAYQB3AGkAbwBuACAARAAuADsAIABHAGwAaQBjAGUAbgBzAHQAZQBpAG4AIABKAC4ARgAuADsAIABHAG8AdAB0AHMAYwBoAGEAbABsACAARAAuADsAIABHAHIAbwBuAGQAaQBuACAATQAuAC0ASAAuADsAIABIAGEAaABuACAASgAuADsAIABIAGEAdQBwAHQAIABNAC4AOwAgAEgAYQB3AGsAZQBzACAASgAuADsAIABIAGUAaQBuAHoAZQBsAG0AYQBuAG4AIABHAC4AOwAgAEgAZQBuAHIAaQAgAEcALgA7ACAASABlAHIAbQBhAG4AbgAgAEcALgA7ACAASABpAG4AdABvAG4AIABKAC4AQQAuADsAIABIAG8AZgBtAGEAbgBuACAAVwAuADsAIABIAG8AaQBzAGMAaABlAG4AIABDAC4AOwAgAEgAbwBsAGMAaAAgAFQALgBMAC4AOwAgAEgAbwBsAGwAZQByACAATQAuADsAIABIAG8AcgBuAHMAIABEAC4AOwAgAEkAdgBhAHMAYwBlAG4AawBvACAAQQAuADsAIABJAHcAYQBzAGEAawBpACAASAAuADsAIABKAGEAYwBoAG8AbABrAG8AdwBzAGsAYQAgAEEALgA7ACAASgBhAG0AcgBvAHoAeQAgAE0ALgA7ACAASgBhAG4AawBvAHcAcwBrAHkAIABEAC4AOwAgAEoAYQBuAGsAbwB3AHMAawB5ACAARgAuADsAIABKAGkAbgBnAG8AIABNAC4AOwAgAEoAbwB1AHYAaQBuACAATAAuADsAIABKAHUAbgBnAC0AUgBpAGMAaABhAHIAZAB0ACAASQAuADsAIABLAGEAcwB0AGUAbgBkAGkAZQBjAGsAIABNAC4AQQAuADsAIABLAGEAdABhAHIAegB5AE4AcwBrAGkAIABLAC4AOwAgAEsAYQB0AHMAdQByAGEAZwBhAHcAYQAgAE0ALgA7ACAASwBhAHQAegAgAFUALgA7ACAASwBlAHIAcwB6AGIAZQByAGcAIABEAC4AOwAgAEsAaABhAG4AZwB1AGwAeQBhAG4AIABEAC4AOwAgAEsAaABlAGwAaQBmAGkAIABCAC4AOwAgAEsAaQBuAGcAIABKAC4AOwAgAEsAbABlAHAAcwBlAHIAIABTAC4AOwAgAEsAbABvAGMAaABrAG8AdgAgAEQALgA7ACAASwBsAHUAegBuAGkAYQBrACAAVwAuADsAIABLAG8AbQBpAG4AIABOAHUALgA7ACAASwBvAHMAYQBjAGsAIABLAC4AOwAgAEsAcgBhAGsAYQB1ACAAUwAuADsAIABLAHIAYQB1AHMAIABNAC4AOwAgAEsAcgB1AGUAZwBlAHIAIABQAC4AUAAuADsAIABMAGEAZgBmAG8AbgAgAEgALgA7ACAATABhAG0AYQBuAG4AYQAgAEcALgA7ACAATABhAHUAIABKAC4AOwAgAEwAZQBlAHMAIABKAC4ALQBQAC4AOwAgAEwAZQBmAGEAdQBjAGgAZQB1AHIAIABKAC4AOwAgAEwAZQBtAGkAZQByAGUAIABBAC4AOwAgAEwAZQBtAG8AaQBuAGUALQBHAG8AdQBtAGEAcgBkACAATQAuADsAIABMAGUAbgBhAGkAbgAgAEoALgAtAFAALgA7ACAATABlAHMAZQByACAARQAuADsAIABMAG8AaABzAGUAIABUAC4AOwAgAEwAbwByAGUAbgB0AHoAIABNAC4AOwAgAEwAaQB1ACAAUgAuADsAIABMAG8AcABlAHoALQBDAG8AdABvACAAUgAuADsAIABMAHkAcABvAHYAYQAgAEkALgA7ACAATQBhAHIAYQBuAGQAbwBuACAAVgAuADsAIABNAGEAbAB5AHMAaABlAHYAIABEAC4AOwAgAE0AYQByAGMAbwB3AGkAdABoACAAQQAuADsAIABNAGEAcgBpAGEAdQBkACAAQwAuADsAIABNAGEAcgB4ACAAUgAuADsAIABNAGEAdQByAGkAbgAgAEcALgA7ACAATQBhAHgAdABlAGQAIABOAC4AOwAgAE0AYQB5AGUAcgAgAE0ALgA7ACAATQBlAGkAbgB0AGoAZQBzACAAUAAuAEoALgA7ACAATQBlAHkAZQByACAATQAuADsAIABNAGkAdABjAGgAZQBsAGwAIABBAC4ATQAuAFcALgA7ACAATQBvAGQAZQByAHMAawBpACAAUgAuADsAIABNAG8AaABhAG0AZQBkACAATQAuADsAIABNAG8AaAByAG0AYQBuAG4AIABMAC4AOwAgAE0AbwByAGEAIABLAC4AOwAgAE0AbwB1AGwAaQBuACAARQAuADsAIABNAHUAcgBhAGMAaAAgAFQALgA7ACAATgBhAGsAYQBzAGgAaQBtAGEAIABTAC4AOwAgAGQAZQAgAE4AYQB1AHIAbwBpAHMAIABNAC4AOwAgAE4AZABpAHkAYQB2AGEAbABhACAASAAuADsAIABOAGkAZQBkAGUAcgB3AGEAbgBnAGUAcgAgAEYALgA7ACAATgBpAGUAbQBpAGUAYwAgAEoALgA7ACAATwBhAGsAZQBzACAATAAuADsAIABPACcAQgByAGkAZQBuACAAUAAuADsAIABPAGQAYQBrAGEAIABIAC4AOwAgAE8AaABtACAAUwAuADsAIABPAHMAdAByAG8AdwBzAGsAaQAgAE0ALgA7ACAATwB5AGEAIABJAC4AOwAgAFAAYQBkAG8AdgBhAG4AaQAgAE0ALgA7ACAAUABhAG4AdABlAHIAIABNAC4AOwAgAFAAYQByAHMAbwBuAHMAIABSAC4ARAAuADsAIABQAGEAegAgAEEAcgByAGkAYgBhAHMAIABNAC4AOwAgAFAAZQBrAGUAdQByACAATgAuAFcALgA7ACAAUABlAGwAbABlAHQAaQBlAHIAIABHAC4AOwAgAFAAZQByAGUAbgBuAGUAcwAgAEMALgA7ACAAUABlAHQAcgB1AGMAYwBpACAAUAAuAC0ATwAuADsAIABQAGUAeQBhAHUAZAAgAEIALgA7ACAAUABpAGUAbAAgAFEALgA7ACAAUABpAHQAYQAgAFMALgA7ACAAUABvAGkAcgBlAGEAdQAgAFYALgA7ACAAUABvAG8AbgAgAEgALgA7ACAAUAByAG8AawBoAG8AcgBvAHYAIABEAC4AOwAgAFAAcgBvAGsAbwBwAGgAIABIAC4AOwAgAFAAdQBlAGgAbABoAG8AZgBlAHIAIABHAC4AOwAgAFAAdQBuAGMAaAAgAE0ALgA7ACAAUQB1AGkAcgByAGUAbgBiAGEAYwBoACAAQQAuADsAIABSAGEAYQBiACAAUwAuADsAIABSAGEAdQB0AGgAIABSAC4AOwAgAFIAZQBpAG0AZQByACAAQQAuADsAIABSAGUAaQBtAGUAcgAgAE8ALgA7ACAAUgBlAG4AYQB1AGQAIABNAC4AOwAgAGQAZQAgAGwAbwBzACAAUgBlAHkAZQBzACAAUgAuADsAIABSAGkAZQBnAGUAcgAgAEYALgA7ACAAUgBpAG4AYwBoAGkAdQBzAG8AIABMAC4AOwAgAFIAbwBtAG8AbABpACAAQwAuADsAIABSAG8AdwBlAGwAbAAgAEcALgA7ACAAUgB1AGQAYQBrACAAQgAuADsAIABSAHUAbAB0AGUAbgAgAEMALgBCAC4AOwAgAFMAYQBmAGkALQBIAGEAcgBiACAAUwAuADsAIABTAGEAaABhAGsAaQBhAG4AIABWAC4AOwAgAFMAYQBpAHQAbwAgAFMALgA7ACAAUwBhAG4AYwBoAGUAegAgAEQALgBBAC4AOwAgAFMAYQBuAHQAYQBuAGcAZQBsAG8AIABBAC4AOwAgAFMAYQBzAGEAawBpACAATQAuADsAIABTAGMAaABhAG4AZAByAGkAIABNAC4AOwAgAFMAYwBoAGwAaQBjAGsAZQBpAHMAZQByACAAUgAuADsAIABTAGMAaAB1AGUAcwBzAGwAZQByACAARgAuADsAIABTAGMAaAB1AGwAegAgAEEALgA7ACAAUwBjAGgAdwBhAG4AawBlACAAVQAuADsAIABTAGMAaAB3AGUAbQBtAGUAcgAgAFMALgA7ACAAUwBlAGcAbABhAHIALQBBAHIAcgBvAHkAbwAgAE0ALgA7ACAAUwBlAHQAdABpAG0AbwAgAE0ALgA7ACAAUwBlAHkAZgBmAGUAcgB0ACAAQQAuAFMALgA7ACAAUwBoAGEAZgBpACAATgAuADsAIABTAGgAaQBsAG8AbgAgAEkALgA7ACAAUwBoAGkAbgBpAG4AZwBhAHkAYQBtAHcAZQAgAEsALgA7ACAAUwBpAG0AbwBuAGkAIABSAC4AOwAgAFMAbwBsACAASAAuADsAIABTAHAAYQBuAGkAZQByACAARgAuADsAIABTAHAAaQByAC0ASgBhAGMAbwBiACAATQAuADsAIABTAHQAYQB3AGEAcgB6ACAATAAuADsAIABTAHQAZQBlAG4AawBhAG0AcAAgAFIALgA7ACAAUwB0AGUAZwBtAGEAbgBuACAAQwAuADsAIABTAHQAZQBwAHAAYQAgAEMALgA7ACAAUwB1AHMAaABjAGgAIABJAC4AOwAgAFQAYQBrAGEAaABhAHMAaABpACAAVAAuADsAIABUAGEAdgBlAHIAbgBlAHQAIABKAC4ALQBQAC4AOwAgAFQAYQB2AGUAcgBuAGkAZQByACAAVAAuADsAIABUAGEAeQBsAG8AcgAgAEEALgBNAC4AOwAgAFQAZQByAHIAaQBlAHIAIABSAC4AOwAgAFQAaQBiAGEAbABkAG8AIABMAC4AOwAgAFQAaQB6AGkAYQBuAGkAIABEAC4AOwAgAFQAbAB1AGMAegB5AGsAbwBuAHQAIABNAC4AOwAgAFQAcgBpAGMAaABhAHIAZAAgAEMALgA7ACAAVABzAGkAcgBvAHUAIABNAC4AOwAgAFQAcwB1AGoAaQAgAE4ALgA7ACAAVAB1AGYAZgBzACAAUgAuADsAIABVAGMAaABpAHkAYQBtAGEAIABZAC4AOwAgAHYAYQBuACAAZABlAHIAIABXAGEAbAB0ACAARAAuAEoALgA7ACAAdgBhAG4AIABFAGwAZABpAGsAIABDAC4AOwAgAHYAYQBuACAAUgBlAG4AcwBiAHUAcgBnACAAQwAuADsAIAB2AGEAbgAgAFMAbwBlAGwAZQBuACAAQgAuADsAIABWAGEAcwBpAGwAZQBpAGEAZABpAHMAIABHAC4AOwAgAFYAZQBoACAASgAuADsAIABWAGUAbgB0AGUAcgAgAEMALgA7ACAAVgBpAGEAbgBhACAAQQAuADsAIABWAGkAbgBjAGUAbgB0ACAAUAAuADsAIABWAGkAbgBrACAASgAuADsAIABWAG8AaQBzAGkAbgAgAEYALgA7ACAAVgBvAGUAbABrACAASAAuAEoALgA7ACAAVgB1AGkAbABsAGEAdQBtAGUAIABUAC4AOwAgAFcAYQBkAGkAYQBzAGkAbgBnAGgAIABaAC4AOwAgAFcAYQBnAG4AZQByACAAUwAuAEoALgA7ACAAVwBhAGcAbgBlAHIAIABQAC4AOwAgAFcAYQBnAG4AZQByACAAUgAuAE0ALgA7ACAAVwBoAGkAdABlACAAUgAuADsAIABXAGkAZQByAHoAYwBoAG8AbABzAGsAYQAgAEEALgA7ACAAVwBpAGwAbABtAGEAbgBuACAAUAAuADsAIABXAG8AZQByAG4AbABlAGkAbgAgAEEALgA7ACAAVwBvAHUAdABlAHIAcwAgAEQALgA7ACAAWQBhAG4AZwAgAFIALgA7ACAAWgBhAGIAbwByAG8AdgAgAEQALgA7ACAAWgBhAGMAaABhAHIAaQBhAHMAIABNAC4AOwAgAFoAYQBuAGkAbgAgAFIALgA7ACAAWgBkAHoAaQBhAHIAcwBrAGkAIABBAC4AQQAuADsAIABaAGUAYwBoACAAQQAuADsAIABaAGUAZgBpACAARgAuADsAIABaAGkAZQBnAGwAZQByACAAQQAuADsAIABaAG8AcgBuACAASgAuADsAIABaAHkAdwB1AGMAawBhACAATgAuAAAAEzIwMTgtMDQtMDlUMDg6MzI6MzgAAAATMjAyMi0xMi0yMFQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQA4AEEAJgBBAC4ALgAuADYAMQAyAEEALgAuAC4AMQBIf8AAAAAAAAlnYW1tYS1yYXkAAATnaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL2xpc3Q/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL3NvdXJjZXM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL3NucmNhdD86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvbHNjb21wPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9jb21wb24/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0ErQS82MTIvQTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0ErQS82MTIvQTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvbGlzdD86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvc291cmNlcz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvc25yY2F0Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9sc2NvbXA/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL2NvbXBvbj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQStBLzYxMi9BMTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQStBLzYxMi9BMQAAAjdpdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAGjdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAAEFc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAEyUNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQStBLzYxMi9BMS9saXN0IChMaXN0IG9mIGZpdHMgaW1hZ2VzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQStBLzYxMi9BMS9zb3VyY2VzIChIR1BTIHNvdXJjZSBjYXRhbG9nIChIR1BTX1NPVVJDRVMpKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQStBLzYxMi9BMS9zbnJjYXQgKEJ1bmRsZWQgdmVyc2lvbiBvZiBTTlJjYXQgdXNlZCBmb3IgYXNzb2NpYXRpb25zIChTTlJDQVQpKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQStBLzYxMi9BMS9sc2NvbXAgKEhHUFMgbGFyZ2Utc2NhbGUgZW1pc3Npb24gbW9kZWwgcGFyYW1ldGVycyAoSEdQU1xfTEFSR0VcX1NDQUxFX0NPTVBPTkVOVCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjEyL0ExL2NvbXBvbiAoSEdQUyBjb21wb25lbnQgbGlzdCAoSEdQU1xfR0FVU1NfQ09NUE9ORU5UUykpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjEyL0ExL2xpc3QgKExpc3Qgb2YgZml0cyBpbWFnZXMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjEyL0ExL3NvdXJjZXMgKEhHUFMgc291cmNlIGNhdGFsb2cgKEhHUFNfU09VUkNFUykpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjEyL0ExL3NucmNhdCAoQnVuZGxlZCB2ZXJzaW9uIG9mIFNOUmNhdCB1c2VkIGZvciBhc3NvY2lhdGlvbnMgKFNOUkNBVCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjEyL0ExL2xzY29tcCAoSEdQUyBsYXJnZS1zY2FsZSBlbWlzc2lvbiBtb2RlbCBwYXJhbWV0ZXJzIChIR1BTXF9MQVJHRVxfU0NBTEVfQ09NUE9ORU5UKSk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0ErQS82MTIvQTEvY29tcG9uIChIR1BTIGNvbXBvbmVudCBsaXN0IChIR1BTXF9HQVVTU19DT01QT05FTlRTKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMzYxMjAwMDEAAAAfaXZvOi8vY2RzLnZpemllci9qL2ErYS82MTgvYTE4NgAAABF2czpjYXRhbG9nc2VydmljZQAAAA5KL0ErQS82MTgvQTE4NgAAAB0ARAB5AG4AYQBtAGkAYwAgAHMAcABlAGMAdAByAGEAIABvAGYAIAAxADAAIABwAHUAbABzAGEAcgBzAAAACHJlc2VhcmNoAAAHBQBQAHUAbABzAGEAcgBzACAAcwBjAGkAbgB0AGkAbABsAGEAdABlAC4AIABEAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAgAHMAaABvAHcAIABiAHIAaQBnAGgAdABuAGUAcwBzACAAdgBhAHIAaQBhAHQAaQBvAG4AIABvAGYAIABwAHUAbABzAGEAcgBzACAAaQBuACAAdABoAGUAIAB0AGkAbQBlACAAYQBuAGQAIABmAHIAZQBxAHUAZQBuAGMAeQAgAGQAbwBtAGEAaQBuAC4AIABTAGUAYwBvAG4AZABhAHIAeQAgAHMAcABlAGMAdAByAGEAIABkAGUAbQBvAG4AcwB0AHIAYQB0AGUAIAB0AGgAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAbwBmACAAZgBsAHUAYwB0AHUAYQB0AGkAbwBuACAAcABvAHcAZQByACAAaQBuACAAdABoAGUAIABkAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAuACAARAB5AG4AYQBtAGkAYwAgAHMAcABlAGMAdAByAGEAIABzAHQAcgBvAG4AZwBsAHkAIABkAGUAcABlAG4AZAAgAG8AbgAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBhAGwAIABmAHIAZQBxAHUAZQBuAGMAaQBlAHMALAAgAGIAdQB0ACAAdwBlAHIAZQAgAG8AZgB0AGUAbgAgAG8AYgBzAGUAcgB2AGUAZAAgAGEAdAAgAGYAcgBlAHEAdQBlAG4AYwBpAGUAcwAgAGwAbwB3AGUAcgAgAHQAaABhAG4AIAAxAC4ANQBHAEgAegAuACAAUwBjAGkAbgB0AGkAbABsAGEAdABpAG8AbgAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAYQB0ACAAaABpAGcAaABlAHIAIABmAHIAZQBxAHUAZQBuAGMAaQBlAHMAIABoAGUAbABwACAAdABvACAAYwBvAG4AcwB0AHIAYQBpAG4AIAB0AGgAZQAgAHQAdQByAGIAdQBsAGUAbgBjAGUAIABmAGUAYQB0AHUAcgBlACAAbwBmACAAdABoAGUAIABpAG4AdABlAHIAcwB0AGUAbABsAGEAcgAgAG0AZQBkAGkAdQBtACAAbwB2AGUAcgAgAGEAIAB3AGkAZABlACAAZgByAGUAcQB1AGUAbgBjAHkAIAByAGEAbgBnAGUAIABhAG4AZAAgAGMAYQBuACAAZABlAHQAZQBjAHQAIAB0AGgAZQAgAHMAYwBpAG4AdABpAGwAbABhAHQAaQBvAG4AcwAgAG8AZgAgAG0AbwByAGUAIABkAGkAcwB0AGEAbgB0ACAAcAB1AGwAcwBhAHIAcwAuACAAVABlAG4AIABwAHUAbABzAGEAcgBzACAAdwBlAHIAZQAgAG8AYgBzAGUAcgB2AGUAZAAgAGEAdAAgADIAMgA1ADAATQBIAHoAIAAoAFMALQBiAGEAbgBkACkAIAB3AGkAdABoACAAdABoAGUAIABKAGkAYQBtAHUAcwBpACAANgA2AG0AIAB0AGUAbABlAHMAYwBvAHAAZQAgAHQAbwAgAHMAdAB1AGQAeQAgAHQAaABlAGkAcgAgAHMAYwBpAG4AdABpAGwAbABhAHQAaQBvAG4AcwAuACAAVABoAGUAaQByACAAZAB5AG4AYQBtAGkAYwAgAHMAcABlAGMAdAByAGEAIAB3AGUAcgBlACAAZgBpAHIAcwB0ACAAbwBiAHQAYQBpAG4AZQBkACwAIABmAHIAbwBtACAAdwBoAGkAYwBoACAAdABoAGUAIABkAGUAYwBvAHIAcgBlAGwAYQB0AGkAbwBuACAAYgBhAG4AZAB3AGkAZAB0AGgAcwAgAGEAbgBkACAAdABpAG0AZQAgAHMAYwBhAGwAZQBzACAAbwBmACAAZABpAGYAZgByAGEAYwB0AGkAdgBlACAAcwBjAGkAbgB0AGkAbABsAGEAdABpAG8AbgAgAHcAZQByAGUAIAB0AGgAZQBuACAAZABlAHIAaQB2AGUAZAAgAGIAeQAgAGEAdQB0AG8AYwBvAHIAcgBlAGwAYQB0AGkAbwBuAC4AIABTAGUAYwBvAG4AZABhAHIAeQAgAHMAcABlAGMAdAByAGEAIAB3AGUAcgBlACAAYwBhAGwAYwB1AGwAYQB0AGUAZAAgAGIAeQAgAGYAbwByAG0AaQBuAGcAIAB0AGgAZQAgAEYAbwB1AHIAaQBlAHIAIABwAG8AdwBlAHIAIABzAHAAZQBjAHQAcgBhACAAbwBmACAAdABoAGUAIABkAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAuACAATQBvAHMAdAAgAG8AZgAgAHQAaABlACAAbgBlAHcAbAB5ACAAbwBiAHQAYQBpAG4AZQBkACAAZAB5AG4AYQBtAGkAYwAgAHMAcABlAGMAdAByAGEAIABhAHIAZQAgAGEAdAAgAHQAaABlACAAaABpAGcAaABlAHMAdAAgAGYAcgBlAHEAdQBlAG4AYwB5ACAAbwByACAAaABhAHYAZQAgAHQAaABlACAAbABvAG4AZwBlAHMAdAAgAHQAaQBtAGUAIABzAHAAYQBuACAAbwBmACAAYQBuAHkAIABwAHUAYgBsAGkAcwBoAGUAZAAgAGQAYQB0AGEAIABmAG8AcgAgAHQAaABlAHMAZQAgAHAAdQBsAHMAYQByAHMALgAgAEYAbwByACAAUABTAFIAcwAgAEIAMAA1ADQAMAArADIAMwAsACAAQgAyADMAMgA0ACsANgAwACAAYQBuAGQAIABCADIAMwA1ADEAKwA2ADEALAAgAHQAaABlAHMAZQAgAHcAZQByAGUAIAB0AGgAZQAgAGYAaQByAHMAdAAgAGQAeQBuAGEAbQBpAGMAIABzAHAAZQBjAHQAcgBhACAAZQB2AGUAcgAgAHIAZQBwAG8AcgB0AGUAZAAuACAAVABoAGUAIABmAHIAZQBxAHUAZQBuAGMAeQAtAGQAZQBwAGUAbgBkAGUAbgBjAGUAIABvAGYAIABzAGMAaQBuAHQAaQBsAGwAYQB0AGkAbwBuACAAcABhAHIAYQBtAGUAdABlAHIAcwAgAGkAbgBkAGkAYwBhAHQAZQBzACAAdABoAGEAdAAgAHQAaABlACAAaQBuAHQAZQByAHYAZQBuAGkAbgBnACAAbQBlAGQAaQB1AG0AIABjAGEAbgAgAHIAYQByAGUAbAB5ACAAYgBlACAAaQBkAGUAYQBsAGwAeQAgAHQAdQByAGIAdQBsAGUAbgB0ACAAdwBpAHQAaAAgAGEAIABLAG8AbABtAG8AZwBvAHIAbwB2ACAAcwBwAGUAYwB0AHIAdQBtAC4AIABUAGgAZQAgAHQAaABpAG4AIABzAGMAcgBlAGUAbgAgAG0AbwBkAGUAbAAgAHcAbwByAGsAZQBkACAAdwBlAGwAbAAgAGEAdAAgAFMALQBiAGEAbgBkACAAZgBvAHIAIAB0AGgAZQAgAHMAYwBpAG4AdABpAGwAbABhAHQAaQBvAG4AIABvAGYAIABQAFMAUgAgAEIAMQA5ADMAMwArADEANgAuACAAUABhAHIAYQBiAG8AbABpAGMAIABhAHIAYwBzACAAdwBlAHIAZQAgAGQAZQB0AGUAYwB0AGUAZAAgAGkAbgAgAHQAaABlACAAcwBlAGMAbwBuAGQAYQByAHkAIABzAHAAZQBjAHQAcgBhACAAbwBmACAAdABoAHIAZQBlACAAcAB1AGwAcwBhAHIAcwAsACAAUABTAFIAcwAgAEIAMAAzADUANQArADUANAAsACAAQgAwADUANAAwACsAMgAzACAAYQBuAGQAIABCADIAMQA1ADQAKwA0ADAALAAgAGEAbABsACAAbwBmACAAdwBoAGkAYwBoACAAdwBlAHIAZQAgAGEAcwB5AG0AbQBlAHQAcgBpAGMAYQBsAGwAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGUAZAAuACAAVABoAGUAIABpAG4AdgBlAHIAdABlAGQAIABhAHIAYwBsAGUAdABzACAAbwBmACAAUABTAFIAIABCADAAMwA1ADUAKwA1ADQAIAB3AGUAcgBlACAAcwBlAGUAbgAgAHQAbwAgAGUAdgBvAGwAdgBlACAAYQBsAG8AbgBnACAAdABoAGUAIABtAGEAaQBuACAAcABhAHIAYQBiAG8AbABhACAAdwBpAHQAaABpAG4AIABhACAAYwBvAG4AdABpAG4AdQBvAHUAcwAgAG8AYgBzAGUAcgB2AGkAbgBnACAAcwBlAHMAcwBpAG8AbgAgAG8AZgAgADEAMgAgAGgAbwB1AHIAcwAsACAAZgByAG8AbQAgAHcAaABpAGMAaAAgAHQAaABlACAAYQBuAGcAdQBsAGEAcgAgAHYAZQBsAG8AYwBpAHQAeQAgAG8AZgAgAHQAaABlACAAcAB1AGwAcwBhAHIAIAB3AGEAcwAgAGUAcwB0AGkAbQBhAHQAZQBkACAAdABoAGEAdAAgAHcAYQBzACAAYwBvAG4AcwBpAHMAdABlAG4AdAAgAHcAaQB0AGgAIAB0AGgAZQAgAG0AZQBhAHMAdQByAGUAbQBlAG4AdAAgAGIAeQAgAHYAZQByAHkAIABsAG8AbgBnACAAYgBhAHMAZQBsAGkAbgBlACAAaQBuAHQAZQByAGYAZQByAG8AbQBlAHQAcgB5ACAAKABWAEwAQgBJACkALgAAADhodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0ErQS82MTgvQTE4NgAAAFQAVwBhAG4AZwAgAFAALgBGAC4AOwAgAEgAYQBuACAASgAuAEwALgA7ACAASABhAG4AIABMAC4AOwAgAFoAaABhAG4AZwAgAEoALgBIAC4AOwAgAEwAaQAgAEoALgBRAC4AOwAgAFcAYQBuAGcAIABDAC4AOwAgAEgAYQBuACAASgAuADsAIABXAGEAbgBnACAAVAAuADsAIABHAGEAbwAgAFgALgBZAC4AAAATMjAxOC0xMC0yNlQwOToyOTozOAAAABMyMDIyLTExLTA0VDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADgAQQAmAEEALgAuAC4ANgAxADgAQQAuADEAOAA2AFd/wAAAAAAABXJhZGlvAAABJ2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxOC9BMTg2L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQStBLzYxOC9BMTg2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BK0EvNjE4L0ExODYAAABkaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAF52czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAAM3N0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAM5Db25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0ErQS82MTgvQTE4Ni90YWJsZTEgKFBhcmFtZXRlcnMgb2YgMTAgcHVsc2FycyBhcmUgbGlzdGVkIHRvZ2V0aGVyIHdpdGggZnJlcXVlbmNpZXMgZm9yIHByZXZpb3VzIGR5bmFtaWMgc3BlY3RydW0gb2JzZXJ2YXRpb25zKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4zNjE4MDE4NgAAAB9pdm86Ly9jZHMudml6aWVyL2ovYSthLzYxOS9hMTI0AAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAADkovQStBLzYxOS9BMTI0AAAAJgBUAEgATwBSACAAcwB1AHIAdgBlAHkAIABpAG4AIABuAG8AcgB0AGgAZQByAG4AIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAAAAIcmVzZWFyY2gAAAkBAFIAYQBkAGkAbwAgAGMAbwBuAHQAaQBuAHUAdQBtACAAcwB1AHIAdgBlAHkAcwAgAG8AZgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAYwBhAG4AIABmAGkAbgBkACAAYQBuAGQAIABjAGgAYQByAGEAYwB0AGUAcgBpAHoAZQAgAEgASQBJACAAcgBlAGcAaQBvAG4AcwAsACAAcwB1AHAAZQByAG4AbwB2AGEAIAByAGUAbQBuAGEAbgB0AHMAIAAoAFMATgBSAHMAKQAsACAAcABsAGEAbgBlAHQAYQByAHkAIABuAGUAYgB1AGwAYQBlACAAKABQAE4AZQApACwAIABhAG4AZAAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABzAG8AdQByAGMAZQBzAC4AIABBACAAbgB1AG0AYgBlAHIAIABvAGYAIABzAHUAcgB2AGUAeQBzACAAYQB0ACAAaABpAGcAaAAgAGEAbgBnAHUAbABhAHIAIAByAGUAcwBvAGwAdQB0AGkAbwBuACAAKAA8AH4AMgA1ACIAKQAgAGEAdAAgAGQAaQBmAGYAZQByAGUAbgB0ACAAdwBhAHYAZQBsAGUAbgBnAHQAaABzACAAZQB4AGkAcwB0ACAAdABvACAAcwB0AHUAZAB5ACAAdABoAGUAIABpAG4AdABlAHIAcwB0AGUAbABsAGEAcgAgAG0AZQBkAGkAdQBtACAAKABJAFMATQApACwAIABiAHUAdAAgAG4AbwAgAGMAbwBtAHAAYQByAGEAYgBsAGUAIABoAGkAZwBoAC0AcgBlAHMAbwBsAHUAdABpAG8AbgAgAGEAbgBkACAAaABpAGcAaAAtAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAHMAdQByAHYAZQB5ACAAZQB4AGkAcwB0AHMAIABhAHQAIABsAG8AbgBnACAAcgBhAGQAaQBvACAAdwBhAHYAZQBsAGUAbgBnAHQAaABzACAAYQByAG8AdQBuAGQAIAAyADEAYwBtAC4AIABPAHUAcgAgAGcAbwBhAGwAIABpAHMAIAB0AG8AIABpAG4AdgBlAHMAdABpAGcAYQB0AGUAIAB0AGgAZQAgADIAMQBjAG0AIAByAGEAZABpAG8AIABjAG8AbgB0AGkAbgB1AHUAbQAgAGUAbQBpAHMAcwBpAG8AbgAgAGkAbgAgAHQAaABlACAAbgBvAHIAdABoAGUAcgBuACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAYQB0ACAAPAAyADUAIgAgAHIAZQBzAG8AbAB1AHQAaQBvAG4ALgAgAFcAZQAgAG8AYgBzAGUAcgB2AGUAZAAgAGEAIABsAGEAcgBnAGUAIABmAHIAYQBjAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAgAGkAbgAgAHQAaABlACAAZgBpAHIAcwB0ACAAcQB1AGEAZAByAGEAbgB0ACAAbwBmACAAdABoAGUAIABNAGkAbABrAHkAIABXAGEAeQAgACgAbAA9ADEANAAuADAAewBkAGUAZwB9AC0ANgA3AC4ANAB7AGQAZQBnAH0AIABhAG4AZAAgAHwAYgB8ADwAPQAxAC4AMgA1AHsAZABlAGcAfQApACAAdwBpAHQAaAAgAHQAaABlACAASwBhAHIAbAAgAEcALgAgAEoAYQBuAHMAawB5ACAAVgBlAHIAeQAgAEwAYQByAGcAZQAgAEEAcgByAGEAeQAgACgAVgBMAEEAKQAgAGkAbgAgAHQAaABlACAAQwAtAGMAbwBuAGYAaQBnAHUAcgBhAHQAaQBvAG4AIABjAG8AdgBlAHIAaQBuAGcAIABzAGkAeAAgAGMAbwBuAHQAaQBuAHUAdQBtACAAcwBwAGUAYwB0AHIAYQBsACAAdwBpAG4AZABvAHcAcwAuACAAVABoAGUAcwBlACAAZABhAHQAYQAgAHAAcgBvAHYAaQBkAGUAIABhACAAZABlAHQAYQBpAGwAZQBkACAAdgBpAGUAdwAgAG8AbgAgAHQAaABlACAAYwBvAG0AcABhAGMAdAAgAGEAcwAgAHcAZQBsAGwAIABhAHMAIABlAHgAdABlAG4AZABlAGQAIAByAGEAZABpAG8AIABlAG0AaQBzAHMAaQBvAG4AIABvAGYAIABvAHUAcgAgAEcAYQBsAGEAeAB5ACAAYQBuAGQAIAB0AGgAbwB1AHMAYQBuAGQAcwAgAG8AZgAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABiAGEAYwBrAGcAcgBvAHUAbgBkACAAcwBvAHUAcgBjAGUAcwAuACAAVwBlACAAdQBzAGUAZAAgAHQAaABlACAAQgBMAE8AQgBDAEEAVAAgAHMAbwBmAHQAdwBhAHIAZQAgAGEAbgBkACAAZQB4AHQAcgBhAGMAdABlAGQAIAAxADAAOQAxADYAIABzAG8AdQByAGMAZQBzAC4AIABBAGYAdABlAHIAIAByAGUAbQBvAHYAaQBuAGcAIABzAHAAdQByAGkAbwB1AHMAIABzAG8AdQByAGMAZQAgAGQAZQB0AGUAYwB0AGkAbwBuAHMAIABjAGEAdQBzAGUAZAAgAGIAeQAgAHQAaABlACAAcwBpAGQAZQBsAG8AYgBlAHMAIABvAGYAIAB0AGgAZQAgAHMAeQBuAHQAaABlAHMAaQBzAGUAZAAgAGIAZQBhAG0ALAAgAHcAZQAgAGMAbABhAHMAcwBpAGYAaQBlAGQAIAAxADAAMwA4ADcAIABzAG8AdQByAGMAZQBzACAAYQBzACAAcgBlAGwAaQBhAGIAbABlACAAZABlAHQAZQBjAHQAaQBvAG4AcwAuAFcAZQAgAHMAbQBvAG8AdABoAGUAZAAgAHQAaABlACAAaQBtAGEAZwBlAHMAIAB0AG8AIABhACAAYwBvAG0AbQBvAG4AIAByAGUAcwBvAGwAdQB0AGkAbwBuACAAbwBmACAAMgA1ACIAIABhAG4AZAAgAGUAeAB0AHIAYQBjAHQAZQBkACAAdABoAGUAIABwAGUAYQBrACAAZgBsAHUAeAAgAGQAZQBuAHMAaQB0AHkAIABvAGYAIABlAGEAYwBoACAAcwBvAHUAcgBjAGUAIABpAG4AIABlAGEAYwBoACAAcwBwAGUAYwB0AHIAYQBsACAAdwBpAG4AZABvAHcAIAAoAFMAUABXACkAIAB0AG8AIABkAGUAdABlAHIAbQBpAG4AZQAgAHQAaABlACAAcwBwAGUAYwB0AHIAYQBsACAAaQBuAGQAaQBjAGUAcwAgAHsAYQBsAHAAaABhAH0AIAAoAGEAcwBzAHUAbQBpAG4AZwAgAEkAKABuAHUAKQB7AHAAcgBvAHAALgB0AG8AfQBuAHUAXgBhAGwAcABoAGEAXgApAC4AIABCAHkAIABjAHIAbwBzAHMALQBtAGEAdABjAGgAaQBuAGcAIAB3AGkAdABoACAAYwBhAHQAYQBsAG8AZwBzACAAbwBmACAASABJAEkAIAByAGUAZwBpAG8AbgBzACwAIABTAE4AUgBzACwAIABQAE4AZQAsACAAYQBuAGQAIABwAHUAbABzAGEAcgBzACwAIAB3AGUAIABmAG8AdQBuAGQAIAByAGEAZABpAG8AIABjAG8AdQBuAHQAZQByAHAAYQByAHQAcwAgAGYAbwByACAAOAA0ADAAIABIAEkASQAgAHIAZQBnAGkAbwBuAHMALAAgADUAMgAgAFMATgBSAHMALAAgADEANgA0ACAAUABOAGUALAAgAGEAbgBkACAAMwA4ACAAcAB1AGwAcwBhAHIAcwAuACAAVwBlACAAZgBvAHUAbgBkACAANwA5ACAAYwBvAG4AdABpAG4AdQB1AG0AIABzAG8AdQByAGMAZQBzACAAdABoAGEAdAAgAGEAcgBlACAAYQBzAHMAbwBjAGkAYQB0AGUAZAAgAHcAaQB0AGgAIABYAC0AcgBhAHkAIABzAG8AdQByAGMAZQBzAC4AIABXAGUAIABpAGQAZQBuAHQAaQBmAGkAZQBkACAANgA5ADkAIAB1AGwAdAByAGEAcwB0AGUAZQBwACAAcwBwAGUAYwB0AHIAYQBsACAAcwBvAHUAcgBjAGUAcwAgACgAYQBsAHAAaABhADwALQAxAC4AMwApACAAdABoAGEAdAAgAGMAbwB1AGwAZAAgAGIAZQAgAGgAaQBnAGgALQByAGUAZABzAGgAaQBmAHQAIABnAGEAbABhAHgAaQBlAHMALgAgAEEAcgBvAHUAbgBkACAAOQAwADAAMAAgAG8AZgAgAHQAaABlACAAcwBvAHUAcgBjAGUAcwAgAHcAZQAgAGUAeAB0AHIAYQBjAHQAZQBkACAAYQByAGUAIABuAG8AdAAgAGMAbABhAHMAcwBpAGYAaQBlAGQAIABzAHAAZQBjAGkAZgBpAGMAYQBsAGwAeQAsACAAYgB1AHQAIABiAGEAcwBlAGQAIABvAG4AIAB0AGgAZQBpAHIAIABzAHAAYQB0AGkAYQBsACAAYQBuAGQAIABzAHAAZQBjAHQAcgBhAGwAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAsACAAYQAgAGwAYQByAGcAZQAgAGYAcgBhAGMAdABpAG8AbgAgAG8AZgAgAHQAaABlAG0AIABpAHMAIABsAGkAawBlAGwAeQAgAHQAbwAgAGIAZQAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABiAGEAYwBrAGcAcgBvAHUAbgBkACAAcwBvAHUAcgBjAGUAcwAuACAATQBvAHIAZQAgAHQAaABhAG4AIAA3ADcANQAwACAAcwBvAHUAcgBjAGUAcwAgAGQAbwAgAG4AbwB0ACAAaABhAHYAZQAgAGMAbwB1AG4AdABlAHIAcABhAHIAdABzACAAaQBuACAAdABoAGUAIABTAEkATQBCAEEARAAgAGQAYQB0AGEAYgBhAHMAZQAsACAAYQBuAGQAIABtAG8AcgBlACAAdABoAGEAbgAgADMANwA2ADAAIABzAG8AdQByAGMAZQBzACAAZABvACAAbgBvAHQAIABoAGEAdgBlACAAYwBvAHUAbgB0AGUAcgBwAGEAcgB0AHMAIABpAG4AIAB0AGgAZQAgAE4ARQBEACAAZABhAHQAYQBiAGEAcwBlAC4AIABTAHQAdQBkAHkAaQBuAGcAIAB0AGgAZQAgAGwAbwBuAGcAIAB3AGEAdgBlAGwAZQBuAGcAdABoAHMAIABjAG0AIABjAG8AbgB0AGkAbgB1AHUAbQAgAGUAbQBpAHMAcwBpAG8AbgAgAGEAbgBkACAAdABoAGUAIABhAHMAcwBvAGMAaQBhAHQAZQBkACAAcwBwAGUAYwB0AHIAYQBsACAAaQBuAGQAaQBjAGUAcwAgAGEAbABsAG8AdwBzACAAdQBzACAAdABvACAAYwBoAGEAcgBhAGMAdABlAHIAaQB6AGUAIABhACAAbABhAHIAZwBlACAAZgByAGEAYwB0AGkAbwBuACAAbwBmACAARwBhAGwAYQBjAHQAaQBjACAAYQBuAGQAIABlAHgAdAByAGEAZwBhAGwAYQBjAHQAaQBjACAAcgBhAGQAaQBvACAAcwBvAHUAcgBjAGUAcwAgAGkAbgAgAHQAaABlACAAYQByAGUAYQAgAG8AZgAgAHQAaABlACAAbgBvAHIAdABoAGUAcgBuACAAaQBuAG4AZQByACAATQBpAGwAawB5ACAAVwBhAHkALgAgAFQAaABpAHMAIABkAGEAdABhAGIAYQBzAGUAIAB3AGkAbABsACAAYgBlACAAZQB4AHQAcgBlAG0AZQBsAHkAIAB1AHMAZQBmAHUAbAAgAGYAbwByACAAZgB1AHQAdQByAGUAIABzAHQAdQBkAGkAZQBzACAAbwBmACAAYQAgAGQAaQB2AGUAcgBzAGUAIABzAGUAdAAgAG8AZgAgAGEAcwB0AHIAbwBwAGgAeQBzAGkAYwBhAGwAIABvAGIAagBlAGMAdABzAC4AAAA4aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BK0EvNjE5L0ExMjQAAADlAFcAYQBuAGcAIABZAC4AOwAgAEIAaQBoAHIAIABTAC4AOwAgAFIAdQBnAGUAbAAgAE0ALgA7ACAAQgBlAHUAdABoAGUAcgAgAEgALgA7ACAASgBvAGgAbgBzAHQAbwBuACAASwAuAEcALgA7ACAATwB0AHQAIABKAC4AOwAgAFMAbwBsAGUAcgAgAEoALgBEAC4AOwAgAEIAcgB1AG4AdABoAGEAbABlAHIAIABBAC4AOwAgAEEAbgBkAGUAcgBzAG8AbgAgAEwALgBEAC4AOwAgAFUAcgBxAHUAaABhAHIAdAAgAEoALgBTAC4AOwAgAEsAbABlAHMAcwBlAG4AIABSAC4AUwAuADsAIABMAGkAbgB6ACAASAAuADsAIABNAGMAQwBsAHUAcgBlAC0ARwByAGkAZgBmAGkAdABoAHMAIABOAC4ATQAuADsAIABHAGwAbwB2AGUAcgAgAFMALgBDAC4ATwAuADsAIABNAGUAbgB0AGUAbgAgAEsALgBNAC4AOwAgAEIAaQBnAGkAZQBsACAARgAuADsAIABIAG8AYQByAGUAIABNAC4AOwAgAEwAbwBuAGcAbQBvAHIAZQAgAFMALgBOAC4AAAATMjAxOC0xMS0xNFQwNzo1NTo0OAAAABMyMDIyLTExLTA0VDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADgAQQAmAEEALgAuAC4ANgAxADkAQQAuADEAMgA0AFd/wAAAAAAABXJhZGlvAAAB1Wh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxOS9BMTI0L2xpc3Rhdmc/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjE5L0ExMjQvbGlzdGNvbnQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjE5L0ExMjQvY2F0YWxvZz86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQStBLzYxOS9BMTI0Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BK0EvNjE5L0ExMjQAAAC8aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACUdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAFdzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAGIQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjE5L0ExMjQvbGlzdGF2ZyAoTGlzdCBvZiBmaXRzIGZpbGVzIGZvciBhdmVyYWdlIGltYWdlcyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0ErQS82MTkvQTEyNC9saXN0Y29udCAoTGlzdCBvZiBjb250aW51dW0gZml0cyBpbWFnZXMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BK0EvNjE5L0ExMjQvY2F0YWxvZyAoRnVsbCBjb250aW51dW0gY2F0YWxvZyAoY29ycmVjdGVkIHZlcnNpb24gZnJvbSBlcnJhdHVtLCBBJkEsIDY0MSwgQzEgKDIwMjApKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMzYxOTAxMjQAAAAfaXZvOi8vY2RzLnZpemllci9qL2ErYS82MjEvYTExNgAAABF2czpjYXRhbG9nc2VydmljZQAAAA5KL0ErQS82MjEvQTExNgAAACEASABFAFMAUwAgAEoAMQA4ADIANQAtADEAMwA3ACAAcABhAHIAdABpAGMAbABlACAAdAByAGEAbgBzAHAAbwByAHQAAAAIcmVzZWFyY2gAAAZtAFcAZQAgAHAAcgBlAHMAZQBuAHQAIABhACAAZABlAHQAYQBpAGwAZQBkACAAdgBpAGUAdwAgAG8AZgAgAHQAaABlACAAcAB1AGwAcwBhAHIAIAB3AGkAbgBkACAAbgBlAGIAdQBsAGEAIAAoAFAAVwBOACkAIABIAEUAUwBTACAASgAxADgAMgA1AC0ALQAxADMANwAuACAAVwBlACAAYQBpAG0AIAB0AG8AIABjAG8AbgBzAHQAcgBhAGkAbgAgAHQAaABlACAAbQBlAGMAaABhAG4AaQBzAG0AcwAgAGQAbwBtAGkAbgBhAHQAaQBuAGcAIAB0AGgAZQAgAHAAYQByAHQAaQBjAGwAZQAgAHQAcgBhAG4AcwBwAG8AcgB0ACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAG4AZQBiAHUAbABhACwAIABhAGMAYwBvAHUAbgB0AGkAbgBnACAAZgBvAHIAIABpAHQAcwAgAGEAbgBvAG0AYQBsAG8AdQBzAGwAeQAgAGwAYQByAGcAZQAgAHMAaQB6AGUAIABhAG4AZAAgAHMAcABlAGMAdAByAGEAbAAgAGMAaABhAHIAYQBjAHQAZQByAGkAcwB0AGkAYwBzAC4AIABUAGgAZQAgAG4AZQBiAHUAbABhACAAaQBzACAAcwB0AHUAZABpAGUAZAAgAHUAcwBpAG4AZwAgAGEAIABkAGUAZQBwACAAZQB4AHAAbwBzAHUAcgBlACAAZgByAG8AbQAgAG8AdgBlAHIAIAAxADIAIAB5AGUAYQByAHMAIABvAGYAIABIAC4ARQAuAFMALgBTAC4AIABJACAAbwBwAGUAcgBhAHQAaQBvAG4ALAAgAHQAbwBnAGUAdABoAGUAcgAgAHcAaQB0AGgAIABkAGEAdABhACAAZgByAG8AbQAgAEgALgBFAC4AUwAuAFMALgAgAEkASQAgAGkAbQBwAHIAbwB2AGkAbgBnACAAdABoAGUAIABsAG8AdwAgAGUAbgBlAHIAZwB5ACAAcwBlAG4AcwBpAHQAaQB2AGkAdAB5AC4AIABFAG4AaABhAG4AYwBlAGQAIABlAG4AZQByAGcAeQAtACAAZABlAHAAZQBuAGQAZQBuAHQAIABtAG8AcgBwAGgAbwBsAG8AZwBpAGMAYQBsACAAYQBuAGQAIABzAHAAYQB0AGkAYQBsAGwAeQAtAHIAZQBzAG8AbAB2AGUAZAAgAHMAcABlAGMAdAByAGEAbAAgAGEAbgBhAGwAeQBzAGUAcwAgAHAAcgBvAGIAZQAgAHQAaABlACAAVgBlAHIAeQAgAEgAaQBnAGgAIABFAG4AZQByAGcAeQAgACgAVgBIAEUALAAgAEUAPgAwAC4AMQBUAGUAVgApACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHIAbwBwAGUAcgB0AGkAZQBzACAAbwBmACAAdABoAGUAIABuAGUAYgB1AGwAYQAuACAAVABoAGUAIABuAGUAYgB1AGwAYQAgAGUAbQBpAHMAcwBpAG8AbgAgAGkAcwAgAHIAZQB2AGUAYQBsAGUAZAAgAHQAbwAgAGUAeAB0AGUAbgBkACAAbwB1AHQAIAB0AG8AIAAxAC4ANQAgAGQAZQBnAHIAZQBlAHMAIABmAHIAbwBtACAAdABoAGUAIABwAHUAbABzAGEAcgAsACAAfgAxAC4ANQAgAHQAaQBtAGUAcwAgAGYAdQByAHQAaABlAHIAIAB0AGgAYQBuACAAcAByAGUAdgBpAG8AdQBzAGwAeQAgAHMAZQBlAG4ALAAgAG0AYQBrAGkAbgBnACAASABFAFMAUwAgAEoAMQA4ADIANQAtADEAMwA3ACwAIAB3AGkAdABoACAAYQBuACAAaQBuAHQAcgBpAG4AcwBpAGMAIABkAGkAYQBtAGUAdABlAHIAIABvAGYAIAB+ADEAMAAwAHAAYwAsACAAcABvAHQAZQBuAHQAaQBhAGwAbAB5ACAAdABoAGUAIABsAGEAcgBnAGUAcwB0ACAAZwBhAG0AbQBhAC0AcgBhAHkAIABQAFcATgAgAGMAdQByAHIAZQBuAHQAbAB5ACAAawBuAG8AdwBuAC4AIABDAGgAYQByAGEAYwB0AGUAcgBpAHMAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABuAGUAYgB1AGwAYQAnAHMAIABzAHQAcgBvAG4AZwBsAHkAIABlAG4AZQByAGcAeQAtAGQAZQBwAGUAbgBkAGUAbgB0ACAAbQBvAHIAcABoAG8AbABvAGcAeQAgAGUAbgBhAGIAbABlAHMAIAB0AGgAZQAgAHAAYQByAHQAaQBjAGwAZQAgAHQAcgBhAG4AcwBwAG8AcgB0ACAAbQBlAGMAaABhAG4AaQBzAG0AcwAgAHQAbwAgAGIAZQAgAGMAbwBuAHMAdAByAGEAaQBuAGUAZAAuACAAQQAgAGQAZQBwAGUAbgBkAGUAbgBjAGUAIABvAGYAIAB0AGgAZQAgAG4AZQBiAHUAbABhACAAZQB4AHQAZQBuAHQAIAB3AGkAdABoACAAZQBuAGUAcgBnAHkAIABvAGYAIABSAHsAcAByAG8AcAAuAHQAbwB9ACAARQBeAGEAbABwAGgAYQBeACAAdwBpAHQAaAAgAGEAbABwAGgAYQA9AC0AMAAuADIAOQArAC8ALQAwAC4AMAA0ACgAcwB0AGEAdAApACsALwAtADAALgAwADUAKABzAHkAcwApACAAZABpAHMAZgBhAHYAbwB1AHIAcwAgAGEAIABwAHUAcgBlACAAZABpAGYAZgB1AHMAaQBvAG4AIABzAGMAZQBuAGEAcgBpAG8AIABmAG8AcgAgAHAAYQByAHQAaQBjAGwAZQAgAHQAcgBhAG4AcwBwAG8AcgB0ACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAG4AZQBiAHUAbABhAC4AIABUAGgAZQAgAHQAbwB0AGEAbAAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAZgBsAHUAeAAgAG8AZgAgAHQAaABlACAAbgBlAGIAdQBsAGEAIABhAGIAbwB2AGUAIAAxAH4AVABlAFYAIABpAHMAIABmAG8AdQBuAGQAIAB0AG8AIABiAGUAIAAoADEALgAxADIAKwAvAC0AMAAuADAAMwAoAHMAdABhAHQAKQArAC8ALQAwAC4AMgA1ACgAcwB5AHMAKQApAHgAMQAwAF4ALQAxADEAXgBjAG0AXgAtADIAXgBzAF4ALQAxAF4ALAAgAGMAbwByAHIAZQBzAHAAbwBuAGQAaQBuAGcAIAB0AG8AIAB+ADYANAAlACAAbwBmACAAdABoAGUAIABmAGwAdQB4ACAAbwBmACAAdABoAGUAIABDAHIAYQBiACAATgBlAGIAdQBsAGEALgAgAEgARQBTAFMAIABKADEAOAAyADUALQAxADMANwAgAGkAcwAgAGEAIABQAFcATgAgAHcAaQB0AGgAIABjAGwAZQBhAHIAIABlAG4AZQByAGcAeQAtAGQAZQBwAGUAbgBkAGUAbgB0ACAAbQBvAHIAcABoAG8AbABvAGcAeQAgAGEAdAAgAFYASABFACAAZwBhAG0AbQBhAC0AcgBhAHkAIABlAG4AZQByAGcAaQBlAHMALgAgAFQAaABpAHMAIABzAG8AdQByAGMAZQAgAGkAcwAgAHUAcwBlAGQAIABhAHMAIABhACAAbABhAGIAbwByAGEAdABvAHIAeQAgAHQAbwAgAGkAbgB2AGUAcwB0AGkAZwBhAHQAZQAgAHAAYQByAHQAaQBjAGwAZQAgAHQAcgBhAG4AcwBwAG8AcgB0ACAAdwBpAHQAaABpAG4AIABtAGkAZABkAGwAZQAtAGEAZwBlAGQAIABQAFcATgBlAC4AIABEAGUAZQBwACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABvAGYAIAB0AGgAaQBzACAAaABpAGcAaABsAHkAIABzAHAAYQB0AGkAYQBsAGwAeQAtAGUAeAB0AGUAbgBkAGUAZAAgAFAAVwBOACAAZQBuAGEAYgBsAGUAIABhACAAcwBwAGUAYwB0AHIAYQBsACAAbQBhAHAAIABvAGYAIAB0AGgAZQAgAHIAZQBnAGkAbwBuACAAdABvACAAYgBlACAAcAByAG8AZAB1AGMAZQBkACwAIABwAHIAbwB2AGkAZABpAG4AZwAgAGkAbgBzAGkAZwBoAHQAcwAgAGkAbgB0AG8AIAB0AGgAZQAgAHMAcABlAGMAdAByAGEAbAAgAHYAYQByAGkAYQB0AGkAbwBuACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAG4AZQBiAHUAbABhAC4AAAA4aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BK0EvNjIxL0ExMTYAAAs2AEgALgBFAC4AUwAuAFMALgAgAEMAbwBsAGwAYQBiAG8AcgBhAHQAaQBvAG4AOwAgAEEAYgBkAGEAbABsAGEAIABIAC4AOwAgAEEAaABhAHIAbwBuAGkAYQBuACAARgAuADsAIABBAGkAdAAgAEIAZQBuAGsAaABhAGwAaQAgAEYALgA7ACAAQQBuAGcAdQBlAG4AZQByACAARQAuAE8ALgA7ACAAQQByAGEAawBhAHcAYQAgAE0ALgA7ACAAQQByAGMAYQByAG8AIABDAC4AOwAgAEEAcgBtAGEAbgBkACAAQwAuADsAIABBAHIAcgBpAGUAdABhACAATQAuADsAIABCAGEAYwBrAGUAcwAgAE0ALgA7ACAAQgBhAHIAbgBhAHIAZAAgAE0ALgA7ACAAQgBlAGMAaABlAHIAaQBuAGkAIABZAC4AOwAgAEIAZQBjAGsAZQByACAAVABqAHUAcwAgAEoALgA7ACAAQgBlAHIAZwBlACAARAAuADsAIABCAGUAcgBuAGwAbwBlAGgAcgAgAEsALgA7ACAAQgBsAGEAYwBrAHcAZQBsAGwAIABSAC4AOwAgAEIAbwBlAHQAdABjAGgAZQByACAATQAuADsAIABCAG8AaQBzAHMAbwBuACAAQwAuADsAIABCAG8AbABtAG8AbgB0ACAASgAuADsAIABCAG8AbgBuAGUAZgBvAHkAIABTAC4AOwAgAEIAbwByAGQAYQBzACAAUAAuADsAIABCAHIAZQBnAGUAbwBuACAASgAuADsAIABCAHIAdQBuACAARgAuADsAIABCAHIAdQBuACAAUAAuADsAIABCAHIAeQBhAG4AIABNAC4AOwAgAEIAdQBlAGMAaABlAGwAZQAgAE0ALgA7ACAAQgB1AGwAaQBrACAAVAAuADsAIABCAHkAbAB1AG4AZAAgAFQALgA7ACAAQwBhAHAAYQBzAHMAbwAgAE0ALgA7ACAAQwBhAHIAbwBmAGYAIABTAC4AOwAgAEMAYQByAG8AcwBpACAAQQAuADsAIABDAGEAcwBhAG4AbwB2AGEAIABTAC4AOwAgAEMAZQByAHIAdQB0AGkAIABNAC4AOwAgAEMAaABhAGsAcgBhAGIAbwByAHQAeQAgAE4ALgA7ACAAQwBoAGEAbgBkACAAVAAuADsAIABDAGgAYQBuAGQAcgBhACAAUwAuADsAIABDAGgAYQB2AGUAcwAgAFIALgBDAC4ARwAuADsAIABDAGgAZQBuACAAQQAuADsAIABDAG8AbABhAGYAcgBhAG4AYwBlAHMAYwBvACAAUwAuADsAIABDAG8AbgBkAG8AbgAgAEIALgA7ACAARABhAHYAaQBkAHMAIABJAC4ARAAuADsAIABEAGUAaQBsACAAQwAuADsAIABEAGUAdgBpAG4AIABKAC4AOwAgAGQAZQBXAGkAbAB0ACAAUAAuADsAIABEAGkAcgBzAG8AbgAgAEwALgA7ACAARABqAGEAbgBuAGEAdABpAC0AQQB0AGEAaQAgAEEALgA7ACAARABtAHkAdAByAGkAaQBlAHYAIABBAC4AOwAgAEQAbwBuAGEAdABoACAAQQAuADsAIABEAG8AcgBvAHMAaABlAG4AawBvACAAVgAuADsAIABEAHIAdQByAHkAIABMAC4ATwAnAEMALgA7ACAARAB5AGsAcwAgAEoALgA7ACAARQBnAGIAZQByAHQAcwAgAEsALgA7ACAARQBtAGUAcgB5ACAARwAuADsAIABFAHIAbgBlAG4AdwBlAGkAbgAgAEoALgAtAFAALgA7ACAARQBzAGMAaABiAGEAYwBoACAAUwAuADsAIABGAGUAZwBhAG4AIABTAC4AOwAgAEYAaQBhAHMAcwBvAG4AIABBAC4AOwAgAEYAbwBuAHQAYQBpAG4AZQAgAEcALgA7ACAARgB1AG4AawAgAFMALgA7ACAARgB1AGUAcwBzAGwAaQBuAGcAIABNAC4AOwAgAEcAYQBiAGkAYwBpACAAUwAuADsAIABHAGEAbABsAGEAbgB0ACAAWQAuAEEALgA7ACAARwBhAHQAZQAgAEYALgA7ACAARwBpAGEAdgBpAHQAdABvACAARwAuADsAIABHAGwAYQB3AGkAbwBuACAARAAuADsAIABHAGwAaQBjAGUAbgBzAHQAZQBpAG4AIABKAC4ARgAuADsAIABHAG8AdAB0AHMAYwBoAGEAbABsACAARAAuADsAIABHAHIAbwBuAGQAaQBuACAATQAuAC0ASAAuADsAIABIAGEAaABuACAASgAuADsAIABIAGEAdQBwAHQAIABNAC4AOwAgAEgAZQBpAG4AegBlAGwAbQBhAG4AbgAgAEcALgA7ACAASABlAG4AcgBpACAARwAuADsAIABIAGUAcgBtAGEAbgBuACAARwAuADsAIABIAGkAbgB0AG8AbgAgAEoALgBBAC4AOwAgAEgAbwBmAG0AYQBuAG4AIABXAC4AOwAgAEgAbwBpAHMAYwBoAGUAbgAgAEMALgA7ACAASABvAGwAYwBoACAAVAAuAEwALgA7ACAASABvAGwAbABlAHIAIABNAC4AOwAgAEgAbwByAG4AcwAgAEQALgA7ACAASAB1AGIAZQByACAARAAuADsAIABJAHcAYQBzAGEAawBpACAASAAuADsAIABKAGEAYwBoAG8AbABrAG8AdwBzAGsAYQAgAEEALgA7ACAASgBhAG0AcgBvAHoAeQAgAE0ALgA7ACAASgBhAG4AawBvAHcAcwBrAHkAIABEAC4AOwAgAEoAYQBuAGsAbwB3AHMAawB5ACAARgAuADsAIABKAG8AdQB2AGkAbgAgAEwALgA7ACAASgB1AG4AZwAtAFIAaQBjAGgAYQByAGQAdAAgAEkALgA7ACAASwBhAHMAdABlAG4AZABpAGUAYwBrACAATQAuAEEALgA7ACAASwBhAHQAYQByAHoAeQBuAHMAawBpACAASwAuADsAIABLAGEAdABzAHUAcgBhAGcAYQB3AGEAIABNAC4AOwAgAEsAYQB0AHoAIABVAC4AOwAgAEsAZQByAHMAegBiAGUAcgBnACAARAAuADsAIABLAGgAYQBuAGcAdQBsAHkAYQBuACAARAAuADsAIABLAGgAZQBsAGkAZgBpACAAQgAuADsAIABLAGkAbgBnACAASgAuADsAIABLAGwAZQBwAHMAZQByACAAUwAuADsAIABLAGwAdQB6AG4AaQBhAGsAIABXAC4AOwAgAEsAbwBtAGkAbgAgAE4AdQAuADsAIABLAG8AcwBhAGMAawAgAEsALgA7ACAASwByAGEAdQBzACAATQAuADsAIABMAGEAbQBhAG4AbgBhACAARwAuADsAIABMAGEAdQAgAEoALgA7ACAATABlAGYAYQB1AGMAaABlAHUAcgAgAEoALgA7ACAATABlAG0AaQBlAHIAZQAgAEEALgA7ACAATABlAG0AbwBpAG4AZQAtAEcAbwB1AG0AYQByAGQAIABNAC4AOwAgAEwAZQBuAGEAaQBuACAASgAuAC0AUAAuADsAIABMAGUAcwBlAHIAIABFAC4AOwAgAEwAbwBoAHMAZQAgAFQALgA7ACAATABvAHAAZQB6AC0AQwBvAHQAbwAgAFIALgA7ACAATAB5AHAAbwB2AGEAIABJAC4AOwAgAE0AYQBsAHkAcwBoAGUAdgAgAEQALgA7ACAATQBhAHIAYQBuAGQAbwBuACAAVgAuADsAIABNAGEAcgBjAG8AdwBpAHQAaAAgAEEALgA7ACAATQBhAHIAaQBhAHUAZAAgAEMALgA7ACAATQBhAHIAdABpAC0ARABlAHYAZQBzAGEAIABHAC4AOwAgAE0AYQByAHgAIABSAC4AOwAgAE0AYQB1AHIAaQBuACAARwAuADsAIABNAGUAaQBuAHQAagBlAHMAIABQAC4ASgAuADsAIABNAGkAdABjAGgAZQBsAGwAIABBAC4ATQAuAFcALgA7ACAATQBvAGQAZQByAHMAawBpACAAUgAuADsAIABNAG8AaABhAG0AZQBkACAATQAuADsAIABNAG8AaAByAG0AYQBuAG4AIABMAC4AOwAgAE0AbwBvAHIAZQAgAEMALgA7ACAATQBvAHUAbABpAG4AIABFAC4AOwAgAE0AdQByAGEAYwBoACAAVAAuADsAIABOAGEAawBhAHMAaABpAG0AYQAgAFMALgA7ACAAZABlACAATgBhAHUAcgBvAGkAcwAgAE0ALgA7ACAATgBkAGkAeQBhAHYAYQBsAGEAIABIAC4AOwAgAE4AaQBlAGQAZQByAHcAYQBuAGcAZQByACAARgAuADsAIABOAGkAZQBtAGkAZQBjACAASgAuADsAIABPAGEAawBlAHMAIABMAC4AOwAgAE8AJwBCAHIAaQBlAG4AIABQAC4AOwAgAE8AZABhAGsAYQAgAEgALgA7ACAATwBoAG0AIABTAC4AOwAgAE8AcwB0AHIAbwB3AHMAawBpACAATQAuADsAIABPAHkAYQAgAEkALgA7ACAAUABhAG4AdABlAHIAIABNAC4AOwAgAFAAYQByAHMAbwBuAHMAIABSAC4ARAAuADsAIABQAGUAcgBlAG4AbgBlAHMAIABDAC4AOwAgAFAAZQB0AHIAdQBjAGMAaQAgAFAALgAtAE8ALgA7ACAAUABlAHkAYQB1AGQAIABCAC4AOwAgAFAAaQBlAGwAIABRAC4AOwAgAFAAaQB0AGEAIABTAC4AOwAgAFAAbwBpAHIAZQBhAHUAIABWAC4AOwAgAFAAcgBpAHkAYQBuAGEAIABOAG8AZQBsACAAQQAuADsAIABQAHIAbwBrAGgAbwByAG8AdgAgAEQALgBBAC4AOwAgAFAAcgBvAGsAbwBwAGgAIABIAC4AOwAgAFAAdQBlAGgAbABoAG8AZgBlAHIAIABHAC4AOwAgAFAAdQBuAGMAaAAgAE0ALgA7ACAAUQB1AGkAcgByAGUAbgBiAGEAYwBoACAAQQAuADsAIABSAGEAYQBiACAAUwAuADsAIABSAGEAdQB0AGgAIABSAC4AOwAgAFIAZQBpAG0AZQByACAAQQAuADsAIABSAGUAaQBtAGUAcgAgAE8ALgA7ACAAUgBlAG4AYQB1AGQAIABNAC4AOwAgAFIAaQBlAGcAZQByACAARgAuADsAIABSAGkAbgBjAGgAaQB1AHMAbwAgAEwALgA7ACAAUgBvAG0AbwBsAGkAIABDAC4AOwAgAFIAbwB3AGUAbABsACAARwAuADsAIABSAHUAZABhAGsAIABCAC4AOwAgAFIAdQBpAHoALQBWAGUAbABhAHMAYwBvACAARQAuADsAIABTAGEAaABhAGsAaQBhAG4AIABWAC4AOwAgAFMAYQBpAHQAbwAgAFMALgA7ACAAUwBhAG4AYwBoAGUAegAgAEQALgBBAC4AOwAgAFMAYQBuAHQAYQBuAGcAZQBsAG8AIABBAC4AOwAgAFMAYQBzAGEAawBpACAATQAuADsAIABTAGMAaABsAGkAYwBrAGUAaQBzAGUAcgAgAFIALgA7ACAAUwBjAGgAdQBlAHMAcwBsAGUAcgAgAEYALgA7ACAAUwBjAGgAdQBsAHoAIABBAC4AOwAgAFMAYwBoAHUAdAB0AGUAIABIAC4AOwAgAFMAYwBoAHcAYQBuAGsAZQAgAFUALgA7ACAAUwBjAGgAdwBlAG0AbQBlAHIAIABTAC4AOwAgAFMAZQBnAGwAYQByAC0AQQByAHIAbwB5AG8AIABNAC4AOwAgAFMAZQBuAG4AaQBhAHAAcABhAG4AIABNAC4AOwAgAFMAZQB5AGYAZgBlAHIAdAAgAEEALgBTAC4AOwAgAFMAaABhAGYAaQAgAE4ALgA7ACAAUwBoAGkAbABvAG4AIABJAC4AOwAgAFMAaABpAG4AaQBuAGcAYQB5AGEAbQB3AGUAIABLAC4AOwAgAFMAaQBtAG8AbgBpACAAUgAuADsAIABTAGkAbgBoAGEAIABBAC4AOwAgAFMAbwBsACAASAAuADsAIABTAHAAZQBjAG8AdgBpAHUAcwAgAEEALgA7ACAAUwBwAGkAcgAtAEoAYQBjAG8AYgAgAE0ALgA7ACAAUwB0AGEAdwBhAHIAegAgAEwALgA7ACAAUwB0AGUAZQBuAGsAYQBtAHAAIABSAC4AOwAgAFMAdABlAGcAbQBhAG4AbgAgAEMALgA7ACAAUwB0AGUAcABwAGEAIABDAC4AOwAgAFQAYQBrAGEAaABhAHMAaABpACAAVAAuADsAIABUAGEAdgBlAHIAbgBlAHQAIABKAC4ALQBQAC4AOwAgAFQAYQB2AGUAcgBuAGkAZQByACAAVAAuADsAIABUAGEAeQBsAG8AcgAgAEEALgBNAC4AOwAgAFQAZQByAHIAaQBlAHIAIABSAC4AOwAgAFQAaQBiAGEAbABkAG8AIABMAC4AOwAgAFQAaQB6AGkAYQBuAGkAIABEAC4AOwAgAFQAbAB1AGMAegB5AGsAbwBuAHQAIABNAC4AOwAgAFQAcgBpAGMAaABhAHIAZAAgAEMALgA7ACAAVABzAGkAcgBvAHUAIABNAC4AOwAgAFQAcwB1AGoAaQAgAE4ALgA7ACAAVAB1AGYAZgBzACAAUgAuADsAIABVAGMAaABpAHkAYQBtAGEAIABZAC4AOwAgAHYAYQBuACAAZABlAHIAIABXAGEAbAB0ACAARAAuAEoALgA7ACAAdgBhAG4AIABFAGwAZABpAGsAIABDAC4AOwAgAHYAYQBuACAAUgBlAG4AcwBiAHUAcgBnACAAQwAuADsAIAB2AGEAbgAgAFMAbwBlAGwAZQBuACAAQgAuADsAIABWAGEAcwBpAGwAZQBpAGEAZABpAHMAIABHAC4AOwAgAFYAZQBoACAASgAuADsAIABWAGUAbgB0AGUAcgAgAEMALgA7ACAAVgBpAG4AYwBlAG4AdAAgAFAALgA7ACAAVgBpAG4AawAgAEoALgA7ACAAVgBvAGkAcwBpAG4AIABGAC4AOwAgAFYAbwBlAGwAawAgAEgALgBKAC4AOwAgAFYAdQBpAGwAbABhAHUAbQBlACAAVAAuADsAIABXAGEAZABpAGEAcwBpAG4AZwBoACAAWgAuADsAIABXAGEAZwBuAGUAcgAgAFMALgBKAC4AOwAgAFcAYQBnAG4AZQByACAAUgAuAE0ALgA7ACAAVwBoAGkAdABlACAAUgAuADsAIABXAGkAZQByAHoAYwBoAG8AbABzAGsAYQAgAEEALgA7ACAAWQBhAG4AZwAgAFIALgA7ACAAWQBvAG4AZQBkAGEAIABIAC4AOwAgAFoAYQBiAG8AcgBvAHYAIABEAC4AOwAgAFoAYQBjAGgAYQByAGkAYQBzACAATQAuADsAIABaAGEAbgBpAG4AIABSAC4AOwAgAFoAZAB6AGkAYQByAHMAawBpACAAQQAuAEEALgA7ACAAWgBlAGMAaAAgAEEALgA7ACAAWgBlAGYAaQAgAEYALgA7ACAAWgBpAGUAZwBsAGUAcgAgAEEALgA7ACAAWgBvAHIAbgAgAEoALgA7ACAAWgB5AHcAdQBjAGsAYQAgAE4ALgAAABMyMDE5LTAxLTE1VDA5OjExOjQxAAAAEzIwMjItMTEtMDRUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAOQBBACYAQQAuAC4ALgA2ADIAMQBBAC4AMQAxADYASH/AAAAAAAAPcmFkaW8jZ2FtbWEtcmF5AAABe2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYyMS9BMTE2L2xpc3Q/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjIxL0ExMTYvYm94Zml0cz86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQStBLzYyMS9BMTE2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BK0EvNjIxL0ExMTYAAACQaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAeXZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAABFc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAABA0NvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQStBLzYyMS9BMTE2L2xpc3QgKExpc3Qgb2YgZml0cyBmaWxlcyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0ErQS82MjEvQTExNi9ib3hmaXRzIChTcGVjdHJhbCBmaXQgaW5mb3JtYXRpb24gZm9yIGFsbCBib3hlcyAoYW5hbHlzaXMgQSwgdGFiLiA1IGluIHBhcGVyKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMzYyMTAxMTYAAAAfaXZvOi8vY2RzLnZpemllci9qL2ErYS82MjYvYTEwNAAAABF2czpjYXRhbG9nc2VydmljZQAAAA5KL0ErQS82MjYvQTEwNAAAABkATABPAFQAQQBBAFMAIABwAHUAbABzAGEAcgAgAGQAaQBzAGMAbwB2AGUAcgBpAGUAcwAAAAhyZXNlYXJjaAAABtcAVwBlACAAcAByAGUAcwBlAG4AdAAgAGEAbgAgAG8AdgBlAHIAdgBpAGUAdwAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAVABpAGUAZAAtAEEAcgByAGEAeQAgAEEAbABsAC0AUwBrAHkAIABTAHUAcgB2AGUAeQAgACgATABPAFQAQQBBAFMAKQAgAGYAbwByACAAcgBhAGQAaQBvACAAcAB1AGwAcwBhAHIAcwAgAGEAbgBkACAAZgBhAHMAdAAgAHQAcgBhAG4AcwBpAGUAbgB0AHMALgAgAFQAaABlACAAcwB1AHIAdgBlAHkAIAB1AHMAZQBzACAAdABoAGUAIABoAGkAZwBoAC0AYgBhAG4AZAAgAGEAbgB0AGUAbgBuAGEAcwAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAUwB1AHAAZQByAHQAZQByAHAALAAgAHQAaABlACAAZABlAG4AcwBlACAAaQBuAG4AZQByACAAcABhAHIAdAAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAYwBvAHIAZQAsACAAdABvACAAcwB1AHIAdgBlAHkAIAB0AGgAZQAgAG4AbwByAHQAaABlAHIAbgAgAHMAawB5ACAAKAB7AGQAZQBsAHQAYQB9AD4AMAB7AGQAZQBnAH0AKQAgAGEAdAAgAGEAIABjAGUAbgB0AHIAYQBsACAAbwBiAHMAZQByAHYAaQBuAGcAIABmAHIAZQBxAHUAZQBuAGMAeQAgAG8AZgAgADEAMwA1AE0ASAB6AC4AIABBACAAdABvAHQAYQBsACAAbwBmACAAMgAxADkAIAB0AGkAZQBkAC0AYQByAHIAYQB5ACAAYgBlAGEAbQBzACAAKABjAG8AaABlAHIAZQBuAHQAIABzAHUAbQBtAGEAdABpAG8AbgAgAG8AZgAgAHMAdABhAHQAaQBvAG4AIABzAGkAZwBuAGEAbABzACwAIABjAG8AdgBlAHIAaQBuAGcAIAAxADIAIABzAHEAdQBhAHIAZQAgAGQAZQBnAHIAZQBlAHMAKQAsACAAYQBzACAAdwBlAGwAbAAgAGEAcwAgAHQAaAByAGUAZQAgAGkAbgBjAG8AaABlAHIAZQBuAHQAIABiAGUAYQBtAHMAIAAoAGMAbwB2AGUAcgBpAG4AZwAgADYANwAgAHMAcQB1AGEAcgBlACAAZABlAGcAcgBlAGUAcwApACAAYQByAGUAIABmAG8AcgBtAGUAZAAgAGkAbgAgAGUAYQBjAGgAIABzAHUAcgB2AGUAeQAgAHAAbwBpAG4AdABpAG4AZwAuACAARgBvAHIAIABlAGEAYwBoACAAbwBmACAAdABoAGUAIAAyADIAMgAgAGIAZQBhAG0AcwAsACAAdABvAHQAYQBsACAAaQBuAHQAZQBuAHMAaQB0AHkAIABpAHMAIAByAGUAYwBvAHIAZABlAGQAIABhAHQAIAA0ADkAMQAuADUAMgAgAHsAbQB1AH0AcwAgAHQAaQBtAGUAIAByAGUAcwBvAGwAdQB0AGkAbwBuAC4AIABFAGEAYwBoACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuACAAaQBuAHQAZQBnAHIAYQB0AGUAcwAgAGYAbwByACAAMQBoAHIAIABhAG4AZAAgAGMAbwB2AGUAcgBzACAAMgA1ADkAMgAgAGMAaABhAG4AbgBlAGwAcwAgAGYAcgBvAG0AIAAxADEAOQAgAHQAbwAgADEANQAxAE0ASAB6AC4AIABUAGgAaQBzACAAaQBuAHMAdAByAHUAbQBlAG4AdABhAGwAIABzAGUAdAB1AHAAIABhAGwAbABvAHcAcwAgAEwATwBUAEEAQQBTACAAdABvACAAcgBlAGEAYwBoACAAYQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAdABoAHIAZQBzAGgAbwBsAGQAIABvAGYAIAAxAC0ANQBtAEoAeQAgAGYAbwByACAAcABlAHIAaQBvAGQAaQBjACAAZQBtAGkAcwBzAGkAbwBuAC4AIABUAGgAdQBzACAAZgBhAHIALAAgAHQAaABlACAATABPAFQAQQBBAFMAIABzAHUAcgB2AGUAeQAgAGgAYQBzACAAcgBlAHMAdQBsAHQAZQBkACAAaQBuACAAdABoAGUAIABkAGkAcwBjAG8AdgBlAHIAeQAgAG8AZgAgADcAMwAgAHIAYQBkAGkAbwAgAHAAdQBsAHMAYQByAHMALgAgAEEAbQBvAG4AZwAgAHQAaABlAHMAZQAgAGEAcgBlACAAdAB3AG8AIABtAGkAbABkAGwAeQAgAHIAZQBjAHkAYwBsAGUAZAAgAGIAaQBuAGEAcgB5ACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwAgACgAUAA9ADEAMwAgAGEAbgBkACAAMwAzAG0AcwApACwAIABhAHMAIAB3AGUAbABsACAAYQBzACAAdABoAGUAIABzAGwAbwB3AGUAcwB0AC0AcwBwAGkAbgBuAGkAbgBnACAAcgBhAGQAaQBvACAAcAB1AGwAcwBhAHIAIABjAHUAcgByAGUAbgB0AGwAeQAgAGsAbgBvAHcAbgAgACgAUAA9ADIAMwAuADUAcwApAC4AIABUAGgAZQAgAHMAdQByAHYAZQB5ACAAaABhAHMAIAB0AGgAdQBzACAAZgBhAHIAIABkAGUAdABlAGMAdABlAGQAIAAzADEAMQAgAGsAbgBvAHcAbgAgAHAAdQBsAHMAYQByAHMALAAgAHcAaQB0AGgAIABzAHAAaQBuACAAcABlAHIAaQBvAGQAcwAgAHIAYQBuAGcAaQBuAGcAIABmAHIAbwBtACAANABtAHMAIAB0AG8AIAA1AC4AMABzACAAYQBuAGQAIABkAGkAcwBwAGUAcgBzAGkAbwBuACAAbQBlAGEAcwB1AHIAZQBzACAAZgByAG8AbQAgADMALgAwACAAdABvACAAMgAxADcAcABjAC8AYwBtAF4AMwBeAC4AIABLAG4AbwB3AG4AIABwAHUAbABzAGEAcgBzACAAYQByAGUAIABkAGUAdABlAGMAdABlAGQAIABhAHQAIABmAGwAdQB4ACAAZABlAG4AcwBpAHQAaQBlAHMAIABjAG8AbgBzAGkAcwB0AGUAbgB0ACAAdwBpAHQAaAAgAGwAaQB0AGUAcgBhAHQAdQByAGUAIAB2AGEAbAB1AGUAcwAuACAAVwBlACAAZgBpAG4AZAAgAHQAaABhAHQAIAB0AGgAZQAgAEwATwBUAEEAQQBTACAAcAB1AGwAcwBhAHIAIABkAGkAcwBjAG8AdgBlAHIAaQBlAHMAIABoAGEAdgBlACwAIABvAG4AIABhAHYAZQByAGEAZwBlACwAIABsAG8AbgBnAGUAcgAgAHMAcABpAG4AIABwAGUAcgBpAG8AZABzACAAdABoAGEAbgAgAHQAaABlACAAawBuAG8AdwBuACAAcAB1AGwAcwBhAHIAIABwAG8AcAB1AGwAYQB0AGkAbwBuAC4AIABUAGgAaQBzACAAbQBhAHkAIAByAGUAZgBsAGUAYwB0ACAAZABpAGYAZgBlAHIAZQBuAHQAIABzAGUAbABlAGMAdABpAG8AbgAgAGIAaQBhAHMAZQBzACAAYgBlAHQAdwBlAGUAbgAgAEwATwBUAEEAQQBTACAAYQBuAGQAIABwAHIAZQB2AGkAbwB1AHMAIABzAHUAcgB2AGUAeQBzACwAIAB0AGgAbwB1AGcAaAAgAGkAdAAgAGkAcwAgAGEAbABzAG8AIABwAG8AcwBzAGkAYgBsAGUAIAB0AGgAYQB0ACAAcwBsAG8AdwBlAHIALQBzAHAAaQBuAG4AaQBuAGcAIABwAHUAbABzAGEAcgBzACAAcAByAGUAZgBlAHIAZQBuAHQAaQBhAGwAbAB5ACAAaABhAHYAZQAgAHMAdABlAGUAcABlAHIAIAByAGEAZABpAG8AIABzAHAAZQBjAHQAcgBhAC4AIABMAE8AVABBAEEAUwAgAGkAcwAgAHQAaABlACAAZABlAGUAcABlAHMAdAAgAGEAbABsAC0AcwBrAHkAIABwAHUAbABzAGEAcgAgAHMAdQByAHYAZQB5ACAAdQBzAGkAbgBnACAAYQAgAGQAaQBnAGkAdABhAGwAIABhAHAAZQByAHQAdQByAGUAIABhAHIAcgBhAHkAOwAgAHcAZQAgAGQAaQBzAGMAdQBzAHMAIABzAG8AbQBlACAAbwBmACAAdABoAGUAIABsAGUAcwBzAG8AbgBzACAAbABlAGEAcgBuAGUAZAAgAHQAaABhAHQAIABjAGEAbgAgAGkAbgBmAG8AcgBtACAAdABoAGUAIABhAHAAcAByAG8AYQBjAGgAIABmAG8AcgAgAHMAaQBtAGkAbABhAHIAIABzAHUAcgB2AGUAeQBzACAAdQBzAGkAbgBnACAAZgB1AHQAdQByAGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQBzACAAcwB1AGMAaAAgAGEAcwAgAHQAaABlACAAUwBxAHUAYQByAGUAIABLAGkAbABvAG0AZQB0AHIAZQAgAEEAcgByAGEAeQAuAAAAOGh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQStBLzYyNi9BMTA0AAABMgBTAGEAbgBpAGQAYQBzACAAUwAuADsAIABDAG8AbwBwAGUAcgAgAFMALgA7ACAAQgBhAHMAcwBhACAAQwAuAEcALgA7ACAASABlAHMAcwBlAGwAcwAgAEoALgBXAC4AVAAuADsAIABLAG8AbgBkAHIAYQB0AGkAZQB2ACAAVgAuAEkALgA7ACAATQBpAGMAaABpAGwAbABpACAARAAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABUAGEAbgAgAEMALgBNAC4AOwAgAFYAYQBuACAATABlAGUAdQB3AGUAbgAgAEoALgA7ACAAQwBlAHIAcgBpAGcAbwBuAGUAIABMAC4AOwAgAEYAYQBsAGwAbwB3AHMAIABSAC4AQQAuADsAIABJAGEAYwBvAGIAZQBsAGwAaQAgAE0ALgA7ACAATwByAHIAdQAgAEUALgA7ACAAUABpAHoAegBvACAAUgAuAEYALgA7ACAAUwBoAHUAbABlAHYAcwBrAGkAIABBAC4AOwAgAFQAbwByAGkAYgBpAG8AIABNAC4AQwAuADsAIABUAGUAcgAgAFYAZQBlAG4AIABTAC4AOwAgAFoAdQBjAGMAYQAgAFAALgA7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dAAtAFMAZQBwAHQAZQBtAGIAZQByACAAdwBpAHQAaAAgAHQAaABlACAAcwBlAHYAZQBuAC0AYgBlAGEAbQAgAEEAcgBlAGMAaQBiAG8AIABMAC0AYgBhAG4AZAAgAEYAZQBlAGQAIABBAHIAcgBhAHkAIAAoAEEATABGAEEAKQAgAHIAZQBjAGUAaQB2AGUAcgAgAHMAeQBzAHQAZQBtACAAYQBuAGQAIAB0AGgAZQAgAFcAaQBkAGUAYgBhAG4AZAAgAEEAcgBlAGMAaQBiAG8AIABQAHUAbABzAGEAcgAgAFAAcgBvAGMAZQBzAHMAbwByACAAcwBwAGUAYwB0AHIAYQBsACAAcAByAG8AYwBlAHMAcwBvAHIAcwAuACAAVwBoAGkAbABlACAAdABoAGUAcwBlACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIAB3AGUAcgBlACAAZwBlAGEAcgBlAGQAIABtAGEAaQBuAGwAeQAgAGEAdAAgAHQAZQBzAHQAaQBuAGcAIABhAG4AZAAgAGQAZQBiAHUAZwBnAGkAbgBnACAAcwB1AHIAdgBlAHkAIABzAHQAcgBhAHQAZQBnAHkALAAgAGgAYQByAGQAdwBhAHIAZQAsACAAYQBuAGQAIABzAG8AZgB0AHcAYQByAGUALAAgAGEAcABwAHIAbwB4AGkAbQBhAHQAZQBsAHkAIAA0ADgAaAByACAAbwBmACAAdABlAGwAZQBzAGMAbwBwAGUAIAB0AGkAbQBlACAAeQBpAGUAbABkAGUAZAAgAHMAYwBpAGUAbgBjAGUALQBxAHUAYQBsAGkAdAB5ACAAZABhAHQAYQAuACAAVABvACAAdABlAHMAdAAgAG8AdQByACAAYQBiAGkAbABpAHQAeQAgAHQAbwAgAGQAaQBzAGMAcgBpAG0AaQBuAGEAdABlACAAYwBvAHMAbQBpAGMAIABzAGkAZwBuAGEAbABzACAAZgByAG8AbQAgAHIAYQBkAGkAbwAtAGYAcgBlAHEAdQBlAG4AYwB5ACAAaQBuAHQAZQByAGYAZQByAGUAbgBjAGUAIABhAG4AZAAgAG4AbwBpAHMAZQAsACAAMQA2ADUAIABjAGEAbgBkAGkAZABhAHQAZQBzACAAcgBhAG4AZwBpAG4AZwAgAGkAbgAgAHIAZQBsAGkAYQBiAGkAbABpAHQAeQAgAGwAaQBrAGUAbABpAGgAbwBvAGQAIAB3AGUAcgBlACAAcgBlAG8AYgBzAGUAcgB2AGUAZAAgAHcAaQB0AGgAIAB0AGgAZQAgAHMAaQBuAGcAbABlAC0AYgBlAGEAbQAgAEwALQBiAGEAbgBkACAAdwBpAGQAZQAgAHMAeQBzAHQAZQBtACAAYQB0ACAAQQByAGUAYwBpAGIAbwAgAGkAbgAgADIAMAAwADUAIABKAGEAbgB1AGEAcgB5AC0ARgBlAGIAcgB1AGEAcgB5AC4AIABPAGYAIAB0AGgAbwBzAGUALAAgADQAMQAlACAAdwBlAHIAZQAgAGMAbwBuAGYAaQByAG0AZQBkACAAYQBzACAAcgBlAGEAbAAuACAAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABlACAAcgBlAHMAdQBsAHQAcwAgAG8AZgAgAGIAbwB0AGgAIAB0AGgAZQAgAEEATABGAEEAIABhAG4AZAAgAHQAaABlACAAcwBpAG4AZwBsAGUALQBiAGUAYQBtACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABmAG8AcgAgAHQAaABlACAAcwBhAG0AcABsAGUAIABvAGYAIAAxADYANgAgAGMAbwBuAGYAaQByAG0AZQBkACAASABJACAAcwBvAHUAcgBjAGUAcwAsACAAYQBzACAAdwBlAGwAbAAgAGEAcwAgAG8AdQByACAAYQBzAHMAZQBzAHMAbQBlAG4AdAAgAG8AZgAgAHQAaABlAGkAcgAgAG8AcAB0AGkAYwBhAGwAIABjAG8AdQBuAHQAZQByAHAAYQByAHQAcwAuAAAAN2h0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQUovMTMwLzI2MTMAAAEWAEcAaQBvAHYAYQBuAGUAbABsAGkAIABSAC4AOwAgAEgAYQB5AG4AZQBzACAATQAuAFAALgA7ACAASwBlAG4AdAAgAEIALgBSAC4AOwAgAFAAZQByAGkAbABsAGEAdAAgAFAALgA7ACAAQwBhAHQAaQBuAGUAbABsAGEAIABCAC4AOwAgAEgAbwBmAGYAbQBhAG4AIABHAC4ATAAuADsAIABNAG8AbQBqAGkAYQBuACAARQAuADsAIABSAG8AcwBlAG4AYgBlAHIAZwAgAEoALgBMAC4AOwAgAFMAYQBpAG4AdABvAG4AZwBlACAAQQAuADsAIABTAHAAZQBrAGsAZQBuAHMAIABLAC4AOwAgAFMAdABpAGUAcgB3AGEAbAB0ACAAUwAuADsAIABCAHIAbwBzAGMAaAAgAE4ALgA7ACAATQBhAHMAdABlAHIAcwAgAEsALgBMAC4AOwAgAFMAcAByAGkAbgBnAG8AYgAgAEMALgBNAC4AOwAgAEsAYQByAGEAYwBoAGUAbgB0AHMAZQB2ACAASQAuAEQALgA7ACAASwBhAHIAYQBjAGgAZQBuAHQAcwBlAHYAYQAgAFYALgBFAC4AOwAgAEsAbwBvAHAAbQBhAG4AbgAgAFIALgBBAC4AOwAgAE0AdQBsAGwAZQByACAARQAuADsAIAB2AGEAbgAgAEQAcgBpAGUAbAAgAFcALgA7ACAAdgBhAG4AIABaAGUAZQAgAEwALgAAABMyMDA4LTAyLTA5VDE0OjM1OjMzAAAAEzIwMjItMTAtMThUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADAANQBBAEoALgAuAC4ALgAxADMAMAAuADIANgAxADMAR3/AAAAAAAAFcmFkaW8AAAL7aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BSi8xMzAvMjYxMy90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BSi8xMzAvMjYxMy9kYXRhPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BSi8xMzAvMjYxMzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQUovMTMwLzI2MTM6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FKLzEzMC8yNjEzL3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FKLzEzMC8yNjEzL2RhdGE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FKLzEzMC8yNjEzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BSi8xMzAvMjYxMwAAAS9pdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAEBdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAACZc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAACN0NvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQUovMTMwLzI2MTMvdGFibGUzICgqUGFyYW1ldGVycyBvZiBMQlcgKEwtYmFuZCB3aWRlKSBkZXRlY3Rpb25zKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQUovMTMwLzI2MTMvZGF0YSAoQUxGQSBkZXRlY3Rpb25zLCBkaXN0YW5jZXMsIEhJIG1hc3NlcywgYW5kIG9wdGljYWwgY291bnRlcnBhcnRzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQUovMTMwLzI2MTMvdGFibGUzICgqUGFyYW1ldGVycyBvZiBMQlcgKEwtYmFuZCB3aWRlKSBkZXRlY3Rpb25zKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQUovMTMwLzI2MTMvZGF0YSAoQUxGQSBkZXRlY3Rpb25zLCBkaXN0YW5jZXMsIEhJIG1hc3NlcywgYW5kIG9wdGljYWwgY291bnRlcnBhcnRzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci41MTMwMjYxMwAAAB5pdm86Ly9jZHMudml6aWVyL2ovYXBqLzY0Mi84NjgAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAANSi9BcEovNjQyLzg2OAAAACEAUgBvAHQAYQB0AGkAbwBuACAAbQBlAGEAcwB1AHIAZQBzACAAZgBvAHIAIAAyADIAMwAgAHAAdQBsAHMAYQByAHMAAAAIcmVzZWFyY2gAAAEdAFQAaABlACAAbABhAHIAZwBlAC0AcwBjAGEAbABlACAAbQBhAGcAbgBlAHQAaQBjACAAZgBpAGUAbABkACAAbwBmACAAbwB1AHIAIABHAGEAbABhAHgAeQAgAGMAYQBuACAAYgBlACAAcAByAG8AYgBlAGQAIABpAG4AIAB0AGgAcgBlAGUAIABkAGkAbQBlAG4AcwBpAG8AbgBzACAAdQBzAGkAbgBnACAARgBhAHIAYQBkAGEAeQAgAHIAbwB0AGEAdABpAG8AbgAgAG8AZgAgAHAAdQBsAHMAYQByACAAcwBpAGcAbgBhAGwAcwAuACAAVwBlACAAcgBlAHAAbwByAHQAIABvAG4AIAB0AGgAZQAgAGQAZQB0AGUAcgBtAGkAbgBhAHQAaQBvAG4AIABvAGYAIAAyADIAMwAgAHIAbwB0AGEAdABpAG8AbgAgAG0AZQBhAHMAdQByAGUAcwAgAGYAcgBvAG0AIABwAG8AbABhAHIAaQB6AGEAdABpAG8AbgAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAbwBmACAAcgBlAGwAYQB0AGkAdgBlAGwAeQAgAGQAaQBzAHQAYQBuAHQAIABzAG8AdQB0AGgAZQByAG4AIABwAHUAbABzAGEAcgBzACAAbQBhAGQAZQAgAHUAcwBpAG4AZwAgAHQAaABlACAAUABhAHIAawBlAHMAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQAuAAAAN2h0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzY0Mi84NjgAAAA/AEgAYQBuACAASgAuAEwALgA7ACAATQBhAG4AYwBoAGUAcwB0AGUAcgAgAFIALgBOAC4AOwAgAEwAeQBuAGUAIABBAC4ARwAuADsAIABRAGkAYQBvACAARwAuAEoALgA7ACAAVgBhAG4AIABTAHQAcgBhAHQAZQBuACAAVwAuAAAAEzIwMDgtMTItMTZUMTA6MjY6MzIAAAATMjAyMi0xMC0yNFQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMAA2AEEAcABKAC4ALgAuADYANAAyAC4ALgA4ADYAOABIf8AAAAAAAAVyYWRpbwAAASRodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi82NDIvODY4L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQXBKLzY0Mi84Njg6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi82NDIvODY4AAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADNzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACGQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BcEovNjQyLzg2OC90YWJsZTEgKFJvdGF0aW9uIG1lYXN1cmVzIGZvciAyMjMgcHVsc2Fycyk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTY0MjA4NjgAAAAdaXZvOi8vY2RzLnZpemllci9qL2Fwai83NjkvNjYAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAMSi9BcEovNzY5LzY2AAAAJwBUAGgAZQAgAEUATABNACAAcwB1AHIAdgBlAHkALgAgAFYALgAgAFcAaABpAHQAZQAgAGQAdwBhAHIAZgAgAGIAaQBuAGEAcgBpAGUAcwAAAAhyZXNlYXJjaAAAA4wAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABlACAAZABpAHMAYwBvAHYAZQByAHkAIABvAGYAIAAxADcAIABsAG8AdwAtAG0AYQBzAHMAIAB3AGgAaQB0AGUAIABkAHcAYQByAGYAcwAgACgAVwBEAHMAKQAgAGkAbgAgAHMAaABvAHIAdAAtAHAAZQByAGkAbwBkACAAKABQADwAPQAxACAAZABhAHkAKQAgAGIAaQBuAGEAcgBpAGUAcwAuACAATwB1AHIAIABzAGEAbQBwAGwAZQAgAGkAbgBjAGwAdQBkAGUAcwAgAGYAbwB1AHIAIABvAGIAagBlAGMAdABzACAAdwBpAHQAaAAgAHIAZQBtAGEAcgBrAGEAYgBsAGUAIABsAG8AZwBnAD0AfgA1ACAAcwB1AHIAZgBhAGMAZQAgAGcAcgBhAHYAaQB0AGkAZQBzACAAYQBuAGQAIABvAHIAYgBpAHQAYQBsACAAcwBvAGwAdQB0AGkAbwBuAHMAIAB0AGgAYQB0ACAAcgBlAHEAdQBpAHIAZQAgAHQAaABlAG0AIAB0AG8AIABiAGUAIABkAG8AdQBiAGwAZQAgAGQAZQBnAGUAbgBlAHIAYQB0AGUAIABiAGkAbgBhAHIAaQBlAHMALgAgAEEAbABsACAAbwBmACAAdABoAGUAIABsAG8AdwBlAHMAdAAgAHMAdQByAGYAYQBjAGUAIABnAHIAYQB2AGkAdAB5ACAAVwBEAHMAIABoAGEAdgBlACAAbQBlAHQAYQBsACAAbABpAG4AZQBzACAAaQBuACAAdABoAGUAaQByACAAcwBwAGUAYwB0AHIAYQAgAGkAbQBwAGwAeQBpAG4AZwAgAGwAbwBuAGcAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAcwBlAHQAdABsAGkAbgBnACAAdABpAG0AZQBzACAAbwByACAAbwBuAGcAbwBpAG4AZwAgAGEAYwBjAHIAZQB0AGkAbwBuAC4AIABOAG8AdABhAGIAbAB5ACwAIABzAGkAeAAgAG8AZgAgAHQAaABlACAAVwBEAHMAIABpAG4AIABvAHUAcgAgAHMAYQBtAHAAbABlACAAaABhAHYAZQAgAGIAaQBuAGEAcgB5ACAAbQBlAHIAZwBlAHIAIAB0AGkAbQBlAHMAIAA8ADEAMABHAHkAcgAuACAARgBvAHUAcgAgAGgAYQB2AGUAIAA+AH4AMAAuADkATQBfAHsAcwB1AG4AfQBfACAAYwBvAG0AcABhAG4AaQBvAG4AcwAuACAASQBmACAAdABoAGUAIABjAG8AbQBwAGEAbgBpAG8AbgBzACAAYQByAGUAIABtAGEAcwBzAGkAdgBlACAAVwBEAHMALAAgAHQAaABlAHMAZQAgAGYAbwB1AHIAIABiAGkAbgBhAHIAaQBlAHMAIAB3AGkAbABsACAAZQB2AG8AbAB2AGUAIABpAG4AdABvACAAcwB0AGEAYgBsAGUAIABtAGEAcwBzACAAdAByAGEAbgBzAGYAZQByACAAQQBNACAAQwBWAG4AIABzAHkAcwB0AGUAbQBzACAAYQBuAGQAIABwAG8AcwBzAGkAYgBsAHkAIABlAHgAcABsAG8AZABlACAAYQBzACAAdQBuAGQAZQByAGwAdQBtAGkAbgBvAHUAcwAgAHMAdQBwAGUAcgBuAG8AdgBhAGUALgAgAEkAZgAgAHQAaABlACAAYwBvAG0AcABhAG4AaQBvAG4AcwAgAGEAcgBlACAAbgBlAHUAdAByAG8AbgAgAHMAdABhAHIAcwAsACAAdABoAGUAbgAgAHQAaABlAHMAZQAgAG0AYQB5ACAAYgBlACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAIABiAGkAbgBhAHIAaQBlAHMALgAgAFQAaABlAHMAZQAgAGQAaQBzAGMAbwB2AGUAcgBpAGUAcwAgAGkAbgBjAHIAZQBhAHMAZQAgAHQAaABlACAAbgB1AG0AYgBlAHIAIABvAGYAIABkAGUAdABhAGMAaABlAGQALAAgAGQAbwB1AGIAbABlACAAZABlAGcAZQBuAGUAcgBhAHQAZQAgAGIAaQBuAGEAcgBpAGUAcwAgAGkAbgAgAHQAaABlACAARQB4AHQAcgBlAG0AZQBsAHkAIABsAG8AdwAgAG0AYQBzAHMAIAAoAEUATABNACkAIABTAHUAcgB2AGUAeQAgAHQAbwAgADUANAA7ACAAMwAxACAAbwBmACAAdABoAGUAcwBlACAAYgBpAG4AYQByAGkAZQBzACAAdwBpAGwAbAAgAG0AZQByAGcAZQAgAHcAaQB0AGgAaQBuACAAYQAgAEgAdQBiAGIAbABlACAAdABpAG0AZQAuAAAANmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzc2OS82NgAAAEIAQgByAG8AdwBuACAAVwAuAFIALgA7ACAASwBpAGwAaQBjACAATQAuADsAIABBAGwAbABlAG4AZABlACAAUAByAGkAZQB0AG8AIABDAC4AOwAgAEcAaQBhAG4AbgBpAG4AYQBzACAAQQAuADsAIABLAGUAbgB5AG8AbgAgAFMALgBKAC4AAAATMjAxNS0wMS0xM1QwNzo1ODo1MQAAABMyMDIyLTEwLTI0VDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADMAQQBwAEoALgAuAC4ANwA2ADkALgAuAC4ANgA2AEJ/wAAAAAAAB29wdGljYWwAAAEgaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovNzY5LzY2L3dkYmluPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BcEovNzY5LzY2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BcEovNzY5LzY2AAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADNzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACgQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BcEovNzY5LzY2L3dkYmluIChXaGl0ZSBkd2FyZiBwaHlzaWNhbCBwYXJhbWV0ZXJzIGFuZCBiaW5hcnkgb3JiaXRhbCBwYXJhbWV0ZXJzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4xNzY5MDA2NgAAAB5pdm86Ly9jZHMudml6aWVyL2ovYXBqLzg2MS8xMTMAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAANSi9BcEovODYxLzExMwAAACwAVgBMAEIAQQAgAGEAcwB0AHIAbwBtAGUAdAByAHkAIABjAG8AbQBiAGkAbgBlAGQAIAB3AGkAdABoACAARwBhAGkAYQAgAEQAUgAxACAAZQBwAG8AYwBoAAAACHJlc2VhcmNoAAAFdQBUAGgAZQAgAGMAYQBuAG8AbgBpAGMAYQBsACAAbQBlAHQAaABvAGQAcwAgAGYAbwByACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAIABkAGUAdABlAGMAdABpAG8AbgAgAGEAcgBlACAAZwByAG8AdQBuAGQALQAgAGEAbgBkACAAcwBwAGEAYwBlAC0AIABiAGEAcwBlAGQAIABsAGEAcwBlAHIAIABpAG4AdABlAHIAZgBlAHIAbwBtAGUAdAByAHkALAAgAHAAdQBsAHMAYQByACAAdABpAG0AaQBuAGcALAAgAGEAbgBkACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAGMAbwBzAG0AaQBjACAAbQBpAGMAcgBvAHcAYQB2AGUAIABiAGEAYwBrAGcAcgBvAHUAbgBkAC4AIABCAHUAdAAgAGEAcwAgAGgAYQBzACAAYgBlAGUAbgAgAHMAdQBnAGcAZQBzAHQAZQBkACAAYgB5ACAAbgB1AG0AZQByAG8AdQBzACAAaQBuAHYAZQBzAHQAaQBnAGEAdABvAHIAcwAsACAAYQBzAHQAcgBvAG0AZQB0AHIAeQAgAG8AZgBmAGUAcgBzACAAYQBuACAAYQBkAGQAaQB0AGkAbwBuAGEAbAAgAHAAYQB0AGgAIAB0AG8AIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQAgAGQAZQB0AGUAYwB0AGkAbwBuAC4AIABHAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQBzACAAZABlAGYAbABlAGMAdAAgAGwAaQBnAGgAdAAgAHIAYQB5AHMAIABvAGYAIABlAHgAdAByAGEAZwBhAGwAYQBjAHQAaQBjACAAbwBiAGoAZQBjAHQAcwAsACAAYwByAGUAYQB0AGkAbgBnACAAYQBwAHAAYQByAGUAbgB0ACAAcAByAG8AcABlAHIAIABtAG8AdABpAG8AbgBzACAAaQBuACAAYQAgAHEAdQBhAGQAcgB1AHAAbwBsAGEAcgAgACgAYQBuAGQAIABoAGkAZwBoAGUAcgAtAG8AcgBkAGUAcgAgAG0AbwBkAGUAcwApACAAcABhAHQAdABlAHIAbgAuACAAQQBzAHQAcgBvAG0AZQB0AHIAeQAgAG8AZgAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIAByAGEAZABpAG8AIABzAG8AdQByAGMAZQBzACAAaQBzACAAcwBlAG4AcwBpAHQAaQB2AGUAIAB0AG8AIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQBzACAAdwBpAHQAaAAgAGYAcgBlAHEAdQBlAG4AYwBpAGUAcwAgAGIAZQB0AHcAZQBlAG4AIAByAG8AdQBnAGgAbAB5ACAAMQAwAF4ALQAxADgAXgAgAGEAbgBkACAAMQAwAF4ALQA4AF4ASAB6ACAAKABIAF8AMABfACAAYQBuAGQAIAAxAC8AMwB5AHIAXgAtADEAXgApACwAIABvAHYAZQByAGwAYQBwAHAAaQBuAGcAIABhAG4AZAAgAGIAcgBpAGQAZwBpAG4AZwAgAHQAaABlACAAcAB1AGwAcwBhAHIAIAB0AGkAbQBpAG4AZwAgAGEAbgBkACAAQwBNAEIAIABwAG8AbABhAHIAaQB6AGEAdABpAG8AbgAgAHIAZQBnAGkAbQBlAHMALgAgAFcAZQAgAHAAcgBlAHMAZQBuAHQAIABhACAAbQBlAHQAaABvAGQAbwBsAG8AZwB5ACAAZgBvAHIAIABhAHMAdAByAG8AbQBlAHQAcgBpAGMAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAaQBuACAAdABoAGUAIABwAHIAZQBzAGUAbgBjAGUAIABvAGYAIABsAGEAcgBnAGUAIABpAG4AdAByAGkAbgBzAGkAYwAgAHUAbgBjAG8AcgByAGUAbABhAHQAZQBkACAAcAByAG8AcABlAHIAIABtAG8AdABpAG8AbgBzACAAKABpAC4AZQAuACwAIAByAGEAZABpAG8AIABqAGUAdABzACkALgAgAFcAZQAgAG8AYgB0AGEAaQBuACAAOQA1ACUAIABjAG8AbgBmAGkAZABlAG4AYwBlACAAbABpAG0AaQB0AHMAIABvAG4AIAB0AGgAZQAgAHMAdABvAGMAaABhAHMAdABpAGMAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQAgAGIAYQBjAGsAZwByAG8AdQBuAGQAIAB1AHMAaQBuAGcAIAA3ADEAMQAgAHIAYQBkAGkAbwAgAHMAbwB1AHIAYwBlAHMALAAgAHsATwBtAGUAZwBhAH0AXwBHAFcAXwA8ADAALgAwADAANgA0ACwAIABhAG4AZAAgAHUAcwBpAG4AZwAgADUAMAA4ACAAcgBhAGQAaQBvACAAcwBvAHUAcgBjAGUAcwAgAGMAbwBtAGIAaQBuAGUAZAAgAHcAaQB0AGgAIAB0AGgAZQAgAGYAaQByAHMAdAAgAEcAYQBpAGEAIABkAGEAdABhACAAcgBlAGwAZQBhAHMAZQA6ACAAewBPAG0AZQBnAGEAfQBfAEcAVwBfADwAMAAuADAAMQAxAC4AIABUAGgAZQBzAGUAIABsAGkAbQBpAHQAcwAgAHAAcgBvAGIAZQAgAGcAcgBhAHYAaQB0AGEAdABpAG8AbgBhAGwAIAB3AGEAdgBlACAAZgByAGUAcQB1AGUAbgBjAGkAZQBzACAANgB4ADEAMABeAC0AMQA4AF4ASAB6ADwAfgBmADwAfgAxAHgAMQAwAF4ALQA5AF4ASAB6AC4AIABVAHMAaQBuAGcAIABhACAAVwBJAFMARQAtAEcAYQBpAGEAIABjAGEAdABhAGwAbwBnACAAbwBmACAANQA2ADcANwAyADEAIABBAEcATgAsACAAdwBlACAAcAByAGUAZABpAGMAdAAgAGEAIABsAGkAbQBpAHQAIABlAHgAcABlAGMAdABlAGQAIABmAHIAbwBtACAARwBhAGkAYQAgAGEAbABvAG4AZQAgAG8AZgAgAHsATwBtAGUAZwBhAH0AXwBHAFcAXwA8ADAALgAwADAAMAA2ACwAIAB3AGgAaQBjAGgAIABpAHMAIABzAGkAZwBuAGkAZgBpAGMAYQBuAHQAbAB5ACAAaABpAGcAaABlAHIAIAB0AGgAYQBuACAAdwBhAHMAIABvAHIAaQBnAGkAbgBhAGwAbAB5ACAAZgBvAHIAZQBjAGEAcwB0AC4AIABJAG4AYwBpAGQAZQBuAHQAYQBsAGwAeQAsACAAdwBlACAAZABlAHQAZQBjAHQAIABhAG4AZAAgAHIAZQBwAG8AcgB0ACAAbwBuACAAMgAyACAAbgBlAHcAIABlAHgAYQBtAHAAbABlAHMAIABvAGYAIABvAHAAdABpAGMAYQBsACAAcwB1AHAAZQByAGwAdQBtAGkAbgBhAGwAIABtAG8AdABpAG8AbgAgAHcAaQB0AGgAIAByAGUAZABzAGgAaQBmAHQAcwAgADAALgAxADMALQAzAC4AOAA5AC4AAAA3aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BcEovODYxLzExMwAAACYARABhAHIAbABpAG4AZwAgAEoALgA7ACAAVAByAHUAZQBiAGUAbgBiAGEAYwBoACAAQQAuAEUALgA7ACAAUABhAGkAbgBlACAASgAuAAAAEzIwMTktMDgtMTJUMTI6MTc6NDAAAAATMjAyMi0xMS0wNFQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQA4AEEAcABKAC4ALgAuADgANgAxAC4ALgAxADEAMwBEf8AAAAAAAA1vcHRpY2FsI3JhZGlvAAABJGh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQXBKLzg2MS8xMTMvdGFibGU3Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BcEovODYxLzExMzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQXBKLzg2MS8xMTMAAABkaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAF52czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAAM3N0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAIxDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0FwSi84NjEvMTEzL3RhYmxlNyAoVkxCQStHYWlhIGFzdHJvbWV0cnkgYW5kIHByb3BlciBtb3Rpb25zKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4xODYxMDExMwAAAB5pdm86Ly9jZHMudml6aWVyL2ovYXBqLzg2NC8xNTAAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAANSi9BcEovODY0LzE1MAAAAC4AQgBsAGEAYwBrACAAaABvAGwAZQBzACAAJgAgAG4AZQB1AHQAcgBvAG4AIABzAHQAYQByAHMAIABpAG4AIABuAGUAYQByAGIAeQAgAGcAYQBsAGEAeABpAGUAcwAAAAhyZXNlYXJjaAAABksATgBlAGEAcgBiAHkAIABnAGEAbABhAHgAeQAgAHMAdQByAHYAZQB5AHMAIABoAGEAdgBlACAAbABvAG4AZwAgAGMAbABhAHMAcwBpAGYAaQBlAGQAIABYAC0AcgBhAHkAIABiAGkAbgBhAHIAaQBlAHMAIAAoAFgAUgBCAHMAKQAgAGIAeQAgAHQAaABlACAAbQBhAHMAcwAgAGMAYQB0AGUAZwBvAHIAeQAgAG8AZgAgAHQAaABlAGkAcgAgAGQAbwBuAG8AcgAgAHMAdABhAHIAcwAgACgAaABpAGcAaAAtAG0AYQBzAHMAIABhAG4AZAAgAGwAbwB3AC0AbQBhAHMAcwApAC4AIABUAGgAZQAgAE4AdQBTAFQAQQBSACAAbwBiAHMAZQByAHYAYQB0AG8AcgB5ACwAIAB3AGgAaQBjAGgAIABwAHIAbwB2AGkAZABlAHMAIABpAG0AYQBnAGkAbgBnACAAZABhAHQAYQAgAGEAdAAgAEUAPgAxADAAawBlAFYALAAgAGgAYQBzACAAZQBuAGEAYgBsAGUAZAAgAHQAaABlACAAYwBsAGEAcwBzAGkAZgBpAGMAYQB0AGkAbwBuACAAbwBmACAAZQB4AHQAcgBhAGcAYQBsAGEAYwB0AGkAYwAgAFgAUgBCAHMAIABiAHkAIAB0AGgAZQBpAHIAIABjAG8AbQBwAGEAYwB0ACAAbwBiAGoAZQBjAHQAIAB0AHkAcABlADoAIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgAgACgATgBTACkAIABvAHIAIABiAGwAYQBjAGsAIABoAG8AbABlACAAKABCAEgAKQAuACAAVwBlACAAYQBuAGEAbAB5AHoAZQBkACAATgB1AFMAVABBAFIALwBDAGgAYQBuAGQAcgBhAC8AWABNAE0ALQBOAGUAdwB0AG8AbgAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAZgByAG8AbQAgAGEAIABOAHUAUwBUAEEAUgAtAHMAZQBsAGUAYwB0AGUAZAAgAHMAYQBtAHAAbABlACAAbwBmACAAMQAyACAAZwBhAGwAYQB4AGkAZQBzACAAdwBpAHQAaABpAG4AIAA1ACAATQBwAGMAIABoAGEAdgBpAG4AZwAgAHMAdABlAGwAbABhAHIAIABtAGEAcwBzAGUAcwAgACgATQBfACoAXwApACAAMQAwAF4ANwAtADEAMQBeAE0AXwB7AHMAdQBuAH0AXwAgAGEAbgBkACAAcwB0AGEAcgAgAGYAbwByAG0AYQB0AGkAbwBuACAAcgBhAHQAZQBzACAAKABTAEYAUgBzACkAfgAwAC4AMAAxAC0AMQA1AE0AXwB7AHMAdQBuAH0AXwAvAHkAcgAuACAAVwBlACAAZABlAHQAZQBjAHQAZQBkACAAMQAyADgAIABOAHUAUwBUAEEAUgAgAHMAbwB1AHIAYwBlAHMAIAB0AG8AIABhACAAcwBlAG4AcwBpAHQAaQB2AGkAdAB5ACAAbwBmACAAfgAxADAAXgAzADgAXgBlAHIAZwAvAHMALgAgAFUAcwBpAG4AZwAgAE4AdQBTAFQAQQBSACAAYwBvAGwAbwByAC0AaQBuAHQAZQBuAHMAaQB0AHkAIABhAG4AZAAgAGMAbwBsAG8AcgAtAGMAbwBsAG8AcgAgAGQAaQBhAGcAcgBhAG0AcwAgAHcAZQAgAGMAbABhAHMAcwBpAGYAaQBlAGQAIAA0ADMAIABvAGYAIAB0AGgAZQBzAGUAIABzAG8AdQByAGMAZQBzACAAYQBzACAAYwBhAG4AZABpAGQAYQB0AGUAIABOAFMAcwAgAGEAbgBkACAANAA3ACAAYQBzACAAYwBhAG4AZABpAGQAYQB0AGUAIABCAEgAcwAuACAAVwBlACAAZgB1AHIAdABoAGUAcgAgAHMAdQBiAGQAaQB2AGkAZABlACAAQgBIAHMAIABiAHkAIABhAGMAYwByAGUAdABpAG8AbgAgAHMAdABhAHQAZQBzACAAKABzAG8AZgB0ACwAIABpAG4AdABlAHIAbQBlAGQAaQBhAHQAZQAsACAAYQBuAGQAIABoAGEAcgBkACkAIABhAG4AZAAgAE4AUwBzACAAYgB5ACAAdwBlAGEAawAgACgAWgAvAEEAdABvAGwAbAApACAAYQBuAGQAIABzAHQAcgBvAG4AZwAgACgAYQBjAGMAcgBlAHQAaQBuAGcAIABwAHUAbABzAGEAcgApACAAbQBhAGcAbgBlAHQAaQBjACAAZgBpAGUAbABkAC4AIABVAHMAaQBuAGcAIABlAGkAZwBoAHQAIABuAG8AcgBtAGEAbAAgACgATQBpAGwAawB5ACAAVwBhAHkALQB0AHkAcABlACkAIABnAGEAbABhAHgAaQBlAHMAIABpAG4AIAB0AGgAZQAgAHMAYQBtAHAAbABlACwAIAB3AGUAIABjAG8AbgBmAGkAcgBtACAAdABoAGUAIAByAGUAbABhAHQAaQBvAG4AIABiAGUAdAB3AGUAZQBuACAAdABoAGUAIABTAEYAUgAgAGEAbgBkACAAZwBhAGwAYQB4AHkAIABYAC0AcgBhAHkAIABwAG8AaQBuAHQAIABzAG8AdQByAGMAZQAgAGwAdQBtAGkAbgBvAHMAaQB0AHkAIABpAG4AIAB0AGgAZQAgADQALQAyADUAIABhAG4AZAAgADEAMgAtADIANQBrAGUAVgAgAGUAbgBlAHIAZwB5ACAAYgBhAG4AZABzAC4AIABXAGUAIABhAGwAcwBvACAAYwBvAG4AcwB0AHIAYQBpAG4AZQBkACAAZwBhAGwAYQB4AHkAIABYAC0AcgBhAHkAIABwAG8AaQBuAHQAIABzAG8AdQByAGMAZQAgAGwAdQBtAGkAbgBvAHMAaQB0AHkAIAB1AHMAaQBuAGcAIAB0AGgAZQAgAHIAZQBsAGEAdABpAG8AbgAgAEwAXwBYAF8APQB7AGEAbABwAGgAYQB9AC4ATQBfACoAXwArAHsAYgBlAHQAYQB9AFMARgBSACwAIABmAGkAbgBkAGkAbgBnACAAYQBnAHIAZQBlAG0AZQBuAHQAIAB3AGkAdABoACAAcAByAGUAdgBpAG8AdQBzACAAdwBvAHIAawAuACAAVABoAGUAIABYAC0AcgBhAHkAIABsAHUAbQBpAG4AbwBzAGkAdAB5ACAAZgB1AG4AYwB0AGkAbwBuACAAKABYAEwARgApACAAbwBmACAAYQBsAGwAIABzAG8AdQByAGMAZQBzACAAaQBuACAAdABoAGUAIAA0AC0AMgA1ACAAYQBuAGQAIAAxADIALQAyADUAawBlAFYAIABlAG4AZQByAGcAeQAgAGIAYQBuAGQAcwAgAG0AYQB0AGMAaABlAHMAIAB0AGgAZQAgAHsAYQBsAHAAaABhAH0APQAxAC4ANgAgAHMAbABvAHAAZQAgAGYAbwByACAAaABpAGcAaAAtAG0AYQBzAHMAIABYAFIAQgBzAC4AIABXAGUAIABmAGkAbgBkACAAdABoAGEAdAAgAE4AUwAgAFgATABGAHMAIABzAHUAZwBnAGUAcwB0ACAAYQAgAGQAZQBjAGwAaQBuAGUAIABiAGUAZwBpAG4AbgBpAG4AZwAgAGEAdAAgAHQAaABlACAARQBkAGQAaQBuAGcAdABvAG4AIABsAGkAbQBpAHQAIABmAG8AcgAgAGEAIAAxAC4ANABNAF8AewBzAHUAbgB9AF8AIABOAFMALAAgAHcAaABlAHIAZQBhAHMAIAB0AGgAZQAgAEIASAAgAGYAcgBhAGMAdABpAG8AbgAgAHMAaABvAHcAcwAgAGEAbgAgAGEAcABwAHIAbwB4AGkAbQBhAHQAZQAgAG0AbwBuAG8AdABvAG4AaQBjACAAaQBuAGMAcgBlAGEAcwBlACAAaQBuACAAdABoAGUAIAA0AC0AMgA1ACAAYQBuAGQAIAAxADIALQAyADUAawBlAFYAIABlAG4AZQByAGcAeQAgAGIAYQBuAGQAcwAuACAAVwBlACAAYwBhAGwAYwB1AGwAYQB0AGUAIAB0AGgAZQAgAG8AdgBlAHIAYQBsAGwAIAByAGEAdABpAG8AIABvAGYAIABCAEgAIAB0AG8AIABOAFMAIAB0AG8AIABiAGUAIAB+ADEAIABmAG8AcgAgADQALQAyADUAawBlAFYAIABhAG4AZAAgAH4AMgAgAGYAbwByACAAMQAyAC0AMgA1AGsAZQBWAC4AAAA3aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BcEovODY0LzE1MAAAAI8AVgB1AGwAaQBjACAATgAuADsAIABIAG8AcgBuAHMAYwBoAGUAbQBlAGkAZQByACAAQQAuAEUALgA7ACAAVwBpAGsAIABEAC4AUgAuADsAIABZAHUAawBpAHQAYQAgAE0ALgA7ACAAWgBlAHoAYQBzACAAQQAuADsAIABQAHQAYQBrACAAQQAuAEYALgA7ACAATABlAGgAbQBlAHIAIABCAC4ARAAuADsAIABBAG4AdABvAG4AaQBvAHUAIABWAC4AOwAgAE0AYQBjAGMAYQByAG8AbgBlACAAVAAuAEoALgA7ACAAVwBpAGwAbABpAGEAbQBzACAAQgAuAEYALgA7ACAARgBvAHIAbgBhAHMAaQBuAGkAIABGAC4ATQAuAAAAEzIwMjMtMTAtMDlUMDg6MDY6MDgAAAATMjAyMy0xMC0wOVQwODoxMTozNgAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQA4AEEAcABKAC4ALgAuADgANgA0AC4ALgAxADUAMABWf8AAAAAAAAV4LXJheQAAAcxodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi84NjQvMTUwL3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi84NjQvMTUwL3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi84NjQvMTUwL3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQXBKLzg2NC8xNTA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi84NjQvMTUwAAAAvGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAlHZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAABXc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAABakNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQXBKLzg2NC8xNTAvdGFibGUzIChOdVNUQVIgcG9pbnQgc291cmNlIHByb3BlcnRpZXMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BcEovODY0LzE1MC90YWJsZTIgKE51U1RBUiwgQ2hhbmRyYSwgYW5kIFhNTS1OZXd0b24gb2JzZXJ2YXRpb25zIG9mIHRoZSBOdVNUQVIgZ2FsYXh5IHNhbXBsZSk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0FwSi84NjQvMTUwL3RhYmxlMSAoR2FsYXh5IHByb3BlcnRpZXMpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAAAAAAB1pdm86Ly9jZHMudml6aWVyL2ovYXBqLzkwNS83NgAAABF2czpjYXRhbG9nc2VydmljZQAAAAxKL0FwSi85MDUvNzYAAAAtADMAcgBkACAASABBAFcAQwAgAGMAYQB0AC4AIABvAGYAIABWAEgARQAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAcwBvAHUAcgBjAGUAcwAgACgAMwBIAFcAQwApAAAACHJlc2VhcmNoAAAD3wBXAGUAIABwAHIAZQBzAGUAbgB0ACAAYQAgAG4AZQB3ACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgAFQAZQBWACAAZwBhAG0AbQBhAC0AcgBhAHkAIABzAG8AdQByAGMAZQBzACAAdQBzAGkAbgBnACAAMQA1ADIAMwAgAGQAYQB5AHMAIABvAGYAIABkAGEAdABhACAAZgByAG8AbQAgAHQAaABlACAASABpAGcAaAAtAEEAbAB0AGkAdAB1AGQAZQAgAFcAYQB0AGUAcgAgAEMAaABlAHIAZQBuAGsAbwB2ACAAKABIAEEAVwBDACkAIABPAGIAcwBlAHIAdgBhAHQAbwByAHkALgAgAFQAaABlACAAYwBhAHQAYQBsAG8AZwAgAHIAZQBwAHIAZQBzAGUAbgB0AHMAIAB0AGgAZQAgAG0AbwBzAHQAIABzAGUAbgBzAGkAdABpAHYAZQAgAHMAdQByAHYAZQB5ACAAbwBmACAAdABoAGUAIABuAG8AcgB0AGgAZQByAG4AIABnAGEAbQBtAGEALQByAGEAeQAgAHMAawB5ACAAYQB0ACAAZQBuAGUAcgBnAGkAZQBzACAAYQBiAG8AdgBlACAAcwBlAHYAZQByAGEAbAAgAFQAZQBWACwAIAB3AGkAdABoACAAdABoAHIAZQBlACAAdABpAG0AZQBzACAAdABoAGUAIABlAHgAcABvAHMAdQByAGUAIABjAG8AbQBwAGEAcgBlAGQAIAB0AG8AIAB0AGgAZQAgAHAAcgBlAHYAaQBvAHUAcwAgAEgAQQBXAEMAIABjAGEAdABhAGwAbwBnACwAIAAyAEgAVwBDAC4AIABXAGUAIAByAGUAcABvAHIAdAAgADYANQAgAHMAbwB1AHIAYwBlAHMAIABkAGUAdABlAGMAdABlAGQAIABhAHQAIAA+AD0ANQB7AHMAaQBnAG0AYQB9ACAAcwBpAGcAbgBpAGYAaQBjAGEAbgBjAGUALAAgAGEAbABvAG4AZwAgAHcAaQB0AGgAIAB0AGgAZQAgAHAAbwBzAGkAdABpAG8AbgBzACAAYQBuAGQAIABzAHAAZQBjAHQAcgBhAGwAIABmAGkAdABzACAAZgBvAHIAIABlAGEAYwBoACAAcwBvAHUAcgBjAGUALgAgAFQAaABlACAAYwBhAHQAYQBsAG8AZwAgAGMAbwBuAHQAYQBpAG4AcwAgAGUAaQBnAGgAdAAgAHMAbwB1AHIAYwBlAHMAIAB0AGgAYQB0ACAAaABhAHYAZQAgAG4AbwAgAGMAbwB1AG4AdABlAHIAcABhAHIAdAAgAGkAbgAgAHQAaABlACAAMgBIAFcAQwAgAGMAYQB0AGEAbABvAGcALAAgAGIAdQB0ACAAYQByAGUAIAB3AGkAdABoAGkAbgAgADEAewBkAGUAZwB9ACAAbwBmACAAcAByAGUAdgBpAG8AdQBzAGwAeQAgAGQAZQB0AGUAYwB0AGUAZAAgAFQAZQBWACAAZQBtAGkAdAB0AGUAcgBzACwAIABhAG4AZAAgADIAMAAgAHMAbwB1AHIAYwBlAHMAIAB0AGgAYQB0ACAAYQByAGUAIABtAG8AcgBlACAAdABoAGEAbgAgADEAewBkAGUAZwB9ACAAYQB3AGEAeQAgAGYAcgBvAG0AIABhAG4AeQAgAHAAcgBlAHYAaQBvAHUAcwBsAHkAIABkAGUAdABlAGMAdABlAGQAIABUAGUAVgAgAHMAbwB1AHIAYwBlAC4AIABPAGYAIAB0AGgAZQBzAGUAIAAyADAAIABuAGUAdwAgAHMAbwB1AHIAYwBlAHMALAAgADEANAAgAGgAYQB2AGUAIABhACAAcABvAHQAZQBuAHQAaQBhAGwAIABjAG8AdQBuAHQAZQByAHAAYQByAHQAIABpAG4AIAB0AGgAZQAgAGYAbwB1AHIAdABoACAARgBlAHIAbQBpACAATABhAHIAZwBlACAAQQByAGUAYQAgAFQAZQBsAGUAcwBjAG8AcABlACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAcwBvAHUAcgBjAGUAcwAuACAAVwBlACAAYQBsAHMAbwAgAGUAeABwAGwAbwByAGUAIABwAG8AdABlAG4AdABpAGEAbAAgAGEAcwBzAG8AYwBpAGEAdABpAG8AbgBzACAAbwBmACAAMwBIAFcAQwAgAHMAbwB1AHIAYwBlAHMAIAB3AGkAdABoACAAcAB1AGwAcwBhAHIAcwAgAGkAbgAgAHQAaABlACAAQQB1AHMAdAByAGEAbABpAGEAIABUAGUAbABlAHMAYwBvAHAAZQAgAE4AYQB0AGkAbwBuAGEAbAAgAEYAYQBjAGkAbABpAHQAeQAgACgAQQBUAE4ARgApACAAcAB1AGwAcwBhAHIAIABjAGEAdABhAGwAbwBnACAAYQBuAGQAIABzAHUAcABlAHIAbgBvAHYAYQAgAHIAZQBtAG4AYQBuAHQAcwAgAGkAbgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcwB1AHAAZQByAG4AbwB2AGEAIAByAGUAbQBuAGEAbgB0ACAAYwBhAHQAYQBsAG8AZwAuAAAANmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzkwNS83NgAABgkAQQBsAGIAZQByAHQAIABBAC4AOwAgAEEAbABmAGEAcgBvACAAUgAuADsAIABBAGwAdgBhAHIAZQB6ACAAQwAuADsAIABBAG4AZwBlAGwAZQBzACAAQwBhAG0AYQBjAGgAbwAgAEoALgBSAC4AOwAgAEEAcgB0AGUAYQBnAGEALQBWAGUAbABhAHoAcQB1AGUAegAgAEoALgBDAC4AOwAgAEEAcgB1AG4AYgBhAGIAdQAgAEsALgBQAC4AOwAgAEEAdgBpAGwAYQAgAFIAbwBqAGEAcwAgAEQALgA7ACAAQQB5AGEAbABhACAAUwBvAGwAYQByAGUAcwAgAEgALgBBAC4AOwAgAEIAYQBnAGgAbQBhAG4AeQBhAG4AIABWAC4AOwAgAEIAZQBsAG0AbwBuAHQALQBNAG8AcgBlAG4AbwAgAEUALgA7ACAAQgBlAG4AWgB2AGkAIABTAC4AWQAuADsAIABCAHIAaQBzAGIAbwBpAHMAIABDAC4AOwAgAEMAYQBiAGEAbABsAGUAcgBvAC0ATQBvAHIAYQAgAEsALgBTAC4AOwAgAEMAYQBwAGkAcwB0AHIAYQBuACAAVAAuADsAIABDAGEAcgByAGEAbQBpAG4AYQBuAGEAIABBAC4AOwAgAEMAYQBzAGEAbgBvAHYAYQAgAFMALgA7ACAAQwBvAHQAdABpACAAVQAuADsAIABDAG8AdQB0AGkAbgBvACAAZABlACAATABlAG8AbgAgAFMALgA7ACAARABlACAAbABhACAARgB1AGUAbgB0AGUAIABFAC4AOwAgAEQAaQBhAHoAIABIAGUAcgBuAGEAbgBkAGUAegAgAFIALgA7ACAARABpAGEAegAtAEMAcgB1AHoAIABMAC4AOwAgAEQAaQBuAGcAdQBzACAAQgAuAEwALgA7ACAARAB1AFYAZQByAG4AbwBpAHMAIABNAC4AQQAuADsAIABEAHUAcgBvAGMAaABlAHIAIABNAC4AOwAgAEQAaQBhAHoALQBWAGUAbABlAHoAIABKAC4AQwAuADsAIABFAGwAbABzAHcAbwByAHQAaAAgAFIALgBXAC4AOwAgAEUAbgBnAGUAbAAgAEsALgA7ACAARQBzAHAAaQBuAG8AegBhACAAQwAuADsAIABGAGEAbgAgAEsALgBMAC4AOwAgAEYAYQBuAGcAIABLAC4AOwAgAEYAZQByAG4AYQBuAGQAZQB6ACAAQQBsAG8AbgBzAG8AIABNAC4AOwAgAEYAbABlAGkAcwBjAGgAaABhAGMAawAgAEgALgA7ACAARgByAGEAaQBqAGEAIABOAC4AOwAgAEcAYQBsAHYAYQBuAC0ARwBhAG0AZQB6ACAAQQAuADsAIABHAGEAcgBjAGkAYQAgAEQALgA7ACAARwBhAHIAYwBpAGEALQBHAG8AbgB6AGEAbABlAHoAIABKAC4AQQAuADsAIABHAGEAcgBmAGkAYQBzACAARgAuADsAIABHAGkAYQBjAGkAbgB0AGkAIABHAC4AOwAgAEcAbwBuAHoAYQBsAGUAegAgAE0ALgBNAC4AOwAgAEcAbwBvAGQAbQBhAG4AIABKAC4AQQAuADsAIABIAGEAcgBkAGkAbgBnACAASgAuAFAALgA7ACAASABlAHIAbgBhAG4AZABlAHoAIABTAC4AOwAgAEgAaQBuAHQAbwBuACAASgAuADsAIABIAG8AbgBhACAAQgAuADsAIABIAHUAYQBuAGcAIABEAC4AOwAgAEgAdQBlAHkAbwB0AGwALQBaAGEAaAB1AGEAbgB0AGkAdABsAGEAIABGAC4AOwAgAEgAdQBuAHQAZQBtAGUAeQBlAHIAIABQAC4AOwAgAEkAcgBpAGEAcgB0AGUAIABBAC4AOwAgAEoAYQByAGQAaQBuAC0AQgBsAGkAYwBxACAAQQAuADsAIABKAG8AcwBoAGkAIABWAC4AOwAgAEsAaQBlAGQAYQAgAEQALgA7ACAATABhAHIAYQAgAEEALgA7ACAATABlAGUAIABXAC4ASAAuADsAIABMAGUAbwBuACAAVgBhAHIAZwBhAHMAIABIAC4AOwAgAEwAaQBuAG4AZQBtAGEAbgBuACAASgAuAFQALgA7ACAATABvAG4AZwBpAG4AbwB0AHQAaQAgAEEALgBMAC4AOwAgAEwAdQBpAHMALQBSAGEAeQBhACAARwAuADsAIABMAHUAbgBkAGUAZQBuACAASgAuADsAIABMAG8AcABlAHoALQBDAG8AdABvACAAUgAuADsAIABNAGEAbABvAG4AZQAgAEsALgA7ACAATQBhAHIAYQBuAGQAbwBuACAAVgAuADsAIABNAGEAcgB0AGkAbgBlAHoAIABPAC4AOwAgAE0AYQByAHQAaQBuAGUAegAtAEMAYQBzAHQAZQBsAGwAYQBuAG8AcwAgAEkALgA7ACAATQBhAHIAdABpAG4AZQB6AC0AQwBhAHMAdAByAG8AIABKAC4AOwAgAE0AYQB0AHQAaABlAHcAcwAgAEoALgBBAC4AOwAgAE0AaQByAGEAbgBkAGEALQBSAG8AbQBhAGcAbgBvAGwAaQAgAFAALgA7ACAATQBvAHIAYQBsAGUAcwAtAFMAbwB0AG8AIABKAC4AQQAuADsAIABNAG8AcgBlAG4AbwAgAEUALgA7ACAATQBvAHMAdABhAGYAYQAgAE0ALgA7ACAATgBhAHkAZQByAGgAbwBkAGEAIABBAC4AOwAgAE4AZQBsAGwAZQBuACAATAAuADsAIABOAGUAdwBiAG8AbABkACAATQAuADsAIABOAGkAcwBhACAATQAuAFUALgA7ACAATgBvAHIAaQBlAGcAYQAtAFAAYQBwAGEAcQB1AGkAIABSAC4AOwAgAE8AbABpAHYAZQByAGEALQBOAGkAZQB0AG8AIABMAC4AOwAgAE8AbQBvAGQAZQBpACAATgAuADsAIABQAGUAaQBzAGsAZQByACAAQQAuADsAIABQAGUAcgBlAHoAIABBAHIAYQB1AGoAbwAgAFkALgA7ACAAUABlAHIAZQB6AC0AUABlAHIAZQB6ACAARQAuAEcALgA7ACAAUgBlAG4AIABaAC4AOwAgAFIAaABvACAAQwAuAEQALgA7ACAAUgBpAHYAaQBlAHIAZQAgAEMALgA7ACAAUgBvAHMAYQAtAEcAbwBuAHoAYQBsAGUAegAgAEQALgA7ACAAUgB1AGkAegAtAFYAZQBsAGEAcwBjAG8AIABFAC4AOwAgAFMAYQBsAGEAegBhAHIAIABIAC4AOwAgAFMAYQBsAGUAcwBhACAARwByAGUAdQBzACAARgAuAFMALgA7ACAAUwBhAG4AZABvAHYAYQBsACAAQQAuADsAIABTAGMAaABuAGUAaQBkAGUAcgAgAE0ALgA7ACAAUwBjAGgAbwBvAHIAbABlAG0AbQBlAHIAIABIAC4AOwAgAFMAZQByAG4AYQAgAEYALgA7ACAAUwBpAG4AbgBpAHMAIABHAC4AOwAgAFMAbQBpAHQAaAAgAEEALgBKAC4AOwAgAFMAcAByAGkAbgBnAGUAcgAgAFIALgBXAC4AOwAgAFMAdQByAGEAagBiAGEAbABpACAAUAAuADsAIABUAG8AbABsAGUAZgBzAG8AbgAgAEsALgA7ACAAVABvAHIAcgBlAHMAIABJAC4AOwAgAFQAbwByAHIAZQBzAC0ARQBzAGMAbwBiAGUAZABvACAAUgAuADsAIABVAGsAdwBhAHQAdABhACAAVAAuAE4ALgA7ACAAVQByAGUAbgBhAC0ATQBlAG4AYQAgAEYALgA7ACAAVwBlAGkAcwBnAGEAcgBiAGUAcgAgAFQALgA7ACAAVwBlAHIAbgBlAHIAIABGAC4AOwAgAFcAaQBsAGwAbwB4ACAARQAuADsAIABaAGUAcABlAGQAYQAgAEEALgA7ACAAWgBoAG8AdQAgAEgALgA7ACAAZABlACAATABlAG8AbgAgAEMALgA7ACAAQQBsAHYAYQByAGUAegAgAEoALgBEAC4AOwAgAFQAaABlACAASABBAFcAQwAgAEMAbwBsAGwAYQBiAG8AcgBhAHQAaQBvAG4AAAATMjAyMi0wOC0wNFQxMTo1NToxNAAAABMyMDIzLTA2LTA4VDExOjUzOjU2AAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADAAQQBwAEoALgAuAC4AOQAwADUALgAuAC4ANwA2AEF/wAAAAAAACWdhbW1hLXJheQAAAchodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi85MDUvNzYvdGFibGU0Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQXBKLzkwNS83Ni90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTA1Lzc2L3NvdXJjZXM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi85MDUvNzY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi85MDUvNzYAAAC8aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACUdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAFdzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAHNQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BcEovOTA1Lzc2L3RhYmxlNCAoSEFXQyBzb3VyY2VzIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgVGVWIGhhbG8gY2FuZGlkYXRlIHB1bHNhcnMgd2l0aGluIDFkZWcpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9BcEovOTA1Lzc2L3RhYmxlMyAoTmV3IEhBV0Mgc291cmNlcyB3aXRoIG5vIFRlViBjb3VudGVycGFydCk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0FwSi85MDUvNzYvc291cmNlcyAoU291cmNlIGxpc3QgYW5kIG5lYXJlc3QgVGVWQ2F0IHNvdXJjZXMgKFRhYmxlIDEpIGFuZCByYWRpdXMsIGJlc3QtZml0IHNwZWN0cnVtLCBhbmQgZW5lcmd5IHJhbmdlIChUYWJsZSAyKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTkwNTAwNzYAAAAeaXZvOi8vY2RzLnZpemllci9qL2Fwai85MTQvMTIxAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAADUovQXBKLzkxNC8xMjEAAAAlAEwAaQBtAGkAdABzACAAbwBuACAAUwBNAEIASAAgAGIAaQBuAGEAcgBpAGUAcwAgAGYAcgBvAG0AIABOAEEATgBPAEcAcgBhAHYAAAAIcmVzZWFyY2gAAAajAFMAdQBwAGUAcgBtAGEAcwBzAGkAdgBlACAAYgBsAGEAYwBrACAAaABvAGwAZQAgAGIAaQBuAGEAcgBpAGUAcwAgACgAUwBNAEIASABCAHMAKQAgAHMAaABvAHUAbABkACAAZgBvAHIAbQAgAGYAcgBlAHEAdQBlAG4AdABsAHkAIABpAG4AIABnAGEAbABhAGMAdABpAGMAIABuAHUAYwBsAGUAaQAgAGEAcwAgAGEAIAByAGUAcwB1AGwAdAAgAG8AZgAgAGcAYQBsAGEAeAB5ACAAbQBlAHIAZwBlAHIAcwAuACAAQQB0ACAAcwB1AGIAcABhAHIAcwBlAGMAIABzAGUAcABhAHIAYQB0AGkAbwBuAHMALAAgAGIAaQBuAGEAcgBpAGUAcwAgAGIAZQBjAG8AbQBlACAAcwB0AHIAbwBuAGcAIABzAG8AdQByAGMAZQBzACAAbwBmACAAbABvAHcALQBmAHIAZQBxAHUAZQBuAGMAeQAgAGcAcgBhAHYAaQB0AGEAdABpAG8AbgBhAGwAIAB3AGEAdgBlAHMAIAAoAEcAVwBzACkALAAgAHQAYQByAGcAZQB0AGUAZAAgAGIAeQAgAFAAdQBsAHMAYQByACAAVABpAG0AaQBuAGcAIABBAHIAcgBhAHkAcwAuACAAVwBlACAAdQBzAGUAZAAgAHIAZQBjAGUAbgB0ACAAdQBwAHAAZQByACAAbABpAG0AaQB0AHMAIABvAG4AIABjAG8AbgB0AGkAbgB1AG8AdQBzACAARwBXAHMAIABmAHIAbwBtACAAdABoAGUAIABOAG8AcgB0AGgAIABBAG0AZQByAGkAYwBhAG4AIABOAGEAbgBvAGgAZQByAHQAegAgAE8AYgBzAGUAcgB2AGEAdABvAHIAeQAgAGYAbwByACAARwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAFcAYQB2AGUAcwAgACgATgBBAE4ATwBHAHIAYQB2ACkAIAAxADEAeQByACAAZABhAHQAYQAgAHMAZQB0ACAAdABvACAAcABsAGEAYwBlACAAYwBvAG4AcwB0AHIAYQBpAG4AdABzACAAbwBuACAAcAB1AHQAYQB0AGkAdgBlACAAUwBNAEIASABCAHMAIABpAG4AIABuAGUAYQByAGIAeQAgAG0AYQBzAHMAaQB2AGUAIABnAGEAbABhAHgAaQBlAHMALgAgAFcAZQAgAGMAbwBtAHAAaQBsAGUAZAAgAGEAIABjAG8AbQBwAHIAZQBoAGUAbgBzAGkAdgBlACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgAH4ANAA0ADAAMAAwACAAZwBhAGwAYQB4AGkAZQBzACAAaQBuACAAdABoAGUAIABsAG8AYwBhAGwAIAB1AG4AaQB2AGUAcgBzAGUAIAAoAHUAcAAgAHQAbwAgAHIAZQBkAHMAaABpAGYAdAAgAH4AMAAuADAANQApACAAYQBuAGQAIABwAG8AcAB1AGwAYQB0AGUAZAAgAHQAaABlAG0AIAB3AGkAdABoACAAaAB5AHAAbwB0AGgAZQB0AGkAYwBhAGwAIABiAGkAbgBhAHIAaQBlAHMALAAgAGEAcwBzAHUAbQBpAG4AZwAgAHQAaABhAHQAIAB0AGgAZQAgAHQAbwB0AGEAbAAgAG0AYQBzAHMAIABvAGYAIAB0AGgAZQAgAGIAaQBuAGEAcgB5ACAAaQBzACAAZQBxAHUAYQBsACAAdABvACAAdABoAGUAIABTAE0AQgBIACAAbQBhAHMAcwAgAGQAZQByAGkAdgBlAGQAIABmAHIAbwBtACAAZwBsAG8AYgBhAGwAIABzAGMAYQBsAGkAbgBnACAAcgBlAGwAYQB0AGkAbwBuAHMALgAgAEEAcwBzAHUAbQBpAG4AZwAgAGMAaQByAGMAdQBsAGEAcgAgAGUAcQB1AGEAbAAtAG0AYQBzAHMAIABiAGkAbgBhAHIAaQBlAHMAIABlAG0AaQB0AHQAaQBuAGcAIABhAHQAIABOAEEATgBPAEcAcgBhAHYAJwBzACAAbQBvAHMAdAAgAHMAZQBuAHMAaQB0AGkAdgBlACAAZgByAGUAcQB1AGUAbgBjAHkAIABvAGYAIAA4AG4ASAB6ACwAIAB3AGUAIABmAG8AdQBuAGQAIAB0AGgAYQB0ACAAMgAxADYAIABnAGEAbABhAHgAaQBlAHMAIABhAHIAZQAgAHcAaQB0AGgAaQBuACAATgBBAE4ATwBHAHIAYQB2ACcAcwAgAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAHYAbwBsAHUAbQBlAC4AIABXAGUAIAByAGEAbgBrAGUAZAAgAHQAaABlACAAcABvAHQAZQBuAHQAaQBhAGwAIABTAE0AQgBIAEIAcwAgAGIAYQBzAGUAZAAgAG8AbgAgAEcAVwAgAGQAZQB0AGUAYwB0AGEAYgBpAGwAaQB0AHkAIABiAHkAIABjAGEAbABjAHUAbABhAHQAaQBuAGcAIAB0AGgAZQAgAHQAbwB0AGEAbAAgAHMAaQBnAG4AYQBsAC0AdABvAC0AbgBvAGkAcwBlACAAcgBhAHQAaQBvACAAcwB1AGMAaAAgAGIAaQBuAGEAcgBpAGUAcwAgAHcAbwB1AGwAZAAgAGkAbgBkAHUAYwBlACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAE4AQQBOAE8ARwByAGEAdgAgAGEAcgByAGEAeQAuACAAVwBlACAAcABsAGEAYwBlAGQAIABjAG8AbgBzAHQAcgBhAGkAbgB0AHMAIABvAG4AIAB0AGgAZQAgAGMAaABpAHIAcAAgAG0AYQBzAHMAIABhAG4AZAAgAG0AYQBzAHMAIAByAGEAdABpAG8AIABvAGYAIAB0AGgAZQAgADIAMQA2ACAAaAB5AHAAbwB0AGgAZQB0AGkAYwBhAGwAIABiAGkAbgBhAHIAaQBlAHMALgAgAEYAbwByACAAMQA5ACAAZwBhAGwAYQB4AGkAZQBzACwAIABvAG4AbAB5ACAAdgBlAHIAeQAgAHUAbgBlAHEAdQBhAGwALQBtAGEAcwBzACAAYgBpAG4AYQByAGkAZQBzACAAYQByAGUAIABhAGwAbABvAHcAZQBkACwAIAB3AGkAdABoACAAdABoAGUAIABtAGEAcwBzACAAbwBmACAAdABoAGUAIABzAGUAYwBvAG4AZABhAHIAeQAgAGwAZQBzAHMAIAB0AGgAYQBuACAAMQAwACUAIAB0AGgAYQB0ACAAbwBmACAAdABoAGUAIABwAHIAaQBtAGEAcgB5ACwAIAByAG8AdQBnAGgAbAB5ACAAYwBvAG0AcABhAHIAYQBiAGwAZQAgAHQAbwAgAGMAbwBuAHMAdAByAGEAaQBuAHQAcwAgAG8AbgAgAGEAbgAgAFMATQBCAEgAQgAgAGkAbgAgAHQAaABlACAATQBpAGwAawB5ACAAVwBhAHkALgAgAEgAbwB3AGUAdgBlAHIALAAgAHcAZQAgAGQAZQBtAG8AbgBzAHQAcgBhAHQAZQBkACAAdABoAGEAdAAgAHQAaABlACAAKAB0AHkAcABpAGMAYQBsAGwAeQAgAGwAYQByAGcAZQApACAAdQBuAGMAZQByAHQAYQBpAG4AdABpAGUAcwAgAGkAbgAgAHQAaABlACAAbQBhAHMAcwAgAG0AZQBhAHMAdQByAGUAbQBlAG4AdABzACAAYwBhAG4AIAB3AGUAYQBrAGUAbgAgAHQAaABlACAAdQBwAHAAZQByACAAbABpAG0AaQB0AHMAIABvAG4AIAB0AGgAZQAgAGMAaABpAHIAcAAgAG0AYQBzAHMALgAgAEEAZABkAGkAdABpAG8AbgBhAGwAbAB5ACwAIAB3AGUAIAB3AGUAcgBlACAAYQBiAGwAZQAgAHQAbwAgAGUAeABjAGwAdQBkAGUAIABiAGkAbgBhAHIAaQBlAHMAIABkAGUAbABpAHYAZQByAGUAZAAgAGIAeQAgAG0AYQBqAG8AcgAgAG0AZQByAGcAZQByAHMAIAAoAG0AYQBzAHMAIAByAGEAdABpAG8AIABvAGYAIABhAHQAIABsAGUAYQBzAHQAIAAxAC8ANAApACAAZgBvAHIAIABzAGUAdgBlAHIAYQBsACAAbwBmACAAdABoAGUAcwBlACAAZwBhAGwAYQB4AGkAZQBzAC4AIABXAGUAIABhAGwAcwBvACAAZABlAHIAaQB2AGUAZAAgAHQAaABlACAAZgBpAHIAcwB0ACAAbABpAG0AaQB0ACAAbwBuACAAdABoAGUAIABkAGUAbgBzAGkAdAB5ACAAbwBmACAAYgBpAG4AYQByAGkAZQBzACAAZABlAGwAaQB2AGUAcgBlAGQAIABiAHkAIABtAGEAagBvAHIAIABtAGUAcgBnAGUAcgBzACAAcAB1AHIAZQBsAHkAIABiAGEAcwBlAGQAIABvAG4AIABHAFcAIABkAGEAdABhAC4AAAA3aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BcEovOTE0LzEyMQAAAvcAQQByAHoAbwB1AG0AYQBuAGkAYQBuACAAWgAuADsAIABCAGEAawBlAHIAIABQAC4AVAAuADsAIABCAHIAYQB6AGkAZQByACAAQQAuADsAIABCAHIAbwBvAGsAIABQAC4AUgAuADsAIABCAHUAcgBrAGUALQBTAHAAbwBsAGEAbwByACAAUwAuADsAIABCAGUAYwBzAHkAIABCAC4AOwAgAEMAaABhAHIAaQBzAGkAIABNAC4AOwAgAEMAaABhAHQAdABlAHIAagBlAGUAIABTAC4AOwAgAEMAbwByAGQAZQBzACAASgAuAE0ALgA7ACAAQwBvAHIAbgBpAHMAaAAgAE4ALgBKAC4AOwAgAEMAcgBhAHcAZgBvAHIAZAAgAEYALgA7ACAAQwByAG8AbQBhAHIAdABpAGUAIABIAC4AVAAuADsAIABEAGUAQwBlAHMAYQByACAATQAuAEUALgA7ACAARABlAG0AbwByAGUAcwB0ACAAUAAuAEIALgA7ACAARABvAGwAYwBoACAAVAAuADsAIABFAGwAbABpAG8AdAB0ACAAUgAuAEQALgA7ACAARQBsAGwAaQBzACAASgAuAEEALgA7ACAARgBlAHIAcgBhAHIAYQAgAEUALgBDAC4AOwAgAEYAbwBuAHMAZQBjAGEAIABFAC4AOwAgAEcAYQByAHYAZQByAC0ARABhAG4AaQBlAGwAcwAgAE4ALgA7ACAARwBlAG4AdABpAGwAZQAgAFAALgBBAC4AOwAgAEcAbwBvAGQAIABEAC4AQwAuADsAIABIAGEAegBiAG8AdQBuACAASgAuAFMALgA7ACAASQBzAGwAbwAgAEsALgA7ACAASgBlAG4AbgBpAG4AZwBzACAAUgAuAEoALgA7ACAASgBvAG4AZQBzACAATQAuAEwALgA7ACAASwBhAGkAcwBlAHIAIABBAC4AUgAuADsAIABLAGEAcABsAGEAbgAgAEQALgBMAC4AOwAgAEsAZQBsAGwAZQB5ACAATAAuAFoALgA7ACAASwBlAHkAIABKAC4AUwAuADsAIABMAGEAbQAgAE0ALgBUAC4AOwAgAEwAYQB6AGkAbwAgAFQALgBKAC4AVwAuADsAIABMAHUAbwAgAEoALgA7ACAATAB5AG4AYwBoACAAUgAuAFMALgA7ACAATQBhACAAQwAuAC0AUAAuADsAIABNAGEAZABpAHMAbwBuACAARAAuAFIALgA7ACAATQBjAEwAYQB1AGcAaABsAGkAbgAgAE0ALgBBAC4AOwAgAE0AaQBuAGcAYQByAGUAbABsAGkAIABDAC4ATQAuAEYALgA7ACAATgBnACAAQwAuADsAIABOAGkAYwBlACAARAAuAEoALgA7ACAAUABlAG4AbgB1AGMAYwBpACAAVAAuAFQALgA7ACAAUABvAGwAIABOAC4AUwAuADsAIABSAGEAbgBzAG8AbQAgAFMALgBNAC4AOwAgAFIAYQB5ACAAUAAuAFMALgA7ACAAUwBoAGEAcABpAHIAbwAtAEEAbABiAGUAcgB0ACAAQgAuAEoALgA7ACAAUwBpAGUAbQBlAG4AcwAgAFgALgA7ACAAUwBpAG0AbwBuACAASgAuADsAIABTAHAAaQBlAHcAYQBrACAAUgAuADsAIABTAHQAYQBpAHIAcwAgAEkALgBIAC4AOwAgAFMAdABpAG4AZQBiAHIAaQBuAGcAIABEAC4AUgAuADsAIABTAHQAbwB2AGEAbABsACAASwAuADsAIABTAHcAaQBnAGcAdQBtACAASgAuAEsALgA7ACAAVABhAHkAbABvAHIAIABTAC4AUgAuADsAIABWAGEAbABsAGkAcwBuAGUAcgBpACAATQAuADsAIABWAGkAZwBlAGwAYQBuAGQAIABTAC4ASgAuADsAIABXAGkAdAB0ACAAQwAuAEEALgA7ACAAVABoAGUAIABOAEEATgBPAEcAcgBhAHYAIABDAG8AbABsAGEAYgBvAHIAYQB0AGkAbwBuAC4AAAATMjAyMy0wMS0wOVQwOTozNjoyMAAAABMyMDIzLTAxLTE4VDA2OjQzOjE1AAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADEAQQBwAEoALgAuAC4AOQAxADQALgAuADEAMgAxAEF/wAAAAAAACGluZnJhcmVkAAACV2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQXBKLzkxNC8xMjEvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BcEovOTE0LzEyMTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQXBKLzkxNC8xMjE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi85MTQvMTIxL3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQXBKLzkxNC8xMjE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi85MTQvMTIxAAAA12l2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAy3ZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAAdXN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAiNDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL0FwSi85MTQvMTIxL3RhYmxlMiAoMk1SUyBnYWxheHkgY2F0YWxvZyB3aXRoIGVzdGltYXRlcyBvZiB0aGUgc3VwZXJtYXNzaXZlIGJsYWNrIGhvbGUgKFNNQkgpIG1hc3MgYW5kIGRpc3RhbmNlLCBhbG9uZyB3aXRoIHRoZSBHVyBzdHJhaW4gdXBwZXIgbGltaXQgdG93YXJkcyB0aGUgZGlyZWN0aW9uIG9mIGVhY2ggZ2FsYXh5KTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQXBKLzkxNC8xMjEvdGFibGUyICgyTVJTIGdhbGF4eSBjYXRhbG9nIHdpdGggZXN0aW1hdGVzIG9mIHRoZSBzdXBlcm1hc3NpdmUgYmxhY2sgaG9sZSAoU01CSCkgbWFzcyBhbmQgZGlzdGFuY2UsIGFsb25nIHdpdGggdGhlIEdXIHN0cmFpbiB1cHBlciBsaW1pdCB0b3dhcmRzIHRoZSBkaXJlY3Rpb24gb2YgZWFjaCBnYWxheHkpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAIGRvaToxMC4yNjA5My9jZHMvdml6aWVyLjE5MTQwMTIxAAAAHml2bzovL2Nkcy52aXppZXIvai9hcGovOTE2LzEwMAAAABF2czpjYXRhbG9nc2VydmljZQAAAA1KL0FwSi85MTYvMTAwAAAAIwBHAGEAbABhAGMAdABpAGMAIABwAHUAbABzAGEAcgAgAHcAaQB0AGgAIABwAHIAbwBwAGUAcgAgAG0AbwB0AGkAbwBuAHMAAAAIcmVzZWFyY2gAAAX7AFcAZQAgAGUAeABwAGwAbwByAGUAIAB0AGgAZQAgAHAAbwBzAHMAaQBiAGkAbABpAHQAeQAgAG8AZgAgAGkAbgBmAGUAcgByAGkAbgBnACAAdABoAGUAIABwAHIAbwBwAGUAcgB0AGkAZQBzACAAbwBmACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAG8AcAB1AGwAYQB0AGkAbwBuACAAbwBmACAAbgBlAHUAdAByAG8AbgAgAHMAdABhAHIAcwAgAHQAaAByAG8AdQBnAGgAIABtAGEAYwBoAGkAbgBlACAAbABlAGEAcgBuAGkAbgBnAC4AIABJAG4AIABwAGEAcgB0AGkAYwB1AGwAYQByACwAIABpAG4AIAB0AGgAaQBzACAAcABhAHAAZQByACAAdwBlACAAZgBvAGMAdQBzACAAbwBuACAAdABoAGUAaQByACAAZAB5AG4AYQBtAGkAYwBhAGwAIABjAGgAYQByAGEAYwB0AGUAcgBpAHMAdABpAGMAcwAgAGEAbgBkACAAcwBoAG8AdwAgAHQAaABhAHQAIABhAG4AIABhAHIAdABpAGYAaQBjAGkAYQBsACAAbgBlAHUAcgBhAGwAIABuAGUAdAB3AG8AcgBrACAAaQBzACAAYQBiAGwAZQAgAHQAbwAgAGUAcwB0AGkAbQBhAHQAZQAgAHcAaQB0AGgAIABoAGkAZwBoACAAYQBjAGMAdQByAGEAYwB5ACAAdABoAGUAIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAdABoAGEAdAAgAGMAbwBuAHQAcgBvAGwAIAB0AGgAZQAgAGMAdQByAHIAZQBuAHQAIABwAG8AcwBpAHQAaQBvAG4AcwAgAG8AZgAgAGEAIABtAG8AYwBrACAAcABvAHAAdQBsAGEAdABpAG8AbgAgAG8AZgAgAHAAdQBsAHMAYQByAHMALgAgAEYAbwByACAAdABoAGkAcwAgAHAAdQByAHAAbwBzAGUALAAgAHcAZQAgAGkAbQBwAGwAZQBtAGUAbgB0ACAAYQAgAHMAaQBtAHAAbABpAGYAaQBlAGQAIABwAG8AcAB1AGwAYQB0AGkAbwBuAC0AcwB5AG4AdABoAGUAcwBpAHMAIABmAHIAYQBtAGUAdwBvAHIAawAgACgAdwBoAGUAcgBlACAAcwBlAGwAZQBjAHQAaQBvAG4AIABiAGkAYQBzAGUAcwAgAGEAcgBlACAAbgBlAGcAbABlAGMAdABlAGQAIABhAHQAIAB0AGgAaQBzACAAcwB0AGEAZwBlACkAIABhAG4AZAAgAGMAbwBuAGMAZQBuAHQAcgBhAHQAZQAgAG8AbgAgAHQAaABlACAAbgBhAHQAYQBsACAAawBpAGMAawAtAHYAZQBsAG8AYwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAYQBuAGQAIAB0AGgAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAbwBmACAAYgBpAHIAdABoACAAZABpAHMAdABhAG4AYwBlAHMAIABmAHIAbwBtACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUALgAgAEIAeQAgAHYAYQByAHkAaQBuAGcAIAB0AGgAZQBzAGUAIABhAG4AZAAgAGUAdgBvAGwAdgBpAG4AZwAgAHQAaABlACAAcAB1AGwAcwBhAHIAIAB0AHIAYQBqAGUAYwB0AG8AcgBpAGUAcwAgAGkAbgAgAHQAaQBtAGUALAAgAHcAZQAgAGcAZQBuAGUAcgBhAHQAZQAgAGEAIABzAGUAcgBpAGUAcwAgAG8AZgAgAHMAaQBtAHUAbABhAHQAaQBvAG4AcwAgAHQAaABhAHQAIABhAHIAZQAgAHUAcwBlAGQAIAB0AG8AIAB0AHIAYQBpAG4AIABhAG4AZAAgAHYAYQBsAGkAZABhAHQAZQAgAGEAIABzAHUAaQB0AGEAYgBsAHkAIABzAHQAcgB1AGMAdAB1AHIAZQBkACAAYwBvAG4AdgBvAGwAdQB0AGkAbwBuAGEAbAAgAG4AZQB1AHIAYQBsACAAbgBlAHQAdwBvAHIAawAuACAAVwBlACAAZABlAG0AbwBuAHMAdAByAGEAdABlACAAdABoAGEAdAAgAG8AdQByACAAbgBlAHQAdwBvAHIAawAgAGkAcwAgAGEAYgBsAGUAIAB0AG8AIAByAGUAYwBvAHYAZQByACAAdABoAGUAIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAZwBvAHYAZQByAG4AaQBuAGcAIAB0AGgAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAbwBmACAAawBpAGMAawAgAHYAZQBsAG8AYwBpAHQAeQAgAGEAbgBkACAARwBhAGwAYQBjAHQAaQBjACAAaABlAGkAZwBoAHQAIAB3AGkAdABoACAAYQAgAG0AZQBhAG4AIAByAGUAbABhAHQAaQB2AGUAIABlAHIAcgBvAHIAIABvAGYAIABhAGIAbwB1AHQAIAAxADAAXgAtADIAXgAuACAAVwBlACAAZABpAHMAYwB1AHMAcwAgAHQAaABlACAAbABpAG0AaQB0AGEAdABpAG8AbgBzACAAbwBmACAAbwB1AHIAIABpAGQAZQBhAGwAaQB6AGUAZAAgAGEAcABwAHIAbwBhAGMAaAAgAGEAbgBkACAAcwB0AHUAZAB5ACAAYQAgAHQAbwB5ACAAcAByAG8AYgBsAGUAbQAgAHQAbwAgAGkAbgB0AHIAbwBkAHUAYwBlACAAcwBlAGwAZQBjAHQAaQBvAG4AIABlAGYAZgBlAGMAdABzACAAaQBuACAAYQAgAHAAaABlAG4AbwBtAGUAbgBvAGwAbwBnAGkAYwBhAGwAIAB3AGEAeQAgAGIAeQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAaQBuAGcAIAB0AGgAZQAgAG8AYgBzAGUAcgB2AGUAZAAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAgAG8AZgAgADIAMQA2ACAAaQBzAG8AbABhAHQAZQBkACAAcAB1AGwAcwBhAHIAcwAuACAATwB1AHIAIABhAG4AYQBsAHkAcwBpAHMAIABoAGkAZwBoAGwAaQBnAGgAdABzACAAdABoAGEAdAAgAGIAeQAgAGkAbgBjAHIAZQBhAHMAaQBuAGcAIAB0AGgAZQAgAHMAYQBtAHAAbABlACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABhAGMAYwB1AHIAYQB0AGUAIABwAHIAbwBwAGUAcgAtAG0AbwB0AGkAbwBuACAAbQBlAGEAcwB1AHIAZQBtAGUAbgB0AHMAIABiAHkAIABhACAAZgBhAGMAdABvAHIAIABvAGYAIAB+ADEAMAAsACAAbwBuAGUAIABvAGYAIAB0AGgAZQAgAGYAdQB0AHUAcgBlACAAYgByAGUAYQBrAHQAaAByAG8AdQBnAGgAcwAgAG8AZgAgAHQAaABlACAAUwBxAHUAYQByAGUAIABLAGkAbABvAG0AZQB0AHIAZQAgAEEAcgByAGEAeQAsACAAdwBlACAAbQBpAGcAaAB0ACAAcwB1AGMAYwBlAGUAZAAgAGkAbgAgAGMAbwBuAHMAdAByAGEAaQBuAGkAbgBnACAAdABoAGUAIABiAGkAcgB0AGgAIABzAHAAYQB0AGkAYQBsACAAYQBuAGQAIABrAGkAYwBrAC0AdgBlAGwAbwBjAGkAdAB5ACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAG4AZQB1AHQAcgBvAG4AIABzAHQAYQByAHMAIABpAG4AIAB0AGgAZQAgAE0AaQBsAGsAeQAgAFcAYQB5ACAAdwBpAHQAaAAgAGgAaQBnAGgAIABwAHIAZQBjAGkAcwBpAG8AbgAgAHQAaAByAG8AdQBnAGgAIABtAGEAYwBoAGkAbgBlACAAbABlAGEAcgBuAGkAbgBnAC4AAAA3aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9BcEovOTE2LzEwMAAAADkAUgBvAG4AYwBoAGkAIABNAC4AOwAgAEcAcgBhAGIAZQByACAAVgAuADsAIABHAGEAcgBjAGkAYQAtAEcAYQByAGMAaQBhACAAQQAuADsAIABSAGUAYQAgAE4ALgA7ACAAUABvAG4AcwAgAEoALgBBAC4AAAATMjAyMy0wMS0yNlQxNDozMTo0MwAAABMyMDIzLTAyLTE1VDA3OjQ2OjI3AAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADEAQQBwAEoALgAuAC4AOQAxADYALgAuADEAMAAwAFJ/wAAAAAAADW9wdGljYWwjcmFkaW8AAAEkaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTE2LzEwMC90YWJsZTU/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi85MTYvMTAwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BcEovOTE2LzEwMAAAAGRpdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAXnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAAAzc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAtENvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovQXBKLzkxNi8xMDAvdGFibGU1IChVcC10by1kYXRlIGxpc3Qgb2YgNDE3IG5ldXRyb24gc3RhcnMgd2l0aCBtZWFzdXJlZCBwcm9wZXIgbW90aW9ucyBpbiBSQSBhbmQgREVDKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4xOTE2MDEwMAAAACFpdm86Ly9jZHMudml6aWVyL2ovbW5yYXMvMzU5LzE1MjQAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9NTlJBUy8zNTkvMTUyNAAAACsAMQAwACAAbgBlAHcAIABwAHUAbABzAGEAcgBzACAAaQBuACAAQQByAGUAYwBpAGIAbwAgAGQAcgBpAGYAdAAtAHMAYwBhAG4AIABzAHUAcgB2AGUAeQAAAAhyZXNlYXJjaAAAA8kAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABlACAAcgBlAHMAdQBsAHQAcwAgAG8AZgAgAGEAIAA0ADMAMAAtAE0ASAB6ACAAcwB1AHIAdgBlAHkAIABmAG8AcgAgAHAAdQBsAHMAYQByAHMAIABjAG8AbgBkAHUAYwB0AGUAZAAgAGQAdQByAGkAbgBnACAAdABoAGUAIAB1AHAAZwByAGEAZABlACAAdABvACAAdABoAGUAIAAzADAANQAtAG0AIABBAHIAZQBjAGkAYgBvACAAcgBhAGQAaQBvACAAdABlAGwAZQBzAGMAbwBwAGUALgAgAE8AdQByACAAcwB1AHIAdgBlAHkAIABjAG8AdgBlAHIAZQBkACAAYQAgAHQAbwB0AGEAbAAgAG8AZgAgADEAMQA0ADcAZABlAGcAXgAyAF4AIABvAGYAIABzAGsAeQAgAHUAcwBpAG4AZwAgAGEAIABkAHIAaQBmAHQALQBzAGMAYQBuACAAdABlAGMAaABuAGkAcQB1AGUALgAgAFcAZQAgAGQAZQB0AGUAYwB0AGUAZAAgADMAMwAgAHAAdQBsAHMAYQByAHMALAAgADEAMAAgAG8AZgAgAHcAaABpAGMAaAAgAHcAZQByAGUAIABuAG8AdAAgAGsAbgBvAHcAbgAgAHAAcgBpAG8AcgAgAHQAbwAgAHQAaABlACAAcwB1AHIAdgBlAHkAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AcwAuACAAVABoAGUAIABoAGkAZwBoAGwAaQBnAGgAdAAgAG8AZgAgAHQAaABlACAAbgBlAHcAIABkAGkAcwBjAG8AdgBlAHIAaQBlAHMAIABpAHMAIABQAFMAUgAgAEoAMAA0ADAANwArADEANgAwADcALAAgAHcAaABpAGMAaAAgAGgAYQBzACAAYQAgAHMAcABpAG4AIABwAGUAcgBpAG8AZAAgAG8AZgAgADIANQAuADcAbQBzACwAIABhACAAYwBoAGEAcgBhAGMAdABlAHIAaQBzAHQAaQBjACAAYQBnAGUAIABvAGYAIAAxAC4ANQBHAHkAcgAgAGEAbgBkACAAaQBzACAAaQBuACAAYQAgADEALgA4AC0AeQByACAAbwByAGIAaQB0ACAAYQBiAG8AdQB0ACAAYQAgAGwAbwB3AC0AbQBhAHMAcwAgACgAPgAwAC4AMgBNACkAIABjAG8AbQBwAGEAbgBpAG8AbgAuACAAVABoAGUAIABsAG8AbgBnACAAbwByAGIAaQB0AGEAbAAgAHAAZQByAGkAbwBkACAAYQBuAGQAIABzAG0AYQBsAGwAIABlAGMAYwBlAG4AdAByAGkAYwBpAHQAeQAgACgAZQA9ADAALgAwADAAMAA5ACkAIABtAGEAawBlACAAdABoAGUAIABiAGkAbgBhAHIAeQAgAHMAeQBzAHQAZQBtACAAYQBuACAAaQBtAHAAbwByAHQAYQBuAHQAIABuAGUAdwAgAGEAZABkAGkAdABpAG8AbgAgAHQAbwAgAHQAaABlACAAZQBuAHMAZQBtAGIAbABlACAAbwBmACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACAAcwB1AGkAdABhAGIAbABlACAAdABvACAAdABlAHMAdAAgAGYAbwByACAAdgBpAG8AbABhAHQAaQBvAG4AcwAgAG8AZgAgAHQAaABlACAAcwB0AHIAbwBuAGcAIABlAHEAdQBpAHYAYQBsAGUAbgBjAGUAIABwAHIAaQBuAGMAaQBwAGwAZQAuACAAVwBlACAAYQBsAHMAbwAgAHIAZQBwAG8AcgB0ACAAbwBuACAAbwB1AHIAIABpAG4AaQB0AGkAYQBsAGwAeQAgAHUAbgBzAHUAYwBjAGUAcwBzAGYAdQBsACAAYQB0AHQAZQBtAHAAdABzACAAdABvACAAZABlAHQAZQBjAHQAIABvAHAAdABpAGMAYQBsAGwAeQAgAHQAaABlACAAYwBvAG0AcABhAG4AaQBvAG4AIAB0AG8AIABKADAANAAwADcAKwAxADYAMAA3ACwAIAB3AGgAaQBjAGgAIABpAG0AcABsAHkAIAB0AGgAYQB0ACAAaQB0AHMAIABhAGIAcwBvAGwAdQB0AGUAIAB2AGkAcwB1AGEAbAAgAG0AYQBnAG4AaQB0AHUAZABlACAAaQBzACAAPgAxADIALgAxAC4AIABJAGYALAAgAGEAcwAgAGUAeABwAGUAYwB0AGUAZAAgAG8AbgAgAGUAdgBvAGwAdQB0AGkAbwBuAGEAcgB5ACAAZwByAG8AdQBuAGQAcwAsACAAdABoAGUAIABjAG8AbQBwAGEAbgBpAG8AbgAgAGkAcwAgAGEAbgAgAEgAZQAgAHcAaABpAHQAZQAgAGQAdwBhAHIAZgAsACAAbwB1AHIAIABuAG8AbgAtAGQAZQB0AGUAYwB0AGkAbwBuACAAaQBtAHAAbABpAGUAcwAgAGEAIABjAG8AbwBsAGkAbgBnACAAYQBnAGUAIABvAGYAIABsAGUAYQBzAHQAIAAxAEcAeQByAC4AAAA6aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9NTlJBUy8zNTkvMTUyNAAAAJkATABvAHIAaQBtAGUAcgAgAEQALgBSAC4AOwAgAFgAaQBsAG8AdQByAGkAcwAgAEsALgBNAC4AOwAgAEYAcgB1AGMAaAB0AGUAcgAgAEEALgBTAC4AOwAgAFMAdABhAGkAcgBzACAASQAuAEgALgA7ACAAQwBhAG0AaQBsAG8AIABGAC4AOwAgAFYAYQB6AHEAdQBlAHoAIABBAC4ATQAuADsAIABFAGQAZQByACAASgAuAEEALgA7ACAATQBjAEwAYQB1AGcAaABsAGkAbgAgAE0ALgBBAC4AOwAgAFIAbwBiAGUAcgB0AHMAIABNAC4AUwAuAEUALgA7ACAASABlAHMAcwBlAGwAcwAgAEoALgBXAC4AVAAuADsAIAByAGEAbgBzAG8AbQAgAFMALgBNAC4AAAATMjAwNi0wNy0xMFQxMTo0NTozNgAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAwADUATQBOAFIAQQBTAC4AMwA1ADkALgAxADUAMgA0AEx/wAAAAAAABXJhZGlvAAAB22h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvMzU5LzE1MjQvdGFibGUzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvMzU5LzE1MjQvdGFibGU1Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvMzU5LzE1MjQvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy8zNTkvMTUyNDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvMzU5LzE1MjQAAAC8aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACUdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAFdzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAG9Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy8zNTkvMTUyNC90YWJsZTMgKFBhcmFtZXRlcnMgZm9yIHRoZSBuaW5lIGlzb2xhdGVkIHB1bHNhcnMgZGVyaXZlZCBmcm9tIHRoZSB0ZW1wbyB0aW1pbmcgYW5hbHlzaXMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy8zNTkvMTUyNC90YWJsZTUgKE9ic2VydmVkIGFuZCBkZXJpdmVkIHBhcmFtZXRlcnMgZm9yIHRoZSAxMCBwdWxzYXJzIGRpc2NvdmVyZWQgaW4gdGhlIHN1cnZleSk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzM1OS8xNTI0L3RhYmxlMiAoU1RTY0kvTkFJQyBzdXJ2ZXkgZGV0ZWN0aW9uIHN0YXRpc3RpY3MuKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAhaXZvOi8vY2RzLnZpemllci9qL21ucmFzLzQyNy8xMDUyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAAEEovTU5SQVMvNDI3LzEwNTIAAAAhAEgAVABSAFUAIABzAHUAcgB2AGUAeQAuACAAVABpAG0AaQBuAGcAIABvAGYAIAA1ADQAIABwAHUAbABzAGEAcgBzAAAACHJlc2VhcmNoAAAEdQBXAGUAIABwAHIAZQBzAGUAbgB0ACAANwA1ACAAcAB1AGwAcwBhAHIAcwAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABpAG4AIAB0AGgAZQAgAG0AaQBkAC0AbABhAHQAaQB0AHUAZABlACAAcABvAHIAdABpAG8AbgAgAG8AZgAgAHQAaABlACAASABpAGcAaAAgAFQAaQBtAGUAIABSAGUAcwBvAGwAdQB0AGkAbwBuACAAVQBuAGkAdgBlAHIAcwBlACAAcwB1AHIAdgBlAHkALAAgADUANAAgAG8AZgAgAHcAaABpAGMAaAAgAGgAYQB2AGUAIABmAHUAbABsACAAdABpAG0AaQBuAGcAIABzAG8AbAB1AHQAaQBvAG4AcwAuACAAQQBsAGwAIAB0AGgAZQAgAHAAdQBsAHMAYQByAHMAIABoAGEAdgBlACAAcwBwAGkAbgAgAHAAZQByAGkAbwBkAHMAIABnAHIAZQBhAHQAZQByACAAdABoAGEAbgAgADEAMAAwAG0AcwAsACAAYQBuAGQAIABuAG8AbgBlACAAbwBmACAAdABoAG8AcwBlACAAdwBpAHQAaAAgAHQAaQBtAGkAbgBnACAAcwBvAGwAdQB0AGkAbwBuAHMAIABpAHMAIABpAG4AIABiAGkAbgBhAHIAaQBlAHMALgAgAFQAdwBvACAAZABpAHMAcABsAGEAeQAgAHAAYQByAHQAaQBjAHUAbABhAHIAbAB5ACAAaQBuAHQAZQByAGUAcwB0AGkAbgBnACAAYgBlAGgAYQB2AGkAbwB1AHIAOwAgAFAAUwBSACAASgAxADAANQA0AC0ANQA5ADQANAAgAGkAcwAgAGYAbwB1AG4AZAAgAHQAbwAgAGIAZQAgAGEAbgAgAGkAbgB0AGUAcgBtAGkAdAB0AGUAbgB0ACAAcAB1AGwAcwBhAHIALAAgAGEAbgBkACAAUABTAFIAIABKADEAOAAwADkALQAwADEAMQA5ACAAaABhAHMAIABnAGwAaQB0AGMAaABlAGQAIAB0AHcAaQBjAGUAIABzAGkAbgBjAGUAIABpAHQAcwAgAGQAaQBzAGMAbwB2AGUAcgB5AC4AIABJAG4AIAB0AGgAZQAgAHMAZQBjAG8AbgBkACAAaABhAGwAZgAgAG8AZgAgAHQAaABlACAAcABhAHAAZQByACAAdwBlACAAZABpAHMAYwB1AHMAcwAgAHQAaABlACAAZABlAHYAZQBsAG8AcABtAGUAbgB0ACAAYQBuAGQAIABhAHAAcABsAGkAYwBhAHQAaQBvAG4AIABvAGYAIABhAG4AIABhAHIAdABpAGYAaQBjAGkAYQBsACAAbgBlAHUAcgBhAGwAIABuAGUAdAB3AG8AcgBrACAAaQBuACAAdABoAGUAIABkAGEAdABhAC0AcAByAG8AYwBlAHMAcwBpAG4AZwAgAHAAaQBwAGUAbABpAG4AZQAgAGYAbwByACAAdABoAGUAIABzAHUAcgB2AGUAeQAuACAAVwBlACAAZABpAHMAYwB1AHMAcwAgAHQAaABlACAAdABlAHMAdABzACAAdABoAGEAdAAgAHcAZQByAGUAIAB1AHMAZQBkACAAdABvACAAZwBlAG4AZQByAGEAdABlACAAcwBjAG8AcgBlAHMAIABhAG4AZAAgAGYAaQBuAGQAIAB0AGgAYQB0ACAAbwB1AHIAIABuAGUAdQByAGEAbAAgAG4AZQB0AHcAbwByAGsAIAB3AGEAcwAgAGEAYgBsAGUAIAB0AG8AIAByAGUAagBlAGMAdAAgAG8AdgBlAHIAIAA5ADkAcABlAHIAIABjAGUAbgB0ACAAbwBmACAAdABoAGUAIABjAGEAbgBkAGkAZABhAHQAZQBzACAAcAByAG8AZAB1AGMAZQBkACAAaQBuACAAdABoAGUAIABkAGEAdABhACAAcAByAG8AYwBlAHMAcwBpAG4AZwAsACAAYQBuAGQAIABhAGIAbABlACAAdABvACAAYgBsAGkAbgBkAGwAeQAgAGQAZQB0AGUAYwB0ACAAOAA1AHAAZQByACAAYwBlAG4AdAAgAG8AZgAgAHAAdQBsAHMAYQByAHMALgAgAFcAZQAgAHMAdQBnAGcAZQBzAHQAIAB0AGgAYQB0ACAAaQBtAHAAcgBvAHYAZQBtAGUAbgB0AHMAIAB0AG8AIAB0AGgAZQAgAGEAYwBjAHUAcgBhAGMAeQAgAHMAaABvAHUAbABkACAAYgBlACAAcABvAHMAcwBpAGIAbABlACAAaQBmACAAZgB1AHIAdABoAGUAcgAgAGMAYQByAGUAIABpAHMAIAB0AGEAawBlAG4AIAB3AGgAZQBuACAAdAByAGEAaQBuAGkAbgBnACAAYQBuACAAYQByAHQAaQBmAGkAYwBpAGEAbAAgAG4AZQB1AHIAYQBsACAAbgBlAHQAdwBvAHIAawA7ACAAZgBvAHIAIABlAHgAYQBtAHAAbABlACwAIABlAG4AcwB1AHIAaQBuAGcAIAB0AGgAYQB0ACAAYQAgAHIAZQBwAHIAZQBzAGUAbgB0AGEAdABpAHYAZQAgAHMAYQBtAHAAbABlACAAbwBmACAAdABoAGUAIABwAHUAbABzAGEAcgAgAHAAbwBwAHUAbABhAHQAaQBvAG4AIABpAHMAIAB1AHMAZQBkACAAZAB1AHIAaQBuAGcAIAB0AGgAZQAgAHQAcgBhAGkAbgBpAG4AZwAgAHAAcgBvAGMAZQBzAHMALAAgAG8AcgAgAHQAaABlACAAdQBzAGUAIABvAGYAIABkAGkAZgBmAGUAcgBlAG4AdAAgAGEAcgB0AGkAZgBpAGMAaQBhAGwAIABuAGUAdQByAGEAbAAgAG4AZQB0AHcAbwByAGsAcwAgAGYAbwByACAAdABoAGUAIABkAGUAdABlAGMAdABpAG8AbgAgAG8AZgAgAGQAaQBmAGYAZQByAGUAbgB0ACAAdAB5AHAAZQBzACAAbwBmACAAcAB1AGwAcwBhAHIAcwAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNDI3LzEwNTIAAAELAEIAYQB0AGUAcwAgAFMALgBEAC4AOwAgAEIAYQBpAGwAZQBzACAATQAuADsAIABCAGEAcgBzAGQAZQBsAGwAIABCAC4AUgAuADsAIABCAGgAYQB0ACAATgAuAEQALgBSAC4AOwAgAEIAdQByAGcAYQB5ACAATQAuADsAIABCAHUAcgBrAGUALQBTAHAAbwBsAGEAbwByACAAUwAuADsAIABDAGgAYQBtAHAAaQBvAG4AIABEAC4ASgAuADsAIABDAG8AcwB0AGUAcgAgAFAALgA7ACAARAAnAEEAbQBpAGMAbwAgAE4ALgA7ACAASgBhAG0AZQBzAG8AbgAgAEEALgA7ACAASgBvAGgAbgBzAHQAbwBuACAAUwAuADsAIABLAGUAaQB0AGgAIABNAC4ASgAuADsAIABLAHIAYQBtAGUAcgAgAE0ALgA7ACAATABlAHYAaQBuACAATAAuADsAIABMAHkAbgBlACAAQQAuADsAIABNAGkAbABpAGEAIABTAC4AOwAgAE4AZwAgAEMALgA7ACAATgBpAGUAdABuAGUAcgAgAEMALgA7ACAAUABvAHMAcwBlAG4AdABpACAAQQAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AOwAgAFQAaABvAHIAbgB0AG8AbgAgAEQALgA7ACAAVgBhAG4AIABTAHQAcgBhAHQAZQBuACAAVwAuAAAAEzIwMTgtMTItMTJUMTI6NTM6MDUAAAATMjAyMi0xMS0wNFQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQAyAE0ATgBSAEEAUwAuADQAMgA3AC4AMQAwADUAMgBCf8AAAAAAAAVyYWRpbwAAAYZodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQyNy8xMDUyL3RhYmxlMzQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80MjcvMTA1Mi90YWJsZWExPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80MjcvMTA1Mjo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDI3LzEwNTIAAACQaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAeXZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAABFc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAABtkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDI3LzEwNTIvdGFibGUzNCAoT2JzZXJ2YWJsZSBwYXJhbWV0ZXJzIGZvciBlYWNoIG9mIHRoZSBwdWxzYXJzIHdpdGggYSBmdWxsIHRpbWluZyBzb2x1dGlvbiAodGFibGUgMykgYW5kIGRlcml2ZWQgcGFyYW1ldGVycyAodGFibGUgNCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80MjcvMTA1Mi90YWJsZWExIChQYXJhbWV0ZXJzIGFuZCBTL04gcmF0aW9zIGZvciBkZXRlY3Rpb25zIG9mIHByZXZpb3VzbHkta25vd24gcHVsc2FycyBieSB0aGUgcHJvY2Vzc2luZyBwaXBlbGluZSBpbiB0aGUgbWlkLWxhdGl0dWRlIHBvcnRpb24gb2YgdGhlIEhUUlUgc3VydmV5KTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAhaXZvOi8vY2RzLnZpemllci9qL21ucmFzLzQzNi8zNTU3AAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAAEEovTU5SQVMvNDM2LzM1NTcAAAAsAEgAVABSAFUAIABzAHUAcgB2AGUAeQA6ACAAbABvAG4AZwAtAHAAZQByAGkAbwBkACAAcAB1AGwAcwBhAHIAcwAgAHAAbwBsAGEAcgBpAG0AZQB0AHIAeQAAAAhyZXNlYXJjaAAABKkAVwBlACAAcAByAGUAcwBlAG4AdAAgAGEAIABwAG8AbABhAHIAaQBtAGUAdAByAGkAYwAgAGEAbgBhAGwAeQBzAGkAcwAgAG8AZgAgADQAOQAgAGwAbwBuAGcALQBwAGUAcgBpAG8AZAAgAHAAdQBsAHMAYQByAHMAIABkAGkAcwBjAG8AdgBlAHIAZQBkACAAYQBzACAAcABhAHIAdAAgAG8AZgAgAHQAaABlACAASABpAGcAaAAgAFQAaQBtAGUAIABSAGUAcwBvAGwAdQB0AGkAbwBuACAAVQBuAGkAdgBlAHIAcwBlACAAKABIAFQAUgBVACkAIABzAG8AdQB0AGgAZQByAG4AIABzAHUAcgB2AGUAeQAuACAAVABoAGUAIABzAG8AdQByAGMAZQBzACAAZQB4AGgAaQBiAGkAdAAgAHQAaABlACAAdAB5AHAAaQBjAGEAbAAgAGMAaABhAHIAYQBjAHQAZQByAGkAcwB0AGkAYwBzACAAbwBmACAAJwBvAGwAZAAnACAAcAB1AGwAcwBhAHIAcwAsACAAdwBpAHQAaAAgAGwAbwB3ACAAZgByAGEAYwB0AGkAbwBuAGEAbAAgAGwAaQBuAGUAYQByACAAYQBuAGQAIABjAGkAcgBjAHUAbABhAHIAIABwAG8AbABhAHIAaQB6AGEAdABpAG8AbgAgAGEAbgBkACAAbgBhAHIAcgBvAHcALAAgAG0AdQBsAHQAaQAtAGMAbwBtAHAAbwBuAGUAbgB0ACAAcAByAG8AZgBpAGwAZQBzAC4AIABBAGwAdABoAG8AdQBnAGgAIAB0AGgAZQAgAHAAbwBzAGkAdABpAG8AbgAgAGEAbgBnAGwAZQAgAHMAdwBpAG4AZwBzACAAYQByAGUAIABnAGUAbgBlAHIAYQBsAGwAeQAgAGMAbwBtAHAAbABlAHgALAAgAGYAbwByACAAdAB3AG8AIABvAGYAIAB0AGgAZQAgAGEAbgBhAGwAeQBzAGUAZAAgAHAAdQBsAHMAYQByAHMAIAAoAEoAMQA2ADIAMgAtADMANwA1ADEAIABhAG4AZAAgAEoAMQA3ADEAMAAtADIANgAxADYAKQAgAHcAZQAgAG8AYgB0AGEAaQBuAGUAZAAgAGEAbgAgAGkAbgBkAGkAYwBhAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAGcAZQBvAG0AZQB0AHIAeQAgAHYAaQBhACAAdABoAGUAIAByAG8AdABhAHQAaQBuAGcAIAB2AGUAYwB0AG8AcgAgAG0AbwBkAGUAbAAuACAAVwBlACAAdwBlAHIAZQAgAGEAYgBsAGUAIAB0AG8AIABkAGUAdABlAHIAbQBpAG4AZQAgAGEAIAB2AGEAbAB1AGUAIABvAGYAIAB0AGgAZQAgAHIAbwB0AGEAdABpAG8AbgAgAG0AZQBhAHMAdQByAGUAIAAoAFIATQApACAAZgBvAHIAIAAzADQAIABvAGYAIAB0AGgAZQAgAHMAbwB1AHIAYwBlAHMAIAB3AGgAaQBjAGgALAAgAHcAaABlAG4AIABjAG8AbQBiAGkAbgBlAGQAIAB3AGkAdABoACAAdABoAGUAaQByACAAZABpAHMAcABlAHIAcwBpAG8AbgAgAG0AZQBhAHMAdQByAGUAcwAgACgARABNACkALAAgAHkAaQBlAGwAZABzACAAYQBuACAAaQBuAHQAZQBnAHIAYQB0AGUAZAAgAG0AYQBnAG4AZQB0AGkAYwAgAGYAaQBlAGwAZAAgAHMAdAByAGUAbgBnAHQAaAAgAGEAbABvAG4AZwAgAHQAaABlACAAbABpAG4AZQAgAG8AZgAgAHMAaQBnAGgAdAAuACAAVwBpAHQAaAAgAHQAaABlACAAZABhAHQAYQAgAHAAcgBlAHMAZQBuAHQAZQBkACAAaABlAHIAZQAsACAAdABoAGUAIAB0AG8AdABhAGwAIABuAHUAbQBiAGUAcgAgAG8AZgAgAHYAYQBsAHUAZQBzACAAbwBmACAAUgBNACAAYQBzAHMAbwBjAGkAYQB0AGUAZAAgAHcAaQB0AGgAIABwAHUAbABzAGEAcgBzACAAZABpAHMAYwBvAHYAZQByAGUAZAAgAGQAdQByAGkAbgBnACAAdABoAGUAIABIAFQAUgBVACAAcwBvAHUAdABoAGUAcgBuACAAcwB1AHIAdgBlAHkAIABzAHUAbQBzACAAdABvACAANQAxAC4AIABUAGgAZQAgAFIATQBzACAAYQByAGUAIABuAG8AdAAgAGMAbwBuAHMAaQBzAHQAZQBuAHQAIAB3AGkAdABoACAAdABoAGUAIABoAHkAcABvAHQAaABlAHMAaQBzACAAbwBmACAAYQAgAGMAbwB1AG4AdABlAHIALQBjAGwAbwBjAGsAdwBpAHMAZQAgAGQAaQByAGUAYwB0AGkAbwBuACAAbwBmACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABtAGEAZwBuAGUAdABpAGMAIABmAGkAZQBsAGQAIAB3AGkAdABoAGkAbgAgAGEAbgAgAGEAbgBuAHUAbAB1AHMAIABpAG4AYwBsAHUAZABlAGQAIABiAGUAdAB3AGUAZQBuACAANAAgAGEAbgBkACAANgBrAHAAYwAgAGYAcgBvAG0AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAEMAZQBuAHQAcgBlAC4AIABBACAAcABhAHIAdABpAGEAbAAgAGEAZwByAGUAZQBtAGUAbgB0ACAAdwBpAHQAaAAgAGEAIABjAG8AdQBuAHQAZQByAC0AYwBsAG8AYwBrAHcAaQBzAGUAIABzAGUAbgBzAGUAIABvAGYAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAG0AYQBnAG4AZQB0AGkAYwAgAGYAaQBlAGwAZAAgAHcAaQB0AGgAaQBuACAAdABoAGUAIABzAHAAaQByAGEAbAAgAGEAcgBtAHMAIABpAHMALAAgAGgAbwB3AGUAdgBlAHIALAAgAGYAbwB1AG4AZAAgAGkAbgAgAHQAaABlACAAYQByAGUAYQAgAG8AZgAgAHQAaABlACAAQwBhAHIAaQBuAGEALQBTAGEAZwBpAHQAdABhAHIAaQB1AHMAIABhAHIAbQAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNDM2LzM1NTcAAADnAFQAaQBiAHUAcgB6AGkAIABDAC4AOwAgAEoAbwBoAG4AcwB0AG8AbgAgAFMALgA7ACAAQgBhAGkAbABlAHMAIABNAC4AOwAgAEIAYQB0AGUAcwAgAFMALgBEAC4AOwAgAEIAaABhAHQAIABOAC4ARAAuAFIALgA7ACAAQgB1AHIAZwBhAHkAIABNAC4AOwAgAEIAdQByAGsAZQAtAFMAcABvAGwAYQBvAHIAIABTAC4AOwAgAEMAaABhAG0AcABpAG8AbgAgAEQALgA7ACAAQwBvAHMAdABlAHIAIABQAC4AOwAgAEQAJwBBAG0AaQBjAG8AIABOAC4AOwAgAEsAZQBpAHQAaAAgAE0ALgBKAC4AOwAgAEsAcgBhAG0AZQByACAATQAuADsAIABMAGUAdgBpAG4AIABMAC4AOwAgAE0AaQBsAGkAYQAgAFMALgA7ACAATgBnACAAQwAuADsAIABQAG8AcwBzAGUAbgB0AGkAIABBAC4AOwAgAFMAdABhAHAAcABlAHIAcwAgAEIALgBXAC4AOwAgAFQAaABvAHIAbgB0AG8AbgAgAEQALgA7ACAAdgBhAG4AIABTAHQAcgBhAHQAZQBuACAAVwAuAAAAEzIwMTctMTEtMjFUMDk6NDQ6MjkAAAATMjAyMS0xMC0yMVQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQAzAE0ATgBSAEEAUwAuADQAMwA2AC4AMwA1ADUANwBUf8AAAAAAAAVyYWRpbwAAAxdodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQzNi8zNTU3L3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQzNi8zNTU3L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDM2LzM1NTc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQzNi8zNTU3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80MzYvMzU1Ny90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80MzYvMzU1Ny90YWJsZTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQzNi8zNTU3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80MzYvMzU1NwAAAS9pdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAEBdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAACZc3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAACEUNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDM2LzM1NTcvdGFibGUyIChQdWxzYXJzIGZvciB3aGljaCBubyBSTSBjYW4gYmUgZGV0ZXJtaW5lZCk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQzNi8zNTU3L3RhYmxlMSAoUHVsc2FycyBmb3Igd2hpY2ggUk0gY2FuIGJlIGRldGVybWluZWQpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80MzYvMzU1Ny90YWJsZTIgKFB1bHNhcnMgZm9yIHdoaWNoIG5vIFJNIGNhbiBiZSBkZXRlcm1pbmVkKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDM2LzM1NTcvdGFibGUxIChQdWxzYXJzIGZvciB3aGljaCBSTSBjYW4gYmUgZGV0ZXJtaW5lZCk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAIWl2bzovL2Nkcy52aXppZXIvai9tbnJhcy80NTgvMzM0MQAAABF2czpjYXRhbG9nc2VydmljZQAAABBKL01OUkFTLzQ1OC8zMzQxAAAALAA0ADIAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgBzACAAaABpAGcAaAAtAHAAcgBlAGMAaQBzAGkAbwBuACAAdABpAG0AaQBuAGcAAAAIcmVzZWFyY2gAAAZgAFcAZQAgAHIAZQBwAG8AcgB0ACAAbwBuACAAdABoAGUAIABoAGkAZwBoAC0AcAByAGUAYwBpAHMAaQBvAG4AIAB0AGkAbQBpAG4AZwAgAG8AZgAgADQAMgAgAHIAYQBkAGkAbwAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAHMAIAAoAE0AUwBQAHMAKQAgAG8AYgBzAGUAcgB2AGUAZAAgAGIAeQAgAHQAaABlACAARQB1AHIAbwBwAGUAYQBuACAAUAB1AGwAcwBhAHIAIABUAGkAbQBpAG4AZwAgAEEAcgByAGEAeQAgACgARQBQAFQAQQApAC4AIABUAGgAaQBzACAARQBQAFQAQQAgAEQAYQB0AGEAIABSAGUAbABlAGEAcwBlACAAMQAuADAAIABlAHgAdABlAG4AZABzACAAdQBwACAAdABvACAAbQBpAGQALQAyADAAMQA0ACAAYQBuAGQAIABiAGEAcwBlAGwAaQBuAGUAcwAgAHIAYQBuAGcAZQAgAGYAcgBvAG0AIAA3AC0AMQA4AHkAcgAuACAASQB0ACAAZgBvAHIAbQBzACAAdABoAGUAIABiAGEAcwBpAHMAIABmAG8AcgAgAHQAaABlACAAcwB0AG8AYwBoAGEAcwB0AGkAYwAgAGcAcgBhAHYAaQB0AGEAdABpAG8AbgBhAGwALQB3AGEAdgBlACAAYgBhAGMAawBnAHIAbwB1AG4AZAAsACAAYQBuAGkAcwBvAHQAcgBvAHAAaQBjACAAYgBhAGMAawBnAHIAbwB1AG4AZAAsACAAYQBuAGQAIABjAG8AbgB0AGkAbgB1AG8AdQBzAC0AdwBhAHYAZQAgAGwAaQBtAGkAdABzACAAcgBlAGMAZQBuAHQAbAB5ACAAcAByAGUAcwBlAG4AdABlAGQAIABiAHkAIAB0AGgAZQAgAEUAUABUAEEAIABlAGwAcwBlAHcAaABlAHIAZQAuACAAVABoAGUAIABCAGEAeQBlAHMAaQBhAG4AIAB0AGkAbQBpAG4AZwAgAGEAbgBhAGwAeQBzAGkAcwAgAHAAZQByAGYAbwByAG0AZQBkACAAdwBpAHQAaAAgAFQARQBNAFAATwBOAEUAUwBUACAAeQBpAGUAbABkAHMAIAB0AGgAZQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAbwBmACAAcwBlAHYAZQByAGEAbAAgAG4AZQB3ACAAcABhAHIAYQBtAGUAdABlAHIAcwA6ACAAcwBlAHYAZQBuACAAcABhAHIAYQBsAGwAYQB4AGUAcwAsACAAbgBpAG4AZQAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAgAGEAbgBkACwAIABpAG4AIAB0AGgAZQAgAGMAYQBzAGUAIABvAGYAIABzAGkAeAAgAGIAaQBuAGEAcgB5ACAAcAB1AGwAcwBhAHIAcwAsACAAYQBuACAAYQBwAHAAYQByAGUAbgB0ACAAYwBoAGEAbgBnAGUAIABvAGYAIAB0AGgAZQAgAHMAZQBtAGkAbQBhAGoAbwByACAAYQB4AGkAcwAuACAAVwBlACAAZgBpAG4AZAAgAHQAaABlACAATgBFADIAMAAwADEAIABHAGEAbABhAGMAdABpAGMAIABlAGwAZQBjAHQAcgBvAG4AIABkAGUAbgBzAGkAdAB5ACAAbQBvAGQAZQBsACAAdABvACAAYgBlACAAYQAgAGIAZQB0AHQAZQByACAAbQBhAHQAYwBoACAAdABvACAAbwB1AHIAIABwAGEAcgBhAGwAbABhAHgAIABkAGkAcwB0AGEAbgBjAGUAcwAgACgAYQBmAHQAZQByACAAYwBvAHIAcgBlAGMAdABpAG8AbgAgAGYAcgBvAG0AIAB0AGgAZQAgAEwAdQB0AHoALQBLAGUAbABrAGUAcgAgAGIAaQBhAHMAKQAgAHQAaABhAG4AIAB0AGgAZQAgAE0AMgAgAGEAbgBkACAATQAzACAAbQBvAGQAZQBsAHMAIABiAHkAIABTAGMAaABuAGkAdAB6AGUAbABlAHIALgAgAEgAbwB3AGUAdgBlAHIALAAgAHcAZQAgAG0AZQBhAHMAdQByAGUAIABhAG4AIABhAHYAZQByAGEAZwBlACAAdQBuAGMAZQByAHQAYQBpAG4AdAB5ACAAbwBmACAAOAAwACAAcABlAHIAIABjAGUAbgB0ACAAKABmAHIAYQBjAHQAaQBvAG4AYQBsACkAIABmAG8AcgAgAE4ARQAyADAAMAAxACwAIAB0AGgAcgBlAGUAIAB0AGkAbQBlAHMAIABsAGEAcgBnAGUAcgAgAHQAaABhAG4AIAB3AGgAYQB0ACAAaQBzACAAdAB5AHAAaQBjAGEAbABsAHkAIABhAHMAcwB1AG0AZQBkACAAaQBuACAAdABoAGUAIABsAGkAdABlAHIAYQB0AHUAcgBlAC4AIABXAGUAIAByAGUAdgBpAHMAaQB0ACAAdABoAGUAIAB0AHIAYQBuAHMAdgBlAHIAcwBlACAAdgBlAGwAbwBjAGkAdAB5ACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABmAG8AcgAgAGEAIABzAGUAdAAgAG8AZgAgADEAOQAgAGkAcwBvAGwAYQB0AGUAZAAgAGEAbgBkACAANQA3ACAAYgBpAG4AYQByAHkAIABNAFMAUABzACAAYQBuAGQAIABmAGkAbgBkACAAbgBvACAAcwB0AGEAdABpAHMAdABpAGMAYQBsACAAZABpAGYAZgBlAHIAZQBuAGMAZQAgAGIAZQB0AHcAZQBlAG4AIAB0AGgAZQBzAGUAIAB0AHcAbwAgAHAAbwBwAHUAbABhAHQAaQBvAG4AcwAuACAAVwBlACAAZABlAHQAZQBjAHQAIABTAGgAYQBwAGkAcgBvACAAZABlAGwAYQB5ACAAaQBuACAAdABoAGUAIAB0AGkAbQBpAG4AZwAgAHIAZQBzAGkAZAB1AGEAbABzACAAbwBmACAAUABTAFIAcwAgAEoAMQA2ADAAMAAtADMAMAA1ADMAIABhAG4AZAAgAEoAMQA5ADEAOAAtADAANgA0ADIALAAgAGkAbQBwAGwAeQBpAG4AZwAgAHAAdQBsAHMAYQByACAAYQBuAGQAIABjAG8AbQBwAGEAbgBpAG8AbgAgAG0AYQBzAHMAZQBzACAAbQBwAD0AMQAuADIAMgBeACsAMAAuADUAXgBfAC0AMAAuADMANQBfAE0AXwB7AHMAdQBuAH0AXwAsACAAbQBjAD0AMAAuADIAMQBeACsAMAAuADAANgBeAF8ALQAwAC4AMAA0AF8ATQBfAHsAcwB1AG4AfQBfACAAYQBuAGQAIABtAHAAPQAxAC4AMgA1AF4AKwAwAC4ANgBeAF8ALQAwAC4ANABfAE0AXwB7AHMAdQBuAH0AfQAsACAAbQBjAD0AMAAuADIAMwBeACsAMAAuADAANwBeAF8ALQAwAC4AMAA1AF8ATQBfAHsAcwB1AG4AfQBfACwAIAByAGUAcwBwAGUAYwB0AGkAdgBlAGwAeQAuACAARgBpAG4AYQBsAGwAeQAsACAAdwBlACAAdQBzAGUAIAB0AGgAZQAgAG0AZQBhAHMAdQByAGUAbQBlAG4AdAAgAG8AZgAgAHQAaABlACAAbwByAGIAaQB0AGEAbAAgAHAAZQByAGkAbwBkACAAZABlAHIAaQB2AGEAdABpAHYAZQAgAHQAbwAgAHMAZQB0ACAAYQAgAHMAdAByAGkAbgBnAGUAbgB0ACAAYwBvAG4AcwB0AHIAYQBpAG4AdAAgAG8AbgAgAHQAaABlACAAZABpAHMAdABhAG4AYwBlACAAdABvACAAUABTAFIAcwAgAEoAMQAwADEAMgArADUAMwAwADcAIABhAG4AZAAgAEoAMQA5ADAAOQAtADMANwA0ADQALAAgAGEAbgBkACAAcwBlAHQAIABsAGkAbQBpAHQAcwAgAG8AbgAgAHQAaABlACAAbABvAG4AZwBpAHQAdQBkAGUAIABvAGYAIABhAHMAYwBlAG4AZABpAG4AZwAgAG4AbwBkAGUAIAB0AGgAcgBvAHUAZwBoACAAdABoAGUAIABzAGUAYQByAGMAaAAgAG8AZgAgAHQAaABlACAAYQBuAG4AdQBhAGwALQBvAHIAYgBpAHQAYQBsACAAcABhAHIAYQBsAGwAYQB4ACAAZgBvAHIAIABQAFMAUgBzACAASgAxADYAMAAwAC0AMwAwADUAMwAgAGEAbgBkACAASgAxADkAMAA5AC0AMwA3ADQANAAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNDU4LzMzNDEAAAIjAEQAZQBzAHYAaQBnAG4AZQBzACAARwAuADsAIABDAGEAYgBhAGwAbABlAHIAbwAgAFIALgBOAC4AOwAgAEwAZQBuAHQAYQB0AGkAIABMAC4AOwAgAFYAZQByAGIAaQBlAHMAdAAgAEoALgBQAC4AVwAuADsAIABDAGgAYQBtAHAAaQBvAG4AIABEAC4ASgAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABKAGEAbgBzAHMAZQBuACAARwAuAEgALgA7ACAATABhAHoAYQByAHUAcwAgAFAALgA7ACAATwBzAGwAbwB3AHMAawBpACAAUwAuADsAIABCAGEAYgBhAGsAIABTAC4AOwAgAEIAYQBzAHMAYQAgAEMALgBHAC4AOwAgAEIAcgBlAG0AIABQAC4AOwAgAEIAdQByAGcAYQB5ACAATQAuADsAIABDAG8AZwBuAGEAcgBkACAASQAuADsAIABHAGEAaQByACAASgAuAFIALgA7ACAARwByAGEAaQBrAG8AdQAgAEUALgA7ACAARwB1AGkAbABsAGUAbQBvAHQAIABMAC4AOwAgAEgAZQBzAHMAZQBsAHMAIABKAC4AVwAuAFQALgA7ACAASgBlAHMAcwBuAGUAcgAgAEEALgA7ACAASgBvAHIAZABhAG4AIABDAC4AOwAgAEsAYQByAHUAcABwAHUAcwBhAG0AeQAgAFIALgA7ACAASwByAGEAbQBlAHIAIABNAC4AOwAgAEwAYQBzAHMAdQBzACAAQQAuADsAIABMAGEAegBhAHIAaQBkAGkAcwAgAEsALgA7ACAATABlAGUAIABLAC4ASgAuADsAIABMAGkAdQAgAEsALgA7ACAATAB5AG4AZQAgAEEALgBHAC4AOwAgAE0AYwBrAGUAZQAgAEoALgA7ACAATQBpAG4AZwBhAHIAZQBsAGwAaQAgAEMALgBNAC4ARgAuADsAIABQAGUAcgByAG8AZABpAG4AIABEAC4AOwAgAFAAZQB0AGkAdABlAGEAdQAgAEEALgA7ACAAUABvAHMAcwBlAG4AdABpACAAQQAuADsAIABQAHUAcgB2AGUAcgAgAE0ALgBCAC4AOwAgAFIAbwBzAGEAZABvACAAUAAuAEEALgA7ACAAUwBhAG4AaQBkAGEAcwAgAFMALgA7ACAAUwBlAHMAYQBuAGEAIABBAC4AOwAgAFMAaABhAGkAZgB1AGwAbABhAGgAIABHAC4AOwAgAFMAbQBpAHQAcwAgAFIALgA7ACAAVABhAHkAbABvAHIAIABTAC4AUgAuADsAIABUAGgAZQB1AHIAZQBhAHUAIABHAC4AOwAgAFQAaQBiAHUAcgB6AGkAIABDAC4AOwAgAFYAYQBuACAASABhAGEAcwB0AGUAcgBlAG4AIABSAC4AOwAgAFYAZQBjAGMAaABpAG8AIABBAC4AAAATMjAxNy0xMS0xMFQxMzowNTo1NgAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADYATQBOAFIAQQBTAC4ANAA1ADgALgAzADMANAAxAER/wAAAAAAABXJhZGlvAAAInGh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ1OC8zMzQxL3RhYjItMTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ1OC8zMzQxL3RhYjItMTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxAAADFml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAALsdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAb9zdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAPaQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyIChUaW1pbmcgbW9kZWwgcGFyYW1ldGVycyk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ1OC8zMzQxL3RhYjItMTIgKFRpbWluZyBtb2RlbCBwYXJhbWV0ZXJzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMiAoVGltaW5nIG1vZGVsIHBhcmFtZXRlcnMpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyIChUaW1pbmcgbW9kZWwgcGFyYW1ldGVycyk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ1OC8zMzQxL3RhYjItMTIgKFRpbWluZyBtb2RlbCBwYXJhbWV0ZXJzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMiAoVGltaW5nIG1vZGVsIHBhcmFtZXRlcnMpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyIChUaW1pbmcgbW9kZWwgcGFyYW1ldGVycyk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAIGl2bzovL2Nkcy52aXppZXIvai9tbnJhcy80NTkvMTQwAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAAD0ovTU5SQVMvNDU5LzE0MAAAACYATQA4ADEALAAgAEwATQBDACAAYQBuAGQAIAAzAEMAIAAyADcAMwAvAEMAbwBtAGEAIAAxADcALQA2ADAAawBlAFYAIABvAGIAcwAuAAAACHJlc2VhcmNoAAAGFgBXAGUAIABwAHIAZQBzAGUAbgB0ACAAcgBlAHMAdQBsAHQAcwAgAG8AZgAgAGEAIABkAGUAZQBwACAAcwB1AHIAdgBlAHkAIABvAGYAIAB0AGgAcgBlAGUAIABlAHgAdAByAGEAZwBhAGwAYQBjAHQAaQBjACAAZgBpAGUAbABkAHMALAAgAE0AOAAxACAAKABlAHgAcABvAHMAdQByAGUAIABvAGYAIAA5AC4ANwBNAHMAKQAsACAATABhAHIAZwBlACAATQBhAGcAZQBsAGwAYQBuAGkAYwAgAEMAbABvAHUAZAAgACgANgAuADgAIABNAHMAKQAgAGEAbgBkACAAMwBDACAAMgA3ADMALwBDAG8AbQBhACAAKAA5AC4AMwBNAHMAKQAsACAAaQBuACAAdABoAGUAIABoAGEAcgBkACAAWAAtAHIAYQB5ACAAKAAxADcALQA2ADAAawBlAFYAKQAgAGUAbgBlAHIAZwB5ACAAYgBhAG4AZAAgAHcAaQB0AGgAIAB0AGgAZQAgAEkAQgBJAFMAIAB0AGUAbABlAHMAYwBvAHAAZQAgAG8AbgBiAG8AYQByAGQAIAB0AGgAZQAgAEkATgBUAEUARwBSAEEATAAgAG8AYgBzAGUAcgB2AGEAdABvAHIAeQAsACAAYgBhAHMAZQBkACAAbwBuACAAMQAyACAAeQBlAGEAcgBzACAAbwBmACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIAAoADIAMAAwADMALQAyADAAMQA1ACkALgAgAFQAaABlACAAYwBvAG0AYgBpAG4AZQBkACAAcwB1AHIAdgBlAHkAIAByAGUAYQBjAGgAZQBzACAAYQAgADQAewBzAGkAZwBtAGEAfQAgAHAAZQBhAGsAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIABvAGYAIAAwAC4AMQA4AG0AQwByAGEAYgAgACgAMgAuADYAeAAxADAAXgAtADEAMgBeAGUAcgBnAC8AcwAvAGMAbQBeADIAXgApACAAYQBuAGQAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIABiAGUAdAB0AGUAcgAgAHQAaABhAG4AIAAwAC4AMgA1ACAAYQBuAGQAIAAwAC4AOAA3AG0AQwByAGEAYgAgAG8AdgBlAHIAIAAxADAAIABwAGUAcgAgAGMAZQBuAHQAIABhAG4AZAAgADkAMAAgAHAAZQByACAAYwBlAG4AdAAgAG8AZgAgAGkAdABzACAAZgB1AGwAbAAgAGEAcgBlAGEAIABvAGYAIAA0ADkAMAAwACAAZABlAGcAXgAyAF4ALAAgAHIAZQBzAHAAZQBjAHQAaQB2AGUAbAB5AC4AIABXAGUAIABoAGEAdgBlACAAZABlAHQAZQBjAHQAZQBkACAAaQBuACAAdABvAHQAYQBsACAAMQA0ADcAIABzAG8AdQByAGMAZQBzACAAYQB0ACAAUwAvAE4APgA0AHsAcwBpAGcAbQBhAH0ALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAMwA3ACAAcwBvAHUAcgBjAGUAcwAgAG8AYgBzAGUAcgB2AGUAZAAgAGkAbgAgAGgAYQByAGQAIABYAC0AcgBhAHkAcwAgAGYAbwByACAAdABoAGUAIABmAGkAcgBzAHQAIAB0AGkAbQBlAC4AIABUAGgAZQAgAHMAdQByAHYAZQB5ACAAaQBzACAAZABvAG0AaQBuAGEAdABlAGQAIABiAHkAIABlAHgAdAByAGEAZwBhAGwAYQBjAHQAaQBjACAAcwBvAHUAcgBjAGUAcwAsACAAbQBvAHMAdABsAHkAIABhAGMAdABpAHYAZQAgAGcAYQBsAGEAYwB0AGkAYwAgAG4AdQBjAGwAZQBpACAAKABBAEcATgApAC4AIABUAGgAZQAgAHMAYQBtAHAAbABlACAAbwBmACAAaQBkAGUAbgB0AGkAZgBpAGUAZAAgAHMAbwB1AHIAYwBlAHMAIABjAG8AbgB0AGEAaQBuAHMAIAA5ADgAIABBAEcATgAgACgAaQBuAGMAbAB1AGQAaQBuAGcAIAA2ADQAIABTAGUAeQBmAGUAcgB0ACAAZwBhAGwAYQB4AGkAZQBzACwAIABzAGUAdgBlAG4AIABsAG8AdwAtAGkAbwBuAGkAegBhAHQAaQBvAG4AIABuAHUAYwBsAGUAYQByACAAZQBtAGkAcwBzAGkAbwBuAC0AbABpAG4AZQAgAHIAZQBnAGkAbwBuACAAZwBhAGwAYQB4AGkAZQBzACwAIAB0AGgAcgBlAGUAIABYAC0AcgBhAHkAIABiAHIAaQBnAGgAdAAgAG8AcAB0AGkAYwBhAGwAbAB5ACAAbgBvAHIAbQBhAGwAIABnAGEAbABhAHgAaQBlAHMALAAgADEANgAgAGIAbABhAHoAYQByAHMAIABhAG4AZAAgAGUAaQBnAGgAdAAgAEEARwBOACAAbwBmACAAdQBuAGMAbABlAGEAcgAgAG8AcAB0AGkAYwBhAGwAIABjAGwAYQBzAHMAKQAsACAAdAB3AG8AIABnAGEAbABhAHgAeQAgAGMAbAB1AHMAdABlAHIAcwAgACgAQwBvAG0AYQAgAGEAbgBkACAAQQBiAGUAbABsACAAMwAyADYANgApACwAIAAxADcAIABvAGIAagBlAGMAdABzACAAbABvAGMAYQB0AGUAZAAgAGkAbgAgAHQAaABlACAATABhAHIAZwBlACAAYQBuAGQAIABTAG0AYQBsAGwAIABNAGEAZwBlAGwAbABhAG4AaQBjACAAQwBsAG8AdQBkAHMAIAAoADEAMwAgAGgAaQBnAGgALQAgAGEAbgBkACAAdAB3AG8AIABsAG8AdwAtAG0AYQBzAHMAIABYAC0AcgBhAHkAIABiAGkAbgBhAHIAaQBlAHMAIABhAG4AZAAgAHQAdwBvACAAWAAtAHIAYQB5ACAAcAB1AGwAcwBhAHIAcwApACwAIAB0AGgAcgBlAGUAIABHAGEAbABhAGMAdABpAGMAIABjAGEAdABhAGMAbAB5AHMAbQBpAGMAIAB2AGEAcgBpAGEAYgBsAGUAcwAsACAAbwBuAGUAIAB1AGwAdAByAGEAbAB1AG0AaQBuAG8AdQBzACAAWAAtAHIAYQB5ACAAcwBvAHUAcgBjAGUAIAAoAE0AOAAyACAAWAAtADEAKQAgAGEAbgBkACAAbwBuAGUAIABiAGwAZQBuAGQAZQBkACAAcwBvAHUAcgBjAGUAIAAoAFMAVwBJAEYAVAAgAEoAMQAxADAANQAuADcAKwA1ADgANQA0ACkALgAgAFQAaABlACAAbgBhAHQAdQByAGUAIABvAGYAIAAyADUAIABzAG8AdQByAGMAZQBzACAAcgBlAG0AYQBpAG4AcwAgAHUAbgBrAG4AbwB3AG4ALAAgAHMAbwAgAHQAaABhAHQAIAB0AGgAZQAgAHMAdQByAHYAZQB5ACcAcwAgAGkAZABlAG4AdABpAGYAaQBjAGEAdABpAG8AbgAgAGkAcwAgAGMAdQByAHIAZQBuAHQAbAB5ACAAYwBvAG0AcABsAGUAdABlACAAYQB0ACAAOAAzACAAcABlAHIAIABjAGUAbgB0AC4AIABXAGUAIABoAGEAdgBlACAAYwBvAG4AcwB0AHIAdQBjAHQAZQBkACAAQQBHAE4AIABuAHUAbQBiAGUAcgAtAGYAbAB1AHgAIAByAGUAbABhAHQAaQBvAG4AcwAgACgAbABvAGcAIABOAC0AbABvAGcAIABTACkAIABhAG4AZAAgAGMAYQBsAGMAdQBsAGEAdABlAGQAIABBAEcATgAgAG4AdQBtAGIAZQByACAAZABlAG4AcwBpAHQAaQBlAHMAIABpAG4AIAB0AGgAZQAgAGwAbwBjAGEAbAAgAFUAbgBpAHYAZQByAHMAZQAgAGYAbwByACAAdABoAGUAIABlAG4AdABpAHIAZQAg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AHkAIABTAHkAbgB0AGgAZQBzAGkAcwAgAFIAYQBkAGkAbwAgAFQAZQBsAGUAcwBjAG8AcABlACAAKABNAE8AUwBUACkAIABuAGUAYQByACAAQwBhAG4AYgBlAHIAcgBhACwAIABBAHUAcwB0AHIAYQBsAGkAYQAuACAAVwBlACAAYwB1AHIAcgBlAG4AdABsAHkAIABvAGIAcwBlAHIAdgBlACAAbQBvAHIAZQAgAHQAaABhAG4AIAA0ADAAMAAgAG0AYQBpAG4AbAB5ACAAYgByAGkAZwBoAHQAIABzAG8AdQB0AGgAZQByAG4AIAByAGEAZABpAG8AIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaAAgAHUAcAAgAHQAbwAgAGQAYQBpAGwAeQAgAGMAYQBkAGUAbgBjAGUAcwAuACAARgBvAHIAIAAyADAANQAgACgAOAAgAGkAbgAgAGIAaQBuAGEAcgBpAGUAcwAsACAANAAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAHMAKQAsACAAdwBlACAAcAB1AGIAbABpAHMAaAAgAHUAcABkAGEAdABlAGQAIAB0AGkAbQBpAG4AZwAgAG0AbwBkAGUAbABzACwAIAB0AG8AZwBlAHQAaABlAHIAIAB3AGkAdABoACAAdABoAGUAaQByACAAZgBsAHUAeAAgAGQAZQBuAHMAaQB0AGkAZQBzACwAIABmAGwAdQB4ACAAZABlAG4AcwBpAHQAeQAgAHYAYQByAGkAYQBiAGkAbABpAHQAeQAsACAAYQBuAGQAIABwAHUAbABzAGUAIAB3AGkAZAB0AGgAcwAgAGEAdAAgADgANAAzACAATQBIAHoALAAgAGQAZQByAGkAdgBlAGQAIABmAHIAbwBtACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABzAHAAYQBuAG4AaQBuAGcAIABiAGUAdAB3AGUAZQBuACAAMQAuADQAIABhAG4AZAAgADMAIAB5AHIALgAgAEkAbgAgAGMAbwBtAHAAYQByAGkAcwBvAG4AIAB3AGkAdABoACAAdABoAGUAIABBAFQATgBGACAAcAB1AGwAcwBhAHIAIABjAGEAdABhAGwAbwBnAHUAZQAsACAAdwBlACAAaQBtAHAAcgBvAHYAZQAgAHQAaABlACAAcAByAGUAYwBpAHMAaQBvAG4AIABvAGYAIAB0AGgAZQAgAHIAbwB0AGEAdABpAG8AbgBhAGwAIABhAG4AZAAgAGEAcwB0AHIAbwBtAGUAdAByAGkAYwAgAHAAYQByAGEAbQBlAHQAZQByAHMAIABmAG8AcgAgADEAMgAzACAAcAB1AGwAcwBhAHIAcwAsACAAZgBvAHIAIAA0ADcAIABiAHkAIABhAHQAIABsAGUAYQBzAHQAIABhAG4AIABvAHIAZABlAHIAIABvAGYAIABtAGEAZwBuAGkAdAB1AGQAZQAuACAAVABoAGUAIAB0AGkAbQBlACAAcwBwAGEAbgBzACAAYgBlAHQAdwBlAGUAbgAgAG8AdQByACAAbQBlAGEAcwB1AHIAZQBtAGUAbgB0AHMAIABhAG4AZAAgAHQAaABvAHMAZQAgAGkAbgAgAHQAaABlACAAbABpAHQAZQByAGEAdAB1AHIAZQAgAGEAcgBlACAAdQBwACAAdABvACAANAA4ACAAeQByACwAIAB3AGgAaQBjAGgAIABhAGwAbABvAHcAIAB1AHMAIAB0AG8AIABpAG4AdgBlAHMAdABpAGcAYQB0AGUAIAB0AGgAZQBpAHIAIABsAG8AbgBnAC0AdABlAHIAbQAgAHMAcABpAG4ALQBkAG8AdwBuACAAaABpAHMAdABvAHIAeQAgAGEAbgBkACAAdABvACAAZQBzAHQAaQBtAGEAdABlACAAcAByAG8AcABlAHIAIABtAG8AdABpAG8AbgBzACAAZgBvAHIAIAA2ADAAIABwAHUAbABzAGEAcgBzACwAIABvAGYAIAB3AGgAaQBjAGgAIAAyADQAIABhAHIAZQAgAG4AZQB3AGwAeQAgAGQAZQB0AGUAcgBtAGkAbgBlAGQAIABhAG4AZAAgAG0AbwBzAHQAIABhAHIAZQAgAG0AYQBqAG8AcgAgAGkAbQBwAHIAbwB2AGUAbQBlAG4AdABzAC4AIABUAGgAZQAgAHIAZQBzAHUAbAB0AHMAIABhAHIAZQAgAGMAbwBuAHMAaQBzAHQAZQBuAHQAIAB3AGkAdABoACAAaQBuAHQAZQByAGYAZQByAG8AbQBlAHQAcgBpAGMAIABtAGUAYQBzAHUAcgBlAG0AZQBuAHQAcwAgAGYAcgBvAG0AIAB0AGgAZQAgAGwAaQB0AGUAcgBhAHQAdQByAGUALgAgAEEAIABtAG8AZABlAGwAIAB3AGkAdABoACAAdAB3AG8AIABHAGEAdQBzAHMAaQBhAG4AIABjAG8AbQBwAG8AbgBlAG4AdABzACAAYwBlAG4AdAByAGUAZAAgAGEAdAAgADEAMwA5ACAAYQBuAGQAIAA0ADYAMwBrAG0ALwBzACAAZgBpAHQAcwAgAHQAaABlACAAdAByAGEAbgBzAHYAZQByAHMAZQAgAHYAZQBsAG8AYwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAYgBlAHMAdAAuACAAVABoAGUAIABwAHUAbABzAGUAIABkAHUAdAB5ACAAYwB5AGMAbABlACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AcwAgAGEAdAAgADUAMAAgAGEAbgBkACAAMQAwACAAcABlAHIAIABjAGUAbgB0ACAAbQBhAHgAaQBtAHUAbQAgAGEAcgBlACAAYgBlAHMAdAAgAGQAZQBzAGMAcgBpAGIAZQBkACAAYgB5ACAAbABvAGcAbgBvAHIAbQBhAGwAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgBzACAAdwBpAHQAaAAgAG0AZQBkAGkAYQBuAHMAIABvAGYAIAAyAC4AMwAgAGEAbgBkACAANAAuADQAIABwAGUAcgAgAGMAZQBuAHQALAAgAHIAZQBzAHAAZQBjAHQAaQB2AGUAbAB5AC4AIABXAGUAIABkAGkAcwBjAHUAcwBzACAAdAB3AG8AIABwAHUAbABzAGEAcgBzACAAdABoAGEAdAAgAGUAeABoAGkAYgBpAHQAIABzAHAAaQBuAC0AZABvAHcAbgAgAHIAYQB0AGUAIABjAGgAYQBuAGcAZQBzACAAYQBuAGQAIABkAHIAaQBmAHQAaQBuAGcAIABzAHUAYgBwAHUAbABzAGUAcwAuACAARgBpAG4AYQBsAGwAeQAsACAAdwBlACAAZABlAHMAYwByAGkAYgBlACAAdABoAGUAIABhAHUAdABvAG4AbwBtAG8AdQBzACAAbwBiAHMAZQByAHYAaQBuAGcAIABzAHkAcwB0AGUAbQAgAGEAbgBkACAAdABoAGUAIABkAHkAbgBhAG0AaQBjACAAcwBjAGgAZQBkAHUAbABlAHIAIAB0AGgAYQB0ACAAaABhAHMAIABpAG4AYwByAGUAYQBzAGUAZAAgAHQAaABlACAAbwBiAHMAZQByAHYAaQBuAGcAIABlAGYAZgBpAGMAaQBlAG4AYwB5ACAAYgB5ACAAYQAgAGYAYQBjAHQAbwByACAAbwBmACAAMgAtADMAIABpAG4AIABjAG8AbQBwAGEAcgBpAHMAbwBuACAAdwBpAHQAaAAgAHMAdABhAHQAaQBjACAAcwBjAGgAZQBkAHUAbABpAG4AZwAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNDg0LzM2OTEAAAD1AEoAYQBuAGsAbwB3AHMAawBpACAARgAuADsAIABCAGEAaQBsAGUAcwAgAE0ALgA7ACAAVgBhAG4AIABTAHQAcgBhAHQAZQBuACAAVwAuADsAIABLAGUAYQBuAGUAIABFAC4ARgAuADsAIABGAGwAeQBuAG4AIABDAC4AOwAgAEIAYQByAHIAIABFAC4ARAAuADsAIABCAGEAdABlAG0AYQBuACAAVAAuADsAIABCAGgAYQBuAGQAYQByAGkAIABTAC4AOwAgAEMAYQBsAGUAYgAgAE0ALgA7ACAAQwBhAG0AcABiAGUAbABsAC0AVwBpAGwAcwBvAG4AIABEAC4AOwAgAEYAYQByAGEAaAAgAFcALgA7ACAARwByAGUAZQBuACAAQQAuAEoALgA7ACAASAB1AG4AcwB0AGUAYQBkACAAUgAuAFcALgA7ACAASgBhAG0AZQBzAG8AbgAgAEEALgA7ACAATwBzAGwAbwB3AHMAawBpACAAUwAuADsAIABQAGEAcgB0AGgAYQBzAGEAcgBhAHQAaAB5ACAAQQAuADsAIABSAG8AcwBhAGQAbwAgAFAALgBBAC4AOwAgAFYAZQBuAGsAYQB0AHIAYQBtAGEAbgAgAEsAcgBpAHMAaABuAGEAbgAgAFYALgAAABMyMDIwLTAyLTA1VDE0OjQ4OjU1AAAAEzIwMjEtMTAtMjFUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAOQBNAE4AUgBBAFMALgA0ADgANAAuADMANgA5ADEASn/AAAAAAAAFcmFkaW8AAAGGaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80ODQvMzY5MS90YWJsZWIyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDg0LzM2OTEvdGFibGViMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDg0LzM2OTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ4NC8zNjkxAAAAkGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAHl2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAARXN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAQxDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ4NC8zNjkxL3RhYmxlYjIgKFJvdGF0aW9uYWwgcGFyYW1ldGVycyBmb3IgMTk3IGlzb2xhdGVkIHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80ODQvMzY5MS90YWJsZWIxIChSb3RhdGlvbmFsIHBhcmFtZXRlcnMgZm9yIDggYmluYXJ5IHN5c3RlbXMpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAAAAAACFpdm86Ly9jZHMudml6aWVyL2ovbW5yYXMvNDkzLzEwNjMAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9NTlJBUy80OTMvMTA2MwAAACIARABpAHMAYwBvAHYAZQByAHkAIABhAG4AZAAgAHQAaQBtAGkAbgBnACAAbwBmACAANAAwACAAcAB1AGwAcwBhAHIAcwAAAAhyZXNlYXJjaAAABrIAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABlACAAcgBlAHMAdQBsAHQAcwAgAG8AZgAgAHAAcgBvAGMAZQBzAHMAaQBuAGcAIABhAG4AIABhAGQAZABpAHQAaQBvAG4AYQBsACAANAA0ACAAcABlAHIAIABjAGUAbgB0ACAAbwBmACAAdABoAGUAIABIAGkAZwBoACAAVABpAG0AZQAgAFIAZQBzAG8AbAB1AHQAaQBvAG4AIABVAG4AaQB2AGUAcgBzAGUAIABTAG8AdQB0AGgAIABMAG8AdwAgAEwAYQB0AGkAdAB1AGQAZQAgACgASABUAFIAVQAtAFMAIABMAG8AdwBMAGEAdAApACAAcAB1AGwAcwBhAHIAIABzAHUAcgB2AGUAeQAsACAAdABoAGUAIABtAG8AcwB0ACAAcwBlAG4AcwBpAHQAaQB2AGUAIABiAGwAaQBuAGQAIABwAHUAbABzAGEAcgAgAHMAdQByAHYAZQB5ACAAbwBmACAAdABoAGUAIABzAG8AdQB0AGgAZQByAG4AIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAIAB0AG8AIABkAGEAdABlAC4AIABPAHUAcgAgAHAAYQByAHQAaQBhAGwAbAB5ACAAYwBvAGgAZQByAGUAbgB0ACAAcwBlAGcAbQBlAG4AdABlAGQAIABhAGMAYwBlAGwAZQByAGEAdABpAG8AbgAgAHMAZQBhAHIAYwBoACAAcABpAHAAZQBsAGkAbgBlACAAaQBzACAAZABlAHMAaQBnAG4AZQBkACAAdABvACAAZQBuAGEAYgBsAGUAIAB0AGgAZQAgAGQAaQBzAGMAbwB2AGUAcgB5ACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGkAbgAgAHMAaABvAHIAdAAsACAAaABpAGcAaABsAHkAIABhAGMAYwBlAGwAZQByAGEAdABlAGQAIABvAHIAYgBpAHQAcwAsACAAdwBoAGkAbABlACAAbwB1AHIAIAA3ADIALQBtAGkAbgAgAGkAbgB0AGUAZwByAGEAdABpAG8AbgAgAGwAZQBuAGcAdABoAHMAIAB3AGkAbABsACAAYQBsAGwAbwB3ACAAdQBzACAAdABvACAAZABpAHMAYwBvAHYAZQByACAAcAB1AGwAcwBhAHIAcwAgAGEAdAAgAHQAaABlACAAbABvAHcAZQByACAAZQBuAGQAIABvAGYAIAB0AGgAZQAgAHAAdQBsAHMAYQByACAAbAB1AG0AaQBuAG8AcwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuAC4AIABXAGUAIAByAGUAcABvAHIAdAAgAHQAaABlACAAZABpAHMAYwBvAHYAZQByAHkAIABvAGYAIAA0ADAAIABwAHUAbABzAGEAcgBzACwAIABpAG4AYwBsAHUAZABpAG4AZwAgAHQAaAByAGUAZQAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAC0AdwBoAGkAdABlACAAZAB3AGEAcgBmACAAYgBpAG4AYQByAHkAIABzAHkAcwB0AGUAbQBzACAAKABQAFMAUgBzACAASgAxADUAMwA3AC0ANQAzADEAMgAsACAASgAxADUANAA3AC0ANQA3ADAAOQAsACAAYQBuAGQAIABKADEANgAxADgALQA0ADYAMgA0ACkALAAgAGEAIABiAGwAYQBjAGsALQB3AGkAZABvAHcAIABiAGkAbgBhAHIAeQAgAHMAeQBzAHQAZQBtACAAKABQAFMAUgAgAEoAMQA3ADQANQAtADIAMwApACAAYQBuAGQAIABhACAAYwBhAG4AZABpAGQAYQB0AGUAIABiAGwAYQBjAGsALQB3AGkAZABvAHcAIABiAGkAbgBhAHIAeQAgAHMAeQBzAHQAZQBtACAAKABQAFMAUgAgAEoAMQA3ADIANwAtADIAOQA1ADEAKQAsACAAYQAgAGcAbABpAHQAYwBoAGkAbgBnACAAcAB1AGwAcwBhAHIAIAAoAFAAUwBSACAASgAxADcAMAA2AC0ANAA0ADMANAApACwAIABhAG4AIABlAGMAbABpAHAAcwBpAG4AZwAgAGIAaQBuAGEAcgB5ACAAcAB1AGwAcwBhAHIAIAB3AGkAdABoACAAYQAgADEALgA1AC0AeQByACAAbwByAGIAaQB0AGEAbAAgAHAAZQByAGkAbwBkACAAKABQAFMAUgAgAEoAMQA2ADUAMwAtADQANQApACwAIABhAG4AZAAgAGEAIABwAGEAaQByACAAbwBmACAAbABvAG4AZwAgAHMAcABpAG4ALQBwAGUAcgBpAG8AZAAgAGIAaQBuAGEAcgB5ACAAcAB1AGwAcwBhAHIAcwAgAHcAaABpAGMAaAAgAGQAaQBzAHAAbABhAHkAIABlAGkAdABoAGUAcgAgAG4AdQBsAGwAaQBuAGcAIABvAHIAIABpAG4AdABlAHIAbQBpAHQAdABlAG4AdAAgAGIAZQBoAGEAdgBpAG8AdQByACAAKABQAFMAUgBzACAASgAxADgAMQAyAC0AMQA1ACAAYQBuAGQAIABKADEAOAAzADEALQAwADQAKQAuACAAVwBlACAAcwBoAG8AdwAgAHQAaABhAHQAIAB0AGgAZQAgAHQAbwB0AGEAbAAgAHAAbwBwAHUAbABhAHQAaQBvAG4AIABvAGYAIAAxADAAMAAgAHAAdQBsAHMAYQByAHMAIABkAGkAcwBjAG8AdgBlAHIAZQBkACAAaQBuACAAdABoAGUAIABIAFQAUgBVAC0AUwAgAEwAbwB3AEwAYQB0ACAAcwB1AHIAdgBlAHkAIAB0AG8AIABkAGEAdABlACAAcgBlAHAAcgBlAHMAZQBuAHQAcwAgAGIAbwB0AGgAIABhAG4AIABvAGwAZABlAHIAIABhAG4AZAAgAGwAbwB3AGUAcgAgAGwAdQBtAGkAbgBvAHMAaQB0AHkAIABwAG8AcAB1AGwAYQB0AGkAbwBuACwAIABhAG4AZAAgAGkAbgBkAGkAYwBhAHQAZQBzACAAdABoAGEAdAAgAHcAZQAgAGgAYQB2AGUAIAB5AGUAdAAgAHQAbwAgAHIAZQBhAGMAaAAgAHQAaABlACAAYgBvAHQAdABvAG0AIABvAGYAIAB0AGgAZQAgAGwAdQBtAGkAbgBvAHMAaQB0AHkAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAgAGYAdQBuAGMAdABpAG8AbgAuACAAVwBlACAAcAByAGUAcwBlAG4AdAAgAGUAdgBhAGwAdQBhAHQAaQBvAG4AcwAgAG8AZgAgAHQAaABlACAAcABlAHIAZgBvAHIAbQBhAG4AYwBlACAAbwBmACAAbwB1AHIAIABzAGUAYQByAGMAaAAgAHQAZQBjAGgAbgBpAHEAdQBlACAAYQBuAGQAIABvAGYAIAB0AGgAZQAgAG8AdgBlAHIAYQBsAGwAIAB5AGkAZQBsAGQAIABvAGYAIAB0AGgAZQAgAHMAdQByAHYAZQB5ACwAIABjAG8AbgBzAGkAZABlAHIAaQBuAGcAIAB0AGgAZQAgADkANAAgAHAAZQByACAAYwBlAG4AdAAgAG8AZgAgAHQAaABlACAAcwB1AHIAdgBlAHkAIAB3AGgAaQBjAGgAIAB3AGUAIABoAGEAdgBlACAAcAByAG8AYwBlAHMAcwBlAGQAIAB0AG8AIABkAGEAdABlAC4AIABXAGUAIABzAGgAbwB3ACAAdABoAGEAdAAgAG8AdQByACAAcAB1AGwAcwBhAHIAIAB5AGkAZQBsAGQAIABmAGEAbABsAHMAIABiAGUAbABvAHcAIABlAGEAcgBsAGkAZQByACAAcAByAGUAZABpAGMAdABpAG8AbgBzACAAYgB5ACAAYQBwAHAAcgBvAHgAaQBtAGEAdABlAGwAeQAgADIANQAgAHAAZQByACAAYwBlAG4AdAAgACgAZQBzAHAAZQBjAGkAYQBsAGwAeQAgAGkAbgAgAHQAaABlACAAYwBhAHMAZQAgAG8AZgAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAHMAKQAsACAAYQBuAGQAIABkAGkAcwBjAHUAcwBzACAAZQB4AHAAbABhAG4AYQB0AGkAbwBuAHMAIABmAG8AcgAgAHQAaABpAHMAIABkAGkAcwBjAHIAZQBwAGEAbgBjAHkAIABhAHMAIAB3AGUAbABsACAAYQBzACAAZgB1AHQAdQByAGUAIABhAGQAYQBwAHQAYQB0AGkAbwBuAHMAIABpAG4AIABSAEYASQAgAG0AaQB0AGkAZwBhAHQAaQBvAG4AIABhAG4AZAAgAHMAZQBhAHIAYwBoAGkAbgBnACAAdABlAGMAaABuAGkAcQB1AGUAcwAgAHcAaABpAGMAaAAgAG0AYQB5ACAAYQBkAGQAcgBlAHMAcwAgAHQAaABlAHMAZQAgAHMAaABvAHIAdABmAGEAbABsAHMALgAAADpodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL01OUkFTLzQ5My8xMDYzAAABQABDAGEAbQBlAHIAbwBuACAAQQAuAEQALgA7ACAAQwBoAGEAbQBwAGkAbwBuACAARAAuAEoALgA7ACAAQgBhAGkAbABlAHMAIABNAC4AOwAgAEIAYQBsAGEAawByAGkAcwBoAG4AYQBuACAAVgAuADsAIABCAGEAcgByACAARQAuAEQALgA7ACAAQgBhAHMAcwBhACAAQwAuAEcALgA7ACAAQgBhAHQAZQBzACAAUwAuADsAIABCAGgAYQBuAGQAYQByAGkAIABTAC4AOwAgAEIAaABhAHQAIABOAC4ARAAuAFIALgA7ACAAQgB1AHIAZwBhAHkAIABNAC4AOwAgAEIAdQByAGsAZQAtAFMAcABvAGwAYQBvAHIAIABTAC4AOwAgAEYAbAB5AG4AbgAgAEMALgBNAC4ATAAuADsAIABKAGEAbQBlAHMAbwBuACAAQQAuADsAIABKAG8AaABuAHMAdABvAG4AIABTAC4AOwAgAEsAZQBpAHQAaAAgAE0ALgBKAC4AOwAgAEsAcgBhAG0AZQByACAATQAuADsAIABMAGUAdgBpAG4AIABMAC4AOwAgAEwAeQBuAGUAIABBAC4ARwAuADsAIABOAGcAIABDAC4AOwAgAFAAZQB0AHIAbwBmAGYAIABFAC4AOwAgAFAAbwBzAHMAZQBuAHQAaQAgAEEALgA7ACAAUwBtAGkAdABoACAARAAuAEEALgA7ACAAUwB0AGEAcABwAGUAcgBzACAAQgAuAFcALgA7ACAAdgBhAG4AIABTAHQAcgBhAHQAZQBuACAAVwAuADsAIABUAGkAYgB1AHIAegBpACAAQwAuADsAIABXAHUAIABKAC4AAAATMjAyMy0wNy0yOFQwOTo1MTozOQAAABMyMDIzLTA3LTI4VDA4OjU4OjUxAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADAATQBOAFIAQQBTAC4ANAA5ADMALgAxADAANgAzAEN/wAAAAAAABXJhZGlvAAARl2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2MwAAB8tpdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAWpdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAADmXN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAGhNDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlYTIgKFB1bHNhcnMgd2l0aCBTL05fZXhwXz5TL05fbWluXz05IHdoaWNoIHdlcmUgbm90IGRldGVjdGVkIGluIHRoZSB+NDQgcGVyIGNlbnQgb2YgdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5IHByb2Nlc3NlZCBieSB0aGUgcGFydGlhbGx5IGNvaGVyZW50IHNlZ21lbnRlZCBhY2NlbGVyYXRpb24gc2VhcmNoIHBpcGVsaW5lIGZvciB0aGlzIHBhcGVyKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGVhMSAoNzU1IHJlZGV0ZWN0aW9ucyBvZiAzOTAgcHVsc2FycyByZWNvcmRlZCBkdXJpbmcgcHJvY2Vzc2luZyBvZiB0aGUgNDQgcGVyIGNlbnQgb2YgdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5IGFzIHJlcG9ydGVkIGluIHRoaXMgcGFwZXIpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTYgKEJlc3QtZml0dGluZyB0ZW1wbzIgdGltaW5nIHBhcmFtZXRlcnMgZm9yIGZvdXIgbmV3bHkgZGlzY292ZXJlZCBiaW5hcnkgcHVsc2Fycyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlNCAoQmVzdC1maXR0aW5nIFRFTVBPMiB0aW1pbmcgcGFyYW1ldGVycyBmb3IgMjggcHVsc2Fycyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlMyAoRGlzY292ZXJ5IHBhcmFtZXRlcnMgb2YgdGhlIHNldmVuIG5ld2x5IGRpc2NvdmVyZWQgcHVsc2FycyBmb3Igd2hpY2ggZnVsbCB0aW1pbmcgc29sdXRpb25zIGFyZSBub3QgeWV0IGF2YWlsYWJsZSk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlMiAoNDAgbmV3bHkgZGlzY292ZXJlZCBwdWxzYXJzIGZyb20gdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5KTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGVhMiAoUHVsc2FycyB3aXRoIFMvTl9leHBfPlMvTl9taW5fPTkgd2hpY2ggd2VyZSBub3QgZGV0ZWN0ZWQgaW4gdGhlIH40NCBwZXIgY2VudCBvZiB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkgcHJvY2Vzc2VkIGJ5IHRoZSBwYXJ0aWFsbHkgY29oZXJlbnQgc2VnbWVudGVkIGFjY2VsZXJhdGlvbiBzZWFyY2ggcGlwZWxpbmUgZm9yIHRoaXMgcGFwZXIpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZWExICg3NTUgcmVkZXRlY3Rpb25zIG9mIDM5MCBwdWxzYXJzIHJlY29yZGVkIGR1cmluZyBwcm9jZXNzaW5nIG9mIHRoZSA0NCBwZXIgY2VudCBvZiB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkgYXMgcmVwb3J0ZWQgaW4gdGhpcyBwYXBlcik6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlNiAoQmVzdC1maXR0aW5nIHRlbXBvMiB0aW1pbmcgcGFyYW1ldGVycyBmb3IgZm91ciBuZXdseSBkaXNjb3ZlcmVkIGJpbmFyeSBwdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGU0IChCZXN0LWZpdHRpbmcgVEVNUE8yIHRpbWluZyBwYXJhbWV0ZXJzIGZvciAyOCBwdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGUzIChEaXNjb3ZlcnkgcGFyYW1ldGVycyBvZiB0aGUgc2V2ZW4gbmV3bHkgZGlzY292ZXJlZCBwdWxzYXJzIGZvciB3aGljaCBmdWxsIHRpbWluZyBzb2x1dGlvbnMgYXJlIG5vdCB5ZXQgYXZhaWxhYmxlKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGUyICg0MCBuZXdseSBkaXNjb3ZlcmVkIHB1bHNhcnMgZnJvbSB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZWEyIChQdWxzYXJzIHdpdGggUy9OX2V4cF8+Uy9OX21pbl89OSB3aGljaCB3ZXJlIG5vdCBkZXRlY3RlZCBpbiB0aGUgfjQ0IHBlciBjZW50IG9mIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSBwcm9jZXNzZWQgYnkgdGhlIHBhcnRpYWxseSBjb2hlcmVudCBzZWdtZW50ZWQgYWNjZWxlcmF0aW9uIHNlYXJjaCBwaXBlbGluZSBmb3IgdGhpcyBwYXBlcik6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlYTEgKDc1NSByZWRldGVjdGlvbnMgb2YgMzkwIHB1bHNhcnMgcmVjb3JkZWQgZHVyaW5nIHByb2Nlc3Npbmcgb2YgdGhlIDQ0IHBlciBjZW50IG9mIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSBhcyByZXBvcnRlZCBpbiB0aGlzIHBhcGVyKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGU2IChCZXN0LWZpdHRpbmcgdGVtcG8yIHRpbWluZyBwYXJhbWV0ZXJzIGZvciBmb3VyIG5ld2x5IGRpc2NvdmVyZWQgYmluYXJ5IHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTQgKEJlc3QtZml0dGluZyBURU1QTzIgdGltaW5nIHBhcmFtZXRlcnMgZm9yIDI4IHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTMgKERpc2NvdmVyeSBwYXJhbWV0ZXJzIG9mIHRoZSBzZXZlbiBuZXdseSBkaXNjb3ZlcmVkIHB1bHNhcnMgZm9yIHdoaWNoIGZ1bGwgdGltaW5nIHNvbHV0aW9ucyBhcmUgbm90IHlldCBhdmFpbGFibGUpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTIgKDQwIG5ld2x5IGRpc2NvdmVyZWQgcHVsc2FycyBmcm9tIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlYTIgKFB1bHNhcnMgd2l0aCBTL05fZXhwXz5TL05fbWluXz05IHdoaWNoIHdlcmUgbm90IGRldGVjdGVkIGluIHRoZSB+NDQgcGVyIGNlbnQgb2YgdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5IHByb2Nlc3NlZCBieSB0aGUgcGFydGlhbGx5IGNvaGVyZW50IHNlZ21lbnRlZCBhY2NlbGVyYXRpb24gc2VhcmNoIHBpcGVsaW5lIGZvciB0aGlzIHBhcGVyKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGVhMSAoNzU1IHJlZGV0ZWN0aW9ucyBvZiAzOTAgcHVsc2FycyByZWNvcmRlZCBkdXJpbmcgcHJvY2Vzc2luZyBvZiB0aGUgNDQgcGVyIGNlbnQgb2YgdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5IGFzIHJlcG9ydGVkIGluIHRoaXMgcGFwZXIpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTYgKEJlc3QtZml0dGluZyB0ZW1wbzIgdGltaW5nIHBhcmFtZXRlcnMgZm9yIGZvdXIgbmV3bHkgZGlzY292ZXJlZCBiaW5hcnkgcHVsc2Fycyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlNCAoQmVzdC1maXR0aW5nIFRFTVBPMiB0aW1pbmcgcGFyYW1ldGVycyBmb3IgMjggcHVsc2Fycyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlMyAoRGlzY292ZXJ5IHBhcmFtZXRlcnMgb2YgdGhlIHNldmVuIG5ld2x5IGRpc2NvdmVyZWQgcHVsc2FycyBmb3Igd2hpY2ggZnVsbCB0aW1pbmcgc29sdXRpb25zIGFyZSBub3QgeWV0IGF2YWlsYWJsZSk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlMiAoNDAgbmV3bHkgZGlzY292ZXJlZCBwdWxzYXJzIGZyb20gdGhlIEhUUlUtUyBMb3dMYXQgc3VydmV5KTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGVhMiAoUHVsc2FycyB3aXRoIFMvTl9leHBfPlMvTl9taW5fPTkgd2hpY2ggd2VyZSBub3QgZGV0ZWN0ZWQgaW4gdGhlIH40NCBwZXIgY2VudCBvZiB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkgcHJvY2Vzc2VkIGJ5IHRoZSBwYXJ0aWFsbHkgY29oZXJlbnQgc2VnbWVudGVkIGFjY2VsZXJhdGlvbiBzZWFyY2ggcGlwZWxpbmUgZm9yIHRoaXMgcGFwZXIpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZWExICg3NTUgcmVkZXRlY3Rpb25zIG9mIDM5MCBwdWxzYXJzIHJlY29yZGVkIGR1cmluZyBwcm9jZXNzaW5nIG9mIHRoZSA0NCBwZXIgY2VudCBvZiB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkgYXMgcmVwb3J0ZWQgaW4gdGhpcyBwYXBlcik6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlNiAoQmVzdC1maXR0aW5nIHRlbXBvMiB0aW1pbmcgcGFyYW1ldGVycyBmb3IgZm91ciBuZXdseSBkaXNjb3ZlcmVkIGJpbmFyeSBwdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGU0IChCZXN0LWZpdHRpbmcgVEVNUE8yIHRpbWluZyBwYXJhbWV0ZXJzIGZvciAyOCBwdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGUzIChEaXNjb3ZlcnkgcGFyYW1ldGVycyBvZiB0aGUgc2V2ZW4gbmV3bHkgZGlzY292ZXJlZCBwdWxzYXJzIGZvciB3aGljaCBmdWxsIHRpbWluZyBzb2x1dGlvbnMgYXJlIG5vdCB5ZXQgYXZhaWxhYmxlKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGUyICg0MCBuZXdseSBkaXNjb3ZlcmVkIHB1bHNhcnMgZnJvbSB0aGUgSFRSVS1TIExvd0xhdCBzdXJ2ZXkpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZWEyIChQdWxzYXJzIHdpdGggUy9OX2V4cF8+Uy9OX21pbl89OSB3aGljaCB3ZXJlIG5vdCBkZXRlY3RlZCBpbiB0aGUgfjQ0IHBlciBjZW50IG9mIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSBwcm9jZXNzZWQgYnkgdGhlIHBhcnRpYWxseSBjb2hlcmVudCBzZWdtZW50ZWQgYWNjZWxlcmF0aW9uIHNlYXJjaCBwaXBlbGluZSBmb3IgdGhpcyBwYXBlcik6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL01OUkFTLzQ5My8xMDYzL3RhYmxlYTEgKDc1NSByZWRldGVjdGlvbnMgb2YgMzkwIHB1bHNhcnMgcmVjb3JkZWQgZHVyaW5nIHByb2Nlc3Npbmcgb2YgdGhlIDQ0IHBlciBjZW50IG9mIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSBhcyByZXBvcnRlZCBpbiB0aGlzIHBhcGVyKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNDkzLzEwNjMvdGFibGU2IChCZXN0LWZpdHRpbmcgdGVtcG8yIHRpbWluZyBwYXJhbWV0ZXJzIGZvciBmb3VyIG5ld2x5IGRpc2NvdmVyZWQgYmluYXJ5IHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTQgKEJlc3QtZml0dGluZyBURU1QTzIgdGltaW5nIHBhcmFtZXRlcnMgZm9yIDI4IHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTMgKERpc2NvdmVyeSBwYXJhbWV0ZXJzIG9mIHRoZSBzZXZlbiBuZXdseSBkaXNjb3ZlcmVkIHB1bHNhcnMgZm9yIHdoaWNoIGZ1bGwgdGltaW5nIHNvbHV0aW9ucyBhcmUgbm90IHlldCBhdmFpbGFibGUpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy80OTMvMTA2My90YWJsZTIgKDQwIG5ld2x5IGRpc2NvdmVyZWQgcHVsc2FycyBmcm9tIHRoZSBIVFJVLVMgTG93TGF0IHN1cnZleSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAIWl2bzovL2Nkcy52aXppZXIvai9tbnJhcy81MDEvMTExNgAAABF2czpjYXRhbG9nc2VydmljZQAAABBKL01OUkFTLzUwMS8xMTE2AAAAIwBHAEEASQBBACAAcAB1AGwAcwBhAHIAcwAgAGEAbgBkACAAdwBoAGUAcgBlACAAdABvACAAZgBpAG4AZAAgAHQAaABlAG0AAAAIcmVzZWFyY2gAAAZjAFcAaABpAGwAZQAgAHQAaABlACAAbQBhAGoAbwByAGkAdAB5ACAAbwBmACAAbQBhAHMAcwBpAHYAZQAgAHMAdABhAHIAcwAgAGgAYQB2AGUAIABhACAAcwB0AGUAbABsAGEAcgAgAGMAbwBtAHAAYQBuAGkAbwBuACwAIABtAG8AcwB0ACAAcAB1AGwAcwBhAHIAcwAgAGEAcABwAGUAYQByACAAdABvACAAYgBlACAAaQBzAG8AbABhAHQAZQBkAC4AIABUAGEAawBlAG4AIABhAHQAIABmAGEAYwBlACAAdgBhAGwAdQBlACwAIAB0AGgAaQBzACAAcwB1AGcAZwBlAHMAdABzACAAdABoAGEAdAAgAG0AbwBzAHQAIABtAGEAcwBzAGkAdgBlACAAYgBpAG4AYQByAGkAZQBzACAAYgByAGUAYQBrACAAYQBwAGEAcgB0ACAAZAB1AGUAIAB0AG8AIABzAHQAcgBvAG4AZwAgAG4AYQB0AGEAbAAgAGsAaQBjAGsAcwAgAHIAZQBjAGUAaQB2AGUAZAAgAGkAbgAgAHMAdQBwAGUAcgBuAG8AdgBhACAAZQB4AHAAbABvAHMAaQBvAG4AcwAuACAASABvAHcAZQB2AGUAcgAsACAAdABoAGUAIABvAGIAcwBlAHIAdgBlAGQAIABiAGkAbgBhAHIAeQAgAGYAcgBhAGMAdABpAG8AbgAgAGMAYQBuACAAcwB0AGkAbABsACAAYgBlACAAcwB1AGIAagBlAGMAdAAgAHQAbwAgAHMAdAByAG8AbgBnACAAcwBlAGwAZQBjAHQAaQBvAG4AIABlAGYAZgBlAGMAdABzACwAIABhAHMAIABtAG8AbgBpAHQAbwByAGkAbgBnACAAbwBmACAAbgBlAHcAbAB5ACAAZABpAHMAYwBvAHYAZQByAGUAZAAgAHAAdQBsAHMAYQByAHMAIABpAHMAIAByAGEAcgBlAGwAeQAgAGMAYQByAHIAaQBlAGQAIABvAHUAdAAgAGYAbwByACAAbABvAG4AZwAgAGUAbgBvAHUAZwBoACAAdABvACAAYwBvAG4AYwBsAHUAcwBpAHYAZQBsAHkAIAByAHUAbABlACAAbwB1AHQAIABtAHUAbAB0AGkAcABsAGkAYwBpAHQAeQAuACAASABlAHIAZQAsACAAdwBlACAAdQBzAGUAIAB0AGgAZQAgAHMAZQBjAG8AbgBkACAARwBhAGkAYQAgAGQAYQB0AGEAIAByAGUAbABlAGEAcwBlACAAdABvACAAcwBlAGEAcgBjAGgAIABmAG8AcgAgAGMAbwBtAHAAYQBuAGkAbwBuAHMAIAB0AG8AIAAxADUAMwA0ACAAcgBvAHQAYQB0AGkAbwBuAC0AcABvAHcAZQByAGUAZAAgAHAAdQBsAHMAYQByAHMAIAB3AGkAdABoACAAcABvAHMAaQB0AGkAbwBuAHMAIABrAG4AbwB3AG4AIAB0AG8AIABiAGUAdAB0AGUAcgAgAHQAaABhAG4AIAAwAC4ANQBhAHIAYwBzAGUAYwAuACAAVwBlACAAZgBpAG4AZAAgADIAMgAgAG0AYQB0AGMAaABlAHMAIAB0AG8AIABrAG4AbwB3AG4AIABwAHUAbABzAGEAcgBzACwAIABpAG4AYwBsAHUAZABpAG4AZwAgADEAIABuAG8AdAAgAHIAZQBwAG8AcgB0AGUAZAAgAGUAbABzAGUAdwBoAGUAcgBlACwAIABhAG4AZAAgADgAIABuAGUAdwAgAHAAbwBzAHMAaQBiAGwAZQAgAGMAbwBtAHAAYQBuAGkAbwBuAHMAIAB0AG8AIAB5AG8AdQBuAGcAIABwAHUAbABzAGEAcgBzAC4AIABXAGUAIABlAHgAYQBtAGkAbgBlACAAdABoAGUAIABwAGgAbwB0AG8AbQBlAHQAcgBpAGMAIABhAG4AZAAgAGsAaQBuAGUAbQBhAHQAaQBjACAAcAByAG8AcABlAHIAdABpAGUAcwAgAG8AZgAgAHQAaABlAHMAZQAgAHMAeQBzAHQAZQBtAHMAIABhAG4AZAAgAHAAcgBvAHYAaQBkAGUAIABlAG0AcABpAHIAaQBjAGEAbAAgAHIAZQBsAGEAdABpAG8AbgBzACAAZgBvAHIAIABpAGQAZQBuAHQAaQBmAHkAaQBuAGcAIABHAGEAaQBhACAAcwBvAHUAcgBjAGUAcwAgAHcAaQB0AGgAIABwAG8AdABlAG4AdABpAGEAbAAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByACAAYwBvAG0AcABhAG4AaQBvAG4AcwAuACAATwB1AHIAIAByAGUAcwB1AGwAdABzACAAYwBvAG4AZgBpAHIAbQAgAHQAaABhAHQAIAB0AGgAZQAgAG8AYgBzAGUAcgB2AGUAZAAgAG0AdQBsAHQAaQBwAGwAaQBjAGkAdAB5ACAAZgByAGEAYwB0AGkAbwBuACAAaQBzACAAcwBtAGEAbABsAC4AIABIAG8AdwBlAHYAZQByACwAIAB3AGUAIABzAGgAbwB3ACAAdABoAGEAdAAgAHQAaABlACAAbgB1AG0AYgBlAHIAIABvAGYAIABiAGkAbgBhAHIAaQBlAHMAIABiAGUAbABvAHcAIAB0AGgAZQAgAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAG8AZgAgAEcAYQBpAGEAIABhAG4AZAAgAHIAYQBkAGkAbwAgAHQAaQBtAGkAbgBnACAAaQBuACAAbwB1AHIAIABzAGEAbQBwAGwAZQAgAGMAbwB1AGwAZAAgAHMAdABpAGwAbAAgAGIAZQAgAHMAaQBnAG4AaQBmAGkAYwBhAG4AdABsAHkAIABoAGkAZwBoAGUAcgAuACAAVwBlACAAYwBvAG4AcwB0AHIAYQBpAG4AIAB0AGgAZQAgAGIAaQBuAGEAcgB5ACAAZgByAGEAYwB0AGkAbwBuACAAbwBmACAAeQBvAHUAbgBnACAAcAB1AGwAcwBhAHIAcwAgAHQAbwAgAGIAZQAgAGYAXgB0AHIAdQBlAF4AXwB5AG8AdQBuAGcAXwA8ADUALgAzACgAOAAuADMAKQAgAHAAZQByACAAYwBlAG4AdAAgAHUAbgBkAGUAcgAgAHIAZQBhAGwAaQBzAHQAaQBjACAAKABjAG8AbgBzAGUAcgB2AGEAdABpAHYAZQApACAAYQBzAHMAdQBtAHAAdABpAG8AbgBzACAAZgBvAHIAIAB0AGgAZQAgAGIAaQBuAGEAcgB5ACAAcAByAG8AcABlAHIAdABpAGUAcwAgAGEAbgBkACAAYwB1AHIAcgBlAG4AdAAgAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAHQAaAByAGUAcwBoAG8AbABkAHMALgAgAEYAbwByACAAbQBhAHMAcwBpAHYAZQAgAHMAdABhAHIAcwAgACgAPgAxADAATQBfAHsAcwB1AG4AfQBfACkAIABpAG4AIABwAGEAcgB0AGkAYwB1AGwAYQByACwAIAB3AGUAIABmAGkAbgBkACAAZgBeAHQAcgB1AGUAXgBfAE8AQgBfADwAMwAuADcAIABwAGUAcgAgAGMAZQBuAHQALAAgAHcAaABpAGMAaAAgAHMAZQB0AHMAIABhACAAZgBpAHIAbQAgAGkAbgBkAGUAcABlAG4AZABlAG4AdAAgAHUAcABwAGUAcgAgAGwAaQBtAGkAdAAgAG8AbgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAbgBlAHUAdAByAG8AbgAgAHMAdABhAHIAIABtAGUAcgBnAGUAcgAgAHIAYQB0AGUALAAgADwAPQA3AC4AMgB4ADEAMABeAC0ANABeAC8AeQByAC4AIABPAG4AZwBvAGkAbgBnACAAYQBuAGQAIABmAHUAdAB1AHIAZQAgAHAAcgBvAGoAZQBjAHQAcwAsACAAcwB1AGMAaAAgAGEAcwAgAHQAaABlACAAQwBIAEkATQBFAC8AcAB1AGwAcwBhAHIAIABwAHIAbwBnAHIAYQBtACwAIABNAGUAZQByAFQAaQBtAGUALAAgAEgASQBSAEEAWAAsACAAYQBuAGQAIAB1AGwAdABpAG0AYQB0AGUAbAB5ACAAdABoAGUAIABTAEsAQQAsACAAdwBpAGwAbAAgAHMAaQBnAG4AaQBmAGkAYwBhAG4AdABsAHkAIABpAG0AcAByAG8AdgBlACAAdABoAGUAcwBlACAAYwBvAG4AcwB0AHIAYQBpAG4AdABzACAAaQBuACAAdABoAGUAIABmAHUAdAB1AHIAZQAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNTAxLzExMTYAAAANAEEAbgB0AG8AbgBpAGEAZABpAHMAIABKAC4AAAATMjAyMS0wMS0xM1QwNzoxMzoxMAAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADEATQBOAFIAQQBTAC4ANQAwADEALgAxADEAMQA2AEF/wAAAAAAABXJhZGlvAAACa2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNTAxLzExMTYvY2F0YWxvZz86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNTAxLzExMTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzUwMS8xMTE2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy81MDEvMTExNi9jYXRhbG9nPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy81MDEvMTExNjo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNTAxLzExMTYAAADXaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAADLdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXIAAAB1c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAABS0NvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovTU5SQVMvNTAxLzExMTYvY2F0YWxvZyAoQ2F0YWxvZ3VlIG9mIHNvdXJjZXMgd2l0aGluIDIwIGFyY3NlYyBvZiBBVE5GIHB1bHNhcnMpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy81MDEvMTExNi9jYXRhbG9nIChDYXRhbG9ndWUgb2Ygc291cmNlcyB3aXRoaW4gMjAgYXJjc2VjIG9mIEFUTkYgcHVsc2Fycyk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAIWl2bzovL2Nkcy52aXppZXIvai9tbnJhcy81MjcvNTE4MwAAABF2czpjYXRhbG9nc2VydmljZQAAABBKL01OUkFTLzUyNy81MTgzAAAAKgBHAGEAbABhAGMAdABpAGMAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAgAG8AZgAgAHAAdQBsAHMAYQByACAAcwBjAGEAdAB0AGUAcgBpAG4AZwAAAAhyZXNlYXJjaAAABJUASQBuAHQAZQByAHMAdABlAGwAbABhAHIAIAByAGEAZABpAG8ALQB3AGEAdgBlACAAcwBjAGEAdAB0AGUAcgBpAG4AZwAgAGwAZQBhAGQAcwAgAHQAbwAgAGYAbAB1AHgAIABkAGUAbgBzAGkAdAB5ACAAZgBsAHUAYwB0AHUAYQB0AGkAbwBuAHMAIABhAG4AZAAgAHAAdQBsAHMAZQAgAGIAcgBvAGEAZABlAG4AaQBuAGcAIABvAGYAIABwAHUAbABzAGEAcgAgAHMAaQBnAG4AYQBsAHMALgAgAEgAbwB3AGUAdgBlAHIALAAgAEcAYQBsAGEAYwB0AGkAYwAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAYQBuAGQAIAB0AGgAZQAgAHMAdAByAHUAYwB0AHUAcgBlACAAbwBmACAAdABoAGUAIABzAGMAYQB0AHQAZQByAGkAbgBnACAAbQBlAGQAaQB1AG0AIABhAHIAZQAgAHMAdABpAGwAbAAgAHAAbwBvAHIAbAB5ACAAdQBuAGQAZQByAHMAdABvAG8AZAAuACAAUAB1AGwAcwBhAHIAIABwAHUAbABzAGUAIABiAHIAbwBhAGQAZQBuAGkAbgBnACAAZABhAHQAYQAgAGEAdgBhAGkAbABhAGIAbABlACAAZgBvAHIAIABhACAAcgBlAGwAYQB0AGkAdgBlAGwAeQAgAGwAYQByAGcAZQAgAG4AdQBtAGIAZQByACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGkAcwAgAHcAZQBsAGwALQBzAHUAaQB0AGUAZAAgAGYAbwByACAAcwB1AGMAaAAgAGkAbgB2AGUAcwB0AGkAZwBhAHQAaQBvAG4AcwAuACAAVwBlACAAYwBvAGwAbABlAGMAdABlAGQAIABhAG4AIAB1AHAALQB0AG8ALQBkAGEAdABlACAAcwBhAG0AcABsAGUAIABvAGYAIABwAHUAYgBsAGkAYwBsAHkALQBhAHYAYQBpAGwAYQBiAGwAZQAgAHAAdQBsAHMAYQByACAAcwBjAGEAdAB0AGUAcgBpAG4AZwAgAGQAYQB0AGEAIABhAG4AZAAgAGkAbgB0AHIAbwBkAHUAYwBlAGQAIABhACAAbgBlAHcAIABxAHUAYQBuAHQAaQB0AHkAIAAtACAAdABoAGUAIAByAGUAZAB1AGMAZQBkACAAcwBjAGEAdAB0AGUAcgBpAG4AZwAgAHMAdAByAGUAbgBnAHQAaAAgAHQAbwAgAHMAdAB1AGQAeQAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABvAGYAIABwAHUAbABzAGEAcgAgAHMAYwBhAHQAdABlAHIAaQBuAGcAIABpAG4AIAB0AGgAZQAgAE0AaQBsAGsAeQAgAFcAYQB5AC4AIABXAGUAIABzAGgAbwB3ACAAdABoAGEAdAAgAHQAaABlACAAYwB1AHIAcgBlAG4AdAAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAYQByAGUAIABkAG8AbQBpAG4AYQB0AGUAZAAgAGIAeQAgAHQAdwBvACAAZABpAHMAdABpAG4AYwB0ACAAcAB1AGwAcwBhAHIAIABwAG8AcAB1AGwAYQB0AGkAbwBuAHMAOgAgAGEAIABsAG8AYwBhAGwAIABhAG4AZAAgAGEAbgAgAGkAbgBuAGUAcgAtAEcAYQBsAGEAYwB0AGkAYwAgAG8AbgBlACAAcwBlAHAAYQByAGEAdABlAGQAIABiAHkAIAB7AHQAaQBsAGQAZQB9AHsAdABhAHUAfQA9ADEAMABeAC0ANQAuADEAXgBzAC4AYwBtAF4ANgBeAC8AcABjAC4AIABUAGgAZQAgAHMAdAByAG8AbgBnAGUAcgAgAGUAbABlAGMAdAByAG8AbgAgAGQAZQBuAHMAaQB0AHkAIABmAGwAdQBjAHQAdQBhAHQAaQBvAG4AcwAgAGEAcwBzAG8AYwBpAGEAdABlAGQAIAB3AGkAdABoACAAdABoAGUAIABpAG4AbgBlAHIALQBHAGEAbABhAGMAdABpAGMAIABwAG8AcAB1AGwAYQB0AGkAbwBuACAAbgBhAHQAdQByAGEAbABsAHkAIABlAHgAcABsAGEAaQBuACAAdABoAGUAIABvAGIAcwBlAHIAdgBlAGQAIABzAHQAZQBlAHAAZQBuAGkAbgBnACAAbwBmACAAcAB1AGwAcwBhAHIAIABzAGMAYQB0AHQAZQByAGkAbgBnACAAdABpAG0AZQAgAC0AIABkAGkAcwBwAGUAcgBzAGkAbwBuACAAbQBlAGEAcwB1AHIAZQAgACgARABNACkAIAByAGUAbABhAHQAaQBvAG4ALgAgAFcAZQAgAG0AZQBhAHMAdQByAGUAIABhAG4AIABpAG4AbgBlAHIAIABkAGkAcwBrACAAcgBlAGcAaQBvAG4AIAB3AGkAdABoACAAMwBrAHAAYwA8AHIAPAA1AC4ANQBrAHAAYwAgAGYAcgBvAG0AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAGMAZQBuAHQAZQByACAAdABvACAAaABhAHYAZQAgAGEAIABzAGMAYQB0AHQAZQByAGkAbgBnACAAcwBjAGEAbABlACAAaABlAGkAZwBoAHQAIABvAGYAIABhAGIAbwB1AHQAIAAwAC4AMgA4AGsAcABjACwAIABzAHUAcABwAG8AcgB0AGkAbgBnACAAYQAgAGMAbwByAHIAZQBsAGEAdABpAG8AbgAgAGIAZQB0AHcAZQBlAG4AIABpAG4AdABlAHIAcwB0AGUAbABsAGEAcgAgAHIAYQBkAGkAbwAgAHMAYwBhAHQAdABlAHIAaQBuAGcAIABhAG4AZAAgAHMAdAByAHUAYwB0AHUAcgBlAHMAIABhAHMAcwBvAGMAaQBhAHQAaQBuAGcAIAB3AGkAdABoACAAdABoAGUAIABpAG8AbgBpAHoAZQBkACAAZwBhAHMAIABhAG4AZAAgAHMAdABlAGwAbABhAHIAIABhAGMAdABpAHYAaQB0AGkAZQBzAC4AAAA6aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9NTlJBUy81MjcvNTE4MwAAAA8ASABlACAAUQAuAFkALgA7ACAAUwBoAGkAIABYAC4AAAATMjAyMy0xMi0wN1QxMTozOTo1NgAAABMyMDIzLTEyLTA3VDEwOjQwOjQzAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAyADQATQBOAFIAQQBTAC4ANQAyADcALgA1ADEAOAAzAEh/wAAAAAAABXJhZGlvAAABLmh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNTI3LzUxODMvdGFibGViMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNTI3LzUxODM6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzUyNy81MTgzAAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADNzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAACRQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9NTlJBUy81MjcvNTE4My90YWJsZWIxIChQdWxzYXIgc2NhdHRlcmluZyBkYXRhIGFuZCB0aGVpciBzb3VyY2VzKTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAjaXZvOi8vY2RzLnZpemllci9qL290aGVyL3JhYS8yMS4xMDcAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9vdGhlci9SQUEvMjEuMQAAABUATgBlAHcAIABwAHUAbABzAGEAcgBzACAAZgByAG8AbQAgAEcARwBQAFMAAAAIcmVzZWFyY2gAAAaoAEQAaQBzAGMAbwB2AGUAcgB5ACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGkAcwAgAG8AbgBlACAAbwBmACAAdABoAGUAIABtAGEAaQBuACAAZwBvAGEAbABzACAAZgBvAHIAIABsAGEAcgBnAGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQBzAC4AIABUAGgAZQAgAEYAaQB2AGUALQBoAHUAbgBkAHIAZQBkAC0AbQBlAHQAZQByACAAQQBwAGUAcgB0AHUAcgBlACAAUwBwAGgAZQByAGkAYwBhAGwAIAByAGEAZABpAG8AIABUAGUAbABlAHMAYwBvAHAAZQAgACgARgBBAFMAVAApACwAIAB0AGgAYQB0ACAAaQBuAGMAbwByAHAAbwByAGEAdABlAHMAIABhAG4AIABMAC0AYgBhAG4AZAAgADEAOQAtAGIAZQBhAG0AIAByAGUAYwBlAGkAdgBlAHIAIAB3AGkAdABoACAAYQAgAHMAeQBzAHQAZQBtACAAdABlAG0AcABlAHIAYQB0AHUAcgBlACAAbwBmACAAYQBiAG8AdQB0ACAAMgAwACAASwAsACAAaQBzACAAdABoAGUAIABtAG8AcwB0ACAAcwBlAG4AcwBpAHQAaQB2AGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQAgAHUAdABpAGwAaQB6AGUAZAAgAGYAbwByACAAZABpAHMAYwBvAHYAZQByAGkAbgBnACAAcAB1AGwAcwBhAHIAcwAuACAAVwBlACAAZABlAHMAaQBnAG4AZQBkACAAdABoAGUAIABzAG4AYQBwAHMAaABvAHQAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AIABtAG8AZABlACAAZgBvAHIAIABhACAARgBBAFMAVAAgAGsAZQB5ACAAcwBjAGkAZQBuAGMAZQAgAHAAcgBvAGoAZQBjAHQALAAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAUABsAGEAbgBlACAAUAB1AGwAcwBhAHIAIABTAG4AYQBwAHMAaABvAHQAIAAoAEcAUABQAFMAKQAgAHMAdQByAHYAZQB5ACwAIABpAG4AIAB3AGgAaQBjAGgAIABlAHYAZQByAHkAIABmAG8AdQByACAAbgBlAGEAcgBiAHkAIABwAG8AaQBuAHQAaQBuAGcAcwAgAGMAYQBuACAAbwBiAHMAZQByAHYAZQAgAGEAIABjAG8AdgBlAHIAIABvAGYAIABhACAAcwBrAHkAIABwAGEAdABjAGgAIABvAGYAIAAwAC4AMQA1ADcANQAgAHMAcQB1AGEAcgBlACAAZABlAGcAcgBlAGUAcwAgAHQAaAByAG8AdQBnAGgAIABiAGUAYQBtAC0AcwB3AGkAdABjAGgAaQBuAGcAIABvAGYAIAB0AGgAZQAgAEwALQBiAGEAbgBkACAAMQA5AC0AYgBlAGEAbQAgAHIAZQBjAGUAaQB2AGUAcgAuACAAVABoAGUAIABpAG4AdABlAGcAcgBhAHQAaQBvAG4AIAB0AGkAbQBlACAAZgBvAHIAIABlAGEAYwBoACAAcABvAGkAbgB0AGkAbgBnACAAaQBzACAAMwAwADAAIABzAGUAYwBvAG4AZABzACAAcwBvACAAdABoAGEAdAAgAHQAaABlACAARwBQAFAAUwAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAZgBvAHIAIABhACAAYwBvAHYAZQByACAAYwBhAG4AIABiAGUAIABtAGEAZABlACAAaQBuACAAMgAxACAAbQBpAG4AdQB0AGUAcwAuACAAVABoAGUAIABnAG8AYQBsACAAbwBmACAAdABoAGUAIABHAFAAUABTACAAcwB1AHIAdgBlAHkAIABpAHMAIAB0AG8AIABkAGkAcwBjAG8AdgBlAHIAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAGwAYQB0AGkAdAB1AGQAZQAgAG8AZgAgACsALwAtADEAMAB7AGQAZQBnAH0AIABmAHIAbwBtACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUALAAgAGEAbgBkACAAdABoAGUAIABoAGkAZwBoAGUAcwB0ACAAcAByAGkAbwByAGkAdAB5ACAAaQBzACAAZwBpAHYAZQBuACAAdABvACAAdABoAGUAIABpAG4AbgBlAHIAIABHAGEAbABhAHgAeQAgAHcAaQB0AGgAaQBuACAAKwAvAC0ANQB7AGQAZQBnAH0ALgAgAFUAcAAgAHQAbwAgAG4AbwB3ACwAIAB0AGgAZQAgAEcAUABQAFMAIABzAHUAcgB2AGUAeQAgAGgAYQBzACAAZABpAHMAYwBvAHYAZQByAGUAZAAgADIAMAAxACAAcAB1AGwAcwBhAHIAcwAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIABjAHUAcgByAGUAbgB0AGwAeQAgAHQAaABlACAAZgBhAGkAbgB0AGUAcwB0ACAAcAB1AGwAcwBhAHIAcwAgAHcAaABpAGMAaAAgAGMAYQBuAG4AbwB0ACAAYgBlACAAZABlAHQAZQBjAHQAZQBkACAAYgB5ACAAbwB0AGgAZQByACAAdABlAGwAZQBzAGMAbwBwAGUAcwAsACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABlAHgAdAByAGUAbQBlAGwAeQAgAGgAaQBnAGgAIABkAGkAcwBwAGUAcgBzAGkAbwBuACAAbQBlAGEAcwB1AHIAZQBzACAAKABEAE0AcwApACAAdwBoAGkAYwBoACAAYwBoAGEAbABsAGUAbgBnAGUAIAB0AGgAZQAgAGMAdQByAHIAZQBuAHQAbAB5ACAAdwBpAGQAZQBsAHkAIAB1AHMAZQBkACAAbQBvAGQAZQBsAHMAIABmAG8AcgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAZQBsAGUAYwB0AHIAbwBuACAAZABlAG4AcwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACwAIABwAHUAbABzAGEAcgBzACAAYwBvAGkAbgBjAGkAZABlAG4AdAAgAHcAaQB0AGgAIABzAHUAcABlAHIAbgBvAHYAYQAgAHIAZQBtAG4AYQBuAHQAcwAsACAANAAwACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwAsACAAMQA2ACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACwAIABzAG8AbQBlACAAbgB1AGwAbABpAG4AZwAgAGEAbgBkACAAbQBvAGQAZQAtAGMAaABhAG4AZwBpAG4AZwAgAHAAdQBsAHMAYQByAHMAIABhAG4AZAAgAHIAbwB0AGEAdABpAG4AZwAgAHIAYQBkAGkAbwAgAHQAcgBhAG4AcwBpAGUAbgB0AHMAIAAoAFIAUgBBAFQAcwApAC4AIABUAGgAZQAgAGYAbwBsAGwAbwB3AC0AdQBwACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABmAG8AcgAgAGMAbwBuAGYAaQByAG0AYQB0AGkAbwBuACAAbwBmACAAbgBlAHcAIABwAHUAbABzAGEAcgBzACAAaABhAHYAZQAgAHAAbwBsAGEAcgBpAHoAYQB0AGkAbwBuAC0AcwBpAGcAbgBhAGwAcwAgAHIAZQBjAG8AcgBkAGUAZAAgAGYAbwByACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AIABwAHIAbwBmAGkAbABlAHMAIABvAGYAIAB0AGgAZQAgAHAAdQBsAHMAYQByAHMALgAgAFIAZQAtAGQAZQB0AGUAYwB0AGkAbwBuACAAbwBmACAAcAByAGUAdgBpAG8AdQBzAGwAeQAgAGsAbgBvAHcAbgAgAHAAdQBsAHMAYQByAHMAIABpAG4AIAB0AGgAZQAgAHMAdQByAHYAZQB5ACAAZABhAHQAYQAgAGEAbABzAG8AIABsAGUAYQBkAHMAIAB0AG8AIABzAGkAZwBuAGkAZgBpAGMAYQBuAHQAIABpAG0AcAByAG8AdgBlAG0AZQBuAHQAcwAgAGkAbgAgAHAAYQByAGEAbQBlAHQAZQByAHMAIABmAG8AcgAgADYANAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABlACAARwBQAFAAUwAgAHMAdQByAHYAZQB5ACAAZABpAHMAYwBvAHYAZQByAGkAZQBzACAAYQByAGUAIABwAHUAYgBsAGkAcwBoAGUAZAAgAGEAbgBkACAAdwBpAGwAbAAgAGIAZQAgAHUAcABkAGEAdABlAGQAIABhAHQAIABoAHQAdABwADoALwAvAHoAbQB0AHQALgBiAGEAbwAuAGEAYwAuAGMAbgAvAEcAUABQAFMALwAuAAAAPGh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovb3RoZXIvUkFBLzIxLjEwNwAAAXAASABhAG4AIABKAC4ATAAuADsAIABXAGEAbgBnACAAQwAuADsAIABXAGEAbgBnACAAUAAuAEYALgA7ACAAVwBhAG4AZwAgAFQALgA7ACAAWgBoAG8AdQAgAEQALgBKAC4AOwAgAFMAdQBuACAASgAuAC0ASAAuADsAIABZAGEAbgAgAFkALgA7ACAAUwB1ACAAVwAuAC0AUQAuADsAIABKAGkAbgBnACAAVwAuAC0AQwAuADsAIABDAGgAZQBuACAAWAAuADsAIABHAGEAbwAgAFgALgBZAC4AOwAgAEgAbwB1ACAATAAuAC0ARwAuADsAIABYAHUAIABKAC4AOwAgAEwAZQBlACAASwAuAEoALgA7ACAAVwBhAG4AZwAgAE4ALgA7ACAASgBpAGEAbgBnACAAUAAuADsAIABYAHUAIABSAC4ALQBYAC4AOwAgAFkAYQBuACAASgAuADsAIABHAGEAbgAgAEgALgAtAFEALgA7ACAARwB1AGEAbgAgAFgALgA7ACAASAB1AGEAbgBnACAAVwAuAC0ASgAuADsAIABKAGkAYQBuAGcAIABKAC4ALQBDAC4AOwAgAEwAaQAgAEgALgA7ACAATQBlAG4AIABZAC4ALQBQAC4AOwAgAFMAdQBuACAAQwAuADsAIABXAGEAbgBnACAAQgAuAC0ASgAuADsAIABXAGEAbgBnACAASAAuAEcALgA7ACAAVwBhAG4AZwAgAFMALgAtAFEALgA7ACAAWABpAGUAIABKAC4ALQBUAC4AOwAgAFgAdQAgAEgALgA7ACAAWQBhAG8AIABSAC4AOwAgAFkAbwB1ACAAWAAuAC0AUAAuADsAIABZAHUAIABEAC4ASgAuADsAIABZAHUAYQBuACAASgAuAC0AUAAuADsAIABZAHUAZQBuACAAUgAuADsAIABaAGgAYQBuAGcAIABDAC4ALQBGAC4AOwAgAFoAaAB1ACAAWQAuAAAAEzIwMjItMDQtMjBUMDc6MjE6NDkAAAATMjAyMy0wOC0wN1QwODo0OToyNQAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMgAxAFIAQQBBAC4ALgAuAC4AMgAxAC4ALgAxADAANwBIf8AAAAAAAAVyYWRpbwAACSVodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU3Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTU/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9vdGhlci9SQUEvMjEuMTA3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9vdGhlci9SQUEvMjEuMTA3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU1Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTc/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGUzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL290aGVyL1JBQS8yMS4xMDc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL290aGVyL1JBQS8yMS4xMDc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU3Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTU/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9vdGhlci9SQUEvMjEuMTA3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9vdGhlci9SQUEvMjEuMTA3AAADzWl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAALpdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAdFzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAoFQ29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNyAoUG9sYXJpemF0aW9uIFBhcmFtZXRlcnMgb2YgRWlnaHQgTmV3bHkgRGlzY292ZXJlZCBQdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTUgKEJpbmFyeSBQdWxzYXJzIERpc2NvdmVyZWQgaW4gdGhlIEdQUFMgU3VydmV5IChTb3J0ZWQgYnkgUHVsc2FyIFBlcmlvZCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMyAoRGlzdGFudCBQdWxzYXJzIHdpdGggRXhjZXNzIERNcyBDaGFsbGVuZ2luZyB0aGUgQ3VycmVudGx5IFdpZGVseSBVc2VkIE1vZGVscyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL290aGVyL1JBQS8yMS4xMDcvdGFibGUyIChBIExpc3Qgb2YgUHVsc2FycyBEaXNjb3ZlcmVkIGJ5IHRoZSBHUFBTIFN1cnZleSAoU2VlIGh0dHA6Ly96bXR0LmJhby5hYy5jbi9HUFBTLyBmb3IgVXBkYXRlcykpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNyAoUG9sYXJpemF0aW9uIFBhcmFtZXRlcnMgb2YgRWlnaHQgTmV3bHkgRGlzY292ZXJlZCBQdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTUgKEJpbmFyeSBQdWxzYXJzIERpc2NvdmVyZWQgaW4gdGhlIEdQUFMgU3VydmV5IChTb3J0ZWQgYnkgUHVsc2FyIFBlcmlvZCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMyAoRGlzdGFudCBQdWxzYXJzIHdpdGggRXhjZXNzIERNcyBDaGFsbGVuZ2luZyB0aGUgQ3VycmVudGx5IFdpZGVseSBVc2VkIE1vZGVscyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL290aGVyL1JBQS8yMS4xMDcvdGFibGUyIChBIExpc3Qgb2YgUHVsc2FycyBEaXNjb3ZlcmVkIGJ5IHRoZSBHUFBTIFN1cnZleSAoU2VlIGh0dHA6Ly96bXR0LmJhby5hYy5jbi9HUFBTLyBmb3IgVXBkYXRlcykpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNyAoUG9sYXJpemF0aW9uIFBhcmFtZXRlcnMgb2YgRWlnaHQgTmV3bHkgRGlzY292ZXJlZCBQdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTUgKEJpbmFyeSBQdWxzYXJzIERpc2NvdmVyZWQgaW4gdGhlIEdQUFMgU3VydmV5IChTb3J0ZWQgYnkgUHVsc2FyIFBlcmlvZCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMyAoRGlzdGFudCBQdWxzYXJzIHdpdGggRXhjZXNzIERNcyBDaGFsbGVuZ2luZyB0aGUgQ3VycmVudGx5IFdpZGVseSBVc2VkIE1vZGVscyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL290aGVyL1JBQS8yMS4xMDcvdGFibGUyIChBIExpc3Qgb2YgUHVsc2FycyBEaXNjb3ZlcmVkIGJ5IHRoZSBHUFBTIFN1cnZleSAoU2VlIGh0dHA6Ly96bXR0LmJhby5hYy5jbi9HUFBTLyBmb3IgVXBkYXRlcykpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNyAoUG9sYXJpemF0aW9uIFBhcmFtZXRlcnMgb2YgRWlnaHQgTmV3bHkgRGlzY292ZXJlZCBQdWxzYXJzKTo6OnB5IFZPIHNlcDo6OkNvbmUgc2VhcmNoIGNhcGFiaWxpdHkgZm9yIHRhYmxlIEovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTUgKEJpbmFyeSBQdWxzYXJzIERpc2NvdmVyZWQgaW4gdGhlIEdQUFMgU3VydmV5IChTb3J0ZWQgYnkgUHVsc2FyIFBlcmlvZCkpOjo6cHkgVk8gc2VwOjo6Q29uZSBzZWFyY2ggY2FwYWJpbGl0eSBmb3IgdGFibGUgSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMyAoRGlzdGFudCBQdWxzYXJzIHdpdGggRXhjZXNzIERNcyBDaGFsbGVuZ2luZyB0aGUgQ3VycmVudGx5IFdpZGVseSBVc2VkIE1vZGVscyk6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBKL290aGVyL1JBQS8yMS4xMDcvdGFibGUyIChBIExpc3Qgb2YgUHVsc2FycyBEaXNjb3ZlcmVkIGJ5IHRoZSBHUFBTIFN1cnZleSAoU2VlIGh0dHA6Ly96bXR0LmJhby5hYy5jbi9HUFBTLyBmb3IgVXBkYXRlcykpOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAAAAAABhpdm86Ly9jZHMudml6aWVyL3ZpaS8xNTYAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAHVklJLzE1NgAAABYAQwBhAHQAYQBsAG8AZwAgAG8AZgAgADUANQA4ACAAUAB1AGwAcwBhAHIAcwAAAAhyZXNlYXJjaAAAAZ4AVABoAGUAIABjAGEAdABhAGwAbwBnAHUAZQAgAGkAcwAgAGEAbgAgAHUAcAAtAHQAbwAtAGQAYQB0AGUAIABjAG8AbQBwAGkAbABhAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAHAAcgBpAG4AYwBpAHAAYQBsACAAbwBiAHMAZQByAHYAZQBkACAAcABhAHIAYQBtAGUAdABlAHIAcwAgAG8AZgAgADUANQA4ACAAcAB1AGwAcwBhAHIAcwAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIABwAG8AcwBpAHQAaQBvAG4AcwAsACAAdABpAG0AaQBuAGcAIABwAGEAcgBhAG0AZQB0AGUAcgBzACwAIABwAHUAbABzAGUAIAB3AGkAZAB0AGgAcwAsACAAZgBsAHUAeAAgAGQAZQBuAHMAaQB0AGkAZQBzACwAIABwAHIAbwBwAGUAcgAgAG0AbwB0AGkAbwBuAHMALAAgAGQAaQBzAHQAYQBuAGMAZQBzACwAIABhAG4AZAAgAGQAaQBzAHAAZQByAHMAaQBvAG4ALAAgAHIAbwB0AGEAdABpAG8AbgAsACAAYQBuAGQAIABzAGMAYQB0AHQAZQByAGkAbgBnACAAbQBlAGEAcwB1AHIAZQBzAC4AIABJAHQAIABhAGwAcwBvACAAbABpAHMAdABzACAAdABoAGUAIABvAHIAYgBpAHQAYQBsACAAZQBsAGUAbQBlAG4AdABzACAAbwBmACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACwAIABhAG4AZAAgAHMAbwBtAGUAIABjAG8AbQBtAG8AbgBsAHkAIAB1AHMAZQBkACAAcABhAHIAYQBtAGUAdABlAHIAcwAgAGQAZQByAGkAdgBlAGQAIABmAHIAbwBtACAAdABoAGUAIABiAGEAcwBpAGMAIABtAGUAYQBzAHUAcgBlAG0AZQBuAHQAcwAuACAAVQBuAGMAZQByAHQAYQBpAG4AdABpAGUAcwAgAGEAcgBlACAAcQB1AG8AdABlAGQAIABmAG8AcgAgAG0AbwBzAHQAIABxAHUAYQBuAHQAaQB0AGkAZQBzAC4AAAAxaHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvVklJLzE1NgAAACcAVABhAHkAbABvAHIAIABKAC4ASAAuADsAIABNAGEAbgBjAGgAZQBzAHQAZQByACAAUgAuAE4ALgA7ACAATAB5AG4AZQAgAEEALgBHAC4AAAATMTk5OS0wMy0wMlQxMDoyMTo1MwAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADEAOQA5ADMAQQBwAEoAUwAuAC4ALgA4ADgALgAuADUAMgA5AFR/wAAAAAAABXJhZGlvAAAEdWh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xNTYvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE1Njo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xNTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9WSUkvMTU2L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPVZJSS8xNTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1WSUkvMTU2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvVklJLzE1Ni90YWJsZTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1WSUkvMTU2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9VklJLzE1Njo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xNTYvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE1Njo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xNTYAAAG9aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAaV2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAA+XN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAsFDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTU2L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTU2L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTU2L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTU2L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAGGl2bzovL2Nkcy52aXppZXIvdmlpLzE4OQAAABF2czpjYXRhbG9nc2VydmljZQAAAAdWSUkvMTg5AAAAEgBDAGEAdABhAGwAbwBnACAAbwBmACAAUAB1AGwAcwBhAHIAcwAAAAhyZXNlYXJjaAAAAcUAVABoAGUAIABjAGEAdABhAGwAbwBnAHUAZQAgAGkAcwAgAGEAIABjAG8AbQBwAGkAbABhAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAHAAcgBpAG4AYwBpAHAAYQBsACAAbwBiAHMAZQByAHYAZQBkACAAcABhAHIAYQBtAGUAdABlAHIAcwAgAG8AZgAgADcAMAA2ACAAcAB1AGwAcwBhAHIAcwAgACgAdQBwAGQAYQB0AGUAZAAgAGYAcgBvAG0AIAA1ADUAOAAgAHAAdQBsAHMAYQByAHMAIABpAG4AIAB0AGgAZQAgAHAAcgBlAHYAaQBvAHUAcwAgAHYAZQByAHMAaQBvAG4AKQAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIABwAG8AcwBpAHQAaQBvAG4AcwAsACAAdABpAG0AaQBuAGcAIABwAGEAcgBhAG0AZQB0AGUAcgBzACwAIABwAHUAbABzAGUAIAB3AGkAZAB0AGgAcwAsACAAZgBsAHUAeAAgAGQAZQBuAHMAaQB0AGkAZQBzACwAIABwAHIAbwBwAGUAcgAgAG0AbwB0AGkAbwBuAHMALAAgAGQAaQBzAHQAYQBuAGMAZQBzACwAIABhAG4AZAAgAGQAaQBzAHAAZQByAHMAaQBvAG4ALAAgAHIAbwB0AGEAdABpAG8AbgAsACAAYQBuAGQAIABzAGMAYQB0AHQAZQByAGkAbgBnACAAbQBlAGEAcwB1AHIAZQBzAC4AIABJAHQAIABhAGwAcwBvACAAbABpAHMAdABzACAAdABoAGUAIABvAHIAYgBpAHQAYQBsACAAZQBsAGUAbQBlAG4AdABzACAAbwBmACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACwAIABhAG4AZAAgAHMAbwBtAGUAIABjAG8AbQBtAG8AbgBsAHkAIAB1AHMAZQBkACAAcABhAHIAYQBtAGUAdABlAHIAcwAgAGQAZQByAGkAdgBlAGQAIABmAHIAbwBtACAAdABoAGUAIABiAGEAcwBpAGMAIABtAGUAYQBzAHUAcgBlAG0AZQBuAHQAcwAuACAAVQBuAGMAZQByAHQAYQBpAG4AdABpAGUAcwAgAGEAcgBlACAAcQB1AG8AdABlAGQAIABmAG8AcgAgAG0AbwBzAHQAIABxAHUAYQBuAHQAaQB0AGkAZQBzAC4AAAAxaHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvVklJLzE4OQAAACcAVABhAHkAbABvAHIAIABKAC4ASAAuADsAIABNAGEAbgBjAGgAZQBzAHQAZQByACAAUgAuAE4ALgA7ACAATAB5AG4AZQAgAEEALgBHAC4AAAATMjAwNC0wOC0wMVQyMjo1NTo0MwAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADEAOQA5ADMAQQBwAEoAUwAuAC4ALgA4ADgALgAuADUAMgA5AFR/wAAAAAAABXJhZGlvAAAEdWh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xODkvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE4OTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xODk6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9WSUkvMTg5L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPVZJSS8xODk6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1WSUkvMTg5Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvVklJLzE4OS90YWJsZTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1WSUkvMTg5Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9VklJLzE4OTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xODkvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE4OTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xODkAAAG9aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAaV2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAA+XN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAsFDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTg5L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTg5L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTg5L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpDb25lIHNlYXJjaCBjYXBhYmlsaXR5IGZvciB0YWJsZSBWSUkvMTg5L3RhYmxlMSAoT2JzZXJ2ZWQgYW5kIERlcml2ZWQgcGFyYW1ldGVycyAodGFibGVzIDEsIDIgYW5kIDQgb2YgdGhlIHB1YmxpY2F0aW9uKSk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAAAAAAHWl2bzovL25hc2EuaGVhc2FyYy9hdG5mcHVsc2FyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAABEFUTkYAAAATAEEAVABOAEYAIABQAHUAbABzAGEAcgAgAEMAYQB0AGEAbABvAGcAAAAIcmVzZWFyY2gAAAnTAFQAaABlACAAQQB1AHMAdAByAGEAbABpAGEAIABUAGUAbABlAHMAYwBvAHAAZQAgAE4AYQB0AGkAbwBuAGEAbAAgAEYAYQBjAGkAbABpAHQAeQAgACgAQQBUAE4ARgApACAAUAB1AGwAcwBhAHIAIABDAGEAdABhAGwAbwBnACAAaQBzACAAYQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABrAG4AbwB3AG4AIABwAHUAbABzAGEAcgBzACAAYwBvAG0AcABpAGwAZQBkACAAYgB5ACAAUgAuAE4ALgAgAE0AYQBuAGMAaABlAHMAdABlAHIAIABlAHQAIABhAGwALgAgAGEAbgBkACAAaQBzACAAZABlAHMAYwBlAG4AZABlAGQAIABmAHIAbwBtACAAcAB1AGwAcwBhAHIAIABkAGEAdABhAGIAYQBzAGUAIAB1AHMAZQBkACAAZgBvAHIAIAB0AGgAZQAgAHAAYQBwAGUAcgAgACYAcQB1AG8AdAA7AEMAYQB0AGEAbABvAGcAIABvAGYAIAA1ADUAOAAgAFAAdQBsAHMAYQByAHMAJgBxAHUAbwB0ADsAIABiAHkAIABKAC4ASAAuACAAVABhAHkAbABvAHIALAAgAFIALgBOAC4AIABNAGEAbgBjAGgAZQBzAHQAZQByACAAYQBuAGQAIABBAC4ARwAuACAATAB5AG4AZQAgADEAOQA5ADMALAAgAEEAcABKAFMALAAgADgAOAAsACAANQAyADkALQA1ADYAOAAuACAAVABoAGUAIABjAHUAcgByAGUAbgB0ACAAYwBhAHQAYQBsAG8AZwAgAGgAYQBzACAAYgBlAGUAbgAgAHMAdQBwAHAAbABlAG0AZQBuAHQAZQBkACAAYgB5ACAAaQBuAGMAbAB1AHMAaQBvAG4AIABvAGYAIABwAHUAYgBsAGkAcwBoAGUAZAAgAGQAYQB0AGEAIABmAHIAbwBtACAAbQBvAHIAZQAgAHIAZQBjAGUAbgB0ACAAcgBhAGQAaQBvACAAcwB1AHIAdgBlAHkAcwAsACAAaQBuACAAcABhAHIAdABpAGMAdQBsAGEAcgAsACAAdABoAGUAIABQAGEAcgBrAGUAcwAgAE0AdQBsAHQAaQBiAGUAYQBtACAAKABQAE0AKQAgAFAAdQBsAHMAYQByACAAUwB1AHIAdgBlAHkAIAAoAE0AYQBuAGMAaABlAHMAdABlAHIAIABlAHQAIABhAGwALgAgADIAMAAwADEALAAgAE0ATgBSAEEAUwAsACAAMwAyADgALAAgADEANwAtADMANQApACAAWwBhAHYAYQBpAGwAYQBiAGwAZQAgAGEAdAAgAHQAaABlACAASABFAEEAUwBBAFIAQwAgAGEAcwAgAHQAaABlACAAUABNAFAAVQBMAFMAQQBSACAAdABhAGIAbABlAF0AIABhAG4AZAAgAHQAaABlACAAUwB3AGkAbgBiAHUAcgBuAGUAIABJAG4AdABlAHIAbQBlAGQAaQBhAHQAZQAgAEwAYQB0AGkAdAB1AGQAZQAgAFAAdQBsAHMAYQByACAAUwB1AHIAdgBlAHkAIAAoAEUAZAB3AGEAcgBkAHMAIABlAHQAIABhAGwALgAgADIAMAAwADEALAAgAE0ATgBSAEEAUwAsACAAMwAyADYALAAgADMANQA4AC0AMwA3ADQAKQAsACAAYgBvAHQAaAAgAG0AYQBkAGUAIAB1AHMAaQBuAGcAIAB0AGgAZQAgAEEAVABOAEYAIABQAGEAcgBrAGUAcwAgADYANAAtAG0AIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQAuACAAQgBpAG4AYQByAHkAIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAZgBvAHIAIABrAG4AbwB3AG4AIABiAGkAbgBhAHIAeQAgAHAAdQBsAHMAYQByAHMAIABhAHIAZQAgAGEAbABzAG8AIABpAG4AYwBsAHUAZABlAGQAIABhAHMAIAB3AGUAbABsACAAYQBzACAAYQBsAGwAIABhAHYAYQBpAGwAYQBiAGwAZQAgAGEAcwB0AHIAbwBtAGUAdAByAGkAYwAgAGEAbgBkACAAcwBwAGkAbgAgAHAAYQByAGEAbQBlAHQAZQByACAAaQBuAGYAbwByAG0AYQB0AGkAbwBuACAAZgBvAHIAIABhAGwAbAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABlACAAYwBhAHQAYQBsAG8AZwAgAGkAbgBjAGwAdQBkAGUAcwAgAGEAbABsACAAcAB1AGIAbABpAHMAaABlAGQAIAByAG8AdABhAHQAaQBvAG4ALQBwAG8AdwBlAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAuACAAVAB3AG8AIABzAGUAcABhAHIAYQB0AGUAIABzAG0AYQBsAGwAIABzAHUAYgBzAGUAdABzACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGQAZQB0AGUAYwB0AGUAZAAgAG8AbgBsAHkAIABhAHQAIABoAGkAZwBoACAAZQBuAGUAcgBnAGkAZQBzACAAYQByAGUAIABhAGwAcwBvACAAaQBuAGMAbAB1AGQAZQBkACAAaQBuACAAdABoAGUAIABjAHUAcgByAGUAbgB0ACAAdABhAGIAbABlADoAIAB0AGgAZQAgAGYAaQByAHMAdAAgAGcAcgBvAHUAcAAgAGMAbwBtAHAAcgBpAHMAZQBzACAAWAAtAHIAYQB5ACAAYQBuAGQAIABnAGEAbQBtAGEALQByAGEAeQAgAHAAdQBsAHMAYQByAHMAIAB3AGgAaQBjAGgAIABhAHIAZQAgAGEAcABwAGEAcgBlAG4AdABsAHkAIABwAG8AdwBlAHIAZQBkACAAYgB5ACAAcwBwAGkAbgAtAGQAbwB3AG4AIABlAG4AZQByAGcAeQAsACAAYgB1AHQAIAB3AGgAaQBjAGgAIABoAGEAdgBlACAAbgBvAHQAIABiAGUAZQBuACAAZABlAHQAZQBjAHQAZQBkACAAYQB0ACAAcgBhAGQAaQBvACAAdwBhAHYAZQBsAGUAbgBnAHQAaABzACwAIAB3AGgAaQBsAGUAIAB0AGgAZQAgAHMAZQBjAG8AbgBkACAAZwByAG8AdQBwACAAYwBvAG4AdABhAGkAbgBzACAAYQBuAG8AbQBhAGwAbwB1AHMAIABYAC0AcgBhAHkAIABwAHUAbABzAGEAcgBzACAAKABBAFgAUABzACkAIABhAG4AZAAgAHMAbwBmAHQALQBnAGEAbQBtAGEALQByAGEAeQAgAHIAZQBwAGUAYQB0AGUAcgBzACAAKABTAEcAUgBzACkAIABmAG8AcgAgAHcAaABpAGMAaAAgAGMAbwBoAGUAcgBlAG4AdAAgAHAAdQBsAHMAYQB0AGkAbwBuAHMAIABoAGEAdgBlACAAYgBlAGUAbgAgAGQAZQB0AGUAYwB0AGUAZAAuACAAQQBjAGMAcgBlAHQAaQBvAG4ALQBwAG8AdwBlAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAgAHMAdQBjAGgAIABhAHMAIABIAGUAcgAgAFgALQAxACAAYQBuAGQAIAB0AGgAZQAgAHIAZQBjAGUAbgB0AGwAeQAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABYAC0AcgBhAHkAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgBzACAAcwB1AGMAaAAgAGEAcwAgAFMAQQBYACAASgAxADgAMAA4AC4ANAAtADMANgA1ADgAIABhAHIAZQAgAG4AbwB0ACAAaQBuAGMAbAB1AGQAZQBkACAAaQBuACAAdABoAGkAcwAgAHQAYQBiAGwAZQAsACAAaABvAHcAZQB2AGUAcgAuACAATQBhAG4AeQAgAHAAZQBvAHAAbABlACAAaABhAHYAZQAgAGMAbwBuAHQAcgBpAGIAdQB0AGUAZAAgAHQAbwAgAHQAaABlACAAYwBvAG0AcABpAGwAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABkAGEAdABhACAAYwBvAG4AdABhAGkAbgBlAGQAIABpAG4AIAB0AGgAaQBzACAAYwBhAHQAYQBsAG8AZwAgAGEAbgBkACAAdABoAGUAIABkAGEAdABhAGIAYQBzAGUAIAB0AGgAYQB0ACAAaQB0ACAAdwBhAHMAIABkAGUAcgBpAHYAZQBkACAAZgByAG8AbQAuACAAVABoAGUAIABhAHUAdABoAG8AcgBzACAAcABhAHIAdABpAGMAdQBsAGEAcgBsAHkAIAB0AGgAYQBuAGsAIABBAG4AZAByAGUAdwAgAEwAeQBuAGUAIABvAGYAIAB0AGgAZQAgAFUAbgBpAHYAZQByAHMAaQB0AHkAIABvAGYAIABNAGEAbgBjAGgAZQBzAHQAZQByACwAIABKAG8AZAByAGUAbABsACAAQgBhAG4AawAgAE8AYgBzAGUAcgB2AGEAdABvAHIAeQAsACAARABhAHYAaQBkACAATgBpAGMAZQAgAG8AZgAgAFAAcgBpAG4AYwBlAHQAbwBuACAAVQBuAGkAdgBlAHIAcwBpAHQAeQAsACAAYQBuAGQAIABSAHUAcwBzAGUAbABsACAARQBkAHcAYQByAGQAcwAsACAAdABoAGUAbgAgAGEAdAAgAFMAdwBpAG4AYgB1AHIAbgBlACAAVQBuAGkAdgBlAHIAcwBpAHQAeQAgAG8AZgAgAFQAZQBjAGgAbgBvAGwAbwBnAHkALgAgAFQAaABlACAAYQBsAHMAbwAgAGEAYwBrAG4AbwB3AGwAZQBkAGcAZQAgAHQAaABlACAAZQBmAGYAbwByAHQAcwAgAG8AZgAgAFcAYQByAHcAaQBjAGsAIABVAG4AaQB2AGUAcgBzAGkAdAB5ACAAcwB0AHUAZABlAG4AdABzACAAQQBkAGEAbQAgAEcAbwBvAGQAZQAgAGEAbgBkACAAUwB0AGUAdgBlAG4AIABUAGgAbwBtAGEAcwAgAHcAaABvACAAYwBvAG0AcABpAGwAZQBkACAAYQBuAGQAIABjAGgAZQBjAGsAZQBkACAAYQAgAHIAZQBjAGUAbgB0ACAAdgBlAHIAcwBpAG8AbgAgAG8AZgAgAHQAaABlACAAZABhAHQAYQBiAGEAcwBlAC4AIABUAGgAZQAgAG8AcgBpAGcAaQBuAGEAbAAgACgAcwB1AG0AbQBlAHIAIAAyADAAMAAzACkAIABkAGEAdABhAGIAYQBzAGUAIABhAHQAIAB0AGgAZQAgAEEAVABOAEYAIAB3AGUAYgBzAGkAdABlACAAdwBhAHMAIABjAG8AbQBwAGkAbABlAGQAIAB3AGkAdABoACAAdABoAGUAIABpAG4AdgBhAGwAdQBhAGIAbABlACAAYQBzAHMAaQBzAHQAYQBuAGMAZQAgAG8AZgAgAE0AYQByAHkAYQBtACAASABvAGIAYgBzACwAIAB3AGgAaQBsAGUAIAB0AGgAZQAgAEEAVABOAEYAIAB3AGUAYgAgAGkAbgB0AGUAcgBmAGEAYwBlACAAdwBhAHMAIABkAGUAcwBpAGcAbgBlAGQAIABhAG4AZAAgAGMAbwBuAHMAdAByAHUAYwB0AGUAZAAgAGIAeQAgAEEAbABiAGUAcgB0ACAAVABlAG8AaAAsACAAYQAgAFMAdQBtAG0AZQByACAAVgBhAGMAYQB0AGkAbwBuACAAUwBjAGgAbwBsAGEAcgAgAGEAdAAgAHQAaABlACAAQQBUAE4ARgAgAGkAbgAgADIAMAAwADIALwAyADAAMAAzAC4AIABUAGgAZQAgAGEAdQB0AGgAbwByAHMAIAB3AG8AdQBsAGQAIABhAHAAcAByAGUAYwBpAGEAdABlACAAaQBmACAAYQBuAHkAbwBuAGUAIABtAGEAawBpAG4AZwAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAGMAYQB0AGEAbABvAGcAIABpAG4AIABhACAAcAB1AGIAbABpAGMAYQB0AGkAbwBuACAAYQBjAGsAbgBvAHcAbABlAGQAZwBlAHMAIAB0AGgAZQAgAHMAbwB1AHIAYwBlACAAbwBmACAAdABoAGUAaQByACAAaQBuAGYAbwByAG0AYQB0AGkAbwBuACAAYgB5ACAAcQB1AG8AdABpAG4AZwAgAHQAaABlACAAQQBUAE4ARgAgAFAAdQBs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YQB0AGEAIAB0AGgAcgBvAHUAZwBoACAAcABlAHIAaQBvAGQAaQBjAGkAdAB5ACAAcwBlAGEAcgBjAGgAZQBzACAAaQBuACAAZwBhAG0AbQBhAC0AcgBhAHkAIABhAG4AZAAgAHIAYQBkAGkAbwAgAGQAYQB0AGEAIABhAHIAbwB1AG4AZAAgAEwAQQBUACAAdQBuAGEAcwBzAG8AYwBpAGEAdABlAGQAIABzAG8AdQByAGMAZQAgAHAAbwBzAGkAdABpAG8AbgBzAC4AIABUAGgAZQAgAGMAYQB0AGEAbABvAGcAdQBlAGQAIABwAHUAbABzAGEAcgBzACAAYQByAGUAIABkAGkAdgBpAGQAZQBkACAAaQBuAHQAbwAgAHQAaAByAGUAZQAgAGcAcgBvAHUAcABzADoAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgBzACwAIAB5AG8AdQBuAGcAIAByAGEAZABpAG8ALQBsAG8AdQBkACAAcAB1AGwAcwBhAHIAcwAsACAAYQBuAGQAIAB5AG8AdQBuAGcAIAByAGEAZABpAG8ALQBxAHUAaQBlAHQAIABwAHUAbABzAGEAcgBzAC4AIABUAGgAaQBzACAAYwBhAHQAYQBsAG8AZwAgAHMAdQBtAG0AYQByAGkAegBlAHMAIAByAGUAcwB1AGwAdABzACAAbwBmACAAdABoAGUAIABhAG4AYQBsAHkAcwBpAHMAIABvAGYAIABwAHUAbABzAGUAIABwAHIAbwBmAGkAbABlAHMAIABhAG4AZAAgAGUAbgBlAHIAZwB5ACAAcwBwAGUAYwB0AHIAYQAsACAAYQBuAGQAIABhAG4AYQBsAHkAcwBpAHMAIABvAGYAIAB0AGgAZQAgAG8AZgBmAC0AcABlAGEAawAgAHAAaABhAHMAZQAgAGkAbgB0AGUAcgB2AGEAbABzACwAIABhAG4AZAAgAGMAbwBtAHAAYQByAGUAcwAgAHQAaABlACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHIAbwBwAGUAcgB0AGkAZQBzACAAdwBpAHQAaAAgAHQAaABvAHMAZQAgAGkAbgAgAHQAaABlACAAcgBhAGQAaQBvACwAIABvAHAAdABpAGMAYQBsACwAIABhAG4AZAAgAFgALQByAGEAeQAgAGIAYQBuAGQAcwAuACAARgBsAHUAeAAgAGwAaQBtAGkAdABzACAAZgBvAHIAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaAAgAG4AbwAgAG8AYgBzAGUAcgB2AGUAZAAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAZQBtAGkAcwBzAGkAbwBuACAAYQByAGUAIABwAHIAbwB2AGkAZABlAGQALgAAADpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9XM0Jyb3dzZS9hbGwvZmVybWlsMnBzci5odG1sAAAACwBBAGIAZABvACAAZQB0ACAAYQBsAC4AAAATMjAyMy0xMi0wOFQwMDowMDowMAAAABMyMDIzLTEyLTA4VDAwOjAwOjAwAAAAAAAAAAdjYXRhbG9nAAAAAAAAABMAMgAwADEAMwBBAHAASgBTAC4ALgAyADAAOAAuAC4ALgAxADcAQX/AAAAAAAAJZ2FtbWEtcmF5AAABfWh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L2NnaS1iaW4vVzNCcm93c2UvdzNxdWVyeS5wbD90YWJsZWhlYWQ9bmFtZT1oZWFzYXJjX2Zlcm1pbDJwc3ImQWN0aW9uPU1vcmUrT3B0aW9ucyZBY3Rpb249UGFyYW1ldGVyK1NlYXJjaCZDb25lQWRkPTE6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9jZ2ktYmluL1czQnJvd3NlL2dldHZvdGFibGUucGw/bmFtZT1mZXJtaWwycHNyOjo6cHkgVk8gc2VwOjo6aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YveGFtaW4vdm8vdGFwOjo6cHkgVk8gc2VwOjo6aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YveGFtaW4vdm8vY29uZT9zaG93b2Zmc2V0cyZ0YWJsZT1mZXJtaWwycHNyJgAAAGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoAAAAXnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAAAzOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkAAAALTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAbaXZvOi8vbmFzYS5oZWFzYXJjL3BtcHVsc2FyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAABlBhcmtlcwAAACoAUABhAHIAawBlAHMAIABNAHUAbAB0AGkAYgBlAGEAbQAgAFMAdQByAHYAZQB5ACAATgBlAHcAIABQAHUAbABzAGEAcgAgAEMAYQB0AGEAbABvAGcAAAAIcmVzZWFyY2gAAAVGAFQAaABlACAAUABhAHIAawBlAHMAIABNAHUAbAB0AGkAYgBlAGEAbQAgACgAUABNACkAIABQAHUAbABzAGEAcgAgAFMAdQByAHYAZQB5ACAAdQBzAGUAcwAgAHQAaABlACAAMgAwACAAYwBtACAAbQB1AGwAdABpAGIAZQBhAG0AIAByAGUAYwBlAGkAdgBlAHIAIABzAHkAcwB0AGUAbQAgAGEAbgBkACAAbQB1AGwAdABpAGIAZQBhAG0AIABmAGkAbAB0AGUAcgAgAGIAYQBuAGsAcwAsACAAZABpAGcAaQB0AGkAegBlAHIAIABhAG4AZAAgAGQAYQB0AGEALQBhAGMAcQB1AGkAcwBpAHQAaQBvAG4AIABzAHkAcwB0AGUAbQAgAHQAbwAgAHMAdQByAHYAZQB5ACAAYQAgAHIAZQBnAGkAbwBuACAAdwBpAHQAaABpAG4AIAB8AGIAfAAgACYAbAB0ADsAIAA1ACAAZABlAGcAcgBlAGUAcwAgAGkAbgAgAHQAaABlACAAaQBuAG4AZQByACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAZgBvAHIAIABwAHUAbABzAGEAcgBzACwAIABtAGEAbgB5ACAAbwBmACAAdwBoAGkAYwBoACAAdwBpAGwAbAAgAGIAZQAgAHkAbwB1AG4AZwAgAGEAbgBkAC8AbwByACAAcwBoAG8AcgB0AC0AcABlAHIAaQBvAGQALgAgAFQAaABpAHMAIABzAHUAcgB2AGUAeQAgAGkAcwAgAGEAIABmAGEAYwB0AG8AcgAgAG8AZgAgADcAIAB0AGkAbQBlAHMAIABtAG8AcgBlACAAcwBlAG4AcwBpAHQAaQB2AGUAIAB0AG8AIAB5AG8AdQBuAGcAIABhAG4AZAAgAGQAaQBzAHQAYQBuAHQAIABwAHUAbABzAGEAcgBzACAAdABoAGEAbgAgAHQAaABhAHQAIABvAGYAIABKAG8AaABuAHMAdABvAG4AIABlAHQAIABhAGwALgAgACgAMQA5ADkAMgAsACAATQBOAFIAQQBTACwAIAAyADUANQAsACAANAAwADEAKQAgAHcAaABpAGMAaAAgAGQAZQB0AGUAYwB0AGUAZAAgADEAMAAwACAAcAB1AGwAcwBhAHIAcwAuACAAVABoAGUAIABwAHIAZQBzAGUAbgB0ACAAcwBvAHUAcgBjAGUAIABsAGkAcwB0ACAAYwBvAG4AdABhAGkAbgBzACAAcAB1AGwAcwBhAHIAcwAgAHQAaABhAHQAIABoAGEAdgBlACAAYgBlAGUAbgAgAG4AZQB3AGwAeQAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABpAG4AIAB0AGgAZQAgAGMAbwB1AHIAcwBlACAAbwBmACAAdABoAGkAcwAgAHMAdQByAHYAZQB5AC4AIABUAGgAZQAgAFAATQAgAFMAdQByAHYAZQB5ACAAaQBzACAAcwBwAGUAYwBpAGYAaQBjAGEAbABsAHkAIAB0AGEAcgBnAGUAdABlAGQAIABmAG8AcgAgACgAaQApACAAbwBiAHMAYwB1AHIAZQBkACAAcgBlAGcAaQBvAG4AcwAgAG8AZgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACwAIAAoAGkAaQApACAAeQBvAHUAbgBnACAAcAB1AGwAcwBhAHIAcwAsAGEAbgBkACAAKABpAGkAaQApACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaAAgAG0AYQBzAHMAaQB2AGUAIABjAG8AbQBwAGEAbgBpAG8AbgBzAC4AIABBAHMAIABvAGYAIABBAHUAZwB1AHMAdAAgADEAOQA5ADkALAAgAGEAbgBhAGwAeQBzAGkAcwAgAG8AZgAgAGEAYgBvAHUAdAAgADUAMAAlACAAbwBmACAAdABoAGUAIAB0AG8AdABhAGwAIABlAHgAcABlAGMAdABlAGQAIABkAGEAdABhACAAdABvACAAYgBlACAAYwBvAGwAbABlAGMAdABlAGQAIABoAGEAcwAgAHIAZQBzAHUAbAB0AGUAZAAgAGkAbgAgAHQAaABlACAAYwBvAG4AZgBpAHIAbQBlAGQAIABkAGUAdABlAGMAdABpAG8AbgAgAG8AZgAgAG8AdgBlAHIAIAA0ADAAMAAgAG4AZQB3ACAAcAB1AGwAcwBhAHIAcwAgACgAYQBuACAAaQBuAGMAcgBlAGEAcwBlACAAbwBmACAAbQBvAHIAZQAgAHQAaABhAG4AIAA1ADAAJQAgAG8AZgAgAHQAaABlACAAawBuAG8AdwBuACAAcABvAHAAdQBsAGEAdABpAG8AbgApAC4AIABIAGUAcgBlACAAYQByAGUAIABzAG8AbQBlACAAbwBmACAAdABoAGUAIABmAGUAYQB0AHUAcgBlAHMAIABvAGYAIAB0AGgAZQAgAFAATQAgAFMAdQByAHYAZQB5ADoAIAAmAGwAdAA7AHAAcgBlACYAZwB0ADsAIABTAHUAcgB2AGUAeQAgAEEAcgBlAGEAOgAgAC0AMgA2ADAAIAAmAGwAdAA7ACAAbAAgACYAbAB0ADsAIAA1ADAAIABkAGUAZwAgACwAIAAtADUAIAAmAGwAdAA7ACAAYgAgACYAbAB0ADsAIAA1ACAAZABlAGcAIABDAGUAbgB0AGUAcgAgAEYAcgBlAHEAdQBlAG4AYwB5ADoAIAAxADMANwA0ACAATQBIAHoAIABCAGEAbgBkAHcAaQBkAHQAaAA6ACAAMgA4ADgAIABNAEgAegAgACgAOQA2ACAAYwBoAGEAbgBuAGUAbABzACAAeAAgADMAIABNAEgAegAgAHAAZQByACAAYwBoAGEAbgBuAGUAbAAgAHgAIAAyACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AcwApACAAUwBhAG0AcABsAGkAbgBnACAAUgBhAHQAZQA6ACAAMAAuADIANQAgAG0AcwAgAHgAIAAxACAAYgBpAHQAIABwAGUAcgAgAGMAaABhAG4AbgBlAGwAIABJAG4AdABlAGcAcgBhAHQAaQBvAG4AIABUAGkAbQBlADoAIAAz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
astropy-pyvo-b70558c/pyvo/registry/tests/test_regtap.py000066400000000000000000001032641510533647000234610ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.registry.regtap """ import io import re from functools import partial from urllib.parse import parse_qsl import pytest from astropy import time from pyvo.registry import regtap from pyvo.registry import rtcons from pyvo.registry.regtap import REGISTRY_BASEURL from pyvo.registry import search as regsearch from pyvo.dal import DALOverflowWarning from pyvo.dal import query as dalq from pyvo.dal import tap, sia2 from astropy.utils.data import get_pkg_data_contents from .commonfixtures import messenger_vocabulary, FAKE_GAVO # noqa: F401 get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture(name='capabilities') def _capabilities(mocker, scope="session"): def callback(request, context): return get_pkg_data_contents('data/capabilities.xml') with mocker.register_uri( 'GET', REGISTRY_BASEURL + '/capabilities', content=callback ) as matcher: yield matcher # to update this, run # import requests # from pyvo.registry import regtap # # with open("data/regtap.xml", "wb") as f: # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { # "LANG": "ADQL", # "QUERY": regtap.get_RegTAP_query(keywords="pulsar", ucd=["pos.distance"])}).content) @pytest.fixture(name='regtap_pulsar_distance_response') def _regtap_pulsar_distance_response(mocker, capabilities, scope="session"): with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=get_pkg_data_contents('data/regtap.xml')) as matcher: yield matcher @pytest.fixture() def keywords_fixture(mocker, capabilities, scope="session"): def keywordstest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "res_subject ILIKE '%vizier%'" in query assert "ivo_hasword(res_description, 'vizier')" in query assert "1=ivo_hasword(res_title, 'vizier')" in query assert ".res_subject ILIKE '%pulsar%'" in query assert "1=ivo_hasword(res_description, 'pulsar')" in query assert "1=ivo_hasword(res_title, 'pulsar')" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @pytest.fixture() def single_keyword_fixture(mocker, capabilities, scope="session"): def keywordstest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert (" UNION ALL SELECT DISTINCT ivoid FROM rr.res_subject WHERE " "rr.res_subject.res_subject ILIKE '%single%'") in query assert "1=ivo_hasword(res_description, 'single') " in query assert "1=ivo_hasword(res_title, 'single')" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @pytest.fixture() def servicetype_fixture(mocker, capabilities, scope="session"): def servicetypetest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "'ivo://ivoa.net/std/conesearch'" not in query assert "'ivo://ivoa.net/std/sia'" not in query assert "'ivo://ivoa.net/std/ssa'" not in query assert "'ivo://ivoa.net/std/slap'" not in query assert "'ivo://ivoa.net/std/tap'" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=servicetypetest_callback ) as matcher: yield matcher @pytest.fixture() def waveband_fixture(mocker, capabilities, scope="session"): def wavebandtest_callback(request, content): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "1 = ivo_hashlist_has(rr.resource.waveband, 'optical'" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=wavebandtest_callback ) as matcher: yield matcher @pytest.fixture() def datamodel_fixture(mocker, capabilities, scope="session"): def datamodeltest_callback(request, content): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert ( "detail_xpath = '/capability/dataModel/@ivo-id'" in query) assert ( "ivo_nocasematch(detail_value, 'ivo://ivoa.net/std/obscore%'))" in query) return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=datamodeltest_callback ) as matcher: yield matcher @pytest.fixture() def aux_fixture(mocker, capabilities, scope="session"): def auxtest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "ivo://ivoa.net/std/tap#aux" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=auxtest_callback, ) as matcher: yield matcher @pytest.fixture(name='multi_interface_fixture') def _multi_interface_fixture(mocker, capabilities, scope="session"): # to update this, run # import requests # from pyvo.registry import regtap # # with open("data/multi-interface.xml", "wb") as f: # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { # "LANG": "ADQL", # "QUERY": regtap.get_RegTAP_query( # ivoid="ivo://org.gavo.dc/flashheros/q/ssa")}).content) with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=get_pkg_data_contents('data/multi-interface.xml') ) as matcher: yield matcher @pytest.fixture(name='flash_service') def _flash_service(multi_interface_fixture, scope="session"): return regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] class TestInterfaceClass: def test_basic(self): intf = regtap.Interface("http://example.org") assert intf.access_url == "http://example.org" assert intf.standard_id is None assert intf.type is None assert intf.role is None assert intf.is_standard is False assert not intf.is_vosi assert intf.capability_description is None def test_repr(self): description = "An example description." intf = regtap.Interface("http://example.org", standard_id="ivo://gavo/std/a", intf_type="vs:paramhttp", intf_role="std", capability_description=description) assert (repr(intf) == "Interface(type='a', description='An example description.'," " url='http://example.org')") intf = regtap.Interface("http://example.org", capability_description=description) assert repr(intf) == ("Interface(type=None, description='An example description.'," " url='http://example.org')") def test_unknown_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://gavo/std/a", intf_type="vs:paramhttp", intf_role="std") assert intf.is_standard with pytest.raises(ValueError) as excinfo: intf.to_service() assert str(excinfo.value) == ( "PyVO has no support for interfaces with standard" " id ivo://gavo/std/a.") def test_known_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/tap#aux", intf_type="vs:paramhttp", intf_role="std") assert isinstance(intf.to_service(), tap.TAPService) assert not intf.is_vosi def test_sia2_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/sia2", intf_type="vs:paramhttp", intf_role="std") assert isinstance(intf.to_service(), sia2.SIA2Service) assert not intf.is_vosi def test_secondary_interface(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/tap#aux", intf_type="vs:webbrowser", intf_role="web") with pytest.raises(ValueError) as excinfo: intf.to_service() assert str(excinfo.value) == ( "This is not a standard interface. PyVO cannot speak to it.") def test_VOSI(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/vosi#capabilities", intf_type="vs:ParamHTTP", intf_role="std") assert intf.is_vosi # The following tests have their assertions in the fixtures. # It would certainly not hurt to refactor this so they are # in the tests (we could also just rely on the rtcons tests # that exercise about the same thing). @pytest.mark.usefixtures('keywords_fixture', 'capabilities') def test_keywords(): regsearch(keywords=['vizier', 'pulsar']) @pytest.mark.usefixtures('single_keyword_fixture', 'capabilities') def test_single_keyword(): regsearch(keywords=['single']) regsearch(keywords='single') @pytest.mark.usefixtures('servicetype_fixture', 'capabilities') def test_servicetype(): regsearch(servicetype='table') @pytest.mark.usefixtures( 'waveband_fixture', 'capabilities', 'messenger_vocabulary') def test_waveband(): regsearch(waveband='optical') @pytest.mark.usefixtures('datamodel_fixture', 'capabilities') def test_datamodel(): regsearch(datamodel='ObsCore') @pytest.mark.usefixtures('aux_fixture', 'capabilities') def test_servicetype_aux(): regsearch(servicetype='table', includeaux=True) @pytest.mark.usefixtures('aux_fixture', 'capabilities') def test_bad_servicetype_aux(): with pytest.raises(dalq.DALQueryError): regsearch(servicetype='bad_servicetype', includeaux=True) class _NS: """a namespace exposing its keyword arguments as attributes. We need this here to let us conveniently construct _FakeResults. """ def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class _FakeResults: """ a minimal standin for dal.query.Results to be used with dal.query.Record. It is constructed with a dictionary that should eventually be used as the mapping in the Record. """ def __init__(self, valdict): self.fieldnames = list(valdict.keys()) self.resultstable = _NS(array=_NS(data=[list(valdict.values())])) def get_regtap_results(**kwargs): """ return a RegTAP result as expected by RegistryResult with all values empty, completed with what's in kwargs. """ res = {} for key in regtap.RegistryResource.expected_columns: if isinstance(key, str): res[key] = None else: res[key[-1]] = None res.update(kwargs) return _FakeResults(res) def test_spatial(): assert (rtcons.keywords_to_constraints({ "spatial": (23, -40)})[0].get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_spatial" " WHERE 1 = CONTAINS(MOC(6, POINT(23, -40)), coverage))") def test_spectral(): assert (rtcons.keywords_to_constraints({ "spectral": (1e-17, 2e-17)})[0].get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_spectral WHERE" " 1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17))") def test_to_table(multi_interface_fixture, capabilities): t = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa").get_summary() assert (set(t.columns.keys()) == {'index', 'short_name', 'title', 'description', 'interfaces'}) assert t["index"][0] == 0 assert t["title"][0] == 'Flash/Heros SSAP' assert (t["description"][0][:40] == 'Spectra from the Flash and Heros Echelle') assert (t["interfaces"][0] == 'datalink#links-1.1, soda#sync-1.0, ssa, tap#aux, web') @pytest.fixture(name='rt_pulsar_distance') def _rt_pulsar_distance(regtap_pulsar_distance_response, capabilities): return regsearch(keywords="pulsar", ucd=["pos.distance"]) def test_record_fields(rt_pulsar_distance): rec = rt_pulsar_distance["VII/156"] assert rec.ivoid == "ivo://cds.vizier/vii/156" assert rec.res_type == "vs:catalogservice" assert rec.short_name == "VII/156" assert rec.res_title == "Catalog of 558 Pulsars" assert rec.content_levels == ['research'] assert rec.res_description[:20] == "The catalogue is an up-to-date"[:20] assert rec.reference_url == "https://cdsarc.cds.unistra.fr/viz-bin/cat/VII/156" assert rec.creators == ['Taylor J.H.', ' Manchester R.N.', ' Lyne A.G.'] assert rec.content_types == ['catalog'] assert rec.source_format == "bibcode" assert rec.source_value == "1993ApJS...88..529T" assert rec.region_of_regard is None assert rec.waveband == ['radio'] assert rec.created == "1999-03-02T10:21:53" # updated might break when regenerating data/regtap.xml, # replace by the new date assert rec.updated == "2021-10-21T00:00:00" assert rec.rights == "https://cds.unistra.fr/vizier-org/licences_vizier.html" # access URL, standard_id and friends exercised in TestInterfaceSelection class TestResultIndexing: def test_get_with_index(self, rt_pulsar_distance): # this is expecte to break when the fixture is updated assert (rt_pulsar_distance[0].res_title == 'Pulsar Timing for Fermi Gamma-ray Space Telescope') def test_get_with_short_name(self, rt_pulsar_distance): assert (rt_pulsar_distance["ATNF"].res_title == 'ATNF Pulsar Catalog') def test_get_with_ivoid(self, rt_pulsar_distance): assert (rt_pulsar_distance["ivo://nasa.heasarc/atnfpulsar" ].res_title == 'ATNF Pulsar Catalog') def test_out_of_range(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[40320] assert (str(excinfo.value) == f"index 40320 is out of bounds for axis 0 with size {len(rt_pulsar_distance)}") def test_bad_key(self, rt_pulsar_distance): with pytest.raises(KeyError) as excinfo: rt_pulsar_distance["hunkatunka"] assert (str(excinfo.value) == "'hunkatunka'") def test_not_indexable(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[None] assert (str(excinfo.value) == "No resource matching None") @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', 'flash_service') class TestInterfaceSelection: """ tests for the selection and generation of services within RegistryResource. """ def test_exactly_one_result(self): results = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") assert len(results) == 1 def test_access_modes(self, flash_service): assert set(flash_service.access_modes()) == { 'datalink#links-1.1', 'soda#sync-1.0', 'ssa', 'tap#aux', 'web'} def test_standard_id_multi(self, flash_service): with pytest.raises(dalq.DALQueryError) as excinfo: _ = flash_service.standard_id assert str(excinfo.value) == ("This resource supports several" " standards (datalink#links-1.1, soda#sync-1.0, ssa," " tap#aux, web). Use get_service or restrict your query" " using Servicetype.") def test_get_web_interface(self, flash_service): svc = flash_service.get_service(service_type="web") assert isinstance(svc, regtap._BrowserService) assert (svc.baseurl == "http://dc.zah.uni-heidelberg.de/flashheros/q/web/form") assert str(svc) == ("BrowserService(baseurl : " "'http://dc.zah.uni-heidelberg.de/flashheros/q/web/form'," " description : '')") import webbrowser orig_open = webbrowser.open try: open_args = [] webbrowser.open = lambda *args: open_args.append(args) svc.search() assert open_args == [ ("http://dc.zah.uni-heidelberg.de/flashheros/q/web/form", 2)] finally: webbrowser.open = orig_open def test_get_aux_interface(self, flash_service): svc = flash_service.get_service(service_type="tap#aux") assert (svc._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get_aux_as_main(self, flash_service): assert (flash_service.get_service(service_type="tap")._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get__main_from_aux(self, flash_service): assert (flash_service.get_service(service_type="tap")._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get_by_alias(self, flash_service): assert (flash_service.get_service(service_type="spectrum")._baseurl == "http://dc.zah.uni-heidelberg.de/fhssa?") def test_get_unsupported_standard(self, flash_service): with pytest.raises(ValueError) as excinfo: flash_service.get_service(service_type="soda#sync-1.0") assert str(excinfo.value) == ( "PyVO has no support for interfaces with standard id" " ivo://ivoa.net/std/soda#sync-1.0.") def test_get_nonexisting_standard(self, flash_service): with pytest.raises(ValueError) as excinfo: flash_service.get_service(service_type="http://nonsense#fancy") assert str(excinfo.value) == ( "No matching interface.") def test_unconstrained(self, flash_service): with pytest.raises(ValueError, match="Multiple matching interfaces found.*"): flash_service.get_service(lax=False) def test_interface_without_role(self): # There's an ugly corner case in our array simulation for # capabilities and interfaces: if there's a single untyped # interface, the returned type (or role) will be an empty # string, and the split() will return an empty list. # This swallowed the interface in pyVO 1.3. rec = get_regtap_results( access_urls="http://example.org/tap", standard_ids="ivo://ivoa.net/std/TAP", intf_types="vr:webbrowser", intf_roles="") resource = regtap.RegistryResource(rec, 0) assert len(resource.interfaces) == 1 assert resource.interfaces[0].standard_id == 'ivo://ivoa.net/std/TAP' # get_service still won't work because it needs a paramhttp # interface (and a role="std"). with pytest.raises(ValueError) as excinfo: resource.get_service(service_type='tap') assert (str(excinfo.value) == "No matching interface.") def test_get_service_by_keyword(self): rec = _makeRegistryRecord( access_urls=["http://example1.org/tap", "http://example2.org/tap", "http://example3.org/tap"], standard_ids=["ivo://ivoa.net/std/tap"] * 3, intf_types=["vs:paramhttp"] * 3, intf_roles=["std"] * 3, cap_descriptions=["Example 1 TAP", "Example 2 TAP"]) # no description for the third one service = rec.get_service(service_type="tap", keyword="Example 2") assert service.capability_description == "Example 2 TAP" def test_sia2_query(self): rec = _makeRegistryRecord( access_urls=["http://sia2.example.com", "http://sia.example.com"], standard_ids=[ "ivo://ivoa.net/std/sia#query-2.0", "ivo://ivoa.net/std/sia"], intf_roles=["std"] * 2, intf_types=["vs:paramhttp"] * 2, cap_descriptions=["A mock SIA2 Service."] * 2) assert rec.access_modes() == {"sia", "sia2"} assert rec.get_interface(service_type="sia2").access_url == 'http://sia2.example.com' assert rec.get_interface(service_type="sia").access_url == 'http://sia.example.com' def test_sia2_aux(self): rec = _makeRegistryRecord( access_urls=["http://sia2.example.com", "http://sia.example.com"], standard_ids=[ "ivo://ivoa.net/std/sia#query-aux-2.0", "ivo://ivoa.net/std/sia#aux"], intf_roles=["std"] * 2, intf_types=["vs:paramhttp"] * 2, cap_descriptions=["A mock service."] * 2) assert rec.access_modes() == {"sia#aux", "sia2#aux"} assert rec.get_interface(service_type="sia2").access_url == 'http://sia2.example.com' assert rec.get_interface(service_type="sia").access_url == 'http://sia.example.com' def test_non_standard_interface(self): intf = regtap.Interface("http://url", standard_id="", intf_type="", intf_role="") assert intf.supports("ivo://ivoa.net/std/sia") is False def test_supports_none(self): intf = regtap.Interface("http://url", standard_id="", intf_type="", intf_role="") assert intf.supports(None) is False def test_non_searchable_service(self): rec = _makeRegistryRecord() with pytest.raises(dalq.DALServiceError) as excinfo: rec.search() assert str(excinfo.value) == ( "Resource ivo://pyvo/test_regtap.py is not a searchable service") def test_list_interfaces(self): rec = _makeRegistryRecord( access_urls=["http://sia.example.com", "http://sia.example.com", "http://tap.example.com", "http://vosi.example.com", "http://website.com" ], standard_ids=["ivo://ivoa.net/std/sia#aux", "ivo://ivoa.net/std/sia#aux", "ivo://ivoa.net/std/tap", "ivo://ivoa.net/std/vosi", ""], intf_roles=["std"] * 4 + ["non standard"], intf_types=["vs:paramhttp"] * 4 + ["vr:webbrowser"], cap_descriptions=["A mock service."] * 5) # webpages should be there, but not vosi assert len(rec.list_interfaces()) == 4 # only sia ones assert len(rec.list_interfaces("sia")) == 2 class _FakeResult: """A fake class just sufficient for giving dal.query.Record enough to pull in the dict this is constructed. As an extra service, list values are stringified with regtap.TOKEN_SEP -- this is how they ought to come in from RegTAP services. """ def __init__(self, d): self.fieldnames = list(d.keys()) vals = [regtap.TOKEN_SEP.join(v) if isinstance(v, list) else v for v in d.values()] class _: class array: data = [vals] self.resultstable = _ def _makeRegistryRecord(**overrides): """returns a minimal RegistryResource instance, overriding some built-in defaults with the dict overrides. """ defaults = { "access_urls": "", "standard_ids": "", "intf_types": "", "intf_roles": "", "cap_descriptions": "", "ivoid": "ivo://pyvo/test_regtap.py", } defaults.update(overrides) return regtap.RegistryResource(_FakeResult(defaults), 0) class TestInterfaceRejection: """tests for various artificial corner cases where interface selection should fail (or just not fail). """ def test_nonunique(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2) with pytest.raises(ValueError, match="Multiple matching interfaces found.*"): rsc.get_service(service_type="tap", lax=False) def test_nonunique_lax(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2, cap_descriptions=["Test TAP service."] * 2) assert (rsc.get_service(service_type="tap", lax=True)._baseurl == "http://a") def test_nonstd_ignored(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std", ""]) assert (rsc.get_service(service_type="tap", lax=False)._baseurl == "http://a") def test_select_single_matching_service(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["", "ivo://ivoa.net/std/tap"], intf_types=["vs:webbrowser", "vs:paramhttp"], intf_roles=["", "std"]) assert (rsc.service._baseurl == "http://b") # this makes sure caching the service obtained doesn't break # things assert (rsc.service._baseurl == "http://b") def test_capless(self): rsc = _makeRegistryRecord() with pytest.raises(ValueError) as excinfo: rsc.service._baseurl assert str(excinfo.value) == ( "No matching interface.") @pytest.mark.remote_data def test_maxrec(): with pytest.warns(DALOverflowWarning) as w: _ = regsearch(servicetype="tap", maxrec=1) assert len(w) == 1 assert str(w[0].message).startswith("Result set limited") @pytest.mark.remote_data def test_get_contact(): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") assert (rsc.get_contact() == "GAVO Data Centre Team (+49 6221 54 1837)" " ") @pytest.mark.remote_data def test_get_alt_identifier(): rsc = _makeRegistryRecord(ivoid="ivo://cds.vizier/i/337") assert set(rsc.get_alt_identifiers()) == { 'doi:10.26093/cds/vizier.1337', } @pytest.mark.remote_data class TestDatamodelQueries: # right now, the data model queries are all rather sui generis, and # rather fickly on top. Let's make sure they actually return something # against the live registry. Admittedly, this is about as much # a test of the VO infrastructure as of pyvo. def test_obscore(self): assert len(regsearch(rtcons.Datamodel('obscore'))) > 0 def test_epntap(self): assert len(regsearch(rtcons.Datamodel('epntap'))) > 0 def test_regtap(self): assert len(regsearch(rtcons.Datamodel('regtap'))) > 0 @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', 'flash_service') class TestExtraResourceMethods: """ tests for methods of RegistryResource containing some non-trivial logic (except service selection, which is in TestInterfaceSelection, and get_tables, which is in TestGetTables). """ def test_unique_standard_id(self): rsc = _makeRegistryRecord( access_urls=["http://a"], standard_ids=["ivo://ivoa.net/std/tap"], intf_types=["vs:paramhttp"], intf_roles=["std"]) assert rsc.standard_id == "ivo://ivoa.net/std/tap" def test_describe_multi(self, flash_service): out = io.StringIO() flash_service.describe(verbose=True, file=out) output = out.getvalue() assert "Flash/Heros SSAP" in output assert ("Access modes: datalink#links-1.1, soda#sync-1.0," " ssa, tap#aux, web" in output) assert "- webpage: http://dc.zah.uni-heidelberg.de/flashheros/q/web/form" in output assert "- tap#aux: http://dc.zah.uni-heidelberg.de/tap" in output # datalink, soda and vosi are not printed here assert "- datalink" not in output assert "Source: 1996A&A...312..539S" in output assert "Authors: Wolf" in output assert "Alternative identifier(s): doi:10.21938/" in output assert "More info: http://dc.zah" in output def test_describe_long_authors_list(self): """Check that long list of authors use et al..""" rsc = _makeRegistryRecord( access_urls=[], standard_ids=["ivo://pyvo/test"], short_name=["name"], intf_types=[], intf_roles=[], creator_seq=["a;" * 6], res_title=["title"] ) out = io.StringIO() rsc.describe(verbose=True, file=out) output = out.getvalue() # output should cut at 5 authors assert "Authors: a, a, a, a, a et al." in output def test_describe_long_author_name(self): """Check that long author names are truncated.""" rsc = _makeRegistryRecord( access_urls=[], standard_ids=["ivo://pyvo/test"], short_name=["name"], intf_types=[], intf_roles=[], creator_seq=["a" * 71], res_title=["title"] ) out = io.StringIO() rsc.describe(verbose=True, file=out) output = out.getvalue() # should cut the long author name at 70 characters assert f"Authors: {'a' * 70}..." in output def test_no_access_url(self): rsc = _makeRegistryRecord( access_urls=[], standard_ids=[], intf_types=[], intf_roles=[]) with pytest.raises(dalq.DALQueryError) as excinfo: rsc.access_url assert str(excinfo.value) == ("The resource" " ivo://pyvo/test_regtap.py has no queriable interfaces.") def test_unique_access_url(self): rsc = _makeRegistryRecord( access_urls=["http://a"], standard_ids=["ivo://ivoa.net/std/tap"], intf_types=["vs:paramhttp"], intf_roles=[""]) assert rsc.access_url == "http://a" def test_ambiguous_access_url_warns(self, recwarn): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2) assert rsc.access_url == "http://a" assert ('The resource ivo://pyvo/test_regtap.py has multipl' in [str(w.message)[:50] for w in recwarn]) # TODO: While I suppose the contact test should keep requiring network, # I think we should can the network responses involved in the following; # the stuff might change upstream any time and then break our unit tests. @pytest.fixture(name='obscore_tables') def _obscore_tables(scope="session"): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/__system__/obscore/obscore") return rsc.get_tables() @pytest.mark.usefixtures("obscore_tables") class TestGetTables: @pytest.mark.remote_data def test_get_tables_limit_enforced(self): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/tap") with pytest.raises(dalq.DALQueryError) as excinfo: rsc.get_tables() assert re.match(r"Resource ivo://org.gavo.dc/tap reports \d+ tables." " Pass a higher table_limit to see them all.", str(excinfo.value)) @pytest.mark.remote_data def test_get_tables_names(self, obscore_tables): assert (list(sorted(obscore_tables.keys())) == ["ivoa.obscore"]) @pytest.mark.remote_data def test_get_tables_table_instance(self, obscore_tables): assert (obscore_tables["ivoa.obscore"].name == "ivoa.obscore") assert (obscore_tables["ivoa.obscore"].description[:42] == "The IVOA-defined obscore table, containing") assert (obscore_tables["ivoa.obscore"].title == "GAVO Data Center Obscore Table") assert (obscore_tables["ivoa.obscore"].origin.ivoid == "ivo://org.gavo.dc/__system__/obscore/obscore") @pytest.mark.remote_data def test_get_tables_column_meta(self, obscore_tables): def getcol(name): for col in obscore_tables["ivoa.obscore"].columns: if name == col.name: return col raise KeyError(name) assert getcol("access_url").datatype.content == "char" assert getcol("access_url").datatype.arraysize == "*" assert getcol("s_region").datatype._extendedtype == "adql:region" assert getcol("access_format").ucd == 'meta.code.mime' assert getcol("em_min").unit == "m" assert (getcol("t_max").utype == "obscore:char.timeaxis.coverage.bounds.limits.stoptime") assert (getcol("t_exptime").description == "Total exposure time") @pytest.mark.remote_data def test_get_tables_utype(self, obscore_tables): assert (obscore_tables["ivoa.obscore"].utype == "ivo://ivoa.net/std/obscore#table-1.1") @pytest.mark.remote_data def test_sia2_service_operation(): svcs = regsearch( servicetype='sia2', ivoid='ivo://cadc.nrc.ca/sia') assert len(svcs) == 1 res = svcs[0].search(pos=(30, 40, 0.1), time=(time.Time(58794.9, format="mjd"), time.Time(58795, format="mjd"))) assert len(res) > 10 assert "s_dec" in res.to_table().columns @pytest.mark.remote_data def test_endpoint_switching(): alt_svc = "https://mast.stsci.edu/vo-tap/api/v0.1/registry" previous_url = regtap.REGISTRY_BASEURL try: regtap.choose_RegTAP_service(alt_svc) assert regtap.get_RegTAP_service()._baseurl == alt_svc res = regtap.search(keywords="wirr") assert len(res) > 0 finally: regtap.choose_RegTAP_service(previous_url) astropy-pyvo-b70558c/pyvo/registry/tests/test_rtcons.py000066400000000000000000000707531510533647000235150ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.registry.rtcons, i.e. RegTAP constraints and query building. """ import datetime from astropy.time import Time from astropy import units as u from astropy.coordinates import SkyCoord from astropy.utils.exceptions import AstropyDeprecationWarning import numpy as np import pytest from pyvo import registry from pyvo.registry import rtcons from pyvo.dal import query as dalq from .commonfixtures import messenger_vocabulary, FAKE_GAVO, FAKE_PLAIN # noqa: F401 # We should make sure non-legacy numpy works as expected for string literal generation np.set_printoptions(legacy=False) def _build_regtap_query_with_fake( *args, service=FAKE_GAVO, **kwargs): return rtcons.build_regtap_query( *args, service=service, **kwargs) def _make_subquery(table, condition): """returns how condition would show up in something produced by SubqueriedConstraint. """ return f"ivoid IN (SELECT DISTINCT ivoid FROM {table} WHERE {condition})" class TestAbstractConstraint: def test_no_search_condition(self): with pytest.raises(NotImplementedError): rtcons.Constraint().get_search_condition(FAKE_GAVO) class TestSQLLiterals: @pytest.fixture(scope="class", autouse=True) def literals(self): class _WithFillers(rtcons.Constraint): _fillers = { "aString": "some harmless stuff", "nastyString": "that's not nasty", "bytes": b"keep this ascii for now", "anInt": 210, "aFloat": 5e7, "numpyStuff": np.float64(23.7), "timestamp": datetime.datetime(2021, 6, 30, 9, 1), } return _WithFillers()._get_sql_literals() def test_strings(self, literals): assert literals["aString"] == "'some harmless stuff'" assert literals["nastyString"] == "'that''s not nasty'" def test_bytes(self, literals): assert literals["bytes"] == "'keep this ascii for now'" def test_int(self, literals): assert literals["anInt"] == "210" def test_float(self, literals): assert literals["aFloat"] == "50000000.0" def test_numpy(self, literals): assert float(literals["numpyStuff"]) - 23.7 < 1e-10 def test_timestamp(self, literals): assert literals["timestamp"] == "'2021-06-30T09:01:00'" def test_odd_type_rejected(self): with pytest.raises(ValueError) as excinfo: rtcons.make_sql_literal({}) assert str(excinfo.value) == "Cannot format {} as a SQL literal" class TestFreetextConstraint: def test_basic(self): assert rtcons.Freetext("star").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_description, 'star') " "UNION ALL SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_title, 'star') " "UNION ALL SELECT DISTINCT ivoid FROM rr.res_subject" " WHERE rr.res_subject.res_subject ILIKE '%star%')") def test_interesting_literal(self): assert rtcons.Freetext("α Cen's planets").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_description, 'α Cen''s planets') " "UNION ALL SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_title, 'α Cen''s planets') " "UNION ALL SELECT DISTINCT ivoid FROM rr.res_subject" " WHERE rr.res_subject.res_subject" " ILIKE '%α Cen''s planets%')") def test_multipleLiterals(self): assert rtcons.Freetext("term1", "term2").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_description, 'term1') " "UNION ALL SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_title, 'term1') " "UNION ALL SELECT DISTINCT ivoid FROM rr.res_subject" " WHERE rr.res_subject.res_subject ILIKE '%term1%')" " AND " "ivoid IN (SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_description, 'term2') " "UNION ALL SELECT DISTINCT ivoid FROM rr.resource" " WHERE 1=ivo_hasword(res_title, 'term2') " "UNION ALL SELECT DISTINCT ivoid FROM rr.res_subject" " WHERE rr.res_subject.res_subject ILIKE '%term2%')") def test_adaption_to_service(self): assert rtcons.Freetext("term1", "term2").get_search_condition(FAKE_PLAIN) == ( "( 1=ivo_hasword(res_description, 'term1') OR 1=ivo_hasword(res_title, 'term1')" " OR rr.res_subject.res_subject ILIKE '%term1%')" " AND ( 1=ivo_hasword(res_description, 'term2') OR 1=ivo_hasword(res_title, 'term2')" " OR rr.res_subject.res_subject ILIKE '%term2%')") class TestAuthorConstraint: def test_basic(self): assert (rtcons.Author("%Hubble%").get_search_condition(FAKE_GAVO) == _make_subquery("rr.res_role", "role_name LIKE '%Hubble%' AND base_role='creator'")) class TestServicetypeConstraint: def test_standardmap(self): assert (rtcons.Servicetype("scs").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/conesearch')") def test_fulluri(self): assert (rtcons.Servicetype("http://extstandards/invention" ).get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention')") def test_multi(self): assert (rtcons.Servicetype("http://extstandards/invention", "sia" ).get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention'," " 'ivo://ivoa.net/std/sia')") def test_includeaux(self): assert (rtcons.Servicetype("http://extstandards/invention", "sia1" ).include_auxiliary_services().get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention'," " 'http://extstandards/invention#aux'," " 'ivo://ivoa.net/std/sia'," " 'ivo://ivoa.net/std/sia#aux')") def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Servicetype("junk") assert str(excinfo.value) == ("Service type junk is neither" " a full standard URI nor one of the bespoke identifiers" " image, sia, sia1, spectrum, ssap, ssa, scs, conesearch, line, slap," " table, tap, sia2, hats, hips") def test_legacy_term(self): assert (rtcons.Servicetype("conesearch").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/conesearch')") def test_sia2(self): assert ( rtcons.Servicetype("conesearch", "sia2").get_search_condition(FAKE_GAVO) == ("standard_id IN ('ivo://ivoa.net/std/conesearch')" " OR standard_id like 'ivo://ivoa.net/std/sia#query-2.%'")) def test_sia2_aux(self): constraint = rtcons.Servicetype("conesearch", "sia2").include_auxiliary_services() assert (constraint.get_search_condition(FAKE_GAVO) == ("standard_id IN ('ivo://ivoa.net/std/conesearch', 'ivo://ivoa.net/std/conesearch#aux')" " OR standard_id like 'ivo://ivoa.net/std/sia#query-2.%'" " OR standard_id like 'ivo://ivoa.net/std/sia#query-aux-2.%'")) def test_image_deprecated(self): with pytest.warns(AstropyDeprecationWarning): assert (rtcons.Servicetype("image").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/sia')") def test_spectrum_deprecated(self): with pytest.warns(AstropyDeprecationWarning): assert (rtcons.Servicetype("spectrum").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/ssa')") @pytest.mark.usefixtures('messenger_vocabulary') class TestWavebandConstraint: def test_basic(self): assert (rtcons.Waveband("Infrared", "EUV").get_search_condition(FAKE_GAVO) == "1 = ivo_hashlist_has(rr.resource.waveband, 'infrared')" " OR 1 = ivo_hashlist_has(rr.resource.waveband, 'euv')") def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Waveband("junk") assert str(excinfo.value) == ( "Waveband junk is not in the IVOA messenger vocabulary http://www.ivoa.net/rdf/messenger.") def test_normalisation(self): assert (rtcons.Waveband("oPtIcAl").get_search_condition(FAKE_GAVO) == "1 = ivo_hashlist_has(rr.resource.waveband, 'optical')") class TestDatamodelConstraint: def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Datamodel("junk") assert str(excinfo.value) == ( "Unknown data model id junk. Known are: epntap, obscore, obscore_new, regtap.") def test_obscore_new(self): cons = rtcons.Datamodel("obscore_new") assert (cons.get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_table" " NATURAL JOIN rr.resource" " WHERE table_utype LIKE 'ivo://ivoa.net/std/obscore#table-1.%'" " AND res_type = 'vs:catalogresource')") assert (cons._extra_tables == []) def test_obscore(self): cons = rtcons.Datamodel("ObsCore") assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.res_detail", "detail_xpath = '/capability/dataModel/@ivo-id'" " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/obscore%')")) assert cons._extra_tables == [] def test_epntap(self): cons = rtcons.Datamodel("epntap") assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.res_table", "table_utype LIKE 'ivo://vopdc.obspm/std/epncore#schema-2.%'" " OR table_utype LIKE 'ivo://ivoa.net/std/epntap#table-2.%'")) assert cons._extra_tables == [] def test_regtap(self): cons = rtcons.Datamodel("regtap") assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.res_detail", "detail_xpath = '/capability/dataModel/@ivo-id'" " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/RegTAP#1.%')")) assert cons._extra_tables == [] class TestIvoidConstraint: def test_basic(self): cons = rtcons.Ivoid("ivo://example/some_path") assert (cons.get_search_condition(FAKE_GAVO) == "ivoid='ivo://example/some_path'") def test_multiple(self): cons = rtcons.Ivoid( "ivo://org.gavo.dc/tap", "ivo://org.gavo.dc/__system__/siap2/sitewide") assert (cons.get_search_condition(FAKE_GAVO) == ("ivoid='ivo://org.gavo.dc/tap'" " OR ivoid='ivo://org.gavo.dc/__system__/siap2/sitewide'")) class TestUCDConstraint: def test_basic(self): cons = rtcons.UCD("phot.mag;em.opt.%", "phot.mag;em.ir.%") assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.table_column", "ucd LIKE 'phot.mag;em.opt.%' OR ucd LIKE 'phot.mag;em.ir.%'")) @pytest.mark.remote_data class TestUATConstraint: def test_basic(self): cons = rtcons.UAT("solar-flares") assert (cons.get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in ('solar-flares'))") def test_nonterm(self): with pytest.raises(dalq.DALQueryError, match="solarium does not identify"): rtcons.UAT("solarium") def test_wider(self): cons = rtcons.UAT("solar-flares", expand_up=2) assert (cons.get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in" " ('solar-activity', 'solar-flares', 'solar-physics', 'solar-storm'))") def test_narrower(self): cons = rtcons.UAT("solar-activity", expand_down=1) assert (cons.get_search_condition(FAKE_GAVO) == "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in" " ('solar-active-regions', 'solar-activity', 'solar-filaments', 'solar-flares'," " 'solar-magnetic-bright-points', 'solar-prominences', 'solar-storm'))") cons = rtcons.UAT("solar-activity", expand_down=2) assert (cons.get_search_condition(FAKE_GAVO).startswith( "ivoid IN (SELECT DISTINCT ivoid FROM rr.res_subject WHERE res_subject in" " ('ephemeral-active-regions', 'quiescent-solar-prominence'," " 'solar-active-region-filaments'")) class TestSpatialConstraint: def test_point(self): cons = registry.Spatial([23, -40]) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)") assert cons._extra_tables == [] def test_circle_and_order(self): cons = registry.Spatial([23, -40, 0.25], order=7) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(7, CIRCLE(23, -40, 0.25)), coverage)") def test_polygon(self): cons = registry.Spatial([23, -40, 26, -39, 25, -43]) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(6, POLYGON(23, -40, 26, -39, 25, -43)), coverage)") def test_moc(self): cons = registry.Spatial("0/1-3 3/") assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC('0/1-3 3/'), coverage)") def test_moc_and_inclusive(self): cons = registry.Spatial("0/1-3 3/", inclusive=True) assert cons.get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_spatial WHERE 1 = " "CONTAINS(MOC('0/1-3 3/'), coverage) OR coverage IS NULL)") def test_SkyCoord(self): cons = registry.Spatial(SkyCoord(3 * u.deg, -30 * u.deg)) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(6, POINT(3.0, -30.0)), coverage)") def test_SkyCoord_Circle(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(6, CIRCLE(3.0, -30.0, 3)), coverage)") def test_SkyCoord_Circle_RadiusQuantity(self): for radius in [3*u.deg, 180*u.Unit('arcmin'), 10800*u.Unit('arcsec')]: cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), radius)) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(MOC(6, CIRCLE(3.0, -30.0, 3.0)), coverage)") with pytest.raises(ValueError, match="is not of type angle."): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), (1 * u.m))) def test_enclosed(self): cons = registry.Spatial("0/1-3", intersect="enclosed") assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = CONTAINS(coverage, MOC('0/1-3'))") def test_overlaps(self): cons = registry.Spatial("0/1-3", intersect="overlaps") assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spatial", "1 = INTERSECTS(coverage, MOC('0/1-3'))") def test_not_an_intersect_mode(self): with pytest.raises(ValueError, match="'intersect' should be one of 'covers', 'enclosed'," " or 'overlaps' but its current value is 'wrong'."): registry.Spatial("0/1-3", intersect="wrong") def test_no_MOC(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "Current RegTAP service does not support MOC.") def test_no_spatial_table(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) previous = FAKE_GAVO.tables.pop("rr.stc_spatial") try: with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_GAVO) assert (str(excinfo.value) == "stc_spatial missing on current RegTAP service") finally: FAKE_GAVO.tables["rr.spatial"] = previous class TestSpectralConstraint: # These tests might need some float literal fuzziness. I'm just # too lazy at this point to see if pytest has something on board # that would be useful there. def test_energy_float(self): cons = registry.Spectral(1e-19) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "1e-19 BETWEEN spectral_start AND spectral_end") def test_energy_eV(self): cons = registry.Spectral(5 * u.eV) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "8.01088317e-19 BETWEEN spectral_start AND spectral_end")) def test_energy_interval(self): cons = registry.Spectral((1e-10 * u.erg, 2e-10 * u.erg)) assert cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") def test_wavelength(self): cons = registry.Spectral(5000 * u.Angstrom) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "3.9728917142978567e-19 BETWEEN spectral_start AND spectral_end")) def test_wavelength_interval(self): cons = registry.Spectral((20 * u.cm, 22 * u.cm)) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "1 = ivo_interval_overlaps(spectral_start, spectral_end," " 9.932229285744642e-25, 9.029299350676949e-25)")) def test_frequency(self): cons = registry.Spectral(2 * u.GHz) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "1.32521403e-24 BETWEEN spectral_start AND spectral_end")) def test_frequency_and_inclusive(self): cons = registry.Spectral(2 * u.GHz, inclusive=True) assert (cons.get_search_condition(FAKE_GAVO) == "(ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_spectral" " WHERE 1.32521403e-24 BETWEEN spectral_start AND spectral_end))" " OR NOT EXISTS(SELECT 1 FROM rr.stc_spectral AS inner_s" " WHERE inner_s.ivoid=rr.resource.ivoid)") def test_frequency_interval(self): cons = registry.Spectral((88 * u.MHz, 102 * u.MHz)) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_spectral", "1 = ivo_interval_overlaps(" "spectral_start, spectral_end, 5.830941732e-26, 6.758591553e-26)")) def test_no_spectral(self): cons = registry.Spectral((88 * u.MHz, 102 * u.MHz)) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "stc_spectral missing on current RegTAP service") class TestTemporalConstraint: def test_plain_float(self): cons = registry.Temporal((54130, 54200)) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_temporal", "1 = ivo_interval_overlaps(time_start, time_end, 54130, 54200)")) def test_plain_float_and_inclusive(self): cons = registry.Temporal((54130, 54200), inclusive=True) assert (cons.get_search_condition(FAKE_GAVO) == "(ivoid IN (SELECT DISTINCT ivoid FROM rr.stc_temporal" " WHERE 1 = ivo_interval_overlaps(time_start, time_end, 54130, 54200)))" " OR NOT EXISTS(SELECT 1 FROM rr.stc_temporal AS inner_t" " WHERE inner_t.ivoid=rr.resource.ivoid)") def test_single_time(self): cons = registry.Temporal(Time('2022-01-10')) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery("rr.stc_temporal", "59589.0 BETWEEN time_start AND time_end")) def test_time_interval(self): cons = registry.Temporal((Time(2459000, format='jd'), Time(59002, format='mjd'))) assert (cons.get_search_condition(FAKE_GAVO) == _make_subquery( "rr.stc_temporal", "1 = ivo_interval_overlaps(time_start, time_end, 58999.5, 59002.0)")) def test_multi_times_rejected(self): with pytest.raises(ValueError) as excinfo: registry.Temporal(Time(['1999-01-01', '2010-01-01'])) assert (str(excinfo.value) == "RegTAP time constraints must" " be made from single time instants.") def test_no_temporal(self): cons = registry.Temporal((Time(2459000, format='jd'), Time(59002, format='mjd'))) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "stc_temporal missing on current RegTAP service") class TestWhereClauseBuilding: @staticmethod def where_clause_for(*args, **kwargs): cons = list(args) + rtcons.keywords_to_constraints(kwargs) return _build_regtap_query_with_fake(cons).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] @pytest.mark.usefixtures('messenger_vocabulary') def test_from_constraints(self): assert self.where_clause_for( rtcons.Waveband("EUV"), rtcons.Author("%Hubble%"))\ == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" " AND ({})".format( _make_subquery( "rr.res_role", "role_name LIKE '%Hubble%' AND base_role='creator'"))) @pytest.mark.usefixtures('messenger_vocabulary') def test_from_keywords(self): assert self.where_clause_for( waveband="EUV", author="%Hubble%")\ == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n AND ({})".format( _make_subquery("rr.res_role", "role_name LIKE '%Hubble%' AND base_role='creator'"))) @pytest.mark.usefixtures('messenger_vocabulary') def test_mixed(self): assert self.where_clause_for( rtcons.Waveband("EUV"), author="%Hubble%")\ == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" " AND ({})".format( _make_subquery("rr.res_role", "role_name LIKE '%Hubble%' AND base_role='creator'"))) def test_bad_keyword(self): with pytest.raises(TypeError) as excinfo: _build_regtap_query_with_fake( *rtcons.keywords_to_constraints({"foo": "bar"})) # the following assertion will fail when new constraints are # defined (or old ones vanish). I'd say that's a convenient # way to track changes; so, let's update the assertion as we # go. assert str(excinfo.value) == ("foo is not a valid registry" " constraint keyword. Use one of" " author, datamodel, ivoid, keywords, servicetype," " spatial, spectral, temporal, uat, ucd, waveband.") def test_with_legacy_keyword(self): assert self.where_clause_for( "plain", "string" ) == ( '(ivoid IN (SELECT DISTINCT ivoid FROM rr.resource WHERE ' "1=ivo_hasword(res_description, 'plain') UNION ALL SELECT DISTINCT ivoid FROM rr.resource " "WHERE 1=ivo_hasword(res_title, 'plain') UNION ALL SELECT DISTINCT ivoid FROM " "rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%plain%'))\n" ' AND (ivoid IN (SELECT DISTINCT ivoid FROM rr.resource WHERE ' "1=ivo_hasword(res_description, 'string') UNION ALL SELECT DISTINCT ivoid FROM rr.resource " "WHERE 1=ivo_hasword(res_title, 'string') UNION ALL SELECT DISTINCT ivoid FROM " "rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%string%'))") class TestSelectClause: def test_expected_columns(self): # This will break as regtap.RegistryResource.expected_columns # is changed. Just update the assertion then. assert _build_regtap_query_with_fake( rtcons.keywords_to_constraints({"author": "%Hubble%"}) ).split("\nFROM\nrr.resource\n")[0] == ( "SELECT\n" "ivoid, " "res_type, " "short_name, " "res_title, " "content_level, " "res_description, " "reference_url, " "creator_seq, " "created, " "updated, " "rights, " "content_type, " "source_format, " "source_value, " "region_of_regard, " "waveband, " "\n ivo_string_agg(COALESCE(access_url, ''), ':::py VO sep:::') AS access_urls, " "\n ivo_string_agg(COALESCE(standard_id, ''), ':::py VO sep:::') AS standard_ids, " "\n ivo_string_agg(COALESCE(intf_type, ''), ':::py VO sep:::') AS intf_types, " "\n ivo_string_agg(COALESCE(intf_role, ''), ':::py VO sep:::') AS intf_roles, " "\n ivo_string_agg(COALESCE(cap_description, ''), ':::py VO sep:::') AS cap_descriptions") def test_group_by_columns(self): # Again, this will break as regtap.RegistryResource.expected_columns # is changed. Just update the assertion then. assert (_build_regtap_query_with_fake([rtcons.Author("%Hubble%")]).split("\nGROUP BY\n")[-1] == ("ivoid, " "res_type, " "short_name, " "res_title, " "content_level, " "res_description, " "reference_url, " "creator_seq, " "created, " "updated, " "rights, " "content_type, " "source_format, " "source_value, " "region_of_regard, " "waveband")) def test_joined_tables(self): expected_tables = [ # from author constraint "rr.res_role", # default tables "rr.resource", "rr.capability", "rr.interface", ] assert all(table in _build_regtap_query_with_fake([rtcons.Author("%Hubble%")]) for table in expected_tables) @pytest.mark.remote_data def test_all_constraints(): text = rtcons.Freetext("star") author = rtcons.Author(r"%ESA%") servicetype = rtcons.Servicetype("tap") waveband = rtcons.Waveband("optical") datamodel = rtcons.Datamodel("obscore") ivoid = rtcons.Ivoid(r"ivoid") ucd = rtcons.UCD(r"pos.eq.ra") moc = rtcons.Spatial("0/0-11", intersect="overlaps") spectral = rtcons.Spectral((5000 * u.Angstrom, 6000 * u.Angstrom)) time = rtcons.Temporal((50000, 60000)) uat = rtcons.UAT('galaxies', expand_down=3) result = registry.search( text, author, servicetype, waveband, datamodel, ivoid, ucd, moc, spectral, time, uat ) assert result.fieldnames == ( 'ivoid', 'res_type', 'short_name', 'res_title', 'content_level', 'res_description', 'reference_url', 'creator_seq', 'created', 'updated', 'rights', 'content_type', 'source_format', 'source_value', 'region_of_regard', 'waveband', 'access_urls', 'standard_ids', 'intf_types', 'intf_roles', 'cap_descriptions') astropy-pyvo-b70558c/pyvo/samp.py000066400000000000000000000065521510533647000170700ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module with helpers for broadcasting results to samp clients """ import contextlib import os import tempfile from astropy.samp import SAMPIntegratedClient __all__ = [ 'find_client_id', 'send_table_to', 'send_product_to', 'send_spectrum_to', 'send_image_to', 'accessible_table', 'connection'] def find_client_id(conn, name): """returns the SAMP id of the client with samp.name samp_name. This will raise a KeyError if the client is not on the hub. """ for client_id in conn.get_registered_clients(): if conn.get_metadata(client_id).get("samp.name") == name: return client_id raise KeyError(name) def send_table_to(conn, table, client_name=None, name="data"): """ sends astropy_table via SAMP. """ with accessible_table(table) as url: message = { "samp.mtype": "table.load.votable", "samp.params": { "url": url, "name": name, }, } if client_name is None: for client_id in conn.get_registered_clients(): conn.call_and_wait(client_id, message, "10") else: client_id = find_client_id(conn, client_name) conn.call_and_wait(client_id, message, "10") def send_product_to(conn, url, mtype, client_name=None, name="data"): """ sends SAMP messages to load data. This is a helper for send_spectrum_to and send_image_to, which work exactly analogous to each other, except that the mtypes are different. If dest_client_id, this is a broadcast (and we don't wait for any responses). If dest_client_id is given, we wait for acknowledgement by the receiver. """ message = { "samp.mtype": mtype, "samp.params": { "url": url, "name": name, }, } if client_name is None: conn.notify_all(message) else: client_id = find_client_id(conn, client_name) conn.notify(client_id, message) def send_spectrum_to(conn, url, client_name=None, name="data"): """ asks a spectrum client to open a remote spectrum via SAMP. """ send_product_to( conn, url, "spectrum.load.ssa-generic", client_name=client_name, name=name) def send_image_to(conn, url, client_name=None, name="data"): """ asks an image client to open a remote image via SAMP. """ send_product_to( conn, url, "image.load.fits", client_name=client_name, name=name) @contextlib.contextmanager def accessible_table(table): """ a context manager making astropy_table available under a (file) URL for the controlled section. """ handle, f_name = tempfile.mkstemp(suffix=".xml") with open(handle, "w") as f: table.write(output=f, format="votable") try: yield "file://" + f_name finally: os.unlink(f_name) @contextlib.contextmanager def connection( client_name="pyvo client", description="A generic PyVO client", **kwargs ): """ a context manager to give the controlled block a SAMP connection. The program will disconnect as the controlled block is exited. """ client = SAMPIntegratedClient( name=client_name, description=description, **kwargs) client.connect() try: yield client finally: client.disconnect() astropy-pyvo-b70558c/pyvo/utils/000077500000000000000000000000001510533647000167065ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/utils/__init__.py000066400000000000000000000001221510533647000210120ustar00rootroot00000000000000from .compat import * from .prototype import prototype_feature, activate_features astropy-pyvo-b70558c/pyvo/utils/compat.py000066400000000000000000000001771510533647000205500ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Placeholder for compatibility constructs """ __all__ = [] astropy-pyvo-b70558c/pyvo/utils/decorators.py000066400000000000000000000010721510533647000214250ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from functools import partial, wraps def stream_decode_content(func): @wraps(func) def wrapper(*args, **kwargs): raw = func(*args, **kwargs) raw.read = partial(raw.read, decode_content=True) return raw return wrapper def response_decode_content(func): @wraps(func) def wrapper(*args, **kwargs): response = func(*args, **kwargs) response.raw.read = partial(response.raw.read, decode_content=True) return response return wrapper astropy-pyvo-b70558c/pyvo/utils/formatting.py000066400000000000000000000035361510533647000214410ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A collection of routines to format metadata """ import re from itertools import chain import textwrap _parasp = re.compile(r"(?:[ \t\r\f\v]*\n){2,}[ \t\r\f\v]*") _ptag = re.compile(r"\s*(?:)|(?:\\para(?:\\ )*)\s*") def para_format_desc(text, width=78): """ format description text into paragraphs suitable for display in the shell. That is, the output will be one or more plain text paragraphs of the prescribed width (78 characters, the default). The text will be split into separate paragraphs where there occurs (1) a two or more consecutive carriage return, (2) an HTML paragraph tag, or (2) a LaTeX paragraph control sequence. It will attempt other substitutions of HTML and LaTeX markup that sometimes find their way into resource descriptions. """ paras = _parasp.split(text) paras = filter( bool, chain.from_iterable(_ptag.split(para) for para in paras)) paras = ("\n".join( map(lambda ll: ll.strip(), para.splitlines()) ) for para in paras) paras = map(deref_markup, paras) return "\n\n".join(textwrap.fill(para, width) for para in paras) _musubs = [ (re.compile(r"<"), "<"), (re.compile(r">"), ">"), (re.compile(r"&"), "&"), (re.compile(r""), ''), (re.compile(r"

"), ''), (re.compile(r"°"), " deg"), (re.compile(r"\$((?:[^\$]*[\*\+=/^_~><\\][^\$]*)|(?:\w+))\$"), r'\1'), (re.compile(r"\\deg"), " deg"), ] _alink = re.compile(r'''\s*(\S.*\S)\s*''') def deref_markup(text): """ perform some substitutions of common markup suitable for text display. This includes HTML escape sequence """ for pat, repl in _musubs: text = pat.sub(repl, text) text = _alink.sub(r"\3 <\2>", text) return text astropy-pyvo-b70558c/pyvo/utils/http.py000066400000000000000000000012511510533647000202360ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ HTTP utils """ import platform import requests from ..version import version DEFAULT_USER_AGENT = f'pyVO/{version} Python/{platform.python_version()} ({platform.system()})' def use_session(session): """ Return the session passed in, or create a default session to use for this network request. """ if session: return session else: return create_session() def create_session(): """ Create a new empty requests session with a pyvo user agent. """ session = requests.Session() session.headers['User-Agent'] = DEFAULT_USER_AGENT return session astropy-pyvo-b70558c/pyvo/utils/protofeature.py000066400000000000000000000040341510533647000220000ustar00rootroot00000000000000from dataclasses import dataclass __all__ = ['Feature'] @dataclass class Feature: """ A prototype feature implementing a standard that is currently in the process of being approved, but that might change as a result of the approval process. A Feature must have a name. Optionally, a feature may have a *url* that is displayed to the user in case a feature is used without the user explicitly opting in on its usage. The URL is expected to contain more information about the standard and its state in the approval process. """ name: str url: str = '' on: bool = False def should_error(self): """ Should accessing this feature fail? Returns ------- bool Whether accessing this feature should result in an error. """ return not self.on def error(self, function_name): """ Format an error message when the feature is being accesses without the user having opted in its usage. This function will be used as a callback when an error message needs to be displayed to the user, with the function name that was accessed as an argument. Extensions of this class may have additional information to display. Parameters ---------- function_name: str The name of the function associated to this feature and that the user called. Returns ------- str: The error message to be displayed to the user. """ message = (f'{function_name} is part of a prototype feature ({self.name}) that has not ' 'been activated. For information about prototype features please refer to ' 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html .') if self.url: message += f' For more information about the {self.name} feature please visit {self.url}.' message += (" To suppress this error and enable the feature use " f"`pyvo.utils.activate_features('{self.name}')`") return message astropy-pyvo-b70558c/pyvo/utils/prototype.py000066400000000000000000000103731510533647000213310ustar00rootroot00000000000000import inspect import warnings from functools import wraps from collections.abc import Iterable from .protofeature import Feature from pyvo.dal.exceptions import PyvoUserWarning __all__ = ['features', 'prototype_feature', 'activate_features', 'PrototypeWarning', 'PrototypeError'] features: dict[str, "Feature"] = { 'cadc-tb-upload': Feature('cadc-tb-upload', 'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next', False), 'MIVOT': Feature('MIVOT', 'https://ivoa.net/documents/MIVOT/20230620/REC-mivot-1.0.pdf', False) } def prototype_feature(*args): """ Decorator for functions and classes that implement unstable standards which haven't been approved yet. The decorator can be used to tag individual functions or methods. Please refer to the user documentation for details. Parameters ---------- args: iterable of arguments. Currently, the decorator must always be called with one and only one argument, a string representing the feature's name associated with the decorated class or functions. Additional arguments will be ignored, while using the decorator without any arguments will result in a ``PrototypeError`` error. Returns ------- The class or function it decorates, which will be associated to the feature provided as argument. """ feature_name = _parse_args(*args) decorator = _make_decorator(feature_name) return decorator def _set_features(flag, *feature_names: Iterable[str]): names = feature_names or set(features.keys()) for name in names: if not _validate(name): continue features[name].on = flag def activate_features(*feature_names: Iterable[str]): """ Activate one or more prototype features. Parameters ---------- feature_names: Iterable[str] An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will be issued. If no arguments are provided, all features will be activated Returns ------- """ _set_features(True, *feature_names) def deactivate_features(*feature_names: Iterable[str]): """ De-activate one or more prototype features. Parameters ---------- feature_names: Iterable[str] An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will be issued. If no arguments are provided, all features will be de-activated Returns ------- """ _set_features(False, *feature_names) class PrototypeError(Exception): pass class PrototypeWarning(PyvoUserWarning): pass def _parse_args(*args): if not args or callable(args[0]): raise PrototypeError("The `prototype_feature` decorator must always be called with the " "feature name as an argument") return args[0] def _make_decorator(feature_name): def decorator(decorated): if inspect.isfunction(decorated): return _make_wrapper(feature_name, decorated) if inspect.isclass(decorated): method_infos = inspect.getmembers(decorated, predicate=_should_wrap) _wrap_class_methods(decorated, method_infos, feature_name) return decorated return decorator def _validate(feature_name): if feature_name not in features: warnings.warn(f'No such feature "{feature_name}"', category=PrototypeWarning) return False return True def _warn_or_raise(function, feature_name): _validate(feature_name) feature = features[feature_name] if feature.should_error(): raise PrototypeError(feature.error(function.__name__)) def _should_wrap(member): return inspect.isfunction(member) and not member.__name__.startswith('_') def _wrap_class_methods(decorated_class, method_infos, feature_name): for method_info in method_infos: setattr(decorated_class, method_info[0], _make_wrapper(feature_name, method_info[1])) def _make_wrapper(feature_name, function): @wraps(function) def wrapper(*args, **kwargs): _warn_or_raise(function, feature_name) return function(*args, **kwargs) return wrapper astropy-pyvo-b70558c/pyvo/utils/testing.py000066400000000000000000000022231510533647000207340ustar00rootroot00000000000000""" Miscellenaneous utilities for writing tests. """ from astropy.io.votable import tree from pyvo.dal import query as dalquery try: TABLE_ELEMENT = tree.TableElement except AttributeError: TABLE_ELEMENT = tree.Table def create_votable(field_descs, records): """returns a VOTableFile with a a single table containing records, described by field_descs. """ votable = tree.VOTableFile() resource = tree.Resource(type="results") votable.resources.append(resource) table = TABLE_ELEMENT(votable) resource.tables.append(table) table.fields.extend( tree.Field(votable, **desc) for desc in field_descs) table.create_arrays(len(records)) for index, rec in enumerate(records): table.array[index] = rec return votable def create_dalresults( field_descs, records, *, resultsClass=dalquery.DALResults): """returns a DALResults instance for a query returning records described by field_descs. The arguments are as for create_votable. """ return resultsClass( create_votable(field_descs, records), url="http://testing.pyvo/test-url") astropy-pyvo-b70558c/pyvo/utils/tests/000077500000000000000000000000001510533647000200505ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/utils/tests/__init__.py000066400000000000000000000001001510533647000221500ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst astropy-pyvo-b70558c/pyvo/utils/tests/test_http.py000066400000000000000000000006131510533647000224400ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.http """ import platform from pyvo.utils.http import create_session from pyvo.version import version def test_create_session(): test_session = create_session() assert (test_session.headers['User-Agent'] == f'pyVO/{version} Python/{platform.python_version()} ({platform.system()})') astropy-pyvo-b70558c/pyvo/utils/tests/test_prototype.py000066400000000000000000000076471510533647000235440ustar00rootroot00000000000000from copy import deepcopy from unittest import mock import pytest from pyvo.utils import prototype_feature, activate_features, prototype from pyvo.utils.prototype import PrototypeError, PrototypeWarning, Feature @pytest.fixture(name='prototype_function') def _prototype_function(features): features({ 'my-feature': Feature('my-feature', url='http://somewhere/else') }) @prototype_feature('my-feature') def i_am_prototype(arg): arg('called') return i_am_prototype @pytest.fixture(name='features') def _features(): previous_available = deepcopy(prototype.features) prototype.features.clear() def add(features): prototype.features.update(features) yield add prototype.features.clear() prototype.features.update(previous_available) def test_feature_turned_off_by_default(prototype_function): with pytest.raises(PrototypeError) as e: prototype_function(None) assert 'i_am_prototype is part of a prototype feature (my-feature) that has not been activated. ' in str( e.value) assert 'please visit http://somewhere/else' in str(e.value) assert 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html' in str(e.value) assert "pyvo.utils.activate_features('my-feature')" in str(e.value) def test_activate_feature(prototype_function): probe = mock.Mock() activate_features('my-feature') try: prototype_function(probe) except Exception as exc: assert False, f"Should not have raised {exc}" probe.assert_called_once_with('called') def test_non_existent_feature_warning(): with pytest.warns(PrototypeWarning) as w: activate_features('i dont exist') assert len(w) == 1 assert str(w[0].message) == 'No such feature "i dont exist"' def test_activate_all_features(features): features({ 'feat-one': Feature('feat-one'), 'feat-two': Feature('feat-two') }) activate_features() assert set(prototype.features.keys()) == {'feat-one', 'feat-two'} assert prototype.features['feat-one'].on assert prototype.features['feat-two'].on def test_decorate_class(features, recwarn): features({ 'class': Feature('class') }) probe = mock.Mock() @prototype_feature('class') class SomePrototype: def method(self): probe('method') @staticmethod def static(): probe('static') def __ignore__(self): probe('ignore') with pytest.raises(PrototypeError): SomePrototype.static() with pytest.raises(PrototypeError): SomePrototype().method() SomePrototype().__ignore__() probe.assert_called_once_with('ignore') probe.reset_mock() activate_features('class') SomePrototype.static() probe.assert_called_once_with('static') probe.reset_mock() SomePrototype().method() probe.assert_called_once_with('method') probe.reset_mock() SomePrototype().__ignore__() probe.assert_called_once_with('ignore') def test_decorator_without_args_errors_out(): with pytest.raises(PrototypeError) as e: @prototype_feature def function(): pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") def test_decorator_without_args_around_class(): with pytest.raises(PrototypeError) as e: @prototype_feature class Class: pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") def test_decorator_with_no_arguments_and_class(): with pytest.raises(PrototypeError) as e: @prototype_feature() class Class: pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") astropy-pyvo-b70558c/pyvo/utils/tests/test_url.py000066400000000000000000000004651510533647000222700ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.url """ from pyvo.utils.url import url_sibling def test_url(): url = "http://example.org/tap/capabilities" siblingified = url_sibling(url, "tables") assert siblingified == "http://example.org/tap/tables" astropy-pyvo-b70558c/pyvo/utils/tests/test_vocabularies_remote.py000066400000000000000000000063431510533647000255210ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.vocabularies It's hard to write meaningful tests for those that don't require network connectivity because essentially it's all just wrapping downloads. Hence, I'm just giving in rather than bother with a mock server. """ import os import pathlib import time import pytest from astropy.utils import data from pyvo.dal.exceptions import PyvoUserWarning from pyvo.utils import vocabularies @pytest.mark.remote_data class TestVocabularies: def test_basic_getting(self): # clear the lru cache in case someone else has already used # datalink/core. vocabularies.get_vocabulary.cache_clear() voc = vocabularies.get_vocabulary("datalink/core") assert "progenitor" in voc["terms"] assert data.is_url_in_cache("http://www.ivoa.net/rdf/datalink/core") def test_label_getting(self): voc = vocabularies.get_vocabulary("datalink/core") assert (vocabularies.get_label(voc, "coderived") == "Coderived Data") def test_label_getting_default(self): voc = vocabularies.get_vocabulary("datalink/core") assert vocabularies.get_label(voc, "oov", "Missing") == "Missing" def test_refreshing(self): voc = vocabularies.get_vocabulary("datalink/core", force_update=True) # first make sure that things didn't break assert "progenitor" in voc["terms"] # now guess that a download has actually happened; we don't want # to reflect cache name generation here, so we just check if there's # a recent download in the cache directory dldir = data._get_download_cache_loc() with os.scandir(dldir) as entries: last_change = 0 for entry in entries: last_change = max(last_change, entry.stat().st_mtime) assert time.time() - last_change < 2 def test_non_existing_voc(self): with pytest.raises(vocabularies.VocabularyError): vocabularies.get_vocabulary("not_an_ivoa_vocabulary") def test_failed_update(self): # Create a fake vocabulary and make it so old the machine # will want to refresh it. fake_voc = "http://www.ivoa.net/rdf/astropy-test-failure" cache_dir = pathlib.Path(data._get_download_cache_loc() ) / data._url_to_dirname(fake_voc) cache_dir.mkdir(exist_ok=True) cache_name = cache_dir / "contents" with open(cache_name, "w") as f: f.write("{}") with open(cache_dir / "url", "w") as f: f.write(fake_voc) os.utime(cache_name, (1000000000, 1000000000)) with pytest.warns(PyvoUserWarning) as msgs: vocabularies.get_vocabulary("astropy-test-failure") # this sometimes catches a warning about an unclosed socket that, # I think, originates somewhere else; let me work around it for # the moment. for msg in msgs: if str(msg.message) == ("Updating cache for the vocabulary" " astropy-test-failure failed: HTTP Error 404: Not Found"): break else: raise AssertionError("No warning about failed cache update") astropy-pyvo-b70558c/pyvo/utils/url.py000066400000000000000000000012011510533647000200540ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ URL utils """ from urllib.parse import urlparse, urlunparse from posixpath import split as pathsplit, join as pathjoin def url_sibling(url, sibling): """ Replaces the last path element in an url Parameters ---------- url : str The url for which the last path element should be replaced sibling : str The replace value """ parsed = urlparse(url) newpath_segments = pathsplit(parsed.path)[:-1] + (sibling,) newpath = pathjoin(*newpath_segments) return urlunparse(list(parsed[:2]) + [newpath] + list(parsed[3:])) astropy-pyvo-b70558c/pyvo/utils/vocabularies.py000066400000000000000000000043161510533647000217430ustar00rootroot00000000000000""" A shallow interface to IVOA vocabularies. See http://ivoa.net/documents/Vocabularies/ (>= version 2) for the larger background. In this module, we essentially wrap the retrieval and caching of the desise files. """ import functools import json import os import time import warnings from astropy.utils.data import download_file, clear_download_cache from pyvo.dal.exceptions import PyvoUserWarning IVOA_VOCABULARY_ROOT = "http://www.ivoa.net/rdf/" class VocabularyError(Exception): """A generic error that occurred when interacting with the IVOA vocabulary repository. """ @functools.lru_cache def get_vocabulary(voc_name, force_update=False): """returns an IVOA vocabulary in its "desise" form. See Vocabularies in the VO 2 to see what is inside of this. This will use a cache to avoid repeated updates, but it will attempt to re-download if the cached copy is older than 6 months. """ src_url = IVOA_VOCABULARY_ROOT + voc_name if force_update: clear_download_cache(src_url) try: src_name = download_file( src_url, cache=True, show_progress=False, http_headers={"accept": "application/x-desise+json"}) except Exception as msg: raise VocabularyError("No such vocabulary: {} ({})".format( voc_name, msg)) if time.time() - os.path.getmtime(src_name) > 3600 * 60 * 150: # attempt a re-retrieval, but ignore failure try: src_name = download_file( IVOA_VOCABULARY_ROOT + voc_name, cache="update", show_progress=False, http_headers={"accept": "application/x-desise+json"}) except Exception as msg: warnings.warn("Updating cache for the vocabulary" f" {voc_name} failed: {msg}", category=PyvoUserWarning) with open(src_name, encoding="utf-8") as f: return json.load(f) def get_label(voc, term, default=None): """returns the label of term if it's in the desise vocabulary voc, term capitalised otherwise. """ if term in voc["terms"]: return voc["terms"][term]["label"] else: return default # vi:et:sw=4:sta astropy-pyvo-b70558c/pyvo/utils/xml/000077500000000000000000000000001510533647000175065ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/utils/xml/__init__.py000066400000000000000000000000001510533647000216050ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/utils/xml/elements.py000066400000000000000000000355171510533647000217070ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from inspect import getmembers from functools import partial import warnings from astropy.utils.xml import iterparser from astropy.io.votable.exceptions import warn_or_raise from pyvo.utils.xml.exceptions import UnknownElementWarning __all__ = [ "xmlattribute", "xmlelement", "make_add_complexcontent", "make_add_simplecontent", "Element", "ElementWithXSIType", "ContentMixin", "parse_for_object"] def parse_for_object( source, object_type, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses an xml file (or file-like object), and returns a object of specified object_type. object_type must be a subtype of `~pyvo.utils.xml.elements.Element` type Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. object : object type to return (subtype `~pyvo.utils.xml.elements.Element`) pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- object : `~pyvo.utils.xml.elements.Element` object or None See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = { 'pedantic': pedantic, 'filename': filename } if filename is None and isinstance(source, str): config['filename'] = source with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return object_type( config=config, pos=(1, 1)).parse(iterator, config) class xmlattribute(property): def __init__(self, fget=None, fset=None, fdel=None, doc=None, name=None): super().__init__(fget, fset, fdel, doc) if name: self.name = name elif fget is not None: self.name = fget.__name__ else: raise ValueError( "xmlattribute either needs a getter or a element name or both") def __call__(self, fget): return self.__class__(fget, name=self.name) def getter(self, fget): return self.__class__( fget, self.fset, self.fdel, self.__doc__, self.name) def setter(self, fset): return self.__class__( self.fget, fset, self.fdel, self.__doc__, self.name) def deleter(self, fdel): return self.__class__( self.fget, self.fset, fdel, self.__doc__, self.name) class xmlelement(property): """ """ def __init__( self, fget=None, fset=None, fdel=None, fadd=None, fformat=None, doc=None, name=None, ns=None, plain=False, cls=None, multiple_exc=None ): super().__init__(fget, fset, fdel, doc) if name: self.name = name elif fget is not None: self.name = fget.__name__ else: self.name = None self.ns = ns self.plain = plain self.cls = cls self.multiple_exc = multiple_exc self.fadd = fadd self.fformat = fformat def __call__(self, fget): return self.__class__( fget, name=self.name or fget.__name__, ns=self.ns, plain=self.plain, cls=self.cls, multiple_exc=self.multiple_exc ) def __get__(self, obj, owner=None): if obj is not None: val = super().__get__(obj, owner) if self.plain: return val elif not isinstance(val, (Element, list)): element = ContentMixin(_name=self.name, _ns=self.ns) element.content = val return element else: return val else: return super().__get__(obj, owner) def getter(self, fget): return self.__class__( fget, self.fset, self.fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def setter(self, fset): return self.__class__( self.fget, fset, self.fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def deleter(self, fdel): return type(self)( self.fget, self.fset, fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def adder(self, fadd): if self.cls: raise RuntimeError( 'xmlelement cls parameter has no effect when adder is' ' defined') if self.multiple_exc: raise RuntimeError( 'xmlelement multiple_exc parameter has no effect when' ' adder is defined') return self.__class__( self.fget, self.fset, self.fdel, fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def formatter(self, fformat): return self.__class__( self.fget, self.fset, self.fdel, self.fadd, fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def object_attrs(obj): objtype = type(obj) attrs = { getattr(objtype, name).name: value for name, value in getmembers(obj) if isinstance(getattr(objtype, name, None), xmlattribute)} return attrs def object_children(obj): objtype = type(obj) try: for child in obj: if isinstance(child, Element): yield (child._Element__name, None, child) except TypeError: for name, child in getmembers(obj): if child is None: continue descr = getattr(objtype, name, None) if isinstance(descr, xmlelement): element_name = descr.name if descr.fformat: fformat = partial(descr.fformat, obj) else: fformat = None yield (element_name, fformat, child) elif isinstance(child, Element): yield (child._Element__name, None, child) def object_mapping(obj): objtype = type(obj) for name, val in getmembers(obj): descr = getattr(objtype, name, None) if isinstance(descr, xmlelement): if descr.fadd is None: if descr.cls is None: fadd = make_add_simplecontent( obj, descr.name, name, descr.multiple_exc) else: fadd = make_add_complexcontent( obj, descr.name, name, descr.cls, descr.multiple_exc) else: fadd = partial(descr.fadd, obj) yield descr.name, fadd def make_add_complexcontent( self, element_name, attr_name, cls_, exc_class=None): """ Factory for generating add functions for elements with complex content. """ def add_complexcontent(iterator, tag, data, config, pos): attr = getattr(self, attr_name) element = cls_( config=config, pos=pos, _name=element_name, **data) if attr and exc_class is not None: warn_or_raise( exc_class, args=element_name, config=config, pos=pos) if isinstance(getattr(self, attr_name, None), list): getattr(self, attr_name).append(element) else: setattr(self, attr_name, element) element.parse(iterator, config) return add_complexcontent def make_add_simplecontent( self, element_name, attr_name, exc_class=None, check_func=None, data_func=None): """ Factory for generating add functions for elements with simple content. This means elements with no child elements. If exc_class is given, warn or raise if element was already set. """ def add_simplecontent(iterator, tag_ignored, data_ignored, config, pos_ignored): # Ignored parameters are kept in the API signature to be compatible # with other functions. for start, tag, data, pos in iterator: if not start and tag == element_name: attr = getattr(self, attr_name) if attr and exc_class: warn_or_raise( exc_class, args=self._Element__name, config=config, pos=pos) if check_func: check_func(data, config, pos) if data_func: data = data_func(data) if isinstance(getattr(self, attr_name), list): getattr(self, attr_name).append(data) else: setattr(self, attr_name, data or None) break return add_simplecontent class Element: """ A base class for all classes that represent XML elements. Subclasses and Mixins must initialize their independent attributes after calling ``super().__init__``. """ def __init__(self, config=None, pos=None, _name='', _ns='', **kwargs): if config is None: config = {} self._config = config self._pos = pos self.__name = _name self.__ns = _ns self._tag_mapping = {} def _add_unknown_tag(self, iterator, tag, data, config, pos): if tag != 'xml': warn_or_raise( UnknownElementWarning, UnknownElementWarning, tag, config, pos) def _end_tag(self, tag, data, pos): pass def _ignore_add(self, iterator, tag, data, config, pos): pass def parse(self, iterator, config): """ For internal use. Parse the XML content of the children of the element. Override this method and do after-parse checks after calling ``super().parse``, if you need to. Parameters ---------- iterator : xml iterator An iterator over XML elements as returned by `~astropy.utils.xml.iterparser.get_xml_iterator`. config : dict The configuration dictionary that affects how certain elements are read. """ tag_mapping = dict(object_mapping(self)) for start, tag, data, pos in iterator: if start: tag_mapping.get(tag, self._add_unknown_tag)( iterator, tag, data, config, pos) else: if tag == self._Element__name: self._end_tag(tag, data, pos) break return self def to_xml(self, w, **kwargs): if self._Element__ns: name = ':'.join((self._Element__ns, self._Element__name)) else: name = self._Element__name with w.tag(name, attrib=object_attrs(self)): for name, formatter, child in object_children(self): if isinstance(child, Element): child.to_xml(w, formatter=formatter) else: if formatter: child = formatter() if not child: child = '' w.element(name, str(child)) class ElementWithXSIType(Element): """ An XML element that supports type dispatch through xsi:type. When a class A is derived from this, it gains a decorator register_xsi_type, which classes derived from A can use to say "construct me rather than A when xsi:type has the value I'm putting in. At this point we are doing *no* namespace processing in our XML parsing. Hence, we discard any prefixes both when registering and when matching. We probably should do namespaces one day; astropy.utils.xml will presumably learn them when they add VO-DML support. Let's revisit this when it's there safely. Meanwhere, use canonical Registry prefixes (cf. RegTAP 1.1, sect. 5) everywhere in your code; these will continue to be safe no matter what. """ _xsi_type_mapping = {} @classmethod def register_xsi_type(cls, typename): """Decorator factory for registering subtypes.""" def register(class_): """Decorator for registering subtypes""" cls._xsi_type_mapping[typename.split(":")[-1]] = class_ return class_ return register def __new__(cls, *args, **kwargs): xsi_type = None # Another namespace trouble: people can bind the xsi URI # to anything, *and* it's not unlikely they have another # type attribute, too. Wiggle out of it by preferring a # literal xsi:type and otherwise hope for the best. This # really needs to be fixed when we switch to namespace-aware # parsing. for name, val in kwargs.items(): if name == "xsi:type": xsi_type = val break elif name.split(":")[-1] == "type": xsi_type = val if xsi_type is None: dtype = cls else: try: dtype = cls._xsi_type_mapping[xsi_type.split(":")[-1]] except KeyError: warnings.warn(f"Unknown xsi:type {xsi_type} ignored") dtype = cls obj = Element.__new__(dtype) obj.__init__(*args, **kwargs) return obj class ContentMixin(Element): """ Mixin class for elements with inner content. """ def __init__(self, config=None, pos=None, _name=None, _ns=None, **kwargs): super().__init__(config, pos, _name, _ns, **kwargs) self._content = None def __bool__(self): return bool(self.content) __nonzero__ = __bool__ def _end_tag(self, tag, data, pos): self.content = data def _content_check(self, content): pass def _content_parse(self, content): return content @property def content(self): """The inner content of the element.""" return self._content @content.setter def content(self, content): self._content_check(content) self._content = self._content_parse(content) def to_xml(self, w, **kwargs): if self._Element__ns: name = ':'.join((self._Element__ns, self._Element__name)) else: name = self._Element__name try: content = kwargs['formatter']() except (KeyError, TypeError): content = self.content if content is not None: w.element(name, str(content), attrib=object_attrs(self)) astropy-pyvo-b70558c/pyvo/utils/xml/exceptions.py000066400000000000000000000020661510533647000222450ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.exceptions import AstropyWarning __all__ = ['XMLWarning', 'UnknownElementWarning'] def _format_message(message, name, config=None, pos=None): if config is None: config = {} if pos is None: pos = ('?', '?') filename = config.get('filename', '?') return f'{filename}:{pos[0]}:{pos[1]}: {name}: {message}' class XMLWarning(AstropyWarning): """ Base warning for violations of XML specifications """ def __init__(self, args, config=None, pos=None): if config is None: config = {} if not isinstance(args, tuple): args = (args, ) msg = self.message_template.format(*args) self.formatted_message = _format_message( msg, self.__class__.__name__, config, pos) Warning.__init__(self, self.formatted_message) class UnknownElementWarning(XMLWarning): """ Warning for missing xml elements """ message_template = "Unknown element {}" default_args = ('x',) astropy-pyvo-b70558c/pyvo/utils/xml/tests/000077500000000000000000000000001510533647000206505ustar00rootroot00000000000000astropy-pyvo-b70558c/pyvo/utils/xml/tests/test_elements.py000066400000000000000000000055671510533647000241120ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.xml.elements """ import io import pytest from astropy.utils.xml import iterparser from pyvo.utils.xml import elements class TBase(elements.ElementWithXSIType): pass @TBase.register_xsi_type("foo:TOther1") class TOther1(TBase): pass # it's unclear whether we want to support unprefixed type names # once we properly handle XML namespaces. Feel free to adapt # the following declaration. @TBase.register_xsi_type("TOther2") class TOther2(TBase): pass class _Root(elements.Element): def __init__(self): super().__init__(self, _name="root") self._tbase = None @elements.xmlelement(name="tbase", cls=TBase) def tbase(self): return self._tbase @tbase.setter def tbase(self, obj): self._tbase = obj class TestXSIType: # Note: most of these tests will need namespace declarations # once we're properly dealing with namespaces. However, I # don't want to predicate an API to proper namespace support, # so they're missing for now. def _parse_string(self, xml_source): with iterparser.get_xml_iterator(io.BytesIO(xml_source)) as i: return _Root().parse(i, {}) def test_no_type(self): found_type = self._parse_string(b'').tbase.__class__ assert found_type.__name__ == "TBase" def test_prefixed_type(self): found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_unprefixed_type(self): # This is undesired behaviour; this test should fail once # we've properly parsing XML found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_badprefixed_type(self): found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_ignorable(self): # This is again unwelcome behaviour, but unavoidable as long # as we hack around namespaces found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_preferred(self): # Another piece unwelcome behaviour. found_type = self._parse_string( b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_bad_type(self): with pytest.warns(match='Unknown xsi:type ns1:NoSuchType ignored'): self._parse_string(b'') astropy-pyvo-b70558c/setup.cfg000066400000000000000000000067041510533647000164010ustar00rootroot00000000000000[tool:pytest] minversion = 6.0 norecursedirs = build docs/_build testpaths = "pyvo" "docs" astropy_header = true doctest_plus = enabled text_file_format = rst addopts = --doctest-rst --doctest-continue-on-failure remote_data_strict = true filterwarnings = error ignore:numpy.ndarray size changed:RuntimeWarning ignore:unclosed =4.2 requests python_requires = >=3.9 [options.extras_require] all = pillow defusedxml test = pytest-doctestplus>=0.13 pytest-astropy requests-mock docs = sphinx-astropy [options.package_data] pyvo.auth.tests = data/tap/*.xml pyvo.io.uws.tests = data/*.xml pyvo.io.vosi.tests = data/*.xml, data/tables/*.xml, data/capabilities/*.xml pyvo.registry.tests = data/*.xml, data/*.desise pyvo.mivot.tests = data/*.xml, data/input/*.xml, data/output/*.xml, data/reference/*json, data/reference/*xml pyvo.dal.tests = data/*.xml, data/*/* [coverage:run] source = pyvo omit = pyvo/_astropy_init* pyvo/conftest.py pyvo/*setup_package* pyvo/tests/* pyvo/*/tests/* pyvo/extern/* pyvo/version* */pyvo/_astropy_init* */pyvo/conftest.py */pyvo/*setup_package* */pyvo/tests/* */pyvo/*/tests/* */pyvo/extern/* */pyvo/version* [coverage:report] exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about packages we have installed except ImportError # Don't complain if tests don't hit assertions raise AssertionError raise NotImplementedError # Don't complain about script hooks def main\(.*\): # Ignore branches that don't pertain to this version of Python pragma: py{ignore_python_version} # Don't complain about IPython completion helper def _ipython_key_completions_ astropy-pyvo-b70558c/setup.py000066400000000000000000000036411510533647000162670ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst # NOTE: The configuration for the package, including the name, version, and # other information are set in the setup.cfg file. import os import sys from setuptools import setup # First provide helpful messages if contributors try and run legacy commands # for tests or docs. TEST_HELP = """ Note: running tests is no longer done using 'python setup.py test'. Instead you will need to run: tox -e test If you don't already have tox installed, you can install it with: pip install tox If you only want to run part of the test suite, you can also use pytest directly with:: pip install -e .[test] pytest For more information, see: http://docs.astropy.org/en/latest/development/testguide.html#running-tests """ if 'test' in sys.argv: print(TEST_HELP) sys.exit(1) DOCS_HELP = """ Note: building the documentation is no longer done using 'python setup.py build_docs'. Instead you will need to run: tox -e build_docs If you don't already have tox installed, you can install it with: pip install tox You can also build the documentation with Sphinx directly using:: pip install -e .[docs] cd docs make html For more information, see: http://docs.astropy.org/en/latest/install.html#builddocs """ if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv: print(DOCS_HELP) sys.exit(1) VERSION_TEMPLATE = """ # Note that we need to fall back to the hard-coded version if either # setuptools_scm can't be imported or setuptools_scm can't determine the # version, so we catch the generic 'Exception'. try: from setuptools_scm import get_version version = get_version(root='..', relative_to=__file__) except Exception: version = '{version}' """.lstrip() setup(use_scm_version={'write_to': os.path.join('pyvo', 'version.py'), 'write_to_template': VERSION_TEMPLATE}) astropy-pyvo-b70558c/tox.ini000066400000000000000000000043311510533647000160650ustar00rootroot00000000000000[tox] # Please note that not all the combinations below are guaranteed to work # as oldestdeps and devastropy might not support the full python range # listed here envlist = py{39,310,311,312,313,314}-test{,-alldeps,-oldestdeps,-devdeps}{,-online}{,-cov} linkcheck codestyle build_docs requires = setuptools >= 30.3.0 pip >= 19.3.1 [testenv] extras = test alldeps: all description = run tests oldestdeps: with oldest supported dependencies devdeps: with development version of dependencies cov: determine the code coverage setenv = PYTEST_ARGS = -rsxf --show-capture=no online: PYTEST_ARGS = --remote-data=any --reruns=1 --reruns-delay 10 -rsxf --show-capture=no devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/astropy/simple # No astropy py314 wheels on pypi yet py314: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/astropy/simple deps = cov: coverage devdeps: numpy>=0.0.dev0 devdeps: pyerfa>=0.0.dev0 devdeps: astropy>=0.0.dev0 oldestdeps: astropy==4.2 # We set a suitably old numpy along with an old astropy, no need to pick up # deprecations and errors due to their unmatching versions oldestdeps: numpy==1.20 online: pytest-rerunfailures commands = pip freeze !cov: pytest --pyargs {env:PYTEST_ARGS} # Run pytest with coverage to include module imports in report # See https://github.com/pytest-dev/pytest-cov/issues/455 for more info cov: coverage run -m pytest --pyargs --cov-config={toxinidir}/setup.cfg {env:PYTEST_ARGS} cov: coverage xml -o {toxinidir}/coverage.xml [testenv:linkcheck] changedir = docs description = check the links in the HTML docs extras = docs commands = pip freeze sphinx-build -W -b linkcheck . _build/html [testenv:build_docs] changedir = docs description = invoke sphinx-build to build the HTML docs extras = docs commands = pip freeze sphinx-build -W -b html . _build/html [testenv:codestyle] skip_install = true description = check code style deps = flake8 changedir = {toxinidir} commands = flake8 pyvo --count

Examples for the TAP service at HEASARC

Simple geometric query on rosmaster with circle and point

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query searches for observations in the rostmaster catalog within a circle of radius 1 degree of the coordinates (ra,dec)=(50,-85) -- i.e., basically a cone search -- and an exposure longer than 10000 seconds:

            SELECT * FROM rosmaster                      WHERE exposure > 10000 and                            1=CONTAINS(POINT('ICRS', ra, dec),CIRCLE('ICRS', 50, -85, 1))          

Simple geometric query on rosmaster with circle and point

The Table Access Protocol Service at HEASARC allow for simple geometric and cross-match queries. For example, this query searches for observations common to the rostmaster catalog AND the chanmaster catalog with a minimum exposure of 10ks.

	   SELECT * FROM rosmaster as ros	            INNER JOIN chanmaster as chan	                ON ros.name = chan.name	            WHERE ros.exposure > 10000 and chan.exposure > 10000	            ORDER by ros.exposure	             

Simple geometric query on rosmaster with intersects, circle, point

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this (slow) query searches for observations in the rostmaster catalog where a circle of radius 1 degree of the coordinates (ra,dec)=(50,-85) intersects with a circle of 1 degree radius around the center of the pointing:

            SELECT * FROM rosmaster                      WHERE 1=INTERSECTS(CIRCLE('ICRS', ra, dec,1),CIRCLE('ICRS', 50, -85, 1))          

Simple geometric query on rosmaster with polygon

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query searches for observations in the rostmaster catalog where a pointing lies with a user-defined polygon, in this case a triangle with vertices (-5,-5), (5,-5), and (0,5)):

            SELECT * FROM rosmaster                      WHERE  exposure > 10000 AND                             1=CONTAINS(POINT('ICRS', ra, dec),POLYGON('ICRS', -5, -5, 5, -5, 0, 5))          

Simple geometric query on rosmaster, chanmaster with join on distance

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query computes the distance between rostmaster observations and a given point, selecting those observations near it, and ordering the result by that distance:

          SELECT DISTANCE(	      POINT('ICRS', ra, dec),              POINT('ICRS', 266.41683, -29.00781)) AS dist, *	  FROM rosmaster	  WHERE 1=CONTAINS(	      POINT('ICRS', ra, dec),	      CIRCLE('ICRS', 266.41683, -29.00781, 1))	  ORDER BY dist ASC          
ObsCore queries