././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1602304 ccdproc-2.3.0/0000755000076600000240000000000000000000000014231 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.0982473 ccdproc-2.3.0/.github/0000755000076600000240000000000000000000000015571 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/.github/CONTRIBUTING.md0000644000076600000240000000030500000000000020020 0ustar00mattcraigstaff00000000000000Contributing to ccdproc ----------------------- Contributions for ccdproc should follow the [guidelines for contributing to astropy](https://github.com/astropy/astropy/blob/main/CONTRIBUTING.md). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/.github/ISSUE_TEMPLATE.md0000644000076600000240000000127300000000000020301 0ustar00mattcraigstaff00000000000000This is the template for bug reports, if you have a feature request or question you can safely ignore and delete this prefilled text. Include a description of the problem: What are you trying to do (include your code and the **full** traceback)? What did you expect? ``` Include a minimal example to reproduce the issue including output and traceback. The triple backticks make github render this as a multi-line code block. ``` Don't forget to include the version of astropy, ccdproc and numpy, just copy this into your Python interpreter (without the backticks): ``` import astropy print(astropy.__version__) import ccdproc print(ccdproc.__version__) import numpy print(numpy.__version__) ``` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/.github/PULL_REQUEST_TEMPLATE.md0000644000076600000240000000230100000000000021366 0ustar00mattcraigstaff00000000000000Please have a look at the following list and replace the "[ ]" with a "[x]" if the answer to this question is yes. - [ ] For new contributors: Did you add yourself to the "Authors.rst" file? For documentation changes: - [ ] For documentation changes: Does your commit message include a "[skip ci]"? Note that it should not if you changed any examples! For bugfixes: - [ ] Did you add an entry to the "Changes.rst" file? - [ ] Did you add a regression test? - [ ] Does the commit message include a "Fixes #issue_number" (replace "issue_number"). - [ ] Does this PR add, rename, move or remove any existing functions or parameters? For new functionality: - [ ] Did you add an entry to the "Changes.rst" file? - [ ] Did you include a meaningful docstring with Parameters, Returns and Examples? - [ ] Does the commit message include a "Fixes #issue_number" (replace "issue_number"). - [ ] Did you include tests for the new functionality? - [ ] Does this PR add, rename, move or remove any existing functions or parameters? Please note that the last point is not a requirement. It is meant as a check if the pull request potentially breaks backwards-compatibility. ----------------------------------------- ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.0986629 ccdproc-2.3.0/.github/workflows/0000755000076600000240000000000000000000000017626 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1639516641.0 ccdproc-2.3.0/.github/workflows/ci_tests.yml0000644000076600000240000000634600000000000022177 0ustar00mattcraigstaff00000000000000name: CI on: push: pull_request: schedule: # run every Monday at 6am UTC - cron: '0 6 * * 1' env: SETUP_XVFB: True # avoid issues if mpl tries to open a GUI window TOXARGS: '-v' jobs: ci-tests: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} if: "!(contains(github.event.head_commit.message, '[skip ci]') || contains(github.event.head_commit.message, '[ci skip]'))" strategy: matrix: include: - name: 'ubuntu-py38-oldestdeps' os: ubuntu-latest python: '3.8' # Test the oldest supported dependencies on the oldest supported Python tox_env: 'py38-test-oldestdeps' - name: 'macos-py310-astroscrappy11' # Keep this test until astroscrappy 1.1.0 is the oldest supported # version. os: macos-latest python: '3.10' tox_env: 'py310-test-astroscrappy11' - name: 'ubuntu-py39' os: ubuntu-latest python: '3.9' tox_env: 'py39-test-alldeps-numpy119-cov' - name: 'ubuntu-py39-bottleneck' os: ubuntu-latest python: '3.9' tox_env: 'py39-test-alldeps-numpy119-cov-bottleneck' - name: 'ubuntu-py310' os: ubuntu-latest python: '3.10' tox_env: 'py310-test-alldeps-numpy121' - name: 'macos-py39' os: macos-latest python: '3.9' tox_env: 'py39-test-alldeps' - name: 'windows-py310' os: windows-latest python: '3.10' tox_env: 'py310-test-alldeps' - name: 'ubuntu-codestyle' os: ubuntu-latest python: '3.10' tox_env: 'pycodestyle' - name: 'ubuntu-build_docs' os: ubuntu-latest python: '3.10' tox_env: 'build_docs' - name: 'ubuntu-py310-test-alldeps-devdeps' os: ubuntu-latest python: '3.10' tox_env: 'py310-test-alldeps-devdeps' steps: - name: Check out repository uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install base dependencies run: | python -m pip install --upgrade pip python -m pip install tox wheel - name: Install graphviz dependency if: "endsWith(matrix.tox_env, 'build_docs')" run: sudo apt-get -y install graphviz - name: Print Python, pip, setuptools, and tox versions run: | python -c "import sys; print(f'Python {sys.version}')" python -c "import pip; print(f'pip {pip.__version__}')" python -c "import setuptools; print(f'setuptools {setuptools.__version__}')" python -c "import tox; print(f'tox {tox.__version__}')" - name: Run tests if: "! matrix.use_remote_data" run: | tox -e ${{ matrix.tox_env }} -- ${{ matrix.toxposargs }} # - name: Run tests with remote data # if: "matrix.use_remote_data" # run: tox -e ${{ matrix.tox_env }} -- --remote-data=any - name: Upload coverage to codecov if: "endsWith(matrix.tox_env, '-cov')" uses: codecov/codecov-action@v2 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184385.0 ccdproc-2.3.0/.gitignore0000644000076600000240000000140300000000000016217 0ustar00mattcraigstaff00000000000000# 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 # Sphinx docs/api docs/_build # Eclipse editor project files .project .pydevproject .settings # Pycharm editor project files .idea # VSCode editor files .vscode # Packages/installer info .eggs *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib distribute-*.tar.gz pip-wheel-metadata # Other .cache .tox .*.sw[op] *~ *.asv # Mac OSX .DS_Store nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .pytest_cache ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1577217856.0 ccdproc-2.3.0/.gitmodules0000644000076600000240000000000000000000000016374 0ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567459403.0 ccdproc-2.3.0/.mailmap0000644000076600000240000000215600000000000015656 0ustar00mattcraigstaff00000000000000Steve Crawford Matthew Craig Hans Moritz Günther Hans Moritz Günther Anthony Horton Forrest Gasdia Nathan Walker Erik M. Bray Erik M. Bray Erik M. Bray Erik Bray James McCormac Larry Bradley Jennifer Karr Javier Blosco Punyaslok Pattnaik Connor Stotts Connor Stotts JVSN Reddy Yoonsoo P. Bach Jaime A. Alvarado-Montes Julio C. N. Campagnolo ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1637778046.0 ccdproc-2.3.0/.readthedocs.yml0000644000076600000240000000021700000000000017317 0ustar00mattcraigstaff00000000000000version: 2 build: image: latest python: version: 3.8 install: - method: pip path: . extra_requirements: - docs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/AUTHORS.rst0000644000076600000240000000455400000000000016120 0ustar00mattcraigstaff00000000000000******************* Authors and Credits ******************* ccdproc Project Contributors ============================ Project Coordinators -------------------- * Matt Craig (@mwcraig) * Steve Crawford (@crawfordsm) Coordinators Emeritus --------------------- * Michael Seifert (@MSeifert04) Alphabetical list of code contributors -------------------------------------- * Jaime A. Alvarado-Montes (@JAAlvarado-Montes) * Yoonsoo P. Bach (@ysBach) * Kyle Barbary (@kbarbary) * Javier Blasco (@javierblasco) * Larry Bradley (@larrybradley) * Julio C. N. Campagnolo (@juliotux) * Mihai Cara (@mcara) * James Davenport (@jradavenport) * Christoph Deil (@cdeil) * Timothy P. Ellsworth-Bowers (@tbowers7) * Forrest Gasdia (@fgasdia) * Carlos Gomez (@carlgogo) * Yash Gondhalekar (@Yash-10) * Hans Moritz Günther (@hamogu) * Nathan Heidt (@heidtha) * Michael Hlabathe (@hlabathems) * Elias Holte (@Sondanaa) * Anthony Horton (@AnthonyHorton) * Jennifer Karr (@JenniferKarr) * Yücel Kılıç (@yucelkilic) * Kelvin Lee (@laserkelvin) * Pey Lian Lim (@pllim) * James McCormac (@jmccormac01) * Stefan Nelson (@stefannelson) * Joe Philip Ninan (@indiajoe) * Punyaslok Pattnaik (@Punyaslok) * Adrian Price-Whelan (@adrn) * JVSN Reddy (@janga1997) * Luca Rizzi (@lucarizzi) * Evert Rol (@evertrol) * Jenna Ryon (@jryon) * William Schoenell (@wschoenell) * Sourav Singh (@souravsingh) * Brigitta Sipőcz (@bsipocz) * Connor Stotts (@stottsco) * Ole Streicher (@olebole) * Erik Tollerud (@eteq) * Simon Torres (@simontorres) * Zè Vinícius (@mirca) * Josh Walawender (@joshwalawender) * Nathan Walker (@walkerna22) * Benjamin Weiner (@bjweiner) * Jiyong Youn (@hletrd) Additional contributors ----------------------- The people below have helped the project by opening multiple issues, suggesting improvements outside of GitHub, or otherwise assisted the project. * Juan Cabanela (@JuanCab) * @mheida * Sara Ogaz (@SaOgaz) * Jean-Paul Ventura (@jvntra) * Kerry Paterson (@KerryPaterson) * Jane Rigby (@janerigby) * Kris Stern (@kakirastern) * Alexa Villaume (@AlexaVillaume) * Brian York (@york-stsci) * Sylvielsstfr (@sylvielsstfr) (If you have contributed to the ccdproc project and your name is missing, please send an email to the coordinators, or `open a pull request for this page `_ in the `ccdproc repository `_) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111199.0 ccdproc-2.3.0/CHANGES.rst0000644000076600000240000003650700000000000016046 0ustar00mattcraigstaff000000000000002.3.0 (2021-12-21) ------------------ Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The `rebin` function has been more clearly marked with a deprecation milestone. It will be removed in v3. [#780] Bug Fixes ^^^^^^^^^ - Fixes compatibility with ``astroscrappy`` version ``1.1.0`` and deprecates old keyword arguments no longer used by ``astroscrappy``. [#777, #778] 2.2.0 (2021-05-24) ------------------ New Features ^^^^^^^^^^^^ - Image combination is faster for average and sum combine, and improves for all operations if the ``bottleneck`` package is installed. [#741] - Pixel-wise weighting can be done for sum and average combine. [#741] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Bug Fixes ^^^^^^^^^ - When filtering an ``ImageFileCollection`` by keyword value, and not explicitly using a regex search pattern (``regex_match=True``), escape all special characters in the keyword value for a successful search. [#770] - Return mask and uncertainty from ``combine`` even if input images have no mask or uncertainty. [#775] 2.1.1 (2021-03-15) ------------------ New Features ^^^^^^^^^^^^ - Improve integration of ``ImageFileCollection`` with image combination and document that integration [#762] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Add memory_profiler as a test requirement [#739] - Updated test suite to use absolute, not relative imports [#735] Bug Fixes ^^^^^^^^^ - ``test_image_collection.py`` in the test suite no longer produces permanent files on disk and cleans up after itself. [#738] - Change ``Combiner`` to allow accepting either a list or a generator [#757] - ``ImageFileCollection`` now correctly returns an empty collection when an existing collection is filtered restrictively enough to remove all files. [#750] - Logging now preserves all of the arguments when the keyword argument names are not used. [#756] 2.1.0 (2019-12-24) ------------------ New Features ^^^^^^^^^^^^ Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Remove astropy_helpers from the package infrastructure, which also changes how the tests are run and how the documentation is built. [#717] Bug Fixes ^^^^^^^^^ - Update units if gain is applied in ``cosmicray_lacosmic``. [#716, #705] 2.0.1 (2019-09-05) ------------------ New Features ^^^^^^^^^^^^ Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Bug Fixes ^^^^^^^^^ - Move generation of sample directory of images to avoid importing pytest in user installation. [#699, #700] 2.0.0 (2019-09-02) ------------------ New Features ^^^^^^^^^^^^ - Allow initialization of ``ImageFileCollection`` from a list of files with no location set. [#374, #661, #680] - Allow identification of FITS files in ``ImageFileCollection`` based on content of the files instead of file name extension. [#620, #680] - Add option to use regular expression matching when filtering items in ``ImageFileCollection``. [#480, #595, #682] - Added an option to disregard negative values passed to ``create_deviation`` and assume the error is represented by the read noise [#688] - Add ``filter`` method to ``ImageFileCollection`` that creates a new collection by filtering based on header keywords. [#596, #690] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Dropped support for Python 2.x and Astropy 1.x. - Removed deprecated property ``summary_info`` of ``ImageFileCollection``. - Improved handling of large flags in the ``bitfield`` module. [#610, #611] - Improved the performance of several ``ImageFileCollection`` methods. [#599] - Added auto_logging configuration paramenter [#622, #90] - Added support for .fz,.bz2, .Z and .zip file formats in ``ImageFileCollection``. [#623, #644] - Modified weights function to also accept 1D array in ``Combiner``. [#634, #670] - Added warning that ``transform_image`` does not apply the transformation to the WCS [#684] - When creating a new object in ``wcs_transform``, WCS keywords in the header are removed so that they are only stored in the WCS object [#685] - Improved warning for negative values in the array passed to ``create_deviation`` [#688] - Removed support for initializing ``ImageFileCollection`` from a table instead of files. [#680] - More consistent typing of ``ImageFileCollection.summary`` when the collection is empty. [#601, #680] Bug Fixes ^^^^^^^^^ - Function ``median_combine`` now correctly calculates the uncertainty for masked ``CCDData``. [#608] - Function ``combine`` avoids keeping files open unnecessarily. [#629, #630] - Function ``combine`` more accurately estimates memory use when deciding how to chunk files. [#638, #642] - Raise ``ValueError`` error in ``subtract_dark`` for when the errors have different shapes [#674, #677] - Fix problem with column dtypes when initializing ``ImageFileCollection`` from a list of file names. [#662, #680] 1.3.0 (2017-11-1) ----------------- New Features ^^^^^^^^^^^^ - Add representation for ImageFileCollection. [#475, #515] - Added ext parameter and property to ImageFileCollection to specify the FITS extension. [#463] - Add keywords.deleter method to ImageFileCollection. [#474] - Added ``glob_include`` and ``glob_exclude`` parameter to ``ImageFileCollection``. [#484] - Add ``bitfield_to_boolean_mask`` function to convert a ``bitfield`` to a boolean mask (following the numpy conventions). [#460] - Added ``gain_corrected`` option in ccd_process so that calibration files do not need to previously been gain corrected. [#491] - Add a new ``wcs_relax`` argument to ``CCDData.to_header()`` that is passed through to the ``WCS`` method of the same name to allow more flexible handing of headers with SIP distortion. [#501] - ``combine`` now accepts ``numpy.ndarray`` as the input ``img_list``. [#493, #503] - Added ``sum`` option in method for ``combime``. [#500, #508] - Add ``norm_value`` argument to ``flat_correct`` that allows the normalization of the flat frame to be manually specified. [#584, #577] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - removed ability to set unit of CCDData to None. [#451] - deprecated ``summary_info`` property of ``ImageFileCollection`` now raises a deprecation warning. [#486] - Logging will include the abbreviation even if the ``meta`` attribute of the processed ``CCDData`` isn't a ``fits.Header``. [#528] - The ``CCDData`` class and the functions ``fits_ccddata_reader`` and ``fits_ccddata_writer`` will be imported from ``astropy.nddata`` if astropy >= 2.0 is installed (instead of the one defined in ``ccdproc``). [#528] - Building the documentation requires astropy >= 2.0. [#528] - When reading a ``CCDData`` from a file the WCS-related keywords are removed from the header. [#568] - The ``info_file`` argument for ``ImageFileCollection`` is now deprecated. [#585] Bug Fixes ^^^^^^^^^ - ``ImageFileCollection`` now handles Headers with duplicated keywords (other than ``COMMENT`` and ``HISTORY``) by ignoring all but the first. [#467] - The ``ccd`` method of ``ImageFileCollection`` will raise an ``NotImplementedError`` in case the parameter ``overwrite=True`` or ``clobber=True`` is used instead of silently ignoring the parameter. [#527] - The ``sort`` method of ``ImageFileCollection`` now requires an explicitly given ``keys`` argument. [#534] - Fixed a problem with ``CCDData.read`` when the extension wasn't given and the primary HDU contained no ``data`` but another HDU did. In that case the header were not correctly combined. [#541] - Suppress errors during WCS creation in CCDData.read(). [#552] - The generator methods in ``ImageFileCollection`` now don't leave open file handles in case the iterator wasn't advanced or an exception was raised either inside the method itself or during the loop. [#553] - Allow non-string columns when filtering an ``ImageFileCollection`` with a string value. [#567] 1.2.0 (2016-12-13) ------------------ ccdproc has now the following additional dependency: - scikit-image. New Features ^^^^^^^^^^^^ - Add an optional attribute named ``filenames`` to ``ImageFileCollection``, so that users can pass a list of FITS files to the collection. [#374, #403] - Added ``block_replicate``, ``block_reduce`` and ``block_average`` functions. [#402] - Added ``median_filter`` function. [#420] - ``combine`` now takes an additional ``combine_uncertainty_function`` argument which is passed as ``uncertainty_func`` parameter to ``Combiner.median_combine`` or ``Combiner.average_combine``. [#416] - Added ``ccdmask`` function. [#414, #432] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ccdprocs core functions now explicitly add HIERARCH cards. [#359, #399, #413] - ``combine`` now accepts a ``dtype`` argument which is passed to ``Combiner.__init__``. [#391, #392] - Removed ``CaseInsensitiveOrderedDict`` because it is not used in the current code base. [#428] Bug Fixes ^^^^^^^^^ - The default dtype of the ``combine``-result doesn't depend on the dtype of the first CCDData anymore. This also corrects the memory consumption calculation. [#391, #392] - ``ccd_process`` now copies the meta of the input when subtracting the master bias. [#404] - Fixed ``combine`` with ``CCDData`` objects using ``StdDevUncertainty`` as uncertainty. [#416, #424] - ``ccds`` generator from ``ImageFileCollection`` now uses the full path to the file when calling ``fits_ccddata_reader``. [#421 #422] 1.1.0 (2016-08-01) ------------------ New Features ^^^^^^^^^^^^ - Add an additional combination method, ``clip_extrema``, that drops the highest and/or lowest pixels in an image stack. [#356, #358] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ``cosmicray_lacosmic`` default ``satlevel`` changed from 65536 to 65535. [#347] - Auto-identify files with extension ``fts`` as FITS files. [#355, #364] - Raise more explicit exception if unit of uncalibrated image and master do not match in ``subtract_bias`` or ``subtract_dark``. [#361, #366] - Updated the ``Combiner`` class so that it could process images with >2 dimensions. [#340, #375] Bug Fixes ^^^^^^^^^ - ``Combiner`` creates plain array uncertainties when using``average_combine`` or ``median_combine``. [#351] - ``flat_correct`` does not properly scale uncertainty in the flat. [#345, #363] - Error message in weights setter fixed. [#376] 1.0.1 (2016-03-15) ------------------ The 1.0.1 release was a release to fix some minor packaging issues. 1.0.0 (2016-03-15) ------------------ General ^^^^^^^ - ccdproc has now the following requirements: - Python 2.7 or 3.4 or later. - astropy 1.0 or later - numpy 1.9 or later - scipy - astroscrappy - reproject New Features ^^^^^^^^^^^^ - Add a WCS setter for ``CCDData``. [#256] - Allow user to set the function used for uncertainty calculation in ``average_combine`` and ``median_combine``. [#258] - Add a new keyword to ImageFileCollection.files_filtered to return the full path to a file [#275] - Added ccd_process for handling multiple steps. [#211] - CCDData.write now writes multi-extension-FITS files. The mask and uncertainty are saved as extensions if these attributes were set. The name of the extensions can be altered with the parameters ``hdu_mask`` (default extension name ``'MASK'``) and ``hdu_uncertainty`` (default ``'UNCERT'``). CCDData.read can read these files and has the same optional parameters. [#302] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Issue warning if there are no FITS images in an ``ImageFileCollection``. [#246] - The overscan_axis argument in subtract_overscan can now be set to None, to let subtract_overscan provide a best guess for the axis. [#263] - Add support for wildcard and reversed FITS style slicing. [#265] - When reading a FITS file with CCDData.read, if no data exists in the primary hdu, the resultant header object is a combination of the header information in the primary hdu and the first hdu with data. [#271] - Changed cosmicray_lacosmic to use astroscrappy for cleaning cosmic rays. [#272] - CCDData arithmetic with number/Quantity now preserves any existing WCS. [#278] - Update astropy_helpers to 1.1.1. [#287] - Drop support for Python 2.6. [#300] - The ``add_keyword`` parameter now has a default of ``True``, to be more explicit. [#310] - Return name of file instead of full path in ``ImageFileCollection`` generators. [#315] Bug Fixes ^^^^^^^^^ - Adding/Subtracting a CCDData instance with a Quantity with a different unit produced wrong results. [#291] - The uncertainty resulting when combining CCDData will be divided by the square root of the number of combined pixel [#309] - Improve documentation for read/write methods on ``CCDData`` [#320] - Add correct path separator when returning full path from ``ImageFileCollection.files_filtered``. [#325] 0.3.3 (2015-10-24) ------------------ New Features ^^^^^^^^^^^^ - add a ``sort`` method to ImageFileCollection [#274] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Opt in to new container-based builds on travis. [#227] - Update astropy_helpers to 1.0.5. [#245] Bug Fixes ^^^^^^^^^ - Ensure that creating a WCS from a header that contains list-like keywords (e.g. ``BLANK`` or ``HISTORY``) succeeds. [#229, #231] 0.3.2 (never released) ---------------------- There was no 0.3.2 release because of a packaging error. 0.3.1 (2015-05-12) ------------------ New Features ^^^^^^^^^^^^ - Add CCDData generator for ImageCollection [#405] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Add extensive tests to ensure ``ccdproc`` functions do not modify the input data. [#208] - Remove red-box warning about API stability from docs. [#210] - Support astropy 1.0.5, which made changes to ``NDData``. [#242] Bug Fixes ^^^^^^^^^ - Make ``subtract_overscan`` act on a copy of the input data. [#206] - Overscan subtraction failed on non-square images if the overscan axis was the first index, ``0``. [#240, #244] 0.3.0 (2015-03-17) ------------------ New Features ^^^^^^^^^^^^ - When reading in a FITS file, the extension to be used can be specified. If it is not and there is no data in the primary extension, the first extension with data will be used. - Set wcs attribute when reading from a FITS file that contains WCS keywords and write WCS keywords to header when converting to an HDU. [#195] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Updated CCDData to use the new version of NDDATA in astropy v1.0. This breaks backward compatibility with earlier versions of astropy. Bug Fixes ^^^^^^^^^ - Ensure ``dtype`` of combined images matches the ``dtype`` of the ``Combiner`` object. [#189] 0.2.2 (2014-11-05) ------------------ New Features ^^^^^^^^^^^^ - Add dtype argument to `ccdproc.Combiner` to help control memory use [#178] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Added Changes to the docs [#183] Bug Fixes ^^^^^^^^^ - Allow the unit string "adu" to be upper or lower case in a FITS header [#182] 0.2.1 (2014-09-09) ------------------ New Features ^^^^^^^^^^^^ - Add a unit directly from BUNIT if it is available in the FITS header [#169] Other Changes and Additions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Relaxed the requirements on what the metadata must be. It can be anything dict-like, e.g. an astropy.io.fits.Header, a python dict, an OrderedDict or some custom object created by the user. [#167] Bug Fixes ^^^^^^^^^ - Fixed a new-style formating issue in the logging [#170] 0.2 (2014-07-28) ---------------- - Initial release. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/CITATION.rst0000644000076600000240000000343600000000000016203 0ustar00mattcraigstaff00000000000000Citing ccdproc -------------- If you use ccdproc for a project that leads to a publication, whether directly or as a dependency of another package, please include the following acknowledgment: .. code-block:: text This research made use of ccdproc, an Astropy package for image reduction (Craig et al. 20XX). where (Craig et al. 20XX) is a citation to the `Zenodo record `_ of the ccdproc version that was used. We also encourage citations in the main text wherever appropriate. For example, for ccdprpoc v1.3.0.post1 one would cite Craig et al. 2017 with the BibTeX entry (https://zenodo.org/record/1069648/export/hx): .. code-block:: text @misc{matt_craig_2017_1069648, author = {Matt Craig and Steve Crawford and Michael Seifert and Thomas Robitaille and Brigitta Sip{\H o}cz and Josh Walawender and Z\`e Vin{\'{\i}}cius and Joe Philip Ninan and Michael Droettboom and Jiyong Youn and Erik Tollerud and Erik Bray and Nathan Walker and VSN Reddy Janga and Connor Stotts and Hans Moritz G{\"u}nther and Evert Rol and Yoonsoo P. Bach and Larry Bradley and Christoph Deil and Adrian Price-Whelan and Kyle Barbary and Anthony Horton and William Schoenell and Nathan Heidt and Forrest Gasdia and Stefan Nelson and Ole Streicher}, title = {astropy/ccdproc: v1.3.0.post1}, month = dec, year = 2017, doi = {10.5281/zenodo.1069648}, url = {https://doi.org/10.5281/zenodo.1069648} } All ccdproc versions (and more citation formats) can be found at https://doi.org/10.5281/zenodo.1069648. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/CODE_OF_CONDUCT.rst0000644000076600000240000000026400000000000017242 0ustar00mattcraigstaff00000000000000Code of Conduct =============== Ccdproc is an `Astropy`_ affiliated package and we follow the `Astropy Community Code of Conduct `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/LICENSE.rst0000644000076600000240000000273700000000000016056 0ustar00mattcraigstaff00000000000000Copyright (c) 2011-2017, Astropy-ccdproc 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1606594688.0 ccdproc-2.3.0/MANIFEST.in0000644000076600000240000000057600000000000015777 0ustar00mattcraigstaff00000000000000include CITATION.rst include CHANGES.rst include CODE_OF_CONDUCT.rst include LICENSE.rst include README.rst include pyproject.toml include ccdproc/tests/coveragerc include setup.cfg recursive-include ccdproc *.pyx *.c *.pxd recursive-include docs * recursive-include licenses * recursive-include scripts * prune build prune docs/_build prune docs/api global-exclude *.pyc *.o ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1640111994.160403 ccdproc-2.3.0/PKG-INFO0000644000076600000240000000067100000000000015332 0ustar00mattcraigstaff00000000000000Metadata-Version: 2.1 Name: ccdproc Version: 2.3.0 Summary: Astropy affiliated package Home-page: http://ccdproc.readthedocs.io/ Author: Steve Crawford, Matt Craig, and Michael Seifert Author-email: ccdproc@gmail.com License: BSD Platform: UNKNOWN Requires-Python: >=3.7 Provides-Extra: test Provides-Extra: docs License-File: LICENSE.rst License-File: AUTHORS.rst This is a package for reducing optical/IR CCD data that relies on astropy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/README.rst0000644000076600000240000000731100000000000015722 0ustar00mattcraigstaff00000000000000ccdproc ======= .. image:: https://github.com/astropy/ccdproc/workflows/CI/badge.svg :target: https://github.com/astropy/ccdproc/actions :alt: GitHub Actions CI Status .. image:: https://coveralls.io/repos/astropy/ccdproc/badge.svg :target: https://coveralls.io/r/astropy/ccdproc .. image:: https://zenodo.org/badge/13384007.svg :target: https://zenodo.org/badge/latestdoi/13384007 Ccdproc is is an affiliated package for the AstroPy package for basic data reductions of CCD images. The ccdproc package provides many of the necessary tools for processing of ccd images built on a framework to provide error propagation and bad pixel tracking throughout the reduction process. Ccdproc can currently be installed via pip or from the source code. For installation instructions, see the `online documentation`_ or docs/install.rst in this source distribution. Documentation is at `ccdproc.readthedocs.io `_ An extensive `tutorial`_ is currently in development. Contributing ------------ We have had the first stable release, but there is still plenty to do! Please open a new issue or new pull request for bugs, feedback, or new features you would like to see. If there is an issue you would like to work on, please leave a comment and we will be happy to assist. New contributions and contributors are very welcome! New to github or open source projects? If you are unsure about where to start or haven't used github before, please feel free to email `@crawfordsm`_, `@mwcraig`_ or `@mseifert`_. We will more than happily help you make your first contribution. Feedback and feature requests? Is there something missing you would like to see? Please open an issue or send an email to `@mwcraig`_, `@crawfordsm`_ or `@mseifert`_. Questions can also be opened on stackoverflow, twitter, or the astropy email list. Ccdproc follows the `Astropy Code of Conduct`_ and strives to provide a welcoming community to all of our users and contributors. Want more information about how to make a contribution? Take a look at the astropy `contributing`_ and `developer`_ documentation. If you are interested in finacially supporting the project, please consider donating to `NumFOCUS`_ that provides financial management for the Astropy Project. Acknowledgements ---------------- If you have found ccdproc useful to your research, please considering adding a citation to `ccdproc contributors; Craig, M. W.; Crawford, S. M.; Deil, Christoph; Gasdia, Forrest; Gomez, Carlos; Günther, Hans Moritz; Heidt, Nathan; Horton, Anthony; Karr, Jennifer; Nelson, Stefan; Ninan, Joe Phillip; Pattnaik, Punyaslok; Rol, Evert; Schoenell, William; Seifert, Michael; Singh, Sourav; Sipocz, Brigitta; Stotts, Connor; Streicher, Ole; Tollerud, Erik; and Walker, Nathan, 2015, Astrophysics Source Code Library, 1510.007, DOI: 10.5281/zenodo.47652 `_ Thanks to Kyle Barbary (`@kbarbary`_) for designing the `ccdproc` logo. .. _Astropy: https://www.astropy.org/ .. _git: https://git-scm.com/ .. _github: https://github.com .. _Cython: https://cython.org/ .. _online documentation: https://ccdproc.readthedocs.io/en/latest/install.html .. _@kbarbary: https://github.com/kbarbary .. _@crawfordsm: https://github.com/crawfordsm .. _@mwcraig: https://github.com/mwcraig .. _@mseifert: https://github.com/MSeifert04 .. _Astropy Code of Conduct: https://www.astropy.org/about.html#codeofconduct .. _contributing: https://docs.astropy.org/en/stable/index.html#contributing .. _developer: https://docs.astropy.org/en/stable/index.html#developer-documentation .. _tutorial: https://github.com/mwcraig/ccd-reduction-and-photometry-guide .. _NumFOCUS: https://numfocus.org/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.0994751 ccdproc-2.3.0/benchmarks/0000755000076600000240000000000000000000000016346 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1589638582.0 ccdproc-2.3.0/benchmarks/__init__.py0000664000076600000240000000000100000000000020450 0ustar00mattcraigstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621387012.0 ccdproc-2.3.0/benchmarks/benchmarks.py0000664000076600000240000001433600000000000021046 0ustar00mattcraigstaff00000000000000# Write the benchmarking functions here. # See "Writing benchmarks" in the asv docs for more information. from pathlib import Path from tempfile import TemporaryDirectory import numpy as np from astropy.io import fits from ccdproc import combine, ImageFileCollection, Combiner # class TimeCombine: # """ # An example benchmark that times the performance of various kinds # of iterating over dictionaries in Python. # """ # timeout = 240.0 # seconds # def setup(self): # self.working_dir = TemporaryDirectory() # self.path = Path(self.working_dir.name) # size = [2024, 2031] # n_images = 25 # base_name = 'test-combine-{num:03d}.fits' # for num in range(n_images): # data = np.random.normal(size=size) # # Now add some outlying pixels so there is something to clip # n_bad = 50000 # bad_x = np.random.randint(0, high=size[0] - 1, size=n_bad) # bad_y = np.random.randint(0, high=size[1] - 1, size=n_bad) # data[bad_x, bad_y] = np.random.choice([-1, 1], size=n_bad) * (10 + np.random.rand(n_bad)) # hdu = fits.PrimaryHDU(data=np.asarray(data, dtype='float32')) # hdu.header['for_prof'] = 'yes' # hdu.header['bunit'] = 'adu' # hdu.writeto(self.path / base_name.format(num=num), overwrite=True) # self.ic = ImageFileCollection(self.path, '*') # self.files = self.ic.files_filtered(for_prof='yes', include_path=True) # def time_ma_median_dev_mad_std(self): # combine(self.files, sigma_clip=True, # sigma_clip_low_thresh=5, sigma_clip_high_thresh=5, # sigma_clip_func=np.ma.median, # sigma_clip_dev_func=mad_std) class TimeCombiner: """ An example benchmark that times the performance of various kinds of iterating over dictionaries in Python. """ timeout = 240.0 # seconds def setup(self): self.working_dir = TemporaryDirectory() self.path = Path(self.working_dir.name) size = 1 * np.array([1012, 1016]) n_images = 25 base_name = 'test-combine-{num:03d}.fits' for num in range(n_images): data = np.random.normal(size=size) # Now add some outlying pixels so there is something to clip n_bad = 50000 bad_x = np.random.randint(0, high=size[0] - 1, size=n_bad) bad_y = np.random.randint(0, high=size[1] - 1, size=n_bad) data[bad_x, bad_y] = np.random.choice([-1, 1], size=n_bad) * (10 + np.random.rand(n_bad)) hdu = fits.PrimaryHDU(data=np.asarray(data, dtype='float32')) hdu.header['for_prof'] = 'yes' hdu.header['bunit'] = 'adu' hdu.writeto(self.path / base_name.format(num=num), overwrite=True) self.ic = ImageFileCollection(self.path, '*') self.files = self.ic.files_filtered(for_prof='yes', include_path=True) to_combine = [ccd for ccd in self.ic.ccds()] self.combiner = Combiner(to_combine) # def time_sigma_clip_default_settings(self): # self.combiner.sigma_clipping() # def time_sigma_clip_median_std(self): # self.combiner.sigma_clipping(func=np.ma.median) # def time_sigma_clip_bnmedian_std(self): # self.combiner.sigma_clipping(func=bn.median) # def time_use_astropy_sigma_clipping(self): # sigma_clip(self.combiner.data_arr.data, axis=0, copy=False, maxiters=1) def time_average_combine_no_clip_default_args(self): self.combiner.average_combine() def time_average_combine_some_masked_default_args(self): # Mark a random set of points as masked n_bad = 50000 size = self.combiner.data_arr.shape img_num = np.random.randint(0, high=size[0] - 1, size=n_bad) bad_x = np.random.randint(0, high=size[1] - 1, size=n_bad) bad_y = np.random.randint(0, high=size[2] - 1, size=n_bad) self.combiner.data_arr.mask[img_num, bad_x, bad_y] = np.ma.masked self.combiner.average_combine() # def time_average_combine_weights_default_args(self): # # It shouldn't matter for timing what the weights are # self.combiner.weights = np.ones_like(self.combiner.data_arr) # self.combiner.average_combine() def time_median_combine_default_args(self): self.combiner.median_combine() # def time_mad_std_sigma_clip_no_astropy_funcs(self): # self.combiner.sigma_clipping(low_thresh=5, high_thresh=5) # def time_minmax_clip_median_combine(self): # """Try doing some fast clipping to generate # masked values and see if that impacts speed""" # from astropy.stats import mad_std # self.combiner.minmax_clipping(min_clip=-4, max_clip=4) # self.combiner.median_combine(uncertainty_func=bn.nanstd) # def time_mad_std_astropy_sigma_clip_avg_combine(self): # from astropy.stats import mad_std, sigma_clip # self.combiner.data_arr = sigma_clip(self.combiner.data_arr, # sigma=5, # maxiters=1, # stdfunc=mad_std) # self.combiner.average_combine() # def time_mad_banzai_sigma_clip(self): # from banzai.utils.stats import robust_standard_deviation as mad_std # from banzai.utils.stats import median # self.combiner.sigma_clipping(low_thresh=5, high_thresh=5, # func=median, # dev_func=mad_std) # def time_banzai_avg_combine_with_mad_clipping(self): # from banzai.utils.stats import sigma_clipped_mean # sigma_clipped_mean(self.combiner.data_arr, 5, axis=0) # def time_DRAGONS_sigma_clip(self): # from gempy.library.nddops import NDStacker # NDStacker.sigclip(self.combiner.data_arr, # lsigma=5, hsigma=5, max_iters=1) # def time_average_combine_no_clip_regular_mean(self): # self.combiner.average_combine(scale_func=np.average) # def time_average_combine_no_clip_nanmean(self): # # Fails at the moment because np.mean does not take weights # self.combiner.average_combine(scale_func=np.nanmean) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1074874 ccdproc-2.3.0/ccdproc/0000755000076600000240000000000000000000000015646 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621385102.0 ccdproc-2.3.0/ccdproc/__init__.py0000644000076600000240000000214400000000000017760 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ The ccdproc package is a collection of code that will be helpful in basic CCD processing. These steps will allow reduction of basic CCD data as either a stand-alone processing or as part of a pipeline. """ # Affiliated packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * # noqa # ---------------------------------------------------------------------------- # set up namespace from .core import * # noqa from .ccddata import * # noqa from .combiner import * # noqa from .image_collection import * # noqa from astropy import config as _config class Conf(_config.ConfigNamespace): """Configuration parameters for ccdproc.""" auto_logging = _config.ConfigItem( True, 'Whether to automatically log operations to metadata' 'If set to False, there is no need to specify add_keyword=False' 'when calling processing operations.' ) conf = Conf() del _config ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621385102.0 ccdproc-2.3.0/ccdproc/_astropy_init.py0000644000076600000240000000054300000000000021105 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import os __all__ = ['__version__', 'test'] try: from .version import version as __version__ except ImportError: __version__ = '' # Create the test function for self test from astropy.tests.runner import TestRunner test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/ccdproc/ccddata.py0000644000076600000240000000063000000000000017602 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """This module implements the base CCDData class.""" from astropy.nddata import fits_ccddata_reader, fits_ccddata_writer, CCDData __all__ = ['CCDData', 'fits_ccddata_reader', 'fits_ccddata_writer'] # This should be be a tuple to ensure it isn't inadvertently changed # elsewhere. _recognized_fits_file_extensions = ('fit', 'fits', 'fts') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/combiner.py0000644000076600000240000011227000000000000020021 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """This module implements the combiner class.""" import numpy as np from numpy import ma try: import bottleneck as bn except ImportError: HAS_BOTTLENECK = False else: HAS_BOTTLENECK = True from .core import sigma_func from astropy.nddata import CCDData, StdDevUncertainty from astropy.stats import sigma_clip from astropy import log __all__ = ['Combiner', 'combine'] def _default_median(): # pragma: no cover if HAS_BOTTLENECK: return bn.nanmedian else: return np.nanmedian def _default_average(): # pragma: no cover if HAS_BOTTLENECK: return bn.nanmean else: return np.nanmean def _default_sum(): # pragma: no cover if HAS_BOTTLENECK: return bn.nansum else: return np.nansum def _default_std(): # pragma: no cover if HAS_BOTTLENECK: return bn.nanstd else: return np.nanstd class Combiner: """ A class for combining CCDData objects. The Combiner class is used to combine together `~astropy.nddata.CCDData` objects including the method for combining the data, rejecting outlying data, and weighting used for combining frames. Parameters ----------- ccd_iter : list or generator A list or generator of CCDData objects that will be combined together. dtype : str or `numpy.dtype` or None, optional Allows user to set dtype. See `numpy.array` ``dtype`` parameter description. If ``None`` it uses ``np.float64``. Default is ``None``. Raises ------ TypeError If the ``ccd_iter`` are not `~astropy.nddata.CCDData` objects, have different units, or are different shapes. Examples -------- The following is an example of combining together different `~astropy.nddata.CCDData` objects:: >>> import numpy as np >>> import astropy.units as u >>> from astropy.nddata import CCDData >>> from ccdproc import Combiner >>> ccddata1 = CCDData(np.ones((4, 4)), unit=u.adu) >>> ccddata2 = CCDData(np.zeros((4, 4)), unit=u.adu) >>> ccddata3 = CCDData(np.ones((4, 4)), unit=u.adu) >>> c = Combiner([ccddata1, ccddata2, ccddata3]) >>> ccdall = c.average_combine() >>> ccdall # doctest: +FLOAT_CMP CCDData([[ 0.66666667, 0.66666667, 0.66666667, 0.66666667], [ 0.66666667, 0.66666667, 0.66666667, 0.66666667], [ 0.66666667, 0.66666667, 0.66666667, 0.66666667], [ 0.66666667, 0.66666667, 0.66666667, 0.66666667]]...) """ def __init__(self, ccd_iter, dtype=None): if ccd_iter is None: raise TypeError("ccd_iter should be a list or a generator of CCDData objects.") if dtype is None: dtype = np.float64 default_shape = None default_unit = None ccd_list = list(ccd_iter) for ccd in ccd_list: # raise an error if the objects aren't CCDData objects if not isinstance(ccd, CCDData): raise TypeError( "ccd_list should only contain CCDData objects.") # raise an error if the shape is different if default_shape is None: default_shape = ccd.shape else: if not (default_shape == ccd.shape): raise TypeError("CCDData objects are not the same size.") # raise an error if the units are different if default_unit is None: default_unit = ccd.unit else: if not (default_unit == ccd.unit): raise TypeError("CCDData objects don't have the same unit.") self.ccd_list = ccd_list self.unit = default_unit self.weights = None self._dtype = dtype # set up the data array new_shape = (len(ccd_list),) + default_shape self.data_arr = ma.masked_all(new_shape, dtype=dtype) # populate self.data_arr for i, ccd in enumerate(ccd_list): self.data_arr[i] = ccd.data if ccd.mask is not None: self.data_arr.mask[i] = ccd.mask else: self.data_arr.mask[i] = ma.zeros(default_shape) # Must be after self.data_arr is defined because it checks the # length of the data array. self.scaling = None @property def dtype(self): return self._dtype @property def weights(self): """ Weights used when combining the `~astropy.nddata.CCDData` objects. Parameters ---------- weight_values : `numpy.ndarray` or None An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined. """ return self._weights @weights.setter def weights(self, value): if value is not None: if isinstance(value, np.ndarray): if value.shape != self.data_arr.data.shape: if value.ndim != 1: raise ValueError("1D weights expected when shapes of the data and weights differ.") if value.shape[0] != self.data_arr.data.shape[0]: raise ValueError("Length of weights not compatible with specified axis.") self._weights = value else: raise TypeError("weights must be a numpy.ndarray.") else: self._weights = None @property def scaling(self): """ Scaling factor used in combining images. Parameters ---------- scale : function or `numpy.ndarray`-like or None, optional Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a list or array whose length is the number of images in the `~ccdproc.Combiner`. """ return self._scaling @scaling.setter def scaling(self, value): if value is None: self._scaling = value else: n_images = self.data_arr.data.shape[0] if callable(value): self._scaling = [value(self.data_arr[i]) for i in range(n_images)] self._scaling = np.array(self._scaling) else: try: len(value) == n_images self._scaling = np.array(value) except TypeError: raise TypeError("scaling must be a function or an array " "the same length as the number of images.") # reshape so that broadcasting occurs properly for i in range(len(self.data_arr.data.shape)-1): self._scaling = self.scaling[:, np.newaxis] # set up IRAF-like minmax clipping def clip_extrema(self, nlow=0, nhigh=0): """Mask pixels using an IRAF-like minmax clipping algorithm. The algorithm will mask the lowest nlow values and the highest nhigh values before combining the values to make up a single pixel in the resulting image. For example, the image will be a combination of Nimages-nlow-nhigh pixel values instead of the combination of Nimages. Parameters ----------- nlow : int or None, optional If not None, the number of low values to reject from the combination. Default is 0. nhigh : int or None, optional If not None, the number of high values to reject from the combination. Default is 0. Notes ----- Note that this differs slightly from the nominal IRAF imcombine behavior when other masks are in use. For example, if ``nhigh>=1`` and any pixel is already masked for some other reason, then this algorithm will count the masking of that pixel toward the count of nhigh masked pixels. Here is a copy of the relevant IRAF help text [0]_: nlow = 1, nhigh = (minmax) The number of low and high pixels to be rejected by the "minmax" algorithm. These numbers are converted to fractions of the total number of input images so that if no rejections have taken place the specified number of pixels are rejected while if pixels have been rejected by masking, thresholding, or nonoverlap, then the fraction of the remaining pixels, truncated to an integer, is used. References ---------- .. [0] image.imcombine help text. http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?imcombine """ if nlow is None: nlow = 0 if nhigh is None: nhigh = 0 argsorted = np.argsort(self.data_arr.data, axis=0) mg = np.mgrid[[slice(ndim) for i, ndim in enumerate(self.data_arr.shape) if i > 0]] for i in range(-1*nhigh, nlow): # create a tuple with the indices where = tuple([argsorted[i, :, :].ravel()] + [i.ravel() for i in mg]) self.data_arr.mask[where] = True # set up min/max clipping algorithms def minmax_clipping(self, min_clip=None, max_clip=None): """Mask all pixels that are below min_clip or above max_clip. Parameters ----------- min_clip : float or None, optional If not None, all pixels with values below min_clip will be masked. Default is ``None``. max_clip : float or None, optional If not None, all pixels with values above min_clip will be masked. Default is ``None``. """ if min_clip is not None: mask = (self.data_arr < min_clip) self.data_arr.mask[mask] = True if max_clip is not None: mask = (self.data_arr > max_clip) self.data_arr.mask[mask] = True # set up sigma clipping algorithms def sigma_clipping(self, low_thresh=3, high_thresh=3, func=ma.mean, dev_func=ma.std, use_astropy=False, **kwd): """ Pixels will be rejected if they have deviations greater than those set by the threshold values. The algorithm will first calculated a baseline value using the function specified in func and deviation based on dev_func and the input data array. Any pixel with a deviation from the baseline value greater than that set by high_thresh or lower than that set by low_thresh will be rejected. Parameters ----------- low_thresh : positive float or None, optional Threshold for rejecting pixels that deviate below the baseline value. If negative value, then will be convert to a positive value. If None, no rejection will be done based on low_thresh. Default is 3. high_thresh : positive float or None, optional Threshold for rejecting pixels that deviate above the baseline value. If None, no rejection will be done based on high_thresh. Default is 3. func : function, optional Function for calculating the baseline values (i.e. `numpy.ma.mean` or `numpy.ma.median`). This should be a function that can handle `numpy.ma.MaskedArray` objects. **Set to ``'median'`` and set ``use_astropy=True`` for best performance if using a median.** Default is `numpy.ma.mean`. dev_func : function, optional Function for calculating the deviation from the baseline value (i.e. `numpy.ma.std`). This should be a function that can handle `numpy.ma.MaskedArray` objects. Default is `numpy.ma.std`. use_astropy : bool, optional If ``True``, use astropy's `~astropy.stats.sigma_clip`, which is faster and more flexible. The high/low sigma clip parameters are set from ``low_thresh`` and ``high_thresh``. Any remaining keywords are passed in to astropy's `~astropy.stats.sigma_clip`. By default, the number of iterations and other settings will be made to reproduce the behavior of ccdproc's ``sigma_clipping``. """ if use_astropy: copy = kwd.get('copy', False) axis = kwd.get('axis', 0) maxiters = kwd.get('maxiters', 1) self.data_arr.mask = \ sigma_clip(self.data_arr.data, sigma_lower=low_thresh, sigma_upper=high_thresh, axis=axis, copy=copy, maxiters=maxiters, cenfunc=func, stdfunc=dev_func, masked=True, **kwd).mask return # setup baseline values baseline = func(self.data_arr, axis=0) dev = dev_func(self.data_arr, axis=0) # reject values if low_thresh is not None: # check for negative numbers in low_thresh if low_thresh < 0: low_thresh = abs(low_thresh) mask = (self.data_arr - baseline < -low_thresh * dev) self.data_arr.mask[mask] = True if high_thresh is not None: mask = (self.data_arr - baseline > high_thresh * dev) self.data_arr.mask[mask] = True def _get_scaled_data(self, scale_arg): if scale_arg is not None: return self.data_arr * scale_arg if self.scaling is not None: return self.data_arr * self.scaling return self.data_arr def _get_nan_substituted_data(self, data): # Get the data as an unmasked array with masked values filled as NaN if self.data_arr.mask.any(): data = np.ma.filled(data, fill_value=np.nan) else: data = data.data return data def _combination_setup(self, user_func, default_func, scale_to): """ Handle the common pieces of image combination data/mask setup. """ data = self._get_scaled_data(scale_to) # Play it safe for now and only do the nan thing if the user is using # the default combination function. if user_func is None: combo_func = default_func # Subtitute NaN for masked entries data = self._get_nan_substituted_data(data) masked_values = np.isnan(data).sum(axis=0) else: masked_values = self.data_arr.mask.sum(axis=0) combo_func = user_func return data, masked_values, combo_func # set up the combining algorithms def median_combine(self, median_func=None, scale_to=None, uncertainty_func=sigma_func): """ Median combine a set of arrays. A `~astropy.nddata.CCDData` object is returned with the data property set to the median of the arrays. If the data was masked or any data have been rejected, those pixels will not be included in the median. A mask will be returned, and if a pixel has been rejected in all images, it will be masked. The uncertainty of the combined image is set by 1.4826 times the median absolute deviation of all input images. Parameters ---------- median_func : function, optional Function that calculates median of a `numpy.ma.MaskedArray`. Default is `numpy.ma.median`. scale_to : float or None, optional Scaling factor used in the average combined image. If given, it overrides `scaling`. Defaults to None. uncertainty_func : function, optional Function to calculate uncertainty. Defaults is `~ccdproc.sigma_func`. Returns ------- combined_image: `~astropy.nddata.CCDData` CCDData object based on the combined input of CCDData objects. Warnings -------- The uncertainty currently calculated using the median absolute deviation does not account for rejected pixels. """ data, masked_values, median_func = \ self._combination_setup(median_func, _default_median(), scale_to) medianed = median_func(data, axis=0) # set the mask mask = (masked_values == len(self.data_arr)) # set the uncertainty # This still uses numpy for the median because the astropy # code requires that the median function take the argument # overwrite_input and bottleneck doesn't allow that argument. # This is ugly, but setting ignore_nan to True should make sure # that either nans or masks are handled properly. if uncertainty_func is sigma_func: uncertainty = uncertainty_func(data, axis=0, ignore_nan=True) else: uncertainty = uncertainty_func(data, axis=0) # Divide uncertainty by the number of pixel (#309) uncertainty /= np.sqrt(len(self.data_arr) - masked_values) # Convert uncertainty to plain numpy array (#351) # There is no need to care about potential masks because the # uncertainty was calculated based on the data so potential masked # elements are also masked in the data. No need to keep two identical # masks. uncertainty = np.asarray(uncertainty) # create the combined image with a dtype matching the combiner combined_image = CCDData(np.asarray(medianed, dtype=self.dtype), mask=mask, unit=self.unit, uncertainty=StdDevUncertainty(uncertainty)) # update the meta data combined_image.meta['NCOMBINE'] = len(self.data_arr) # return the combined image return combined_image def _weighted_sum(self, data, sum_func): """ Perform weighted sum, used by both ``sum_combine`` and in some cases by ``average_combine``. """ if self.weights.shape != data.shape: # Add extra axes to the weights for broadcasting weights = np.reshape(self.weights, [len(self.weights), 1, 1]) else: weights = self.weights # Turns out bn.nansum has an implementation that is not # precise enough for float32 sums. Doing this should # ensure the sums are carried out as float64 weights = weights.astype('float64') weighted_sum = sum_func(data * weights, axis=0) return weighted_sum, weights def average_combine(self, scale_func=None, scale_to=None, uncertainty_func=_default_std(), sum_func=_default_sum()): """ Average combine together a set of arrays. A `~astropy.nddata.CCDData` object is returned with the data property set to the average of the arrays. If the data was masked or any data have been rejected, those pixels will not be included in the average. A mask will be returned, and if a pixel has been rejected in all images, it will be masked. The uncertainty of the combined image is set by the standard deviation of the input images. Parameters ---------- scale_func : function, optional Function to calculate the average. Defaults to `numpy.nanmean`. scale_to : float or None, optional Scaling factor used in the average combined image. If given, it overrides `scaling`. Defaults to ``None``. uncertainty_func : function, optional Function to calculate uncertainty. Defaults to `numpy.ma.std`. sum_func : function, optional Function used to calculate sums, including the one done to find the weighted average. Defaults to `numpy.nansum`. Returns ------- combined_image: `~astropy.nddata.CCDData` CCDData object based on the combined input of CCDData objects. """ data, masked_values, scale_func = \ self._combination_setup(scale_func, _default_average(), scale_to) # # set up the data # data = self._get_scaled_data(scale_to) # # Subtitute NaN for masked entries # data = self._get_nan_substituted_data(data) # Do NOT modify data after this -- we need it to be intact when we # we get to the uncertainty calculation. if self.weights is not None: weighted_sum, weights = self._weighted_sum(data, sum_func) mean = weighted_sum / sum_func(weights, axis=0) else: mean = scale_func(data, axis=0) # calculate the mask mask = (masked_values == len(self.data_arr)) # set up the deviation uncertainty = uncertainty_func(data, axis=0) # Divide uncertainty by the number of pixel (#309) uncertainty /= np.sqrt(len(data) - masked_values) # Convert uncertainty to plain numpy array (#351) uncertainty = np.asarray(uncertainty) # create the combined image with a dtype that matches the combiner combined_image = CCDData(np.asarray(mean, dtype=self.dtype), mask=mask, unit=self.unit, uncertainty=StdDevUncertainty(uncertainty)) # update the meta data combined_image.meta['NCOMBINE'] = len(data) # return the combined image return combined_image def sum_combine(self, sum_func=None, scale_to=None, uncertainty_func=_default_std()): """ Sum combine together a set of arrays. A `~astropy.nddata.CCDData` object is returned with the data property set to the sum of the arrays. If the data was masked or any data have been rejected, those pixels will not be included in the sum. A mask will be returned, and if a pixel has been rejected in all images, it will be masked. The uncertainty of the combined image is set by the multiplication of summation of standard deviation of the input by square root of number of images. Because sum_combine returns 'pure sum' with masked pixels ignored, if re-scaled sum is needed, average_combine have to be used with multiplication by number of images combined. Parameters ---------- sum_func : function, optional Function to calculate the sum. Defaults to `numpy.nansum` or ``bottleneck.nansum``. scale_to : float or None, optional Scaling factor used in the sum combined image. If given, it overrides `scaling`. Defaults to ``None``. uncertainty_func : function, optional Function to calculate uncertainty. Defaults to `numpy.ma.std`. Returns ------- combined_image: `~astropy.nddata.CCDData` CCDData object based on the combined input of CCDData objects. """ data, masked_values, sum_func = \ self._combination_setup(sum_func, _default_sum(), scale_to) if self.weights is not None: summed, weights = self._weighted_sum(data, sum_func) else: summed = sum_func(data, axis=0) # set up the mask mask = (masked_values == len(self.data_arr)) # set up the deviation uncertainty = uncertainty_func(data, axis=0) # Divide uncertainty by the number of pixel (#309) uncertainty /= np.sqrt(len(data) - masked_values) # Convert uncertainty to plain numpy array (#351) uncertainty = np.asarray(uncertainty) # Multiply uncertainty by square root of the number of images uncertainty *= len(data) - masked_values # create the combined image with a dtype that matches the combiner combined_image = CCDData(np.asarray(summed, dtype=self.dtype), mask=mask, unit=self.unit, uncertainty=StdDevUncertainty(uncertainty)) # update the meta data combined_image.meta['NCOMBINE'] = len(self.data_arr) # return the combined image return combined_image def _calculate_step_sizes(x_size, y_size, num_chunks): """ Calculate the strides in x and y to achieve at least the ``num_chunks`` pieces. Parameters ---------- """ # First we try to split only along fast x axis xstep = max(1, int(x_size / num_chunks)) # More chunks are needed only if xstep gives us fewer chunks than # requested. x_chunks = int(x_size / xstep) if x_chunks >= num_chunks: ystep = y_size else: # The x and y loops are nested, so the number of chunks # is multiplicative, not additive. Calculate the number # of y chunks we need to get at num_chunks. y_chunks = int(num_chunks / x_chunks) + 1 ystep = max(1, int(y_size / y_chunks)) return xstep, ystep def _calculate_size_of_image(ccd, combine_uncertainty_function): # If uncertainty_func is given for combine this will create an uncertainty # even if the originals did not have one. In that case we need to create # an empty placeholder. if ccd.uncertainty is None and combine_uncertainty_function is not None: ccd.uncertainty = StdDevUncertainty(np.zeros(ccd.data.shape)) size_of_an_img = ccd.data.nbytes try: size_of_an_img += ccd.uncertainty.array.nbytes # In case uncertainty is None it has no "array" and in case the "array" is # not a numpy array: except AttributeError: pass # Mask is enforced to be a numpy.array across astropy versions if ccd.mask is not None: size_of_an_img += ccd.mask.nbytes # flags is not necessarily a numpy array so do not fail with an # AttributeError in case something was set! # TODO: Flags are not taken into account in Combiner. This number is added # nevertheless for future compatibility. try: size_of_an_img += ccd.flags.nbytes except AttributeError: pass return size_of_an_img def combine(img_list, output_file=None, method='average', weights=None, scale=None, mem_limit=16e9, clip_extrema=False, nlow=1, nhigh=1, minmax_clip=False, minmax_clip_min=None, minmax_clip_max=None, sigma_clip=False, sigma_clip_low_thresh=3, sigma_clip_high_thresh=3, sigma_clip_func=ma.mean, sigma_clip_dev_func=ma.std, dtype=None, combine_uncertainty_function=None, **ccdkwargs): """ Convenience function for combining multiple images. Parameters ----------- img_list : `numpy.ndarray`, list or str A list of fits filenames or `~astropy.nddata.CCDData` objects that will be combined together. Or a string of fits filenames separated by comma ",". output_file : str or None, optional Optional output fits file-name to which the final output can be directly written. Default is ``None``. method : str, optional Method to combine images: - ``'average'`` : To combine by calculating the average. - ``'median'`` : To combine by calculating the median. - ``'sum'`` : To combine by calculating the sum. Default is ``'average'``. weights : `numpy.ndarray` or None, optional Weights to be used when combining images. An array with the weight values. The dimensions should match the the dimensions of the data arrays being combined. Default is ``None``. scale : function or `numpy.ndarray`-like or None, optional Scaling factor to be used when combining images. Images are multiplied by scaling prior to combining them. Scaling may be either a function, which will be applied to each image to determine the scaling factor, or a list or array whose length is the number of images in the `Combiner`. Default is ``None``. mem_limit : float, optional Maximum memory which should be used while combining (in bytes). Default is ``16e9``. clip_extrema : bool, optional Set to True if you want to mask pixels using an IRAF-like minmax clipping algorithm. The algorithm will mask the lowest nlow values and the highest nhigh values before combining the values to make up a single pixel in the resulting image. For example, the image will be a combination of Nimages-low-nhigh pixel values instead of the combination of Nimages. Parameters below are valid only when clip_extrema is set to True, see :meth:`Combiner.clip_extrema` for the parameter description: - ``nlow`` : int or None, optional - ``nhigh`` : int or None, optional minmax_clip : bool, optional Set to True if you want to mask all pixels that are below minmax_clip_min or above minmax_clip_max before combining. Default is ``False``. Parameters below are valid only when minmax_clip is set to True, see :meth:`Combiner.minmax_clipping` for the parameter description: - ``minmax_clip_min`` : float or None, optional - ``minmax_clip_max`` : float or None, optional sigma_clip : bool, optional Set to True if you want to reject pixels which have deviations greater than those set by the threshold values. The algorithm will first calculated a baseline value using the function specified in func and deviation based on sigma_clip_dev_func and the input data array. Any pixel with a deviation from the baseline value greater than that set by sigma_clip_high_thresh or lower than that set by sigma_clip_low_thresh will be rejected. Default is ``False``. Parameters below are valid only when sigma_clip is set to True. See :meth:`Combiner.sigma_clipping` for the parameter description. - ``sigma_clip_low_thresh`` : positive float or None, optional - ``sigma_clip_high_thresh`` : positive float or None, optional - ``sigma_clip_func`` : function, optional - ``sigma_clip_dev_func`` : function, optional dtype : str or `numpy.dtype` or None, optional The intermediate and resulting ``dtype`` for the combined CCDs. See `ccdproc.Combiner`. If ``None`` this is set to ``float64``. Default is ``None``. combine_uncertainty_function : callable, None, optional If ``None`` use the default uncertainty func when using average, median or sum combine, otherwise use the function provided. Default is ``None``. ccdkwargs : Other keyword arguments for `astropy.nddata.fits_ccddata_reader`. Returns ------- combined_image : `~astropy.nddata.CCDData` CCDData object based on the combined input of CCDData objects. """ if not isinstance(img_list, list): # If not a list, check whether it is a numpy ndarray or string of # filenames separated by comma if isinstance(img_list, np.ndarray): img_list = img_list.tolist() elif isinstance(img_list, str) and (',' in img_list): img_list = img_list.split(',') else: try: # Maybe the input can be made into a list, so try that img_list = list(img_list) except TypeError: raise ValueError( "unrecognised input for list of images to combine.") # Select Combine function to call in Combiner if method == 'average': combine_function = 'average_combine' elif method == 'median': combine_function = 'median_combine' elif method == 'sum': combine_function = 'sum_combine' else: raise ValueError("unrecognised combine method : {0}.".format(method)) # First we create a CCDObject from first image for storing output if isinstance(img_list[0], CCDData): ccd = img_list[0].copy() else: # User has provided fits filenames to read from ccd = CCDData.read(img_list[0], **ccdkwargs) if dtype is None: dtype = np.float64 # Convert the master image to the appropriate dtype so when overwriting it # later the data is not downcast and the memory consumption calculation # uses the internally used dtype instead of the original dtype. #391 if ccd.data.dtype != dtype: ccd.data = ccd.data.astype(dtype) # If the template image doesn't have an uncertainty, add one, because the # result always has an uncertainty. if ccd.uncertainty is None: ccd.uncertainty = StdDevUncertainty(np.zeros_like(ccd.data)) # If the template doesn't have a mask, add one, because the result may have # a mask if ccd.mask is None: ccd.mask = np.zeros_like(ccd.data, dtype=bool) size_of_an_img = _calculate_size_of_image(ccd, combine_uncertainty_function) no_of_img = len(img_list) # Set a memory use factor based on profiling if method == 'median': memory_factor = 3 else: memory_factor = 2 memory_factor *= 1.3 # determine the number of chunks to split the images into no_chunks = int((memory_factor * size_of_an_img * no_of_img) / mem_limit) + 1 if no_chunks > 1: log.info('splitting each image into {0} chunks to limit memory usage ' 'to {1} bytes.'.format(no_chunks, mem_limit)) xs, ys = ccd.data.shape # Calculate strides for loop xstep, ystep = _calculate_step_sizes(xs, ys, no_chunks) # Dictionary of Combiner properties to set and methods to call before # combining to_set_in_combiner = {} to_call_in_combiner = {} # Define all the Combiner properties one wants to apply before combining # images if weights is not None: to_set_in_combiner['weights'] = weights if scale is not None: # If the scale is a function, then scaling function need to be applied # on full image to obtain scaling factor and create an array instead. if callable(scale): scalevalues = [] for image in img_list: if isinstance(image, CCDData): imgccd = image else: imgccd = CCDData.read(image, **ccdkwargs) scalevalues.append(scale(imgccd.data)) to_set_in_combiner['scaling'] = np.array(scalevalues) else: to_set_in_combiner['scaling'] = scale if clip_extrema: to_call_in_combiner['clip_extrema'] = {'nlow': nlow, 'nhigh': nhigh} if minmax_clip: to_call_in_combiner['minmax_clipping'] = {'min_clip': minmax_clip_min, 'max_clip': minmax_clip_max} if sigma_clip: to_call_in_combiner['sigma_clipping'] = { 'low_thresh': sigma_clip_low_thresh, 'high_thresh': sigma_clip_high_thresh, 'func': sigma_clip_func, 'dev_func': sigma_clip_dev_func} # Finally Run the input method on all the subsections of the image # and write final stitched image to ccd for x in range(0, xs, xstep): for y in range(0, ys, ystep): xend, yend = min(xs, x + xstep), min(ys, y + ystep) ccd_list = [] for image in img_list: if isinstance(image, CCDData): imgccd = image else: imgccd = CCDData.read(image, **ccdkwargs) # Trim image and copy # The copy is *essential* to avoid having a bunch # of unused file references around if the files # are memory-mapped. See this PR for details # https://github.com/astropy/ccdproc/pull/630 ccd_list.append(imgccd[x:xend, y:yend].copy()) # Create Combiner for tile tile_combiner = Combiner(ccd_list, dtype=dtype) # Set all properties and call all methods for to_set in to_set_in_combiner: setattr(tile_combiner, to_set, to_set_in_combiner[to_set]) for to_call in to_call_in_combiner: getattr(tile_combiner, to_call)(**to_call_in_combiner[to_call]) # Finally call the combine algorithm combine_kwds = {} if combine_uncertainty_function is not None: combine_kwds['uncertainty_func'] = combine_uncertainty_function comb_tile = getattr(tile_combiner, combine_function)(**combine_kwds) # add it back into the master image ccd.data[x:xend, y:yend] = comb_tile.data if ccd.mask is not None: ccd.mask[x:xend, y:yend] = comb_tile.mask if ccd.uncertainty is not None: ccd.uncertainty.array[x:xend, y:yend] = comb_tile.uncertainty.array # Free up memory to try to stay under user's limit del comb_tile del tile_combiner del ccd_list # Write fits file if filename was provided if output_file is not None: ccd.write(output_file) return ccd ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1577130803.0 ccdproc-2.3.0/ccdproc/conftest.py0000644000076600000240000000443700000000000020055 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # this contains imports plugins that configure py.test for astropy tests. # by importing them here in conftest.py they are discoverable by py.test # no matter how it is invoked within the source tree. import os try: # When the pytest_astropy_header package is installed from pytest_astropy_header.display import (PYTEST_HEADER_MODULES, TESTED_VERSIONS) def pytest_configure(config): config.option.astropy_header = True except ImportError: # TODO: Remove this when astropy 2.x and 3.x support is dropped. # Probably an old pytest-astropy package where the pytest_astropy_header # is not a dependency. try: from astropy.tests.plugins.display import (pytest_report_header, PYTEST_HEADER_MODULES, TESTED_VERSIONS) except ImportError: # TODO: Remove this when astropy 2.x support is dropped. # If that also did not work we're probably using astropy 2.0 from astropy.tests.pytest_plugins import (pytest_report_header, PYTEST_HEADER_MODULES, TESTED_VERSIONS) try: # TODO: Remove this when astropy 2.x support is dropped. # This is the way to get plugins in astropy 2.x from astropy.tests.pytest_plugins import * except ImportError: # Otherwise they are installed as separate packages that pytest # automagically finds. pass from .tests.pytest_fixtures import * # This is to figure out ccdproc version, rather than using Astropy's try: from .version import version except ImportError: version = 'dev' packagename = os.path.basename(os.path.dirname(__file__)) TESTED_VERSIONS[packagename] = version # Uncomment the following line to treat all DeprecationWarnings as # exceptions # enable_deprecations_as_exceptions() # Add astropy to test header information and remove unused packages. try: PYTEST_HEADER_MODULES['Astropy'] = 'astropy' PYTEST_HEADER_MODULES['astroscrappy'] = 'astroscrappy' PYTEST_HEADER_MODULES['reproject'] = 'reproject' del PYTEST_HEADER_MODULES['h5py'] except KeyError: pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/core.py0000644000076600000240000022446200000000000017162 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """This module implements the base CCDPROC functions""" import math import numbers import logging import packaging import warnings import numpy as np from scipy import ndimage from astropy.units.quantity import Quantity from astropy import units as u from astropy.modeling import fitting from astropy import stats from astropy import nddata from astropy.nddata import StdDevUncertainty, CCDData from astropy.wcs.utils import proj_plane_pixel_area from astropy.utils import deprecated, deprecated_renamed_argument import astropy # To get the version. from .utils.slices import slice_from_string from .log_meta import log_to_metadata from .extern.bitfield import bitfield_to_boolean_mask as _bitfield_to_boolean_mask logger = logging.getLogger(__name__) __all__ = ['background_deviation_box', 'background_deviation_filter', 'ccd_process', 'cosmicray_median', 'cosmicray_lacosmic', 'create_deviation', 'flat_correct', 'gain_correct', 'rebin', 'sigma_func', 'subtract_bias', 'subtract_dark', 'subtract_overscan', 'transform_image', 'trim_image', 'wcs_project', 'Keyword', 'median_filter', 'ccdmask', 'bitfield_to_boolean_mask'] # The dictionary below is used to translate actual function names to names # that are FITS compliant, i.e. 8 characters or less. _short_names = { 'background_deviation_box': 'bakdevbx', 'background_deviation_filter': 'bakdfilt', 'ccd_process': 'ccdproc', 'cosmicray_median': 'crmedian', 'create_deviation': 'creatvar', 'flat_correct': 'flatcor', 'gain_correct': 'gaincor', 'subtract_bias': 'subbias', 'subtract_dark': 'subdark', 'subtract_overscan': 'suboscan', 'trim_image': 'trimim', 'transform_image': 'tranim', 'wcs_project': 'wcsproj' } @log_to_metadata def ccd_process(ccd, oscan=None, trim=None, error=False, master_bias=None, dark_frame=None, master_flat=None, bad_pixel_mask=None, gain=None, readnoise=None, oscan_median=True, oscan_model=None, min_value=None, dark_exposure=None, data_exposure=None, exposure_key=None, exposure_unit=None, dark_scale=False, gain_corrected=True): """Perform basic processing on ccd data. The following steps can be included: * overscan correction (:func:`subtract_overscan`) * trimming of the image (:func:`trim_image`) * create deviation frame (:func:`create_deviation`) * gain correction (:func:`gain_correct`) * add a mask to the data * subtraction of master bias (:func:`subtract_bias`) * subtraction of a dark frame (:func:`subtract_dark`) * correction of flat field (:func:`flat_correct`) The task returns a processed `~astropy.nddata.CCDData` object. Parameters ---------- ccd : `~astropy.nddata.CCDData` Frame to be reduced. oscan : `~astropy.nddata.CCDData`, str or None, optional For no overscan correction, set to None. Otherwise provide a region of ccd from which the overscan is extracted, using the FITS conventions for index order and index start, or a slice from ccd that contains the overscan. Default is ``None``. trim : str or None, optional For no trim correction, set to None. Otherwise provide a region of ccd from which the image should be trimmed, using the FITS conventions for index order and index start. Default is ``None``. error : bool, optional If True, create an uncertainty array for ccd. Default is ``False``. master_bias : `~astropy.nddata.CCDData` or None, optional A master bias frame to be subtracted from ccd. The unit of the master bias frame should match the unit of the image **after gain correction** if ``gain_corrected`` is True. Default is ``None``. dark_frame : `~astropy.nddata.CCDData` or None, optional A dark frame to be subtracted from the ccd. The unit of the master dark frame should match the unit of the image **after gain correction** if ``gain_corrected`` is True. Default is ``None``. master_flat : `~astropy.nddata.CCDData` or None, optional A master flat frame to be divided into ccd. The unit of the master flat frame should match the unit of the image **after gain correction** if ``gain_corrected`` is True. Default is ``None``. bad_pixel_mask : `numpy.ndarray` or None, optional A bad pixel mask for the data. The bad pixel mask should be in given such that bad pixels have a value of 1 and good pixels a value of 0. Default is ``None``. gain : `~astropy.units.Quantity` or None, optional Gain value to multiple the image by to convert to electrons. Default is ``None``. readnoise : `~astropy.units.Quantity` or None, optional Read noise for the observations. The read noise should be in electrons. Default is ``None``. oscan_median : bool, optional If true, takes the median of each line. Otherwise, uses the mean. Default is ``True``. oscan_model : `~astropy.modeling.Model` or None, optional Model to fit to the data. If None, returns the values calculated by the median or the mean. Default is ``None``. min_value : float or None, optional Minimum value for flat field. The value can either be None and no minimum value is applied to the flat or specified by a float which will replace all values in the flat by the min_value. Default is ``None``. dark_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the dark image; if specified, must also provided ``data_exposure``. Default is ``None``. data_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the science image; if specified, must also provided ``dark_exposure``. Default is ``None``. exposure_key : `~ccdproc.Keyword`, str or None, optional Name of key in image metadata that contains exposure time. Default is ``None``. exposure_unit : `~astropy.units.Unit` or None, optional Unit of the exposure time if the value in the meta data does not include a unit. Default is ``None``. dark_scale : bool, optional If True, scale the dark frame by the exposure times. Default is ``False``. gain_corrected : bool, optional If True, the ``master_bias``, ``master_flat``, and ``dark_frame`` have already been gain corrected. Default is ``True``. Returns ------- occd : `~astropy.nddata.CCDData` Reduded ccd. Examples -------- 1. To overscan, trim and gain correct a data set:: >>> import numpy as np >>> from astropy import units as u >>> from astropy.nddata import CCDData >>> from ccdproc import ccd_process >>> ccd = CCDData(np.ones([100, 100]), unit=u.adu) >>> nccd = ccd_process(ccd, oscan='[1:10,1:100]', ... trim='[10:100, 1:100]', error=False, ... gain=2.0*u.electron/u.adu) """ # make a copy of the object nccd = ccd.copy() # apply the overscan correction if isinstance(oscan, CCDData): nccd = subtract_overscan(nccd, overscan=oscan, median=oscan_median, model=oscan_model) elif isinstance(oscan, str): nccd = subtract_overscan(nccd, fits_section=oscan, median=oscan_median, model=oscan_model) elif oscan is None: pass else: raise TypeError('oscan is not None, a string, or CCDData object.') # apply the trim correction if isinstance(trim, str): nccd = trim_image(nccd, fits_section=trim) elif trim is None: pass else: raise TypeError('trim is not None or a string.') # create the error frame if error and gain is not None and readnoise is not None: nccd = create_deviation(nccd, gain=gain, readnoise=readnoise) elif error and (gain is None or readnoise is None): raise ValueError( 'gain and readnoise must be specified to create error frame.') # apply the bad pixel mask if isinstance(bad_pixel_mask, np.ndarray): nccd.mask = bad_pixel_mask elif bad_pixel_mask is None: pass else: raise TypeError('bad_pixel_mask is not None or numpy.ndarray.') # apply the gain correction if not (gain is None or isinstance(gain, Quantity)): raise TypeError('gain is not None or astropy.units.Quantity.') if gain is not None and gain_corrected: nccd = gain_correct(nccd, gain) # subtracting the master bias if isinstance(master_bias, CCDData): nccd = subtract_bias(nccd, master_bias) elif master_bias is None: pass else: raise TypeError( 'master_bias is not None or a CCDData object.') # subtract the dark frame if isinstance(dark_frame, CCDData): nccd = subtract_dark(nccd, dark_frame, dark_exposure=dark_exposure, data_exposure=data_exposure, exposure_time=exposure_key, exposure_unit=exposure_unit, scale=dark_scale) elif dark_frame is None: pass else: raise TypeError( 'dark_frame is not None or a CCDData object.') # test dividing the master flat if isinstance(master_flat, CCDData): nccd = flat_correct(nccd, master_flat, min_value=min_value) elif master_flat is None: pass else: raise TypeError( 'master_flat is not None or a CCDData object.') # apply the gain correction only at the end if gain_corrected is False if gain is not None and not gain_corrected: nccd = gain_correct(nccd, gain) return nccd @log_to_metadata def create_deviation(ccd_data, gain=None, readnoise=None, disregard_nan=False): """ Create a uncertainty frame. The function will update the uncertainty plane which gives the standard deviation for the data. Gain is used in this function only to scale the data in constructing the deviation; the data is not scaled. Parameters ---------- ccd_data : `~astropy.nddata.CCDData` Data whose deviation will be calculated. gain : `~astropy.units.Quantity` or None, optional Gain of the CCD; necessary only if ``ccd_data`` and ``readnoise`` are not in the same units. In that case, the units of ``gain`` should be those that convert ``ccd_data.data`` to the same units as ``readnoise``. Default is ``None``. readnoise : `~astropy.units.Quantity` or None, optional Read noise per pixel. Default is ``None``. disregard_nan: boolean If ``True``, any value of nan in the output array will be replaced by the readnoise. {log} Raises ------ UnitsError Raised if ``readnoise`` units are not equal to product of ``gain`` and ``ccd_data`` units. Returns ------- ccd : `~astropy.nddata.CCDData` CCDData object with uncertainty created; uncertainty is in the same units as the data in the parameter ``ccd_data``. """ if gain is not None and not isinstance(gain, Quantity): raise TypeError('gain must be a astropy.units.Quantity.') if readnoise is None: raise ValueError('must provide a readnoise.') if not isinstance(readnoise, Quantity): raise TypeError('readnoise must be a astropy.units.Quantity.') if gain is None: gain = 1.0 * u.dimensionless_unscaled if gain.unit * ccd_data.unit != readnoise.unit: raise u.UnitsError("units of data, gain and readnoise do not match.") # Need to convert Quantity to plain number because NDData data is not # a Quantity. All unit checking should happen prior to this point. gain_value = float(gain / gain.unit) readnoise_value = float(readnoise / readnoise.unit) # remove values that might be negative or treat as nan data = gain_value * ccd_data.data mask = (data < 0) if disregard_nan: data[mask] = 0 else: data[mask] = np.nan logging.warning('Negative values in array will be replaced with nan') # calculate the deviation var = (data + readnoise_value ** 2) ** 0.5 # ensure uncertainty and image data have same unit ccd = ccd_data.copy() var /= gain_value ccd.uncertainty = StdDevUncertainty(var) return ccd @log_to_metadata def subtract_overscan(ccd, overscan=None, overscan_axis=1, fits_section=None, median=False, model=None): """ Subtract the overscan region from an image. Parameters ---------- ccd : `~astropy.nddata.CCDData` Data to have overscan frame corrected. overscan : `~astropy.nddata.CCDData` or None, optional Slice from ``ccd`` that contains the overscan. Must provide either this argument or ``fits_section``, but not both. Default is ``None``. overscan_axis : 0, 1 or None, optional Axis along which overscan should combined with mean or median. Axis numbering follows the *python* convention for ordering, so 0 is the first axis and 1 is the second axis. If overscan_axis is explicitly set to None, the axis is set to the shortest dimension of the overscan section (or 1 in case of a square overscan). Default is ``1``. fits_section : str or None, optional Region of ``ccd`` from which the overscan is extracted, using the FITS conventions for index order and index start. See Notes and Examples below. Must provide either this argument or ``overscan``, but not both. Default is ``None``. median : bool, optional If true, takes the median of each line. Otherwise, uses the mean. Default is ``False``. model : `~astropy.modeling.Model` or None, optional Model to fit to the data. If None, returns the values calculated by the median or the mean. Default is ``None``. {log} Raises ------ TypeError A TypeError is raised if either ``ccd`` or ``overscan`` are not the correct objects. Returns ------- ccd : `~astropy.nddata.CCDData` CCDData object with overscan subtracted. Notes ----- The format of the ``fits_section`` string follow the rules for slices that are consistent with the FITS standard (v3) and IRAF usage of keywords like TRIMSEC and BIASSEC. Its indexes are one-based, instead of the python-standard zero-based, and the first index is the one that increases most rapidly as you move through the array in memory order, opposite the python ordering. The 'fits_section' argument is provided as a convenience for those who are processing files that contain TRIMSEC and BIASSEC. The preferred, more pythonic, way of specifying the overscan is to do it by indexing the data array directly with the ``overscan`` argument. Examples -------- Creating a 100x100 array containing ones just for demonstration purposes:: >>> import numpy as np >>> from astropy import units as u >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) The statement below uses all rows of columns 90 through 99 as the overscan:: >>> no_scan = subtract_overscan(arr1, overscan=arr1[:, 90:100]) >>> assert (no_scan.data == 0).all() This statement does the same as the above, but with a FITS-style section:: >>> no_scan = subtract_overscan(arr1, fits_section='[91:100, :]') >>> assert (no_scan.data == 0).all() Spaces are stripped out of the ``fits_section`` string. """ if not (isinstance(ccd, CCDData) or isinstance(ccd, np.ndarray)): raise TypeError('ccddata is not a CCDData or ndarray object.') if ((overscan is not None and fits_section is not None) or (overscan is None and fits_section is None)): raise TypeError('specify either overscan or fits_section, but not ' 'both.') if (overscan is not None) and (not isinstance(overscan, CCDData)): raise TypeError('overscan is not a CCDData object.') if (fits_section is not None and not isinstance(fits_section, str)): raise TypeError('overscan is not a string.') if fits_section is not None: overscan = ccd[slice_from_string(fits_section, fits_convention=True)] if overscan_axis is None: overscan_axis = 0 if overscan.shape[1] > overscan.shape[0] else 1 if median: oscan = np.median(overscan.data, axis=overscan_axis) else: oscan = np.mean(overscan.data, axis=overscan_axis) if model is not None: of = fitting.LinearLSQFitter() yarr = np.arange(len(oscan)) oscan = of(model, yarr, oscan) oscan = oscan(yarr) if overscan_axis == 1: oscan = np.reshape(oscan, (oscan.size, 1)) else: oscan = np.reshape(oscan, (1, oscan.size)) else: if overscan_axis == 1: oscan = np.reshape(oscan, oscan.shape + (1,)) else: oscan = np.reshape(oscan, (1,) + oscan.shape) subtracted = ccd.copy() # subtract the overscan subtracted.data = ccd.data - oscan return subtracted @log_to_metadata def trim_image(ccd, fits_section=None): """ Trim the image to the dimensions indicated. Parameters ---------- ccd : `~astropy.nddata.CCDData` CCD image to be trimmed, sliced if desired. fits_section : str or None, optional Region of ``ccd`` from which the overscan is extracted; see `~ccdproc.subtract_overscan` for details. Default is ``None``. {log} Returns ------- trimmed_ccd : `~astropy.nddata.CCDData` Trimmed image. Examples -------- Given an array that is 100x100, >>> import numpy as np >>> from astropy import units as u >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) the syntax for trimming this to keep all of the first index but only the first 90 rows of the second index is >>> trimmed = trim_image(arr1[:, :90]) >>> trimmed.shape (100, 90) >>> trimmed.data[0, 0] = 2 >>> arr1.data[0, 0] 1.0 This both trims *and makes a copy* of the image. Indexing the image directly does *not* do the same thing, quite: >>> not_really_trimmed = arr1[:, :90] >>> not_really_trimmed.data[0, 0] = 2 >>> arr1.data[0, 0] 2.0 In this case, ``not_really_trimmed`` is a view of the underlying array ``arr1``, not a copy. """ if (fits_section is not None and not isinstance(fits_section, str)): raise TypeError("fits_section must be a string.") trimmed = ccd.copy() if fits_section: python_slice = slice_from_string(fits_section, fits_convention=True) trimmed = trimmed[python_slice] return trimmed @log_to_metadata def subtract_bias(ccd, master): """ Subtract master bias from image. Parameters ---------- ccd : `~astropy.nddata.CCDData` Image from which bias will be subtracted. master : `~astropy.nddata.CCDData` Master image to be subtracted from ``ccd``. {log} Returns ------- result : `~astropy.nddata.CCDData` CCDData object with bias subtracted. """ try: result = ccd.subtract(master) except ValueError as e: if 'operand units' in str(e): raise u.UnitsError("Unit '{}' of the uncalibrated image does not " "match unit '{}' of the calibration " "image".format(ccd.unit, master.unit)) else: raise e result.meta = ccd.meta.copy() return result @log_to_metadata def subtract_dark(ccd, master, dark_exposure=None, data_exposure=None, exposure_time=None, exposure_unit=None, scale=False): """ Subtract dark current from an image. Parameters ---------- ccd : `~astropy.nddata.CCDData` Image from which dark will be subtracted. master : `~astropy.nddata.CCDData` Dark image. dark_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the dark image; if specified, must also provided ``data_exposure``. Default is ``None``. data_exposure : `~astropy.units.Quantity` or None, optional Exposure time of the science image; if specified, must also provided ``dark_exposure``. Default is ``None``. exposure_time : str or `~ccdproc.Keyword` or None, optional Name of key in image metadata that contains exposure time. Default is ``None``. exposure_unit : `~astropy.units.Unit` or None, optional Unit of the exposure time if the value in the meta data does not include a unit. Default is ``None``. scale: bool, optional If True, scale the dark frame by the exposure times. Default is ``False``. {log} Returns ------- result : `~astropy.nddata.CCDData` Dark-subtracted image. """ if ccd.shape != master.shape: err_str = "operands could not be subtracted with shapes {} {}".format(ccd.shape, master.shape) raise ValueError(err_str) if not (isinstance(ccd, CCDData) and isinstance(master, CCDData)): raise TypeError("ccd and master must both be CCDData objects.") if (data_exposure is not None and dark_exposure is not None and exposure_time is not None): raise TypeError("specify either exposure_time or " "(dark_exposure and data_exposure), not both.") if data_exposure is None and dark_exposure is None: if exposure_time is None: raise TypeError("must specify either exposure_time or both " "dark_exposure and data_exposure.") if isinstance(exposure_time, Keyword): data_exposure = exposure_time.value_from(ccd.header) dark_exposure = exposure_time.value_from(master.header) else: data_exposure = ccd.header[exposure_time] dark_exposure = master.header[exposure_time] if not (isinstance(dark_exposure, Quantity) and isinstance(data_exposure, Quantity)): if exposure_time: try: data_exposure *= exposure_unit dark_exposure *= exposure_unit except TypeError: raise TypeError("must provide unit for exposure time.") else: raise TypeError("exposure times must be astropy.units.Quantity " "objects.") try: if scale: master_scaled = master.copy() # data_exposure and dark_exposure are both quantities, # so we can just have subtract do the scaling master_scaled = master_scaled.multiply(data_exposure / dark_exposure) result = ccd.subtract(master_scaled) else: result = ccd.subtract(master) except (u.UnitsError, u.UnitConversionError, ValueError) as e: # Astropy LTS (v1) returns a ValueError, not a UnitsError, so catch # that if it appears to really be a UnitsError. if (isinstance(e, ValueError) and 'operand units' not in str(e) and astropy.__version__.startswith('1.0')): raise e # Make the error message a little more explicit than what is returned # by default. raise u.UnitsError("Unit '{}' of the uncalibrated image does not " "match unit '{}' of the calibration " "image".format(ccd.unit, master.unit)) result.meta = ccd.meta.copy() return result @log_to_metadata def gain_correct(ccd, gain, gain_unit=None): """Correct the gain in the image. Parameters ---------- ccd : `~astropy.nddata.CCDData` Data to have gain corrected. gain : `~astropy.units.Quantity` or `~ccdproc.Keyword` gain value for the image expressed in electrons per adu. gain_unit : `~astropy.units.Unit` or None, optional Unit for the ``gain``; used only if ``gain`` itself does not provide units. Default is ``None``. {log} Returns ------- result : `~astropy.nddata.CCDData` CCDData object with gain corrected. """ if isinstance(gain, Keyword): gain_value = gain.value_from(ccd.header) elif isinstance(gain, numbers.Number) and gain_unit is not None: gain_value = gain * u.Unit(gain_unit) else: gain_value = gain result = ccd.multiply(gain_value) result.meta = ccd.meta.copy() return result @log_to_metadata def flat_correct(ccd, flat, min_value=None, norm_value=None): """Correct the image for flat fielding. The flat field image is normalized by its mean or a user-supplied value before flat correcting. Parameters ---------- ccd : `~astropy.nddata.CCDData` Data to be transformed. flat : `~astropy.nddata.CCDData` Flatfield to apply to the data. min_value : float or None, optional Minimum value for flat field. The value can either be None and no minimum value is applied to the flat or specified by a float which will replace all values in the flat by the min_value. Default is ``None``. norm_value : float or None, optional If not ``None``, normalize flat field by this argument rather than the mean of the image. This allows fixing several different flat fields to have the same scale. If this value is negative or 0, a ``ValueError`` is raised. Default is ``None``. {log} Returns ------- ccd : `~astropy.nddata.CCDData` CCDData object with flat corrected. """ # Use the min_value to replace any values in the flat use_flat = flat if min_value is not None: flat_min = flat.copy() flat_min.data[flat_min.data < min_value] = min_value use_flat = flat_min # If a norm_value was input and is positive, use it to scale the flat if norm_value is not None and norm_value > 0: flat_mean_val = norm_value elif norm_value is not None: # norm_value was set to a bad value raise ValueError('norm_value must be greater than zero.') else: # norm_value was not set, use mean of the image. flat_mean_val = use_flat.data.mean() # Normalize the flat. flat_mean = flat_mean_val * use_flat.unit flat_normed = use_flat.divide(flat_mean) # divide through the flat flat_corrected = ccd.divide(flat_normed) flat_corrected.meta = ccd.meta.copy() return flat_corrected @log_to_metadata def transform_image(ccd, transform_func, **kwargs): """Transform the image. Using the function specified by transform_func, the transform will be applied to data, uncertainty, and mask in ccd. Parameters ---------- ccd : `~astropy.nddata.CCDData` Data to be transformed. transform_func : callable Function to be used to transform the data, mask and uncertainty. kwargs : Additional keyword arguments to be used by the transform_func. {log} Returns ------- ccd : `~astropy.nddata.CCDData` A transformed CCDData object. Notes ----- At this time, transform will be applied to the uncertainty data but it will only transform the data. This will not properly handle uncertainties that arise due to correlation between the pixels. These should only be geometric transformations of the images. Other methods should be used if the units of ccd need to be changed. Examples -------- Given an array that is 100x100:: >>> import numpy as np >>> from astropy import units as u >>> arr1 = CCDData(np.ones([100, 100]), unit=u.adu) The syntax for transforming the array using `scipy.ndimage.shift`:: >>> from scipy.ndimage.interpolation import shift >>> from ccdproc import transform_image >>> transformed = transform_image(arr1, shift, shift=(5.5, 8.1)) """ # check that it is a ccddata object if not isinstance(ccd, CCDData): raise TypeError('ccd is not a CCDData.') # make a copy of the object nccd = ccd.copy() # transform the image plane try: nccd.data = transform_func(nccd.data, **kwargs) except TypeError as exc: if 'is not callable' in str(exc): raise TypeError('transform_func is not a callable.') raise # transform the uncertainty plane if it exists if nccd.uncertainty is not None: nccd.uncertainty.array = transform_func(nccd.uncertainty.array, **kwargs) # transform the mask plane if nccd.mask is not None: mask = transform_func(nccd.mask, **kwargs) nccd.mask = mask > 0 if nccd.wcs is not None: warn = 'WCS information may be incorrect as no transformation was applied to it' logging.warning(warn) return nccd @log_to_metadata def wcs_project(ccd, target_wcs, target_shape=None, order='bilinear'): """ Given a CCDData image with WCS, project it onto a target WCS and return the reprojected data as a new CCDData image. Any flags, weight, or uncertainty are ignored in doing the reprojection. Parameters ---------- ccd : `~astropy.nddata.CCDData` Data to be projected. target_wcs : `~astropy.wcs.WCS` object WCS onto which all images should be projected. target_shape : two element list-like or None, optional Shape of the output image. If omitted, defaults to the shape of the input image. Default is ``None``. order : str, optional Interpolation order for re-projection. Must be one of: + 'nearest-neighbor' + 'bilinear' + 'biquadratic' + 'bicubic' Default is ``'bilinear'``. {log} Returns ------- ccd : `~astropy.nddata.CCDData` A transformed CCDData object. """ from astropy.nddata.ccddata import _generate_wcs_and_update_header from reproject import reproject_interp if not (ccd.wcs.is_celestial and target_wcs.is_celestial): raise ValueError('one or both WCS is not celestial.') if target_shape is None: target_shape = ccd.shape projected_image_raw, _ = reproject_interp((ccd.data, ccd.wcs), target_wcs, shape_out=target_shape, order=order) reprojected_mask = None if ccd.mask is not None: reprojected_mask, _ = reproject_interp((ccd.mask, ccd.wcs), target_wcs, shape_out=target_shape, order=order) # Make the mask 1 if the reprojected mask pixel value is non-zero. # A small threshold is included to allow for some rounding in # reproject_interp. reprojected_mask = reprojected_mask > 1e-8 # The reprojection will contain nan for any pixels for which the source # was outside the original image. Those should be masked also. output_mask = np.isnan(projected_image_raw) if reprojected_mask is not None: output_mask = output_mask | reprojected_mask # Need to scale counts by ratio of pixel areas area_ratio = (proj_plane_pixel_area(target_wcs) / proj_plane_pixel_area(ccd.wcs)) # If nothing ended up masked, don't create a mask. if not output_mask.any(): output_mask = None # If there are any wcs keywords in the header, remove them hdr, _ = _generate_wcs_and_update_header(ccd.header) nccd = CCDData(area_ratio * projected_image_raw, wcs=target_wcs, mask=output_mask, header=hdr, unit=ccd.unit) return nccd def sigma_func(arr, axis=None, ignore_nan=False): """ Robust method for calculating the deviation of an array. ``sigma_func`` uses the median absolute deviation to determine the standard deviation. Parameters ---------- arr : `~astropy.nddata.CCDData` or `numpy.ndarray` Array whose deviation is to be calculated. axis : int, tuple of ints or None, optional Axis or axes along which the function is performed. If ``None`` it is performed over all the dimensions of the input array. The axis argument can also be negative, in this case it counts from the last to the first axis. Default is ``None``. Returns ------- uncertainty : float uncertainty of array estimated from median absolute deviation. """ return (stats.median_absolute_deviation(arr, axis=axis, ignore_nan=ignore_nan) * 1.482602218505602) def setbox(x, y, mbox, xmax, ymax): """ Create a box of length mbox around a position x,y. If the box will be out of [0,len] then reset the edges of the box to be within the boundaries. Parameters ---------- x : int Central x-position of box. y : int Central y-position of box. mbox : int Width of box. xmax : int Maximum x value. ymax : int Maximum y value. Returns ------- x1 : int Lower x corner of box. x2 : int Upper x corner of box. y1 : int Lower y corner of box. y2 : int Upper y corner of box. """ mbox = max(int(0.5 * mbox), 1) y1 = max(0, y - mbox) y2 = min(y + mbox + 1, ymax - 1) x1 = max(0, x - mbox) x2 = min(x + mbox + 1, xmax - 1) return x1, x2, y1, y2 def background_deviation_box(data, bbox): """ Determine the background deviation with a box size of bbox. The algorithm steps through the image and calculates the deviation within each box. It returns an array with the pixels in each box filled with the deviation value. Parameters ---------- data : `numpy.ndarray` or `numpy.ma.MaskedArray` Data to measure background deviation. bbox : int Box size for calculating background deviation. Raises ------ ValueError A value error is raised if bbox is less than 1. Returns ------- background : `numpy.ndarray` or `numpy.ma.MaskedArray` An array with the measured background deviation in each pixel. """ # Check to make sure the background box is an appropriate size # If it is too small, then insufficient statistics are generated if bbox < 1: raise ValueError('bbox must be greater than 1.') # make the background image barr = data * 0.0 + data.std() ylen, xlen = data.shape for i in range(int(0.5 * bbox), xlen, bbox): for j in range(int(0.5 * bbox), ylen, bbox): x1, x2, y1, y2 = setbox(i, j, bbox, xlen, ylen) barr[y1:y2, x1:x2] = sigma_func(data[y1:y2, x1:x2]) return barr def background_deviation_filter(data, bbox): """ Determine the background deviation for each pixel from a box with size of bbox. Parameters ---------- data : `numpy.ndarray` Data to measure background deviation. bbox : int Box size for calculating background deviation. Raises ------ ValueError A value error is raised if bbox is less than 1. Returns ------- background : `numpy.ndarray` or `numpy.ma.MaskedArray` An array with the measured background deviation in each pixel. """ # Check to make sure the background box is an appropriate size if bbox < 1: raise ValueError('bbox must be greater than 1.') return ndimage.generic_filter(data, sigma_func, size=(bbox, bbox)) @deprecated('1.1', message='The rebin function will be removed in ccdproc 3.0 ' 'Use block_reduce or block_replicate instead.') def rebin(ccd, newshape): """ Rebin an array to have a new shape. Parameters ---------- ccd : `~astropy.nddata.CCDData` or `numpy.ndarray` Data to rebin. newshape : tuple Tuple containing the new shape for the array. Returns ------- output : `~astropy.nddata.CCDData` or `numpy.ndarray` An array with the new shape. It will have the same type as the input object. Raises ------ TypeError A type error is raised if data is not an `numpy.ndarray` or `~astropy.nddata.CCDData`. ValueError A value error is raised if the dimension of the new shape is not equal to the data's. Notes ----- This is based on the scipy cookbook for rebinning: http://wiki.scipy.org/Cookbook/Rebinning If rebinning a CCDData object to a smaller shape, the masking and uncertainty are not handled correctly. Examples -------- Given an array that is 100x100:: import numpy as np from astropy import units as u arr1 = CCDData(np.ones([10, 10]), unit=u.adu) The syntax for rebinning an array to a shape of (20,20) is:: rebin(arr1, (20,20)) """ # check to see that is in a nddata type if isinstance(ccd, np.ndarray): # check to see that the two arrays are going to be the same length if len(ccd.shape) != len(newshape): raise ValueError('newshape does not have the same dimensions as ' 'ccd.') slices = [slice(0, old, old/new) for old, new in zip(ccd.shape, newshape)] coordinates = np.mgrid[slices] indices = coordinates.astype('i') return ccd[tuple(indices)] elif isinstance(ccd, CCDData): # check to see that the two arrays are going to be the same length if len(ccd.shape) != len(newshape): raise ValueError('newshape does not have the same dimensions as ' 'ccd.') nccd = ccd.copy() # rebin the data plane nccd.data = rebin(nccd.data, newshape) # rebin the uncertainty plane if nccd.uncertainty is not None: nccd.uncertainty.array = rebin(nccd.uncertainty.array, newshape) # rebin the mask plane if nccd.mask is not None: nccd.mask = rebin(nccd.mask, newshape) return nccd else: raise TypeError('ccd is not an ndarray or a CCDData object.') def block_reduce(ccd, block_size, func=np.sum): """Thin wrapper around `astropy.nddata.block_reduce`.""" data = nddata.block_reduce(ccd, block_size, func) if isinstance(ccd, CCDData): # unit and meta "should" be unaffected by the change of shape and can # be copied. However wcs, mask, uncertainty should not be copied! data = CCDData(data, unit=ccd.unit, meta=ccd.meta.copy()) return data def block_average(ccd, block_size): """Like `block_reduce` but with predefined ``func=np.mean``. """ data = nddata.block_reduce(ccd, block_size, np.mean) # Like in block_reduce: if isinstance(ccd, CCDData): data = CCDData(data, unit=ccd.unit, meta=ccd.meta.copy()) return data def block_replicate(ccd, block_size, conserve_sum=True): """Thin wrapper around `astropy.nddata.block_replicate`.""" data = nddata.block_replicate(ccd, block_size, conserve_sum) # Like in block_reduce: if isinstance(ccd, CCDData): data = CCDData(data, unit=ccd.unit, meta=ccd.meta.copy()) return data try: # Append original docstring to docstrings of these functions block_reduce.__doc__ += nddata.block_reduce.__doc__ block_replicate.__doc__ += nddata.block_replicate.__doc__ __all__ += ['block_average', 'block_reduce', 'block_replicate'] except AttributeError: # Astropy 1.0 has no block_reduce, block_average del block_reduce, block_average, block_replicate def _blkavg(data, newshape): """ Block average an array such that it has the new shape. Parameters ---------- data : `numpy.ndarray` or `numpy.ma.MaskedArray` Data to average. newshape : tuple Tuple containing the new shape for the array. Returns ------- output : `numpy.ndarray` or `numpy.ma.MaskedArray` An array with the new shape and the average of the pixels. Raises ------ TypeError A type error is raised if data is not an `numpy.ndarray`. ValueError A value error is raised if the dimensions of new shape is not equal to data. Notes ----- This is based on the scipy cookbook for rebinning: http://wiki.scipy.org/Cookbook/Rebinning """ # check to see that is in a nddata type if not isinstance(data, np.ndarray): raise TypeError('data is not a ndarray object.') # check to see that the two arrays are going to be the same length if len(data.shape) != len(newshape): raise ValueError('newshape does not have the same dimensions as data.') shape = data.shape lenShape = len(shape) factor = np.asarray(shape)/np.asarray(newshape) evList = ['data.reshape('] + \ ['newshape[%d],int(factor[%d]),' % (i, i) for i in range(lenShape)] + \ [')'] + ['.mean(%d)' % (i + 1) for i in range(lenShape)] return eval(''.join(evList)) def median_filter(data, *args, **kwargs): """See `scipy.ndimage.median_filter` for arguments. If the ``data`` is a `~astropy.nddata.CCDData` object the result will be another `~astropy.nddata.CCDData` object with the median filtered data as ``data`` and copied ``unit`` and ``meta``. """ if isinstance(data, CCDData): out_kwargs = {'meta': data.meta.copy(), 'unit': data.unit} result = ndimage.median_filter(data.data, *args, **kwargs) return CCDData(result, **out_kwargs) else: return ndimage.median_filter(data, *args, **kwargs) # This originally used the "message" argument but that is not # supported until astropy 5, so use alternative instead. @deprecated_renamed_argument('pssl', None, '2.3.0', arg_in_kwargs=True, alternative='The pssl keyword will be removed in ' 'ccdproc 3.0. Use inbkg instead to have ' 'astroscrappy temporarily remove the background ' 'during processing.') def cosmicray_lacosmic(ccd, sigclip=4.5, sigfrac=0.3, objlim=5.0, gain=1.0, readnoise=6.5, satlevel=65535.0, pssl=0.0, niter=4, sepmed=True, cleantype='meanmask', fsmode='median', psfmodel='gauss', psffwhm=2.5, psfsize=7, psfk=None, psfbeta=4.765, verbose=False, gain_apply=True, inbkg=None, invar=None): r""" Identify cosmic rays through the L.A. Cosmic technique. The L.A. Cosmic technique identifies cosmic rays by identifying pixels based on a variation of the Laplacian edge detection. The algorithm is an implementation of the code describe in van Dokkum (2001) [1]_ as implemented by McCully (2014) [2]_. If you use this algorithm, please cite these two works. Parameters ---------- ccd : `~astropy.nddata.CCDData` or `numpy.ndarray` Data to have cosmic ray cleaned. gain_apply : bool, optional If ``True``, **return gain-corrected data**, with correct units, otherwise do not gain-correct the data. Default is ``True`` to preserve backwards compatibility. sigclip : float, optional Laplacian-to-noise limit for cosmic ray detection. Lower values will flag more pixels as cosmic rays. Default: 4.5. sigfrac : float, optional Fractional detection limit for neighboring pixels. For cosmic ray neighbor pixels, a Laplacian-to-noise detection limit of sigfrac * sigclip will be used. Default: 0.3. objlim : float, optional Minimum contrast between Laplacian image and the fine structure image. Increase this value if cores of bright stars are flagged as cosmic rays. Default: 5.0. inbkg : float numpy array, optional A pre-determined background image, to be subtracted from ``indat`` before running the main detection algorithm. This is used primarily with spectroscopic data, to remove sky lines and the cross-section of an object continuum during iteration, "protecting" them from spurious rejection (see the above paper). This background is not removed from the final, cleaned output (``cleanarr``). This should be in units of "counts", the same units of indat. This inbkg should be free from cosmic rays. When estimating the cosmic-ray free noise of the image, we will treat ``inbkg`` as a constant Poisson contribution to the variance. invar : float numpy array, optional A pre-determined estimate of the data variance (ie. noise squared) in each pixel, generated by previous processing of ``indat``. If provided, this is used in place of an internal noise model based on ``indat``, ``gain`` and ``readnoise``. This still gets median filtered and cleaned internally, to estimate what the noise in each pixel *would* be in the absence of cosmic rays. This should be in units of "counts" squared. pssl : float, optional Previously subtracted sky level in ADU. We always need to work in electrons for cosmic ray detection, so we need to know the sky level that has been subtracted so we can add it back in. Default: 0.0. gain : float or `~astropy.units.Quantity`, optional Gain of the image (electrons / ADU). We always need to work in electrons for cosmic ray detection. Default: 1.0 readnoise : float, optional Read noise of the image (electrons). Used to generate the noise model of the image. Default: 6.5. satlevel : float, optional Saturation level of the image (electrons). This value is used to detect saturated stars and pixels at or above this level are added to the mask. Default: 65535.0. niter : int, optional Number of iterations of the LA Cosmic algorithm to perform. Default: 4. sepmed : bool, optional Use the separable median filter instead of the full median filter. The separable median is not identical to the full median filter, but they are approximately the same, the separable median filter is significantly faster, and still detects cosmic rays well. Note, this is a performance feature, and not part of the original L.A. Cosmic. Default: True cleantype : str, optional Set which clean algorithm is used: - ``"median"``: An unmasked 5x5 median filter. - ``"medmask"``: A masked 5x5 median filter. - ``"meanmask"``: A masked 5x5 mean filter. - ``"idw"``: A masked 5x5 inverse distance weighted interpolation. Default: ``"meanmask"``. fsmode : str, optional Method to build the fine structure image: - ``"median"``: Use the median filter in the standard LA Cosmic \ algorithm. - ``"convolve"``: Convolve the image with the psf kernel to calculate \ the fine structure image. Default: ``"median"``. psfmodel : str, optional Model to use to generate the psf kernel if fsmode == 'convolve' and psfk is None. The current choices are Gaussian and Moffat profiles: - ``"gauss"`` and ``"moffat"`` produce circular PSF kernels. - The ``"gaussx"`` and ``"gaussy"`` produce Gaussian kernels in the x \ and y directions respectively. Default: ``"gauss"``. psffwhm : float, optional Full Width Half Maximum of the PSF to use to generate the kernel. Default: 2.5. psfsize : int, optional Size of the kernel to calculate. Returned kernel will have size psfsize x psfsize. psfsize should be odd. Default: 7. psfk : `numpy.ndarray` (with float dtype) or None, optional PSF kernel array to use for the fine structure image if ``fsmode == 'convolve'``. If None and ``fsmode == 'convolve'``, we calculate the psf kernel using ``psfmodel``. Default: None. psfbeta : float, optional Moffat beta parameter. Only used if ``fsmode=='convolve'`` and ``psfmodel=='moffat'``. Default: 4.765. verbose : bool, optional Print to the screen or not. Default: False. Notes ----- Implementation of the cosmic ray identification L.A.Cosmic: http://www.astro.yale.edu/dokkum/lacosmic/ Returns ------- nccd : `~astropy.nddata.CCDData` or `numpy.ndarray` An object of the same type as ccd is returned. If it is a `~astropy.nddata.CCDData`, the mask attribute will also be updated with areas identified with cosmic rays masked. **By default, the image is multiplied by the gain.** You can control this behavior with the ``gain_apply`` argument. crmask : `numpy.ndarray` If an `numpy.ndarray` is provided as ccd, a boolean ndarray with the cosmic rays identified will also be returned. References ---------- .. [1] van Dokkum, P; 2001, "Cosmic-Ray Rejection by Laplacian Edge Detection". The Publications of the Astronomical Society of the Pacific, Volume 113, Issue 789, pp. 1420-1427. doi: 10.1086/323894 .. [2] McCully, C., 2014, "Astro-SCRAPPY", https://github.com/astropy/astroscrappy Examples -------- 1) Given an numpy.ndarray object, the syntax for running cosmicrar_lacosmic would be: >>> newdata, mask = cosmicray_lacosmic(data, sigclip=5) #doctest: +SKIP where the error is an array that is the same shape as data but includes the pixel error. This would return a data array, newdata, with the bad pixels replaced by the local median from a box of 11 pixels; and it would return a mask indicating the bad pixels. 2) Given an `~astropy.nddata.CCDData` object with an uncertainty frame, the syntax for running cosmicrar_lacosmic would be: >>> newccd = cosmicray_lacosmic(ccd, sigclip=5) # doctest: +SKIP The newccd object will have bad pixels in its data array replace and the mask of the object will be created if it did not previously exist or be updated with the detected cosmic rays. """ from astroscrappy import detect_cosmics from astroscrappy import __version__ as asy_version # If we didn't get a quantity, put them in, with unit specified by the # documentation above. if not isinstance(gain, u.Quantity): # Gain will change the value, so use the proper units gain = gain * u.electron / u.adu # Set the units of readnoise to electrons, as specified in the # documentation, if no unit is present. if not isinstance(readnoise, u.Quantity): readnoise = readnoise * u.electron # Handle transition from old astroscrappy interface to new old_astroscrappy_interface = (packaging.version.parse(asy_version) < packaging.version.parse('1.1.0')) # Use this dictionary to define which keyword arguments are actually # passed to astroscrappy. asy_background_kwargs = {} # This is for handling the transition in astroscrappy versions data_offset = 0 # Handle setting up the keyword arguments for both interfaces if old_astroscrappy_interface: # pragma: no cover new_args = dict(inbkg=inbkg, invar=invar) bad_args = [] for k, v in new_args.items(): if v is not None: bad_args.append(k) if bad_args: s = 's' if len(bad_args) > 1 else '' bads = ', '.join(bad_args) raise TypeError(f'The argument{s} {bads} only valid for astroscrappy ' '1.1.0 or higher.') if pssl != 0: asy_background_kwargs = dict(pssl=pssl) else: if pssl != 0: if (inbkg is not None) or (invar is not None): raise ValueError('Cannot set both pssl and inbkg') # The old version of astroscrappy added the bkg back in # if pssl was provided. The new one does not, so set an offset # here that we later add in then take out. data_offset = pssl asy_background_kwargs = dict(inbkg=inbkg, invar=invar) if isinstance(ccd, np.ndarray): data = ccd crmask, cleanarr = detect_cosmics( data + data_offset, inmask=None, sigclip=sigclip, sigfrac=sigfrac, objlim=objlim, gain=gain.value, readnoise=readnoise.value, satlevel=satlevel, niter=niter, sepmed=sepmed, cleantype=cleantype, fsmode=fsmode, psfmodel=psfmodel, psffwhm=psffwhm, psfsize=psfsize, psfk=psfk, psfbeta=psfbeta, verbose=verbose, **asy_background_kwargs) cleanarr = cleanarr - data_offset cleanarr = _astroscrappy_gain_apply_helper(cleanarr, gain, gain_apply, old_astroscrappy_interface) return cleanarr, crmask elif isinstance(ccd, CCDData): # Start with a check for a special case: ccd is in electron, and # gain and readnoise have no units. In that case we issue a warning # instead of raising an error to avoid crashing user's pipelines. if ccd.unit.is_equivalent(u.electron) and gain.value != 1.0: warnings.warn("Image unit is electron but gain value " "is not 1.0. Data maybe end up being gain " "corrected twice.") else: if ((readnoise.unit == u.electron) and (ccd.unit == u.electron) and (gain.value == 1.0)): gain = gain.value * u.one # Check unit consistency before taking the time to check for # cosmic rays. if not (gain * ccd).unit.is_equivalent(readnoise.unit): raise ValueError('Inconsistent units for gain ({}) '.format(gain.unit) + ' ccd ({}) and readnoise ({}).'.format(ccd.unit, readnoise.unit)) crmask, cleanarr = detect_cosmics( ccd.data + data_offset, inmask=ccd.mask, sigclip=sigclip, sigfrac=sigfrac, objlim=objlim, gain=gain.value, readnoise=readnoise.value, satlevel=satlevel, niter=niter, sepmed=sepmed, cleantype=cleantype, fsmode=fsmode, psfmodel=psfmodel, psffwhm=psffwhm, psfsize=psfsize, psfk=psfk, psfbeta=psfbeta, verbose=verbose, **asy_background_kwargs) # create the new ccd data object nccd = ccd.copy() cleanarr = cleanarr - data_offset cleanarr = _astroscrappy_gain_apply_helper(cleanarr, gain, gain_apply, old_astroscrappy_interface) # Fix the units if the gain is being applied. nccd.unit = ccd.unit * gain.unit nccd.data = cleanarr if nccd.mask is None: nccd.mask = crmask else: nccd.mask = nccd.mask + crmask return nccd else: raise TypeError('ccd is not a CCDData or ndarray object.') def _astroscrappy_gain_apply_helper(cleaned_data, gain, gain_apply, old_interface): """ Helper function for logic determining how to apply gain to cleaned data. In the old astroscrappy interface cleaned data was always gain-corrected. In the new interface it is not. This function works out the Right Thing to do given the inputs. cleaned_data : `numpy.ndarray` The cleaned data. gain: float The gain to (maybe) be applied. gain_apply : bool If ``True``, the cleaned data should have the gain applied, otherwise the gain should be applied. old_interface : bool If ``True`` the old, i.e. pre-1.1.0, astroscrappy interface is being used. The old interface always gain corrected the cleaned data, the new one does not. """ if gain != 1.0: if gain_apply: if not old_interface: # New interface does not gain correct, old one did. return cleaned_data * gain else: # Do not want gain correct if old_interface: # pragma: no cover # Old interface gain corrected always so take it out return cleaned_data / gain return cleaned_data def cosmicray_median(ccd, error_image=None, thresh=5, mbox=11, gbox=0, rbox=0): """ Identify cosmic rays through median technique. The median technique identifies cosmic rays by identifying pixels by subtracting a median image from the initial data array. Parameters ---------- ccd : `~astropy.nddata.CCDData`, `numpy.ndarray` or `numpy.ma.MaskedArray` Data to have cosmic ray cleaned. thresh : float, optional Threshold for detecting cosmic rays. Default is ``5``. error_image : `numpy.ndarray`, float or None, optional Error level. If None, the task will use the standard deviation of the data. If an ndarray, it should have the same shape as data. Default is ``None``. mbox : int, optional Median box for detecting cosmic rays. Default is ``11``. gbox : int, optional Box size to grow cosmic rays. If zero, no growing will be done. Default is ``0``. rbox : int, optional Median box for calculating replacement values. If zero, no pixels will be replaced. Default is ``0``. Notes ----- Similar implementation to crmedian in iraf.imred.crutil.crmedian. Returns ------- nccd : `~astropy.nddata.CCDData` or `numpy.ndarray` An object of the same type as ccd is returned. If it is a `~astropy.nddata.CCDData`, the mask attribute will also be updated with areas identified with cosmic rays masked. nccd : `numpy.ndarray` If an `numpy.ndarray` is provided as ccd, a boolean ndarray with the cosmic rays identified will also be returned. Examples -------- 1) Given an numpy.ndarray object, the syntax for running cosmicray_median would be: >>> newdata, mask = cosmicray_median(data, error_image=error, ... thresh=5, mbox=11, ... rbox=11, gbox=5) # doctest: +SKIP where error is an array that is the same shape as data but includes the pixel error. This would return a data array, newdata, with the bad pixels replaced by the local median from a box of 11 pixels; and it would return a mask indicating the bad pixels. 2) Given an `~astropy.nddata.CCDData` object with an uncertainty frame, the syntax for running cosmicray_median would be: >>> newccd = cosmicray_median(ccd, thresh=5, mbox=11, ... rbox=11, gbox=5) # doctest: +SKIP The newccd object will have bad pixels in its data array replace and the mask of the object will be created if it did not previously exist or be updated with the detected cosmic rays. """ if isinstance(ccd, np.ndarray): data = ccd if error_image is None: error_image = data.std() else: if not isinstance(error_image, (float, np.ndarray)): raise TypeError('error_image is not a float or ndarray.') # create the median image marr = ndimage.median_filter(data, size=(mbox, mbox)) # Only look at the data array if isinstance(data, np.ma.MaskedArray): data = data.data # Find the residual image rarr = (data - marr) / error_image # identify all sources crarr = (rarr > thresh) # grow the pixels if gbox > 0: crarr = ndimage.maximum_filter(crarr, gbox) # replace bad pixels in the image ndata = data.copy() if rbox > 0: data = np.ma.masked_array(data, (crarr == 1)) mdata = ndimage.median_filter(data, rbox) ndata[crarr == 1] = mdata[crarr == 1] return ndata, crarr elif isinstance(ccd, CCDData): # set up the error image if error_image is None and ccd.uncertainty is not None: error_image = ccd.uncertainty.array if ccd.data.shape != error_image.shape: raise ValueError('error_image is not the same shape as data.') data, crarr = cosmicray_median(ccd.data, error_image=error_image, thresh=thresh, mbox=mbox, gbox=gbox, rbox=rbox) # create the new ccd data object nccd = ccd.copy() nccd.data = data if nccd.mask is None: nccd.mask = crarr else: nccd.mask = nccd.mask + crarr return nccd else: raise TypeError('ccd is not an numpy.ndarray or a CCDData object.') def ccdmask(ratio, findbadcolumns=False, byblocks=False, ncmed=7, nlmed=7, ncsig=15, nlsig=15, lsigma=9, hsigma=9, ngood=5): """ Uses method based on the IRAF ccdmask task to generate a mask based on the given input. .. note:: This function uses ``lines`` as synonym for the first axis and ``columns`` the second axis. Only two-dimensional ``ratio`` is currently supported. Parameters ---------- ratio : `~astropy.nddata.CCDData` Data to used to form mask. Typically this is the ratio of two flat field images. findbadcolumns : `bool`, optional If set to True, the code will search for bad column sections. Note that this treats columns as special and breaks symmetry between lines and columns and so is likely only appropriate for detectors which have readout directions. Default is ``False``. byblocks : `bool`, optional If set to true, the code will divide the image up in to blocks of size nlsig by ncsig and determine the standard deviation estimate in each block (as described in the original IRAF task, see Notes below). If set to False, then the code will use `scipy.ndimage.percentile_filter` to generate a running box version of the standard deviation estimate and use that value for the standard deviation at each pixel. Default is ``False``. ncmed, nlmed : `int`, optional The column and line size of the moving median rectangle used to estimate the uncontaminated local signal. The column median size should be at least 3 pixels to span single bad columns. Default is ``7``. ncsig, nlsig : `int`, optional The column and line size of regions used to estimate the uncontaminated local sigma using a percentile. The size of the box should contain of order 100 pixels or more. Default is ``15``. lsigma, hsigma : `float`, optional Positive sigma factors to use for selecting pixels below and above the median level based on the local percentile sigma. Default is ``9``. ngood : `int`, optional Gaps of undetected pixels along the column direction of length less than this amount are also flagged as bad pixels, if they are between pixels masked in that column. Default is ``5``. Returns ------- mask : `numpy.ndarray` A boolean ndarray where the bad pixels have a value of 1 (True) and valid pixels 0 (False), following the numpy.ma conventions. Notes ----- Similar implementation to IRAF's ccdmask task. The Following documentation is copied directly from: http://stsdas.stsci.edu/cgi-bin/gethelp.cgi?ccdmask The input image is first subtracted by a moving box median. The median is unaffected by bad pixels provided the median size is larger that twice the size of a bad region. Thus, if 3 pixel wide bad columns are present then the column median box size should be at least 7 pixels. The median box can be a single pixel wide along one dimension if needed. This may be appropriate for spectroscopic long slit data. The median subtracted image is then divided into blocks of size nclsig by nlsig. In each block the pixel values are sorted and the pixels nearest the 30.9 and 69.1 percentile points are found; this would be the one sigma points in a Gaussian noise distribution. The difference between the two count levels divided by two is then the local sigma estimate. This algorithm is used to avoid contamination by the bad pixel values. The block size must be at least 10 pixels in each dimension to provide sufficient pixels for a good estimate of the percentile sigma. The sigma uncertainty estimate of each pixel in the image is then the sigma from the nearest block. The deviant pixels are found by comparing the median subtracted residual to a specified sigma threshold factor times the local sigma above and below zero (the lsigma and hsigma parameters). This is done for individual pixels and then for column sums of pixels (excluding previously flagged bad pixels) from two to the number of lines in the image. The sigma of the sums is scaled by the square root of the number of pixels summed so that statistically low or high column regions may be detected even though individual pixels may not be statistically deviant. For the purpose of this task one would normally select large sigma threshold factors such as six or greater to detect only true bad pixels and not the extremes of the noise distribution. As a final step each column is examined to see if there are small segments of unflagged pixels between bad pixels. If the length of a segment is less than that given by the ngood parameter all the pixels in the segment are also marked as bad. """ try: nlines, ncols = ratio.data.shape except (TypeError, ValueError): # shape is not iterable or has more or less than two values raise ValueError('"ratio" must be two-dimensional.') except AttributeError: # No data attribute or data has no shape attribute. raise ValueError('"ratio" should be a "CCDData".') def _sigma_mask(baseline, one_sigma_value, lower_sigma, upper_sigma): """Helper function to mask values outside of the specified sigma range. """ return ((baseline < -lower_sigma * one_sigma_value) | (baseline > upper_sigma * one_sigma_value)) mask = ~np.isfinite(ratio.data) medsub = (ratio.data - ndimage.median_filter(ratio.data, size=(nlmed, ncmed))) if byblocks: nlinesblock = int(math.ceil(nlines / nlsig)) ncolsblock = int(math.ceil(ncols / ncsig)) for i in range(nlinesblock): for j in range(ncolsblock): l1 = i * nlsig l2 = min((i + 1) * nlsig, nlines) c1 = j * ncsig c2 = min((j + 1) * ncsig, ncols) block = medsub[l1:l2, c1:c2] high = np.percentile(block.ravel(), 69.1) low = np.percentile(block.ravel(), 30.9) block_sigma = (high - low) / 2.0 block_mask = _sigma_mask(block, block_sigma, lsigma, hsigma) mblock = np.ma.MaskedArray(block, mask=block_mask, copy=False) if findbadcolumns: csum = np.ma.sum(mblock, axis=0) csum[csum <= 0] = 0 csum_sigma = np.ma.MaskedArray(np.sqrt(c2 - c1 - csum)) colmask = _sigma_mask(csum.filled(1), csum_sigma, lsigma, hsigma) block_mask[:, :] |= colmask[np.newaxis, :] mask[l1:l2, c1:c2] = block_mask else: high = ndimage.percentile_filter(medsub, 69.1, size=(nlsig, ncsig)) low = ndimage.percentile_filter(medsub, 30.9, size=(nlsig, ncsig)) sigmas = (high - low) / 2.0 mask |= _sigma_mask(medsub, sigmas, lsigma, hsigma) if findbadcolumns: # Loop through columns and look for short segments (>> import ccdproc >>> import numpy as np >>> ccdproc.bitfield_to_boolean_mask(np.arange(8)) array([False, True, True, True, True, True, True, True]...) To ignore all bit flags ``ignore_bits=None`` can be used:: >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits=None) array([False, False, False, False, False, False, False, False]...) To ignore only specific bit flags one can use a ``list`` of bits flags to ignore:: >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits=[1, 4]) array([False, False, True, True, False, False, True, True]...) There are some equivalent ways:: >>> # pass in the sum of the "ignore_bits" directly >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits=5) # 1 + 4 array([False, False, True, True, False, False, True, True]...) >>> # use a comma seperated string of integers >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits='1, 4') array([False, False, True, True, False, False, True, True]...) >>> # use a + seperated string of integers >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits='1+4') array([False, False, True, True, False, False, True, True]...) Instead of directly specifying the **bits flags to ignore** one can also pass in the **only bits that shouldn't be ignored** by prepending a ``~`` to the string of ``ignore_bits`` (or if it's not a string in ``ignore_bits`` one can set ``flip_bits=True``):: >>> # ignore all bit flags except the one for 2. >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits='~(2)') array([False, False, True, True, False, False, True, True]...) >>> # ignore all bit flags except the one for 1, 8 and 32. >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits='~(1, 8, 32)') array([False, True, False, True, False, True, False, True]...) >>> # Equivalent for a list using flip_bits. >>> ccdproc.bitfield_to_boolean_mask(np.arange(8), ignore_bits=[1, 8, 32], flip_bits=True) array([False, True, False, True, False, True, False, True]...) """ return _bitfield_to_boolean_mask( bitfield, ignore_bits, flip_bits=flip_bits, good_mask_value=False, dtype=bool) class Keyword: """ """ def __init__(self, name, unit=None, value=None): self._name = name self._unit = unit self.value = value @property def name(self): return self._name @property def unit(self): return self._unit @property def value(self): return self._value @value.setter def value(self, value): if value is None: self._value = value elif isinstance(value, Quantity): self._unit = value.unit self._value = value elif isinstance(value, str): if self.unit is not None: raise ValueError("keyword with a unit cannot have a " "string value.") else: self._value = value else: if self.unit is None: raise ValueError("no unit provided. Set value with " "an astropy.units.Quantity.") self._value = value * self.unit def value_from(self, header): """ Set value of keyword from FITS header. Parameters ---------- header : `~astropy.io.fits.Header` FITS header containing a value for this keyword. """ value_from_header = header[self.name] self.value = value_from_header return self.value ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1101136 ccdproc-2.3.0/ccdproc/extern/0000755000076600000240000000000000000000000017153 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/ccdproc/extern/__init__.py0000644000076600000240000000010000000000000021253 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640020543.0 ccdproc-2.3.0/ccdproc/extern/bitfield.py0000644000076600000240000004512100000000000021312 0ustar00mattcraigstaff00000000000000# External license! License can be found in "licenses/LICENSE_STSCI_TOOLS.txt". """ A module that provides functions for manipulating bitmasks and data quality (DQ) arrays. :Authors: Mihai Cara (contact: help@stsci.edu) :License: ``_ """ import sys import warnings import numpy as np __version__ = '1.1.1' __vdate__ = '30-January-2018' __author__ = 'Mihai Cara' __all__ = ['bitfield_to_boolean_mask', 'interpret_bit_flags', 'is_bit_flag'] # Revision history: # 0.1.0 (29-March-2015) - initial release based on code from stsci.skypac # 0.1.1 (21-February-2017) - documentation typo fix # 0.2.0 (23-February-2017) - performance and stability improvements. Changed # default output mask type from numpy.uint8 to numpy.bool_. # 1.0.0 (16-March-2017) - Multiple enhancements: # 1. Deprecated 'interpret_bits_value()'in favor of # 'interpret_bit_flags()' which now takes 'flip_bits' argument to flip # bits in (list of) integer flags. # 2. Deprecated 'bitmask2mask()' in favor of 'bitfield_to_boolean_mask()' # which now also takes 'flip_bits' argument. # 3. Renamed arguments of 'interpret_bit_flags()' and # 'bitfield_to_boolean_mask()' to be more technically correct. # 4. 'interpret_bit_flags()' and 'bitfield_to_boolean_mask()' now # accept Python lists of bit flags (in addition to integer bitmasks # and string comma- (or '+') separated lists of bit flags). # 5. Added 'is_bit_flag()' function to check if an integer number has # only one bit set (i.e., that it is a power of 2). # 1.1.0 (29-January-2018) - Multiple enhancements: # 1. Added support for long type in Python 2.7 in # `interpret_bit_flags()` and `bitfield_to_boolean_mask()`. # 2. `interpret_bit_flags()` now always returns `int` (or `int` or `long` # in Python 2.7). Previously when input was of integer-like type # (i.e., `numpy.uint64`), it was not converted to Python `int`. # 3. `bitfield_to_boolean_mask()` will no longer crash when # `ignore_flags` argument contains bit flags beyond what the type of # the argument `bitfield` can hold. # 1.1.1 (30-January-2018) - Improved filtering of high bits in flags. # INT_TYPE = (int, long,) if sys.version_info < (3,) else (int,) MAX_UINT_TYPE = np.maximum_sctype(np.uint) SUPPORTED_FLAGS = int(np.bitwise_not( 0, dtype=MAX_UINT_TYPE, casting='unsafe' )) def is_bit_flag(n): """ Verifies if the input number is a bit flag (i.e., an integer number that is an integer power of 2). Parameters ---------- n : int A positive integer number. Non-positive integers are considered not to be "flags". Returns ------- bool ``True`` if input ``n`` is a bit flag and ``False`` if it is not. """ if n < 1: return False return bin(n).count('1') == 1 def _is_int(n): return ( (isinstance(n, INT_TYPE) and not isinstance(n, bool)) or (isinstance(n, np.generic) and np.issubdtype(n, np.integer)) ) def interpret_bit_flags(bit_flags, flip_bits=None): """ Converts input bit flags to a single integer value (bitmask) or `None`. When input is a list of flags (either a Python list of integer flags or a sting of comma- or '+'-separated list of flags), the returned bitmask is obtained by summing input flags. .. note:: In order to flip the bits of the returned bitmask, for input of `str` type, prepend '~' to the input string. '~' must be prepended to the *entire string* and not to each bit flag! For input that is already a bitmask or a Python list of bit flags, set `flip_bits` for `True` in order to flip the bits of the returned bitmask. Parameters ---------- bit_flags : int, str, list, None An integer bitmask or flag, `None`, a string of comma- or '+'-separated list of integer bit flags, or a Python list of integer bit flags. If `bit_flags` is a `str` and if it is prepended with '~', then the output bitmask will have its bits flipped (compared to simple sum of input flags). For input `bit_flags` that is already a bitmask or a Python list of bit flags, bit-flipping can be controlled through `flip_bits` parameter. flip_bits : bool, None Indicates whether or not to flip the bits of the returned bitmask obtained from input bit flags. This parameter must be set to `None` when input `bit_flags` is either `None` or a Python list of flags. Returns ------- bitmask : int or None Returns and integer bit mask formed from the input bit value or `None` if input `bit_flags` parameter is `None` or an empty string. If input string value was prepended with '~' (or `flip_bits` was set to `True`), then returned value will have its bits flipped (inverse mask). Examples -------- >>> from ccdproc.extern.bitfield import interpret_bit_flags >>> "{0:016b}".format(0xFFFF & interpret_bit_flags(28)) '0000000000011100' >>> "{0:016b}".format(0xFFFF & interpret_bit_flags('4,8,16')) '0000000000011100' >>> "{0:016b}".format(0xFFFF & interpret_bit_flags('~4,8,16')) '1111111111100011' >>> "{0:016b}".format(0xFFFF & interpret_bit_flags('~(4+8+16)')) '1111111111100011' >>> "{0:016b}".format(0xFFFF & interpret_bit_flags([4, 8, 16])) '0000000000011100' >>> "{0:016b}".format(0xFFFF & interpret_bit_flags([4, 8, 16], flip_bits=True)) '1111111111100011' """ has_flip_bits = flip_bits is not None flip_bits = bool(flip_bits) allow_non_flags = False if _is_int(bit_flags): return (~int(bit_flags) if flip_bits else int(bit_flags)) elif bit_flags is None: if has_flip_bits: raise TypeError( "Keyword argument 'flip_bits' must be set to 'None' when " "input 'bit_flags' is None." ) return None elif isinstance(bit_flags, str): if has_flip_bits: raise TypeError( "Keyword argument 'flip_bits' is not permitted for " "comma-separated string lists of bit flags. Prepend '~' to " "the string to indicate bit-flipping." ) bit_flags = str(bit_flags).strip() if bit_flags.upper() in ['', 'NONE', 'INDEF']: return None # check whether bitwise-NOT is present and if it is, check that it is # in the first position: bitflip_pos = bit_flags.find('~') if bitflip_pos == 0: flip_bits = True bit_flags = bit_flags[1:].lstrip() else: if bitflip_pos > 0: raise ValueError("Bitwise-NOT must precede bit flag list.") flip_bits = False # basic check for correct use of parenthesis: while True: nlpar = bit_flags.count('(') nrpar = bit_flags.count(')') if nlpar == 0 and nrpar == 0: break if nlpar != nrpar: raise ValueError("Unbalanced parantheses in bit flag list.") lpar_pos = bit_flags.find('(') rpar_pos = bit_flags.rfind(')') if lpar_pos > 0 or rpar_pos < (len(bit_flags) - 1): raise ValueError("Incorrect syntax (incorrect use of " "parenthesis) in bit flag list.") bit_flags = bit_flags[1:-1].strip() if ',' in bit_flags: bit_flags = bit_flags.split(',') elif '+' in bit_flags: bit_flags = bit_flags.split('+') else: if bit_flags == '': raise ValueError( "Empty bit flag lists not allowed when either bitwise-NOT " "or parenthesis are present." ) bit_flags = [bit_flags] allow_non_flags = len(bit_flags) == 1 elif hasattr(bit_flags, '__iter__'): if not all([_is_int(flag) for flag in bit_flags]): raise TypeError("Each bit flag in a list must be an integer.") else: raise TypeError("Unsupported type for argument 'bit_flags'.") bitset = set(map(int, bit_flags)) if len(bitset) != len(bit_flags): warnings.warn("Duplicate bit flags will be ignored") bitmask = 0 for v in bitset: if not is_bit_flag(v) and not allow_non_flags: raise ValueError("Input list contains invalid (not powers of two) " "bit flags") bitmask += v if flip_bits: bitmask = ~bitmask return bitmask def bitfield_to_boolean_mask(bitfield, ignore_flags=0, flip_bits=None, good_mask_value=True, dtype=np.bool_): r""" bitfield_to_boolean_mask(bitfield, ignore_flags=None, flip_bits=None, \ good_mask_value=True, dtype=numpy.bool\_) Converts an array of bit fields to a boolean (or integer) mask array according to a bitmask constructed from the supplied bit flags (see ``ignore_flags`` parameter). This function is particularly useful to convert data quality arrays to boolean masks with selective filtering of DQ flags. Parameters ---------- bitfield : numpy.ndarray An array of bit flags. By default, values different from zero are interpreted as "bad" values and values equal to zero are considered as "good" values. However, see ``ignore_flags`` parameter on how to selectively ignore some bits in the ``bitfield`` array data. ignore_flags : int, str, list, None (Default = 0) An integer bitmask, a Python list of bit flags, a comma- or '+'-separated string list of integer bit flags that indicate what bits in the input ``bitfield`` should be *ignored* (i.e., zeroed), or `None`. | Setting ``ignore_flags`` to `None` effectively will make `bitfield_to_boolean_mask` interpret all ``bitfield`` elements as "good" regardless of their value. | When ``ignore_flags`` argument is an integer bitmask, it will be combined using bitwise-NOT and bitwise-AND with each element of the input ``bitfield`` array (``~ignore_flags & bitfield``). If the resultant bitfield element is non-zero, that element will be interpreted as a "bad" in the output boolean mask and it will be interpreted as "good" otherwise. ``flip_bits`` parameter may be used to flip the bits (``bitwise-NOT``) of the bitmask thus effectively changing the meaning of the ``ignore_flags`` parameter from "ignore" to "use only" these flags. .. note:: Setting ``ignore_flags`` to 0 effectively will assume that all non-zero elements in the input ``bitfield`` array are to be interpreted as "bad". | When ``ignore_flags`` argument is an Python list of integer bit flags, these flags are added together to create an integer bitmask. Each item in the list must be a flag, i.e., an integer that is an integer power of 2. In order to flip the bits of the resultant bitmask, use ``flip_bits`` parameter. | Alternatively, ``ignore_flags`` may be a string of comma- or '+'-separated list of integer bit flags that should be added together to create an integer bitmask. For example, both ``'4,8'`` and ``'4+8'`` are equivalent and indicate that bit flags 4 and 8 in the input ``bitfield`` array should be ignored when generating boolean mask. .. note:: ``'None'``, ``'INDEF'``, and empty (or all white space) strings are special values of string ``ignore_flags`` that are interpreted as `None`. .. note:: Each item in the list must be a flag, i.e., an integer that is an integer power of 2. In addition, for convenience, an arbitrary **single** integer is allowed and it will be interpretted as an integer bitmask. For example, instead of ``'4,8'`` one could simply provide string ``'12'``. .. note:: When ``ignore_flags`` is a `str` and when it is prepended with '~', then the meaning of ``ignore_flags`` parameters will be reversed: now it will be interpreted as a list of bit flags to be *used* (or *not ignored*) when deciding which elements of the input ``bitfield`` array are "bad". Following this convention, an ``ignore_flags`` string value of ``'~0'`` would be equivalent to setting ``ignore_flags=None``. .. warning:: Because prepending '~' to a string ``ignore_flags`` is equivalent to setting ``flip_bits`` to `True`, ``flip_bits`` cannot be used with string ``ignore_flags`` and it must be set to `None`. flip_bits : bool, None (Default = None) Specifies whether or not to invert the bits of the bitmask either supplied directly through ``ignore_flags`` parameter or built from the bit flags passed through ``ignore_flags`` (only when bit flags are passed as Python lists of integer bit flags). Occasionally, it may be useful to *consider only specific bit flags* in the ``bitfield`` array when creating a boolean mask as opposite to *ignoring* specific bit flags as ``ignore_flags`` behaves by default. This can be achieved by inverting/flipping the bits of the bitmask created from ``ignore_flags`` flags which effectively changes the meaning of the ``ignore_flags`` parameter from "ignore" to "use only" these flags. Setting ``flip_bits`` to `None` means that no bit flipping will be performed. Bit flipping for string lists of bit flags must be specified by prepending '~' to string bit flag lists (see documentation for ``ignore_flags`` for more details). .. warning:: This parameter can be set to either `True` or `False` **ONLY** when ``ignore_flags`` is either an integer bitmask or a Python list of integer bit flags. When ``ignore_flags`` is either `None` or a string list of flags, ``flip_bits`` **MUST** be set to `None`. good_mask_value : int, bool (Default = True) This parameter is used to derive the values that will be assigned to the elements in the output boolean mask array that correspond to the "good" bit fields (that are 0 after zeroing bits specified by ``ignore_flags``) in the input ``bitfield`` array. When ``good_mask_value`` is non-zero or `True` then values in the output boolean mask array corresponding to "good" bit fields in ``bitfield`` will be `True` (if ``dtype`` is `numpy.bool_`) or 1 (if ``dtype`` is of numerical type) and values of corresponding to "bad" flags will be `False` (or 0). When ``good_mask_value`` is zero or `False` then the values in the output boolean mask array corresponding to "good" bit fields in ``bitfield`` will be `False` (if ``dtype`` is `numpy.bool_`) or 0 (if ``dtype`` is of numerical type) and values of corresponding to "bad" flags will be `True` (or 1). dtype : data-type (Default = numpy.bool\_) The desired data-type for the output binary mask array. Returns ------- mask : numpy.ndarray Returns an array of the same dimensionality as the input ``bitfield`` array whose elements can have two possible values, e.g., `True` or `False` (or 1 or 0 for integer ``dtype``) according to values of to the input ``bitfield`` elements, ``ignore_flags`` parameter, and the ``good_mask_value`` parameter. Examples -------- >>> from ccdproc.extern import bitfield >>> import numpy as np >>> dqbits = np.asarray([[0, 0, 1, 2, 0, 8, 12, 0], ... [10, 4, 0, 0, 0, 16, 6, 0]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=0, ... dtype=int) array([[1, 1, 0, 0, 1, 0, 0, 1], [0, 0, 1, 1, 1, 0, 0, 1]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=0, ... dtype=bool) array([[ True, True, False, False, True, False, False, True], [False, False, True, True, True, False, False, True]]...) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=6, ... good_mask_value=0, dtype=int) array([[0, 0, 1, 0, 0, 1, 1, 0], [1, 0, 0, 0, 0, 1, 0, 0]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=~6, ... good_mask_value=0, dtype=int) array([[0, 0, 0, 1, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=6, dtype=int, ... flip_bits=True, good_mask_value=0) array([[0, 0, 0, 1, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags='~(2+4)', ... good_mask_value=0, dtype=int) array([[0, 0, 0, 1, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0]]) >>> bitfield.bitfield_to_boolean_mask(dqbits, ignore_flags=[2, 4], ... flip_bits=True, good_mask_value=0, ... dtype=int) array([[0, 0, 0, 1, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0]]) """ bitfield = np.asarray(bitfield) if not np.issubdtype(bitfield.dtype, np.integer): raise TypeError("Input bitfield array must be of integer type.") ignore_mask = interpret_bit_flags(ignore_flags, flip_bits=flip_bits) if ignore_mask is None: if good_mask_value: mask = np.ones_like(bitfield, dtype=dtype) else: mask = np.zeros_like(bitfield, dtype=dtype) return mask # filter out bits beyond the maximum supported by the data type: ignore_mask = ignore_mask & SUPPORTED_FLAGS # invert the "ignore" mask: ignore_mask = np.bitwise_not(ignore_mask, dtype=bitfield.dtype, casting='unsafe') mask = np.empty_like(bitfield, dtype=np.bool_) np.bitwise_and(bitfield, ignore_mask, out=mask, casting='unsafe') if good_mask_value: np.logical_not(mask, out=mask) return mask.astype(dtype=dtype, subok=False, copy=False) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1637332708.0 ccdproc-2.3.0/ccdproc/image_collection.py0000644000076600000240000011067000000000000021522 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from collections import OrderedDict import fnmatch from os import listdir, path import re import logging import numpy as np import numpy.ma as ma from astropy.table import Table, MaskedColumn import astropy.io.fits as fits from astropy.utils import minversion import warnings from astropy.utils.exceptions import AstropyUserWarning from .ccddata import fits_ccddata_reader, _recognized_fits_file_extensions logger = logging.getLogger(__name__) __all__ = ['ImageFileCollection'] __doctest_skip__ = ['*'] _ASTROPY_LT_1_3 = not minversion("astropy", "1.3") class ImageFileCollection: """ Representation of a collection of image files. The class offers a table summarizing values of keywords in the FITS headers of the files in the collection and offers convenient methods for iterating over the files in the collection. The generator methods use simple filtering syntax and can automate storage of any FITS files modified in the loop using the generator. Parameters ---------- location : str or None, optional Path to directory containing FITS files. Default is ``None``. keywords : list of str, '*' or None, optional Keywords that should be used as column headings in the summary table. If the value is or includes '*' then all keywords that appear in any of the FITS headers of the files in the collection become table columns. Default value is '*' unless ``info_file`` is specified. Default is ``None``. find_fits_by_reading: bool, optional If ``True``, read each file in location to check whether the file is a FITS file and include it in the collection based on that, rather than by file name. Compressed files, e.g. image.fits.gz, will **NOT** be properly detected. *Will be ignored if `filenames` is not ``None``.* filenames: str, list of str, or None, optional List of the names of FITS files which will be added to the collection. The filenames may either be in ``location`` or the name can be a relative or absolute path to the file. Default is ``None``. glob_include: str or None, optional Unix-style filename pattern to select filenames to include in the file collection. Can be used in conjunction with ``glob_exclude`` to easily select subsets of files in the target directory. Default is ``None``. glob_exclude: str or None, optional Unix-style filename pattern to select filenames to exclude from the file collection. Can be used in conjunction with ``glob_include`` to easily select subsets of files in the target directory. Default is ``None``. ext: str or int, optional The extension from which the header and data will be read in all files.Default is ``0``. Raises ------ ValueError Raised if keywords are set to a combination of '*' and any other value. """ def __init__(self, location=None, keywords=None, find_fits_by_reading=False, filenames=None, glob_include=None, glob_exclude=None, ext=0): # Include or exclude files from the collection based on glob pattern # matching - has to go above call to _get_files() if glob_exclude is not None: glob_exclude = str(glob_exclude) # some minimal validation self._glob_exclude = glob_exclude if glob_include is not None: glob_include = str(glob_include) self._glob_include = glob_include if location is not None: self._location = location else: self._location = '' self._find_fits_by_reading = find_fits_by_reading self._filenames = filenames self._files = [] self._files = self._get_files() if self._files == []: warnings.warn("no FITS files in the collection.", AstropyUserWarning) self._summary = {} if keywords is None: # Use all keywords. keywords = '*' # Used internally to keep track of whether the user asked for all # keywords or a specific list. The keywords setter takes care of # actually setting the correct value, this just ensure that there # is always *some* value. self._all_keywords = False self._ext = ext if keywords: self.keywords = keywords def __repr__(self): if self.location is None: location = "" else: location = "location={!r}".format(self.location) if self._all_keywords: kw = "" else: kw = "keywords={!r}".format(self.keywords[1:]) if self.glob_exclude is None: glob_exclude = '' else: glob_exclude = "glob_exclude={!r}".format(self.glob_exclude) if self.glob_include is None: glob_include = '' else: glob_include = "glob_include={!r}".format(self.glob_include) if self.ext == 0: ext = "" else: ext = "ext={}".format(self.ext) if self._filenames is None: filenames = "" else: filenames = "filenames={}".format(self._filenames) params = [location, kw, filenames, glob_include, glob_exclude, ext] params = ', '.join([p for p in params if p]) str_repr = "{self.__class__.__name__}({params})".format( self=self, params=params) return str_repr @property def summary(self): """ `~astropy.table.Table` of values of FITS keywords for files in the collection. Each keyword is a column heading. In addition, there is a column called ``file`` that contains the name of the FITS file. The directory is not included as part of that name. The first column is always named ``file``. The order of the remaining columns depends on how the summary was constructed. If a wildcard, ``*`` was used then the order is the order in which the keywords appear in the FITS files from which the summary is constructed. If an explicit list of keywords was supplied in setting up the collection then the order of the columns is the order of the keywords. """ return self._summary @property def location(self): """ str, Path name to directory containing FITS files. """ return self._location @property def keywords(self): """ list of str, Keywords currently in the summary table. Setting the keywords causes the summary table to be regenerated unless the new keywords are a subset of the old. .. versionchanged:: 1.3 Added ``deleter`` for ``keywords`` property. """ if self.summary: return self.summary.keys() else: return [] @keywords.setter def keywords(self, keywords): # since keywords are drawn from self.summary, setting # summary sets the keywords. if keywords is None: self._summary = [] return if keywords == '*': self._all_keywords = True else: self._all_keywords = False logging.debug('keywords in setter before pruning: %s.', keywords) # remove duplicates and force a copy so we can sort the items later # by their given position. new_keys_set = set(keywords) new_keys_lst = list(new_keys_set) new_keys_set.add('file') logging.debug('keywords after pruning %s.', new_keys_lst) current_set = set(self.keywords) if new_keys_set.issubset(current_set): logging.debug('table columns before trimming: %s.', ' '.join(current_set)) cut_keys = current_set.difference(new_keys_set) logging.debug('will try removing columns: %s.', ' '.join(cut_keys)) for key in cut_keys: self._summary.remove_column(key) logging.debug('after removal column names are: %s.', ' '.join(self.keywords)) else: logging.debug('should be building new table...') # Reorder the keywords to match the initial ordering. new_keys_lst.sort(key=keywords.index) self._summary = self._fits_summary(new_keys_lst) @keywords.deleter def keywords(self): # since keywords are drawn from self._summary, setting # _summary = [] deletes the keywords. self._summary = [] @property def files(self): """ list of str, Unfiltered list of FITS files in location. """ return self._files @property def glob_include(self): """ str or None, Unix-style filename pattern to select filenames to include in the file collection. """ return self._glob_include @property def glob_exclude(self): """ str or None, Unix-style filename pattern to select filenames to exclude in the file collection. """ return self._glob_exclude @property def ext(self): """ str or int, The extension from which the header and data will be read in all files. """ return self._ext def values(self, keyword, unique=False): """ List of values for a keyword. Parameters ---------- keyword : str Keyword (i.e. table column) for which values are desired. unique : bool, optional If True, return only the unique values for the keyword. Default is ``False``. Returns ------- list Values as a list. """ if keyword not in self.keywords: raise ValueError( 'keyword %s is not in the current summary' % keyword) if unique: return list(set(self.summary[keyword].tolist())) else: return self.summary[keyword].tolist() def files_filtered(self, **kwd): """Determine files whose keywords have listed values. Parameters ---------- include_path : bool, keyword-only If the keyword ``include_path=True`` is set, the returned list contains not just the filename, but the full path to each file. Default is ``False``. regex_match : bool, keyword-only If ``True``, then string values in the ``**kwd`` dictionary are treated as regular expression patterns and matching is done by regular expression search. The search is always **case insensitive**. **kwd : ``**kwd`` is dict of keywords and values the files must have. The value '*' represents any value. A missing keyword is indicated by value ''. Returns ------- filenames : list The files that satisfy the keyword-value restrictions specified by the ``**kwd``. Examples -------- Some examples for filtering:: >>> keys = ['imagetyp','filter'] >>> collection = ImageFileCollection('test/data', keywords=keys) >>> collection.files_filtered(imagetyp='LIGHT', filter='R') >>> collection.files_filtered(imagetyp='*', filter='') In case you want to filter with keyword names that cannot be used as keyword argument name, you have to unpack them using a dictionary. For example if a keyword name contains a space or a ``-``:: >>> add_filters = {'exp-time': 20, 'ESO TPL ID': 1050} >>> collection.files_filtered(imagetyp='LIGHT', **add_filters) Notes ----- Value comparison is case *insensitive* for strings, whether matching exactly or matching with regular expressions. """ # force a copy by explicitly converting to a list current_file_mask = self.summary['file'].mask.tolist() include_path = kwd.pop('include_path', False) self._find_keywords_by_values(**kwd) filtered_files = self.summary['file'].compressed() self.summary['file'].mask = current_file_mask if include_path: filtered_files = [path.join(self._location, f) for f in filtered_files.tolist()] return filtered_files def refresh(self): """ Refresh the collection by re-reading headers. """ keywords = '*' if self._all_keywords else self.keywords # Re-load list of files self._files = self._get_files() self._summary = self._fits_summary(header_keywords=keywords) def sort(self, keys): """Sort the list of files to determine the order of iteration. Sort the table of files according to one or more keys. This does not create a new object, instead is sorts in place. Parameters ---------- keys : str, list of str The key(s) to order the table by. """ if len(self._summary) > 0: self._summary.sort(keys) self._files = self.summary['file'].tolist() def filter(self, **kwd): """ Create a new collection by filtering the current collection. Parameters ---------- regex_match : bool, keyword-only If ``True``, then string values in the ``**kwd`` dictionary are treated as regular expression patterns and matching is done by regular expression search. The search is always **case insensitive**. **kwd : ``**kwd`` is dict of keywords and values the files must have. The value '*' represents any value. A missing keyword is indicated by value ''. Returns ------- `ImageFileCollection` A new collection with the files matched by the arguments to filter. """ files = self.files_filtered(include_path=True, **kwd) return ImageFileCollection(filenames=files, keywords=self.keywords) def _get_files(self): """ Helper method which checks whether ``files`` should be set to a subset of file names or to all file names in a directory. Returns ------- files : list or str List of file names which will be added to the collection. """ files = [] if self._filenames: if isinstance(self._filenames, str): files.append(self._filenames) else: files = self._filenames else: # Check if self.location is set, otherwise proceed with empty list if self.location != '': files = self._fits_files_in_directory() if self.glob_include is not None: files = fnmatch.filter(files, self.glob_include) if self.glob_exclude is not None: files = [file for file in files if not fnmatch.fnmatch(file, self.glob_exclude)] return files def _dict_from_fits_header(self, file_name, input_summary=None, missing_marker=None): """ Construct an ordered dictionary whose keys are the header keywords and values are a list of the values from this file and the input dictionary. If the input dictionary is ordered then that order is preserved. Parameters ---------- file_name : str Name of FITS file. input_summary : dict or None, optional Existing dictionary to which new values should be appended. Default is ``None``. missing_marker : any type, optional Fill value for missing header-keywords. Default is ``None``. Returns ------- file_table : `~astropy.table.Table` """ def _add_val_to_dict(key, value, tbl_dict, n_previous, missing_marker): try: tbl_dict[key].append(value) except KeyError: tbl_dict[key] = [missing_marker] * n_previous tbl_dict[key].append(value) if input_summary is None: summary = OrderedDict() n_previous = 0 else: summary = input_summary n_previous = len(summary['file']) h = fits.getheader(file_name, self.ext) assert 'file' not in h if self.location: # We have a location and can reconstruct the path using it name_for_file_column = path.basename(file_name) else: # No location, so use whatever path the user passed in name_for_file_column = file_name # Try opening header before this so that file name is only added if # file is valid FITS try: summary['file'].append(name_for_file_column) except KeyError: summary['file'] = [name_for_file_column] missing_in_this_file = [k for k in summary if (k not in h and k != 'file')] multi_entry_keys = {'comment': [], 'history': []} alreadyencountered = set() for k, v in h.items(): if k == '': continue k = k.lower() if k in ['comment', 'history']: multi_entry_keys[k].append(str(v)) # Accumulate these in a separate dictionary until the # end to avoid adding multiple entries to summary. continue elif k in alreadyencountered: # The "normal" multi-entries HISTORY, COMMENT and BLANK are # already processed so any further duplication is probably # a mistake. It would lead to problems in ImageFileCollection # to add it as well, so simply ignore those. warnings.warn( 'Header from file "{f}" contains multiple entries for ' '"{k}", the pair "{k}={v}" will be ignored.' ''.format(k=k, v=v, f=file_name), UserWarning) continue else: # Add the key to the already encountered keys so we don't add # it more than once. alreadyencountered.add(k) _add_val_to_dict(k, v, summary, n_previous, missing_marker) for k, v in multi_entry_keys.items(): if v: joined = ','.join(v) _add_val_to_dict(k, joined, summary, n_previous, missing_marker) for missing in missing_in_this_file: summary[missing].append(missing_marker) return summary def _set_column_name_case_to_match_keywords(self, header_keys, summary_table): for k in header_keys: k_lower = k.lower() if k_lower != k: try: summary_table.rename_column(k_lower, k) except KeyError: pass def _fits_summary(self, header_keywords): """ Generate a summary table of keywords from FITS headers. Parameters ---------- header_keywords : list of str or '*' Keywords whose value should be extracted from FITS headers or '*' to extract all. """ if not self.files: return None # Make sure we have a list...for example, in python 3, dict.keys() # is not a list. original_keywords = list(header_keywords) # Get rid of any duplicate keywords, also forces a copy. header_keys = set(original_keywords) header_keys.add('file') file_name_column = MaskedColumn(name='file', data=self.files) if not header_keys or (header_keys == {'file'}): summary_table = Table(masked=True) summary_table.add_column(file_name_column) return summary_table summary_dict = None missing_marker = None for file_name in file_name_column.tolist(): file_path = path.join(self.location, file_name) try: # Note: summary_dict is an OrderedDict, so should preserve # the order of the keywords in the FITS header. summary_dict = self._dict_from_fits_header( file_path, input_summary=summary_dict, missing_marker=missing_marker) except IOError as e: logger.warning('unable to get FITS header for file %s: %s.', file_path, e) continue summary_table = Table(summary_dict, masked=True) for column in summary_table.colnames: summary_table[column].mask = [ v is missing_marker for v in summary_table[column].tolist()] self._set_column_name_case_to_match_keywords(header_keys, summary_table) missing_columns = header_keys - set(summary_table.colnames) missing_columns -= {'*'} length = len(summary_table) for column in missing_columns: all_masked = MaskedColumn(name=column, data=np.zeros(length), mask=np.ones(length)) summary_table.add_column(all_masked) if '*' not in header_keys: # Rearrange table columns to match order of keywords. # File always comes first. header_keys -= {'file'} original_order = ['file'] + sorted(header_keys, key=original_keywords.index) summary_table = summary_table[original_order] if not summary_table.masked: summary_table = Table(summary_table, masked=True) return summary_table def _find_keywords_by_values(self, **kwd): """ Find files whose keywords have given values. Parameters ---------- match_regex : bool, optional If ``True`` match string values by using a regular expression search instead of equality. Default value is ``False``. The remaining arguments are keyword/value pairs specifying the values to match. `**kwd` is list of keywords and values the files must have. The value '*' represents any value. A missing keyword is indicated by value '' Example:: >>> keys = ['imagetyp','filter'] >>> collection = ImageFileCollection('test/data', keywords=keys) >>> collection.files_filtered(imagetyp='LIGHT', filter='R') >>> collection.files_filtered(imagetyp='*', filter='') >>> collection.files_filtered(imagetyp='bias|filter', regex_match=True) NOTE: Value comparison is case *insensitive* for strings. """ regex_match = kwd.pop('regex_match', False) keywords = kwd.keys() values = kwd.values() if set(keywords).issubset(self.keywords): # we already have the information in memory use_info = self.summary else: # we need to load information about these keywords. use_info = self._fits_summary(header_keywords=keywords) matches = np.ones(len(use_info), dtype=bool) for key, value in zip(keywords, values): logger.debug('key %s, value %s', key, value) logger.debug('value in table %s', use_info[key]) value_missing = use_info[key].mask logger.debug('value missing: %s', value_missing) value_not_missing = np.logical_not(value_missing) if value == '*': have_this_value = value_not_missing elif value is not None: if isinstance(value, str): # need to loop explicitly over array rather than using # where to correctly do string comparison. have_this_value = np.zeros(len(use_info), dtype=bool) # We are going to do a regex match no matter what. if regex_match: pattern = re.compile(value, flags=re.IGNORECASE) else: # Escape all special characters that might be present value = re.escape(value) # This pattern matches the prior behavior. pattern = re.compile('^' + value + '$', flags=re.IGNORECASE) for idx, file_key_value in enumerate(use_info[key].tolist()): if value_not_missing[idx]: try: value_matches = ( pattern.search(file_key_value) is not None) except TypeError: # In case we're dealing with an object column # there could be values other than strings in it # so it could fail with an TypeError. value_matches = False else: value_matches = False have_this_value[idx] = (value_not_missing[idx] & value_matches) else: have_this_value = value_not_missing tmp = (use_info[key][value_not_missing] == value) have_this_value[value_not_missing] = tmp have_this_value &= value_not_missing else: # this case--when value==None--is asking for the files which # are missing a value for this keyword have_this_value = value_missing matches &= have_this_value # the numpy convention is that the mask is True for values to # be omitted, hence use ~matches. logger.debug('Matches: %s', matches) self.summary['file'].mask = ma.nomask self.summary['file'].mask[~matches] = True def _fits_files_in_directory(self, extensions=None, compressed=True): """ Get names of FITS files in directory, based on filename extension. Parameters ---------- extensions : list of str or None, optional List of filename extensions that are FITS files. Default is ``['fit', 'fits', 'fts']``. Default is ``None``. compressed : bool, optional If ``True``, compressed files should be included in the list (e.g. `.fits.gz`). Default is ``True``. Returns ------- list *Names* of the files (with extension), not the full pathname. """ full_extensions = extensions or list(_recognized_fits_file_extensions) # The common compressed fits image .fz is supported using ext=1 when calling ImageFileCollection if compressed: for comp in ['.gz', '.bz2', '.Z', '.zip', '.fz']: with_comp = [extension + comp for extension in full_extensions] full_extensions.extend(with_comp) all_files = listdir(self.location) files = [] if not self._find_fits_by_reading: for extension in full_extensions: files.extend(fnmatch.filter(all_files, '*' + extension)) else: for infile in all_files: inpath = path.join(self.location, infile) with open(inpath, 'rb') as fp: # Hmm, first argument to is_fits is not actually used in # that function. *shrug* if fits.connect.is_fits('just some junk', infile, fp): files.append(infile) files.sort() return files def _generator(self, return_type, save_with_name="", save_location='', clobber=False, overwrite=False, do_not_scale_image_data=True, return_fname=False, ccd_kwargs=None, **kwd): """ Generator that yields each {name} in the collection. If any of the parameters ``save_with_name``, ``save_location`` or ``overwrite`` evaluates to ``True`` the generator will write a copy of each FITS file it is iterating over. In other words, if ``save_with_name`` and/or ``save_location`` is a string with non-zero length, and/or ``overwrite`` is ``True``, a copy of each FITS file will be made. Parameters ---------- save_with_name : str, optional string added to end of file name (before extension) if FITS file should be saved after iteration. Unless ``save_location`` is set, files will be saved to location of the source files ``self.location``. Default is ``''``. save_location : str, optional Directory in which to save FITS files; implies that FITS files will be saved. Note this provides an easy way to copy a directory of files--loop over the {name} with ``save_location`` set. Default is ``''``. overwrite : bool, optional If ``True``, overwrite input FITS files. Default is ``False``. clobber : bool, optional Alias for ``overwrite``. Default is ``False``. do_not_scale_image_data : bool, optional If ``True``, prevents fits from scaling images. Default is ``{default_scaling}``. Default is ``True``. return_fname : bool, optional If True, return the tuple (header, file_name) instead of just header. The file name returned is the name of the file only, not the full path to the file. Default is ``False``. ccd_kwargs : dict, optional Dict with parameters for `~astropy.nddata.fits_ccddata_reader`. For instance, the key ``'unit'`` can be used to specify the unit of the data. If ``'unit'`` is not given then ``'adu'`` is used as the default unit. See `~astropy.nddata.fits_ccddata_reader` for a complete list of parameters that can be passed through ``ccd_kwargs``. regex_match : bool, keyword-only If ``True``, then string values in the ``**kwd`` dictionary are treated as regular expression patterns and matching is done by regular expression search. The search is always **case insensitive**. **kwd : Any additional keywords are used to filter the items returned; see `files_filtered` examples for details. Returns ------- `{return_type}` If ``return_fname`` is ``False``, yield the next {name} in the collection. (`{return_type}`, str) If ``return_fname`` is ``True``, yield a tuple of ({name}, ``file name``) for the next item in the collection. """ # store mask so we can reset at end--must COPY, otherwise # current_mask just points to the mask of summary if not self.summary: return current_mask = {} for col in self.summary.columns: current_mask[col] = self.summary[col].mask if kwd: self._find_keywords_by_values(**kwd) ccd_kwargs = ccd_kwargs or {} for full_path in self._paths(): add_kwargs = {'do_not_scale_image_data': do_not_scale_image_data} # We need to open the file here, get the appropriate values and then # close it again before it "yields" otherwise it's not garantueed # that the generator actually advances and closes the file again. # For example if one uses "next" on the generator manually the # file handle could "leak". if return_type == 'header': return_thing = fits.getheader(full_path, self.ext) elif return_type == 'data': return_thing = fits.getdata(full_path, self.ext, **add_kwargs) elif return_type == 'ccd': return_thing = fits_ccddata_reader( full_path, hdu=self.ext, **ccd_kwargs) elif return_type == 'hdu': with fits.open(full_path, **add_kwargs) as hdulist: ext_index = hdulist.index_of(self.ext) # Need to copy the HDU to prevent lazy loading problems # and "IO operations on closed file" errors return_thing = hdulist[ext_index].copy() else: raise ValueError('no generator for {}'.format(return_type)) file_name = path.basename(full_path) if return_fname: yield return_thing, file_name else: yield return_thing if save_location: destination_dir = save_location else: destination_dir = path.dirname(full_path) basename = path.basename(full_path) if save_with_name: base, ext = path.splitext(basename) basename = base + save_with_name + ext new_path = path.join(destination_dir, basename) # I really should have called the option overwrite from # the beginning. The hack below ensures old code works, # at least... if clobber or overwrite: if _ASTROPY_LT_1_3: nuke_existing = {'clobber': True} else: nuke_existing = {'overwrite': True} else: nuke_existing = {} if return_type == 'ccd': pass elif (new_path != full_path) or nuke_existing: with fits.open(full_path, **add_kwargs) as hdulist: ext_index = hdulist.index_of(self.ext) if return_type == 'hdu': hdulist[ext_index] = return_thing elif return_type == 'data': hdulist[ext_index].data = return_thing elif return_type == 'header': hdulist[ext_index].header = return_thing try: hdulist.writeto(new_path, **nuke_existing) except IOError: logger.error('error writing file %s', new_path) raise # reset mask for col in self.summary.columns: self.summary[col].mask = current_mask[col] def _paths(self): """ Full path to each file. """ unmasked_files = self.summary['file'].compressed().tolist() return [path.join(self.location, file_) for file_ in unmasked_files] def headers(self, do_not_scale_image_data=True, **kwd): return self._generator('header', do_not_scale_image_data=do_not_scale_image_data, **kwd) headers.__doc__ = _generator.__doc__.format( name='header', default_scaling='True', return_type='astropy.io.fits.Header') def hdus(self, do_not_scale_image_data=False, **kwd): return self._generator('hdu', do_not_scale_image_data=do_not_scale_image_data, **kwd) hdus.__doc__ = _generator.__doc__.format( name='HDUList', default_scaling='False', return_type='astropy.io.fits.HDUList') def data(self, do_not_scale_image_data=False, **kwd): return self._generator('data', do_not_scale_image_data=do_not_scale_image_data, **kwd) data.__doc__ = _generator.__doc__.format( name='image', default_scaling='False', return_type='numpy.ndarray') def ccds(self, ccd_kwargs=None, **kwd): if kwd.get('clobber') or kwd.get('overwrite'): raise NotImplementedError( "overwrite=True (or clobber=True) is not supported for CCDs.") return self._generator('ccd', ccd_kwargs=ccd_kwargs, **kwd) ccds.__doc__ = _generator.__doc__.format( name='CCDData', default_scaling='True', return_type='astropy.nddata.CCDData') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/ccdproc/log_meta.py0000644000076600000240000001263500000000000020016 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from functools import wraps import inspect from itertools import chain import numpy as np from astropy.nddata import NDData from astropy import units as u from astropy.io import fits import ccdproc # Really only need Keyword from ccdproc __all__ = [] _LOG_ARGUMENT = 'add_keyword' _LOG_ARG_HELP = \ """ {arg} : str, `~ccdproc.Keyword` or dict-like, optional Item(s) to add to metadata of result. Set to False or None to completely disable logging. Default is to add a dictionary with a single item: The key is the name of this function and the value is a string containing the arguments the function was called with, except the value of this argument. """.format(arg=_LOG_ARGUMENT) def _insert_in_metadata_fits_safe(ccd, key, value): from .core import _short_names if key in _short_names: # This keyword was (hopefully) added by autologging but the # combination of it and its value not FITS-compliant in two # ways: the keyword name may be more than 8 characters and # the value may be too long. FITS cannot handle both of # those problems at once, so this fixes one of those # problems... # Shorten, sort of... short_name = _short_names[key] if isinstance(ccd.meta, fits.Header): ccd.meta['HIERARCH {0}'.format(key.upper())] = ( short_name, "Shortened name for ccdproc command") else: ccd.meta[key] = ( short_name, "Shortened name for ccdproc command") ccd.meta[short_name] = value else: ccd.meta[key] = value def log_to_metadata(func): """ Decorator that adds logging to ccdproc functions. The decorator adds the optional argument _LOG_ARGUMENT to function signature and updates the function's docstring to reflect that. It also sets the default value of the argument to the name of the function and the arguments it was called with. """ func.__doc__ = func.__doc__.format(log=_LOG_ARG_HELP) argspec = inspect.getfullargspec(func) original_args, varargs, keywords, defaults = (argspec.args, argspec.varargs, argspec.varkw, argspec.defaults) # original_args = argspec.args # varargs = argspec.varargs # keywords = argspec.varkw # defaults = argspec.defaults # Grab the names of positional arguments for use in automatic logging try: original_positional_args = original_args[:-len(defaults)] except TypeError: original_positional_args = original_args # Add logging keyword and its default value for docstring original_args.append(_LOG_ARGUMENT) try: defaults = list(defaults) except TypeError: defaults = [] defaults.append(True) signature_with_arg_added = inspect.signature(func) signature_with_arg_added = "{0}{1}".format(func.__name__, signature_with_arg_added) func.__doc__ = "\n".join([signature_with_arg_added, func.__doc__]) @wraps(func) def wrapper(*args, **kwd): # Grab the logging keyword, if it is present. log_result = kwd.pop(_LOG_ARGUMENT, True) result = func(*args, **kwd) if not log_result: # No need to add metadata.... meta_dict = {} elif log_result is not True: meta_dict = _metadata_to_dict(log_result) else: # Logging is not turned off, but user did not provide a value # so construct one unless the config parameter auto_logging is set to False if ccdproc.conf.auto_logging: key = func.__name__ # Get names of arguments, which may or may not have # been called as keywords. positional_args = original_args[:len(args)] all_args = chain(zip(positional_args, args), kwd.items()) all_args = ["{0}={1}".format(name, _replace_array_with_placeholder(val)) for name, val in all_args] log_val = ", ".join(all_args) log_val = log_val.replace("\n", "") meta_dict = {key: log_val} else: meta_dict = {} for k, v in meta_dict.items(): _insert_in_metadata_fits_safe(result, k, v) return result return wrapper def _metadata_to_dict(arg): if isinstance(arg, str): # add the key, no value return {arg: None} elif isinstance(arg, ccdproc.Keyword): return {arg.name: arg.value} else: return arg def _replace_array_with_placeholder(value): return_type_not_value = False if isinstance(value, u.Quantity): return_type_not_value = not value.isscalar elif isinstance(value, (NDData, np.ndarray)): try: length = len(value) except TypeError: # Value has no length... try: # ...but if it is NDData its .data will have a length length = len(value.data) except TypeError: # No idea what this data is, assume length is not 1 length = 42 return_type_not_value = length > 1 if return_type_not_value: return "<{0}>".format(value.__class__.__name__) else: return value ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1263268 ccdproc-2.3.0/ccdproc/tests/0000755000076600000240000000000000000000000017010 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/ccdproc/tests/__init__.py0000644000076600000240000000017100000000000021120 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This packages contains affiliated package tests. """ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1324606 ccdproc-2.3.0/ccdproc/tests/data/0000755000076600000240000000000000000000000017721 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/ccdproc/tests/data/README.rst0000644000076600000240000000040100000000000021403 0ustar00mattcraigstaff00000000000000Data directory ============== This directory contains data files included with the affiliated package source code distribution. Note that this is intended only for relatively small files - large files should be externally hosted and downloaded as needed. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/tests/data/a8280271.fits0000644000076600000240000211070000000000000021605 0ustar00mattcraigstaff00000000000000SIMPLE = T / BITPIX = 16 / NAXIS = 2 / NAXIS1 = 536 / columns NAXIS2 = 520 / rows OBSERVAT= 'SAAO ' / observatory source TELESCOP= 'SAAO 1.0m' / OBSERVER= 'crawford ' / observer INSTRUME= 'STE3 CCD' / DATE-OBS= '2013-07-13' / UT date OBJECT = 'rf0420 ' / IMAGETYP= 'object ' / IRAF image type FILTERS = 48 / Filters 10*A+B RA = ' 22:04:08' / ra DEC = '-00:55:31' / dec RA_OBS = 331.0334 / ra in degrees DEC_OBS = -0.9253 / dec in degrees EPOCH = 2000.0 / Equinox of RA,Dec UT = '00:57:33' / UT at Exposure start ST = '21:45:06' / Siderial time at Start exp EXPTIME = 150.040 / integration time in secs SECZ = 1.176 / Air mass at start exp TRIMSEC = '[ 17: 528, 1: 520]' / Useful part of data BIASSEC = '[ 4: 13, 1: 520]' / Overscan region BSCALE = 1 / DATA=BSCALE*INT+BZERO BZERO = 32768 / GAIN = 1.9 / e-/ADU RDNOISE = 5.0 / e-(rms) read noise CCD-TEMP= 180.2 / CCD Cu block temperature MJD-OBS = 56486.0 COMMENT = END ƀӀԀрԀـрրրҀ׀׀ր׀ҁ$&/%+%,$/$#$'7 )4 &&/)+5 $'%, *&(%/#'+! '*8/&,-&04)&*1!+$'##%!7"*'(-%4) $-5#71&''&4%#&' ),'!5(,%'&','%)"-.( -*$&1-" '!'5"#"(($+-5'';('*,1,,.*8#3)'%!/4+1;(/ /$(#'%.&%!%!2%*!$1())' %5/%,2")% &'7!0*!''&!)/-%*%%.+%.('.*)+#,$*)2.,-*+) ,&#%%#>.0-#"//)&-+/$-(**/+*$!)*523,)",&4!!/+'0')()++/ $&3&1-9%#'#1-.!/ &*)%"13#621/%-3($%$!($) +1-"$(0,,*$ &0,5:6*;2(0$$/,(( %1'3**0$4"$1+('(1(*(",+)*-1&8 $+*.$,)(.5*&$-+2*-#',%#'#).'()0&'*"**2. 089&/#1$"-%.!/)#0#-2؀Ԁ܀ـ؀Ҁـ׀ՀƀڀՀ݀ހրԀ׀ـڀՀրڀրہ;9.-65$32.53-8<08()&15.75/+#/$%2.4"89+2:'853)3-/>+"&3.*-*--(!(!9/.2,*),*,!-)719+/56@#*5,*3(2,5/+/%.-10%(,0-&*-44/$%2,0&).*+22$#%-3(0.1C4-*+1%%*%/87"+5'7,;*-$/&5:%(1)*)423512*/,'),70+*&9+,62,72/'1B>+7/3867++:50+-*4.$!/6!&01:28+37%',6;1).)9+*.157+'2531.)+1++&(5%$)/23.+&4# /;66-4.0)//*:3')*,92/5;''75.+-/20%%0/',4*3*$-7+3&.-'56/*3-:(,-41)435/+3!0+)?+46;(@6,2+):660.,)17&B'8%3470%0*23+4/-(/8 ?7+9/6253./4*:-;)37,A/%17*7A ++=(2"29,69'*=57,52$23'6(11.69754,)8=(.0505'=2=73.'365-91/1'6:/.3)&75/356;5,76.ــ܀ր؀݀Ӏۀڀʀ׀؀ڀրــۀۀ؀Ԁـ߀րց0*466+,7)62-005.7(&1-*C&#,*;+(/%*5.3-7:2.+3*,=*'0$)19(.-%7?+6+6952..52;,.2-+(7334,>0=/'5."0%"%828.0/03/-9:9.43(A7%'9:/238+444.-6)'+)$+-*01;55/2/9)"$,63-*7:8/-/+6(9'0;1',+5*&%-0+-,061$66/'+,1,62-7870!726,+/*)-:1$/6-543/55/-58,>+.(1/)853"*20283//0)*.:<+/943(8/11),ـ݀ـڀـ؀Ӏ܀ۀȀڀۀــԀڀ߀Ӏ׀ـۀۀ؀ׁ(03(,73<31':30''8,5;7;#3.@9+(79$5%$3''!)/02;3+3-#'/2/63/+"3 ,/17;1,163*.365)40210639/.2/29*1).)())0#@8$'%2, 4,+:7.4;/43*2--.,-+2;**4+1)7&63'1.7+77('(3.,#1,211@/(-66:44'48%/25*;-*224.5*(.46*,27-../2*+3F9.65-92'5 >09):/7>251+/+4*4-3$1/7,(#-(3..2/-/8/-=3/,$+@-#-8/.=2$0$%.+2*,10"'*'-*6)49;58$*%'&(.0:!:',7'1#-$*2'/9(7)()01,5'0<.'/ *3;#-(#1#*.(00+($11,.%$$4--5A&0(.74)0%--2*(<'+%558922-42/72E880$//=:7;5728'ր׀ۀ݀րڀـԀۀʀ݀ـۀڀրҀـڀڀ؀Ԁ׀׀܁5?4.$",51,'41.75!//7+55!1450+9-21' -)546#,&.0//-&>&:.90,19#1($%:,585,3.-,/+%16090 -45,"!7'+(1-.'1-*3.-671/&)&/05'&+$3&+7-2(+%)61(&;:#-2-41$-+(-$26+/60!-69,,2"/(4/+.5%-;/(2/3.+6+(.*/=)52"0*42#.**32@);#,+/9!($(6%4=,18)&'%2/%3*12:2.73.*3/83/*78*:*A!(-.4=50;%3(20.,(-3-7/5/153*C71,-/''*$<0&62?7*%4.4D218&)+,2,/.52-<+8+(..,/4*-8/.1+48.#3+/5:9-/<+/)2519,3.5&++":!045!*@5<"$.7.3+34!0@<'6-73,,+-8,)ڀހڀـՀـԀـՀǀ݀ـ׀׀׀ӀڀՀԀԀ׀ր׀ׁ&/6(-:/6/3/835+2**5'*/>2$950:>)%''6'1*'1-,3/ %,#>017/+&.11,/$**07+("&-*#/+%(30'-(>#$+5-/+,$22(6+32+%,/,7%3,)+'0/$=,4#(/,1.5-,+5)%'81/ 27C#4)(& ))%3&/+:$4-&//2.)3/,:)5//5220)1,/)-+51/..'26*!#*%<-1'-6#-*6,1<49*(17+%546;3%/,0%/38,99$-*(-/%81)/9>1&4%$3+(/2)+-1.#581."*2--$-5(*8+))/*%,7++'-%#<,+))9"*,<67&;3+*(*+*(/;,0514,:130=,)(4-+,/7%"+$1-)74017')<+6%*,)94#//(22,+04)!&(1.).-;.9(+)./1671041,,*,3"9--112;%5//1(-;')153.+/1+3-0'+&7,"/6'%2++156" 1(6*('/3(*4)7)(,330"-325!)&5;33,/)14-&%760/#,71%-*-2&&+,,-','5>րۀԀ؀ۀ׀؀܀րʀԀ׀րـӀրۀӀ׀ր؀ހր+34.,)3$+(/"++;00$0(".,&/,@$2/981+%,.+!218,&1(2(21-,13%(9'+! '3#')2;)(+-+-+9*-7&.%7/81553')1++051101;+)-7/$+30'-+%!.)#/.,(%-)'2#.5'$*-&42!-*6,5*3/$2-015'9(.91+.;52/*%-91/25.(6&)735('/&&/+")%#&/33'(B4*+0,$:,"6%)50)/-,'027-)F%;-"00).2&0.:310--),(5%)$2(5)4+18)(#232.(,1&+3).93)'160%:--/+(08%-)*4//!1,+(;.$=9:+1$&#+4)15*(!+;@1+-/!5*#<&0"#.4%2//4'+4+,82',-*(9/*611(/''6%#%/4/'*$0)&3()!7$/ *9#,*1,/(--626*$6',.-/*1":,,"55!###!)922)/%(/67;.,7*/)/-)-31641-7+-$7"-@1/071*(+)1' 4)-0)3/3.24:4%36*(3&-81=1/5"0׀рـՀ׀ڀڀــ̀ـۀ׀ـ݀ۀ؀׀ӀҀր׀܀ف'?&$(+75-0134*$,//:3%/+.#+!5,61!++.*23)&F(-50(=-.30*79$-2)=,*8)3%%39+)>49*,&)!0%-45#/31/42')2,302--,)(*644/03'/#$+&!)-/07'$/$/83,(,.)-/?6@+&+%"42$%(0*%5$.+""5%6'*6)+-?3,)(012+7 !1(+-("47822,;3.)3%.2,'/+//-41-.6+%%1.*3$4'54+2-'#//7/4<)!42C/ &5$%%+05/2)%&59*-()/($15',&,(*2$%7-2111"-8-!!53-+.)?(5/$*;--0-2-!&',5--7.528(+"04/!743/901'4/983/1!#%*0*01/641*0'&7+/=**1++'5!-(#:(3(+24;)/446#6)/5)/71!8-,,#,4.%-&/-,#7/-&(5($0*2#'2)/%1$/*0(---4)1/2%$4.-+&*$524-((&/:)+'/+&+900).< 41,:"/10>223-.23+2.:+1+;܀ր߀ڀ؀׀׀րڀŀրՀڀڀـՀՀڀ׀ր܀ـ׀ց.4)>, 3')6*-.,(1775*&)13)90$01 /"#/,*&,1%)),/*()+5,02441781,,9)#265)+72(6&//-004/)%"802-3/--. )975.,03#&.%.6/+5+4-',571.70',&5,&2+/2.&/(*-!0"8.#2, 4 <00/;1$),*/ )230:..Y/+%-#0**/+74&7/('4).*'*/ */2-?*5$"/$21/17-*=$ +00,0)467)3&'B340/0*(*!-;,4.*).+/*21(+2',*)227).1@*<+*/-,)2/-%/=/!$+.'/21*12&,1*1'&&:+,3,,23 ,729&!;%#9*2*+ 7'()%. :2#.9((,+9%-(((-014/2$9!(4-)+%4/+.*,%3,6-2+')>+03%.$5%&%)..=2(4--2431),&)+/*/948',1."*"4*-3'-/3*&$ ='#41+,,,9,-6!12&%1.6$'%1'25+(1,/ )2!-3*4:13/,42.'-422:/+&14-)-24=ـڀ׀ـ׀׀׀׀ՀƀԀۀڀ؀ՀրҀӀ׀րՀڀڀց733)703/&3" .84+0//,&6*/!/+:2")-*-.,/!)2$0,A+3:.-/)"1/2(/-+$$13/#/)8&6%#+34/",./2/85#3,?73*-=)0+0),%;+2&,)3'>3,/23.5/&03 8+5 0/%'2)/(9.#&/./.4"092*%/%5/(8*7$-(0!.%5/1.%C&=0+#2+-*(1;(!(%3+'*.#2,(,/(2%49&',*72*2/721,"*7--6&32'=22+* 0-,"7+38*%'"&213./0((48'58(1*0)!45'8-<3/*'0%+'1-/&#-0*3(&-4-&,&0>!&)1?3,$0$)'2(*%243%("#/41&'.0&1%$"7+4)/$"9-+%+0//..1?&2G,(6./E6-0-09,<4+-0$+7&-&6,'4+#+(/,,-$'2)+>&<55+,.3(21.(1*/0=-%'4,.')-1$,!60'),4$359$*5#(,))"++,6'5+&0",2*-%&8,-(.,2'.(*2;->)46$1%3#-.&-/"<12/Ѐ؀րЀ݀؀րրҀŀրـ؀ۀ؀׀ـ׀ր؀׀рـӁ17///+*+&+.)57&441-0(B1)/'#5( /8<+.(074;82%,50$':6('/4--/+-#%# 13.$:0(()2-*+021(+=(-+/3!';*&"3"- #/"3/&,#8%/1.!%/-'))31))-+/.)''0 1/%,*1/-5*4#8(8.%"2,-'1+/$0*$.+%.//'34!/4.4($0)!1*36-!)(-6,;74**50%&+01&.)57(-052,=//&.6;%!0%8)&!--/0)-,,"7)#/-/,102-*#'2'!/.5'/4%/+'',.2&..1+/$".,.) )-, ',2)@/94+$/4))0(+&-.,%3//6%0%2(%)-(/'+%>.D:,1#*++405,)*35+0.)-77$%20/:))1+*(03.$3):>9#2*$624)-!)+'-+354$*,(/+*0#')(/.2./�%)-.&552&)/+.,&+68/2(13*"5'(+*+(*=/471+! 1+("#(+)2,B/13%803!(&*.+.(.1,7('!*()?*+3<".%2"7,,*2ـ؀ՀـՀۀӀҀрǀӀ݀׀؀؀Ӏ؀݀րրЀڀՀځ4.*2+-"-/5*'90-.')'(5+111*4(6-+,'!%%4%-/.#/:10+.0)+)76*,2 //&?)-5.&-2$*'4//4#$#/()0%31/)2'E/0&$+'"",$%74'.%.+$ .'&1+)8/#.$.-'9105-.#7)!00-.5))&%#(-.5!)*)(+:/%'*+6-+&2/$!/1/%/,.,-&-.5%-3@#17'4!,-..(#*#$>-7)"+')%-(.!#&1=(*14)/3,5, &'3'"%5,.-#&-,/+%%5(1)/..8%9(+".5$7%+/$+,#&3*#%1($)&=*(.87#*(15--+-22+/9!&%25(1&'51#0/.0(+$/!34(-2/3,'(/4.40&)+/(/,0+-%,416$;5'3 0),<-(550::%"-79*2$',142/7/721,-=4%+1936,06499/.1'$/70?0-3/21*278&4713391,,5Հـ܀؀ۀԀրӀ׀΀ԀۀրՀڀրـրӀ׀ԀրՀՁ !0/41"/)116)%;1(.*!04.",*-%2/6+12. )&+3*/(7965:8+**1'/*,3"1/.-!-'(9(<$9&,0&5'#2.1.,:&6)6+)0%!,-2- 5+#-+))".(#.!-"-!5-*'+06+$+5#*706%7%-6/0/02"),84+8!'<533'21;3=6-44./&2016(2$'3*1#$2/:!1%&) 0-/%**-114(6'%/!1.):%-+/&(1(:7*#*+/<,'.:($.0+.($*#1.1$=9///,+%)+%-+)+&./ )-&//(,1$;,'4/"32)#-0 ,4.+5/0%-+57,'(//95(*-242!7"'21(,5'*-)5#*+33+?"%-00!4&, .(1(//-0;2.&- $.&%'%&4&;-)54!1+05-3/##(/-+-'"97/(29029;/*1.%(4.&*&0,0'2/%000*%/K1&%.)'9-73-42 08+24%"/'-&"#)+-.+0!3)()-3#4,<+$!@8-$:08.))*//+2$#*&(135;?CրӀڀՀՀҀ׀ـՀƀ׀Ӏ؀ـ׀Հ׀Ҁـڀ׀Ԁрׁ%;$5',41/%3$',21+&+.04:#7#4(7143$)1**32&-4=/*/6(#%0-.%.(+:,.8#)$1&9>/'$#!2*,%*2(9$.9"'+"5+(1!)*.19&)*'3,)%'042 +&65&%4.6*-.5)6,.3,7&-5+-7()2*0013/.30.--%+3246,)+'(3&/0)2='2%*.3+.+0&.4+1)./0/0++0&006/4 ;/#&;.;%-02'1)2,,(35-&#&,0.,*-514&' 6-,;+%5*'/%*+)1'50-,#01&9/?&5'-)&)(! %;(">!-)%/55)(/%--417 &1'(-/+'/--*2"-')02/++/1,%1$61)*2&.5+*.60)&//95&&,-+%4--"./1!//-/$'1084+),.-(+74.+)&%7'5&%28*1.)(3%+,6/12&1-6"+//*# 5''2+4))=17( +0.34(+&.*").3:)-3,, 6++&&")-+&")/'$)-63072+*503$22404(&(0+,(1*/3+12+%76-.׀ՀـԀ׀׀Ӏ׀׀Ȁ؀؀р׀ڀՀڀՀ؀ӀրԀـف/03'9(6233);#-1"/$#,(/3)"2# '&.#&$))(*82$)1-(%/6('%4+/90(&,,&+(/;4 /4)45%,5/*+!* )*!,(%*?#&+)&"*3(/'9(/*4'/*48)+.-*5/%'+/0/$&524))79(/+--08-120*172%)).(+);8%"4/(/%,)#++*"#*34+-&+2>(*1:,34'.'&;"'91%903.*<'"251.+/'4&1*4+/)3&)4+&3)63,(;'24/1,-)/)).27*(4&1#0,/705&%2&&/507+"0(/$54,(41(.32+,($.)+(.&) /!&6-1"/(73* 7-50+)2-*.8725/#'$(),*(!,(("*)''(';!4))-!'(-=:/%+09(,((:$15%34*! 52'9(7)#--'.((.2-)-( ,1=27#6'1/.311-12>,$-7*&,42-.-"/%3-&3592-+)0*-204,1'%,&."&+/3.3-3057.5)0+'$'0'-1.!$4-'%'$.'29,85׀׀Հ׀؀ـր׀ՀǀҀՀ׀ӀӀԀՀՀ׀܀׀؀Ӏف&%461/:)?11*(!')). (4-1#*+'$(7'$('*).),4(*'&'+.20/-(, 0; +,--"66$+,%,3+45'"313**,+,920+,'/-#4%-,.&.**/A%!')$9)%(4()1D*)/$)1-:0(.,07'%/11,9%+:",7.225"*)05-+*'1*",-.''*-3%.*4-,&6'46+#,'!1#,$1./-/6##*%1+1/*1"/1./37/'462)4)5/3:3%*/""2!.'+1&0./9-*4..?65.$'*/-6%7+/*,/9$'2,/-,%1#*4*'<./#&(82&2$.3/5%*%#32&(&$)*,->$>-)-+&,.#+)/%001('71*9&($)/9,02+1%'**&(4/2#&4.)**'!+",)@$/1+59&,&$*+.%$&").4.2$).)6.4.:'-69-!)-+."-!344#1-#4+1#+2%.2-0+(+'3"&,6/;//.-4$.<)*2(-*6/- 1)2+-'%3"&,+,7!9-1-4#1(2&&'3(0/%/7ՀԀ܀ӀրــՀ׀ȀڀՀҀڀրۀրԀ׀؀ـ׀րՁ,&,1#*2-((:-(7--%5$,02.&5'"(!.28&/33"'(%65 '+2"8%3/(,'+77!!/-051,8-*0-., #+(-5(%*35$*0"4*%+%1-23%&3$)4/&*),+41.'0/1&+3/306(&2(-17/")31)(3(2/3**/$1&0/4 //0(+#*!$0,-#)031)1.!)%($#.#2415&)%1/$/''.$"/,%%-11>4'*&*'+%+.37,%3''/)8&%/43*7(,.$4-,%2,6/0.0%8.%0-1&+%C(%./#%*/,65)&(/4.(+$5(21. -%=)'/,32"&9$)'"3'&.((%3&&&!4"75)&24./4(3&5)0')"5%-)3/#/+-(/>8+-!*+/''.3(Հ׀րԀЀӀ؀܀ۀπ׀Հ؀Ԁ׀րր׀؀ր߀рӀԁ:2(/*!/++."(+-4':'-$)/6* 3-$&5#*#.40+4"<(*0#$21-'4.%)&2$)5>!6!070(5$3%&*,',-0)5!7-/./#'010;+!*1+.0#2,+*(*)2/8"9-+3;5-/**)"/.0'/,5,(5*72131$8*+.3/':-"02++((,-%+(+-3//2-'5/),#/ +,$;#!)/-3(-7 %5#'*3$&+/71#,50)+%/#"/.+2*+A(20,/,*"/4&./,((.5:/+%-,+/5<"6113#&'$&58,5+0!(,+ -#1 .(,((* :/#4,,.('/ ///)'$)#+4/'&+",'3 ='+((.).01' D'(!$$+/+,;(0.*/ :!-/3.371$5-1+(3''-06#,1830262%6''2'04.6)$0*.#"+'8*+&05&&.5416)1$(*#*/-:*/2-7!(,.+&*/3(2*5/-+5/2)0.!/0&&1.%0#3/++&*/*-01'103*67./#-AG'02(8/5.&0381'"2+/-/)؀ـЀԀՀԀ؀׀Ҁɀ׀׀Ԁ׀ۀ؀рԀրՀրրԀԁ/-%-,((+28+#96"%&*,,45$-(%.8#..036-7*0(0+/,//)00'"'$-%/!.,#'20-30*-2 ;")':-.#/+6*4!*2%,-#"3.%-9/4(4:!+)#(9)-(0 0'-%!(, )",/3.:(',('32(-(2,0$!&*.):555,3%13$455##4- *)/3&)*.-6).1<8%# )3%)&.(6+:/1 #)+'-+/%,/4$.9$+%:.))0.*)(#40/%.,7,%*0+"(3'(%#&/1/510#)3'0524)-2"*5('&123/*2)/%/,6:33) '(/+-)2,-(')7&1,-51*1-%7*=*1.#,40;-*/',,802*'7/$/'&(, %1&(=-)*-#)/'.+4,&'3.+(#$#7((@)/16-+&,!)(-//*-+-B'*$%*)10:4)+)#*$*+(//4$&-+8;$(6!+&*.1-377*).*-.-(!(7#6..**1.+1&$94+9"3?+5%.5#$(2,%//30#//0/*+1-3+#06/'2.',/(8Ӏۀ܀Հрـ׀׀ՀƀҀ׀׀ـԀրրـҀ׀؀׀ԀՁ,:(-73(*/--0- 8)0.5.'$%.'-'7+"/"$%1%%)#1)-")10/$*((*-5,?-5)-3**)(($18#.+-0'-+93/,6"#$0#&7/8''+,)/().4 0.3.-*!&-7*'5#&6,620*32":// $)&%4/,6.(/D,(65, 22( 20+-/5+734(),,%'3&61.5'22#%/,)25'-% +.14)%-%32* $+$.,-*&%#//251-%#.!%%70"43),8*-6(/-5-'0(6("0.1$.58-08-(3:+,429#6'-%7#/=+74?'*#.3/972$-'9)**3,8!0*#%-6,*#/# +%(&"1../2-%%.52,*-*.- 1 /424-2*'',2/8+,',(/#,1.*+'5.-/%'&*/,36).&%,4&62(#%/00'2/&2//7'5+4*++.=,)1313,<2&21.&+&'(-*='13+,+"))2)6+1(2+%//1)),2'5# ),);(5!!2,/256/'(0-"B0(7+'/+3234#B-؀Ѐ׀܀ـԀՀڀՀʀҀ؀ـԀۀ׀ր׀׀؀܀׀ۀԁ&,6"/.-/$/&.),+/0'%,97'4.,*!,&;&%+0)('*4)+)3).));.0%!16 %1 .4/1&,*,(/#3,'"!/H9'+:&2,/&+$*/1/&3!2(),3')25/+'&.70(/,2!,&$/-2,/424"%#,2/41,7++9')-%'1!42-1739@+-.&,-50#+,.450,4=,!#*8'*7-6.&8%!(1&.+0.'60.)/=)/--"7'(%!/)(!'' ($.+0()3#2.(0.5+;/*+&(.&#-2'&+1-('%)#-)))4$)!<1/34)2(%(95$(03)+#(.#/6,6,(21,312'+&5.6+('18-,1248)+6#/*.39"&-&*,5-",="%7/1/))#-)7-:!,-(* .1/%07,66/*4$4902045'22)7 ,$!!2&8!0?&+>)%73+,")-"*.)25.&,>$#32/7/5+.*'")--%,."/%)/.-&40,2//2-*(&"4% 57'1$+70'$30+063*",85.*=,,7416-1ـրԀӀԀՀҀـр€؀ՀЀЀۀـ׀ԀԀԀ׀ҀրӁ91"%;,,&&.4.(04,-/#,/91&78*%+-8+.%+0!"*+%3#'6+%)#4#/712+$&!2"&-3-8!'302 9!+$0 -#&--%2,9-.5(,4 .0)+31*02/342(/0!)9-3/'6.(97: )3'15&("()+/6=/7+.8.%251,1(**,82./778/''9-&),-:-)1+(%'9),/.%."/3!11'2/)'(&/*&(3.(.*30&6(,3)0.8(#%+69+'@/ 2(,%6&"*,.$),(!'060&.-(+8)42,#1#1)'')5!.&1-"+.+./')-0/+1&(&.&%*4(03:)*$'-((4'+'+4'*3(#*)(*,--+&6,4))6),3<;7/#250%11"%*'/&,)/+ 14-6'-*+:/)."+%.-5+4,"60+-6+&--'3&!)1&,!3.+;-*//,!>9$ >42,46,79-(..3*./- 3')''*00.+%%65%5300-4$+/*6($&*6+.+2 /7-)&"*+/#7&<14"1'3*+5-2&7;#2*%?׀׀׀؀ր׀Հ׀Ӏǀ؀ـــ؀ހӀ؀܀ۀ׀ӀҀف&/*23+)25%1;%0///6%102&(.++.&:+&&1(7%/%)&3) &,6(,)4&1,2!/".-0#2./"0%=*<5!8'-,0752+1(5/+435!&3.70/)1,!2%//;"(,-+/&!0$#,+/-5!463/*6+(.%3+);.%,&)/+@+6958'*20<*"%!9/;/*-3((,"23&%=(%;+,4)#/51#*$)A%/*600,%/0$*#1'712.%(//,3,%*0'62%5#'0.*(9--=+3%.3001!':%%/*//'#'(23,4-//.#$#&$'&- *0%++-%(,73**<1<#7/--$(11-("0',,*194/23)%0&4*%,+&%5),.-*1&5(4%4.46%-5/%($)1"714.'$8/'!'-#"34+!(".+7/(% <+B3*(+7$$1/123/2-+8*8'/?/1(-3$)0*&/)'//-'',#89(,",4-+-3)'((,=*(0-&77.4--/671275)%;?24/!*3%/502,*0/"1+)+3'8/39,6'?1рրՀڀـ؀ՀՀڀɀՀрրҀڀՀ׀ڀӀ܀рրրׁ#1.3+!#$9,&.:!/0%,' /8)5,),75".7#,''#'&,51.2$(.2.87''0#/($;0(%0 7##(+4,1&+/)321 ,$2 $+++.*35$/,46)"&0662%+//2!/%*-/2'4"64-3+1)4&-+'5=.5/$2/-+++-'8@-J31.,?35:9)1&/31A5+)0)?$"*88./+-!%%('%)-*,5.=$ 0270%(/*0'//9+(5-/-#,&(-1()%)!8.3!3.+4#3.$)28;':.&+/;#6 3)!:$%1)&0,,540*-.$+2!)*1-&;1.133.%)/4$D*//!3&24"5'2./"')&.%#8/6+1'!&/.)% 6/9&&'/$. #%/#(':&*&2+!&4$.1300"")-%13!2420J,;2)*3 ,))*./(!!'#,$/$18/-2-*,$.)5,'&%&)/,:%3&11/%+-"$/-1$(>#/0.4)&')*(%(7(-+(0--,<-&61"7.:!-'.:)/-1+*(-'/*0/"6;,.-"/&$262,؀Հ׀ր؀ԀррҀÀـрրրҀրڀ؀ԀրՀӀۀց24-&-#(!56./+))(5+;#'475*'.-)."#(-$1*31/44)#/#>39&/5"6((3))#4$+%&4;-#/7*%+42'2'7!925-&%*-&.,*/+(&,&'7-//)*7-/++,C.&&; 1."(*)(/*30"7,0")(:)59?/<@>=:@E6)3'@+%3><.%*13'2#"..$0+!1-(//// %--.+0,+*(/. -))+ ='58(',+)'3-;.3%/'1''1.;"/4%5;-* '30&04'1/-2'02)/,&0'7&@,.#)'./*.'*,*"-)1*#"'$')(-"!+/'/.%1((0/0"9%/7(0/$/&/'#)/$12"&3'-1**//,5#-/0+#.151*' *+(165/%/*')&.&1-2*'1-')2"('98*(,$-0:493))7/(.3&1/7+/2':*1..4<#708+'" 2-&0*6,9,.*5#%1)23+6)1%*26/.)+1.0!.2,#/,+".*,/,K%(3-;30(*&,&11)+56*/-5#'$*#+)ՀՀԀՀԀՀ؀рՀǀԀրӀրڀ؀Ԁ݀Հ׀؀ӀԀҁ5)9"42#.0%-,,2/3&4(#.*/*&'3-%1.*2!,1.+'(*8&%)/+($)$/'2)".4':!*6+,*')(115.+2211"-3$-3127,.'6&"3;2'//$(,4-A+-''*64'('7-5*'5&4(#.;,7)-,#.,,1854.84;8;193866L81+8/2&10!*1()34!)*)'$+24%-3, ..>-%$-#*,$..""$*')45"&-12$(,+/-1)$25-,-3&)+0+45)#9-&,'(33+29-!7"./.+&-.#)/,30-(2&-''(/*%-3/',%07*35%&3,-+7--80*6)#5 - (,/*$#+$3,)&0&*/+3&%+($'3*$+$&'+%$'.&(3&4&'0$5-+4&2,&11(*'""/1**0.2,(1(31/. &-+)/&.&;)<383**/,..)8-+%"!<2'*.<2.+"5$*-(+)9%(7:+/9*"-@.2+.)1&(3;)254'0+-.95915A/+#-7,.-/18&''$0--*.112-:ӀـԀՀ׀րрـрǀ׀܀ۀـ؀׀Հ؀րՀ؀ՀӀс./($'+4#2-2.$--/56-*,.,63-)&.1#3(1$-$03,#)<)-).2 7(-##-%5-%1)#,,+/)("+"/5.%)509*+*-<)'4!!".)7(2/4#!%1,';+%-++/35.47,8(11*+,$9.*1:/1:8+2-<74+;.=@WG7F:=93/24&.2&("1*!4+18.:"(*----!"0*6.95&2%**%&&"06"%13'-&3'5,.//3,"//((66*'&4)+'+'24"2+&%%7(*$'33,+'&0'$2#3(.'-&5-')+-7*05241 $+.23.&2)/+>*+/&")+#/)0/-$*3 ,)(54!!#1.11/0 %&+'%.%+5$*2)&1)2".6*+(05&09B208%,+23!+-*+0--((%5(0*+ ,1B,-/.5'+'3/&1,3:+&&$+06%1*,7,$'&)7((.(,#+).+0$*8 *-90'/ -/%4 +)!4=(.-:*4*0('2@2+-&+-!-+$.&5.&2+$1!:.%3%/,2,%3/'/'4/40*'95ր׀ۀ؀րՀՀԀۀˀڀԀ܀ӀЀـր׀ـҀڀڀրҁ,25@#&,'0!,&82+%/12%"%!6*2'35,%:01//2)(#++'8-.4/"/*0*!*0/ )/4'$'1-&-,%3*/$3)1'-&&3--0+./'%+00'1)%&.2/=.:-."7.0*/&5 21)!+89//5+1/-'0&1'<49>@:JMOFN9JFIM48F:B)%)27&=+--.2(*2%.-+4:!%)+*/2$-,1+/.,8*+#7!"-+,(#&/9%-(5%2)197''(:.%+.2-2%&1*2637$.&*,%#&+)+(@+1 7&*30''3(/*!1-&--+(&/%)2,+2'+24)3)3/%,3%./7$))5)0(1'(..)3-2?/3"+'-07/(,/$:#2=/+/343*>,/%03'#/.&&+1(7-/+) *8'2)/*4$.."(.4/3.*(*41'313++0.+=.*+&2-+.+!1-240/)0!$'-)$/ %*$,1!+4.*)/+@."'%.&0,"!/104%#/(,)-/+&.7$*.(-)*"&9.%0,):4'15+#;#0($#&9!000=8ـՀԀ׀׀׀ր؀׀ǀ׀ڀրԀ؀Ԁ׀րրՀҀԀڀӁ/#0"<2$4-.%*85"2%+$,311-,0:6/.3%)",,)%!/.(5&/6/'4!&1 "/#0-*4)*+*&*'6).01%&6%4%;+03'2+$.:*<$'049+#1/*'+0!161%-&1%0.'95.4'/ 0/-3-:% 3C/GF?;E[P^Sgk[Q]TGCC9/99780.-2 /&%(01,/"9;;3+/)* 0&*++0613)4'+"-'./(2$6'%0(/++#&7%0)'",=0+*-#)//&21)"''%* ;9,#- 0 -,1!%2%-98)(313/.)1((-5'*-(*('$1.3+3 !%9#%-,4()-0$1,0+ )),6.),*!%'& 3:)2(&'-$E@6*''#4*<(412+8',);'7*,*,$2%..&,+'#);*'((7+1*E+$.00($1 5.2+$0'12.177&(8'4/+(4,,2..+-.0$2./+59((9.1%2&.6,&#*0#1*+*5.?4-"51'#13$/&$,9/6;0(385(',$ *42&(-,6:-+-6-+25=2ۀ׀׀Հۀۀ؀ԀЀÀՀրπЀ؀Հۀր׀ԀҀՀЀׁ/,(++,23-$&+'12#63,)*1%'/.#659.430'(1,' /**75)47-0-01&"!#$),#(.*)285&(/"-(%(2)+1'30!-$+,*%"+*-",'+!1/#?$0-03+$%&,',/<%).*&'4',. 1*31*3.*389IKQYeXktowzlTeYE;=4G,229:2?9%-/-+*(F/7)3*)@1/)*6#+#/"',-3/'' "+,(*6,+0&3$/)#*0/72.!'8)106+3!(/"/&1."'-8,/(!%-*'(1'-,-#*'2.813'3+%*(#(-75,+"%//*)''*7%**.+.1 1+2/,61,()1''#2*,/2/2#($('%1+()4(+)-+6)) 3(*&231!,*+-!)$5#2(2)'*/%%)-#)02*,,+, 01*."!* 11)'(6*2$'//%7-6445'<-4.7*%/.&'*.*1-'&5.&&./ -,7..(+5+.&:.+.))-!%8/-)/,.232*7*%#(**7(*&2"0.70,8/0/8*240/*9*83+6#6.:2*"*Ҁ؀ՀـՀՀՀ؀ЀƀрӀրۀـӀՀրրـԀրրց63.$/*%4$)+#2-*'333(659,8#4-9./%1%%3*')83#!).%5! &17,13+' '(:'/'%'0(,1*,1$)608/())*122$$'.-1*(#(3*6(-054!(-+$*#%:/*&)"%"4+3/3,-*)'.5$=1?HFX]o݁܁߁ԁp_^S?;A763;$1/>+)&/+ 4'$+%++,(* 53+$4-"*0--"+$%+-&,*:)5-)2%%#."#+.+702)*&'/ %')15'1,*3"#*90)4*7,8,54743)$./,+"-/**1-$-.+%1+%!',1.4-++2650%.'"#0('"2+.8)/1/#) *+%!,)$:*79'+#+)0.'*'$02)/+.30-)#,'3&3 &1+#(7'-6/,+'/",;016.')083&,$2"-2,012*)8"$#:59*,1+)+3<850)0'-2+0711,1,"7)#%3)!)%/% 30'5$:6"/;3+(./,)/).42#1'*,/'5//%($+$%,()8/'.?+2$9,2(-/03ӀЀՀـ׀Հ؀܀ՀǀـԀՀрՀҀـՀЀπڀԀՀЁ0%8#<#$09$1",(.5*/)62&0*102%4(*0&"+;9/(/4'+9(374"*%4,.3"# 3(.)7-%,42-+4+"0() +)7;#,!3%&,30*/)0(04/"1/';&0&-%-*'*/+!1*%..+0:0#%.:9,//%5=/\kSlx/O;ہfW\M=A729/:,'+')%3+806) )(/2&1-):'0#..8+,,,/+16()+4*,+*).)01+&&0/.*0J-&'-*+&)3&%03 016/C&,4?/.-(#(5#+3.+)+.+# (-.(,('&)30+/01./!7* .3=1#*#+,-1-*1/+-).&'/#%)..()2(.(-1/"/3+6+'$6/5$**31"2)(# -3,+$%** +(-.03+<(%'!:,3#."1" -($,4 &8,=--1/-6!3(!%1&-$5-&+90:4#.':!+)+!1$,)9&.)8)9""4*%."(+"7%++-+10',.#4)++&-4%5/,(.$3&.-'-;++;+0.'.111'*+/-5-4,232349׀׀΀Ӏـր׀ڀҀ̀րڀՀ̀рۀЀ׀ր؀؀ր׀ف4701$+-":!5*5/$4':):/- '+2>1.254*$-&,1&5= /84/(271,%#!%)1.)0$+/%145,'57",--%191**5+5&-/44) **41+230)-5$6$/0++!/5/-3((: #,"%5.").-,'1=5BCMYrłUx邻pÁ]_NH:5612%//6'A1/3=,%'/.#?*+.'0*,7 /%&1/#8#,+1$)B -* .*/(,1,/1=),;34-%%..7,"($)*),&$0,3"!,,"$(.2('>!'643/$;/11>22-%)3'&-%+""5+$+#2.2.0!)2%()3$!'*(1+12))9-3""95&4+2'3'&-0/'0&9 ''++*%0)5%3**$*)' )%+1()',&)0#9#-%2-074-A+&'4#1!)$(.2.'9-/#&'145%#,(37'1.45%610:,,,3'##541/2"1+,0!#>/0&2+3'.5,%1*38).,&$1*)/&'-1.862&5++%+)#'&2;3,9&%*2+1*#-45%9,4&--,/32؀؀ـ׀ԀՀԀ׀ـŀՀր׀ـՀӀ׀؀ր׀ӀӀ؀ԁ2-&-4/',-)0/!'"$3882&(1-2 )'.5'"$*+..+/4..:43!3)'0"45-%2- (3#*).#-++0+%3!1+ +57++,-.(!1)0''!52-<64)84&**%%*."0"6+++*))+71(.4-6-0(321)+-(=9OQWyPF3o遗qaJ5225?/>-1' 2)073*0/46+:1%)'20(6/('(+0-3$+/#(,;+'114-'+../2'-)4+5" 02&1"?--+(,!'6)'4')%%',++,(.$5)&!144('838-*)+;+()--**90&)03'*.7,&'4,,()4.$2'",/.%/&&)72/11%".$$+2F0#/#+'"9$8#$')&-2#=".)2%2/-,2,&2!'+%+/(!7(&#&(';/)3!9)&$%2/.1.,*5/((.%/+*!:&86'6*%90/++1-(*(%!+7,'3,5/!(,$8-%:#+ *$).1,(./-7&0)13#$-:&4,'05G#;&&&(-'1+*/('042$!/+-(&8рۀπۀԀӀ؀ӀۀƀҀրԀЀ߀׀рҀڀրՀՀ؀Ձ%0%'.+.74)042%*'"-60"+$!..%),8''2(.!+'/26,>/($3+1('+%-,/*153(/4%3+-//4 +!:(4(8'#/**0.*5-@"3A'%1/!3.( )--3$#/&) /)/1'$!5++$/14'#0+/;31-ND9S}ÂnۃsӄYMち}^PBFD17*)5$'+61"-+):)01.2171!./#+-++&-84+45+/81512+,.034%31.-/%)*!+#$()*55/"#*-,/+'(5(/()+((-1$#3-+'+/-"/&+1&$1*:%2"&(4/'6%%-)/$3%&4&(,+&&:"/,%0))/2'-/.!%,50+$8/-'6$$'".*+$%982)!0'9+"(9%(3 1*()1(10(7)./%.+10+,*#2&%.*)'$ %2,/,#(+4)>0-++7:95,%4"()544,.',(2)1(.1'/*/*3)1,+**'$2@*6)&&.1&"$"*!9,2#$/)(,/34 %49/,4+778'4-42839'&+*4##4&'.24---ԀրӀՀ׀ӀրـӀŀ׀׀ҀԀр׀րրڀՀր׀ՀՁ&694*$060/-)(/>47(/!425//4/.'!-1',)7,'.93)#1%26,,*'2/)4'(-'4--,)'.+7'#/%()($((##3*58/1&046-)--58+).$)-/0&)"&6)6)+(18&/-6')20=774,RWiqԂ'cÅQ}JxqEQ?9218323'$)'/&/93:/A,2$!.111.,),3-(*9#5(15*2//(+/3,*"1#)(6.1/.#4'%$-+2601/*7-+*%*..0@,+/$&-.':4(.'7*&5(4.).0/!,,&8/1),*-/.(./'#*%.4,-$)%4)+")*--6$1*5.*+)2!000%-'-4'3*,"; /)."0.)"#'+ 16.--*5*%-/''")"(;!91,/3.-4//,',-C;7+-+-2**=(&-##6/01,,&*3%/8*503$(/*-0./60+='00'/)/'.),/ %"/30+/11&-&6%+/*82.1)-0/0:*)),;((*3'*()(%<.5&7,4/86/,׀ڀ׀׀׀րҀ؀׀ǀӀԀԀ؀ՀՀ܀Ӏрڀ؀؀ՀӁ-#18:.:%-.165,#-,0:+8$5..5)4&-($5*,1 3!9)(-3+4%/+(/)'-3#%<")')"1*+)7.&(01)('22)6)-#-6*(-)52-3)&,71%38&./01+;''-1&80)/73$.9*0%+'31+8::96IZ_͂ ӄi񃁂G偶ykTEC6EB76>13:1(/:08.%1$#0*63)4/'8#.51'/2&05!("1$.=-+('02&-$&621)#,.!.4(4.#+/0"-1-&"*,-*=$-0/%++(+-(2")./,7'1*)4/'$/4-6--//./&6 7#7($+<2'00*-2.1.&0++0*>((,01:,1.%-/4265')*$%5+25$(2/'"2,"+5"3+$&3',,/1)*%&3"(20( $/'5.,7)1+1#%6#*++22!$63+-&&3+1"*!( 2-''-.5-/,3#-95,).5"5""+',5.)'34$-*#66./#(0*7'/7%*#5&:5*17&%13%0,,.12/1#6%-2*"*+3%4+ +/7(%5#+ـӀЀՀՀ׀؀ـԀɀπـ׀ـ؀Հրր׀Ԁր׀ڀف0*'#-085('# .*-/-#1+/57)-98$02+"7+6""'*('50&3$)40%*%3016-0&2.,&-.*3$5 )&/3(0+2*'&"27,2#-1%&+-#3-0 3-(0&:/($? % ++2/2533:.B%#/-6"87(ABDPahҁq5-@S=끯bF;A,8=723./6(-3//$2(7+ *110/'%6)7+&"%(.--+,'-/$!6/+%4+'&4%4!1%&//,-*/#+8!-,4/'*&'1&67'%39&0%3$2(!)(5'0'0,-+)#13!0$)/2-*4(+21)(!5$$/8(6#&+,$1.)0*32)-.&$)(-1-#=!/&,3/(%121&(4)*3"8+4,-,050-7;73%/"0/-**,'"("-*5.-%#"*!3/(/.&1)%,*-3(31(6.+*524!.)-3(2'%35,:&&+&)&5,&5!*+-/&).3(*;#-)7,*,* -$-%%3/,-&,/$+*&00(%/2*)"*3-+/)134'$-77),+42/6*)'+؀ҀـЀ؀ـՀр׀ȀۀրЀ܀ր݀ـۀՀЀڀր؀ׁ6#/)1)<'5/0*,-7.1/94"2$("(%)+3-..>"'4;->%.(&%*:)**+#35+,331(.1&9'%!+++-2%.''-.-7+/+5* 6-&'$)%#%:"'.0-%(3'4,+8.''5365,,#;/4!+;+*&-@5274AKMZÁCx˃v}mP΂k XbA3C;11;A#5#A,85*4-(&3*12' !.()$2,+$(5/#!32+29//(58,(/3.5.%$+8750&22&+1'!-+5,//3/$/%#+79*#(9+#,2%5.---,)%%+0)0,"(1$7.0'545,(!5B0%+1)((#2//"$'4(2)"+(%#&":,(3"4@/:''(9.-3/!/&*,.+#$-0'(%8+-.$%#0/#/1. /4+))#-6!,5'8075+*/.;*#5;4*":3/3$;+)305+*%/+0*"/2)-1',.&5)))!'4&'55)2<"'/"91$-/1*521702&%/01+3>35)*50;5.5:&.)746(#,+2/<*'0/-1008,+4/7"؀؀؀݀؀؀׀׀ӀŀӀ؀ۀڀӀҀր܀րրՀڀ׀Ӂ,70($0<(C3-6/36"/<7)(.()**-/1*5"7=,.)*(%'.+)(&-/) -$5/"9+.:,,#+,$/,'."/+.7#0,%0%4,/7*-8/.1$0&'0%*,+)&22%+,4()16+2,&%%..//)**4/)6#?5(+<7+OK>V_wʁ/|Ƃカ`,ځsNO:A>=:A8043.))"-21;1/;.3)5#%!6,/)#,004/%%($+)!0+'%43!&&7*.'"'5//=6&+$,-),!3",)&%-*$/2."$7*/,&,125$.2'322/+4("('+02#,/,'"!(1 051%)0(/.*+" ,"1'(*0/ /:'+/''. )%*/3!*1$%+1+*0/57',-*(3#,2+"3&@0&3/21,$8$)0/*.)'6(90#51/11%('(/,1+)&"0-+2-'%/22"+38-!().,#2502++1>(=4&2'(&/10)(35&'/+*!!+740)5,42/++5%+/*(2)''74.4*-&2/$/'*69:2"0352)/*.0 )ۀـڀӀ؀ӀـڀԀˀڀՀڀՀ׀؀րՀـӀҀրրԁ$+-*)%#&-+66&/*'' ).(**/0*$).,3*&6)#//-!,- /,%-*/81*2:.+3#.20432+-,* (1*'80*1.*650<&///1+.#3+-2(11-1,%'"&-,+)(#&#15+5.&4,3-$,""6I=//16=F@dn@FEe\5فw^KS5I/E7154('4=+),!068.)/)(%)2)+66*+.&%0''4&+090&%'1&4..2&(30479/4%.:30#*374(,2")/2*+#%5/ -+#/-%'*$/2)/,)/%*%)-2($+/1&+/)+/00-3)'0/ 19-,/*/551(=*&-/)*$+2/1"/"4("*%3#+5+%)5&0,--#*%)/'2)0%5'(A2-,)40'/&5.,!,/*0!(/&5,'1-%*)(.#2''* ://!$'1--,(%7,8(1-#,/'#)0*"0)-#6(3/:*+%6*6, %/&)0!2'//'$-(2&35813&,#$*01+,*)&'1/&,#5+5+&**",./+')3G".1!822"1-5׀ՀҀ؀؀׀րڀ׀ƀ݀ЀҀրҀ܀ՀЀ؀րπՀրԁ3&*4*.1)-'/'$ 2%0(6&3*//$:6-96-.1.%06$+'%(,7'"(5&%-A3260<3+ /+')/6$6-(14;*%&&*+8)3)&,.!+17-'%/!,./7%-.:)-)5/4++0/4268*/3-26,-*0397*()1,:=HMTdvŁԁȁuvJJGCD0*-%())?&$*(*#-(.1/"2&'+%&0''/3$&3%.4!)($&/#(*9)!.0%*4+6,.2+$))2&+׀ӀրۀԀ׀ـӀՀȀـҀӀӀ׀׀Հ׀րՀ׀ր܀ց132&0:6,2'$;*00&/"0-#$*#)')&2.4+&,#'".**,*+.%0'*45-%/%5,/--&+/+0"(4#*/84.42-'5./'&-#1'-&7)+*+*5(///03+,%3',,0--7*(+*'!.6="5+(;),:1..';B<61/-27!%9053"7-3#)37"(+--26$0/#*$% #-&!-,-) 5-5/#(%(#$6,(&*."&"$1#!*57)60(14*5&,50)6+//7;.2/+308()/3)-,*,./1,* 5#/!01147",,*038'"11+.5*'+.9-2,;+(6&2',,18(1(&53'!%&2+#'.!7*%"4.*1*+!/2#01++/$'$3-37/%5&%909.*22.,-#-!3$.*'+/10.*3*''--6)93%6*..&-/0#,2-/',/%&2+-)#2=.%$(");4$($(6/ -'"06(1//."+-$.)-*%0 :60)"$*9+(@53%6##/4$,060.//.47ҀՀӀӀҀր׀Հ׀Ȁـ؀׀ـԀՀӀ؀րր׀րրҁ"11*(241,*!6%70*"2*4'1237$217+622!8";(%50/()*+2*"10, +*$+33#/2)**/*A)(!0#41/(%*.,,:$312%42=26:)))%4%1';#%5,0+,2)+)52'%+#-$.2*5%(0*0%(77;5->BFR[SXjjzmpkQV59;/A&-574+>50/+27&4((,*&7,%0()%-+&%<5/+67*-+5*%/7&,!<7)".+/'22./()$1#114)()%+'1'#+&3*,1-,&,'#-+$%.3,#*&,30#;/*(/%='&,+!.. *56"1)$1'5'*"'5"!,#.%3+12-+*,7%+1'&1'')('-./3%*(&)*'#17*&85'42#+)0$*6%*)"0%-)-/))#&)+$,0/!)9.!)6.-%,#+( $/0. $0+,/*)6*#*')$&-#$)-0&/*#)/709,24$+*38,91/ *")4&&)-*!4.17*8)300)5-16#.)3'73/'(+35;4*63-3)0.1.4;'(577Ԁ׀Հ؀ڀҀҀՀ׀ɀ؀ڀրڀ׀׀Ԁ؀Ԁրր؀ӀӁ0!*1.&0&-%'-$/1*1&64*)//2)(-'):(*+":54&/#10())"33%"%.0*#((,-3*2)0($-1(/"&7&5,$2-*2*&8%&.863)#)*3#//"622*14:(730,>7)-'),,*+9(3#+'".$') ).;9BKIZT]cgmfi[`\C^LJ<41=3)4&-*5>%5/#456621+'5(9-'.13"1!(/ ..!(/,1 -"./)3% (+1')'-/9'%-,305&-?5$"#,:$#2//=-0$ "-#6'(.(&.;/$*)!&/(+/(#%)('(.,4&00)7/:(,$/5-"% 4.1&##5..7'13./*',1%'()&0'!$7$)&%(+;1):03+4$38*00/.++*/("'<./0',/5-&(4247+$,-&%3(&-*)/8 ,-5!'0&))2+/1,%'43$. +8(-)3$'+-+..3&'*-+54)./0(+)(((1$%2)./#14)7226B/&7+1.83*2*%3/1)'4180%*+)/73/>-)25/9-&.51,,Ԁ؀ҀӀր؀ԀՀҀȀۀ׀ـրڀ׀ҀڀӀ؀Ҁ׀Ӏԁ2'-6/290)2/+,(-%54(#. ''+)/*2.$.,7(042!713&.'6$1*-1$.'1+',0,-/1"'1&7% /90(%!*52,9)3!&7-&%.''.000-',/81+-+*%).=)-(,'(/33.1)+02-/5(/42198BFFC=DXcSWbiS@XIHC5F163:.+)70*,84))&/$$)7.7*('71*,%5%0'51+011".',1$11*)5),5415,5)50**"+D')"<)(5/*.-),.(2%.;30)&291,.0-*&+#4,33:#/-(+5)4(3+'6'<1+%$25$:2,-18&(5)+.-5+5#14#70/27'30(9(+)-)%(3(0'$)*-1&,)2--,2,$*-:"306+/&%($-)3!('"5+/ 1'!)9-0$+:(*)3),5+2411&+(%+6(3.'%2($(**)(#:'(,/$0 42'<,'&%),&489-69;/<.-)22/)>3;//.".12:$0-&4-1&)73,/7./)%)8'(D+&/87:-8:/2րՀՀ܀ӀՀрՀӀƀ׀ҀԀՀـԀҀԀӀڀӀՀӀց..#-)#++(%*5%-41.(,1,>.+.,-/('3+!754+!$-+4#)$#$)'B4)--0-(/%3-"7/"%%--..-0,,7,+()(//2+:0"$+/&"/'7-/*()--6,0/*%-0'7)"/)>/603'-3.2'(13163*>54ONRU9L9EE=6C20%)40-4+!#,#.+&)'3#-+,5,3+0(.0-"'-6+.+<*.533'0'#%2%:&-!';>#+(1'*+++,340:/6*.#+/:5,!<)'+* %,5,//4%)':+,4*,>0%$0#-*--0#7(+)+(0 3=,$3.1),,3(4)),*!//,/'')(=)7)/,-5*.( 05,0'3.!/(65;1,*#%1&53&!.) 1+%)(($1-/'(1%130#/( '("5#),"!'"/&33-(1$5.$2&/$/."-")):/..',)%>0(4'01-+/.-&/0'3$85(1+/-20-/*0;3#&:2$& / $-7/1&--7*/,2')+ ,!)8#0%*+#*.؀ՀӀӀ׀Ҁۀր׀ɀۀـՀԀՀՀӀր׀׀׀ӀՀׁ7-+!),$,,;;(-+-5+*.(&7,4#,43/0**#*!/7(,"-)4/+!-4-,*+=6$%!(".!'*(/0,'*=3(-(414%*.)!-'00-24 *(-3/:"5+.",'$*/(&).&$'7*,#0+2.(<.33&*/.,)0#-)6=.$-@;:5ID=FBK[<=D./=1).:$+( )+&2',(*03&'+.'5,%3'--7!$.'+-7)"("4*'.*,$*-+90&0*(!(2%571,/232'&+/-/)1(8 "/9.5$+2'-)7 /B(910, )05>,77,*,(+/-609%!,)$)&%%.03-7+,:&(#.**2(6$#!&2+!3*!1')%('(,'6%".$(23',34,4)20))24!$00-'1%(1%#2)/).&'>$1+//&')#/"))#7-4$3)/%1-!&4%77)((0#$/.'+(.(:1'(3 &)+5,//6'2=-2/(.8$$,-.4,./6:1#'-&2)/'++$((05)/'"'-6*+1(0-('2/!-1/?,#,5% 6(',,#+Ԁ׀Ԁр؀ҀӀրӀȀԀ׀؀ڀՀրրրԀڀӀӀԀՁ3+2)2+')!*!1515+/%+3-2-&1030-28//6 '+5')08#+$/7-+--#(+++0%5( 7/)3+2).".2%7%#''.//(*+/*,"*36%/28*.+).' (,"4'*'&-%0'&*#$0&-+#)1%4?0*@+8D764<93+D@79A'I;E24AC9+/<844&./'(7*/&*)1'#--/)(12"'-%-2+)(/&&)-9$(&(0!30-+?53)-4!'-&,.%,2/)2#$*&*+(7,='&-3(9/-%#((45$=!, '7-'4!(=)(9'-+.3-3-,.'/("+%/"*'-'3--22%.% .2-,.-$2-+2/-)8/,%,--'&*0!)*,$ % !*'/(:!0#//:/7+"10' %%'4+//77%!$4(,1+-**6+"+4!$.&&8,'*$-"&/%$2/,/21)*(#.8,5,/,&"9-9() +0)*'"--0<0+,7-*/&1/!/0$*-/!"&)$0.,0+ ()/B$'+-4: ,51.1 7-2+$5'1#*2׀ԀՀ׀׀؀׀րۀĀрրπۀՀЀӀ׀ڀրڀՀԀ؁,$./2.4'05(($+-&)1.*-./)63!(0)"*/6'7/16*9.;*<%"-/%0) &84,+$7%$2* ',(.$5"9&8!&*)'21**/+"*,4$1.+.,69.1')4'&'*..*4&7-%-1-+%/*)38:#6&1"2#-:*0.29<0<,8=7981I562@47)-+/&/26+3;38*(()*'-7+3#*(955*/&8)-@/#(&+*/6 -#,/!+2/'741"/+63-(+.3)(**//&"3&3.<..'+'&,3-(5.&3-4,1"#*0).6+41"*')*(//#*1!++/*-4%-$7'6)!)0*.,-)*#/$6*-03- )(!'-65%+#*()&,.0/&-'&,%(017!"64#*'(2-3>$'+30,&/<-'#64,03#*.$.*0+3-)*5,)#+1"2.,/,15/!4*1-.5%),4)43*+;3"&"".5.5 0//%+) 4"1+6*/146*)5*-./7-$..,2)4**95"0"*".$1<1/##1,+'(.6)/'.!07+8,(׀ԀۀЀـۀ׀ԀۀĀ׀ՀрӀ؀؀ــ݀ـՀրՀЁ#%*.&(1.* &(+0.-@12+$,&<+)(.7,-&!+'')-0.2-#5-0#)-)(#-2!&A@*>+0*0,!%&'1%6#�)'22*.&/+)/,/829%3&!/1)!;*.3,1.17%"*$,04.$+76"'&2#2((.&C*--,%,*:));5/*>7@2.=<$38-+%%/22*/&,7.$/''5+2/&%7 ()."+/=.*&",+)-/3-!(* *.7)% 6.1/%.5"2%*.&%+.,1%32.!0 1(3,/"5.+/+36*-*/).,+07/,/40*-,!(/6%('"#(3*+&""/37.,+0).-2&/21.7)/$..#$2(/3)!(/+2)(%$1/!.(+3%-/'.8/(&+.+%1.%-415(+/$)*3&+2;-2#&%127#,1")3.= %5/,882$.-1/$(3&/;3&"':-&@&2)5%#+,-.#/%,0*53'++-&.+'*!.3&1+-#(5,+%1%/*(3+,5% '720'&)+2*0,1=!'*3.3*5/.775)24.+=6&45ـԀ܀؀ԀԀՀՀՀǀՀ׀ۀــՀҀրӀրրԀڀ؁+-96/-+94#=/,'(&!)(,.!-$2042&-011//20/)060'.1*(+0&'*-+,031;*%//)A%'2*5#%%%6-2#'&(%*3(3&(*21&-/1/*&.0-09$//6.3 3!-7#227&@321&'4#.6--9)4)/.78205<136;/$2264-%<8%<349--(:15*3=&18(0+-/+*"/)&&-)=+-9)-%6%%)(-& ($$.,/(,3-&19.-(1!1((%%74+,(2+06'$():7!&71&2)3-3.,(.40.*+7$#-+#*(/65)!5+4++5/-"&) )224/,6)((1/401*B)3-5,','(7$+-4%-&,-% .&.)/'/"2-$,'*+131%2+)2+3%<!/.)%28/6/)&45-.) !/4.4/#%/. '&,*3"*(2//5,3-52(('/'4$'))8,' !2,/1)2.(%3($)!%057--(-(/)!23+"%!(+.2;(,/2,*@3#*#82 .##8*,("C-)/3-+)#-/%1$(.51%4ـЀӀ؀Ѐ܀׀Հ׀ʀ׀׀Հ݀Ӏр؀׀׀рӀՀڀց0 ),:1*')(2)%/+))285%'/>1,+&$*$6/2+2"./-*($",,2'%.'3)05.*&3<-1-(.&8+7**8*<-$0/)":%'4$4+$,2&-1%052A/;/()+*,,(&+$8>(%)1%*)-203-/!+.;4)!""()-%,*%B22(//=0-((0 38*(;.,+*" 2!)4.7-(#%2! 6-('&16,'1"4+.-#(153)#'3/0;.7+%-2+1$3'-/-!),+' ;/. !( &'0(.*- 318 ,86%3"*#*+23-(6;"2 -"+5-.1',&%/32.+,+:/+-+3*0))'&,-)%!'3)(1:5-1. ".') 7.,$.0 -$.3077&'(0&%'*'/+742%0)/1,1:4'0'1"..3($8*22$)1$/#(%3!(.7/&!32!3'!%./(.23*-3*0,(%-,+!67,%(-1*2,$!(*+,"83(9. (&$+&(=/=!0/%9)"+0!9+&6-/+/.#%# 7*82/135&453<')(؀Ԁрۀ׀րӀ؀ӀÀҀԀӀۀڀـҀ؀׀ՀӀ܀Ӏс'&0.21)1,*&15&&-4-))-)-4%-+'%!)+#*# 20',*/3%)41*(*1!1&0-(2+"*''(0'0*.!-%"-%9."+*,.)*1!)' 55"''A 2521!./-(9&/3+,&87.647.*!21+%.22))1%+0%/'$8/"2+)'>),+"0*96,#,!)&().&0)*%3 .5**3+$-8%-&($5,.49!09%&'/4%+"'"*$/.'$85$/#0$!.0*-*2+ $01,)//2,"50/+*-%/1/6*5)($2.41(5') 735'30 %'113,#2#+3(+.311,12.0",&3. /2+41,&%%%,%10)9.3+2)34(*(/(1$-/,4% *(!+/,%:$4()$-#+*5*43/9/74+)0+!(,/.%/).11#!/).0%2 *1/4')7++'1% ,+9-47/46,6.*3-$:93+/!/4#8.7-6/0/1;:+#$<%%,1+'/+'&/4/70%1׀րրՀ׀րҀ׀ՀȀ׀ԀӀ׀ـ׀ӀӀ׀րրӀրӁ:#;2+,$((&4-)**'8;1)+3",0%2'+&))06'*01//'.'+1--'2!4'5*21(*1)#412*'/./44)%/'3'$3'(<4)/00-0%%7**:* ,*##-#+$-+&%*,#5*+'19,))+&/,.!97)3'%')797*5(/($/<69/::,,0*("+&7-*-)12/42$!01',0(&,0/(!.,'!&6*%0!,1/*4%/3)02**,1'?,/0+,7$,$ .,3%+,13(-/05,2,2#$)-/*')&-/%. &9):)58,=/2)'+*/;-0$/8!';..:+.---$-30"-,%&6+4:0+2+' 5(-:0-+-(-'((475*+3/33.3*)'/�//9,+(+3.,#!),'$7()# +32,%,,4(@"! 7*,+"+$"6/3#')*3'!2/,"(-0.&-&*.%)'+-3/6( )%'1+-87/&,+)-'.$'+%***/2/+'-01;$15./+1,*'1')*)."-6)$0'(;+/9"&(9(%53*=3#ۀۀ׀؀Հ݀Հ׀րǀԀڀ׀ӀՀۀ׀׀ـ׀ՀՀ؀ׁ7!(.7'50%;&)$%+(,-,6++1')&9!"#'#20#81++6#+%*#;$)"*0,"')*/). (+$-**0!&&)#'19#3)-)04-'4(2),,%$,!0-))'.:&,)(03/, %1)&+/-1+0'+*+6&+1*1//3&+4%*-?*1):7)+(&*%(-(2*+'-3,' /"#&./3,1*$&#%*09&$+,4"/7*'-(.-+61.## &2+',(4(//,(/(#/&*2-47.5)+',+'&&1*$23/3/1"!/"(+-(#0)24, )/.+1&,&8"*, +()$%1/ ,(4:/!1$%=:;$/$&*/-<16*0%)%/7/%&/4)(&)6)++((&,.1&/''2,:1*#/+%$*'/*!*23#,! $0'!&2+!/&(9 '.#.&-"#,()"-$()370&)'('0-*+-+&11,%1%&31-.0#()/)2*(.,<1/()2))0!...-6,432'+%05'1&**1+*!0$2'.%1*1+"237+9'&97(/7663,)>)ՀӀԀ׀ՀӀـۀՀĀՀۀڀҀӀՀ׀рրրրՀՀҁ(.+3<7//'05(.3--@%$#5$3''."!.2*$0))"/)*$,"<23(0+,/(./2)2$0,1#1*,)2)#%.!,+0."-'(;2+*+&!'/#&0+1( &2#-9,-5//73+$.1(.+5':9+&7-'7#.*3.2(+, )&*'+*3-+;&+2&:2/8'-24+.$5&12 :0$+2+4)>!%,".9/+*+'!)3-6)&1&02!'('+/68!19!-0*-2)+&.+,-56).#-/<*4824%314,6)1/%%+(#"1!*''+)/.6*1%/!'*/.360'"'/'0('+)B$,2403/+,661*0#$/5'-+3.:1%"*4,%(#/(;12,+*'/)3!"15/8$*)'*/17+0),(''.863482)+&'7#1'*(!!.,--!2(+&2-)*"326--(5,5!'1*.8),&:*-2%$),67-*"#)2,"(0(&/,7+0'"*0115*! )&)-*-&6*/*6+0#&'+.1/((;%351*6;=+,7.-*24'../2,."+547'/!5Ԁ݀ۀԀ׀׀ҀӀԀ€؀ӀҀӀ݀؀׀؀؀׀ـԀ؀ҁ'$53$'2(,/,(7)1(%.%-%83+-&%1/.)3#/(-$.15+)'+(()$/.)672-0//,!21.*/:((<./)(&;*,%1$'/"*+")4+'69//.+3%&&19*4($/-2(&"'.,4)''&.*)*'2-+,1"@024%) 6',-01'(+8).,#'%01+&1'67+*0B'1:)&40$&9$-+/(;$3,.'.%/123)=,+$%/,-1*354*$-B&#)0+9/'!%/4,.,#-*/8--1'-15/.-!.2-+'5"($**0%+2+5-8ՀՀӀԀ؀ҀԀррǀڀҀҀրӀ׀րـԀրԀՀ؀ԁ)%+)-,+!0#%#-2,%*(1&8.+7&1 66),-7/")"2 $/$.+-'"!.( !.1 ,,5$%-1'5'3!5.(2&!3,1,%/%(.7)0.1#(+%48/+2.)1./)"5%+-+&,,#,0%2,(,:/3(,+0/'12"-/8)0/%-10!-(*$6 /0#*7+5'*)**'#6)#! +(-13+3#+/11-(*+"4!+%-,!':.;+*."!*)-/,"*+,)( 13%#39/3#''%'+/+*,2+!1$51=),02,9+(0/()1$8*50/*',//631.$!3!3.+0%(,29'.*73,/'0,2!*2(*12"".)1%(2'-()/&4%!,(&,79'$/:3),$*#(3'((,,4*2)+,+"+)"#+70)/+4#+-*"%!*&*/#(%0,!+50(7"0+/"('")((2#(,-+!1'=0*,-8(%.(25(055+#5%&+**/., //,3*60+.-(+43%52-$=/00/'-+%&835>#.3%"7-9.)>$*831.'&1',.րрӀԀр׀Ԁ׀рǀ؀Ӏ؀؀؀ڀڀԀҀрրրҀց/*3(*,#,7/'*'9!!26!',,+$ .,7$%7$'*"#!"./%&%%$/(2+!$%'43#3//)2%!/#)1*+94*&)(-/&*B)+3+1)#04%"15++(4#1+/*0/$-8+*,!237*#,(&''&'$05+)),$3'753)'$).3$-,4-,(()))9+72!/);")3(*+6-'8!/)*-2:0".0.%(+(/(&11-(*',7)5# 124(/**--(95/3',&736(*9()%&13% 9(/+)*&#%3&,'$-;3+,,#/+!))&)+1"48.'*"+,4,*2*.38;*802/*-# )*+7)*/./%6-/(*#"")(*(4*'&1)#6 %*(..&4-3,&-)+>02-/& -)%*)-$2)./71)/3!)+.7%(6"1.+/ (2/3$)2-*!71'(-,+""1-53)-;.**.(=)"2"+2+.+$'=5*1"#'.0.*.+$!$',"-15+<4+,'&+&&-(/$/*$0-/,.2)2./5-*4&%--4/7:./0,41/=4&$ӀрԀ܀Հ׀ՀրՀȀՀՀրԀڀр׀܀݀рր׀Ѐԁ)"01,/5,.7)".--4/2& "./%&%>*)16+)"2/,42-0++-(&7'/2%$4$&&!+)/% !4'(72--47"*7'+//5,'667%',) '311&1+3/.$+-+*+*-,!-1%1-('37(;6.-('+,8(.&(*0(.9/").:)("0+%4/5$)*$760''/&$'%'$9/ ),03&2+//---4..*-,%%+&/%)71!/7'+()$)*1%(.1# +'$&*7%/'+3$1'6$0#30,:-4:93""221#93)31+*/!3/-8-!.(*!('/,0(-#$%-2,.-'*678"62-05)*-"(%%&/-.(+" !612$%,,(0+*%)$+%'1!5+%&0,3-1$"+&45" $/* (-11-'+/+7!+1'1%0-.)(,#>%)-$/*(*)'('%,&$,%;*033'0.),'$('')#$" /('+/$/')0-$&)'26-6'/-2/(4#&210--2(.'1)$/0390+70.&4!+"-.,!*(!3+-2%/(-&3530%#40ԀррـՀրՀ׀ԀɀҀـՀ؀ـӀ׀׀ր׀׀ڀրՁ'!@*6'+'-3."/4'3-0;(('4(5/+.-%+003-$*%22,)1+)&-.$-1)2.1)4+'&.*2 $2((92-*(@-."1-$#/"1/0)$(*0//24+%/42#(79+.*,!;-.0!2(+,("-3%-1&$/!:/(('7(/0*%5&&#)!&-5?+/*2(07272"/2%0.1(<'##*($,-(/&))(' '%"+43+-*17#'-)*--*-4"#-*2)(&/+&-3%+5)4()5 )561*1'+"3$+6..''(- *!*-+3030-.2.))"#/ *,;0,%31.5-. .6(92.)43#";,,(#.9*+2'0(-(/-*'%%*=,2)-/3'!* '.+..#.5('2)$-07.'''1,2)1*/'1*(/,%/)3 % )+'-+0!0*6((/)'".#!(004+'&3'//",,%10*!-*'"2*%020/51&* '+$,1*#,'7-'1$*+/20#,+%,)'<"17-<+1(+(6/'(3&&-/86-5,.'A!$&6&+/.'-9ՀـԀڀԀԀԀ׀Ӏŀ׀؀ـπր׀ـ׀ՀۀՀ؀ӀՁ1*3%..3/'#!58/6+ 24@,)+/$+8*+/+9)-'.%&5$://...2.)4-2%):'&+"*%,/3$ &"=02*6(-%&840&%- "/"&)+3144$+(!$-%4?*!--'1/3*1$5&)66%'.('--56/#".(/-,**9%%* /'+-8$+()"#2&0/30+28.) '/+0>=.(//7+ B2(3-2.,#('%/$''1/+7/),'4$1./-'A!%*,;9', 4&)367/7)/-/$-/).."+0+""**&<60$2 +!0#++'3*./41)0*2*'#%'$5%61*(!=*:/*0 ;$&3,03.$/(/380($0%)('2#12(9.'.**(",&6.+).'*5+"*;66 7--%'&+710(0+7$/+/0"'-%'&& /9*,116"40*//%)55,/!,.'3+3.'%(2/))+)'&&'*-,*'14!7%3/,=101/,.90(,95+1%%)+6/B<24&.5&*1/-0;'$-702%*1)'0.9'؀ـۀހـ؀ـ׀րʀӀՀӀ؀؀Ԁ؀Ӏ؀ЀـӀڀف/.$%-0)- .53@+,)*44/"5"$*.)%2-./1+)+1.*12-6(#&), *('72,.*(-&&,$.-4%++)"*."/'3*,%6%5*-2%)'32;11'2($7&-73+($#''1A+2-(**,#*//,!36&$B30/)43-.#-0 '<-/<#$72+2++00,'0:)*%6./7-+5//(,%'"*+)/%&(:*.-* -1(4(8%)+0*)*/,#./2/,&/+,8/*&65)+")3%)3$+$-8,*#%'4-,!( '#50&3'901531(/"+3>'(&&-3-!!.-'2*"-1+&3+!#92.'*,*0'#%()3.-1#'.5'2&5+,,#0*0)"/(30&-)'033&)25&"&)+#.#0'(/+1&)8)$85$1'$)*%("9*%-..+$(0(./+#*/'(,,.28,'(1:.-,6 ".#%/&%07+7(.0/#'8-3(5%.4,5-1*7+%)0=/)+81)'!?,+46%6))8-3.+:./4-!1,&/.4(!3*-)/!A%,)< -$?ڀӀԀ׀׀؀ՀԀӀˀՀՀՀ׀׀ҀрՀӀрӀ׀Ҁׁ2..000+0+373(,4*61.51426/.+3# -!&#/6730,*/6%12%2%)0)("!152 &)1+30%%+2+/1$+(.!{*,+(,(&'(/""/-&&)2+01#"10&*-,'+-/$,#69!-&14/3'/%+3%"!1,/7*$;&+(%23&(/ 453$ +)((6;,5/*4+48/1+3)$.%#//056*&)8)!0!$9'A,,,(-8+0(-1+*%&%+)3)#1/'#/%)&/&+1-&''3/-!.#":;% (7/0.,%)+*'02+/32'%'&$34-''+0/.(+ ,.;.2$/5#&+.9-'<.5'680*5!!22++%(-,+/%./+!%*-8'+/'&0&,;$*'/ /&',!(+4/4%4)),:)/)0*0*)2#34&&/&@2''"1'#,#-'"7-*0"&%.,7$(&&,$0..(3:"/1-#&(4".+5*0)58,--,:0&$-)$2065).3/7&)$*&6%0#3!436, ).("!2)2+)/,/1*2..>'&-.5*%-+& 4!,16-Ԁ؀ـрـՀӀրՀʀӀ׀ڀ܀ڀڀՀՀЀπــԀׁ13*&0+$,':$(,+2!)++/).5$%?('(%7*+!30/-!13+%,''/-'0&!*0#(7*)00*(0+'#*++%&/+6"%:2O +#*$*,4.%)12. /'&7)4/.11%)+&$(*-%%$4))0('10(1.5))( (-/8*"-*$0'(%.''(.++-9+'/(,.-1-().13(.01(2*//6))(25*%/0+36455*+/")"+*#,*#*'9':,22+6-"!&+":')-+5++#$*)-->4.-+#03!$/( $*+$3!+135%+6,1#++3712 8)2!!&5+8/!'-3!3*)..%,*4'75'67&(#1&>+2+.(4.("-7!:78(".)-+&&&):5-'/1))**.2./*, *(22( .>+.$2&0(0,#"/-0/9/7?#*1+,+$+4%-5)5/&-+ 4211( 6*&+,+0?()+/0''64-,('*%,:")%.3%4!-, /36*&1+9;/('*216?%(-/8$1,/*/7+,-$#%,@,/'&7)* (ـ؀׀Ҁր׀ـҀՀǀҀӀՀрՀӀրۀ׀ӀրՀրҁ8$-.-')&+5$))3),2*0&1"1#+0.)(1%/+,*'52-4-84.).0&$%%/+3.&'///29'4(%/*0* #$(*-%&*:,,&*0$1'- .))&#.)&+5 3&&)-2''++9&$1)+()6/4.(.6$4(''(%4*0")-08;*"#.4'+%%!!%& 1).)*&/&.%40%.*#-,%&,%)!13$2-$**=*')(,+*8-$6 '1(8#&#**/'-&*$'/4/4-*2+.)#*&$(6$./*1->*0%#$)'';++&3$.3-/ ,1),3()+&*47"+6,-5-2&(%:'/1,**!3*,?)$997@-(&/'+((-&.).47-%. %$(.8/"2',.'$"+):),2+!/'5-('(/5+&16 )./+/#,.-+(/110'/3&2/$',+.%0(+$&232%)<'()4+&2"+'*$.3&4>'*%&-2'%2+/;003"'#)3.//(&.#,'*/)'1'%1-19!<0$9-+%0,)"5-/)(3 19$+2512)&249,6%6(-.*ڀӀ׀Ѐ׀ЀԀ׀Հˀр؀ۀՀՀЀՀԀـրրՀҀׁ8$:/# *%-.0.0//,'8.+,07#!1 7*+-/)*,55/+&$(*00,'3!-$#%'(2+&, $,.,',11273+/(%'/0"*.338-242!'51**.)'/,%("0/3'+'<'#0%8+#<'+)5%1/.1#+41+%)+,1-$650/-')2-($*%5*:($-!4&*0''.)$2%+$5# .7!-!/+-4 /$)*+.+!)+'-*,<)(-36/!,*#)/.-85&0+*'-8-%->;93*-)5)3 91)+(-.)1#2/,,*-+)"+4-5;7&%)(=&%:&1!-'/")/02/-'& A(-4+&!(.#'%-72'::3/#2+*%3-2(%#''4),&(-&<4.'+:/&$062*2"%,!/2#/1%"'-&03,-)/2%+.-++%*4-!% (,%4+')!,4;)%'&/9,'5,46*,,)'--(0!)4+(),"2&0*!?.*@3/'(-!/9*'=2/3-+/&:/"'&',0.1!/'!22;67*5(/*)=+:/,.%/$(789$3-)(6/='#ـրՀրـՀՀՀ؀ƀՀۀԀрـՀ׀ۀր؀Ҁ׀рՁ&5.#%0&+++5/435(,/1/--7+-#0%%3&..$:(/2,1%15:#+')!,-,;3) 31/3(6--%#-6(0*10-%1 ,"10-6.1(29"(1(*8+(/+%5.+5(7"5')++!$*4)4/*2(/8::+#**(.103&&+3-)4+*0 $6/)-.)+05283+.-,8+#)')444)''>">!&)/$:.845 (1(%!3-!--"7--)#..$,)0(+3)12',).-0%&() 7#)25//!/#+.4()/+,+&*-/*52!.)'%+%'0+ *7%0"(5-+15-'0#,57./++''-6''%-0;+%6/6,3%4+*3)׀ր׀Հ݀Ԁـ׀ڀ€׀ԀрҀۀրրӀ׀؀ՀԀՀց(3&4-+2+1'.4!8-*//-'%4!+.--$+$).92*,*,3 )"&7.20-,)/*29(5%5-8):,(-+3'0(-*5-67&6,//+0-$(50#3#"!!*+17&.#('#,+$0%(*,*4-/..36'8-*)//#&*&&.#,%-2-1)/"./)+7)0.&.'"0&&.1//8'-$1$;&.:2-2,6%%2.-(/1/2&*,-,,(,1(00/)'*",.6.-())*-6(($)&+'.%'&+'3+(,**!'7&&+$&"+-1)/-6274<,#9*3/-(2%*#'1 "%.'86,/%-0)'0"6!-&/()'#$((+)%+&/* (02.++0(*!%3272()6)* 70#$0/,,#)%#"%38+(!$+#<.'3+ 2*-'<'*2#'&'/440*%2/-$#*7-'-7/--3)4%.-#(0&/,%*6(',@&4.1/&0000.+&"-84/"*-)%++0()0$)%0#0$%+7 6($5<44&4!"*#',*5,47;5A+)+2)6&+//.,,01/0׀ـ׀Ӏ؀ր׀ӀԀ€؀ــӀՀրڀӀ׀ӀҀ؀Ӏہ>;"4'-/0/7,753%=&)&'/)0#5#;2/'100)+#/1&27(,8".2:*.+/(**-/+'4"!2/$/33&'),$%)7$*%%-00()+/&1+3+#% 4/02.*2!8,).%'/3).;%$.-6.%,#1,.-!)&9).)%/#3% 12%/ 3#%).0-/(),&#%").+/*%+34*%,$!$.+/!*,847"+%,*,04,'4..%.*!.?-&.3-*'+/.1#+(*7&'"+#9'#:+*1+$71/.-*)"0)-,#%$%:$4,,>3/0)%,',',.(3./(7)(&*'&4 #+-*4)"*--(*72$>6<)3,!2-*+,-*-2/*.,9(.2.,36-)-("#10.-'12-&*61)-*("&/-6.*!8-5&.25-,/%8+-,*$23 )#(*/))0&2-'0)7%#(,2*02%3&1))&-.%*(),/1.5%+.3('8)1.5'.*978-,.6-01*$'5-.***>8* $.)-///1)8)+()03",-1/6&613*6-/׀ӀۀԀրҀՀԀՀƀ؀׀ڀՀڀԀЀԀ׀׀Ԁ؀܀Ձ81*!2*3#/73/45/-/*5-3'.$-7 6:'+7643+1(6*+08()+(/.7,*17*.*-89-&")-&*-%.1"%1-#*2/0%%:-* '.*2-/5 "#$;(4(&.0 +'7.(=/5+#',3:,/)-/.1+($4)/2.(,,,,/9'*)*+.& /%-.+*,/'21*'-0:.-/$28',3%6,)/''# ''4-3+***8#)5'3,(!'1:)") 6/$ ")25/-&0*.2$(&+,/)1&'>(0,2$+&34)0)4+)9 #3(;)9(.#/'2*+)-.*2.//#($*'-&%0).$0-6*%&-13.02*0'0'%*0*0)**%/:,160,'/3..1.)6//&0<+7(12''/ 5'+#-%-,+2#*7.0%+.3(/07,#&!.%'#-(.6!&),)"9'1'0(,$.)))3 2"""/'))((,)"0,4-*(%'-'*3/!;. '1*)+.,/-,9.17.+(*4'2/,'$.5%7#%346-41-'3%1.)-&('3*71#(4+$,.+܀؀Ҁ׀Հ׀ր׀Հ׀׀ҀـрӀЀр׀Ѐۀـ؀ҁ5)/E/0,1!+;"#4#2&)(202/6#"1+%!+/,).25%0.-*%41)","!)*-(';0'.&'*7)5+#:$-!21+9-*9%&3($/6)25)"4+3)/1!" 0+8."'"0*+/,* 3/201$/.0&@#-%2&%1121(097.8*8$-402/+.+65-'-.$3./5))>.'2#4 1/+' '"'5/'1%/,'%0',/5$3++/+.$!&.)#8# 4"+).0$-5&"(,+6!!+*#0-?+&.83)'&'+).,( 3,"54(,/*,+/41%5')'&+!&,-/1(/3/!',/-(!$/!)3"&/'(#)+*&5&*3-%),6/+)*)'%/75++' /2)'2(3#("/,%,5.)2.-2"(4&1*//.,3$51,)/!"/.+!+&5*)#/%4&'2") &.'!+'&7,&."$.&&'776&6/$&($031-,',*-=-7?1 75)+-43/1(11*'$-##(*).'/*(9,"&/205>/(%61$5&-,!,-1+%('/0,+34'׀׀ҀӀҀҀڀـ׀ǀـ܀ր؀׀ԀՀԀ׀ՀՀڀۀց7&6$2.(4/5+)9,'?393+99*/* 077&4.(:'5*3)+4.!4. -(*020#'1*!6$++#'!>136<(*-# /4*+/6/ '41.$%12%*&)((8/*-"1+-'3,/#&0)$2%'(2%#1-!%+&.,.%9-+63'5'"-).'%<60+*'*720)'2+3171+-.$/($))#*83#)1-31((.&-&-'&0++-&)+-0+$!**+)52 /+1;$-00,0*(/--'//$#0(-")%1+0$:7 ,'8(1(%6!*5*" &,+7$(513*-,*-%% '02#+)1&%22*#1$0,*3'%%.%2#&!3#'-"$.)#3<5)/1%1*.+6'+"(%*44+-368""'$(061 2$','"0.%*4/3!#+),4#8.32+$6$1&#*$%4%2/-/#1!08'/5124&2),)5,$,)#$56*),0&,"+*.",5--/(,5-''/2.%.,+* //,D&-////)08(-",'/)43..+30*143%/.!3$)(&+,C6!%"4-ۀՀ׀ҀՀڀӀԀ؀ȀрրЀـ׀׀рրԀՀــրҁ('-/61(,74*1"023,2/()0',%(17+#4037+31,;:'%&-7/380,'"+&-& #;'1-##0+--*;-)%1/.%)*5(')321.4*")(&8$7*)*$%0,-'#%30/"*6&-'03&+$!(90./,./$,#'0*(#!/"2'/,16")&'207 5)(*0(-$'.%%1,,)2<*9)+#6,,,8%40#2$ (3))-4*'2")(.15+(+%+,='+'.+",(794+-3'$&90-)-/4**+*-','.'2'%48!24',1!11)(.#/4/-/3)*3'.,&-6;5%.-#3$0%55/,'1,9#*$"+!++.#(),3"1,&'*5-/"&'/-+4 )($!#(,1/(!,5&0!(,1/20"/*/&.-%;,38*7+035&+&-01/$.')103++.;3'!.,&,3/*'!104$+2"/.-!6-''4,.03$'*7%- 2/+%''$*%'-8&"0(*))2&6&#'A.-"'.//6:+)4300&$3--04:+#22")/?3,;ԀрՀՀ׀Ԁ׀׀ՀƀՀՀ׀ӀՀڀՀՀ؀ԀҀـՀԁ#3'@1?:.-+3/1:'!#/2',!,#(,#0.+7(.4)/,,0/"(.)+%'(*12/$ (#0/%(/%!*7,6$7."68',*.'&&.00''2!&D672,(6 0/2+!2#*+/#!)07%6/&%16).($+63/.$"#(534*/)1,'-.)/2"*"'/.!2.22*3%.'(1'(((#*2,-0#+&/!#6&*'/)/$2 - !"5-*1(%)+, +!+(310,/*3/6#3'2/-,*57-(!>,38.+93:,,+#64)*!(21$;/+3-',$3!.)./###1.0&/'$%*0-#7'1*+,%)'92,2'%'2.-)--!-*01"+,/#1$0---.+1.-3$+01 *'7&*'-7'%(3-%6(/0)547(*,,/G%//'-$$* (4":&1) "&&00*/%*-& /(",.$//0+.%%'5,)-%.+,.,#5-!#/$$!-1. 7)6"%3//+#(*-3,#)/91,1+&$/-:.4*%+/+/5:-+(0(6/3*&1'334(؀Ӏڀڀ؀؀ۀՀҀȀ׀ԀԀҀ׀ڀӀӀՀՀՀ؀܀ׁ&%3(2.--75&5.+7$)*( *%)4)#14(401,3->#*5+/",4,('0#6(:5-0('*,$*#!2+5*%7#'3/+-1**,($!1-(-$4*'6*2/$42/)3*5)&1+*$1'(,',2$"+/$2,)+%(1%5"&-0/,+5(+%-"(&5*/&11//!(6'*81-/&&&'/$);,3 863+6+4347/-*38-3=8"5,$3(-5*''%%$(,2+0-;1+9*++(&+!((1%!"#)6/)764(+.*"'+437*,%B*/<2;''(+(+.2*23')5** #!'/&*/,2-;&"1/'++-)2*')#"766#'3#'++3-'1+:3614>+,1-0!&,/,);#29250--/7%;(+.',&**,(*2'''+%-*'+&0'/%'3.2(&$"0"-3.3665*!-(4+/(8('5'8,.42)(+)(,7)**/-*)2*+3'/5(#2,*'05.!-!)9' *-17355.+7*073,*)+015!/'76'')'+621'(!2$+2+*8Ԁրր݀ӀӀۀـڀɀՀԀрՀـ؀׀ӀՀԀ׀ӀՀԁ)<')/0..(-25-,*%+(&('.4.,$*1 <.6,!&4%%(1(/4*&++3%"# ,%83#:2)$*3!1-21,/,'-/(>!003'5/-1(,'%,6--#()'$/&,/*'*&/5+ *04.&#:!"-%0/1*"%2(,'.<*(4&/&2.(**3'$1--2%'&%+<5-&'//(/-2-36%$+(4-,)-0'%#2.%222/03(*+-/&%.(5+,'/'%-"1!/.*3%6/+*3+5.),$,%&'5((/!++$22&%)7/)' /+0%'&"(/!-'(11),).8'./(-%/#,&6'/+$51(-,)(5,1/,(*%.8$1%/*.4$$+*!**)/.97 (.(!$%2 '/;8#+*!#/!/,'!81()%//&07+*/"4*)0+2$%37(-&0&'5!'+/9)3/##5/)15 .: ,&"%&&26*).%/!'(%)+,.+*!8 2/+-$/)+-/3,-'.%'(/34/& 5%$-5)+1:%<-"*+6&/6-.,%1%051/(1-*&/+9%('4/׀׀ԀՀـ׀ՀӀԀƀր׀ۀӀـԀ؀ҀҀڀހրـׁ:*.0'$6'06 *5)..4 15''0')0--1)"'' %1/1*1-).0-," 1)2/+$/,1!&(6)(%+.+-(-807,37!5+2#0'*24+ 2#+.-0&&+2*,&,-)0!%%(3*+))3,&--)0++*6*/;9&*/* (152(,+0)&-*4;/9.(0,=.0)6*($+652,2-(0'*,'1 )!6.'= +4#1&#('$+2*-/%-6!/6:2(3@'>,*&,033!5/80''&+,0'*1)4)&-4*64$..+0+$.)1/1!#%+5'*0 01'/./+*)&7(2').(4)0.2.;#+5%A<<'..1/'))*1"#*F3(/0,6)4,%-2.%(*%6/'#+(($&0-%+81,($*$)*/'"#1&-'&)"*/-7)25$*('*./%$(/8/(2.+)!220.12#'03")/+%(*(0-"%":"+-1#3).1/*"+((.// *( )1//;33'1306.+.9..2$1'*+.*(5-1':-*,&8>7.7.2$2(((1).-2(/1)'0-*(ڀۀ׀ӀՀՀՀր׀ǀրπ؀Ԁ׀ՀӀӀ؀Ѐ׀Ҁـҁ",4.*#8-#$13,'91&1/1*(.%)/-'((2(24*.#,098/+*4& (+5(')$-5/(71/)+/%((#2)*3212/2+0+'-$..*&&#/#(/,2!+<$+2()0+(%/2.'0%4,320,( 3+!03.*2<'0*-42/'$/(4/'5%$(.&)112-35 2+4*:3=4>#$')-+((+%),/#$/$#.25:05.0#-9)126$/(6*(*-#(1.,73)#61+4%3+1<9"7(#%-&&1%&&3.%042;)2'(/)*49!#(2*3/1.(."*(,1+"9,++*/',4!302-/)1-.(+11.*+#9)* )/*3% .+4/+,-":)4.*80((3++/ ,5)!%*.!(-)4'*.'/+,$,)31))1 +3++1**(0!&&(* &5-1&*'1+$,&$3,$)1+&#(#&03//)01:(18-)9+/())++%(%&*+).4)-",3/'6#14<<6$&7+/*+#4A9+-"1-3'&/,-*(/%/?2-0E%06++0'&ۀـԀۀӀӀ؀րԀŀՀԀـԀӀ׀Ӏ؀ـހրՀр؁.)68',,73(6-,(9)402+1&+-)/(#-+)'*-*",,013(8&5,)9)8($'2!3.'/&*-'%#3,*3'#5*,$/++-(4-3-%$)62)+0+.0"6+=,*0/%%*'32./0,)!7-(6"!,(01$.#+/($',,0--76"0.3/)-6/..%,5%2'&.'-&,$'!02  */))<,$)#*',/%+/0'-/0,%7',3-3 ', )*2$%/''-)/),)1%8)))(2/#!'*) #)5*.)'321-1$+5 --(2) 8+& ' %2.+"$ 6 %+%90+#-&*$-0%-+*)@/.9'-*-('-/9%'5-**%(4+-*./@/,(+40//(3 7.)2+-.0(9:2/B0+*0$6/7/*5+#%031!,82-(4-61-/%,*&62):-!729(;08+1#%)(*2(/'2627&!&;,(4<#"2%-+1,/11'!3)7 4'3,)/()0''(!';)19'''%- ,$3%&&1.5'14<*'.3+7;+,,'95-0/)'7,%C/ۀـԀ׀ՀԀ׀րՀǀڀҀҀԀ؀րۀ׀Ӏ؀ԀـՀہ""(,-)/$'3)/-#.-%-13$5$275(0'-/*)23,,7215''@&/3,-5-:)-((/"33(%826*/!.1* 11! 700')-->2$!' 0%#(&-',.2.$&/ -(&%5%").5)4#)-,%/%$(0+95/$-!-2-+-)%6 .**3',3;,99*&%)0(+%!4+% ,.0'&!.$-'')+- .+!603(03*4$0,&) ,-,-$<(3/))-%(3&(' 0,2$(%8*+%#(:)/,?&*"%%*-11$ %5%"'6&$?,$#,55)+80!7,1"04,0+6+'*/%-)!!0'7,3*&/!4(*!050$87$(,$*1%%+'//)-*$>),)5/-1/-'+5% -!-//1 (-+;(2/(1*3/'(0:*-(-0+0)."21/<-(0&))'+/+%!*.2%&")) 21)&1 -$7-(10)$-&,#) (+3&2*&**.89*<1*%,1.'--//*-'0')0/);/*7)1+'$/&;'73#/*%-+"$*&-%()+9.'0=+6))+23"-2!#=(/((2.$+/#5%"')'#%.//63#&.$)40<(8*/@.-"./'&-+:/)/#/$01"(--*,.)'+.(6+24/4+7#13':*&-/22/+2%)-#%׀р׀Ԁـ׀ԀЀрӀՀԀـրۀҀҀӀـӀ׀Ҁҁ?(<15"+62).'&.$&(8+6%-/"/'**'5,'8,),*),37(1*)(),-+5 +2%**,*3-5/%,35+')4%(4'1&*3/,518'630/(.2)7#.$+1//'),+*2."332+-+)*(9)/(%0"+9."#$0/('$,5+1-40%&,*4/%2*)8,+451..1-$+.0-/2) 12;&&&'7&1(71/4/)"31/&%#/'+%+(1)$*):* $));0#)41%,.5*1.%0("'6'2"-"5$8&0+#'5*5)%*2(95//32((1)#+81>$+/)/0/#/(*8..2,0)5-)!-;(%%9!61.)2$.%-)*3+-4%4$-0+40-++4$)-')1"%%-0$/,+5&3,+%(,<,&,)*#$(#+/=)*,(5!)-7)2!9+1/-9$&4,/#*/= -5-2'7. *3/#"(0)(*"0(&702+%(*"/-(.-/++-.2'4/)%'--.3.7 ((0- +/'/&&/3*.&/+)- -$(9)1.,)-30,6,352.+2-܀؀؀րրـҀЀ؀ÀԀրҀ׀׀ڀԀՀӀԀՀ܀րׁ#:)5'&#.*72*1 +&70/='3*60''$.'+2-"8."((.-7 -4(..*+('1./'/5*,. / #','*4735+--/1%6/#-'0+&"#1%0%04+/2/+$)*//65/7,13'))1%'/+)5*)'4(-!:,, 71$21,*4+%%+:)/+2&+#412 ++"-13-(9.03/3--&<,)$4$:)!"4)!0+22)+#222&)4'&/.5+$ '+!#&""2'&2*.,*3B'/%)2)))4, ;&#/%'-+6;+"*)'!( $+#0).21!35#).'-,5+6).-"#&,$/183*$/64=+!4%00)'-1-'34,-*)$'))5&-+/,'18&3/#*3(/(/(+.%'(%+:&44(/-/5*"3/53%%)/32&9 /'.-&7#.!+$(%$)***5&**((1?51+2.,5:"()9&03+/!)&38111:%7$%,.(3(!-$5;/-.9."(;+'-7/.,)5@++)-))+&582(('+510423,%./>*#2&)2Հ؀׀ӀրՀ؀Հ׀ÀـӀӀڀـڀӀҀӀԀـӀր؁.)'2-*%,)1595,1/ #:-)%.1<306*./4)%+/115/29--'+**3.$/*/-6+*(70*2+*(210(%;.;#10*0."@#2.+/+'5%3,!4"+(7+&$.))9#+)))*#*(%/# *1%++'0$22*%,*,( #1!!3(+.,+C$&4,+0'5*15+5/'10&2..-"$33**31(!7!!';%48+)/0+/% &-+1#!)0 9'45'7&"/35.)/&6(0&0&2&2 11*2'1&.8'/4-(%.#,%,&0--,0341.)'4+.0;'2//*5-&/73(%$1')#+-32*#6-*')3+ .,-.00$4#*(.'3@5)#2+.45"0/65+*:*(08=+#()6&--20"*#3!*.9&%1*4(2*4*%*&2-5))65'/2.:9003&$",-&+<,#+,+0;,!#""".&(%/6,-3+&#&-$0+/++)%2'&/1#80'2'=&+72+470'&5:,+,'/.*,+4730.$-02$2+/+ /%+%2)8*/*ۀӀҀـــ׀؀ՀĀ׀؀ۀ׀؀܀ՀӀۀۀրրրс/),/*+0'/2$.2'-91'*'/-58*%$-"3!2,1*%4)%3!"((74$8)"(4/1'51-/**0&1(#)0-:.312. -#&*&*+-&5*'')"3.%+6/ &&>4":+( '.+,1/0905/#/,0$*;?2-4'*615/#5#$)/$(/$0#0)(-/(&(()(&*5/&+/"3%.=42%5$(8+:"-,'/#12'+8%"26'('1#&31&$-**,)*07#42'.)='/;8)7,)-#,("'@24%)*4"'0!.%/06,%-3(9*)%,/,5026()54",&3*'%1:$3+(1+.(.*2-1.630$&$-1*0 .$$,!6' '"%(2-''&.11)*'4+'%!(/0%++#//*) 5,"3#,&/?++:8:/(('$"0/.7(*/.<+#7 "59/*296&0&(*&:/)+/"+1;- %5/- )*0.-04(1+#-14 *./%%3+$,4-'2-3,'6+1$7,++-+=.632+-3+54/235,3''/76323+,3%2)B'-2&&-Հ׀ـۀրրـۀրÀЀՀ܀Հրր܀р׀ڀ׀ӀӀс$+%*)2<6()**-&4&,0"0G-(/245+(1243,.8/%)*/-$20,'7%/%'+&29#2%.7+43,7$7/./',#(-))).4,+4-->*6(:18($1+,%(+"-.#.*($='/)3!#1E)2"$27=% #))/#B")+','%!05&-*".'5#%1)0#.=.%3/1)))!&3*$2/'&.:-/$6/*011-0&-+,3$''0')/(8,3+#,!"74()( %7$+-$#(* -*&;2,,+-/-"-"0&&3)'1;#3:3*.2+#,#$+%,+.4/6%&'E(/-#4#-$9$3'/,:/,)"?1,,5#,"&*$()*!,,(02'8.'$-''2#"'1",,3&4.+(#1/)&*).) *!)#1/8)*"3+/00*"0';1.+'"/,,"$-2*"'".)%&(*&5!:+).,#&.6$*-4).')+0%(,*9/+4-)(-*"/%),40(%1)1$*4.#.-))(++-5<2-<290*0%%0,)+3.&(%2/?C-1-:.+;7!.@<3,+<#:,181؀Ԁ׀ҀҀ׀ҀՀҀĀӀڀ܀؀Ԁ׀ՀրՀ׀؀րۀՁ1-*#10&)/5/357/3',%8)3%53.)%(''4&$)+/4#&8'2/3.+2$5#21*3%3*&/+.'-''%5'1>5'&9)';,2(0(81/)%29(++4#(.4!*.'(*1))#5&-&((4"/**38-09+&,(*/+$($/++)3%04'0$73++$-(-.%#76/+*!1-3/$(%*2),,-,1()1.$.(2.#+1/2(%..(,/.+(6.03#3')48*$+$"(51*+%1&0."?&'!&0$3,,&!*&2'+)30!<#,.3)(")/,'$(=+!,:,'37.16*9.-*-2*'"5',)6& !($)*%&4/+0/.+*/0*1#?/9//6--/. '/6!'4$,'1#309(+11.+-3..0/5'53146-)5.#-+&0!3.+3-,('0(8/74-29')1/+23#$%/-16)(+-.+-/*5+)*2+7+*.2/+$3-/$+*&(+$+""+,'6(!1-0$)0'#-67)::-./5747*3*'%"3-$*%6-++)(<)!4//35/"&/+%$.66ـҀ׀ۀҀՀހ׀؀ŀր߀ڀ؀؀ՀրԀҀҀր؀р݁&++1/'2!+9%'324+F2-*-:2&&7;$3/-&2,/),"1">-%)3 !%5&9-3'%(9001!.0&-/'.2'/3+1(.-E//1)%'33-*&/1.&,E,0&31/+5+'3.7::.+-572-)&5&--4$/#!#/*3*&./,&1.+*$%-+/<' 7$-(%'&)#4#@** 1(2,-:))24$5#,0%""55%3,&21/301$./)/2$0.)02/6-1(,)#"!/+-1&$.*#1/*#)343/'6-#7':0$.24#'%,;1&&.'+*$.+-5%-4'*.(*()!3.23&)0$03,,25-.*6-)2)(,,+,6%#'.(%+$,21,#*27-/!+!'..2/,".<*%))',/.>5'! *-1230%4,,:05+=(5/0(&3!3),%0%0 $2+)&&/%<.,*4'%',/,- -.(%0;#.3**(,(7#:..10('0--,-) 40+&)/7+"+&-,:33,.6+)""7205$&+&()5-6!08+1+1 .%45$!5&/$%))%/8%+2*7/׀Ԁ؀Ӏـր׀΀ԀȀррԀՀـՀԀրՀր׀ՀՀӁ/!()&5/4/9!'+*%%,2+&%''41:(,-%/87384(/+/2:5310&26#**#8+7--%,/-%01-#+0-!11))-*(3+/833.).#)$$50/'1/:$'!-0&<8)3/23*#$/&"#/2)&!4+4-0' "+"+#2/15.1-)&"*2.,,(*+. ,&7-,14!#-*3.*%-6-$) 5*).,$%/&0'4,%/#(2!%*%81)'<*',/27%/2((*!9+*6'(*/@5)&!*%/,,<5("2*1'%0"5&1**-*8$1*0+'"+--11-'6'57)&-$&0#;3../-'*0)+*<+15,5/%%),#8;*0!,-*%/19$'/"$0C.%'1/$+24.$/)&+04(-),4+5-#-*'-9-.*(*)5+#+2)3+(3+#"(4:04&2/"&2+*++0(%( 3 #!//5.#'/4#664'/7,+'1'.1'+18)%+0% ".5&1%9?)4'32)%#81732 '#%!'02#(4(83201+3..*9).(,,)+5.$*#/#153307',:2ԀـӀրҀՀՀӀրԀҀր؀׀ӀۀրـԀԀ׀ӀՁ+').(&3>,#,-/440(+,.(-.006/18),!1(0/--#5%/3/,10!+*0/&2'-+8'*!,/0/*1*/1%2-'0%03%7*,//9(&* *7'/%!.'-4+-+("$)$%)6--%*'(5#*)3%2!';$6"<&#);(*'$(/37))9%%-$93/+-08,31*-%2..;4'+!2(211/(-*+ ).--04/065,%0) 4('.%1,2",#$16"46,804(׀ـҀրӀր׀Հڀɀր׀׀Ҁ׀рπ׀ӀՀрۀրЁ,*0,4/05-.//16%4$"4*'2(<43$"/,"31*5*%;/)01*(* 45-0*/46,(0/.1A!4+#&#(;--'-8-7&&+")6!&-$2)2*#=#//.9"0&#!$-).%+)'15@$1#7'',.)16 &2$/*44&,'.+.-3+$86/9/4'-3,.,#3,-3+4,+#, -3+%"$*32"+#(/ -'!*$$0/&.F.*&! "+"3 *$,#8&,*(,528$/((./)'*!),!<7'(40.49)21+'&+00304.(-2!1''-1-632.-90 21-+2,+6)+..+)+$1>, ."#2.2)$)(*>%+-(. 3$(,/(6*/////)%(.'/8&7)31%'*(5!/(),!&4,*-*)4,.+/)$50*-=#5+&./#15+5/' /486*8'*)0*+;))+1+96//%&0(37-+7.0'*'%'1('=9&$+0+#;32-%*2$.'&7)"%9%)*8/"03-&9(+()4)0/0,*,,(1=7/*,%'.*71/3%,=07/+6Ҁր׀׀ـՀրրՀȀрӀԀրڀ؀ҀڀՀԀ׀ՀӀՁ0*%+%35%1.(#,) /+3<:.$15/+1,(-2"0!3+3 "8.!6)$'.-/'!-'&.+*))4"/2.(&*-,)2/%(=%3,2(-.!'-.!&.(.!/-,:--83%/((#*//)1*..+&+10+.-+0"('++8++2;*(-04)(2-+*#5!1#-2*/%7+66)-/%%3.%'.215,2#*+;3'(*6+:)?3:534$"#+#-#.+-0":15,"4,=; )2*5/),?8"1&$+-+1#/02!%$*/-)6,-<23-6%+0'1-2$-/+-2/,&"36)4/%1-'=%2*0+0*+0":!95/+, *2(+51,%.411)./),(/<"27!!:2*),$%/'65-/+.$,3*+%,20-7$+ +/)+/)-#8/031%6//+90*"()-1/'.-(*-)$(8'0.$.(360$#)29# 1$8A7/./7+./-%.1+(*.-<$;'6)*3!+))4#.).--,*+#2'602%2/623:/'1*)62+"1*#46/243'+%#53083-3ррҀ׀ڀـրπԀƀ׀ՀրـۀـڀڀـЀԀӀ؀ԁ))1'/-2#/9.;/9'&-/0(,%.0#2'$-'4-,&+1+%#3',.&-:*-'*$*,%+-6/76,!--,)-$$*67$40-*+)05)+6172,,84%0$#203&)#!%&91 +%+&*.&+.$:&3!(.+%"0$-+-""/'.,+&1%(13)#&04 #5%'++#/2,#'8601&$)&46&1/436.,()03&*$#<,3.'!/-''1/81,/"+*-6.*%)2)(2,)32%;0'5-3';-5))."+!2+5#&& +)14+)*-"'%%)'./.*!1!%#0%*9&/$2%4%4)$012*/+%2+3,*=1!1$(4.'/%421--5)0/'.(.*,-2--)!/6@/$'2*"'*0.21.3'/2+%;4/02&(*+G%/'()61%38.4 "+),).$)".3*1.,*(#..-(%,%220$)'/!'"&9++,/.%356---4-"',,#3*3,('#0:*+/)0*)7%4%<+3):%28+7*,622*/0*(-//1&-*;$0" .24.#(-,))8-)ـ݀Ԁ΀р؀׀րՀɀրՀӀՀـ׀Ԁ׀ڀՀӀրӀԁ-1)620@.44:A,-5(4(/1,'-43)0'01'2!$/-.2.-3*/<02/6#,%(3):&$)(!(-&,&&*+2-03%.11-03#+++1230"):.2(#&.412<#+'37(093/&.0-+!*1+2.1.2-4-(-<:%17/1#1)*2."7'*%!2&'368(#"#0,0!%7*+@/4*!//2+-6-'#*56$,&+3400,*2/);+2-&$1/)&%,,1(1-&:'4)(#6(6*.%!,.(-/&"-$ $-$.,3/211:$.-/+0,&-*/"(+0).7$'-7)"/!51/'3"/5-,'',%- 51:240&$5423&%/+1/ +' 50&)(&5,7-#'&2/*)',/5'-)-/+0*$!$%-%('$$'6*15-(0)40600')3'2'''# +$6-.-%7)''4-*&'*,1,,%/%/+!8 1)8*+0<4035!/#)-2+(1$%75."%6&0*;$'+=&*(/00%*):,)/!.60%7##&/";=0'*&*139-5135/(53!%(/3*5-316ـԀրـՀӀՀ؀ЀĀՀՀ؀Ҁ؀ҀҀ؀ۀ؀׀ҀҀׁ%)6;63<6/,*-+30,+%#/%6*65'+86+-)/9*2*,035++ !/*(2)#%-//)$)$*< #,(50 +09+(,,,.+1(>:=*0%3&/)%4.;+/"* 21-1/-&&,),(/2(/)*% 4 2.*71'%$*(&-'#"()8 )#%(0-.35"()/**)!4*.0(";#**+/&!+7(+1%3'*.1,)!1)-&+,&0%2%'0-'8(%.,)+",%$,'*% 24!".,($3!5'&%-$&/=/-)/<+!%$'*/."++,2/!24.7$,-).(00"0.*-/'+5&0$+)29;'*3('1048'-257"',.3)/+*.,.&5+)/)1+# *0*,&.,!+.$041:,0"51.)**"'#$7-;0)42'%'3./42**.)'$27,3%,*)+!+)+33;>,2,3201*(-))(-''97&,/3%*4)++* 6#65("1(9,12"9/30.-5#+.2//ՀӀ׀ԀـՀԀـրÀ؀ր׀׀Ӏ؀Ӏ׀ՀҀ܀ԀԀځ1.-3%5142/,*7$-7/+//')+-1+-*'!/0+2.1!201!5*-+7'6*7&*))%-*$5.4/(!- +#-6(-+.%+!(2 0& 4-+/1&(.-*07(--)40#(&77!,.#0/$)1+,)$%84+"%!%,*%$+-$4)''!4'!)!452$'.0!+01+.+#71+)'01//)5-+/!,./6">$((.*#*5$ 6.6+>457'/.$7-!,/$2"!#)&/70.$22,/#6(-&)(*0,##;#&0*..,240+,$"0+1!,*.1.!'*%'11/+%(&/)/ '&6(/!51///(%67)/2!2';''%(-+ '&,-- ($,&:)/,(3.# +:20,#/21.%')*>)+3#9++,'-/,202.(2*(*8.1-705) *4&)(!(-.3("-&7'1#(#+/**)3"2"/)7-&/,''.,-'/$+&((!'(''$#(*%0#4$/9/5''&))0=/#=/ "0,#(&>5&,/)(*//9+/(!A11>2+*72,0&3,)<@(/BՀ؀ـրԀــЀڀĀ׀ۀր؀ӀـҀ؀׀ԀՀ؀ـׁ794)".65.*1-.5*)9*,%)66-)*)0,(3'-,/$%+&52*+!-+!/)(6++/10%()#"7;(%%2)','--+"1,#(%82/$8$'-&.1&/&&#(-,"%0.,//11",52$/25((730'(1/(0"$3*%7$'2&*/.-&86!$01.-')$.!' 70//1*(0/-$*)))..)&-%*'*5,3(*%'?9+79,'#(0144/$1 50!&1)8*1(*/5/4',2+3((+&1,(4$-24&1!.1'(.#*%-'=883*(!'%-'9'0,$#/*'-&3,22$ %1-( +$)')1-3)*+7:#("71:+5#%4492+.63-*!,5'//##132&$4.!%3(/2-*/14&+'5,300!5'/ **.$$&87(!"6#8,#*!!31)#=9-*()31/!,)#/:*%.$-$&'(&$1!-#-+!/8)($1++4"*04%&3'.,"1)#29014%-$1-(4!0" %/4171)&*.6)<0>-)$+2(-1*9,1 5--&'63*1Bڀ׀ـՀրՀ׀׀׀ƀـӀӀրՀӀ׀ۀՀրр׀ـԁ)%+#&A ,.7*+/+8%-+'/32'&2#(1+1+'",+-0*'1/.=)'/'-//50+../-$+.!*'-+6+8$1%2%9)-0+-(*#'!(%,%"0-,%-+".1+5 4+*' 12/-5'1 /.)2.#+0&4*-5-!%3.70/02'(3&**5/!//52#""/#53$&//60% *)//6#&*-2($$$=3$#$3/*%(,$-# .1'8%.44%/5.<0+((#.0#5.)&/'-%.()$.14'#(4*/4#"+(.05*.')#(+/-,2&4-%2':)8/'0*-143+$)2!1*0,3$433(% 5*:+).)''' -8*7$0!!/6&/.)'#5**%/&'3 '/.1*2/1*2-%2+,/*-9=.10+4,&$)+,-&6.%3'1*-#(12+..(!.(0,00.).-!%-) 0,3219/'$2-*(.)(.0+!$-/,/(''($.04,12423--/497%/*%+52"#,3-3+/3145/9&+0- "0"-(:5)/3--)5*'')1.+,&%23,+рҀր؀Ҁۀ׀؀׀ÀӀրԀՀրҀڀ؀׀׀րԀԀׁ;42)>+573*6-+)03,8-:607)4'3+-+C2>A#2+(5.- %)(+/%+8/*32(*+-1)/#,+#/3)03 .)/(+657!/-/ 1+9)!0*1%5.)+ +$3,,,&"#48+/%'5!(4*7%%/&/*%.2"-+4(/-/,+*)-.$0 "./*,%/%:+3."'82*#%"*&!)"*/5+*-.'#1-)+%)0&-.'30'"3)1?/2.14-58//#'$+$0,5&.-+/111+)/$/41/+(0+$*(-'/5'-)*)))1.&6)'7('.'9+.);-2,#.&+#/0,)*+#3&&)24*.*-8,//3$+'6)+(+#.%(+%+02'.)-%4!0,$0E-,,&/10*4$+3-$:*#$+$ %6%&0,*%7+2!++-/5#(9-+,1'+)!$4+)/##)+30'2&+6'"((.+()5##,0*-&-,+&00'''05%-+6'-#%*+,&20'?+3-2!,9-%59%.'#(,+5-2-7(.$)+&%5&"&)(6<>׀րڀՀӀՀ׀րրȀՀـҀԀӀ׀׀׀Հ׀րـ׀ԁ *60++220'+/-",.1++1.&&)'0//3(35+/(4+$9+,,)+,916"*++'&4))+#-+4*-*+26')4+-':2%/171505(*'6%#$9'+1'3,(,0%&:9-%=.1*0-3/.2'((3-/-!2 4!,*%+(*+0-*7)9<&0)#+8'--1+2110,422/-;.1$(4-60$-70''&/-"13//764(/(4//$%/&63-"-2").+*.6?*3.+*#/$*7*-/ .%)*9!0-#+,#,*$'):'+1/)2&15%-*2!+.#*7)*6/&/+.1--&,#-"*3!.2(1-3/-#)"):"5.+*.043/-/$'**3&'4)366(766))7#%*$"92-%!,,)/,11)*'+,!*,#2/(/4-7,8.8./''#-)09$%9+2().(4"%-)97/*:)/%1*04)+. ,713.70;+14'+4*$-46!!')"3%$/&&'*0-1:!.'1&+,#$'/"/0$.725(/:*&(/73,"/13+ (-+=:,/&4ــӀՀ׀ҀҀՀԀɀۀڀրӀπրԀ؀׀рՀրրց8*3,*#%,#'*(//:42/0,5%/1/*5#,')9%406./+)).(1,-/*$+$//-+66131#)/1")+0*2>31)+%'$. 2#1(/-&/()0+.4-*)%(';-."#,+1!*0/*#&3'?,%',-%:'".&.--1,#'$1+-*0/41*(2%+..--40!$%!$1'21..,(8-/.3#1 ,)*.'4(,=6+/4 72/5*,//6&&),-*/,1&*3(7,)#(0%(*";*4*4(- 1...&8+3:-(5/%+%(.9+)1,'+..,*%)/54+0*.(?((*&5/3(.$':'.:,,<'14-.+1,1&+3;4,0/1176+)'/(/,4).4!+.*-!%-58*.'+,.+5&#, 2(2)&/4,',)(/-/%,0$9(*+-<+1&5/9,&;5/5!&"&55$'-*02&.45;#.4-3(-="1$&$.&"(%1*'&139$'*-*/0!$0+0!,:+-=)9/-#0)#,,2+ //,0,/&-'19#,4)#%")90&+/!2,,-";6ـրҀՀـԀӀԀ׀ƀՀۀ׀րـԀрۀրڀ̀Ԁ׀؁(+#)-?1*#%/0"&,03.-)-'2,)!2*31$/(.)6?2/--/)%#470+&0.)++%A.$.,3$'1<+/40,2($31'*10) /),:+5.#-*0$*-15#/'/)!#())6 !&! #0$#%,!/-#,$3,%1"%4,)8'8+(&*) ,$ 2+%931%4,,#$.:%01--)/-)0*!/')(%/:*.3#()2.)&+$(()A0(&-$#1%7./%:1+13)'("$$&)+$+172<$1)%* $35)2.&)( )+(,52'3/.$'7/.0$*+/! 6'%9%%38+6/*34'/.-77*/'*./)$/#7- ,7,%20,+$.%6+'1./%70'*"'%,(0/-.!'.&2=*-%/(4--;'2%(/"/*8:+$%+"') +:62$.)++4),0*/0+!7"()20-.51-/!96'0.8+&.'*,#(3,0**5%5#!0&''.!3+)*'6+./#"+,.7.))0&20# (20-52'95-&!;%)4=%-)26(07.-+,,+,'-6.400-#ӀԀӀ׀؀ـ׀рրȀӀڀڀрۀՀրҀҀ׀ڀӀԀց2#<-03( 8-2%,/(/)$/+ ')-(0).&'3$/')!",'&0!0+!#%8&49$,,*-3&(,-7(<8--,$%%*+&%1+/-)0#$)+'('&1*8&.(21'6&4)!,0%5)'$,) &.(,0(/4&-* 4.-'.3-0,5 57#8/,/01.;+4+)0&-57!%.1,')79$-'.!/')1*='$ !(-).8#5*'/-(4"!0")(5+&%5.-(- %2(++0%/!9%54,,4)#('5+-'-(02730))'+,-05*0,,,02++&-C4+111-%*,=$50,,)7:0'4,**&5%)- .%!#''&//!%2"2*,:2"-$:-4$/-$2/&& -&3&+4'+.6$#/(2"$)*(&*+(-$-/+ #(*&/,+1;05312''*&B*+1.#.6.11*!+4,!03)//+)31,' )-!.'/--5((),'"+3---'/%*00,()0*3%1(-%+$.+)++)-"-64-945*) )/,4/ #3:!2/6*)6/&0)*/0%#2?$3(׀Հ؀؀ՀՀـ؀׀ǀӀـ׀Ҁـ׀׀րـՀӀӀӀف7!6$-')3!0(*36/*01'/'/1%*+&+.(.(:0)'0848''+'")3)0/.*5%&$*'*-,).7+-&/)3&!#-3,$+%%++7!"0$+5694(,,/(2!)/:).+,%:1&+-%/+$"0-/()95,/'32 %,//)#01'502'%#2$$*'0-0!)1#0'2.-/-0"0//'&,5'(4 '/'7.*1(-%%&/#"/+6$.',+(6(:(%)23+%$)/0!71(0(+'*31*+&*)-636''132+'(+&+1)4%5)/(!*$$&19(*%++ "/$$3(,:'%67.&*+./&!',$&/%+-("052 0#3$-++1.43/)(-*1+2!,"*+;5*,$3."4/*29#443$0-,$&*5-.7&0'''20.&*1*#'0 +- 5('15*&543@-$1,+3)5#0/942-"-*"+!>65%--42.%)-($45+#!6)&' ,-+,&/:97%,/48,!2%*6166*/-)6.(/'51'%6201,2-3.+/-./)/0&/3!8#/ 1%-'2",ր׀րπՀڀҀـ׀ƀր؀ҀЀրՀ׀ۀۀҀـ׀ր؁9'-3)28+2%*-.,().05&$'/%6 2,4&$<1,03)%).-+;-+1$(),!)3 (;-4*1%05!*',--<#0--,8+,7#+,*1$-3('+)(6,(8*'01, %')4%+-,'-*451-0'*5'&-(-+16./* 54-8//*6))-33#%-4#/.9*3+#&+$"',$.11)'/,2)*6,-)(?".'4!0%38'#1*#*--10,2/,"/2*','502')4./2$/71,')2&/&*(!+-.5#6(/,45%/-#-"%09&&+2"/&-7&224+25 +3/0'*/,,/)*)! .5/+(%4+61!.4-,&847+*0/0 %;+%+./%0*4',09!,,$--4&+1(.*(-3(%4%:1+$-%)0.-4-.5/2+6.(/$+0*./$/,'-*7)/72/*(',/**/).."/+-" -(.$7"2+#57 (+&!2'$404+'$("3&20+--%,-++4#7 41.0)*!2#'/6'%),"4'<(0$1/(6-5:'=,.&)8ڀր؀ՀՀ׀݀ـՀÀـӀҀՀՀڀӀ׀Հڀ׀րӀԁ+(),2/+-6%$.5%0%*0,912)14*1)/'0:*#$&7*7)6")383'18,'/:*--+1+#5-!$.,.+-%0(#,/31/+582.32/+ 74/0-'/$&:/&0.54')4)+,**&,-*-.,(.1--)**2/,-19.6*$, &8+/#=/4-5#+'+-/(2"./4(0%-(6$*%7**-,3/+1=220)0.-#12./-6*'2.0(3%3',) 04#6/&.$*)..(%$,('),/3(8303&**6'*4-5&.*$)6.+,/"$6( #)?/ ,8.-,3*(29&.--,#,@5)')2,.27$'5-)2)5%)7('/#%,-/$%/5,+"*"/#/7'.2+'.500+("2:2*-*%-.//'#%!%(4(".,74-,*&(+..+/$(),!8/0/$&+046)8%,,##!.-1!0 :$#0()'4*#9/(+05-/%#2/6'2%2+34#$./*$--,7)+*$0-7-2;9%3','(2'؀ԀՀրـ׀׀؀ՀǀՀ؀׀ՀЀրրԀҀրԀԀՀՁ-6"./(:.&*,+1%//09(#?-:..#-!-$.$55! '016-+*))6(+* -&0(.(8,0)+"2)/,-*.,,/-"0'$+%,+<%")$7**--79$0//4+?$3*#.2*.(2)7.%0&4'+.,.;9-2%('/"%')%1-"%/125%7-/74,41+'0!*%"))+-$:0.-1)!/3'7#1/29-/ 1#!(+"&/ 722#0$,15*#2:'$/:2!,**/1)'0#5)+0&!$%*("!3)0*0/!4**-6!"')#),&26>+.13.70+3*,*(24*+-)&((5) .B+!3/3&'?+*>#+#/$+)(-2!/% 1'$8'%-3%)11-$++*++'21-02>+51%&'4:0275+!.(".2%.,4!)'%+30:*$'2"5,7*:*&(/9,9?93+6-4/3׀؀րӀπ؀׀ـՀˀԀ׀Ӏ؀؀ۀӀـրڀӀҀـՁ230/0"2&/.2,=')3+2&0,2-(3/*6#:17%5+70/465()#5 /&)!1/&-"-/*/+--('*9%$/6*'0+'8312'/#5'%(/,-+ 16%;$,,%-:#(&/9.%0+6+'*1+1&-4/+(+*,..)$'*-*)'6 (/+%9,''2,1;42)8. )1*-7$'05'80%&(&&-/+&&8-&,)54'.!0,1/+'"#.*,4''.53&--'"&% 24-/"50( 5)-+ *215)24,0!#,%28 4.(-02((*')$%#/%1)(7 &,'$!4%1/(,9 #,)1'.4"*.)-8*2"1*3$''2.-%4$/1$+")/&0*,(/1/.)042#&/$%/%-'.$'/0.G9*0.1/1=0)(01*%"" 2+10"/95;'((11'2'*-,-)5/-+51-.#-;/%'/-.,/!-.,+*!)2-40(./"/"-/*%%1#1:0&#'!+ $+/%)0(0++/5*#-*+9/-*5*,,5/("2,+/6,+,44"+171 !32+%5%'(1)/24*#8/4.0$*/156+(",+*+0/-/$()5+1"!-*(1,--$1/)+&%%-6--/*(3*',"839*"0.,1)&.+2.(8*:(-1#1)-/--#7,2/+-#+"*9,.%+*37+(&004173+<)1,92*--#52:5)(%0.-+&7..3/$'"5$2430 %7#(6:.10)34!5.%')!'*-12'-(/-&-:!#' 2րրрԀԀ׀׀ـӀǀրڀӀրԀ؀׀ր׀ۀـ׀Ҁہ)+-%/,)/,.$##:)4$0#).+%3!1,*/(01#&6711'0//1&)(+2;&,8 0*1*%$2-4A'&)- +)'1:'54-'+3'/5/.'!0)/%*--3:4/,%.*&%1$8(&5%.1'*+ 2:1.+7***%1**,+2!;3$35,-,+4,,+320&&.$)(-.-8):(0+%J'/).&'4-/):-04/0';$",+,+*)-$++*#-$)#/'/- )/4% &2)9$-+-!*./1*30(70'"&-',7+$+81+",.,34)#0,1)30=-2)!'&1%%2"4.84(,&-&+,"#"?)+2!;(*#%1*(*0 1"(/.:272/#,8).!)2-*./',$/(#&/8*,!)8%*)*8?'-%!4."***1*2%9" )";!(+1)&.2*3,)(!"4(+,2#'*((&-0"+*- *.&&)+-.)*&.380,%5) 3;/)2/98&7%+),/&'3#'/'2 342)%1&(603"3*/-*;,)**074(%/5,/+2',025-,$-2,**Հ׀ՀـۀՀـՀҀɀ׀ҀڀՀրрـ܀Հր׀Ӏڀԁ 2>5(&49$).1%-*.0/0-7,0-5!1//3):&,5+1.'+4*2&04-",/%$+10+0(+0,*,09."08,*/3+4 /)**,'*5+3.2+%!,):'&,&-18%/:#21 -$-((#&.6?2:+%1$3(&+/+,**/.-'&11$4*/$94-$.,$+2'8(64+$!(!//3/!0523*/"061'0?2"12);0/$4 '%91"#%$-(-'$54*)/8/..57$-,$3%'.62+%+&/+7*$& '++*+34/1"-"52*).0+244$.*'),6'(40+/+#/)0*)+%#/-.2)-)2)02,.24&% %20:(0)-&0=$,#6 %-.!*(/$*%+!),+409)03.%-2();+-0+,-H0*((3-))-2#+1..3"0!(*&4/'0%$1110*"56)%&',!4",/)2*,0%-)*(-.!.(:4)*%)+3-*04/&%)48-6'%/%&4&3,'$.28(:.+#'(#+13,(.*4& *5375+(++'('15,5-*$ـ؀ـԀՀڀ؀րրǀԀՀ؀ԀـրրրӀՀրـՀՁ4++62'-35>7),(8$#/,$(/->':)372&)/%%)71-10..,#,0-+$02!8/+:#*0-.04 $%0 /+#/+1-2,/ 3(#236+=7"50(-$'-*,*7*(+47/)&*!20)%1'(3).+)"2%2/(;&58*%,+&1.*0:-/3-***12+0&578!/%/)-A(;)1&-!$$&'-,&,/&(.'+)5%(/&.+5*3##:!#(,&)&6)//#"'!60().('(1+ +"$.)/+';@1#-#($.)./)&(*65. ,'+%-/+/(,'3%'4/1.'(%02.'0()0,(+/*&&.'/:.'-!'((0.*++4$,5*/4$4,'-9,"2,;((#910,01,5-4'*(4--&2&.7$2#P+',*$$1!,(;),.'&7%#)*-/-((1+/"+-%:231.&*2(50;&!1 *%01+&1(7.2',10"/5.-+)!-/;&/$1"/-<31(,( ) 7$50,7"1(0.#&0#+!2+'4*&.5.34/%$..%.5<9%0#06Հـ׀րԀՀЀπ׀ڀՀր΀׀Ҁ؀؀ڀԀــҀځ(;-.6()&$'-.13$7)1+'*0 %::28-758+++3(%1-)(/1+(/=)'0601#"(**#!).* )#& 56,-='%1362(!'*)3*21&7/(,+,',/.9'*-6/(+2-* 333,5:/,/.)-,(056)+1-,1747.2-20"/,5&*60%0**'+/#(""1'5'21-!)/-"00)101 $30+('),=*3:/+5(0.221..6$4(%,2*/82,) /6)*,3)4%$43/+7*:57"$(0*.+8#>,%0,,-#/811*0*#21'*+'3751/(+8..!-,))!((+8,&,"5/$*17]oh2- ,)3!30',#*&(+*$2.3326/!-*&1/#+.7,!),(7+6-=&("),*(*04,*;"&+'2/+7&&-8&-9$(#'6"/.-0%3$./,.8()2)*4/,+16/06(&82/ :.*!7*#&2,--#'.26/-7)$>54;0/.1#/,!70+'+*3:+5)!*4 4 3259($'**-&.C0Ҁ؀ҀӀ׀Հۀڀ؀ÀҀрրӀӀڀҀՀ؀؀Հ׀ҀӁ;5')#+18./57+414':*#/2-4(/!(7":2/(%1(7+*%%*-)&($-&-2:.,118&**")/)-3).$)9/'75#1%4&!1+2-,$*)$+.)4/!3/ <1&%%/5/"+ $,1-)7.57&24)$2+&)1(#,8'*# .5820#6(,0#!1'./,/3%($-=*:*/,970()&/*-$&3+*/4'"'+##-3#-3;)$3%*('$(+#&)2+ $0,*1/%** 7?%+3+)56&27*51610/()(/+*75,))$04&5=1-%/1(.,/2=$2'8+.&.-,3%)3>#3*2).& )*"0.$ !+''2*9e-/!;''.$+0'(!0(,/.*1(134 9+3(4$;6/6.+$'*-+566./52463-*+'0$2"&1)("141(++1/-'.*0.2&:@..>,.&!,)*-/,($4($!.0/,3313*3/+80-2'-%&&3,*4 '-2,)/062$).%0-2)53')8-1'+5/*(3$ +5*06)0&/42'%7.%+#!/,226$4#.0-/0&(-'38+&#,%)2/4-5'-8400"!4.1+3&5/39/')00)-2-(,!&$)!27#(3*.!2+21)+:)+4)./(&*2/(-0#)%3&01-91))5+.+4&),(*(',#*4,0'4-)/9)0/1,75-/.*82'3'%(&4 #./(./4-1".-(.$1,-/',+0'-4=$&()6!(50248-0,),'4%#+3+,1A,.+,&0,;%'+-*#/ &>-/-3!1#+-1"'4/!0-*0F5A3-*88.*')!((0'.&6?2&4#0077-,(5(02--1/1+0,1%8*##3%'(/)).%$$))($,/$21,0&!)%:'5-3=%*%91()))(17(6.0..&*4*,4$;!(-!+1+/%%3."'./=6#013,%=.3---110.233'2.;.7A'1('/1#.)"!9126*1%33'1/-8'7/Ѐ׀Ӏڀ׀ڀր׀πÀԀҀ׀р؀Ҁ׀р׀ӀҀӀ׀؁81(+3=+(3'0-$,=**+4."#,)(!*(!*&5-16324&/4 *472,1,53(&%<''221*-6$$,<7(",1))314*5(9/"+-18/,2)6"(.((,4(:,<=/ 3+.7)/#2()+*+. /**,,2+,&,+>+),.,0**#%.&%$/85'2'0#3+(+,9)*(-)'*02604)!001-0-#53(-'($3.1'(26+(;&*-&4$%*0;=9)1:*% *)/05'"/&%."/,4-/,&%/(55)(/'30% )+("9-4/ ,--3+1*<2#$*#/.)1/$15%&216+%!20,0-!0*%&0*0'**$3,(3#*)3&1<&2)//"0.'2%%3+#90+.!#3(*'#+ %2.,%%.*546597(-,-4+7(7*(9/'*,:+18+4(#! 6010%'09#/*(.=/!/3;$%((/1''5%./022-6#83'#1'2"3"'+$+)'&*"*/*6*0'(+'144")2'23#&/3*6(%)%)+'154**./$.*#!,6.142()''6"+ԀӀրՀր؀րڀ׀ǀـڀрրր܀ӀӀ܀ՀրրӀс='6?$>,55+%,-3.(#5,2/20*/+4&)3-,16(*$##3./ (-*(4&%7(*$/0/&.-,)2-4*,5&-%4*-&**,*3@(--7,/((0-'1!1)*6%,4#%''+/,,+"-+%&*#3 /%/47&0'%/0++&/,4(&0,616"70,!-/2 #&,)( )3#=%))1':41*'72-#+++$(--(!','/6.2,2()+'('1-+,($*10$'.---,5&('&&1)*))+6*$)#(+.!9-$('17#,30/*#.'/)*'$B!1*04 5*1*70-/-,"'1682#+*.30,$%654, 26"&,1 +2-423/3%331,/!%*3 -'($+3$$-(-,39+/'-#%/60()+8(4-,#&*#+30!3/8)&%/!"4=14)'.&/16,&(*9-160&/.4'7,)9)8((1)(%0-'20$,3%-%2+-)0+(&.'*/+ #7+2+*51;6"+:2*')6*95.+,77))8*$442,1,0 ,).-+#%%13)1.9=4.(0:5ՀҀՀڀЀԀрـրĀ؀ӀрՀԀӀӀ؀؀ӀӀՀ؀ԁ+,(.&'*0-271.5*&+0+*2$4.$$'5*& '/37.'4-'02(&6753-/.,''++.7$1%".&37/:*#-.:7$)/$1/.!)*.$#&,3/10+4'+$"'/;--@"*9*40-*"*-,' /&$)2(+!61.+#;$/A2//&1)5320(."$7)&0,2%4/# 7)6%)/6#,2('&5-..(*!&&.5(-3+//+3')*",1(+3-)/%-/#/"5131*.+)')!$.!(6'+/+/(061$.531%+/+()'.#!/-(2,3)0)2472 $&1.-,0),'-'&3)+-+34/+09,+-0+)17/.#-&)"*483:1&!'%#&'1'!&%%,-'4(/=0*51%"4&+'"%++!&$-.%((>%'61,",#"@& +%)&.> &+)7%*.;.)&()**'8%3+/1)0+,'&9(".95%, /+=8#/7++=-%*1/4$#28+3>+&5=$51/1/57#>&/!/,!8-,''$+*84.;1,3;&**3'ڀӀڀӀրԀӀ׀րĀҀ΀πۀ܀؀Հр؀ӀրۀրՁ!03(&3)20&+-*38/+1 41+7,.*/++4 (#"&/,(-&%&/.&2(&61%62&!24*:,.-+-0+&)(%0*/(4'0&+)('36'7,07=/%70(0'1%5;"&,.86/-/4( # *+9!#$&-5-(. /0'%%,.768'3-.( / .#+082)'/!,.739-"++""!254+1#(4'!.%7-*5(:2/,+,*0-246,&$07)*/'-,2.,"+.-4%*,0+-.0-0*%-,&'-*0,++'%&)!*9,-+8(''"#&,. .-0+%$-$)+,(/ $9*/*$1,.++!"*63D-$%01*/#/(+$",$3B0$0.$1$1.<, $1"B3''715,'(-1,/2+$(6+$'+5%&+($,)(%6 +#& 1*3650**1.%/0162#/!'+9,,,+13,26+'=5--,)(9,'("=2%.4 1+2 5,0,!5%-(,%)4,"7/,0+(C(4"/2.&%+2,)$-6,())(.))8+14.**.*/+'":%.0)+"5+-$+"'.%2'(+ӀԀ׀׀Հـ؀րҀ΀Ԁ؀ـҀ׀ӀӀ׀ۀԀԀԀԀՁ0+(0$(1+))-&6""+0!22,)..2-.8)5'/.&1"**2+*4$%.)-*7'&/4+3/ #:-,/&%+2=-'/)(5')+'*7-*+))&(4/64''%$'#/=37#52//@503-+.&')3/+=/-$41<"-6)/$5-1&-,!'0+(3!-.+,'%5(.#++.$*5"5)7'=+0*4)*.,*1$(.,-20'0.&&!-0,)+'*0%,($+/#$&$'.(&,+6&.(& 5'?; *-&#)*!2%,6.5$$&:5#.,#+/($$&2&%$),--/6(2"%0*1.,5,',38('3,%*%/$7*6$/#13+ "( %/0/*-.))8$-2++D')0(!4)-((1*(%))/('/21%,)&'!,,1<10(47115-%*&:#2,/%$$)/5%+$-$5*+11-!,10#/##$?0%*1"-!7?6404+202:&.5*3%,.21'A)&(6*8&!)-3'&+,>0.$)7$'5:,=0&%2*-#20--+"+7.2/(#,0'3(1"'10493,/؀Հ؀Ҁր؀րӀ׀Ȁ؀ـހ׀׀Ԁ؀ڀՀՀրՀـ؁.,:.:8+0-1''3'*169>%*36*+/'1$,!'*'$&!()1-.*-&;71,(*-(+"".7*0-+.&,"3$-//,%-/+).$04'*'*/12+('-,--214 *#''---#1(043+-,%')-"04%#2'5:/.*0,1.+$9!4.&-6%&.( .& %2/9("*/,/%8*'%13/9'+%*)51//)37*+!)**,1/' +03((#'3')().)-.0-.!-"/' 13,)9*5/$0*0&!.)*$#,(!;(*#/5+$9'/69=#*&.+3(22+7+(#(-+0,'"./'1(.3$010$31*(&/'*0702"!/$>7)3&:"&)32#*&'-%2#(+%*'550,/?-270!0519*+(/7.$4%+-)4"0!-/) &'/&.-''*33,0'1%./*#4.)%!+ )#*1/&('4.-+"!)-$*"4((/)8-+$7)&257515+,"+*%/2+0/4//#/80)<0)(3)%0" 875&,:)/7:8*.*='76,/(3؀׀؀ҀԀՀԀրՀȀ΀րրӀــԀ׀ـրՀрӀҁ+*$31;,%,-/ ..-.3$.!+.),3++$$0&')2-33/?"'!4#-48"$*/"-+0-%//*%&&/*1-'4)*+,359%/80%25+%/)-,"@3$26 4*.+9(34+*&%/3$+1(/$&--,&,,..,(45(-&';'2,- 0#&*.:'3"3'-2-2&&#."D+:$8/.!0%6,/*%31/(')''-/&(%,5.21--*"11!%.3/).'"-./+'%3"$/*# 4-,/4205(&'3#'2( ,.'/42<.%40-#(.1/*A,#)#/4-%'-*2/*4$,9)+7,1)-#/*34+$61 /&:.2;*/+5%%3&3+-"916(?.):),/72.7-'%*.0,,,(&)$3/-#%(-%,6205+'26!''!02+0!-7(/,2((-'43)211"$)1-/3&6-'+%.21/6!'#%)$+ 3%3'#,,+,11)'/(/-6'/++!$1"//+;!1+",+/1/1$2"/)$),1*+ .0,).5&9.+92+)'2+5)2ր׀ۀ׀׀؀׀܀ӀȀҀр׀ҀـӀ؀׀ՀՀ؀Հրہ5)*3"*?9& $*&61/2(+/-+*-:*3-7-)&*4<(6R83/3-/%*0/'%'),02+9,/-/2+/+27>0/$+%#%.-#+ 3.*"(/-','1, 04#3-%##/5//9##?/+4613*!+,#$#)4,.!0**0")"+-(#$'&.'(3/&)/?-2);%-/#/'"0($,*./&+,2$"7('. .1'/)3','13*'/8..(0.&.'3,//%"%.#$/..#/.7*'"+4$,)6-&-%/'&+%.,:',#+.,2*$)*%7F(*0/:*2*,(+&(/'+**#("%/*7,-5+.&7,*<'21)2$$31&$03**8($-#(0-5(&16.; *&-2=3,"$)=+')%*&23#1.-- :/212,,/+/,%9/)1)//%,!.+"#'&,3$05/+"/1)/-0#&/> -0'#9&'$.--/**..),,-/&.+*!%5/%+& + ,(,-5.0)+&'"+%&'7'1;%'8+/)/+05*!8+;8&2):"(#&!/.18$34-0,.24؀Հրրр؀׀ӀҀɀ׀؀ր݀ـӀрԀ؀ــրڀׁ**6.5=0#=-3&"*;)4''(.-013*:-/;,4-'(.,*/&/!+(&7*/0;-8+5&2',7%-/%$,7*).).',' 1,3%/&$7&1+,'%*'*!-'-,,%*'%160*%%3#*3-+%$$1),,$#,&+;('39'!5)!4>&,&(&/14)3.66925)!#+)221.,2%4207(,,*3-'!3+(,0&',-&/)%$02#->2.98*570$**('#)4#%*)04%0%%;61"35954,1135 .*(720.(.-"4%5 3$3$7,)'&()'+#%635.)(%%.%,((,/0*"+-*'3$.*'/%()*-/$9./0536+(')("#'//0$$4)3%+8(3+,.+./*,9+(6)-*;&1-&!3/*-&(2-!$./'61.,2&4,),*<,6--?#1ـ׀րـ؀ӀԀـԀʀՀـۀր׀рրـڀҀրՀӀЁ,/)9%74'+('*1*--/,*-/.(#1)+4,.(,%/58.)504*-%----'#:'4&&9*)/3.*/(!%''&!5(0-->3%4/*-#.),)+3;/6,200?23.(<)!%*(8%3/306*3/*"*,94%'//+>!)2.+15/--)0(1$$/%$#"'(+12"$./&#%'%5+'2+#+.5"+ '*7,0/.!41.&60)/5'&*&/,+'',*/,*-"%-#,4.2 /'0+)(!*%(+/*-355$4(+$8.),(+4%:+*5/17-!/<13"$,1+..).(ف)0,456 %'*4+/&+(,/-1*=%'*0%,&+(46/?-+3/*(&'')5'*2!'!<2+0&/+5"#3+((1-$0%.+%2(9$&54'5!,%%%.*1-+)-1$1")5/"+1-3-+&(2'%%(/'.#%740(##"!861*2)'- -,*/!'2,,/,50$7'(7-%6'-.4%%))5.1+8.++.$%,)4."1-*4+91-+)22/10-6,-4)-,/#-'(.%642.12ՀՀ׀׀Ӏ܀؀؀ҀĀ݀ڀۀ׀ҀӀӀ׀ۀԀـ؀ڀՁ16+2#0()#).-D'.'/1#?"+(+?2.2/&.%%")$&-7-@2),;470&')&71/1+#01&8)'7:&5"/)/,'#12%-%3)0<(.1*.11""/##!3.*461' 91+#,/','2$,)2++2 <70//:14+.',*5%$"#(*+/)' +##./#%(+2$)9+*34($+'' ,)/ +/"..+:)+90'2"7*/+2#0 4-32&0/().$*/-2"09/'+1(.*&.).3()&7- .#C(0$1+)$%%0%0,'3&80)*49%&'&$1)!52,%//!$(3(,)707-+4)"01+(!&!%/!#6/-/)".)1#2*'+''+$&-&.(+:--%,%00,8./*+.,'+%/2(=++3)/)&32(,.B&=2**31/),,-..04))'&4233:0!) (073$0%2(*-5*&$%')&!6..5$-3/,3#%'#*.+"6)2%42$'*,,0!045 B,/<'9-&/2659;&:'+30./#3+01ҀԀՀӀԀ׀ڀԀրǀۀրՀԀՀـ؀ҀՀ׀Ӏ׀Ӏׁ!&'.:/+*))"(3!',)")',&+)(,2 4&!++6-2-!(1160+&=))-7/)+1*,+(-)*)/5,1833%2%*4&352,)0%/$1#1/-6)4"0*,(5&'++##/2'$ (9+- 34.7%*)&!:5+&$27#&$-'4*,#+-%+00-5 ())+1%)-)+'.3"2-*7.0'-,"1((+!0$=5$,/.(+'092)(',*0"#3-"'&.&($*/*+(5'3#)!(!(%-:4-)#($#+.-+%/.#+-.1(5$B)0&>,3/)%(%.F$./'(%/5.').'0"(6,250(-+/6/&$&"2).1'*50#93%*,!')3//3'8)/)#(43118$ (1-7%8,+,13*)*'//-../102'+'61(/#&'"/)-5")(k#&$*'")#1*4*+)&%%-5"1"(%35''73&,4/(,3,9'3?'+#2.4*- 2'&-2$/62)/10,)..075/%&%*6*7*29'-,7(+/+(&0-#/1!:0*-41.+%(22'/'./ 52؀ۀՀ؀ԀҀـрԀɀۀ׀Ҁ؀׀րـ׀ր׀րڀـӁ505+<563&49+&'3*30,,(+#0%)-1.(8/$+*>/+/)8&3+*,(',.*("14-#4)2$5+.4%*$#2. '&.23$0.)6$*8--3"/#+0/--&/*-!-=1"7,-+,,-/2&()7(1-+/C+(.01!2?%',(#",'0$22 ).7+'$2).,(1*%1,1=.3(.50+!-.44&-#0#.*,23+( 0=&41(,/.*.-**.:%&",1!-'+$1/%<3/(+(/&%++&!+,2+9+"./.'1(('/1&,'$50. '1**"1 *$+:'3.(71",'&4)&!$7+,&5(1*',**+&!& .83&04,/9#1'.%#5.)'"(-.'+*+,+-($/)#/$$,42&*7,.+3*&,+-D)/&)4/.0.'+*/%!++/$'3.,.#)*-,!.!/'##!71-3..%"%717$,(/01,)*(()4)/('/$,.8!,+4-%*,8-"+3-6(15+704)*!3D+1+!1/&&.3*189/87!')/*7(3"*14C23.A+ЀҀـր؀ҀӀրրȀ܀Հ׀ـԀ؀ڀՀրրـӀԀׁ/,$+6217"*-*.#61"1(90,A,-/#5,.)85'-1$/(=67/0)/, #&B )%#*2 -5) .,/)*8-),.%+.6&*.,.+1")*$%:01$"+.'&+'"#).-#00-30-"/*.%2/2+*(/83.7/('.#:2-(&*-/.+-)$++*+4,!/4--.-1! #7-%<",-(%4+;&'(0%-*$13)&'/,")5*526'24$&-,&'!/# (22'.-.//"#,/2#9%!"16&$5'1,$53;3-"0-3(%1(.'&030,)6-+/&'9(-3#..!1)+.!"8+)$,+')/-*$0-'!.0(<(!.!-)+"'"))%!,*2+--+(2'!//(* "0-*2&.3!+)&.%)(+1"+4(./-%"/5$!15,),,5/$1*.02& .&' '8"5053$).)2-1;2-)1'-1"#8%05+6',(#"(7-@(04$-+)-('>,"#.(&$3.,.''%)/(-%?*##+$6'<+!6.+3'%'/4:&2'4*/)4.,6+݀؀рЀӀԀր׀ǀ׀̀Հрр׀ՀՀՀ׀؀Հ׀Ձ-'+/4&,0+/1()''2$5*+6.*6*%3-(23(('1-<2?42=&%A/1.-3'&1,/0#$,2$'(!.).+<7+#*8"(-'"%/"411+103.((61$/1D/*,))0+1-*'%1 ./2+#.'!(,&'-(7'40(&*%%",$/#1(-% $-(7 /%/4.'+3%,#$-#2/"5&2%+/,8(!0&2!#&%*/0))/,*3(+!522+*$/%3,-/#2%*+%8',33$ #/%.0#(&$*,&+-'+,4..')4*3.- 345+)"2-263 '.)&33778*#+)'.+&$-6/#;('(,1,$*))2'14*('42+33)''35/('5/";*81$&6-2..+'.()4&1+-3*'*9&4'9"1,4,*3-$+**)(%" .%#,$&&/+&+#"32)/*(. $%-':42%$-+),0032!'7/ '$%)/,*/7&2,'! ='/*4,22549-'*%&,*1."=0+=7!32-($4<.)(()-30*1325/&.*5-0,6+081&׀ԀԀԀԀրڀՀՀƀ؀ӀԀҀ׀ڀՀԀՀрр׀Ѐҁ00)2-#1,'(0/0$+)15,)51-, -03+="))4&31),-6,(9'3.#%%-*0&--.&-&!/6&)*,11&$%121,+#,//-&6',":%/1'.,$0*0".5081-2)+0;"*++0$5.(-+*$1)!9+.'-.$')#((':-*&#//(+51",+*1$-/5*-+++0,-,6-71()0,!$)+://%+6'(+-."(&% ++#/+4*..23)+)0=0#.%*$'+//;""+,605,'9.#':(&6+*&,$.:,#3)9//++'#!/5.(/!6%#3)2(/+),/-&%->+-.$7!2&+!1..5(7402)(* #/:)11&6(& ""4/>*&&)7+!+$.8;'33-1,".(-(-.#$*(%"/) 7*)-6$"!"%5***2)0+)$'(2%+$9,'<$':#16+*/9&@ .',$9%%--/!%41-.'3&4/16*$5=#'%$/)3(1*01+%/ -82('-#/'5/&2'.'7.+$(.9&.00-*(:*!+))9(.&-1&24:6*:%7-/=ۀՀڀڀۀրрـՀˀـր׀րԀӀـրր׀׀Ӏـׁ&')+15/)2/.'(.7+'0.!'-,/,*:38:(*0.5+#.*#9"-'(#$+(%4/ '1&2/$)/&62,C+#%,6+#//'/5"/-1#+8#6./4&6*(("%/20#)3)--#1&%$1-/%+1)*(%3)>2.(9)&:;'+ .,-1&'.(2*'$,5*/.*/('+%,)(-"73!53(/%80%0-+*.$-.73(73::.: +0/331-8,3)26+%2+2#.4,-&&7)"(1-!3/$!!-7,,14(,%/0*"--2)/"/-/3$1<%-7&.3(56&",/'=-874*!)*/'%,/$//%%12$0&$+/*0/&0./0!25&'+#33#5((-3-$()51%/5#)/'403%!&($4)-/8%*/)!)/8%=',-73), $5&,&5/-.+$1.<.,+0,/-<(264/"*$)/(0'60"-.-1),5'*+/6-3.+!#""-(7&C*6&/0%<(*(10,"0+/10#.$*)1%$/*0%)"8+%)&03+-$&'3&8.*%+.&*,&-/4,.3++,+./239&3)- (8,#..$(%,)'2*%.-)./,/7!2)*3,)1%*2*/'"+*':/,2++;34#&%6*1+1,!.$#<27/)#-?167"/#(&#,<"5/2'0)+3+,6+",<)/=,.,)/%2.*%7&(9:+1+6-0'$,4#-1. 34#%/)10)(3.(--)41-(.+-)8(7%),-5&"#+$'+7"06%%/70+/#(>+ ;%/.0 .*!'6'#,02)0,*,0!93*6./-'&290#4#'(/*7>//,-- 4%06/4.-+('/- "!2*/%*6 /,34&%/62,.0++-*1,2)3-3",9-4;+(1<5+#++2.ҀӀՀ׀؀׀ԀӀՀǀـҀ׀׀Հـ؀׀Ѐր׀׀Ӏց$7(6%,7//:-.'%/'A)2/8"4070-/%'$3*..9,.*).1- ,6**+;18#)2,1-,'( (00)&.*'5.'4*!0(-4;:($";75,4)*-.!,#5'43-*/+57&&.&0*/"405%&5+;2/# *3&(8$)!5/62,*4*/+/0.##%40'"((')+5(+-01! 112)&'<5"4&%6%8)'/0&!-%'-,(3&+''7)5'-12%3.5)+ 2271 2 )&-041''56/!)-2!%- 149$*&2"+(6! ) *#()2.33/6,6'0-,*4(:"/+$,&%;.2%*(4/"!.!577)00%3# #57),*)9143%/#2-8%/2)518,23+&(72,%.*4)%-()21)>/=6'',"'(&*+0.(2$'7%& 1&,(!).& /+(0@)+4837/2-'*)<*,?:.).,ҀҀۀЀԀ؀ڀրՀĀրڀԀ܀рӀՀрրրـԀ؁1/.5366%3+&6./66-2-1346'*#6 5<*)4"(.7$-48)3&-*8)14%'634$6, "($/&!4,))$1('4,3-0###'-7)6.+419/$',))-,..2-!,%:2.2,+#&3.*.0 '71G4(@2$4$-35!/-,4'1@-3,-11*"(-<)')5((+.11/,"3351+;2!&1/6--*,%$2,,,.+5)'9*1'+)7$,0,+-,-**)5 /(%/"+-/9#<623(1(9,&0**-10)1'+(-:%2.)!((;%223$/6218(.)%*)0.0$#4(33:'-.&.&9-50.+,.4*.5<-+,=&,3/,$15 841?%+++,.'4%"9/0$)+&'/+44'%2'2$#2,#+--2,$)05.0./.,*/+6,41*$/0%9!96'*&#?#(1%4'+++2:).)'*$.5*6)/8!*0.$3&$*$0(!&18$6//54,&-";#)6..'0",,!#0+2(27&,,--I&)/-5 $1&)!*12,20,@DB95)1ԀۀՀ؀ӀҀ׀܀ŀڀ׀ـҀ؀׀րր؀րрՀҀ؁+13*";%"*7.)2/0):5(6/8!)/1%+*:.).+1-2C#)++#3%)*0&$$14*5%*#6*-.#31:,*335//)4)02435#/')4)+#&-(-2*&+6)*457*(**33"-,&/-"++%!"/$()+"/=/2*.$32'-#%*;&+#43%")4!(*3)'-%*%&/51 !.)4'%,%-'-./)#')/)-1,%2$%/!2&!1)3%).-2$(1/)#,23)1()!4*85*24*.,/-&);82/) /(!--2$//(...'2(-+.2*&'(;3&8 2-07-*'*0'3#--(/&"*$!, 9(%74#)0),5,44(1-#./+)+2"+-8'/"1&-&$'/E!/',.'2$/%-&+% 4(.4)(/;.%42-'&$")&(#,-+)0-7+4&6"+/"3+'&*!(122:&/7.*1$-+#5&%&%(-+%*/;1+$!8:1//&!./.!#.-*3+./24/ 2*,+(1,%.#!(2/12-( %:(/(/+$()918-0,/.1*/-7:(0/$5,%րҀـԀـڀՀԀՀŀ؀׀ԀՀ؀ҀՀՀրۀӀ؀ހׁ32.%&.:25*&!(-;%H!.51.#/3(2)**,6)".44,)5,%4 )'2('.'3')$#(1(4'&$,$), 3%1/+'*.(9#3,7#&.(%.)#,)'/'=9!01!#++12))*5'@".2%4 (80.614+.+/0+*-0!#2/(-%+) $++8(-2*(+3'&+/#'3&"'/25"+)&'!+5+,#,34**-5+/(=..$-&.12#&%.+D4%5,!..,9&(2$)):**%)*")&/4/(*043#+6+$%!7'$B)5(3+ )700)0(1"$.)17,..(%05-5%#..$#(&##3'+-)-#&.2/0-=3%&-.0#00%25*D02+6+"&/$,#()&!%)/0410#-0.)-5%/&(-$&,*5),48,)'$0. ,+/3.24.(,-")"(5'$-,(0',((',(+..!#'8+8*/--(4(:,/)*+%-/.-(2"2,4302.)#,&/($.31-):'9556(+ '5753" #7/.,$5$8&)160+7%4.0-7-"%14773.:3ӀրӀـԀԀ׀րԀƀррՀ׀׀׀Ԁր׀ԀրҀπف&.6,&2)0*+5%00-*'-/+9,-2:1/ .6<4+11960(4350<58/5.69(."9/):5%42(/2./3!,*?2--(7+74/3(23.,/+;30,2(?(*-0/*3,8%+2.08%6'&?*&**'-+-!)6")61,&*$,)$#*)!--,)1*621*1%+.,$%5,/)')2/#!(#(+'!?.4-03+"(#96+#1+$4)(1'.**7'88$(*1/-%%.42/9*+1'!)&!/+-(*,,'*".>+0:",4:30)#6%/?!(*)#*-/")!(.'/+)-2# ##',*%)#@++)*)! *0%*3%2"5'17-#$01/624#/.3)0//-(-4736*/:#778.#*,*+)06,$:/)&6-"('>/4:@0!/#0(.)$/'+6(2-&;*$/(,++&245)%&.+%!$$1-!$-(/)+-,.8')/$1+2,+&/3',2-4#!>69#)5#*.($).,(1*&*(<"%>,)((<',52',3.."*95&7-8.%1"-0$2.#207 .%*++980&%$/)56+3#"4#-1" 9*'4(*++0$"2//"6'2.02,/'?7$*.,!%*"+*)0,&.(*-#()/&/$!2+-3,.(+,&,"-/<1/*<)+-,+6',+7'-5#$*0')!)+,-'# 12/*#-&/%(-/+-4-3C*011-0&A3$')%-%)5*'*&3!#--+8,+3$*-,>#3+0/;" *0.<7+'(./!&8+:*%؀րՀӀՀπ׀Ԁπŀ؀րԀ׀׀؀׀ڀ׀րӀ׀ڀՁ6/5;(05$70-$&,"#'6 .4,-2-$/'/#8/(3.%831+..20)$$03%.80$8%'-326)$=-4'43)"#<1!7,'/-%2 #%#:!!/2+*')'.0#-,.1!2-$#(6/2-4#%/6*+.%/1 /'"=2,/#*%/"5-6.(#9'/03'$',37.+!%*,'/133)6.&7+ .**3-18$'"'15;3/4'.2/@2#+/16'%&8,,&'%)+/+91(80/)+"&&6*'*-((".(5''-.+/.6<$6-48$4-14)4<#+)9'12))2/)'#0-+&'/+((20*2+5-1*++*7'$.-(=-/-.**%0$4+3024-*,*07 ."2,/19)*//!'/"'+/7,/('&((.$+!'-+$-/+(.1=$661-(.*)%%!4%)- +1!0+8+;(,;+*2%01$)/+',4%($-+*!,&,*<(1%'/*2$),;3/3/$&/$/,.%;,)5.&/ <&+'3+'#3)*08 1( %10//,+*2/(7&1(*,*-'&5 Հـ׀րڀ׀Ԁ׀׀Āـ؀Ԁ܀Ԁـ؀ҀـԀրՀڀс262<5).%9)$%,'.172)#1.9(13$#0+')&+$+)18'+*+,*15))):(.%8'&,'%030#)//).,92-";/&$-%+%-42(+7&#'.+#61"#+"#4%#21(*-'(0-'A(3('13,3$+4((3%8%'(3/ -%)%#"#'#*$)/!1)(:#)"#%#"32*/6*.&((#(3-83423/& 956*'2#*"1:*,(%'*)-*&%/&'(3.%6$),,,":'30+12)(2'.=!'9!%%.'161.37$)34+)/0:/+'%&''3&&0",262*-..)3$&.5"00+!1%6(-&"#2)+&)( ( 2''&(%104)-+',(15+/."/*'&((&0'.7"$0, )).*?&*)# 08"0+,-*5-)*%.8-":+11+5")2&"50)%%-*+.&2$3)#"/=6(,!%+%#>#14(&).'(%(-%'95/3(!,4-.581)$+/)'!05*6- 02.,4<12170+363'&%.$4,.(3%=2#3./'!%--//)5+-(*,&'#րڀҀրրՀӀ׀ՀԀՀ׀ـۀـ׀Ԁ׀ՀـπڀӁ1,7+',&--)74")).!9).*'*/%.1+3+,)51.(6,##76$7',&1,,'20/3+,+3''07-'/%.+6!04/<.%/-.0>;)>31/212#52=*0*&%0,/)<)$.&(.5*2-!&$6)&25&)."-,*&.*0"4*5'&-/4,$$&-1%&-%33'63+.)53(63-/'80/=-,:+2'7!+*)9+>"4)&)(/).)2)&/4-50,1 -,$4)4+,0),.3%7;+,(1-!(&1+-)-+,-)+,-,*(%+* &/3'#23/8-%*4.&$8!2/'%)',&3-3<3)+&4/-#(+%/-3$8$1)!&.(*?33*()/1'*,$0.81921/(3/'/*,-$+" !)*.,%7-4"(75/9$1)*,*,$/-#2 )/11%'+0/-(9/2)75,,:$+5/)+.$/0%"*/.$"*1&34755*/+-56(85(,&9',+%.,+/,+!;8$(-2(/8+3.4*0+-4)*1&)063&6,)9/22++(-.*)3(/$(++0!:%62:-ـӀ׀րۀԀـπԀɀڀـ݀ԀҀҀ؀ӀррՀ܀׀Ձ,/7."&2#;$-//263.!/6-/'1')-$(7<%%:)/922$/(-:!(.2/"/ 36!2(3,+520-&.$"'%'%.0352<0/*3+1&-%/$*%052/-8,-22%.*%9*=*'')3$) 7'/)-%+#5'*'.,!&(+5/ =%:+'0--43106!:-0-(!'1/(!-.9*&15#,+*.6!1$)-5+),+7*5!%)5+$/2).+/,))%2%%'*+3-(+'%-' .#*.&0(&!)4,/-.1)=04.+/ %3%1.*(#(;+'0&)',:-&!++0"+(++;//2))0,"8!*)!).10.4&$. '&+%)-*/.2!)) % 4$0$'.'%%$0%./*"!-*8.8"8+(*')5.-*6* (,4/"0-$")0*!*)*-6+'31&2/)'#",7-/+(4-03109-'".4:*0-&&. &&/,%(%"*0*)).4,',+)&/2!%/% 13--4/02+/'8,.3$262-$%+0=&93>.)9*0%.4+33(/,(7+.%-ڀ׀րր׀ڀՀۀԀǀրԀրр؀ۀ؀ـԀ׀Ԁڀڀف+11?047(;/74*191-0&5)1/69.&,$*0,0+4.&36&-++"(,;0$!5(23;.2&.#-()+&3)$3%''+7*,#!*,13.,+5&*2)-)-E73+#0,--925)*-7 //-/#6+155*/+*+)3++65**+/9#.#7.,0&)58(53& ,.)**)'.@-.'!14+8"5.!'83''(/"*06.%'1%+*)6",*>)7!4/*,"0$-"+2007.4*+/)5!)#:2/+5+/6$''*$6+74(%;1'4.()(2+)/7%*3$%22)40--+1/%'&)*)8 8)&79-!/(B0"26"2+ +7;%&)%**7.2(+8=#15!4/&,(83,2/&)/2,,+,,,0.' )2>2++ 536'+&4!*$(-(2($)/$@/$/1.)/%$>20(--)-(8' (!/$66+%.-$=64-@-*-05"(,)--//*&1)7.!+&,.4'.)9*&'(*$$4',3.'5*/&5(./6+3+3/#'!+$."70-07#G-+&/003,$.,%1$9)׀Հ؀Ӏ؀ـԀۀրǀրҀՀހԀՀ׀րـ׀Ҁ׀Ҁց%.+ 2+35+4/&805$)#$/-0*,4'/0#1)'.-$6/!15&24"51+62!17(3/)96)&!*#+')|݁F%#0>:&#)=+&,),.+'! -!#*2,,+-!*.)).')3'3$6,.;.<1:--/-)&(&:3=0.()*&&+,,58"$0()58;7D64-+!1#*$5#-+.0"/!$0/ '7.)21+1!/+>,-0'+2)2&).-5-%%%,"%!,2#-/(. ;8+"#'()0.0* *.+(2,+' 3)90.3/'30-.-/+4')"/,2&,$,-"%/($",#-2/)$,/***%+!7'$-#*&0-/&.'-&+'"(*2.720%&023"+&-"'0*# +/)&*/30/5836.'/,1/&:<-./--%5%%.2 //.*9'%;,0!',0,)0,6)$$)))'*&$#$-5%.""*+;1(:3,!()/"'-65-(8+)1)/4Ҁ׀Ԁ׀ՀрրрӀƀـրրـҀՀπӀ؀րրڀՀׁ+*15.1>:&(.;$-+#6**+*(9++04$'-5..+*$60+'/##0-6$/%,B(!:'&".,#,+$(,.2.#8*7'(/-/*22.!273%'1&"5+112:#.53+1*041''6/0"/ ++)931/5'3('.%2,QR6/*/+/"3).-*()"//#/&,-&* $(*/$,.*' -)/.)( ")%*#(-(2+4!+=.4/1A/ 1.65%22./1.10.,,3'150$02$3./$ ''1",.#0+1&%1(#,!2)$2,5=,;%'(+7./3*%:,6 $,+"0/)-%*#),*?+&%1$-#.*+)7#015 20&%0.#)*+%(.#3$2*-+/&.20I-5$)7++,2(--%*1-:34/23, /)%/+7+,3=(&!("% ,/&&5*(-,.1+!&%+/*''0-+*2$&;($+5.4)2)!;$3*4)80--/1 *0-1(,)-* 2%0&"/42-&()+6-17*1,:6/*,+-$!!'-75,5)%'2&)5/4"0-8):5(.;,6,1(%/ҀՀҀ׀ۀـ׀ՀՀŀրՀӀڀրր׀ڀՀ؀Հ؀րρ)+35'?5%.':-*,3C-+!%,9"&17-2+24.%2/%.)+32(9'%,8/(5/>?+'!;/))(#49/%*#12())&-5$'2'7//!6*00+&'/*--+)+/&%3++,$/$$,6(0".+/++/1-'7*%,1-/+7 #14#"1#)%%+"")+,+(6/(1,&/09*&3)/&8+;/.8,"(0#0:7066//4&,-7'/3+43.2+/(* 6-)4#1$14&0*7+%2#&'246#'3(+,%&)*J-/8%&-.0(*23*/31+'71-"+&.*)(-%#2*4;+./)5%51(17)"###501'*%')+( :#<&/5#2$!-6"+#/$/045#(+(1(-/0*62'?'*2&(.46%!($)/'+/&-'*655+(*-39-% +/&-*'2')3$-%+("#"++-%)8,>&(9!:*?/,3''"--'!-9-+'&*<(%2).+"1$.##""!$0+%--;(%6-3*)&?%/&)7(>#242;/+/#7B4*0*+!.7$5%*=%;4)&:93-1+-)ԀՀڀՀۀ؀؀׀րŀЀՀՀԀڀӀـӀڀրπՀӀځ 5+3'.&2)/2.+3,($.$7(#+'(6,.23%15D4'..766)%-'4%*%<9'"-%"76#+2/6(2&(,4'()*#$&*&.6.2/%<(.0',*/"(#*/-14(7/*%(%'%'&0@*65-/""(!)-4%2119)-1$&+9%$'%"*1$55C13,&)8%#'1+'.$,5-',/0('-$-1'+.)-%-.()&452%!5/5(6 &0-)/3-/"&1+%+'!!8*!*-!4+0)*/++'3'$&(*"$$--'#)!+20/!6/2&/8$(++&#&++;)24',0.&89)#,9-,.6-+>26.'($);3(%51&./-,,+3077׀Ԁ׀ـ؀Հ׀ր؀ʀԀڀրՀрӀրրۀҀۀ׀݀Ձ*369 )'$4-/10/7,)./2/2!7'"!5.%/+-.7?1+(*'5"9%4&/&*048+''%(/-..1"/(1307421+(+)(+2 &6%$#).%+,1(-230'. )+:&0)8/)4!<)3*$/1:9+7+1#,/4#,#')#-!148.'%000:(((*>'6"'1(#4-,2-#!'&1&)!.9)# 6)' -*&2.3..2+)$/014*'8&#,)+2)(#,#2*3%(&''.&14<4&6$* ,6&0+&3%(&31*$*+!%%:28.$;-3&0/35'2&0+4%4-2$-+%31%,%+0.)/31//36;**($1+C+5!*"(:1*".%36&#$$+#0"&3//)#"*-:%:.*#%0&-80&'*4).9(+"0("5+)625-.22*42<,((+0(75"$>&"1! +<3,+&/,("+;!,-922*/),"5'!8+-6,0,(+)*+3/,.(..6+3(&4-*6+&/-9#".'-#;/*1(+859,#(:+3.'-& *(,*)0)!('@7-,*,5(16%3$6րـӀ׀܀؀րՀҀŀр׀рր׀Ԁ׀ڀ؀ԀՀЀـځ20 335,//.;&=2/#/*,&'-+6 ((--&@);5()/++)111(18'(-',.+(.2(#(03+&4+52+-)/* 1)<0#$D33*0I%))<,)0&15+)%'%!(!:-'?,.-%)/,8"3%)'$!..:.#7%((%4&)).-+-%)10"-13$&:,+%6-14/(+& 42+86$,.//+!"%+0$'0:/-)(%62&$,+/&&))0(.+#%$&.22'652&-%6(/)/+!&.62*.4+-90,2-$2/% 75%!,./90<-%-31.!&%3(,.,+.'25"#./!'-6+-*./3$$-"-%2+#05$2/&-%-,4%5@//)&;+7/$%/,**497%/7*.;#4"(:23.-&+.-$2,+;#*#2#)'2%7'*0-$,83#,.9-/&D".')+*.%&&--4&*)6)'"$(&-2./,2--(+/-0-7'7(0/4'3$04'(.&-&/5&1$"3'-2?-% /5-)%$3'+/%#$,0/$7:-.,/*+160/)=5(3!0.5&ՀЀـր׀Ѐր׀րɀӀ؀Հ׀Հ׀ۀ׀ՀӀՀՀҀՁ/;.2&&&%*'2/6-+/9%$)++4%);<$61'+,,+= .*.'!'338+2/!*$*#/.#)4+,$0/&/:*')"2(&(")'*3-(*),75+5,*/.&-1!,3,#.&.$+"0%+7-17.(283*-6.-.! &-2$-1#,$&(( *' *,)0( "-*+2/54*-202)/.)74+-3/",--)/)*','6(3)**--#)-'&#+-17/+$*/470"53'0(%3,69-$-/'*3+.)%2&/.3/%-/-%55#)0>(3-5'16$%*50/(()0&4:-5-8),'($$,+6#&61*2:%#,)/6!&,%).4.)1##*,*&06/**3%&H%*-(7=#+6*6(55,%%#1+/.0'--15693,7-),+%'1/3'(6((.(,4($/ ',+/.'9$$-6&*(/2*;.3(2/.3*,-1'65**!1/ &#.(#..$6):+-0/.#-05//'A-4%)..*%%7/'15"/*&6+36,4.'.92"/2!+/"(+;0+5/, ; (5܀րրрրӀӀՀՀˀրҀЀـڀ؀ՀӀԀۀӀ׀Ԁׁ."9:1-+ &,-"-)-3!'.)%(1",6/0',.0:'#&2)%,('-3,*=60-2+*3-6#3815*,-7211'"-")+0+:87.3"*+0,8 +.=+$.'/815#'*1+,97;-$(&'!71"")01.$0'/.27o7) './$0).#)#/#+7,2!$**=-*$/+/1.1*."2&7,&7/11+392(#)+1)#0+4(3,;5/)*1('&+*10+-(0*12/5/*-0-%**' 8-*)(3%7.&+0+1*'33++*92-+41)+1#..10-211.'.5.,#'/'4)/.."5,1'+') ,:.30**10,**+(-:4)&*,+'-;..5!1%3'62B6/1/3(7',)!!5%#$*3+'5.$6'13#%.)+."3#/*&''313&3-(50'+0*-/2-6,7))*="#/ ,)$%22"2,5').3&)$6/0+#$-31.-8 52''7#.'1:.'">&+-&3406/3),)/+$,)1/.+-/!3*,&1+)+$-,/0>%3+-*200$/-61&Հڀ׀Հր؀рր׀€ڀր؀ҀڀՀـՀՀր׀րҀՁ/<6(&?3)72;!)1/)2(311+6)2(+5(9.7+.=44)#**=*2&+#0#;$(((.&( -+',':-+32$+8*"'$-/$.-20%.0#(.32 '/-$*#(33-6,+)*+2,13-+4),'--6&%$+'5!#'%'. ;&,'*)(+3.$-70.&%2'1*#4')#/&$#52-).,53') 5-7#0'70,1+.5>.+3&13$(.,&9;'2%-.4205 */+0---41$/ '3).&).;1-3%1(8$*/8/-01/*'12/;-!-!0)&('23/.,./0+;20*55/&7"5+)5'')/*.($&#**-9*5';/5)/".)*7('9)-/@116+)3%+-;#.:*0/-**.'4),7%/),)-,-=+:$$)034//.3*+)-*&1*,/%&/0*/+,#+, ,5.+.%1.;/)-.5(.%1-2'%/&*&/32+/47-*0%*&"*9-7.%2#*71-D+"&/''8/),)3"?19(2742594+(&1&>-*32#%"@190-#11-8-:0;/+؀ڀՀӀԀـ׀ЀـĀـԀۀڀԀ؀րҀڀҀ؀؀ـӁ*6/-).6%*2/#.474'*,.,-24'*&%6#)8$-+6)2+?000+43//,F174"/%, @1/'-)--'228)&)-<.- $','C/!8+5(*.%-5-2)54(-///$-#3-&.&4 $3+2'2(/*6&12,766().+6-*$%.++*4$@+);)+,('#0/%)+(-/-'!<3'32(##./+"-!%46--4.&&-01,&&,/',!&09/$-+.2(1..*,+-2450>#,/$3&-(11$'0*#+,8-&,1"1 )'275+(/0,7+)435($63/#.!1)&*-+0/-4/*2&'&3&%.".,++,,.2*#-)($()-",2'"1/)%)$', */",%/##3>12= +%,+5,.;'0+33-)+.&1-#-.'-0)-*3."15(#&&%.)6.10#-%+'$*!9+',+(+*--%9'/0**&3'>5#<(&5""0+,+-%)%$(+<5)1$40+')/4,!.)2(/'&-/29/-6)#12".1:.('2./(+69:-%-.1*&ڀـۀـӀڀՀπҀɀــڀԀڀڀ؀ՀՀҀրրـՁ5$'+.#*5/4*)/.%&,),!!7(2+1,(,#11/2/3!6,/-%0'7/%+*.%43#/-5).,(,%;/+#"1+72>3--4'+0),;-,6)-'(++ (,"36;*( */#%'-&.+1$3)6'&;,2#"9)/-"(+&98()+1-8/)'.!"+,//%!,.>$*/:@:5,/41/(&%4$&,/*())+(-6&*(-".$'31="0.*(/=).'*,06$72+*1%$&5,2-9-0-+!*+'2'-&+0#18('7.'2)+)6.*,/(5#:4,7)+!6-%0/'4%'#5)$41:25+0015&.9",9(5+1(4(.3.!02>:2(0(F*$2*07(&672).'+*(.47. $/'&/01%*,'5++2++,9$(#.*+'208'"90$)!+$+$-/6(+*'#'70.#!&'4 %20())83 3/"32;+13+'0)1+*(3,*!+&'/1,6())633/+6),%+6)('/=)31)$'&1/1*-&+$1,*/+/'*/&!!1'%'6'/#4%10/'.<, 0-#//"5),%#/0+#7-33,%&($#-2,'-*#!4+-#*0'#1#. ")"*9/--.;%+,&#/$?7-&##8!&1)-5/6*A'%0)+%%0+&261%&,$=/6*"7(,!-1-$/&6)5#,+//=(# 94&') #1?$# (7,%";-"1/$".076.3.&&/.21),#,*&$,4.1&71-.0&53+1+#.#*/9&.*4-1&8.(92) 1!+2)*(60'#)/,!)320*%8#8*--/-*,)/+3-&+(3)/.8/5&2*%*+'2*('-2.%) .,+2#2(1)-+/0 0- .#3&J'*+;),$3'-++/("$"772,+>45--$3A43+.,8#7/3'".,) (1914*12,5%')&$/.%627*%9;,<&րҀԀ׀׀ҀՀՀՀŀրԀҀրЀ΀ՀՀՀ؀Ԁր׀Ӂ.+):3#549(D/-03%%$%5'-(2%+'':$%/422'017019+0)/'0(2(0&'*9+0#)45'*$&-%+#$)+(#2/&:0*(,*--**),0%*/,%,0*&+1,- +.,7(/$.+!/*0',-)/81)/).)3($##+'.3**24$0'$*%+%7-/1).##"/"#..+)0/07$21'1,/-#4#7:-4$%/$175'+%,&(&1.2--3'1)C*,))(#0&%2$.+&60%(,%$23-"&(35-$7- .:-(%5$&/),$)**(0./(/(--#5*$!-%"*4/C-,0""&!)'/6126*)84#51(1-&%17)"')!3( 1/+'*("/5$81 =00.+/!012*6)%'?/0++3B&6')*546)22#- &5C+/'5,*($)$+<*9#,;2(6 =3(%*))&("%9$1&=#/!,&;.065-7(*,7#00/604/1-/*80),, #,"-3C#*.40;6 50%+./0&+&*. +8/25(70;>$1/1/%(67&2<3׀ԀۀҀڀӀրՀԀȀڀـ׀׀׀׀ր׀րՀҀ׀׀Ӂ//5,**&24;'.37-45#5'/1/1+/)''.7."&6-.)3 /*2+=);0*,3:,$(-(6)=7,/>1$*(2(/%+6').02*.!5$)*,)(064(13/*#&5/.-/.#+%#,')%99(-./)7-)++-*7.*/+#03(/+6/(%+!6>-&6+2%",/0-411!"$0%$+/0#.**/"($8)1+&9*+36, *)/-&/+&/&&)6 -*" )#4.2&,&'/!/2+'2('/+/01--'5%1%'#7B*)//)3 0%/,1/,0+.(-%)**(-0*.'5*4/('0(&/-)2$00-&*0&2..&).*+)+'-/0/7)%521.=#0+-'7?&.1#1%$%*+4+),&+&37*01)<"+<)21.4$-1#" 0 %!%81+4)<*.-23)0-4/*,#/+51%11,,$)3,7#")#-2.1)4670 01('.5C**1)23-"),4* ,0+1/;-)8''.3"+)+75.*)7,9/#*&51%---))*73/0.'--%*(/$4*:ԀՀӀ׀ـҀӀրՀ€׀ՀҀ׀рՀրڀрՀՀҀЀ؁-)1/<+1&2#(1*5,(,-()'13+%..+,/(4'5!-6##4&9+8(6,-+%30/>$53$2-&+" /,5&3),+'1/#74-*!,)00$.%,$&-&)&$)*)1:$--0!6$2"-+"7+1-(5(/9+74$04"1(30,%9.2*',)%&)('$9#4땠",30$/ ?8/4&#/)+ 3%)#+/)2**(-"6)'&*"'-0-7),)5) ')%#6'04'+.$'6'."2((//(/*(8-&-* -*9*-&0+#/3.,);)141'$,)(9/%431$!1/)+!9*;%+7."1%*-$4.")'0781"(.0')#3,02:,*0.3:#.%&7'20'!*/.$$0.-+2$(:#"+ .)(*6++0*,*<3./&'&-7,'7#+#+3'))308/0.@&/($+9'23$76+-62$59(0$!/;& 3/1.-3-,1(%&2"$ :/))(302&'%/+0'*/)4'(3)56. *2*F)--..'(028.81-.-$5426,(06*()6(*׀րԀՀـ؀ԀڀՀǀҀـ؀рӀҀՀրрՀԀրԀց4(2!#1"5)67,**(4=),3)/2/(+'0+)'6,(67'1(0?+%;()$!,*+8'&.40)!*%0<1-5/=#<+.'1/&#$9+4".;<4:5*2+*!-!;"+/'(:28 &17''-+2(0,+%+4$$)2-2/,('+#*(+1/%+0#)3*:.)*.0&-(1'$/)1--".,83)/%*1%#'8!2ЀҀހπЀـрـՀȀ؀ހ׀؀܀܀րـԀ׀րЀր؁/012*23$+29-&29*&2)41+178&/"58&0*,43/-"#$,().4,$(7%1,'8-$4.)22%.*****///,1%(%(/*#2/0&0*"2,(5#/+"+.)/;%+-$5&+)/%&--#,)2(&089,(1:02,'2$'-.85,*/")$$'-*&&6,(**1(6.M6/1+5- ,45&0,!&4,6/09*)'&+*./##1 &.&')$/12)0+- &&!(+7)-!,).#&)**$+*-*8'$2$2*4--()3"-&60/&'81#))44'#'0,8&#&,-.!$2%46 0+;-2,1!+(211/"*-21/93/*A/,)(=0$3%.$("!1(,",2)')*02#71&0)423*(1-&37)4+-%,/+-*&0-1"0&-3&#$-6,-00-+8$85/.+,.2%'3+0-,3(5&)0$)34*/2"+"//'*.+-.-./$/.1,40010&3.-)+"*0$6/-$:2+(&00.2&+1(*$-/*)/63"5223.#$.1+/7*9/./.րـրӀ׀׀ۀՀՀƀ׀Ԁـ׀πԀҀҀЀԀ׀׀׀ځ2:"=3#3"%6:&5,*)6'6&(B7*5865+*0/-',"*/29,613%*-+'0)*.))&1%3$#7.62()%',%,*;;+%2'$.''/*,.?(?0'*.,!1$")+4+* 5@5")%0,/0--5()/-/!%+*1)++%+)0!(-+)"+/&00,*)155'5/%*//$")-$*/"#+001'0!.*++*(+.&%&4%10%2-)5*)4.,2.(%301)%.-%381,2'3(!%'02-3".%/#((0 &!',++7'+3,,8"/,,+8"(&-2-1-0)(+"(/&8.!'*22*1)/3/).6%%%0-23.3++-%/'.5-3$-/+/,/*")"73+92)02+315.1+81/$),*&.63%7*0*++4//16274*ՀҀހՀӀڀԀՀՀɀրЀڀՀ׀ՀրӀՀЀ׀ԀրՁ3.';-%.73.%*0:0,-018(0&1(60/*72,)&12/%5<+0%>-4/0+6;8--5:%&-.+1*.$2+*."-584&!-'/,043-?(/3#=11.%(((&!("6.,20"*$"%&(-3#-,,,+,'&*435 )'/./!,-!#&(1452(.(,/)!-$23./)%+1.(24!)+#(-*%2)2/',+*20&'+*+()51;0(+()52)(2$*10*3/32!.(#50�.&05('+,/+.4&1%*-.,'-*:078&/*0/$&.+6&22%-0'0+%+'9(2(*!%:'#(,6,/*12)-*+.$'&*//'5*-+/17//"6;23%.(0(- $/"0.!)+)-7")*0-)#*$<-*1&334(%4,%%#+;/+(0*&*$50'+:)5!&$91"8'/-).&*/+)&562$6%5".,7+6(#)/',.(.*(-2//5,.!/30)&970%81,-.$%6)4'%,0)6(6$*6%&58+";5&(4*'((&9-4$=.,,1#'-0*(/%;Հـ׀׀ــՀҀ׀ƀրـހҀԀՀր؀؀߀ր׀ـԁ503+3!%.&)-2''+*2),&2+5,(+/.0'+1(+,-8.)6:/%*49',001.72('5-4,+/$&%*%$,06- 6,)/!!.1//&0/%&/'/4(0./6*/,-.) -50!'+/"/-0,#0+(+#5#60"96*1%7".-222$3 (*$4.)+)7*/)$(0+'/*'',(.#?*!60*-)*,'%*/#%.-2/.-"&&3((&,5C*2 ),)5'$7&,+$4!%'-2"#5+%&%>%9-*4:'636/"'314;/#+%268.)&6-&&+*.;0$+'30#21,(1+/0 3 )1(1$2":9)73#/)4/*#/&(.%%34&)#649.,*%('(0''02%0.')(+))).);--9&53&'(!/*/3*-.,$2-#,*, *.%1)-()&%)(*!!.)2-*)'+// 1&,$,(2).()&#+,#/-,*&**.,6#$%*0,):1$-7(-<(.8$ *3/0"215*+101*+; ('4;31$&.4=92/*րՀ׀ڀрڀҀЀ؀ƀԀڀр݀рՀӀ؀ӀՀՀڀҀ؁-'(+11$1+*7D/*+*0#.40**2(4'7$/522"+%,(+A8?6,)2.==/2B$7/*5&4"*,/#-&$2.(52&)%%./38$"-.--(33)-#&%+)/494-3)3+2'-/,1(-$))#&,0%)-,+56(2'0')&3-*/#%*(&%&'*(3&*23**-1"%35'))5#08!-,!'-)),.(**00/81 (93>,-&,8")(*.-&5#&#)-4(+/$&/2- *!.%.+.+14&+51%.$-401)!+%,8713+--6!*(11,-3$$1-&3 3***4-)-*-09, )0*+%$1-+-.(."038).'(8+'+-/07(-!-051%!+1,*-440!+2+01('6,7302&0'+<0./:%/#%(-"2/!(/.4%1,*((;%"-1.&0.(-,*//+)5*8+*&5/05+/& 0)* /'.*),-(.?#+*%/!"%3)--0;(3'-.%%3*64+-+9-%!%+27(&23$'63'!2<++''6$"0331(1*-3(-,14 74##$.$ՀۀՀــҀ׀ڀրƀӀրրр׀рրЀ׀րڀ׀Ҁԁ,)/-($$+)>49*4-*)02./2&,(#77)#"21&/4/(%-'7' "+54*"9-.(0+*&,#-6/-*/+2')514--'*646/.2#3,4#"1-'8/*0/.<(4)1+&*(3%"(!)&'+#()(9()*%4A1/.)(/2""#!,4#*#,+/'*4'&'-"3/"-(7';"1/9):(.'%;+8(. &2-'-/-7%//3-(%>:*%'4$-$/2-&-%0'!&%='(#7/$&,,(33/,-2-$+/5'%$31/'4"/4$2/-+,/03/,.++8(0&-/26*+,6//*-"$#/3&+#&8'B&3+*);29/!.*2$..!+1$%4+'/''+/% '#/(&,6&.3#,,9 0*4!52/**-/("(3'-/%/',#),"2%*2+' *=)#3&2.+*5#%4*0.,0*)()-$33)0+1,2+,(<$86.*,47/#),(&;-/()+)-*'(0*-+3,,+<,)'!1141-#%./03(%*04%%:'(6-**"":&9,";1:#4-)*-+*2'*06Ԁ׀ـ؀р׀׀րрրۀՀ܀Ҁ݀րրր׀ӀրՀց$945(#%3+"784'*2,1(+)-+."/<-1 @.:3/,1+1#,(*7&001+)4. +:2+&2#1#03.)#1&--(%/-&/,52/3*0*<5+,*. ,0/.*/.((/*&%/55.*+*--&*,,-4$3+,/5,42+:+%1-32$+)+/,7. (,, +' /"2+&'(/"1,-111%+)'/$35-5$",%3),&(-)9-),('%,0$'-@*-1'4/$3.+-.4''-%7%*1+94*()..61//&%/,0!()/,##'/!*+.&/(.5*4.2'5'#.*;$&,)4323'0-),!3;(".2/4-#*),7:/04* 8-*2!-5'+-+%3#7(&+ )1'-'1!-+2.,*)*'*11-.7%%'&(0#.55225*0-2'8)"0',-*+(2)2.,#$*"0280(/=3"6'.%12'+-+73-/5%#'*60<%!! 3&7*'-'"/0!98)1.63/.+++*4(-4/./"%$+<**.1:+102$*#*032!%6!1*3'/,րԀԀրπրՀӀ܀Ā׀ـ؀؀րԀ׀׀ـրπՀ׀ׁ,$$'7;-5,"9<1(%9".+5%(/+8$)043.1:)(,,$''#-1)(D*((!$('*23+6,#3$&#)234'"1,0/7. #<*-.&7'4*-((-$ ,"./",%,2-$,#(/%+-/,',)(')! /''*$-2%-)2/0!1%&3$)$1%-#--*&1.++'0***5'8-12"/"0'")1 0',-/=5)#21*"/(3,%6143/,("65+,/%/2')0);8+$#231)2/5,"&'$3$*+*:6+!.3;//#@(%2#,/82&(9',"!00034 +$)1*%1%3.$7%"+7-!,-'-"&<#*4&5,)*(.+//#)66$504$)5/%(-+23/.!6%5,$7&*6/*.+25'/*)-2++!/)+ ,!'2,-/$/'4+/-)(3)-$'0+5*1325.<0&5# )3"$2"*()$.1,,*",2*)$/ #+7++%+''1/7.50 6**3.''6#0.)5*2"20*-&*%&-2+2*52+27.+/53+.+4-%/րـՀ؀ӀրԀ׀ՀǀՀ܀Ҁ؀ۀ؀׀܀ՀԀ׀؀ـف580*31*06--&*+28((*)/+5/4?$372 *-<'+1%%.5()"1+&1 =+//-1,.--,4!-"56&+5,,+/*3-'+;'--$*)++/#.+"?2$805-)%-#&62-**+-*-2?-. 1%&1+,+.31/$$*00)&!,0*'-,(0-)5 8 --%*/+4(*.#H-+' -!%+*7)4,3!./!*$$$8$"'09!-1(-!042/$'(-/,)(12$*22+*0/",<'3)-*!4*,&.5.*,<.(0/5**/%+$+$2':2'/%#-/,.,/$2,)+$/"//*/.4(.140*,#4,)+)5')0-#*0,9/=(/*-#%;'#%')8<)'*(&3&+.+--'&:5$(&$1&1#-,,3+-*/)6"/-4*6/0$,=.'"!() )7#.0")5((+.:3)*$"!), *7"3".!.#2007)2$/1.1%'3*+./31,@:(.3-/3+)(()'**+%/&/(/-,0*'40.)- +-*+,=1"<%*802<'+:.1/&;,.0-&%4ӀـҀҀـ؀ۀڀҀɀ׀ۀ׀׀րڀ׀Հ׀Հ݀Հ܀ׁ09+*-*%/:91)<-/!6+206',45*#81*6+/-21(5*#;+.(,'76%*'+)'$#(.-*./#-(/$7#363+-*),)2)%,@)41&7).)1"-(2/,. )02)8((-32/+-/2)%-&)-1%.%12%/0-/'#&%,%./,/501'#)%1-+&$3#56#8527+$/$43(12*5(/""*).91"$5$5&)$/2,:$(*/00.1+3%)$./+*(/13*.)1,+4%'%-2,60#"*., ;-!%,11.,0 .'#3(++4+2!/$*)=,.)+#*>'-3&2*/+$/)%$-9-*1)2-6*&++,7;$'%4+1+*+#:16*44:4ՀրҀЀՀՀ؀ڀՀŀӀՀԀ׀ـրՀۀ׀׀Հ׀Ԁځ)&$/(7%0)*/1../-/'&/,#,$>-.',*$1#5*++.-&1<,+'0(,)+2/& 94*,,>!%#!&(/;*4!;(2,6*!#*0(6$&11.*.0,$%*#$27<'1+3%'?3'+0017'+,@"/9#-$1*)-/0./?8('6*&/0%+ +1-*"2+/4*+91)*($1;('*)".56:5//%.5/,3%4&+/-+0(*#(3(.5*$<06-'%)-'4"1$)%$.-4..5&3)-*,/#+2%/3!#+5*,*15%3)(+!#*-*-!*6%+,.)%5,-1+0,-:+,/$5'-2,%'+*,$&!+..&//+*.$+0!,.'B500:<'.')*'.)()1('14/0$1+/++$&=( !1)308/*6,32/*)523,/$(6%.5#0#-2.&((4.--:.%..)&("/(4&2 (#3%7,2)/%80/41"7'17-,,$.&'",*)*!%/1%,015$#7"()(,/2)&.2.>+318#%8.637)*/+/55%#7$ <2/!2)+@0,,*2%"30*ـՀՀӀـրՀӀ؀ɀ׀ՀՀ׀׀ҀՀԀҀ׀؀Ӏ݀ց"532"(+637(.14$1*01%4!4E&2*.0:*.%.-77)$ !!0((#(6" %+*/;&6,,(+00#((5+712-5#?"14I+-"204&*&5++*.>+!-6/3-*+( 2745*"76$-&-.',23/+*)3.4/4,%1#&$)-&5 !%72&".0++8-4-,02-) A,,*,(.6.' '2"2.*,-,"/%3.&&5&)%#/-/;-%1+51**5+-+001*&/-+/.(403-#03+2/041׀؀րԀ׀ր؀ـڀɀӀӀր׀؀׀րրրրހЀҀځ0.=*1,1'/'+-22,+&.6 )+1,5&-7/ ',&%*,/@4,!/419*1$#*@,*- -<%)(.,&06'/('+&#*+;'(/ 0"#7(8/#)/-(%.0#27#+/15+-,&2"-#/C)(64&1%/38.06'25,%1-%43!&.97+5*"*'+)!0))&"%#'2%90/<+3,'/+'9,#,(:/011((-1+3+1)9,.3"3!/)0)0%-#./-$#5@0+7(*,4%0/ '410(/,/)0*.$.'43-+(2()16/%1+09/3+*2" 200-1"*!#!%-*./.0)?(,)23'/46,&8&""2#*-+16%-,96('%4).0-5036039:--/$*,+%$*+115-'07'-%%2/05-)*&$..'3/',0=/*4.),%$6 +/"'5!./0#/6':+%8*)/!#$"2++).6)-,/*7/!;)+%+=9(8#1()-3)1/3'.4(& (2,"0/**',8&%',3)&*)(0)2+%4<*/+)0*6/<-+8) ,4+,%$)/4%*-%<&5+&2)0׀ӀЀـ؀Ԁ׀ـրˀ؀؀πҀـЀ׀؀ـՀڀ؀Ӏׁ(*/.,=*9:&+35)$"#,+!$''.'"+)!-)//((9*/3 2*9:57-68$.)*/8+**'(-.3/0$26)&4#5#+-(+:2-%(/90/))20+1/#3!)14,7(-,(*9*%0&-E0*%-'!,2/#),"304%*")2/9*03"5+&337/&/)-.'-/++=%(5.+6;'<(&,""210-7++-.-/5%252).($$*-(2+./<,9151%5+*+-/.+&4-'%!5*,(127&&.8"0-$2)')=:3&%.<3!/-(----97-:, 73.-/-%+4//#()7'.1<3''23**$),!,)4"1*/&8)/.*/-:&13)/'*4981-.'&/#('+6A*/&1$65+#1)++!/(&/(-2,7'0/.')#1.+#-(*9-32!.4/17+%'&6%.7**3',&1++7!-*&5*-1.)28.40,+)'*-(97+3+*7$"# 0%2,"3044!*#+/3,.)"(+5+.24$K/'0.45-(,//(#5)1"52-0:,,5(3&ۀրр؀ۀ׀ՀԀـ΀ԀԀԀ؀Հ؀ـՀڀրրҀـԁ6)!/&.-8 (./ +2&/0570:&9---/*,810.-+,,/"+22%<9'-%+-(*%+;$(++-*1&+&!!%("/**!(*&+-&7-1, %,-1,%,($2-54<753130.9-'*1)%()/6<0&/>1738+".()8()/.-.,$1&0!"./-,-,+,60*0'5#-%(*-.9*0&.&-%!2/3(/1"/9%/.+*'-3A00-03//2';'.2#1"+0:$0.1*+-2-%07/'.1"3%4/5.3( %+%) (2(8(/$'%'#,48)783,,&)67%&.&#-#&13+-&+.-),+8)1):02'31*'!%.;)*72),5>(7,:-!%.#0/'91'4 .&75/(+4.)0.+=04) /'#/2%02"/ ++$4&+'0#%2/'078&++-(.4< 58,+,./&30),738"5(%)1#.!%060,&6--&+%'9$*2/ *,7,8,5(/6'/3.**1"36*''1-/,.11"*2*)+'*$5&.%4**90,/6, '.:=+46ԀрրӀـӀҀЀӀрــ׀ՀՀҀՀڀрڀڀ؀ց<#');)-&='0/;/"-)-+)356&40+245,(4'!.4+*+.-7=14/?.12%./4'-4),6*'.)20'+9-(&.&>1*3,&*3)-7'9"!3","+!'+6.& 32+51+,'-<#);3"&3+01.!*"7%&((,3*/%(21&7((-23/22'&)4(,++)''2!(3/17%(%.0)%0+%21,*5>,&/*13))/"++*!'2(1/'/+&2+/.!)'0/021%1*-2/$5,(+"()2(#"-4&&3 * #/+'-3./,/3):*-1 )4(.8',.-*/$*(0&25)$++*-653;+%/%,,2/5-11-7"4512,*'**0+,44/-.,613,.?1!;78''+-3$12,;2 /''1-*) %17*3%225,)-&!1!&%,**(+%#/74)1$,-&.,0!,*,!)'-,'0630&$+2')2,&6/'**')/1*!,)20/0620'.-#<-443$0#.)#%0!($*'#.5 .9-3%,/+)22/ 967%-/+(441%-3.14,(1ҀԀԀۀрՀ׀׀ӀȀـ׀ԀԀڀڀր؀ҀՀՀӀӀ؁6#(=!82(+=-A4!+-*/,+)9,/3'1+-5!'/)).*-9(7(.)A.,$(+2..>2(6803&*-(0,.+/..)7.%(1-")'(.41&:0/3$$,.1.5#)/431(46*.#*')"*"!5".--(054'$'53+<14,$-.5->2,+)'/'#',0+%.3)-*3++#7423%5/',-519-!(03'+134#%6+)',:/++))@6&%,%7+*1'*55).3/. &+/1++1/96"9.&/.-:(=#,(%/0,-$,$4-&5)!7)0)+'0$%0#/*'!>)0*0.,.+022.?/##5:0+3+0+)#'/(&2*+/0*)(!&'+4((*%01++(/('/.2304-,!(2/040/(+&1'0''=0%((0)$$%&+1;.,&5-"*&"2)-5-1+<'#B1/1-@/+$#C';%'26&)-33-2&&,214(0760*.+*!*/5)'56)&*,61(+&%)B'-)32,7,-/"50&',#5#*59-65$@/5(/)(-#180,1*+/ +5(4π܀Ԁ׀ــ׀؀րĀ׀րӀ؀ӀՀ܀ԀۀՀӀԀӀҁ+%(14&6/&*8./(5+*5)#&7+>: :('-,1/%62)7$-0026320(5!,,;*5!4'%*(27-0)*9'4+9);2=C+'4(/13) %0.)+.&05:*4%.7#+8,2->1+-.,60='/'2/-4'+8,#5'5/ --8.'(6*1%*-(&2/)%/-.-23$(/-3+/5<'6$7"5-68!C$ ,&.?%((!&<.'-(#+5%0*+%3263-4'6 %'5('295)(01,/,#9)''<-)+,+*/&.'%'*6(+./6-/,0)*50-25.*,-.*,/0.26',13(-88+/.)/+2% #..-/-0#&+3&&)!*:.5,(/-,($+2((/,)%) &%)/2-+-:"*)),3' 8#%1'/1657#$$32$1*3*.))6&+$-'"//$+,9&'*(-:*).4)*- *#,)(%+21.2(<0*343&/2,9#')-F/,%"6)1-+/,-+7",* -+-)'-'+'70;7936%%0$,+$7$""33/0/-+/:9),/7&<,Ԁ׀׀݀Հ׀ր׀Հǀ܀ـڀۀрԀ؀πـ؀ր؀׀Ӂ:#8'(&.,-6(+1+1+'9(65!8#12./$+/4+(.72=4,0(32( "+/1.)"40(0#!3,,$1)0)9B1172%'' .*''$6&2#,%(!9-($).5/$,%8(-*.'%1&+245*")-/,!,./(*0%(/+$ '(#&:-*$10% -.&."((:0%,*("(#'$8)>3!6.($5#/6'&((,##%0. ,7&.$0(1*5,'6+)'1%*-,)"93)%#/&.;+-2&'./*%'*3/2--- $;(8"%),*9((10-(%)63>'4&D2*#'"3//2/4/'$-;%.,*$//")+&)*)00/'#+.%/#.0,242,.87 /!<19<$7*).2%),'; )/!+3 %-*:%//!4,1/!0"+'"4&3#!01)/0*)+"'*;6)&)1&&**#")8/-6077!%-.-%3(#40(/"9/%.0-,/5"6 10-/'*)*/7), $2$-44*&:.8&+()6+(-'")- -$#*4)-,55-/.+3/&,--#5(4-97؀ՀՀ׀؀Հր؀Ԁƀրڀـր׀؀ҀՀՀ׀Ҁ؀Ӏف6<5-#+!7+7337-0-$ 1#-4),3/(-(3-<)#..+2'2/$(%)4=/23*(#2)4&%+&#://3#-14)53)(*.-"$1"/:*/6+500)33&%#81/281/*0).!7.9)**$+-35.14%0&%)9'(.+.2/)*)#11-./,,#)8+11(8.&/**6'(5*.1 '/)..-+$#-'"!//-5(%(59/+-58-12=0&);"/(6-54'0,-/&04*)5*"0"+&#+3!,0$8('%66(8!.)6(&+ *),321%&0(4.'%,*62'(,)40+2.)1,/)-666%02-6.1$),0)"+,&),/$97""-/))21#4-*2/4(0#($'39(7)0+41$2/-7.#-01!*7/6-'+(2$/1*$-2/>/+-.,%.3('-*+"4'#/.03$-,+/)1+03+(, 0+273-2'%,':3-!84$:*#*2.++/%+/$-.4()%1*(*(.)*))908+*01,.41(!6,/1,,)05+++0%81"(4*7ڀڀ׀րۀ׀ҀԀـǀҀۀӀրڀڀրҀրӀրԀՀӁ9,430/5$-0&/-%+&0#37-*'/,%51#4=3>'/..$./3$0(120(+,--/"/+"('7*-*"%2 &4*$ "1*,.)3&328(6**#*1323)(,''*'%(+**)++/&(+&-3)<9+$7/ 4*(*,$(&-)98.)'/*3($) &9/5/6(''0) *(0-0-+(%11.-"9+/0*;,,#-*9׀ӀӀ׀؀Ҁ؀ԀՀĀـڀՀҀ؀ՀۀӀӀ؀րՀЀف)-/),'.0&-,*3)(/8$.34.2A)/")%7$,8+/0.+%/*$211#-2)$#34+$5&0/1, ,++56+.'035,.-/9$,"01/%;-#..') /".,&,'#,'&+,0!/;,.44,3,,,96!()7;/*.012*$0.'-01=--'62-,&->.2&8/+(0("/3-53**)#+3',&*+!!#!-'82()-)70.29*)%2&+'5. +3./8)-*-',9+3,;0')'%+1.2%!%1,(,."))&+20'-+7&" *1%2))2',,-$"#'1-*,%-*(=+()61+)--/0#7203:.*8('$6+-) *.2*)-0,77'7:*+!4.6!/)<322,%/&87&1*.29)$-,!)/7#-$*:5-5!4.*'('%-/%(.+0/.),,(6#1 >046!&1"4 /(/2&#/?*'1+ 6-3$+2"!--26+%3'"40 &,5+!1,)4,-6.(+!-,'*17)/*5,28+43/(6/%9/=,+75%(-,2+41$*(4!/,)@9/&4Ѐ؀Հր؀Ѐ׀րڀŀՀـրـԀՀԀـ؀ԀـڀӀف52*1*=2*--*/%+9*06'7+8924,#'7&1%=3#750'*4)$=!0+.)/!&1*)#-&6,!9! $,/20$1'2".&*, 7'((4& #,(//#.&"./$/($55227*/1;'$%%8'6/+(/, *,/#5%*-)&5-$-!,27',(%&85/02,)()#:(%497&1#3%+-11-.4#)3-(3&3'3+-4$$,/#6-"32-%.-'&(+#+1&5+01'05%,+.0!%54$2/%.6<0% /&,-%-2:%#2%1-,*.2-,-$2,!"*((,,?-.(6=()"*2/+-*2.%#*4'$"((("%"(#).))06-!2#(%611(%*&.)*&*-2+5)1&+4.1/3,*.(!7!/2)-3#&,0'/121#'(/5'!3)'9-)#<$4&"39;+/'0,/%!$62'--27-.1&33',3%'(20%/5!,+-*)G)5--#)6*'-3$:,<*)(%-3?4/0**,'3-2-%-%4.355&#%.<)6$(*15*+/.1'-*6+6<,)6'28**րـրҀ؀Ӏ׀؀Ѐƀ׀Ӏ؀؀рۀր׀ԀҀ؀ڀ׀ځ-//./,(/+(014119'3,0527 4*@+%+"=0,0* %&2,*+#:1,,((5$+/10(4))(.'+1#.(&*/$"-5;#610 -1**-.(/ 2.20/'6+(./)1.(&.*1)9'+33#5%%(0)',.$%2('*1,0*/##3)1)(2'%:-'/3/"%-,%3+*&&,,(32/&.#,#/?05 65,;*.(/"&+%-5/%2$1&$$33+)))+(,04%$/3#.31033*&1,4+&!)21&)3"' ../%,.+&+'.0-9&*0%1+"3)65)/,+%1+2#4-+)#-)/#,74'*020.+24*341'/($#$) 4'1(&- ,$%28$++"'23.*"21,$"2++(2%),3(%+/"+%0-7,&0+%.*8,;+)*1')(%,&:*%,'"*),3,$%31-3.+006/3*".*-'+&+25&,(/,25%,,+"&2!""4(%+55//"0)%$(0 .0'*))$07%(/63=+(+18-/59#+$-+5#&+-0-6("&(-=$Ӏ׀ՀԀۀڀـԀ׀ŀـ׀ۀـۀԀ؀ۀրڀр׀րՁ:28=!!;/*20&61%414(8 (*8=(%0*-921249)$0= (+!(%8#+&2+*""6&'#+(03(/ %5&!1//.)/"'&+&219//2*%%&$';!78%/<2'*,%&+.)),&")/$#-$$2.)/4'&)7)836(''(-&++*,)%(2&5*0'+9'*+#3(4((-,$'--*+*&'"6,!-,%+0"3/-3 -,33-::*53-&#(1%%+'+*&+4/,*'. '/(&+)+1%%95(*!-!"8/ --203"&(.)#%;.714:;2'$-4F1.+=(.2;-*012*+*, 3-9!4))=#&5*01'-4 )'/-*0&'+*.+*"01413"/&*+-:+&;0,%%/-+5*'/-25& /3;$%$301/3!0*2(*'55-2**(6/6215*'$*290''3$)."3(+((/ /0:*+?$$)=)<-+%,3'-/)2+0-#6+&'#$"%7,".:0*/-,%6*#)/*00%.*/,/(-.4"177%():!/,'//231/&,2+//:$/)7+&Ӏ؀ـЀҀԀրӀՀǀӀՀ؀Հ؀ЀՀրӀӀ܀ЀՀҁ/5-5351,.92*)+,%0-:.%3,1(#)1.(/.'"/()/$*3),1.1$-*:-+10-8,)-1%1023.+/-0*./<,",)'%./20./22+'1//) )&874:-2(1#3,'1-#*"*#+-!!'.&1215-2:(2$+ 7'.*1*/'&0$7,*!.);84"""-*(11-2-1.'1!"48'0! 64*)1#3/$5-(#!$-%92,%(0 ($.!"0'+'.%4+))',,-*!0)'13/342+9):',21$/63!&7$'-'1,4177+2/$:+)-2/'0;"5++5%%'#$/")(&%,);-3 )%3-*%.%-%"#4)*)*-(%)',-'2?)+&2%/-,,390&!1!&)((187. 10*/;6(00(%$-5+/.00)"-,+/@,)("+3.'(3%,9$*//<$*+."3++3!0*'-0&.8+13-''1%!' %-'7/2.5"0*"/+5%%-&*'2+5-5-210-6@")5*7&3.8+7+'7'5&7+$9-).6'( +@2-'+ۀӀπ׀׀Ӏـ׀ـŀр؀׀ՀҀ؀Ԁ׀ՀՀրՀ܀Ӂ8*5/%52'*-41%0!-+2&!3++'$4//#)4).+7&*+31*1'0*+1&/#043//)0 5&/-/*/81(/2""7&4'703*41*0.))/!).(//#)24((/10 3((0<++:.>1,3+7'81"+4(''3%0/ 0,',)"+&3$$#6*(9!""(.&"0$/'-////&/2')/5%51!&"-#5&.&"&2$:7&"&+&+()7""2*"(+''068$"1<*#2/.+>0)-)()!+0)%+1)3)+%.(43/$)2#.&6)8'-'91).*/()"2''-*4&/+,"3+$)).)70)"0%29%84(.(/+'+45,)(,(1&+/*:**)7.7+)+4+34/3,1+,46#'&.*) 31?)-)./3%2.+,'.3$+%05)/.(,'14(4+/-6%:.175'/0.!4*/7/+)(2%2)('<9-+*+/*%9)-%82,2&+.*!++5 "&8&>/'9)5/"'/&),8*/#'-81:+/$/)1"-71/'--#+'+-*.(14212%14):&/+)2ր׀Հ܀րՀՀ؀рÀրӀ׀Ӏ׀Հ׀׀ۀ؀Ԁ܀րׁ,7#4-1)-?"80/$!.<27#(/!0%+)-88=1#54-33$;8./;4+0'--3011232"#$-1&76/,..853!%%-75( /+6 ,/1$.5*//"-586+.0"2"5%16#)0#,-!910'-20*$3%+,)/2+&&4+5/&3,,'4=1!"/&294++(*/!.'0"+"/4(17'3$)"(6(.!$$&2-)%-.$+)(04)*10*5+!#&-'&.$%/.&1205(78*2..5-2+%1&"'9'+53(/;',..-6/+3&8"7',$(0*-%76(,-651//1+/--)-%2201*4-+/4/(+&8'%0,)2/-',404.$29,':8)/*-%.4(,+3(1+#'$*&$:/+"%1.$+34(/70/1%*)#6"101206"2.:=$-'+'47+*30708%5-&!+"2)&)3%-25)/&!).1++4-& 5"4*B+/;35+'2)/(1()?*((/,6'.7>87)18*/2*1/03# $/*.4,041.9+,3)110-+7&*6.5700$&ڀ׀ՀӀՀՀҀ܀ӀȀր؀ـՀ׀րՀـ׀ҀҀۀԀՁ.+56#+/0,!-./!'6-%0'/9!7&2-'1/0%/7)/+ 1%1#-'4.+.)4/!6/8$.3 ,;/(#'3/-*510(1,*(.%6046*:)&5,)+++8(6");*4-/*)&+#.2$/,0*2((&-6#5)65*!#+-+"#!6(,%'.40@,1%+)3/-:.(&%0%.;.+%%/&$&,0)>).33+*,-.#*(&"(+(4+0"(!(**-5;*0#*,) 4-'.0%#%"19-*+($22*5(:)(*)03)5)##$1.+2-,;%."-0)52'2$6*&>!=0;%7)),6&,/3+11(02")"&6/9&%-.-(25,8&3/+-53*+'8+).)4*(11:2.(&'5*28 .&&'$-4.*$ <'*+'/A8+/#"1)'2572*'+-.7054'*241/"=21,+30'8!**'%*.+57/.1!,)&):-/%/(*(-*7"#9+/+5!--,7./29&0*5%4#%2/)*-)/+-(3/86>5.6-)42;0*$0 3:*-/&'/)2+3267܀րڀӀ؀׀ـ׀ӀǀրـۀԀـ׀ӀӀҀ׀ۀـ׀ف!6(/#,8':/(+(5).&/>53+*B?&,0*!8&-,.*%-$3*&'17/.+))$,,"*#-&3(-#2&22()/1180#(.)2 /0--..'%((C7-#+5-?)1')9 %*+*22$/+2;.3"$!-*+0*5$(7,$:4/',B2*7'2)))54)" ,1.%$$'7/22-(!,5+3 4+2/*7213)'0++ (+-''(.,(01'''17)+./%*&/&/)+9)5-.+."$%5.9&-=-0/-)*./**1#(1("D1#-.1*6#3.0$-3.++)"6 $1#-*-4A(5.-$%1/-*&+4''+-8*(8.+/6"6!:%44&2"3($:3 *,+(2/-*(#-&&&.-.(6*'6)0-"-(15%%4'3-!0,-))3-(,!683$%3$)$#=+1%&7285(3'%++00?+*.)5.,'//6'((0,&(./,06-5-96=)0.%*2)311.$-,),),'/+,0%+!3%0+,!%)2%4=,//73344"0,4//,+/%''#7*&6& $);2Հ؀ڀր׀ԀڀրـʀӀՀ؀؀׀րӀЀ׀؀Հ׀Ԁׁ8'7,*70*5)253,+0+A0-,*:+$-!5'&7:-,%75'+/.%//)26($)0 $5/3'4#!?3-&+-+..2 1(/%3.30+052-"2#&05/(&,2+*$*./+'),.((,)63//43%..7-(350 ,',$!+1,,-+6*,,$4*+1-3.('#12#0403/!2#(,)-'+-).'4% */$&(()2.'"0-!'%&-1)+)7+.'"+)-!4#.+0#$)4$+'/()16?%+*03'4*.)+*008%/--,,7+19'20-0:$/-+(.1!.2$+8+7#!&-2.;%2'3 #(8*--+-1*//0*-+2;-"!246+0-12$ 1,3'4+'0*./03%1$10(09$-1)-3147()-.0/",&2*,(<1)0%25.83.)C622+133!/-3(--0(/3.2&3&,&'+)&8-,972(71)60)&*40(06)*."+5'8'"$%+++20./ *++%2/%+),&%)-(,3 -,.(4,*+24%#0(-03#,0/0!1%",!0 /8),!*&+9,$''60''9+3--1&?,0-&4/0$/'.=.&5)+/(-!8.)+ -;#&*,1,-+"&05))+/8(#416?(4 713&!2"')'&#+"+A3/-*15/81/*#*:'++/"(.+3,,8*.)',-)&)'/,, +84/13('-0/11,4&2+93#2//(,'?/-6%&0'06=+714,%.-.3+6:(2,%':5'5(##5)+0(,020,4")$)1+/+,.(5(/π؀րЀ݀ՀҀրӀǀԀӀ׀ҀՀـ׀Ӏրـր܀րہ1<-3=$+15323/.62&)3-/)2>8-!.1*#5(933:2.!1*32"(/(:2".%(!)(+'.-7<)(.511,424%).#3+*%/;5",!)&%//'!%'/+5845$(&0&))9(9+70+'('#) /,'3&25/%,))5/*."/-'4(!/72<%/-&#)17*++&/,&",83/,,%%+4&,)3*0/!4./..(1!#)-3*-'(("*+(%+.0-1,%%+7/(3(-.5"!3#%%0*33+5()(50*%9)8 /(''*.:05(C/1/+,#721,((+*0'/1*.(%+0'-<24%*1*2(70-/$4')20**'/'2/)(05',(7 4"04+5+"(1#$'#'(+<.0,4-%++60*#3,+* (8%$*,%-03(/(*+/8%5&+''/'*+0.'12)+/%',"=*7*+,2)"%2*19&0&)&)/-*9$-+*%+ *64#1(23,+./).%/,<#7(&',".'):-$-!-/27$- -*"3-54%+7%#00.,')1)+(,39//-85)ՀӀրҀ׀ـ؀ՀՀƀԀӀՀۀԀҀӀՀ؀ڀ׀ـӀց1'#,-*,+/20.)-59)/%%,%5(!0'/(%3)(=$17459/18-(/8;)&2(+#6&58.0&+4(*$(-/.1&. +'34*!&7(*"5+.&+3$)$+#$2+.- #45%44'++$'+$/70-%(2<+,&&,:)/00!1/(5))*4"0.-,11115/2,++*.!/9+!<-*.(&,'1.27++)0),/)*093,4$-($-1,1&&&*75./'&+&.'++'5"$0:.0),''/12(+2+,/5&1%$,/+()0+/1414,"45 "4,),.1%'-*/"03(,+$1+(:"&##'7)93:+632$$0:.6%%16#9(2' &323"--&3,4/.1'6)#',%2'1.(39,"(32)')- 62-:#+(0)7910-*'/!=+E)14.-,)0:%+2*5,%.+$.*%10'$4/)('.-.&&-*5'"=*)6+!6*''6)*4<(,/$)/,0)+71.,#-2,&%$$19))/$.#-;.(;06/-#3*0!*)(1 +0*:-( 0 /؀ڀӀՀր؀ՀۀـɀӀ׀ՀӀրрԀҀــր؀׀݁02+,.6/!'65.)%:++0./0'34.-(4.83/'*/522/$+%*+'4+21-+% 1$7%32)(&'70&(63"%-*-)"$3,:(4*2#(-7/!#*.-".0,1* *!+)$32*.(41-/%10/#,&+)+)/,+.#*/8!:/1'.4%/5+*(*4"%*2$-*%,&/%*;*6+#)((&7@013!1/!,66"4<'&*(40'*+0%/"2&%*8,2')%5-89+A"+*/)/7&129&5- &%80,D,032-'(+'2%37"/+518./25.30;%!(*)*016.&::4/03@:*/.'//+21&0+'+--,1#.!.(6+6%-.'(=*&,2/%1)!&&4@41$))&/3120*%'/*(6(0108',/)''++/'">/.;1%2(*0%*.+5+''%'!&)!)88/-$2"-#.''$6.2"-)!/&-"6%4,1) 2&%0*0/.;*-2'51.-*'))0/(+'3"+'7$,.1)00'23)-(!2.)'(31(;6."8%2!&)'2!29)(49)Ӏ؀րـՀր׀؀րɀԀ؀Ӏــ܀ـրՀــӀՀҁ>?)00)2 6-+=37+&&//(#:+( 1-,$/#-)1.'$>7$5../0*.#%8,)1##1*20,'#/0+302(66-.6*!..-&,%):.-"/-0.('$(4#0-+*+(=91$ )6=0+4'5,3%.#2((%'"%+'$'.6.5!+///+'4%))(+,1-$8/"3*.)'#)%2'',$/-+ 3, -!1.("".9++-"!!>!.)5/%&%)'/$7-1&31(,)2)-&&0,2+,)4++-)++2,)2.23/#-/+0-$#C,31*08/(1.*-/./+)+.55//(1/:.'( ( ;3'41(.,2%8*/'/9((((+),)%+**/7*/),*,5/'&,*43009;(&-0$0+/%#4&/-. ./-(7+2,&+6)2 0")),-/-/1(2&"%+"'%,%,9"9<+(.+2,1&,(+%*2,./4+)*3+-6/#3,2.$$!-5.3,2+,)71,+/&403$(&$",$-?%)!+#'1$31 +#6&$/"*)(7-<'!/#20/2+-,/3)<3)2րՀۀҀ׀ӀӀӀҀ€ԀԀ΀׀ҀԀҀ݀րԀр؀؀ց2.-(9#%/#+7*/.8/ /$"16''/019+08(##@6%'27'1,//2)1&(&3(&3'5#$64.4=)(-;#)*2.%&3.6-&7/,-8#,1.$*2* 0" "5?,,<--(/'7-+)(=0-3-)')0 -'1B:*'1+))$02+484,,",(1!,#62-<(/*0/)$163./(&)4,62.3&*%)(/2':36$4++",-/)?G).562',1/+*#=03/'+/'*(3/23(:%-210*6%,,*):)B.//0--/83$(%&04*%0&%(,++("%046)&+*/3'+*&$.2%,-:+ ;+%7#-'0"#-1*(-.:.,.3/).&3'3)&+7'($$'51C%1&6%'*,'9,7',.203*/)+"7'(/-3$25"2+'("2'2)$.!&(+/+&1,+.,+!* (',-(**7/ &,'(+'(-.+'11'),6!.+#,!4/&)"*9/1/'(,"#7*/%$1./%"1/./$97%&?='3#/392 '2<"%000&75#(+3&6#2,(*7.9-"&-)54),*+(511@-+&".*2-)4(6.//-&57,3+2,+0.,1+0(7*+45'()2+&)1'44: +, ,/52,' /;-//1:./)77,-3)*;')+4)4.2%/+*0+1)+-)-'*./-265.3-,0-5+$$*6)# 6#(.#(6$'3'&+1&+%-:60$9*$(&4-9',%((1-/*'4/9+.%(.%%0,"54$%-.)(0'7,,/*41(4*%.#+):9,6320*.7.8/+.8%%:2++'.#&,+4(:*5*371/2)1.7-%93(/%0$--+2%#/'&4,!.#,%%-!+72.,)2$(0)292('(.'%32!7)221*223.7)-1%."1 .,,")-)/.%3(1&-1./%(*&/-&.90,)7-!$1%4)/,00(8*)#/"4,-45/%.(6#(,,++"/4,-()-/0*&,"-1'2*'/C.10(17)567.-ҀՀـՀրـҀ؀ՀŀҀрԀրـր؀׀ڀҀԀԀҀׁ-'*#0(=*00%3)-A*43"54721+21.-4+6'8-*,//!3,-''5/-3 $$,$7*0<10..>",-.'/++/6)*,"+*/1,18*.02&3,2/*%)%(A!(+*3$-85=0.)%1%'#24)5//!3-9)(-*/*/3'+*2!?/7*&,/11*.'+$4!654555.5(+'14).$$8)&0:*38%5,.3,&:-,,/)&&(!$*97*/-$38.8$%$.5&+/*$),/&/4*2:3-*&+'"322-0.)-4-1*4/#,;$,7 -!4*,*6!.).&$6%.!6***)1/0**)/%03)5%-%(-74/#+.+%'.21,.4/!('8$0/-10!/00>4./"/$.'?1/,&;!+'3",/'/6/20./'%2(5+&$(+ ),)!4."5$,,,$.71%4+,6%17*#//'&+*')+)!$,)1(+/#.-9-1+0)'%- 7(#.2(3*)&!$.%//,&+1*4(#3(&;'5#!*80(54-+..0+%(--8@*051>01%,.׀׀ۀрՀрۀԀ׀ŀ׀Ҁ܀ՀЀԀրЀ؀؀ՀրՀց/+9)/&&<)-&330>(-7 '+#,('/3(:&1.7)/#))/!(2%1'$.+*47(1(9/)&$- (-;#5'35.51&-&+*/>*4&307/@0.(2*+6)(1'82%,"(1#%-*))62#0(2>9(.8--"&/+(,/74+3++(*.0#.>1&79%)730;#/,2%$#+&+$#*9$7,,*,-4+*6.3).(0#72'%%-,*'.?'6;1*4'&,.6*.+41&,+%0+%(%+/31-8:&$3#207-0+#*,(6%$$+56"-2?+/*6(("$?48:72%-&*!3<5"-3/,9$/20'8)+(',0*3+>$,0#)11%!243<2-:.*,9/.-%-"$2-043$2336!-#"+$*30-*0-- 1.&*#/-)((+4$&)=1/-& 2$4'+&)-""5*,$7/"*!:3/-)59!-2*2,/'00)$=*'+&'.(.,('2:70.)#//92.'$/+.('&:33+0&-/:+2(/','0=5&&82+)0,15.#//.//2:0+0,++/&0.؀ڀڀڀ׀ـրـӀƀр؀Ԁ݀ڀԀՀـۀــ؀ڀց(:,%-&+2%%7//2+15&5+12+&!*%7/2-*)#)4.*-*$823"7/%))!-2%4/"-030"''60,++%"5(,'57$#) (22/%)-0,,5).4%%/&/)'-'*(1'3/*5+-1/+$'-%#/(%,1'%'#79.1-;,+*$)+?&'.)+.)*+/;)1*+)!-*4"62#.&(#%5$-/-)*82*/:2z *#:.)=#%)&$%*$(*&(0(.#'/126*,9/)1),)/,*,//<)',.'% +27+-4#1.*(+;+5$'+//*4+2)/-+>*/+-#14,0,$0-2.-04-(/,+.'*.4910!5'#*1)'8:4%,)1),').1*%!+-0113,'3(;$,* /$2./+4-',+.3 3+0,+5+44+@*1,(<#/#+&/420/59'- .%603-0'+'2:*$6/.*20+75#.29*7(%-%+(21$;.().*.$4,.01(A%/-"-),-/-"-*/2.(2%(+,,'&")-0+ .*0/3+,0B/-܀ۀҀ׀ڀҀրՀ׀ʀ؀׀Ԁ؀؀Ԁր׀ҀڀЀӀ؀Ձ-)4 7&-02$&.'',-2&/')5,,9++171@+.-'3228-/+,+',43,&7*,!*/1758.0*5.)22-(0%/5*!/) +082%+*.1-':.&,0$.%66,3-+7;3,*0/./*!/#"*8+,+;2)25,1!(. !*%*/01-3$/&/(/!/7A*4*+(($42,7$**% +7)1%# *4,%1+--*.%'+,3"&/0&+5(1)1/''(*0+35/ ..++6+,--%,88*/,,)<3$5#)/1)-,(/1-0",12+1,('*(5*.+'3.,0@<#)+0)3%33)20/.%&2073*!'&)*)(-&''*-+3 3+).-@..&.%'&(44*)5+--+)5!+)-1(*"3&*.4".*1.)6"4%25*%*#(*'8.%321&))"-5'42(410%$)*3#*%*&$5(&&3*&4'++2.# %=)&1)*;%&27+2+/&2://%&-%6*+))-%:(%!2'$8;52"('.3(/"+1))9+)0$2(*5+.+'0..!1#'7,,2&/3+ҀӀ؀ԀրԀ؀Ӏ؀ɀрրҀ׀Ҁ׀ـ׀݀ـՀ؀րӁ2&7(43&2/*-1041''=32)26+"4.///./0:/-,$/5,/7$3((,0'/13'63/$#%$%1&5!)#2/(4&/!01%0&%","=-&0-&!(1!##'$),20.36')*$ 3/5*406,3-8#-."*/*#*,'0%. 73%-&!+91/%"*(24+,02#&/$*&,%6'.'4-+0-'/+2/.'$+24*//; $3"&)0,#-..7)/)*9&"0%;1=?+3(!15,-) 7(&4&/$*2$!/52/(&*0+0.2)&0:4**#02$',0*203$%!+,9/%771/")'),+%(*&%*$'#&8*&3141$+-'))/3%(('0-)"154&3')+1/22/%)/)22(2!'=.)1'/*/21-/59#+05'%5--1).4,0+'7,*4!&,/&.-!,/)&"2-411.%9$#+".+'7').561+5*-1,&)?/7-+-#"&4#.*;!10*#32&!58/+-!90("*+08.33#:/>7 3)+/9%0/,)*6,+))/+'$3:$48/# <3!,3150%5;1&,68+#54.2-4$0(+)=)/))&-2-/*$/-.'/,/5-36+,24&.-'/,41*;6,'+2'*+6./&4)&(&)*1/548.)'3&2'**98396(((&.+',744)!"6+220 &-./*-0!$)<,"'/-0>/1*,%1%')5&.$ &0"++7)$%5)#%#%/,7#:1+*2,..*3,&30%1/!(*)2#-1#%$,+.)).8?02 3$,+,*.4)&$23&,**%"&50%)4.40 -&4++41%"-3$*+/./%.8,0+2$(6-3-,,:$2*:('+509-(31(5-1/*4#$4"4&$) $!3,*0%-(3)/*'&*,4+,+432-,90%,;;00-05-<(+8-+4''2&%.5-%2,/)%5&4-(3)4+*+.$1)*)1-2/17/"6( )&/25!,-,:,*/!5)+3! '.5$++*,"14//63))7**2%73%*-1,'-/,+2.ـڀՀԀՀӀԀ׀ӀǀԀ׀ـрۀՀՀր׀ހր׀׀ځ+318+(08)"+$)*"/'0$/.#!+7/.* -3%)"95-"')%-- 7!.02*"+&51+;+(+5=.1.&-*6+,-$9%6(.7/5(.521*+ +0/)+%),%*#.&0,?)7+6<,7%2+0)"1&())!/!5+1'&'(0"4+,3+/.#.%/0+)0/)&,+ ./"2(<&(7/,(').3)(&-'">485))4('4=)"+3$0%%.%.0!%.&03 6'&$$3-(!%6))*"5) 6**/$530,*(5,&+13%+/./;+43%/"+/+-/%,6.03'-12"-")9%!&(/$(,()()1,1,,$,5*0:3/90"0, /"16?'1>.*+,%3*35%1#.$3)*&07%&#+02-'%E,21&)%!*-92(;/( !$0.'//21,+*06"8 ,33*'7')0+3, +')#-45$1,)'+9(3 )+3.//04*/((+.#(5)8-:./)-/!/+/.(/-+-)(,$11:4,,*+%-A,/"50-&///8/:86.$**.1-6621#0-"Ҁ׀ՀӀ׀׀ԀրӀĀҀՀր׀Ҁ؀؀Հр׀ڀՀրׁ,)0'73++&#-,+1*%5://;266++7,),*1/)1#''-*'*#:'.,<4'02."3"$4-C*+3@(# &#:3(&+&3(4'$*91=3/0*+-14(+!%!2.-3(*,88,)<4-./*1-():%%+!1+9'(9&#/-#7;-)(.2.102-5-'))()13/*!/',+(#-,3**"/--,/--3<&11+0&4)6)# (#.+0.!/&)0,%!*'4,!(&.*/003 . .5*611,+6-&&/.-)4#* #$#49*-05:+''0+5/443*%.#!+3,5&%/'*B:13&)<. .50-!#86,0/2&&433",7%)51*3(/,-;1+%)4,%/+/(9,:2/&$*/'!2%0%%&/51+-40--42%(1!'+9="-2>%+0!2"-";(3"#1,(-&5+)7-5!3',0,+-'(4 &73062:44)/=//25#3("*--/$3'&+,,* 40)3/:7/'%*)2)1--.(*52#'8>1-*0+$).+%91/-,3)7-2884$'-ڀـӀր׀р؀؀ր̀؀ـՀрԀ׀ـԀ׀Ҁ׀܀ڀ؁2(%3':6)-,1)4$(-3,2)4.&&4(4-%*7.1-0.3'-+,30/'44.+6&)3#0(6+&2773+)&*,;)#-0'*)/-1,47&&4* /)$.$'*1/7(#+$(5118-!+.3!+,&(".# .'*%()2&$95!',+$-'*3>,86#&1+.'!2(%.!$"-,+')&','"(!"++!',%''-##(#1/*0,,'*7 .'$D0*')2'5(+542":6. ,'/0,A#*$/,,,#8#30.&3017/& ##39+.'39(4+)/13+&'--08--5,-(,-''00(7/.*/22",-,9&!$,+,,1/')0$1+=.0'0+#!.$$-).4<"5/+'#3".,32.*'7,0*'%4 2*(9&*-456'%.,+'-)7#0+"3(++- ..9(.2%5%?2/-#0(*)3%- !&"-;)62&3+*)."%2;':3,5/2(.3#.7-"6)-.2.2/(2/.3,"-<03.'+-'6#,'5+"(2:(-3+.'+/'52&..,7/923-*2:1*%& ۀրՀ׀؀ՀӀҀԀȀڀр؀ՀՀ׀ӀՀ؀Հ׀րԀׁ4"/).)5+/.,-(7*!+23.3,&2%37.6"'+2-'36211/' 0-$02/5&&'-,+/1+$($/9"-',,)%.,/,7,43*=/)/"64.&8-)-%4%(0:.2%0"*362&4+#;*2+26%(+0",)/3)*3*<*5(%26(:'A,+34/"#3;11/+,'../(,4/0*( +(2,/)*&&,!*()...'+ ))/*8, *.*).+'%&21;)+/*&1/41'5',69)7#4(/(*('"$315:0"*%31*243(#2&/!)#//)'.-5 -$ )&'-)1('*+3'%*16,$(0-==3/!.74'0)'1/(&)'-.$5@$)*%0$9./%%).5/)'&0)$$2!7)+':+/ 6*36-*070$+,/(-*6-'''%*(10'%($&+.#,.3)0,39.8/& '-75/20*+$272$&',&1."-)6)-*,80,"7'/772"/.)7/-"=$-&A)-,26(&?2'Ԁր؀ԀԀ؀ЀՀ؀ƀ؀׀؀׀Հ߀րր׀Ҁ؀ЀրՁ)2(/,%2$1(27 1*=)6-6-9+%'1,7#.")1-;?1;%!;3-'()5,25%*:$40&1)*(3%()4)*+ 5 '-+(-)*".)0:*.%1? +(7"$ (#2(+/-%-$.% 2)*2-%5"'&.-/9-(*62%# 3 ,'5+("1))4'0%,0+!0)>-=;0+ 0-';2#(65*$.(*1*(+!+/-+&$)/+,.)'((3(/*-10)/&-.34**0,7-..1,5)&($713,$,+0'+3'()".&)',+/7#.5&*((-.)4 40 *)$&(-(-'.7!9$6("4'&'"34 &/%*)#(10%6*/"&56%-/+%'(1;)))")/1+3-1/%8.$-1'(3*+)-,.#),+/.**5/'#4 *13->+5 (+*-.3';)"%%/()'.,#6+,&>.6!!!$/2/'6%5&579-6)&'$%+#,9(0/'*;.//#;0041&,(-//+'):74&/!-''#+.034%$:!+7*$,*'))1*&33(4-*!(1%4&+++:27&(,, '&#׀܀р׀ـԀ؀Ԁրŀ׀ր܀рـ׀Ԁ׀ـӀڀԀՀց%*'.,4.2A)!+" .1%1-3$/0),+/+$)1=18-2-+++((+&/4)7)2 ,'/+424#*+)#)1%.)%'$/,A51,/-4-%='):)$912);/('!4!3)2+*%3/:)&).+40+4+(113-/;1#7:.#0%$,/,#,++"'+#+,-'-)+8'73"&1'&;*# #%4+,&0%0/)$'-25/.(%),-*+2"+(",0--$<<,,&+6,54$81&"0.$1#+$+1'''503-+'*"6&*,9#%''+5% 4*-8%83#"$/0-8)*'&011*9*>*.-//,+()13++('79=##)'=4'+;-9-70--%%)-+). '(!0'($9:%%+)0)).&,)$$5,1)(/1(#/.520)4%15*A0;4$#/(0-0/+4':#7 '2'0+0.)/5,&&3!&!0%#0339/9::$*,7!.-*/<+&)(7*40$+*'/++)1$5..5+#0-"'$!&)6,%/),5!+5(8/@'*3./1*,521(?-<50,1-+!-4!/.,@*+'@؀рՀۀրրۀ؀րՀ׀ҀҀՀԀـҀڀـҀրՀځ5"&!34"-+*')"95,60)*1'+*!>5#20$-67#-$,..&*3/'%*(+.&9)+3*'(!*+49,-+7*,!3(&020-%!8-,5(5,*%)/0.+(,",+7A-(3-.,-7%++2%0-#4;&.()#1."'1(),)/9*727,-/6'"-$7("/6529*'/$5,*,2!)*5(-=%+/-#,-%(+(-+&%-+ 6-#$2(5&*'4-'5$"-&%%--6+3,#.$"+$,5-96#!/8*4+,)0.482*#-#(()81%,2,*.#!$=*-'.:&,5',#)#(52%&%..&3(6)B*8-4*-/=+!'*1$-/$7*"*#,-3'&++))/$1)0-*'+,.% :'##.(5..0@<3!/-1!+-<'++)1*/&*/0'0220///+++5'(*#)(+!(?0 ,3&(0+)5#3 (+)3$.+'/050(6")&/2*+(0-2*'+1-((4*))0/2-5180'-.$/3$";%<*5%#%*6"0/0,/&!3)1.51!($..'8ۀ؀ԀԀӀԀՀԀՀŀҀր΀ـۀӀԀրԀՀՀـҀց$')03)1-1:,/,8.&36:/0+*;/31*$%4$.')-'$%8$&$'1A/0.23-2/1)(%)&!3(/232)0,,4/.E$).#0),,%,)*'>,*.%&)/+*-0,')%8)+/&,(, ./1,#2* ++3)2)'$62)+0(%)5),.9+5>'//)*.7+'&(6$1/*",0/(*+/;(#'&21)%((,)2)&&,*4/$)?9/# *'22)).//.$.-'2!4&+.(.//%#) "%24,/1/1)) 24.%+(5%++. -#9*+)+4,/*((50;.!+)&#+(-.%)(=,%.' (+2 11370/b1)3932&*#'*11%&),*.)!%#)(,1$."2$%,.),&13+++01//%.)-/2/"0'-)/$81,4(.)33#$5'*!1.,/#4'$($/%67&/#4.1//7. 44+.8)*/2#,3&%))3;730 23/4%*,-'&23."-.*--%;5/,110!1%.4/>9&#:9(3>'&*'//'/1,5)5015-,0'5/-8׀րՀԀр؀ڀۀ׀ŀӀӀЀՀ؀րր׀ۀ׀ԀـӀс'&1$)(4'44%/.% +&(&!-3&/(+(- +#!3(<*52+$*&8/#)*(6-;$3/2%&)3'76%/2&7+)7."(,+*8"*("('.4%%1'-461.()%@:/695((*+*$ $)7-,"#01/53&& 2*+7))..,#.(4(*+787.*--+).#'-),(-". //27*.$'+&.,,/*62(4: +"--)%1>#(17/'7-0(9')34)32=#27/);/( !%0)7+/2+*1,%78.175%4/#!19#,*".1%.".&/-/7,)#1+1.1*'$2(,,5%+)$83)-/%)1*2t&(*-3;)4-6+)5011-&- +*5/:5'*/&,-7,)*/.+/%!",)#)%0%)"&//)*+#40%.#4,(!''%:.4$&+'%$*-.(51./,+50-51,37112-/$'&5)0 )//1>+)"-)).*2;5+.,'39-3/&3&,@-()+251+0()")"-3*-,=.0!3485&'4$.),$0<.)*3,2+*;.3&,7/,(Հ؀߀ՀӀҀ׀ـՀŀ݀׀؀ҀՀ׀Ҁ׀܀Հ؀׀׀ځ4.13:1//'630##7&#*7$.!#++00(+-'+(2/,8,1,2.#4;;6:/.6*0-.(-+60$/%-1-042(7/2)0')('17.--00("/(;$/7 /(*%+(%%5(A3$11-*"17803=/(&4,1"*//.-*2-45(-"%+4'#$520+$'+--('.1'$*.8+-/1'$*5)�+'10,A3)1353!%:)"(/-$*(/:$2(.-4 :0#"*.,&.)!")$&23#6)2&*1622'-.*./$,1)33)2&)#(2,/).-;#)>,*.1252+)+$%/3'+6':"#,5'*&5'"-,#$//+/(%/+07$+,625+'-,()')1+%+-&(+('0&Հ׀րӀՀԀۀԀԀŀԀԀ׀׀ـҀڀրԀۀ׀ҀՀՁ%/-'5??/*'/&)($2#)5&/69++*%4*743**);//9'!0% '%725+32..+.4-1)8 15&)(0&'/%/"**-%==,22,&&./:/./)"*:.)$* 68/$8/&/)*&%*,#*(8(+.5/4)%,,&16&,3&*20)+*))".((+0+ )%-(7/,/**%*8/%,-'>!8"&!+%/"'00+ (/9)")+)/0'+&4/#+10**.,--,,8-,'2)1/,/$/'5*&8#7P&)+0-'+3+++3$0$,%+,,+8+',%,'5/1(-/)+0,+,-**4)$':13*.41*(/ +1),)).+/<:&"&()+.',!13/*,;'.#6.'3'5+"5&40,C5&++2&,12!---2'/2106'(400+,8$)#%5/%3*1&+!1+','6(0-..!%$1!/2,$)5)*<+!+#!))/%'!4+.%)&+2*"'#! % 72&-+3&(20 (-6(,(33#"64261',,4-5+%1++)-->(72-%;6()27-';56&%334#02.81.Ԁ׀Ӏ׀ՀӀրӀ؀ˀԀ׀׀р׀؀ԀҀۀ܀Ӏ؀׀Ӂ&*$F+944>"35.$+73,-2+3%5&<6+-)..*("',4(333.*6#!/4.&(3**55#/:)3+-'&(*/#:*0>$5+&+'(2,1-*%7(.(+)-3+&%/133(2$/)*/**3).+)-79#*%.14%5*6%?-0;0.-,'2,(.$2(,).5,-3%%)1+*+"32(.)53C+(4/)(!%04.+8,#(.2*>,05-!-;4--1+,4#(2).6:,)-)9--%'.-*/'#*&2(*.%)*/3**91&833=.11)'020464324,-"1%)006'38J.,?//.7&*12&-//-2+423@,-40'8?-(.+/'1/&4.!%1-./"* -0'&+2*'(8%,9.(439/5&&+,/2%:0/ 3:*0-,#/606-)#-++%/$&$43%&--/5(-(*/,%32//)$*%2.,$(2*1.1"&3&3,)+1.))*,(#03*2&)6,+.1<8+.)3"#:*8&,)+*2)// /0(('07'33,"749-#3+*)&1ހրـրڀԀր؀׀ŀ؀րրӀ׀ۀ؀؀πـ؀ր؀Ӂ',/'6997%$*/,(?$.$.'#/-5*:!:#67(0+&%/%37')3/((048)+31%+#./')-,8*-*8!41+4',1832,-.!0(0&781#++'7&-#(+&-*/6/0,!-/(%C+"5-'4(32$+)0-9&-$//#%3(*$&!'+*3&5/4)%,+(-/,.,-215!8.-//00+(!$/1+'0)(7!$2%;&6%9%,(#3#-!",8+&'/,02)+,"+'#,/(.,)/2*+1/5='':-0,)/2*)!'6)9/4&4&/-3*2%#.)4$,&'(04./1'+(;1)*7#6*4"+,4-/,%)-/ /" 8/-04*/:"0/=,2,-(82"2'9''$.8":+62=#&/,3/'&/2':)&!**,,"4'/:0+230&9&/",7($.$'.05.),1,1+/'(#/*=(/25)1(17(")4+*#0+4%*-+2-+))$+2='!,.+8,.035>#- 3);2$9#(93.;&"0,-),-)))1,;8!/.(58* 6,0/*.7#)'../$6'Հـ׀Ӏ؀׀׀׀ـƀـՀҀՀЀԀրڀр؀րրԀՁ:,3&/**4,%"/+/1.9,'92'+.92&=)0037'3.F*-.3%2#$-4/74#530 72>)+/>&1%'++6+/"+'("-//",/)7+%#)1''"+9#5-4%+,'(2/'7$-)+++,,.++-7.&(%8$1/'(-* 0'&&;#/$0:%!/,8')1-+(/0+.,'(0))3/,,%?(,)(9'+-/*/"+(07"6)-(:921,&1'3,,;-$.#6.9%"(,+0,983.30-02+#%,%%*>'$-0'/#2''%+)0**(8%'+**$2&,-#2-&71(0$$&5-(/$'1&3&3.8/&/3+%!*0#3)46<&-*3#$-6/('+;+*51(/*3)+0(/&5-(1/'8%)>2)1821%/"3..+(/1/2+3/!907(/'70#?,'38.218I.2)4%&*=/.'07!-'5# &((*./22;?+&1'(+%)''",+420*08/(,.5#(7#*$% !/8.+2.*5(,(-(!-&*004%7&7%&#)2/(-'*5'/,,--;)+30"&,+'*)9&0,//*'!0)8!#+5&4-)01/)(-#/04'/2+%5,8,(9)'%,0'1///,.//,..*.!;- 22'+*7/:*1",ՀՀـ׀܀ӀրҀӀƀ׀ۀՀ؀ڀ׀ҀՀՀՀـՀՀՁ<#*%3)%+1**%.,'1)*,$-/'")///##%+,-(*18*$'1!3+0*$+8 *,+-.5#.2&='65.9'1:.*+'=0$-+/1'&/,)4:$ 2,,5(--.2/51%%)$(/(1.+!&3587%'+*215&!#45'*.$4-!(+)+'1%32,+8))$5$'')+-8-($4%.72',6*),!$%%/%2$4(2'0,-1-1)2+)!1.$/05%-*03,-,&"%'#'"-'(&)/4$711'&4/!//51&*245)3" ' )&.*5%3.>5(9 5//.710,$/+4"5)$$.6(+.&%#:)(&.)2('$3(,2%7)+)&57&%3('='4.13&(+%7(.7!+/1&4-..)$ (C,-)*3%./630-$'"%.).0/0*4*1C-,(-5 %,1+">*(0*63$(-',4/!)3*)%31$6(/.5*/)8%/'43,/2--.3/"1$$0)'-/1+3*.-4$3+)"&6#'%.+"-9*+3(9450+'/0/0!/*1<&+1*0)&-'-׀ـۀڀ؀ـ؀ҀـȀԀ׀Հр؀ՀրԀ׀ԀڀՀ؀Ձ1',.9;-),,$-48&8/%/&1:82'4.>(035+0/+,A(4#+(/',--5-)/03$&-)A$"))02!#+/**%(3/-#4(9/*75/).9#1/551,$-04).).7&,-.)+$&)&?,/0*'<;>5&%,$&0+($+:%;0.7'+8+%*6-2!%2-5-*3,333$"+784+*&)/ ,,,0/,) -)#,%:/,,/12*/3/&8)%,#-&(=,*@+%!),)&+1/+)2/'# 0%!$/,+-.3#,$&)0/210.,!#&&'#' 4%(*)4%'52(" 36<1)"+,*0*:(-!!(04('2);)--'2220.-''"0/3%-)45'0//=+'5(5<)%)$)*,.5+;':-*-0,4840,2,042.47/1 /6-43/*/0"-#)1%+'/",$))/)#*1&+,5( +('8*.(*4')07#,(+!*89%((/+,'-0 *++$0",=2#%4)5"%%0,8.5//+(/*()&,/+&'1+(,#,6+B+/14+/0:5'ӀـրՀ؀ՀՀ׀πƀ܀րҀڀր׀ӀՀ׀Հ؀Ѐ׀ԁ)9411./*2*-"10-7'"<@)B554)1*6-=/&*(+&/';%.*2*!)&+)*-+0&-*-43602,22'-.!',"-( 745'4!***&,/!'.3&)/(%+')4#.((/-+(-8&0/3(1!$*%&8"+'-'$-2,$(+-2.1)-#4,&"085. "' 2'44.=)-%(-&&)'+(%,#0310)1+30$'0,$-+',6+*!,')-" ().3,!($ %<7.25.)&%(&.!+5*(($+)-)06//+8#((&6)-*/+!'A)-%-!2%'';&,4)%+15=.'-+-'**1!#,3 5&/7,-+"+.4$+$-!'30#)2'20,/+;/-(1'+28'6&7)/%.,5-#/-,),1:*3-<--)% +2//4-*+3&4'"3:),30)/4514)#,-+#*15,!$0./., /$4*9+1222&4(3+%.**/1!,0+57"'2(6)*).'""$5/53%**')(-2/9).57-9@.4/.+,-*<-7*(%:%9$6#0<7-/#+,);092рՀрԀڀڀۀҀրƀ׀րԀ׀ր׀ڀրՀڀр؀ՀՁ#72'>C'120%++>+ 9+'-.-"2(8.-)542-%//,2,1#*.#6*,5)+-1/2%(%9".-1/-)7&/5(!26-**////(26.)5)-(500+-4-4"0,'-",(2)%(-2-"/-'.8').1%,$+)..2/3"&%')%4.#%&/73*8.* 1/.6(1/()+#))**3+/)1&61.984229*4%'.++)'+,.!+).,*4&/+1,*/0(!.++1$#&1,4++,/*%%/1'3,3).4>-/!-.&=6+1/.4($3<)6 ,:2,)"-%&+4-/*$$&5(+.!+&4 .&(427.)4"2=6%.0./,!114+*6*(5#(-6-(.3++%)'))/;,4!'2/721'3:/"%)"/7$-6; <)!/+%1*4*5!4+&$**52-.)%%+37*-7',&-.++,(4(-&0)*.3,+:%%,*((26,#/+!&/+36%*1,,%(%"7#C///-2*72550%6'3#/.)/24%*63,+3;+-014!2<7('.,806'-+*/9/-ӀրՀԀր؀ڀԀڀǀրԀՀҀӀ׀ր݀ـ؀׀܀׀Ձ.8'85:67#4$4/;0.#:%&)3;.< +5'70-'-3/& *@+-,/-!!/'-.0-+*'&/&4,--.4''$++%',%1&"*0(.06+".*,0,."2&*(<5%$<%&.5%*##'2-,-%(0/(:9/+)9))=.0002*5.+0%$1)5'8#3,/--7F/).0'7*6($0//316,")-%%44+-(02)&/*)$-$/5"1,.,6-324*;+%3*'(4'0/*?'.- 314/03+9*+.%.'0,?/#'++1<1,5.&2//-.-3+))-'.+,5&.%,2-/5!)+5A?48+/&%<)):+(.)75-1.'--(###9)1+2*.$40,+'7"/+,-+$/5+1+*/#6( '5:7#+-@$,'#.))./3/+++2!(3".%$ )).'1513/*3)%1+3,;.(-31',$**/))4,3)/*0?4:24'*,5-1/50,,,+<(7--/*,+.*,&618103)+&7$:83#,.&425+#1%-.+1,,'0++6?00.1+16)00-/,"*7+;03$1(Ӏր؀ՀـЀԀڀՀˀڀր؀ր׀π؀؀Հ׀Ԁ؀׀ց.-'+3-6.)%1%)!13//,/-)1+2,'*.02,('"3+2'812$4+)'+,+*3&-11)+*+'6;#(<2/%(*),.0.,"+15411-(*)1/ !-)#%1,$'*%-9&1,((292// 7"%32#+/:-.&8+'!(*+5*$4(%((-!%)*5!-892-3%-4 5*#90-73)318.-!$0',-1,9--*6*(&/2&.& !)1%)1&$,**%-/-0*'48%@)-43..4%?-0,'$-;&*206+206%"68%.' +,-4+''204*$.8'+&(/)/*5/'11 &(--4A3(!C('=+.0198,7'3*)4'562(33%* 2#9.(5)0-&0/ 8":,$/&&$313-+)'4,"+)%,&-4&/7-);*$00 )3&/63*0/10(,20!2+#"5:+203/2:(/*/4)+5&&33,!)1,2/;04#6,1*&'/3'5&3092&7,!)*9'11*,/0/+4%:'&0#-(70:3(.,)#".'!/," ,=*9.,:7-,1$5>,4׀ЀրҀՀԀҀրրɀЀӀـԀ׀׀ـրрՀӀԀրՁ<,)36,.+(@-77#,+%4%*)$)%/'?.%3=2+,##+)=+%+3'/1%0-.3!42+#.-(=3-'96-2-5%$75<1-+(6&*B3",:/(!)*(0*,1#+/.&,2151<)0%!*-3).&*/% .5)*(//*2+-1/7'(-%.$69!--2.+!/*5$0)(*0*-+**1**)%(*!$2!5,--.&&%*63+%&,).$&&100-'1*./*4+:++8,33%2356$&3$1+2-%,/%#0,31+!6*4+(/);/$/'%,'%2,3,*!.-%2+#./1*,/*-#!-+($,1'&+54-/8'6*+)*("%3.1*("%65'++&1'70/7($#+)4&!0+*0/1(1(%#1)+0&;'0(<+'1.;'/(/$0('1.(0/2.)(.#0(-.'0.,63%!+&-1-7'1%:#*0>#/'++3C+$0-3*/,7"0,($40$0/."**%#,-(5!#7,2**$&3%30(%%&:.-*5"$6B),6720(.%#22+2)<)-.97,=-.*/3;57׀߀׀׀ڀҀڀԀՀȀր؀ـۀЀ׀܀րՀ؀ڀۀ׀؁/-+"'+0+5(;3)+0')81+'06('&23,4433/8/*,.-3'-+20"%&)+-'-'.$#.3,)6&1+,&4(58,2%65'3/'+)02"()),/%#3'+=,%4&&+"%117),*($$)+%12*0247#//)*-(01)# 7G*/(,9%-$&:-4.8)%9%*()77'555)3+/%$%>+=$$./!6()2/.4#*-6'), ,0*+3-- *3*62302,1',#,.,+1+43@*/+3&&,5(02260-$05")+(4+*))A$5"6",9++.+)'*)%&651))4$/2((".+31-(3&07*$**&",<)-4.+:#$'%%7'!5? +,,.+&,%,-%(4%+037+2.-&-54'4$ 3473'+.)%='3"--.<23.+.'- &'6(*)*&3&(/&&*12'9'26&/&59.5/.52*)&+'%20)!$&!03'-1#=822+54-*7:&)(*'/0"+;*-))(16(-6($&4*)+37)*/''7)23.<-/.(50*7'84(*+-&ҀրՀր׀Ԁڀ׀ҀπӀҀӀրՀڀ׀׀Հ؀׀ԀҁB2'#4)1/$&"8.5'3/(/& 0!..!1+5/2.&&.'#121*+2&+)6,//)/(7&6$+/4(%56.+34-08)5.7))-/*=*3('50(/*&42!//%$'#0$%8#3)30)1!6/(1"-+&'(+*2,66+1*,&,(,04'&+'/%4'/27'&&2$-(+6!3.'/!+='$'%&(8/,*4+&-9#&'1-*%+&,1,#1*(;4$ .%3+*&*"&(#/##.%5*0!-%2 )0!!' 5+/#&- 2%11(6'0)-'+)&#./$"(-.**524-$8%1&*@,4/#,*37*-1*$,')%)/))/ B5/*//%5"+56$.'00+( 37+4%)$7/*#)!0*8#)%2-".0"9$#)7(18.7/-')+4#/9&2-527'+(-(!!909+(-).-+2!)02/'%/560'*)2%0*?9-2:3-:'4*)-A*.0-02-)'1.-.02/++(>',1/+#-*621629%)"*21:(7)*!,*1+,$55;:./*1*1+7++/03$*:.28."0րۀ؀׀ҀрـԀրрԀՀ؀Ԁր׀ԀҀԀ΀րՀՁ)).1304-?.63(-2-/8+9++-'30&)4).1()%6.37303026=+(%/5/<2/7'+0,.'#)615,.(5)!'/=((*)*, 0)'$5*$$3-)%),4+,82/430!.,23**4'.2-%4$5 ',.2+:97#3'.) 1,$-'.6,5!*.,+)(+2$/0,#+.3$#,"3+$'/+A626!27,,6"$$&,--/! *3!#;)4/#(*0/%=*1,',.9#%//2.((*41+'!.7'&1.*)//>'%%" 4$"+&$)(.&)/"+()-0%0'+/4$ ).=).-/4;+)'$%2!)4'(( ,+029")(/4 *7+;7-')*'1.&!%*011**'-2/2#2)!(%&,,#-*%/1,'/:/10*"/4(/3,%*9"22),1454/---!010*/+.50'.5%-:(2*+6+/+&57-/&-*)(-):%-&-$/#-/. '.2%0. .$%)#*-5+;;#/43)7& &,)6*)'4'/(%+//2$28/<20'*'.9/+30Ѐр׀րր׀ԀՀӀˀڀӀԀрҀـ׀ӀڀՀրԀڀׁ/#+.,8:.5)5/1812,*.'2.00.*1++!25+#4/.=+$.0+;)(&-(&'%')7%*%60'?2) 1.2+-'%#3!%,%'/0,/#"'*-%.+9#5/0-#*/(3&*),0+/)%-/' 1#/,*+9*),-/&-3$%,41)3<07*"$:.2--6%%'+%.+4'#((&2#/+5*9+1$,%($.&'-%6)*',/6.*,/%"2(/7--*(-5+.(,1+./( =*00799--<*52)7+/%(*#!)87+/1/6+./)74!*5,.,:)"-02*1&?&20#/*1. #/*/9%/*7%0*-.+;-#),/ 2-527+3(3)-3"*4)+.7-*+3&)'&%-1",30$(,//4.-#2%6%!+<&50(.1,%-)!) (/!'$1*2--*/!;1.)))/27+6)2@35 !'0)357,34++(0'"$%7.-*.("-// %/,545*11048#-5&2&#$7!*',(!+4,+(05!0-9/63 =2,7/35/&59-0)22"#73'4:A0(8)')Ԁ׀׀׀Հ؀؀Հрƀ؀׀ՀրԀրր׀ՀՀڀրր؁1,234)'5+,7/3+)-/631,:2(4075*-".0(%2'(1/,#1-():2'+/)'+,*())12'* 0$9((():,-8,+-"041))(4:-*.$"'()"2*7)-1-0).7(((27.".=550,%.#%!/;*+5 &,2(14*($./&&/3#!4%-$13()0+2+-),*10!+&3"+867#2.*90+&%&#*?--('7%2)*(,,&*?/055-,'0>-5#=#5/,.*(/1+"2&0)1,&0*77*7/&.28,)#)",-/1'***/+/-34-0454.5()*#"+9/(/6=.0(6--,(%/0(0* +A&)-'0*).:),!-6*&5+*%,2-*'1&6"!D+(+(9:"#-8'+/".)3/,4(4*<&5#$2)%+4--1-<)).//'+254#$#$/7+-6))3!/),55/1)".%#(*0/)4.2%)-/6+%!,*,4)$)/)015)# /&*2)*-,(2'1*.)+////3/1/1&1/**54 7:#+"8+%578-24%,;37)6׀ԀҀՀӀրـ׀Ҁ€րՀـЀՀ܀Ҁ׀Ԁـ؀πրՁ.1-01* .20+6#-(5-+*'1*-1%%?)4+073!*.&2):!:/+/*''.0'(+-:6/-#//3+..$5"*0+/0851/-1,10/*5,'0/--%*3)%0,-(0%'-++08>&+6).6!9+)/*/&#:%(1$+$"/.3/.6*4#%#2#(($'&/! 93/8*#'+)'2&/6@*/&&/)&8/-%(%, (+0 1!0)6.&&$.#+5%/9<+('%+?-(5)/1+-&/.)9&(* ,-,/&!"92,+')%3*8/()*+).*/*3--21,(52"49,.%!413"33/5",/6),5(3..0-'($1,505*4)(3/,$5,%9(&+/23)25*6(00()0(,%)%5/$"0/2-'-*2#5'.(+..6*1,2&).5!.*()('-+)*.+4>-8--0:*-&,5$(%*,,*/!6'++'*4!%&*/'%+5-973',%+)*)+-'7+5.2'/B+&(8*(8*-31.1/2,244*4+*-5(.:!;33/..$-530/8+3#14/568.(1-2؀Ѐ׀ـ܀Ԁր؀׀ŀ׀ր؀؀ԀڀـՀ׀Ԁ׀րҀց*$(3)+1$(2&7$3*2)8/0/0"/6!*10*020&9 -3*-71-0-+-+9.,3).:%26#(91//!'0.4-'(9/%')3%%-%*55*(+#+++'4.*+,("5+*(*.43<1&*-*"-/!/-#/&.5#3*/&031-'3-(13(',!515"*6%0%1"5*/-8.,('4/.+&( .77-2--, 6#,&')0.(.(0/(32(4-'5.*36&7+.$$)"7(1!= 3&.781" $('(-.,2+3$&+/&*(&*!)+. >8<701/$35:,0$#-/*"/,(-(6<22+#**254'448(,*-0(0*4&*/-'+7)=+3%&0&'$2,!3$ -3;)'+,",%131./3(#8/>(/.5//2&1+21963"21'*&Ҁр׀ӀրԀՀـӀÀрڀՀҀ׀Ԁۀր׀Ԁـڀрԁ,>,1*,3+14%/373.) +5%#).7.'46'(():113,$5:*+(4%,0*+"$2*6/2,.2+6/149+82%.1#-!#//0(15#73/(* *8'4//,0!-+,*!*),0&**%+2,4/1"-/#$)4*2(41,#!4/7)2=%7.-,(&.9,7+(60.-202,%.','$4-%#,++.0''5491#&-$()+(/1(%-(3672#*396-526:5$=*(*1,6&/'7%"!*-/,6&+7(*%+$%+6+(;%('.)/2.)-40+:.+#-4%8451;0303)3415" *+/,/"'-5/&('+++$01+(&;,'-./%/,2$=/)4;(3*9,%:%%*333)&%) 8+ .03)!-')) '5<./+'('&4+,'..1/(#4$,?+7((-."+/&3+,1(+',0*+7%*-+$2.6*&+64*(3%/%.('(+&!("0/8/,7)$0.30-63)1#%'5:/.,2%(%1/4*0/1,327.)2;0,,!2,#)6,0+0+3!(=+@62ՀՀπـԀՀπӀր̀Ԁ׀Ҁր׀Հ΀؀Հ݀׀؀Ӏԁ;7/0('-*&)4.,3'%(#(<#723.$+!1*06,4"'1+5:*0(+'-, 12%/8-.%*+.4'&4$/20&63#200'.1D5$)(.1"//+-3%/'.-$.3/&8'6,0! (.4763'!71; 7,(.)$"32(4!**-,*(5%+)&,)*)21"7-%'/(2-.(3-+**-!-#2(.)&2)(7+($3-++*&*&-2*+'$3),/*..$0C',$;04+36(/02>+"308(2,/%-%9)881&.*/%2/.+"$.5$0*/! (0.)$#!/-44((048!6#''$63&"-89%3*+2(&*1,*3/'').-#.-%/.12.)()70')84/1!'A(066&1#4)&%#))4+)0?*%1$2/ #+%*),*$45%&'2057(#763*/!7/4*$2/ 6+,,&.0**(4%(/!* -/,4$(&!"!4)+7+.'.0/8 +2#*$&+/7-+1#6-B)50/5)-/&'3)*)1#...,'(76 */212,'1+03.322$/84;9."րۀӀ׀ՀԀՀՀրɀҀҀ؀؀ԀҀԀԀԀՀҀ؀Ӏف.$'&*!(/%%4/1+6/.!5(-.60*+:*-1,-4/!/()/*-2-3."/*'*003&2/(0*"*9;* $-'0(.+-1)41+7%#-5&(!(%'$,(%':0/'$6))7,.40*6<&7((-%-"-622)"%/44,(&7561%5.(&$;'-(4%/**31."#6%"05/&#$&/+"+10+2+9/23%.-*-!3+($,%".39*'(54)9)37 )-&$1//+.7(2!+#3,-%/** 1-7)',3&.+-&2$)0#3+105((-/+.1$15*-%%5/.,614(--/8+' ,!<3$#3+/56)=+;/9##0&/$!31*7!1/4!,4*1=((5(/+7.'/&#-+.$+#7,'*.'-%+54/1(0,252---)/.8(*>&)*73$%4.20!&##.1+66+00*&41(4&@:&-,+,8**)//,'%4%48"&,'96&4,&+5#41-#-%*0)!*.3!&9'*29#-,>(!.*+/-'$)/17#&*7*-1:'-7.++(265+*+69*-Ԁ׀ۀ؀؀׀Ԁ׀׀ǀրՀـҀڀՀ׀ۀҀـӀ׀ـՁ.)7,521.,2#"'4&.2('6.0$0-7-,'+*4/+3)&&%'?/5590!,+*++1' +)4-)15.%-))'0(*0)+&-$11)'$(;.94)5,1&4'&1#./4(5)+9&,)#-2+6'-(%!',& 2/6)/21613, )0.2+)52&*++7%'))+&/#2/4+-5(.&'%&//./"),))(7/0%7((13+%-'+(9,!4./%2$,1"%(-,8#(/#%.':.=/7%*$.<2)%243+./3-!5)*&*7'.&)/8:20-39'0+1#/'232,*=*0%))(/-*1'1"'5*+)9/)+'!,/7/%0%*/8) .'*,-!($%(5&&/'&79!+&2),,2(&/0+-614--*1+2#,$)!5.+/'1%)(-+1),+!(",/7501$*0)1,"9+3-,0-0+.$-+(0-0&5-5.4"%.(+.*))A+4$/'4&-&,4(-5*??>+#19.241-)+-+!*12210+2*3(>'97--.?1)6$84,0,5'2)&-+=(!2+22/;-/;؀؀ԀҀՀրــԀȀՀҀހ؀ԀԀՀـڀҀրҀڀց&:+08$0/&1,0"-#:4"+$5+/-<+4>*3'2--"0'6/;+-+1.,.%)2*,13-1;/-$,(&'.2%')&447*(52-. +'/%$#/&),+3./)56;#.),1%$2!$%+/5(+,3-'4+/2+/'1$-.$)2/)1.)7)!)#'(,'"0&*+0)'74%")#,@23%5$30/*53..+"2(+<,+<,-4-34@33/2%'!!+./21 +*-&%*,44')-&:'))(.4)'*(#143-,'++,!"+)9%$-,(':;5$40),/*-!3:.&.4.<, $:6?$+!")/ //*"(-)*0,!-&.+*34$;()$,6-2/9-:81=7*=2 ),7*);'-6+,/#:݀Ԁ׀ԀրրԀڀӀɀӀۀԀ܀րـր׀Ԁۀ؀ӀӀЁ-18%2!-72-5&(4)/-),2$(0*$'44!11+72-*24-+7$"1&3 )2?3/1&$"'8%. &3$0%!.,7,)3.-,1-,4,-"6)030%/;1'&)8(+6'5+./*&(.(04/23')'/3(.2)3,(-'*'33&)+!1,&''5'$5&-*230/3#0%-!5#%(')4/(/,4+$3735(&&!&,1)44#&),-'%&0(*35'/70#)01((+$/1/$),,6%",.,67$0)/+4"-105-4<(2.*$1+44/%-.%00!$(/$)()+/#+%* '-4&/822+.-0(,*.,#!*&).61*#25.'421--/*')A'$-,8+.+.&*/ //+6(/4,06!&*/-# %2-+.!1*6/%"1!"+& 7--0:) '*/+.*7'--//5-0-08/> '130*(-#7+'%0/%(!''1/,6"#/')+"+*%1&6#%1,%+22-)2"(%)&/,>43/5&!!6(*$)'&)9)'.)63-*#(/34")87/07172=-32;9؀݀ـրрҀ؀ـрŀπԀӀ؀ڀ׀ـӀۀڀՀ׀ڀЁ687+'/;11(+7)-),9&).71&").$;)*?"&#(6#%3,&+-86.,-%'533/./:7-2 0,%1,(*2/!)$3(&7!-0($7 $1-6+&".(%!'/!+%)23$'/$&'%6)3?$'*)%*7 ځ:0)(24"$'+,4/$/6),"1/+40:$25+..3+=(;'71+$.-$3',450/2&:+#3*5"!8"+)0-,*'<56#&8+00!47<70/)*6547/)%3(,'(20#3+#4458.+"+-''&%&&'/(&93(51,-7 &<&+%--9/,+-'/5+/&,-0#;./))*'*/*'/5' -,5*-00"/-*58435%3)8(%)&2700E2-)*-077-080,+''!,93)3%+0&3#(/@/// ((//'3&/,,2'416/5(*25'+244-/32<6+/(*0"'*)%%7 2&+2*231'5/+3)#"6;---B3/8-*&9-)(%**.+.)&',6+3(% /1)/1,)",(()$(9)& %,/*'.391!%/&%)/1%/,14.&2!-5-/'312-'&.+$'+/&3&-75)3&"+$%3%0(&73. ,%/'0/)&),+%8&+<6).,/(06()+6.6+*(/++)%1'15 2''4'&-09*)-//17.1-)0&$(0+6%$%+#)0*/0',0,&+/*%%)2.+5!8$3"94%.-)26-5/*'=36&$+,).&3$*69*$(#'3"-%)3-%,2#(,*/2-)+(7%0-,:()1$-0*15"&$ ///,8:,.2-,-21)%%+&*#)+)-9'&%)*%(1-718---!,-#5.+4>")0$2&&(,6%)1#1*%0+1(;%730%3++@)1=Ӏ׀ۀՀـ׀Ӏ׀ՀɀӀրՀــՀҀ؀ـ؀؀׀؀ҁ4/"> /@)-/84./3*'-$=)/((/0<.:1,4+4/.-.6/!-1-.*,*$:''8+&/1(<+.F.&$33,//)%+(%0-<,(5&1101(9+1/6;#.$+,88$-7)+* /534)6,,((6(14/()-:7/2+*74&5+&*)-.1=')-5*75,0'$26.-$/.'*6+-$5)9#'#0.*,7/-)&,-"&!&(1 ++ -1/'<.'%)/.*-3***(352)7&3*-@.44*&")12(7.+753-//&323))%0+"-,,): )0/+9(/./."9-70(!+-%',(/4.",<.0$+!)3124"-)%'-8&-6$&'0'77*).)/+#%(!6%(+'$143'(+.-+++3(7+%/$*-" )/2+-.1)$63=&().*0$) ++,-*&,304+1315+$*",*&-.0(@-+4+()+-'$1.)*);+''3+$0.!.,--0(312 /10=!#../>02&'/++(360&1-+7=(16-7(4 3616&"4;5-81#*+)#܀Հڀր׀ڀԀՀրŀ׀ՀԀҀ݀րԀрրՀӀԀՀԁ'095'$)8,;#513"@1,+1,'4"1-22,/1/(7="051)$':.$(24,4,,2246)0(5*!;(!,33(5,.+!,/)*1),&4*+20,$1+$0*/&0'5!2-)/*0%9.(;7.22%+(#+./%+..57%2&' 1&;0'*&$-/-,!,% (*3/53-"+%2&'3)) %&,&)-#%$*(&&(701+-1*)+&51-.%'>$,8+")%1((7*(('%#/&(*+(5+%*"'83*>1?/%.+7'4$/,.>%!..22/()&.%60805,3$%%4)%+-+.5**(.,,'2?.-4+'*1)4)%):)!'3$-/8+(/-"0-*+72/>-./:9#(- 0/1%/,-26*0#(/10+8)&5!-*,.2'<.,&.()*1/7*%/'(,(-:5/3(.73'#9-%16(50-)(,*-"))4,60-$(+2411/)'/4#)2/$%*/#3(.<+=,84(23$*2$*51*7+*%7.'!-"#2*&2,/+()590-61+/6+(1-(('-0#*./%*%77*;+&.(1,%.))6&:.33)'7׀ۀ؀Ӏ׀Հ׀׀ـĀ׀׀ـӀ׀ــՀրՀՀӀ׀ׁ%%*")5'2;6;205*)"',%1<'*4387)5<&+.0)(,%*205&220+&((1%%47%*=#6/)1*.0)&*@%+0()0+)(+04/'-/'3)(.-43'//2-62)3!&+"1-',&/$3$ 0)-+###/&,&$6%0.-2/2+!$(+)56-*+8,5%/92.(6,%.-,4)1%:'-%90-113/0*$#!7 (0.,,*%"4?;/(-4<;"("1&2,/&+!/-'762(*,25.0%%((., >%!+3/5235$**/1(3#.3,%2*+1.-+11(1-,'#$$,'31*5$.'*#2%4=)-(83./+'.//',))1-))%18: 9!!"0.3%;.",/&&-.(?1,,+.+459-.23)(*'*+06'1520$-4)(1./7-3650-8483(4%3)6/790!1+'0-29)(81.'%+5.1931-.'%0* .10+*4%36#.82/7)3>934Հ؀ӀԀـ΀Ԁ׀рɀ׀؀ՀՀԀЀրـ׀Ԁۀ؀؀ׁ.(38+97%--,97(+06)#1.,-21).,)6:/+,,%2'(2.(:#+/&*,%&+/)#*2*37%(*")+5!/%0+6+./,-0)27(+- '&2+!H//2860*7#-',781%20,1#33=%11@$%*),+)('2&";(#2)#'30**44()*-'20,0.+!*!*4+8+0%*$'(.(3///)C)#1/*34:%%))22.(*)-6,&3#5%3 2%1$.5-+40%)-&&3&7/021'25"/##4#)'46//5850'./(.$,*:34#+*+,0)'+"1#3)7,'&3 %(+/0+/8432'/+/0310-+)7-5&&7$+' )')&)*/*3*%-)(.% ,1%$- /*,0--2@#&4$,1+-/03.6-+/*!$$1''96.3(14.8%8+23)-4/&2#.*+)0%,$11(7('202,)55/7(16#-+# 657)&2&&+5.(%90-""-$352'(71&0)&,1&/'$.3,5) -*+("1-.-5-42,&<&(33$'-&,/#'3%)3%ЀҀ׀ԀԀՀ؀Ӏրƀ܀Ӏ׀ــڀҀۀππԀπ׀ҁ*/33 %$+0+:80:.%))..85.+19*/$:)4.;#/%)+7+&/-,4%%-+;.%5))+.'1$-$/#36,'+*&#*/ 72#%,/-")(09(/1?5512#)#/#'/#,*+0'+>24/*0:510($)/2)+2*!(40%- +8,5$,*.-,%2'+'*'- )26'3-3*+"4#79 %(!'"(:6!#-//.!#2&)94+66%!*39/0,1=+*--&74/*6()=?)+),.'30*;.<+./6!-%7/16%$5,+%4;806+-4$'#!2-*,** *%!30#1230.'&3*& .$<+'+*5/+5 )0<0*,.0(-.(/5/71.#.4-+(9<82;6.1)/(574'4-*+$.;8.063+(9(%2+690.3)'.*$-695%./'-$+++#)!(# -3112%-6,'*1-&-1:")"++%"3)+*227)3%+#/*+"*'&/':-9!<(0(2/--#(16#&*%+:/3"5('#)'778701"-)"'%$/$9*1,/<,)*51,)/-146)*$#%%.+)+"136;+*%.ЀӀՀр؀Ҁ؀ӀԀǀҀԀ؀ՀրӀԀ׀ڀЀ׀Հ؀Ձ-+!15/400++4+1(82*2'!8;=*&*>70%&:$ 0#%*1--(%+0&"* +3-))&&))-$.*9/.0-+<'0#--53#);,#/)!671+)/0*271/5*#<"40/0-,4>%, 12,6",,%/+5)/-(1,7*)3&,+$)3/*((2;4'.3$"18'5+$33(761/0&&9%*4&1,0(,+0-,!'#43#*1,64445/3+.+7/3.-+5, (5+8-!= (=2&'#2$),*8%/."+./-#117"42'&)/"16+#2 #0%)#*%8%5 ,2+/3*%*2)4=%/$+22)&&8)/(1%.$&"8)4(91$1&").)*/1,#*4-/0#.,.23(()2--3(+/%24/0'. 6,/)51)&'%'4@*(/')-*&+('4+4+'6,&$',-,%4!.(-,).'/(/*/ 27*4%$6.+24../0'2%1#&&-(0'*"-.,1'(/(.6&',.!(/)3(43-!1'71+!42$'7$8-+.:*/-.84/)1''$-08%3*40-6рـԀ׀ҀۀҀրԀǀՀҀۀۀ؀׀Ԁրـ׀׀ր؀ׁ,-+:23%63-)"42;8,3320'+(*3$$./,B)/&);$.%7@-7("'/ --8&%926-0.0 A#73948.115!)'$@0!0%$**=)0*$)2,0%+>*-299/&%-'"-1+!*0$$"%A1?-+2$/K%:2*/#-$ /#20$%/0 %-5+61+-%-(+12-9.>' 142#+079'(/0+ %,-+&/'5&'-).#20. )02,+!+%/(/(+"60-.'7!)&,,*#/).$111-.&)7&4(0/!*7';.4*#18/--$-/70',#-)721*!5()+'"+,*0,25//3)58)-%'7 + 6".''-&3$.,&+,/3(6())&%20**#'')09,0**$,3*03.%9+&.0425&-'%334"&',/,*-%',=+!(*-.')1,'%*-$&'0/831.8$(-)'0(6.+*#*)*1%6632*)..,21*+45#*(4:0##(1+,),+#2!(21++7*(':.0%-%0!%<7#..,+0+#;&+**./6+$'&;-+1/$#500(%&+7)&$)0#*5(#.6)7"62;,-3'&/-755*88&5%.+07)3Հрրր׀ӀـڀҀĀԀ݀ۀҀڀր׀׀؀ڀ׀ـՀԁ/$0$0-%602/20"2-:(/#214+'&)'&+/%C%962.-+'3""&(/"4/3+8&001(9.8)"6+559/+&*B*,47&-/&3)*0%2(*(5)3(06&/?)0*%9/'.6'(0*/&71'*'+)7:2'&0-,-/*4(*/&&.'/+&80 '+01.)8()35 :%&,/,65+&+)5%!+/.7"*&%+../,-:"$-.#!3+)/*1(*2.3 /&+%)*/0+7/-2#*-,#17(7416*//(-/37/A-*7(-)+"'*$%/+:.8690%1#.3+&-%2=!7/)2",&0%+=6%&//'040/324(3 &,.%,6.)&(/+4%'**&26/-(- ,0.'$3)8-)-62##5/+!)/-/159??'/+*08(-0+!922.),%5%/$+35.5/$'+/ *"(('(2*0;,))-10*+ ,&0+%)'9)'3'6-+(+8**./,/4-&")/69/%),08'"$!*1%'9)0)))*;2.3420-$+/5&2#)0#;2/,883/836016"-6+0׀Ӏ׀ۀــ܀Ӏ׀ŀπڀڀڀ؀ҀҀڀрӀـՀՀׁ1562.,$'36..,--/:53600,2#%/$3,441+5,1+13.B9/*.7062##/#01/( )1&(:4/,43(--+'7/(7-74%)1.!'#863:0)2(//6'1*67+**%&'.-++))"(@70*).&"!<,,,/5/ 15$'&/,'&0)+(96)%-"')4 .4##!-1*$6 */*)27/"%%',$&/0353(,&/<(5>&-'06+)*(',+50('-!)2$)'(-035(1%'/'613-&'1#$)01182.$6/%'*/'&4*,70*'(01*73**- <32*3++-6#4+'2%+/+'83.'-5:4 +)()-(,(5-*0'*,&,& '+)#5/;*))/78)/407"1+(9%%./*.*)9:(/)/(-.-"951&4!6);-($+/$+-&1/,%'30*43*3' %%-3)'/%"+.9/%!/!.+5(.-+/.--2(+3*.)1',,--&3*-11%05',$-(!0#!-)92.)7"&2#..02"$.-80",+)*)5-1׀րрԀրՀԀӀԀǀـՀ̀րՀڀ׀ր݀րـ׀Ҁׁ/&)/.6#+-=0*2)0=81/)7'32-2,+)&&$31#1-&)%$/-+#!$3-(,0)%+4'$1"(/2-086<8"$%+-)80+*?.12"'++/(..,44&1(0;03'1/'M1-30(0(%0%,+#'7/'!*0/'&(0(7-.$:,<%.,1 '1')(&:++1*+2/2%)1&4;5'19'-).288++0.'+('2*$2()/2/-/.-2037/.7.'),'0$6%(54(#,.2""+3-%86-)*+(+!#()('*40)&%("+-5*,.0(/)5&!)*,'+(%'$;*%-+&/3!+%+ )/),0,84/./<'+#*'/"!/&,*1,#%/137=8042*9$8),)180.,3(&+-(+*-)3451/*-/4/).))0*%5+.-# +&0#.#,9'!/0% ))/+&*1%,,,'%!(88>($&1+$ "3-4)->" *'-35*,*$".)3)%'3*21*%;,"*'(*.31)-"&@&A-.,)0&+0;&%:$4%/,.-%%.01#:,' %0:30*%0608 4؀ڀրր׀ր؀׀ҀĀԀԀՀՀԀ؀րՀ؀؀׀ۀ؀ԁ&;0-2%6($"%'31*)165+-82&&.-,<$8%0;&164+ ,%0%,/)0 2 3$20'.+07$.$--"'8"*.,84**+2)#3*$2+*',0."53-11/1'+)2&-$.:1)&-19;!'& 7'*,,*:%..%7(%"),$''-"-'./73'3$*'1+ 6#+6''-*'%/%&15./3"##&1.,.',*$%!*%$)/**%'+&%-3(!1#$"1*/-)'849/4+)>(-40';/%)<-3"5//)"+'4(5-7(.,-6.(,&() **23"/03'3 -../')*%686'*.44('20:)%!3'-+7$'547-%(00-,,+3')9%//0+,*&-13/(?#*%'-,* '+8".-*60@/,,.+ &%*2/1'-"-4812*-*+5+4".5'-%*/3!'0)*+*+1/.,&'/.'30.+;#2#94$)</+%-'-,,1'.'((+0(".,)0$53(1'7+20*%$,3)"8*&)(*'4%0.>*&1*(23(73-1:)*+%8&1,%--->);3+5Հ؀ۀ׀ր׀׀׀׀ʀހـՀԀـԀـԀـӀ؀׀Հ؁-9('.1.+-)?0';//$)&06/'0-9+&4':'-+-#0/,0*))#.32),#/3)+%6/.0:#4-6)6075,*19$%)$,67.9/30,*7*--4#0)*+)&/$3- (1+'+0),1&1&!/%&3'2)7*.*4$-$/282!>4'&)1*-4'/&&0-*)(10%-4&53(-!.*/8)/7*+0/,++&")%1(-63*0$=!6-36*!--1*/+&'0"*/")!..+'),#/430-'('6/&#))+$)5+7-,#.&!%!3'&9'3++1&-) !1*/ -18#-+..3+=/-%9&-4-0'!@(822' ';3*01*.(+%,-7( &.'"(>+/.83B&'8/,5,3*&()(##*30'$3+)!.(*(/(%8721-/(/$6)$ '"*(7'+$%!$(+17--#<4<"'12* -* )'%*++"'/0:9('4%$,3): *0'%))+(+.5)((,*$//),&4<-01'.1+-0/.5;%3%+*3/+629#-,7&40@/+)5<-5:/+**ԀԀ׀ۀ׀ՀրՀӀɀՀԀӀԀԀ׀׀ՀՀӀ׀ӀՀӁ5,'G$+1*/)14-'& )<.A036,+)*%-)33.+9)3347)$%(* #1)5*;7)+*'* 0--$(&&9+-)3'212,*&(%3+/+-//('"-/.,()%0(3"1/ "",#!'$'323(,;..*,0&&+#8'+'%%/%!20-&4+/,;*0!').&3'*$,4,&$72;03&$#'%:'0(;0,/.3#2;-)<(1248#2#'9.3&'/*10%)7)/2)&/-/83*9*$5.)+.-%#!,'(0/ ,!9(33'*/"4)&2-++2-#++(*'3(/6'((/!-2/((,%1,)*$36+/"2/62:4-&1)&%47-!+**7/'3./,50&-) 6+(6//(+0/+<-'7)*1-)/134..%,/.$/ '(,7*8$/943/1/0*1*-#0/17'1!1 1'474(#)'2%&5/.!.:.")%)*-'(-'$3#/)'(-!*$++ )/$"7,8.,;7%*/1 %2(1!5.$;-,%"6/"// *0+'55/.*9+.0&36+*&)5*%53,.5,&&.%2/'8րڀـ؀׀׀Ҁ׀ՀʀӀـ׀ӀҀ؀׀ــՀҀՀՁ(67'%;,)-3-#-'1*!&9%.4%4,))%)/45%3(,'2.6$.>&'0#,(,31$-,;20511(/-"3*-,/,''427/*5/*90*/(76./#*/,.4)(%;#,+0$=;&*+*>'.%?&#!24(+(-372(+*)*.:00-3%)())0'+*0&0626'(!-#'3-3-,'++(/'-/4%:)4(*,527")()/*./-7/****/-#22*2&6/(+1/4323$'#$9+)!((/+--#-+*-%(51)$5)6/%21*,''1+%10.14+1( :-51())0 '121)50/0(?/) .151*)-)&'+6(3:$31#/7,1( #2-#$0-,5,'*0':!&)!2/ 06..# .0.5+5=)(5)047&(++0.%#.-9!*,5+1:'6$-)01)0.#.,-6"+*/,-/2(6+>(8&/+6<4,5-4+'- 01& $1#-)2/'23+7/'.-$'#31$5/10<*(.20$*:'/!29'!4"/10'!&+,:2*$/1.03(5,.--,5րڀրЀՀՀՀրԀŀۀԀ׀Ӏ׀րҀԀրԀՀ׀Ҁ؁3-.)*5%7;, >.2-*079!0+%/(&0%&1),+1-/#;%#1665+,,))+.0)%(*%&'3+'(/.(%.(+!+21-++"$*(10-.$,.,1#/6/)*!+/#7)(&,7"&5,&+(3%3(+*40*&,5+/.9.-:+'16&+$24>-360/9.=2*+24.%&) 1<%"/+/2/8;-&,59**24-'*0++206"#/1,&/+$.+/-#(:<)#0.+$*30&%-'.+4+.*25,(7&08%!822+-++5)87-'=80.7,&+6"*++$)'&)#333 **2-(/5.8)',':,'/3+#5- &+("/..'-+$9.3!(<&(3%/+!51(03+38-,0>/$-ԀӀڀր׀Հڀ׀րȀҀ؀ЀՀՀҀ׀Հ؀րԀـԀҁ/.4($$17/5'';+07.1:*$4,1%C&!79+-5*7$+.',,7+-$;/!#$53&%1$"/7.5+,1$-&/7)*0/''-.6 6.)#,#2*,/(,#2)(#.)(02/%1).*$1:25-19601)#'+,++22-,6-/3/%,9-3)%*-",3,1(2'-&-,1%)8*4(; &+:-),('*11*&5&:")#')$40(4+7/)61420%*+(),'8,2$4(5$%)&>4%1/3/+1"(-.,8%+2 $$%-++/!*'(2&)+3&)+(.-'13#*$;:0+$%=30(E+*/(3)/)2$)5'0+-0-'*'-$/'7&2.7.2/(14-#4)2"&-70;#+,)+5$-0713-/.8'/%!%,*,<.++=$.24)03!1")4'%4:)$)0''''9-&-(7,1/( '$,//* 5((%(/,-1*.+*/$/%/1(&"+-'1'*4'+*-*)01)!##!&/3*/6'*7..$*)/'-'/2"61-%07 " %&842*+/!&%)36$8$+10&'&$8Ӏ܀ـ׀ـՀӀր؀ŀՀրӀــ׀؀ڀրۀԀڀӀׁ#).&:!-8,*'..% *(&6/'/1'44,)+%1/,).)-81*(,.'4,+13+7)310')//0%*"5134.;)/-'"9/2+374(,'0)05$1")+.02+%170$("8!%%+.*,/20(,.#2',0-,+1$&4/"),$071**':'1&&2//!)1% ,$$-(&2'8"%1'..".+'*414)+//+)1.+/&!'&//40( .'#+*&&;(-%-*(%&3,210)-+2=20#"'4'-#.+#4)%.6,)33.//+-2-9+$$?*%.*-)7/3#+1(+&5+4$/#-567'5"/*//''';,=-/%-.22*'#)+"2;3)+$*0*6A)'*!+"##/@##345&%+/#>-)-,.%2)--#)(1,25*,//-+%/02&?14)-%-3 %18110+$!5)1;.&6(.92/)24/3-"*,/;(&):4,,1",$.;---C'#1$*$5/)3+%/$1'211%#2 (57'1-7!A32653/?+'.+.//$/**6(2($@*&,2-,31-/%Ԁր׀ӀՀ؀Հ׀؀̀׀܀рՀԀӀЀπڀՀ׀ՀԀց@46+',3-443/6&!:'281./!,57,).6-2>3<+%8+/01->56#).>(7(&1+2*-'",%(*73,..&1'/**"%('+/*('91($*-22,)4'%-3(-&/."/*/"*52('01, +* %'33'32.+,)-6+<.#5()+>+34/0-)>55+#1$6-86,()3!&=5+5)"+%7(1$2"69(11-:26!.<-/04&(%,&*--%1) *./))*'/+1*!+*,'-0$;%/%-$'*+$4%"+.+5**20,1//)0.35$"!<24-002$$$*!/+0//++0(''/'7+,).(6#+,%7, 8!#,165-@/-4%!155"*'*:..'1+%68.'$("/,(4,!,20:(/))+9'17#24%-+1#?1'-0.-/)9)42*.)&(7!1.34-/+1,/$4,).*/6*-3!$3*2(+-%+3&'0=/5/'$60.010*-)#-+/(9*,7,:%+&)/5;032/$36;5;5'3('',/2,:/9:.=112(*$21&ԀԀ؀ԀԀ؀рӀՀŀـԀЀՀԀՀ׀Ԁ׀Ѐ؀؀рӁ59133'2?)-*3-5070%/05**3''=,+*7%%+-! 821**3(-'53/%(%7'/3$28(5>E")'/#.(-*+,*.%2 ! $/#4,(.*1.!-:)=*,00(0)4.)-*'($)&4*+'+14+6<+)2"3*0/-#7% )5#:/++/>5./,720-51+1!7/"*%&.++1*0+%3!,#+0 +%'%75(0,,#./.*&#$#;%/&&3'5&F1'*%-1/&4*/1)202()+1*4/),&01*&1-)1*/+72%31-5,C-1-8.%4'0/A?9+<7)/*1+)%'-:20,*94(/.!/.-/*./&-:+'')'* )<,#,5-$-,>& 10+*9)01/36$+6/+%(..+.$&1$/./+&*2'+"(91+%02,)-*(/) 3.3+2.$=7((./3$4*%-%%**5 +'-(/09/6.-1,'.:(.-41&5$-30',5%*,,)*314328)( /-1 ()450%50$!/!50.0)+)45'&;157&0+1;&2.-6%')$(7/рԀՀڀՀԀЀՀӀĀӀԀӀ؀րр׀׀Ԁрր׀րՁ--4*+$.- 31;,5&2&'*03,+/''0.&1$76)1*',2+/%3%*"))+5.(")74%+''3'&2.&/-,+3&63)4-(1/43"2/29"(0#:%8.3'9710,*8-+* #4(8 -3-5-.4+0'1"((/-6.(/&)/2: /.1(2)/0"1$(&-44,.%+'&30-2.+7!+$2).5#(1%#.,)*--,./1&1(,-0.($9,21$)!*/4*.-+-:'&3)(--/(7)200"(1//-"2%91/32/':(5)2)-$.'6-2)#;'4-+6+)'**0/1%&3- '4+2&)*+ 3#.&3.$$25!4'?';-%$1%'B#9*011$92.(.3-2%...600*(+883*)++18(+&3182:)'.0**4'&%/0/*,(6/)0$0)/!*',6$=14&))16)04//$07*)"-/-6.)4(#/; 51&&3%,0-0+50.*"4+2)5)3.'/*3&,/-4*&(.(%(/,0-(3-3+7(#97E'+/3&32>/-,-&)+/րЀڀـрր׀Ԁր܀ր؀ԀԀր׀Հ׀؀׀؀Հс..3.,6)-,.5.$"(( >/);1)&(.(/8'3-:-+80/7*25-*#%1.,'4%)(%0,#-6/-'))+#-/0,&))41+/%&(',1(-8+<,#$<2)+.''.'20%'' (-.,),1.3)83(89/-.+.3%+//43//-'6+17 /1/117'7421'"%'/-22.29+3-/#.9,#&3 "5*84-150('&'((%+(&+#,*.1.,03&+00(-4/##,6*(432($7+3,#-'(:6190!'?7"/(3*-$!0,$,07$.4.,)!'((#/202/--,0753+*07*+'-'+54+,#%(/&)-'.*)/,11&$05)">+16.4+0-;:(43'.<-+&0)'4, ;+=$4"/ +;'(/*-*938 %3'/#) 3#3/0%8$$1!/06:0-'&*)2(*+1,'' 0)0,&2005#!.2306#.,)6*,/(52!)#)740'61 +4-54(,':+'),++#02++*-!3(")+1-*-,'//):9+?-3%/4,,)* *0&؀ՀـӀӀҀԀր׀ǀՀр؀րӀـҀրҀ؀Ӏ؀Ԁԁ6*",):(185.+>!/+/.$0.0(,.%6*3.131-./-'9$-36%#0,.).00".(6,+)108!17411%$='51+1220($$#&%4$+3/8'0.%#0+"0*8*%&6*#;+*%'$',%"/+(73,:$.-'(1,)724/1/"-40-)9>%37%<,)*)"3.$+.-7/752)*41&)%=-++$43,8).4.#* 0$2*4&&+,$*"$%)6,/+'*1#-+))/80$,2$++71((03(2=,8, #-8((5++%2.:/"*7#++,8'/ *(-$'<#1%5,;;+-6#'3/#/%#3,$6;(;,5*04/&/*(%3#5(0510$/2,%) '*2+%"$-%,2/2=#1$/71296)+/5/)+/-9+8'$312'*#.6)'-*#3=/3!,*1.13)1$(++)+)1*%'&&/-/,%/+5?)-**2+)$+/-,/300& 1(/%2/%',,92.2-(0-*+6%9(;&5+&&3+%%2.32*&2,?77+71%#+'3+-/*4+;'0=.+$%.//3217/ۀ׀ӀՀـԀՀ؀׀ȀӀـ؀ր݀Ԁـр׀Ҁր؀Ԁ؁/&91-6/0*.$-(?/'+'/#36)3*,3(%0..--//(7#3 4*02+114!'&$0/54 :'$.-$-,+3/,5#0 02''6+/-4(++3/4.&3'(-'(*+#:-""00&(#* '7&,/'>"092/ .),-33",%1().9-,+*)#(% +#.-4!)('*2,$0//$*1)3-#5+,(-++!%*(4).%12--"''//6)/,)-63+0.3/+37,.93"(0'3)/54'-!1*/+,)$+%&/0/)@.+*'-':.)3,2%%../;3#*,#$%+'91.4)+/&1=+(/,-1()2/=-%/,+)!/(9'/7*.,*-&.21('41%0!0-(5/%+#/**..2"-1-*303378)75'712,%5*)42"2//3;%'&(1(*,2+6;))- '!&51/*3(2+13(,"9+/."$/-%(/!'$'(-6347-4077/+6,!6!.00;2#*.#&9)()8,*0*67%%> *6,71(27(0?-.'$#(7"%8///78,'9$ЀրՀ׀ۀ؀ۀڀӀˀ׀рـԀՀրڀ׀Ԁ׀׀ՀӀҁ/61+.:6+1/142)1+'/!0/3-04+/6:*/'%)"3,*,2D)1*-+**@!%21,/>2227'3/) %6-6#1+)4+0!5&+%2& 5/:)++'*)$.( *#$$//!08%#.!247/6)#/.#.*1''**-2*"6*%25-C2'+-) .)(7+1,/+&*-00$.4)%'*":'*.8*,#--'.0-'(1&3),.373/*)'&5*)0)"$5)+),5*()*5%,5$*+*,3)'/32)-#/+"7//!)2,#.!7/,63$*-7)0,1'$/1+&%.-%"*(&3(-*6/'23%)050/21#2'&'&3-%/8*3-%"+.4/'%--0#)2,'/$+#-#,$6'&**'8.0)+)2-@1.07/,90/+4+1$+."+"%6),,,/-.$''%$!2+.)"'+(%$/,"5/(1&/*0+#(.#+*+$1%=/')(,4".-7"#',")),65(.)#)+./?0.92%1+;2/(,)/+!2./'.7*/1;7-1&4-2("-%).6%'*=,-2#0*1,-#'&66/,.8؀ԀՀրր؀Ӏ׀ԀɀՀԀր؀؀Ӏ׀Ӏր؀ՀۀՀՁ/:-,)409-4*..#,/4&0+12##(1*)1'/0-'675324$00+&,'/*7"30 ,5+0/4++/270)6+)'',('1(-(*7-7*:&.1"(/,&2(.#;'/'%)& 667"/3$/5%*..$(+//*/1((1+5!7+%+#)..!/*('%5,-(.<>/1#2,%,3!0+/74*4*(!#+#31$-%'-0038%(/$-'+'-(,0+*"(1(/6//42.7&/-&27+)%1%)+*83@+(')% ')&+*'+*!,-,*2'5-:+!61,$1/.#+4'&1*)0)337@3=*+=$0253"2."%2,301*22'/.1#0"3+,7;$%4.3$<4׀ր׀؀׀րԀրـȀـր׀րԀ׀׀ԀӀ׀ڀ׀؀ف/(.3)$3(1/,...)+,362+:183%499$" &)-%')&0+19-500+# -!%-)1)##1#6,9)-(3+,12*"")0*&/.'++4?0*-//"406.-#3 0%+(1*//-')/'+'%:.)(/3*,0*)+721-7(+*!'30'0-)**+(%;*-15'-*3&1,#12-//440!11/&+-;33&-5$+1.(#-/)(92-7)(0+2#-/6.+601*.$01%.#4=9*0-,53$1,3*'$-2)$!4%'!++*"21;)#1(0;06+! +130&)(=#*/,+>4,'4.+/.0"8$91-%+./-*0*(-$--*!54)(.390)5%+/*<6**!22 ((7@1+,$)./%*-$9'5)'83,/+2)//'/)*'-3'&'%3--#%-(.(17$+.&#,0.5).'..-9 ,/*(&/.'3'3$($%131(/-5/(0,!29-*/2.! +/(/'#4&&/-.'!3,*&.-50%/ /(-/#-/&F4.-1.(70'*5.;('-+";*-':րڀ؀ր؀р׀ՀрŀԀـԀ׀Հ׀ـӀҀ؀׀ـӀف;").$-09'".'1 00487*$&*)(31()+%($1%/2#449'3*%+/,#%,;(.&9+"#&%/-./'4*&+1/2,;+%3+/$+,/%"+/.3-+:. 3#)="$4/)+0*4+.-<"0&',- &"*02320!)',*2'8*52259+5)4*&5#&(%,04.%(-5/)(,!(--$+-6+//#)-/,*./0*,/;$'.402001(.,1.-;00%/&.7:250*;&3%!5&9#9'*<09$*&+"'(4/1&7,().3.,4250/4***+'1"#'/&**'$130+'$70*>4+),6=>&%#2=(8,*')1;&#()'28(+'()-'50*/'(1"&$'1 ;%*.#31;*((6+@$"/+"-//-/&&&%407-'/'5.'(3)2-**%)*015,#/'''$11 ( 0.&".<5'0;(21*-(-2'*$)%'"2+$-"-+;$-*/**(#-2/42'2$)/0),'!@10)/,--'(()(, 7#,565.,77/-'2'6$:!;؀րՀ݀ـӀ׀ڀۀǀՀ؀؀Հ׀րӀӀրӀۀ׀ـՁ0*&4-1!/12>)/54&1.161(%34*5,02;4*2%#/;&+2(.03*1*=0!+,)"6'0)##'-0%:-+(',*0+/'42-4/4)+%)/69.*)2B/'./0/45/34+3)&,'('#%*1-(%+6-9%-.$%&&2!09(3$%0(-'1+"-/-17+5&**//1$!)7(&3!/)&"3&+5+0'.1(09 +-/#63*&3*)41-4/8+.&'(4%(+.,4.-,!('-'&-78- +/0//-3,16/*)+420*+:)1*!3,5((('5/(--.$'338/5'88*0!("*,$,(.+2$*,333-.&/*<'6%0&:.3"2%&#(!4"+@0352/('&%(3028%++?9-)5/5*)&%#(5.3.%+:6132*//3&+%)+/#+%*+9*'3",+<**29.".5-4,12'70/5%5('- <3'%3&7*--/52(,*<%2%'/,1+6:(.%((9%-1383112;-0+0*)'.)+*()+95/.37).&"02-?%,/,;:1//1.(:++25,ӀڀۀՀՀӀ׀Հ׀Ȁр׀ڀ؀ҀՀԀӀҀր׀ՀԀс2.1047/-070)+4$-1)-$=&,**50-0<"&-*#9*,1(/#:("$).%2/+21/".040,1'0.022(&02$)2-,,+.2444433..2/)&'6')4**6:$,3)8,-5%4#+")3.1"%%1($78/6..'2-'*-!-7.)+?/- 5-)&36*%*$1/"!.?(#:*%35.."-+,:7-+."/66'%"47*2"%46(*%.0$.(+8,7/-7..-5-.4;,.3#6'+"2,%&$,%7/,323/%10+5$&2 )&3+)/<4!13)7((/$+)('1&,6&(3!4C+22++0..+(', ,(-&;**/(3.-"(*4/6?88)"3,+??81&4+20 %'*!)&)+'3:#)677+(10)6;5%1:;3-#(*209A/--)"-,+08,.!2,<4(1.&(#-,6&* 60/71,#%7)*7:7'(/%(#*-"#245.1=/..41+6,-%/6.1&--'/&0#7ڀ؀Հ׀ՀӀրրրÀ؀ՀԀրӀ׀րӀ׀ۀۀڀ؀ہ08?!+/@570&-7)%3-1+,1%(,+-'!/1%#79+.&-8-%+3.4(482&/*!('3//(;,,3)7-)5&7/--)-/,+*82*6&(5''$--70-$&7*'!'8//*64&&*.)&**+/%5-&*-338,4%&!6!?4%)"(1+4*#$!),"*=0/"-#/56)'7.&&1)$39,3#+&=34)61<"8!((0,)81/$1*-&1*4+),3%.2 -0-5.0.$)"*%B068:+-,%3(,14;0)%+(1,=-?(;(*%%!)4/24$%&$),;++'(**(%230,*1*"*+09**03/9- &.,%,-//#%)4"/+'65**)5&+5)%.%1#$235@#!%('3"14/$%%1#/%0,5623+6'#'&/+/-''6; @-1((+"2.-!,604)'1+$(**-"/-+.9*;1/(+1/13#.2)1(:,/:21),/-.))9րՀրҀԀԀӀրЀŀۀԀԀ݀ԀـڀҀԀڀՀҀրف'(5+I*%'0,!2(,#+#0*,$'-."$//'-+.''!'7,:'!,'.'8$@4';8!03*5.1 +*3 /(0!--6 ,5".!5 /3,.3#:5(9'#&3(%.9)+/&3!/,&6.9$&--<"'(*'/)')+,13#+,/"/)0*0.+. #(..+%%.%-//053%&8+'+4*+..)!,,$(1/,>-3&&-+,3%&"'6-*+9,'4+'(+",4.!3+ /-(.#7,-/#1+2%70+/0++,;143'*9+3)4(/04"/&-#(#*0- '6-,-$!;3,$=,-?'@+'!-+4'22'*(-,*3:#.*5'B1.-4/*+3/(69-.+/ -90&21/1' %1,%+)9/0,&0)%*8#)A8:).#42(&3)&,.)-&&%,(,410<11-"1*0'7. &"(;(&-(:/#0/'-1++2+8%)(%*.0//%!(463(&!#134.+./'/;/-/9(.1255'*'8+39-02+'3(02%2)576+0-B6/,&36-:(//1*.(%*&-56:-;+?րՀՀـ׀Հـր؀ʀ܀րՀ؀ӀۀӀӀЀրۀրրځ7-'/-)/+#&40&/;303"%/-80/*--8#1&&/.6'2+) ;47,', /34* 9-2(/,D,%,-!,:4$'1;.;*-.51/2+3*!&)(/+.'$,;452(,2+)/,/*,$'/('*;'* 8*/&2?57%51)9,#-0)*$512'%6%3942) -$%-)+((1A3-/,&1,!)$ 3#5)(%7*%$$-A+*$/B(:.)5,2?".)3-11$!36/-,4%-%1+;-//(5%(#-#0'1/67%,9':"///5,-+)$!. 40&/1./'&)7/%34),"-&:.3*(,"381'/,//!-+''121( &$.02*8& .8%/&22'.,-4'&;587$)&37;$(:58;2/ &08'-/)4((-#6*2$+4)-'..=-4-/'7/?+'1,))#5*061,+$'/('("-923.0.*/3,*10%+ +&.:)"),(*8*3&!(//8/1/("<$24)(1)$'+!/*'A/*752&%32'0))/+.%(01,**4231Ԁ׀ӀՀ؀Ӏр׀ڀȀЀӀҀۀۀ׀ـՀڀЀҀ؀؀ҁ.( )%+-1++"+7.&4131-#.-H/-32%+#.!-4/2%#')-5*3 953-.,/+*<) !10/.'$'/2,&.40)8+&.(2()6.+7/ 57".*$*(6/(%=*+.6"6,66.**+-(/0+(0'/217-*(-6,0)+*3++-/4+&&1:/4!*"1--350%!,+1&-/5!-)(046.%)'0;,+36:20+.*'0611A,/*3+&5-&.-+,%8$3#2(030-( /.5.,13(+)(5.&3A/+"4+-/%$7'1.*%"+/-)&7)% 1*+$),1/4-4)% .() -3$%+.)43/! 0" '.#,%'0#/1)*),+*/3$+ 26&+,%!/<8-+,4+!,/+(*-2((:&3,+$/".01-"()">*0!$114(+%F$&/2713#2!- -)/35&%)$-'%$0*-1'!/+-()-/$(."/06+$.5'#'),24"!8++$+=2952?#2'(024),/9 ,;*(#4-7"!-,)82*5%3)'&-$:77!68+.),7,.@0,7ҀԀӀ׀ԀҀڀـπƀӀՀ׀ր׀ԀӀҀրـրрۀځ,-'-//-*51,$$(4./&,1+.(8 1;)#'80,*0N+8%/)!/$=,+71/%!4(2-&((.6/17'13*)*.;.>=$%+/2&)$*0./-(/#'/(/+4'#4%)55**+++4; 3%*2-0+2++)/--9%+&/46%�/.),54,.2!:+4"0,+2?+#-#0../+".''9.0'!(-#:"28'5)!<1%+)#70$.(%)"-2/8:,)4 %,)++/'5-:#&2',1+ $! %632#+-1,()0&.#%-)#&1/.#3#.*.-2/0'+5'4A),/0-'.0.$05?3*&',.-2<$+.'3#-0,-.$*54;&4*4,90*/),)0,3(/-+)9))-42)-2+4 %&0%&%7,1.:.5&*;ـӀπՀՀ܀ԀـրƀՀ؀рـڀ؀ۀـՀ׀րӀրՁ*04&.8'&8///0-++,34-<)8%,*.".*1"4%++/.")&*5:7&1+.,4(,*<,1-,3&74)75:6+4/*4ӀӀրՀڀր׀ҀրՀր؀؀׀р؀ՀـӀڀր؀ف%5<5,*1>%*;9/+/+.$%(6)*4+#1,-6(,123'%/-D-86#+)6*2/+.)),'22%#15+*512'./0-&6*#"%!)*5%23.%0"+=&$>$%7+52%)/7/13/2,'!#$*../)9/#3*+$&$-'6!#+172#!)51'+*4%2+2-+5)9105>29,%06,39243+34*!3"/+3%5+6''"++$'< 220=+)+-2).-75(&))121%) $/.4>-11,-+%)+23./*"(/7:2%(%*,,(8/-*/)01'41,(1<0/-2#(0*5*5)(5(*1((%,'#(+,/+-.*050B43(.0**7-*'"&6=18#+1/#/+8(!*/*7:'+20(-.0'++'0'=0"2$++07,."-),1"*%,!'%6!4*/.$)951!5:7+/,+)&#:+$*-)#&0%&'+'3)#,8-7/.+8/(4)$/)0,-1/(4,)1&&+0+/&.45&&" (2,(6!')++"7%:&+""$--$*6).-'3(+0&0.-&;5A/@,ۀ׀׀׀րր׀ԀӀǀԀ׀ڀрԀـ׀րӀՀ؀׀Հԁ&)()&>/1?*+%,,(4763';//$/)7..;*%//*2-+.5%./,-/&/6-3:"+ ./)$(1-),2111($)*0&&1$8<-1##./+/*/6#7+ -)+&*;62&//-57-0+'0.*05)'90++-%/$&0'0(&4*5##*+,-$+&&)).6)7-,+&*(1+))#/"47&4%$,',!,/424.&%*,13'57:0/03C4%4%*%-(%#,-9'%0!1-,20.5/$/0&,%.+/#(-.15..++3+"*;7&00%1,4%+..)471--/(-!*<)/%4)35,/,,6%8/;7&'*"/)A1)(-,+>'$--31.0(*",54''($=*:.(5(2'!%(-0,./2&553+ *1@"*23#/+)@%'%:*47"-).4)'/()-0+-,)0/)01005/31)8*'&/3-'--u8'(2)+!.(%.1,'7%3)3.171,<'(24/(-5***(3/#'('1*/143&-(($&,,:1%5/-93+:51,*5'./2.'$#3,%(3'/6.")!%-+2*1*0(7$75(3ۀ؀Հ؀Ӏۀـ׀Հǀր؀؀ڀ؀ЀڀӀڀՀՀـЀہ%($41&,.//4#% 0,3# .$'/0&+--$46/;3,6//A#/ (%($%+7-5)$)38)(".7%#:)73%,$-$))&/3,$(&/-&*;)'+ (+;)+,!(6*2/%'(%'+%#*/2.$'+1(+'40, :+.,02/72%%)5-5))"1--76>10 6/&-$-'4.)3+6'":%$#+6!9%8,.,0-&12" *-)2.9)032/8+*3)5290/-0+'.+.,--5(++(%-#50+.0!2+)+)-$ ,/)%*%+11I!"*%)'+'!6++>2"$7/*;$%*,,'54+4'/%.;510$4*570+()5-=45'7!(+$14.(%:-M*(-'/%30D%7/)41*9''03%%,1*%0."-&=+%0*>./(+%0$$$*4557832.1$&.13..0))-%,(5*2'0&32>441&5712.:65-/#-03*1:-0րրр؀πՀՀրҀŀ׀ЀՀр׀ԀڀԀ؀ڀҀр׀΁,)05&56* "2(+7($,//,.63,+)/ &&'+0/+!*.746(.-1)/-(0-//*1;-9*%'3(3,2()E5'+3/>8-9&#-(7/ $'-4-053!(.647)*%$)+'-2**.+321+&8/*=+*#'-0((4",-!*%+3 -7/1,$& (+1#2+/7,-0%2&300),+*/@<79!+#)//)0,&3"/.6=92)/1*,/1,*+2;/,&#!26-7+48--3 (5%-?#59268++/60,)*#3/-3+72'!$."5-(&;/#+%0-(((-"3955('!)1'27-)-6*%1*#%,3+;1'7*-(9,0)/*.255(1)-/#+*!0!'&!!*'28(.64,2*#103&2($*.*&/+39++))'(%-,/+3/+*,.-95"65(223.,,+%.1/r:08(.*+#+02(.-"$8'+$2%:"0/+*+*1*',,"%)*),703-$-'.4#+*&$1+''9()4>(/6.((.7.'9)23, #,+$-2+'%1&,7-%%*-+2%)-;7րՀՀڀրրՀրՀǀրԀ΀Ҁ؀ԀڀՀӀ؀ـۀՀӁ*/-0/44+%1."$200/+*+$4,23842;+!*."!,).#3/63914..3(+',+* (3-70-32'+2++%))4-31&1/.,)7* 0%7,3.(%6&.(#)>)*5870&.+20-)(*)0(#)'*/(&6#.2*#*,'G/.+#+!&,%/0!(2%*'(%&$-+/&())$-0";32+''0)+)')(#-0/*.",%!'3)(+6.1/&())+ ,))#3&7/0,+5,:1$-('1/62+:9),7*3##+4'./')-0"*4-+>8,(10/+-.-.,.&)+*2*%035'+0#1%-(+6*3=30;(/75,/-,':'33%")5".83).%,*'58+..)A.#0--)2(#64,2,1'(#+-332$25/:/-1.-0/69+-01-9'3//-!'(37+9.*(21-<4%/-#10010) %$)9#-3-)+,)(1*59 ;/%7## ;**$(%5.; /)/3&2..&)+8.#".)6(4$705540%+63'8161<2%"53**#.-20./3=1-6*03)׀Հ׀׀Հ׀Ҁـ΀̀ҀЀ׀ԀրӀڀՀՀԀՀڀՀҁ81/-(23)7,92+4)7+*/)7-4=-(%*)'3-/7//.!-++#'%4*9,21+/*9,2(*%6%3+1*.0$'#0'',7//#*04)*&<'4.- 424+&/&+2#3>2*;2*,./("#*)'#"*.55-0='2!+42/<'%)!'1( ).2/!1)!0*'76)/2#7'&;-0')!#+*+4&&-()/.7'*#2%51'3/)46(&(+. /+7+632"')(8*.234+*,$*1,30''>)&.#+7/$"01'&.$"+.!0,+,-5)()(/:7/@'$(#&-/-'2,("-142+5*2!,622;%G0.;:/097*4*)0-**10$-*9!%) 3)5+3+,#$ )+.1405!*+B4&+'*,306+-;/$27./$")/''0)20(167(17+**,/2-*9#17-23""3(.1#4#3+).)'0*!27,<.+-+'-.6+Հ؀Ӏ؀ۀۀր؀ڀÀ؀ـրր׀؀р̀׀ԀӀրӀف8)-/)0#0+(,./)3#%(--..2/&)).(910(2026(3".70)#.6/3(1A .)1%3).'3;$@(-/&3.'<0+/;*6)'+/2#0/,+##4//5$-37%(/(((3"-+326(/+3)!-'75/&60)1/7&2'3)2,1-$+)%,1*2/1001//,+*-.2'%,30+/6*/3.+447-*7204.8((>./)402*))10-/.!"23(8' 3**(-*<)+$-*1826172;,+73-+%* 24#3$35/.'+30/')39'/),1-170'",%/6,.,%3160-260/-'87.-/(-2)/-,622/*%+213$+556* .7%-)'5%*9# 8+#(:4%8;)9)+/$%%%#+)(',&56'+>,/-'):;85@257<9-.%/7-8+/:.%*34)5;3.4),&7:)*#&1#04/)=;"')'01-0)!0$>'+#",2'(&5()('12"/+&/.;./&%8;(,0)./*% (*/+1)156.,'41=32&.0823*1'8,рҀ؀׀Հ׀ـр׀ƀـրڀՀրҀـրր׀Ҁـ؀ځ1/.,=*3+11('7%&%*+*&-))*(0'%0$4'664-6+.()+/-4*+*+/0'5#- 3'##0-,,(0B-8&7'/. *0#B94902/(6+''+1+61-/003!&*0*4.',*:3("(&=*0'",+5+3)217-$.*)'#.'#)-)& &9-,7))4,0*,1&*"-$/%B#*+95%-)7*33.4500/#4<*),2*.-2!)'%314*)#)-$+4+*#1&/32*-+36*/&,*2#!146*)244+3>%-.0/ %!1!6&++,%,&.4121.15/.)'.%31%&,-+)!=3/-3&5.1$%310* 7*&$&*714//!*'D,"/-(2.,/?/ *".1/))+/,0($#0%)1,/7.' -#4+./'-/.=7&.27246B87+512.6+0!.-3-+$(+!&/56 /'2./@/+*.#85+)3-#>+,/'61.2-0-#/1*0,$36'+(%'*$&8#0+2**28(+275*-26-3/#2'11060+*8+(2,:+'1;+0**#+րڀՀ׀ڀՀ؀׀ـÀրՀҀـ׀Ӏڀ׀Ӏڀ׀ـրځ+.,5+9%25;)-0(-8;.2/<&&13,')/.)-/&*/1(?*9+*3/,+',%(.8/)825*#1+(-,*,2+<'+4*/")131,2.+*(26,-/1'100)208***)(/8*71.2&6++2./(+$$(&)5"**.,9)8(+(.,4$(1#)/,.&1#/#,2-*1,0,0#.)*!,!-5025563+8):+$2*@%)&%('%4,5)1,%+!4),"$4.&37-67,.17,&.4,#32'8-$+*,01/-.!(:/9%/./,3/357$'*.(,6/#3)(,152"4!0)6.+,6;30'1)3+/+$ 65+#=&+(5+#0.F("$+&'((",+'2)(&7%/).0 9&/8$('$,1C*0'1(329.#+''/)'&*$+-,<%(('3'/2$!.0#,./1+'(**/%(9+%$+,' .%'0&2&'"-,,)# )6.06+7%7.01##./*5/%*..4.40-,3/13/3)79'%',''-,./-2!*"+-7'8490/,- +(+),)-&80'-#'./(7,&+49)*6/3?#/$41%%/3,/ *515)1 0./(275'-32,)(6'*3,#&80#*0+''/'-*&?*34.&&#"#,,#,**()/'3*,.9-.*) 2+/$%02/(?'6'7$/3<:&):--/1<;PETKBG^G871(=(/(C&$>.+/13$)&,11&/''(,%*+1+"/&'!+'#'.+//!%+)#4+)8/."1,--/6'+3/6*+81#4=+$51!/411'11,(&%!-=23,:/,/'12(-9,'1($2(4ۀۀڀՀրۀـЀۀ€րـՀրՀ؀ӀրԀۀ׀ՀЀف*5&3(1,;&#*.39(/)5&0(-/142. -1&+-&3/"1*+&$))(-2)%#*$3/6,('0$+$./4.5/)+'*(,,+2%-)3+6'+.*1,/+.5+'3<4!.'0))7'1-)1/(0-/,' +,(,.*2*)0##*'311. %#(')* -/:##)0+$+#-*/'+%3+>"#*(7'$.7**1!6$>,3///*#=1/-*8#13*71 1+,&$1%48!0'-12/)''6,,,7(3.15#+,#90!'%)/0.,09%%(- '%&3:1/.3//59)26-)"2*5*!+'/..)44-+!+08.;1-2-/-)''7%-*+/9/'#(410"'-(0*!+3"$-.0.052%.+$:'3)--&")./*,804*8+,.1"$5((%+16=AEMIaA]O\HK5=1+//=.1*+%3-.$$)3" .0.'2 2/("+7:/25-%&-(: 70+/7-12, 001-%9&/0,$-/*7$ ./<63!'1%7-+!##0)+,5651+#:629-+.(*+4.&/!&2,2ӀـՀրрӀӀ׀рɀӀҀҀ׀؀ހր؀݀׀ր׀׀ԁ".:-=-!,@12+'6&/;0-0#-/(' 252(+=.2"3>1))'*!'2+@-.4-(// &)36.-(!9'$839350)#:/,(+$//4'/$+"//)3,*)+32*&*+#/500!3%%#!)4$10.3=1"+()33&)$&"*$/&)%+5'&)*)+*;*'*4'#*$4'*/%,29+6+50:3)$6$,/.+/)%6(*(#/2!9#;"$%-3'-+..*:,7'8:00-(,/8%.$ "(2%&./,5%0.%(+*6(*3,6')/))-2*1#2/*+7'/50'02!"):41)/9/'3'3(/!(/11/,*0:"-&/%0**$/0,7 ##-)$2,,+/$/9*(#,.(/%/*#)*5/%*6&2- 8(&?)2'2-4.9*;46-.2+0;(-6)/D@X[\NYNE5Q272335%/%75.+)24;'*'221%5=%% -/1%1+"2),)24-2.$C%'*,0+&(*%4-&2(*-+4&*&-(-+72,?:$%9#-1("%2.+'%42 .1*/++0,5<)1$6/<ـր؀Ӏـ׀߀׀Ҁˀր׀ӀӀ؀؀ԀӀր؀ڀ؀ـՁ6%/54*&44/(.1)51!3718-"5#*%8,%(*)71,(!-4207(# -(-+- 1(-))4,6*31)(!+01)8"-;*,#4,,'#06/#9#0!47(%*,!!*-%3!6!#6)./')"2,/+%*55.42'47,&&-5 ))5&"#$486&0'895-%).3.87#(,$(.)3<.@'6%&,/#,-'3250)--4"%'/1..6*1"!/9-#'824&/3(%3$17*#,*//#4-",/(2%?- 8-!)#+#042)+#0+2?&5-5//)6'A.+:%. ..../04))29&6)/0*,66/,,/()+- 3*,2*)+*,.,/1/1),+/.%)'0"*)$2&/23"2)(*"03)393):-2 /%-6.7%00'-2-6486C:E@RONbT`QNR6;3&1" >7+++016%$'2%2815*+)'1*52,--00+,.4'1(6(#)7'0!! -77*-2#8,+0.+/+1$,"*!8!/+"..,-3(46.%.."0,/(27$/,-..3505)2+(݀ԀЀڀрՀՀ׀ՀĀԀ׀րՀπ׀؀ـրՀ׀؀ԀӁ+B2!#=6*/'*(2.8(%**8)7."/8 *'$."&98)32+.3 6+-/5!5,2!--6)'//#/%/+)##1%3##+0/5*)'8!.20/*9-&91#/12"&'%4,-+<#:)((*0',9$9%(/'*7)&1!+(.014+70'&11&2*#6,+$2,,'(%'!/(/)1*+-(2*!*16'1--/)-#.-*".&02-)/&,;,*'("/.2-6*).&05%51$.)35;/,) -34%#,:,$2-,3*-,+%0-&-4'*00!*032(24)'*)-3-"53*41'/('-0'22)26*+#)&).-=6'5(-6*&,/05'#%1'7"*!/*$,$/(+/."%-+-,.0()/&!6%0#'38.(8&6$$*&%.,6.&&-/./%73))4+F>4:;[OWMKTF4>51,/*8,7+,*5&)/-06,?*)(+/&93,92/*.)70&9-%+35!-(+*405/3$,.2--.,,-0:$2/),-/720,(.2"/ #%.#/)-)*$./%1018+)@3/2-7/3&.383؀р؀ҀрՀ׀ـՀÀՀրрՀՀӀрՀӀրـրրρ.1:>1(+02649&02-&6-+1186(88/'9)/!5"')8#2*,(*''-1$.)00!$9+%20'.500+0#)&'&.2-2)+#++0'&2"%)5$&&2!(18#7'(&,*:--'0*1!(5,,/'*(*-%."2.3-'/!*(',(-)/15*/*+#!.*0'(644"+,&.$(6*)6)33+*.6('9;(-,.+&2&4"04)(-//87%*".,1-0,<(&1.2#8% ;*:32*26%%*36$)4.+& 3(%41+797!%&14+6,:/.68%!,+)/*$-55*-2-('2' 0'.-2%61!'2.**,6,/)90)1+.2''*4(+7#25/+8+20(5"4")*-+/23--/,/%,4&*!3(!%/&2/&('*6%0-40:1/2"(:/8,701CAbII>>=+9*&=%45(5.$'9%/',*#.(0(4848,,,$1-**+-'%;.;)'*4'1/'%'6'5- ,1#,53++3/0+.05/'/1%5%5+,,6)),.-/1*01/2--7,-&*,*09&2;3)/)":*'ӀԀЀր݀րՀـրɀ؀ڀրր؀րր؀؀Հ׀Ӏ׀Ӂ-7,25,!+6+1$+3&/+%0.0/((*19:*-3'+./&0/ -.0228(1*012%#7+)$8/+<,0:./5!27-5/5)-):3,/236(5'!$+.6,1'38*/8+/+2&+((1&7+1%$,4%$/($-,72221"')*)'.*1%,+%*+2&)&2".(,,"&/-.-.3(4(/*."+-13,$%2 !"' -((+//4'/5;./),3*9/&://'$(),.2/,=-0!&04/40!/11+,)/:/33)+"2*881644/+<9+''#:5(/)&)%*)-+3:03&4&,&=++&'"(:.'"+49 /#+!1(24!7+0#&*2.04$'./*'0+(8%-+*/#)#',)!,7 *:)2,4+264%---+%0$7./2,/$/'%366/4I# AJTE=@9C069(/#(*=A'757*-0.)--2&*5240"2,%.61/-*+0/*-59.0*7$*8#;(7*-6.(01'7B+.4:)5B1"511(52%2׀ـրӀ܀ڀրԀ׀ƀۀրـ׀׀ՀҀԀ؀ڀ׀ۀـׁ=%04<)2!-15+)C/3&1$(%!%-+(6!8;!90)$6%%(-,3(-3'7(7-8$4(*2!-&63-+20&"&.(+-(++1;.4+.+%2-,7)'0&1-&%''".5!.2' ,,/.!(+$&(/*9/)-(-'3&(/6/))3*(450///'*%-5/.&2.2;(-,2*5*'3*.0/+*,%85-1<,,/-)!! -/9#6/14&1'/,!$+-&-A,)7,1/1.(96+/;+/* (&33);0+-.2&-(5)&4"-.:%%#.0:8# ")2'%7-"(5%.4/%2"+$2+*<-5/.32(.(9/ $%>)/.%,$+@/-/3.*747--,<*2)-,+8/ +)+)2&(!%3-0 /.-"*.$05),- 3((+'&.(+*'+29(+52157/8;:C*>F?;,@'5146(!4*+0#(!. &*7'3* /#3/%5:&%+<(/&,1*.,1$12&!1*55-/)55)&')'9..5+-'(-5''7#'4"+9%7090%3"'/&"1(65;1;A 4*)%)3(-:(4*6ۀۀـ؀ӀـҀ؀ӀǀԀۀрڀ؀ۀՀـԀڀـ׀׀ׁ-4..)*:=)04#%'3791+,'*3/87&*&5:+--!2-%5%+570/'4/*-")"*=/)!68)$*%+',9*0 6*$$09+%3*- 0'5%)2) $++%'#.,$6(+931('/3')/+3">-)-2$$1/1./8#0/+/- 1)$5/98/%)+62#.5/*35-03/*49-,%7$1#4)2:+.,2*/-2/-/)/+*.-1#13(-//'(%/)#1>'2..6&01@(-7!06&/+6/1/*$#(&'#%5*(6.-401*0341,22,1*$(4&,1&-'.5*,/5.2->+(76/1+"*&2#/14+* ,/:*21.-+&71/%..,,.2*/3+/,&5,&,-''&%/*$$;6+/,15 */)$)'&#8"3,1%+3(,12316, (284,6.33"52!":5+)0,').-!-:;3,!#33(-983"25+4'5//1)1-9813',0'+*(#0&0'0102$7!/!*)8/46$%(''%4''5&),(39,*"17"4" )-2/1(+(&.1;*4;*/.+8:7&*'181$/,.7*+6+&131(%"*/()/4*.;,10,$"1-0)2/7!.2'27%)1"7+$).,))*.1+3.(-2'"()$14994*-07#,0!0&,'(,"0-7(!2((&*3 0$0);/<#&+)(+(2)2#1+'-, ..,(*+#)3///3#+$"0',3#)#-,'+'0/'*-./,,3!3#08.+%)/7673<(-:--+%:(61//22-((&4,714,$"0.3/'/#2&0/)##)).#/6-:7;1:(;9%(5/37#5-00/3'61'0(:/.2+'&32- +'+:+/.*,220'+-'6"0*%,0.32/2$()2(;,1:%27(,4րπրԀـ׀Ҁ؀ӀˀҀҀՀՀ׀؀Ӏـ܀׀Հ׀׀Ձ5$--#9*'+)/0"33,/*-$0(320,,/7'71'3--"-1.#21)2-"<$2-9+/()++67(,+9,1(/::(""+3&*(:0+#1**7074'-#+&&5"*+0>(2-8(,+',$.24.67-&.1.>$1#!5()%&11+,1",15411.+6!(,'00*(1+0.?/+5,.%%/($',*1-*#-/+0*-",//6)3-+"6'4=,(%;*'%4,6'*:3"2.#7()0%')128/*7(52',(,0"-6*,06+/2+:-)%(-+'0,-0-8.314-/ 34.&*0= +'H'24')!%+.=&-).$, '+41$8=-5#2%.--.*.-)+,17.#-20/'4&*',)"1-4+&#&5''.6514"&6.3/)7-+*+56*&/36:4/5%1,6=2),46)7&3%7*////-#,0'.''%$)7/:$"0':1(%1+."'4#'905&;5-5!7'#)02 /505"6'1/4+(/099='/-3%23,65"0$*/-*&(=,;07%-2)%3&/./(Ԁ׀ՀـԀ׀ـԀҀˀ׀ԀՀـ׀Ѐـ݀׀׀Հ׀Հρ.4)(-1*377!$41/11),4177--*1)$)75;-7+=0()'%-3'12,+030%/6&!#,2%3%+%9$/ ,/,!2(.%5$5)-9)$:,,":-/%%?'2-+)#.3&5-%-3%5%-,&6),7&0/2,4*#'.+36).>6;$),+,,-)),90%$"//2+&%(.052+%0%*-'-'5:433*/-1#$/,<%).04+-0,'*5(,/!5*2&*8, /(/)+1!,0(*05%/55$0;8/*1. %8;(3+.8>,/6,#/,*.3),2)=='$..2,+"43(*),57&*%.,%9,-2--%3*1/&.7+/'3-(5$4!#0523(+/4;&11/20.6"417374(%*/32/0)(%$1$6+3.'&-/,128)$+-/133*=0)%)&4%51"8%,/"'')$11"/")*#3+2,* 1",**+41(& )%'7%"(1+7&#/.5.47'+/",*2../-4*-#-!(-',*-?""104"'6,33*1&36#,33(46720..1!7-*7.'01-,0.2#)6/%47/)/:>)*-",).,)%00,11+*8#10/,;/+*"$725%*21*2*$41(.3:6(4355$00/*2+.$$/&2,6$7,.3-*11/9&*.'3-)+61.%/%%7+Հ؀Հ؀ڀـπՀՀǀՀڀԀ΀րπӀـрր؀Ӏրҁ/,*A/*,8*4-0)0--6>10.7,30$55/-;0.*6 8,$0+"66,/1#+8/$+'8'(&/.((2(4*/(3-7+)/!27(-(=%+(/-2*";.%*.$5%/'24'/0--!7/!&;&4& **-+/',+")*5)/.!'.@.15-))-,/A#0',-$0-##0#151!5/%,1 -346+"%*247*,.3#9-*,/#/*:',2(%4-4!.,..,02613/ )-)%0!$3,36&)**'/*3(."'/')/9))*/$+ 1 ,:/ -$4%12,84++%.%"06$'3521&?&).&/%-""&$,-'+.3/. /,+-'.333/--&+.72,$$"/-&402124;+22.8 $%+(0++<2",13(-(2=14.54/+(0)-05$'0**$-)($26-)&4($%6,(+.!/&1, 7%,;4.-1!/13!.(-*)(+-713-%*;-+0-+-'/!&/+#.+3&1'(2,351'!0-5%*+&$,%&+*+/1-.',..2'D'62<-+/12745!*&2+0!/.!3#<10 !1*-.32/$#5&),# !4/7$%57'1$-/&872''%4$:6#%$5#&$&,+,'//(07*#/"*2+0/(*,3,7)&3&'5/5375-22-/521,7&0#*":08,1,;&(*+3'1:-7"*794%;)*%31.$24/!+$3,%)*:5.)&%%--9,7.#)84)(38:0!4+(%5$(+3'%%4,3/.6-';+%.(!53'%(8".%$3'#79%.9,6&)"1(2C+"%+*1/021?*),558;9&2+'+*,%1:0-()/&3+#.6%%)01),0$3/%*++#'%2.)'586%,%;)25*'-%/"48)$-&//)+!))/(0)0891.40+!+.7,517,*,+9.63'5:&3%/1#1.)%)3/=+)$ ؀Հ؀Հـր؀ڀـŀЀӀ؀Հ׀ӀԀр؀׀݀ـ؁)-49":+,$$.*$)*0+6%1'=%$3&)&.2'2-'3*+0-5'14-.:<,/-/.8!%7'.)0-.,#')11&/0;4'+:.4)$0$(%+7&%*/&#:,59$ %2%0620:-0+81'<%1%0*1##++5())"&-*$ 002'&5/4*(#.)-%!.23.).&0-,'."#9"&1!5/7#*21%7*?'("6)28#-0/'=2#5*/+*10"./35'-#'#+0&*1(8 /(82,-A/&9"1)+-/)(#,/,#0'62$ 8=820125-*݀ӀՀ׀ҀрՀՀԀŀՀ؀րҀҀ׀؀ր׀ڀԀ؀ڀ؁"738(!,.3;'!-/.-1(110321+&%$27('')%*%01&3,+!$01'%'+'/'$&%%;+0#) ,5.=44,("(+/*/",7 )):'(+%/1-(+)6)&)5230!797(",)0'"9&5,, +..#-#/)(- 5$/,4(%5)%-+-/?+)"2(1'*>$%*#23*!( ))7/'(1#(*(*22$'%.29.(-5%,+'3 $14*!735,167/0&0$#&75(1%-%2)-3+'"+-9%.1*-.3.!&)$$'.(1026:3-%3*#9&'- 68),*/9+0/8/4,%*%/!3)4-4.(+)/*-1,,)#$/!-'&3",!4&%;,*())//)):1&00&--,1(+5'#)-3&.).%)/$0%$)/&%/53,'%2,,/+,",)3 *27 *)',!,(0,--7,'4(.03!./:54+1'**-!)7#+259 7)/4A2352.-246+5553(.&09 3ՀӀڀҀՀԀ׀׀րȀ؀؀րӀπۀҀӀҀրՀ؀׀Ձ-*5-0$)403$+A,.*)6,0().('..9 (22*.1,+-0,'0*7%*./)(7+/!'////.+&4))$1+1$5/'16$*!+*5-H+8).6#('/("'+"+'9():&/(2"1,"'+-5/-1'+!''#/'4$20;;*&"/%3&5#'""-2:''6-%,#('(2$'03)%44>&$)1'>73(+23)-*,/%%)'"C)),&8*:<)'%0-,)0;,2.0%,39-4++5"-0/(++)'/&:&/46)'=0,/5-02*70-(0#2-/,-;>.0 --+1,+3%,3'/+1+80/3!-6,$1(/$/-)+'&-"*),/)&!(9,#1)%&*,8#&,/&2,3)%)-%4&(5+&/4(213+$2*-3&60.-6.)(//3$'(:+/3/(;%.&7+/(.;8003$/,!*5-,/0&1D/0+2%$)3"16: 053400*07,8-5/--!;+!'&"1-)*(5//$5$$( 3)7.0&243(*/)$;'*"7-)0((C5)5."56+6<2%+473+)$8'+'؀ր׀׀ހԀր؀׀Ȁ؀׀׀؀ڀ׀ҀـԀ׀܀؀ҀӁ.&+>*#-*+5..,&002.).:0.9%--<-+-$; )/.'22',.9/5,'.$%95%- ,(.*5=2(/!42?2#'&/,'+,5#5'',&922.3,'4$+29.:=.&1'0;)','/$/,&-'/1&$.."''.(!)/'.*(5-,(*3< '1%-9/)2#/+4=-'/1-;',.78%3''-5%+;,-426'.5*)-3*'!4.*57+.13*..(#"/$(,()4(0:2( --).%3,#"/*,)()1/2".,5:16((/-441-'#4+9..2;)+33&-(*$%&+0,90)/&.+,#,*-/'./)$1<,-#;(+%$#+-%,;45, ,*+'+>$%)&3)<.)4/<%($-3#,'2.**5+5++$+&/!%$.-&.(-/3"&"&!4881+.4..,/-5#2,"7*389%"5402(-'*'*%+6-%+527!8"+#+2'+/5(,'/3&..%+*&-&0&,((/5/64347=.3,'1"%%')0/+7(%$3+**)%5?*"/,*,34*"$%)0&5(2(&#)'0*)7,"/''(17*")/63)('--',5*(,'0.+).'#)&,/,$!74>/2,4+53'/.$7:26# '-1 21$34.?">/'-3$-='1(.-)4-*)16!./932&)854."6%(,*)/'+,1(.2$%*)&,1+8/+/86'+2-'.&+.404*Ԁ׀Ԁ؀ԀӀ׀܀׀ɀԀҀ؀Ѐـ׀Ӏڀـ׀׀րՀԁG+%8+*6#-*'0/41501/9&-14<2-4)!14)%6+3:0*1/ !%#5(*2+,&-/#*)$/443-9?,/*%E41&,,#5+),!:)&-,,01''4,/%)+'$8*44/+/-=/)/+0.& -1-$2 1,47(2)*6 61B'1/$3*52!,0*53&+"8"!(-3$/7.470;)%%(1+/#3$4/"''/8*&7-4*-"*41'',$'.3)1+0.%&651"2(+7,:&4*0*)>8'4.)%76/!-4"")&1,/<6')(03()-%(''*'53)-'1./:1)--8%.+6333(%9/.%'%:#.0.('+;))%-9+-(8-*=2//14/,2++/+$/'/0*473&#;/%%4.*1.)-)3%1 (2#),7-:4:02:+-2*(;/3-!-1.6..2)+%('+9&),(%/'#1)0-(.3-5/1(*, ")336,0+(-/7/0427+70*(',/'61-5##17325."'+4:1(%#05C2903-0, 64.*&1+7,91):'9&(0(,ڀԀԀ܀Ԁ؀ӀـπÀԀՀӀڀ؀Ӏ׀ՀՀրրӀՀ؁338)'$,'9?+&'0))'3(*&;/+*:)4.'!0!5//%*(1/#,)6&*327.:+#//2%-$,(4%-5$8.(,&**-+#2)*.0915/2(4%%(4&*./(44+4+'0$%%4C..<),-*(20 &%//*./+1+&9% ##,)),-%94'140-!7+,# )$),7.494/8+-))%,')')9-'2,0+#%-0-+)#/(-$.1/20'.-?5,'""/%*.0%&2('!0'3", 3/"/6)4*.) 2#<))1,.+-/*/)8"/+1/+4+."1$))2)/3(+A+1+.'.-.+//,8(5$3*07!*/2)'/-!+:#),-#5,,$*."&)10.-03!+//,(5&-+.-/03,877&13&*(+6$4+.160-(5%44" /"0'++#'-%%( "/?7-.&27.-/(.7(-,++2.*:(-*,4%).=#+#3'$9---"$)2<-!&/%2407,16>/'-/+':;.1 /.%+93*%0&)7B/:%1:*).48)*/9/+78,&,60+׀ր׀؀րրӀـҀŀӀـՀ׀Հ܀ՀՀҀ؀ՀۀրՁ47*//3/*<-7./)('32).74%3$ +0/**2 2&)6%'*90##,$97 001''#-%.1%#0*1*.$2)&+(1"15/)""86+(+!1%,:4'('60$+2:01,'+*#)(A,5((+,#4'.1%<,+*0'2%',#+/#>1+6#+,.&%32$,4-.-') ='026.1#2'.)13%!"1.&('(%;#'6%:/"+0(142*&565#&-/8&&/-.2%4&/H'32+++"285+(2/<88.+)847(/0*/)8&2186*1+%*5+1'6.&+'@+;7):/7-4%6$76./&2'$".25-:**4#%/935!.+-*+3.&A%1!31%B$1,%"('3/%'8+453=,93,390/(1*+.?#(1*21+&+*,32&74)&,2'0-(&,,(/--1.4'13,(#=../.+(,(-+0/9(,0"1!51/$$3.$(431+-@'(,(&-+ *&.*-0 /7,(1+- ,)&-;)6.7=7.57/)'1%;+") (/7(.0-'3$+,/!5%-1.-/Ԁـۀـ؀рԀӀӀـڀӀЀՀ؀ۀр݀Ԁ׀Ѐـځ0116/8'1(.7)0(*9;$)53)/##*01023!31/) ?/-)6%(8,&+',/-2/-8*$+&-0/)'3+=,$1223,72/+7+&9 !55$/.0.+394+,9++611*%#-/($5++)!(%6(24"2(-(7+/&-747*(147,.*-!6*))('(5,)*2-/"3$..(;:/)"6/+-1#%%+-&,,-%-$,$'- '#$+2'*/262*53E-(+)4/-0/'+/(((5.116*).13(3:+)*..0'%$*4+2%2&+'9?($++1,/#)/(). 2(+.,-#313'#78/:")08&1#3.):*(*++&+8(8/73(1'+))-3(3+/'-5$//8),.;,03%0.'-2,$):-"@!3-)%.%6"0--341&26$86%)6:7)27;0:/))2-3#+/3/(&!%'+%632*2& ",$+$/'')047,+3')*2'##+#.86*-#-.2#2/*',+(D'0(3+5%*-.2'+,?6,*2,08':24-'-1'-0/=)+-),'&73ԀҀ؀܀Հր׀׀ـˀՀ׀Ԁ׀׀׀ԀڀـڀрՀՀҁ%':()4+3112&%"21,)(;2-305(/,(5%':%%-3,-10+=',#.>-,!57-&12-/(-++98451*(359%524+6&%),9+34#50'+&*26-+"59).3(/6.%6&/'.$-/$0)?:*2.)320%.&0-/(2625++.(/)03/$+%-3-*+0%5/$-+6$)"2.1"*-'%7$-+'0)))-")#& 4(41/60//5)6',/40'#&0':*7.)4-12<14.))&&'*704)-9)6)341 /4&$.1139-..-+*3%/1+6'';(/")/#$8 (137.=*+3$30"1++./3*103A4%&+4.-%5)'9.81,'52-*#,/1,4;,13, ,)#0')4'-+0)4#9'$&;/:.9(58))(*-$&,+*2.,)#4!$%-"5*7',/++-')+33/+#60E' 5$6'4+/ 24;C'((%5+/1$+0/&5,$"=3/C,/4<3)-#4 7+858/4%ՀӀրрр؀ҀԀՀŀ׀րՀ׀΀π܀ր׀Ӏ݀рԀځ562,#66%*.!0%#)+)/64152+0/))=)+++16*6+1'.(465,-5;1%,,26-%*'.-.&&//*6#910&1+-%&$'/%8)5.1*&1,)$2'+-$607#53#;5.3@3*,--&)01/*/02.3&;A%+)0.0$($1*'#*8!(+*0+.-+($+)*9)+1./9)+6+6(/ 233+4$*",4++$-$,5'$12/. *3.,9-//7$6 03/,$+&#%01.24/20,!*&80(,28*0(3--&%!+&.89#,*(#/8.&<3%-1/0%$)*+(,0), 6(3/)+52+*:'$')%5,;'#%&&)&+--$1*(6;)'"$+/"1#)7=$32,&#'&(.-!".'//'.,'/!)02-.03*%+12>)&(+.0''+,-85-(.$"*&')")'+*1*&.&3(-**&)+(18$-1$(1/3"(-(*0-//!+0'4#0-10*3.0$>#;,,*')&'%/3&(-(+8.(&0);2/)1/)7*2:)//;-( 5419&19)142-16$5ЀԀ׀ԀՀՀۀՀ܀ŀрҀ؀Ӏ؀ــۀހЀՀՀրҁ/,//<&#"51(;.)1.'E/:--/.3)6(-#$"1-1(4//+59/7-/'*/%."*,((7.+/,25;/):1(3-)%)$)0/2012)+*+23('(%.0-&-) (%45&,&*"93-4;1,+4. $'835.#&//17)--&*7%+(26%1-3*-&+094.:$%(#!7/-''$#-241)-0"+7(/2*21"./+&:%.*"+'&(#*)/5&)*+1$:2%&-06554'#*--)/*%*$()&05%.%)%&1&&:+3+-20%*/50(4.7(8)-.#4(&,,&*/!-**'5,&81*3..%"):*('%4(4'54*-1&+*7!3!#)$-300'&910+#7*&&32'%+'1((1=-6/B/0$2'710))(2 -+,$2+9,4%<29-7;&& $2)+(*/60*0.! #+,+/84.'(%6032-&*6//%3*-1!4&++$")3)1&'9 .3/++.-0/6+1$/955+'2/*701,.+-3)#8,85*-0<3$*1-0-6;.2-9(10'րր׀׀րՀππڀƀԀ؀րـЀ؀׀܀ЀՀԀـڀہ7.+-%--,7-,24))(&60511#)/23'&-08&('2)-'9069).)52+-*27+70/-#1/#/8+2,.'1/+5'%./0*./(0:*#*5(,)#!&.3+/ )1/2+,*/,%6'+/9.-/*1:"06/-((2--&)*, (76+.:+#''$#+/ '+?+/!1"%)$)+' +++'/5+$'/0/!0$"""*-.&46/9)(6:.&0-9)+).-1,1-5'+.$;/74+ 1&0+3)7)(/),''-(#1)!3(2$*'1 :1&,">)(2"#,&*/45"'(0%*5)!8'.2'.4235&&(4,2->)4'&4(-2+#92.-:')"/625),/3(264(-*)095)-).5,",%7)!*("0-16%/"5)80/43.*/%04%4').1*+<,'/7#"!4%%4)+.83<)&'*,,%);),/3-2.()/7(!&%).2'+$(57229!13&#.%7'+5..40- 8055")**-.10)4*"'+763*18"'.$7.37/'1!&/.84(-#',8-1)׀׀π܀Ѐ؀Ӏ؀ӀǀՀԀրր׀πڀ׀؀׀րՀԀف2'76)#+$(*1+.)/-,8.-+.+-&34#-#6+5-&&;'%;4*/$/30;=.6"2):-+)5&./9)$+1'9(&6410'';&)')7#(/%(1)%#)++2,,4133("33&!)2')!./#40-+)5,+#++5'37,1+*!..21!,($*1-7*..5&--,/&&0;-:)7%5(';)&23//4'*E1+74,%A3,-,5/, >+&)0)D+1(.44%&!.(./A%-*:#;7 ' &"6+12'2*+86!(+-*'+,)27.-+(+1?"5'200-4&-&%":.60049&93.,.)*0#5B59./3,)-3"+$((!.%'")*3$$790*,+(/$0-*(%55)42,@./-3)"/+/"'>5)(<-%%*-&1$//')/*-/$./&53((+)!3.9(6804'# ,%10$7,9,*/(25+,'+*2@12/2(.'6"01-2=*0**)+*53/-.,/ 35."(3/+4;9-<*,,)*)7/"/.1.2%*422.3'(95;/&21-0.1*3&50-Ҁ؀ـ׀׀Հڀڀۀʀ׀׀؀ҀӀۀրـҀՀ׀ЀԀف,%0/(4#0583%."&20,%&1+0/'"&337+1+#746%3+,1,8'#%1;"*-$1(0+/;();)+%+2+9$11+&(-.*10,&<&3'-"%-&!"15-%.3%1&%6+6!83..&3%32(7'2)2&(*52*)6&4/6'/,6')#(1%&/&*-73+ &'/3*)4-(-25*'%#/%,'34($.-%#2342+6&*3*")1/1(/3+74&)1/-))1(-(-5+-0*2323*( -!+1+-57.22-#/+/1#),5%/-,/)%%(15(""&1*'13%-,)1.07(-.++68A0%*-75+-, )4'(0- + (#00( 0.537*7/16(+.,1+501*,4.6;21&A-,.$1 14&&#"->.**2,711$5,.1+,&027*77%$;)),+2-/85+"1'%-7%/,3'1% +!6(7(4$/1512,׀Ҁ׀׀ـ؀րӀԀ€ԀӀԀՀڀրӀԀـ׀ހԀڀف+2/*,+-(,*:$13,/36?)55'/-%&')%&/-2--94)-**.$/,($1+)3+'-).+2%$+264)#4)8'-1**0:5-,,3('#2)1+-8'7&-7,-)!)823.2!-47,/.,5--=5,'6/(%'/=!5@/4&.49&+(*$-?!)""!&))46 )$-.(+%&/%@2/2,/,"%1,%7.1<-+-.+'+$-%(5..-!)$'3/'2+61$(.3.#$,")&,*+":&,*5*:'&#)5$-+&$6)' -(01/1/5.0,'0$'**2)-&/)3-+")"2-2#9129'*74/'&*(,+40--2%%+9#(<+!-, 8+A<53%02.-5('50,9/823102,%%;"$*-.*,0(66%,&1,<),/08(*( +"'-/)0'3)2409..7/>8".)(7(!.+$+)''!0)*1-$40-40-'+("**2.-1;1%/-3'2($+(, -2+'02/4.)/'.45)+4.-(4-+:).0.'".+3(-)5.$)&5&73&&1#"*-,$3,*7/9+-4ՀڀԀ؀؀ـրӀڀɀ׀ڀ׀߀Ԁ׀ԀـۀԀрۀـՁ0'*13)#*&)+2.),<26-;6'3+,12+().-"47#+642-(39<13,%$2-*-&'5@9&+('6/5"/0706!3 08&!+3,&+#&63,0&'#2,(*7#+23$.%(/2)-.1/5.+*6&1"(2/",.4$-40-../+2+!1,-" .*1)(/+5!.,-/.50)-..,/(!2B%92.'5$;!'.5),5-'(3%1/6.1,/5/'6206.5-"%83.,()*'4/431,0#,/*'2% ,$-12*+33.("#+.='*8.0*4.)**-6'/84)3/1--&//0/5.24(2*-,2 %'*-6")2/1--302,"/3$1--*+59.'/027%:./*./'/-.#((&5:3&$8D/1(-)51.49856'"2,'+/!7+)/9**&(:+()+%*/)&(0'--+4+84!,).&5+2**-,%5./.,&.*)*,5+31+:2%*--,#*(1)9+4*/-�/=.)/68++9)0+6233-*40-.532A610/* 3/.>21(2.-2-93Հրۀ؀рӀҀ܀ҀǀԀրр݀ـր׀ӀۀԀՀրՀ؁?+(,'444?/+*#/5&,77,)11!/5&2!**5 4-57+.A///27,61+=+3&-17+&+'*'-))5&-.7&3/(2;474*/6&5,1(66-=/2,89.6:0,'1/.3'&*, //*$(+. 2*'1/.5#)9+3*9+<'20;*F9+30/*75+,5$,0)*11! +;+,4)),(0%.'$"5/2)+:7167/5%&#%(8!'2#9*).+.3631/1*/&:75#1B"+'357H3!!5&3%-.#,.7-$&$%4',@1%"2/*7%01?.!.#0))./&+1$(3225.5',>+0420#/'.70+*+'&3/01(*6%-*8!/.3-' 1.1,1%505:-#47%)+'-,4/#2.+1*7.-&4-136/42.+*(-+16+/,<9-0$28*-*.$/+,$# ,*,.+.+=/+%#1,(7+538&-* &6#/1+8.3)1%528+($4( 6%'0)<4,;'3,').24/!2((2/!-+433+/-./3...+3,(-$4*9.41..5660.)532:*;.8+#׀րՀـԀրՀـրǀҀـԀ׀ڀԀ؀݀րڀ؀׀Հځ=4//64*4/,=6/,7-*';4(*7$0*2//771)3$)06,57'0'502+/8&2:'=/3-'9,5++,*;-300/,*8 ,01/*. :/6-%+0((.5>/9)**0#32/+1((;1,//9-!/%.28*//)/.20(+/# %(2.()+)'31=8(!;-)>5,.,,;-6&0$22),92%.*-',0)='/,+&&-2$5-(&,&$.)$)#!#&**+4&%5(,1'$:6-1..'2(#6(#(*;04?1+4);1-$ .56-% )!/-0+% ,255,' %1120.#*)?4#,9,1#0$+,/38,+-:./:2/ 2-:$/6/!2/-)+.+/#<+'3;0.4 '8.'1/!-+-&!(#>3-//9:-6-+2)(/(29*()&2++2(-&-/*.'0-''"(1%+#986)"2+0#13/+-)+?%289)(0-7)/*>-;.;2:7593.0&2#1%*'0&',*/4$1(,,#7'4,0.61+5 (*8.5-'0:*.'.,+'&.531/302&*2%./*/&Հր׀Հ׀ـր؀ـĀـՀրـڀԀӀԀրրЀրـՁ2/>//.;-4+0$/:+%14%)/208>56611-(%"/5<*+)0+,)6764(&/'$.!>%"/!-'-'-#5/1,'/+(.#0&47;,(/('&5+)4(,!&/++,%*.&./5*&/0/9"(:'(3&95(-(2*+- *%-)./*/61(&00(5)'!(1-"(*$#.99.-%+,+-4#*0+"3)/,#41%./+0*2 1$&#./)0&).+2 )7($!%,#051"/52:+0-&)('#63)"5'//(+,0&-+&29.&+);9)*'45)6+0$%2'(-12$<)1*#,/$2#$7 '/#/-.*2++-?++"1(06")1&*.,(4-**(."#8%%-*40544*3*(53-+))(4:'///0)*))/2-28*&83,:*''-*)&-0>!+4-"(91(/2(*5B*5//;.2:10)"1.*($3%-#/+-'- 75&''//8##%0%1!D.:/8961+A#086230'/824-86-*13(0)3' ;((4'++(($,--656(%)*'+3#=-5,#7$#,0%/,'35*:011(9+/,/=&28-,-/8(&/0).:+#&&*%301)'#3"#'%401&)=2((9*!(/C-)9)0:/54,5*33'73-9)7/9655׀րՀ׀ڀրڀՀՀÀ׀ՀЀՀԀրڀԀـڀҀ׀Ӏԁ.,?72F61654!,+/$.(*/+%(17)B1.#*+%12-=*3../-+-4.561/1*"!093//8+0/1<,/='1)42=-/'#)%."*&.=@//2%9+'*/(5%7/*.'++%-9&-85-443/2**)"*+4590.256;-24%=),4&'+/)+0%-<#/ ++:./85,-)3,21(312032,(%244/'4*$)!034.&*.&.8 4=%+8"73,'2,#3/*&. '3&"+4:6)0-0&53=(#*7/'1$#*,869$/:4')$/42&%)A,$#&1)A))"(/*-+-/&)/%/04,6;:**13 #)/(/+.99:1%+$5*-')/*)2@,+)3/ %,)@(-7,,&09>4&'/$#15$/7::+3(,0)/&'35#:853+,0*%138(?1#")%45..&)%:--)/&..5*0*/&5.#,!0.1-7,'%35+1"0&2!+/&6-))/34+6$':,(/5"15+$&.'(11B/*20$0*+3%0#8(482.!114?-(1(9+1(89ԀՀրՀ܀րրЀԀɀрӀӀՀπՀ݀Հـ؀ـӀـс15%23(+@$69:1/@7,-$-(-..-')'744/+"/+56>%++*-+!..4+-+9.,*,71*(,9!,,+0-&!-)0'#&93;2D 72/3+,"6. 52//*45,+4+&+!20-/&/5'5,*!5%39(50%7*26+-*1531//*1.55')3%0 #&0,"+/)<%24'--2'0*,,-915208.%#3'-1&$%0$5/++*3&*$'!60-(*'/)))2*.5)*6-*5$%,53'&/*/2$!9/-!4-(3(1'(1,-*2$5(##0,-,/%(1*/!2*&#/'6-+$()9./+35650"+'**.=*///5+,-")'42*&.7",*1#$.&0..-0".!/'5/&)33(.+.3*$4---(2E-5537-5)('50"/)A+(#(*2.#41/2&28$3>+-6*+2'-'.0*7-.*-221<.,.#2"(&#-0 )4*+,/+261$-:##*!/'0,./153320/,/03'*4,!+!2;-13./)+83/6.3+8&-60(.6**6=)3;*2,81ԀۀՀـڀӀڀ׀ЀǀӀــՀڀ׀πـӀۀ܀܀ڀՁ53/6*5+906/':%9//;.>*92)1<(*6.!*/!)++,%(/;%-:)%)#-1+"":&)%0!-61*/)&&+.(((.34/3--./*-(4%167//1,1)1#3*5+8#1&')/3' /.)1)13*3.1!:'.4&+/$'%+/++*2-/5',',%(/%)!-)0,!(*/2"*+,!5.(%52)//-1,2!/;,3(0%''*.)62'6173%),*%%-..*&+"(*+:".04#1 2($%16%$-63&/(52/005&(6-/*-8":+19*5/'#6,%'-('/&3*(4'(3+6!660-)%/2#.05*!!,(-(;20$,+#%6'26,'.*+)%''-8:@" /%12/-.':)276.,*/2=223%77>207,9,&4.4*3(+)*/1,/1% &;# +;-(-.;-0/"452*16;;+%&%4#55!07$3*,")-6&52$.5..$3+3-55+02'6*-0#.+&3%1*5%<5"1.#.% /**+*9-930!.&7+.2()2&,,&2)-#69#/2;,78ـҀπҀրր׀ՀրȀ׀ڀـՀ׀׀ҀрրԀԀڀրׁ&0%/3300,)?,600.&('/+%,#/6// #62&63)-,1-$)9:3%!$31@,(2"-"<3*.,8&/0,2/;1/"+622&63%B+)-/,5/0)97-(4;*!9*5,&72*-64-.#,&7/2;3/4365!/)/&22&3-2%=#.+(,2,#&,(4/,0/'#)5$,$960,, 2)&*284@,36%1636>"+//+(+!2;0&+-2'32/+ ,)%-'+9&,."#0%)3'5"4(/#6-)9537+:'+-$+$&./*('1(5++," )+5.!#"4//,3+(3+1/#)-**/84"*+-07//'0@)()*0'0=8&,*))71$#:4-2#+6..)$'*))3.'+%,%/%4/,(((-86+%)*-*!(+23./33)(5 +*&(/85%'.#(;.),).+*,42,'"%.2#.$!/%$40&/)+0)-(8&1'91()3/-8/*6*9*)#%.'-,06/6-*22,1 =3,18+*(&4<--*-)1.3+&9>">.-910*.,.*64-+&'322+388&ۀՀ؀ր׀ـҀۀрƀ׀ـրր׀׀؀Ҁ׀؀ـ݀ӀӁ91#**=//41(/50/39.(041)2%+)25.0%5**4)5)-.6+0;/5(",*%'$106910')%=,10+1*5%5'(/'1'/37%0/*,+ +-/-%6%'.-3;%*3&99=113&,(.366#01*'0>=*,%/$++/$&&!"2,-&84/7>%+$*$331/(+)4'+7&."(&(1?.2++!((0'&"0.+1*,0-*+1#:':$-7-'0'&"2"+1,/2 6(&1&-/-80:.'1/+$3/5+5*:)**%%*460/1@/9)13--0// %-662+*5*,1(!)%.6%-72=*,1(1.*'+5/,:#--01)+0(+19/,#)1/+& ''-3:1!'1&,(9//$6--,.(05332')+'1.2>2+(%,0+%%+-,6'94/25%!2;&-(%5/%&(>4(71/+-7;&-92.#+&*(#/33/57/-+0-/)(0'&.03/%&A2--)&:*!.7+&!.+,0+,'*=519,,0,)"*2$&9+,=3;+4. -1,8,&2*+'2./ 3'.,"/1ԀڀրހԀ׀ڀҀـƀրր؀Ӏ݀׀ހ׀׀ր؀ԀӀс)856-:::9)5# 3/93/'*!-/+%*1'13@)"5)('&1%1=.1#(2/1.55)0)&*2 '',0,/=+!&;4%/&5,=)+C0+C$+&++(-/'7*1++)5(/(4#'&1,"*5725,%/-&=(*(*)&.*#%3"..+001*58"60/&4/5-*,,'3',%)32',8/''3'22'"&-!0&5$00%/%8%+,*3".)/0)1"1( ,+80#-+'.(8#/38(+)#/.4)%,0 ",13-"9',.+-*/441&/-,&+13&-5--4"///+"!( #31,1-/++)-/110%/$/$'%51!9%35,"0($84(/71((#"40)+*0.%-*6-(46492*&:-%1<6-00!/$&"*"+882$-0.+<)-%-5:$)62!-7+,'%+,///.-*0>18.'++$ )2,''.1'3-5#/+-..617$4+!,(,1*61+79"+:#!A2,.$%84,0*#*2:2.64,%+..573+.1132+0*,41.73>4*49-/..(*.31,ԀҀـӀҀԀـՀـ€ڀۀπր؀ــр܀ـ؀Ӏрׁ2!):$/)&/* .&*$,&.'.6**4,2"(.+(#7'.&**,3*&31%4.('1'7'.02'%,'<,8*+1('.(+*),+2$%.'0%"5-352,,,/'=-+///)6%.,%&)3-41111335*/1.,,'5*2-#"*'&/1#.&++&/1#(-'6,%/(347/(4:,4)(%0/9,2("6,*-.0*)*,(1-":4-0()*2/5,<1.-5.)$/$+5+6+/)/'7/0(02-0.1-+/+ 23(/$,+1"%1)1+15",0)7%5*2+!.)"3D0Հ؀ӀԀ׀րــրĀՀـԀ؀Ԁހ؀׀׀ЀԀրـׁ3053:!@/2)5*3)'-#7-(20)%2%/6)/*&-!83B'-:3$%;,4(%(-*8.-%%4-(&2*7/(3,40+.1.+3+/7:!'$(5.4).?0';81")-&.()=9*7*2D30$.+.$+3)*6)"(73&65#0%.744#'$*(3*.+%64,*.*'+(("3(-#1(/( **12' +231)9>(/)05/6%.$)2'2/:0.*4+4!24 (*45*&//.17/-/- #/+(.0/81%&83'6+1C!*(30))$+2*%*6-02(*#.1+),$5-)7*+)*0")&&5&/.--)#+*0'6!;*00,(!-"556'57,+:ȉ++$,/$730(&3!9'-/1*02+*..10!/2<,,-.$5:/8-7)+/+/,15&4*'4.+1#%/"(4,-,(*$.(0%,20,2..9+$03.!.1-1.*"2A/%3/4%1,-;-#'2+20:#)*3.(+6///3.2-#=/*!)5-<3-4-,19:$501(7-$(&'*433-(47./3!8!?) *-1&,2րـ؀ր׀ՀԀ܀ՀȀۀڀ؀րԀۀՀـ؀Ӏ؀рπځ7*5;-5,/1-,?%,56)".%.7'+)(F1+,+%42+=/3,"=74-+'/5):(33*,+/(7%9)/'3"/&.#3&+921!3+1+=/.4'9(1*+-)'/693$7(/,&:/?%08/&+9*+$03+)?264+%0%7/0'"1D+1334/+1--'(3 *0$-)/3:"-$4#-.44 +/.--/5-:**6-"-(="%/'1+*3,*-.+--*,()5/*+/595 +-8'$$."*$,3+!!.16/-9--'8#.%.&71&..'+(3*/./++1.+)#3.)-22/141,,,/+505(.8"$3'#((7 *%220),7)4$)# .1&&-,81*/1),&:)(13;/%/+/482".-:(/-*7/'A4/ ,+0,3)58/(2201--3#(-A&11!:0 B711$36-*..,#@,?(5)813"*2:04&13-'/7:.2B+*54*&.)'8--41$0057/(0.%,(+()2+33&4:?2,341&7)/"$70/&*!5'".83,15")#"0.00--#ր׀ـЀӀԀۀրڀÀր؀ҀЀڀр؀׀Հ׀ՀӀՀف+/>;%0?%6%+9.2;:$'+3*",'8*+7644,#/=,''0'1*8'+4040-0077,.!-;'-(---3.+0--5,"+/'6+3/-%30%02+6'1$/..($)34'4)#7-14..(/")!"0/$$(?0?*2)(2!,(-"841=%%.2, *)%8/,*(%4*5A(1*'/6%$%4.-4>%04/1%/1(*-),0-!8*22>&2+"<.6)+%+05'-'*.*(262*3/'"5-/$'-<&36-3-+/+7#3)0&.1%",()/./#/%-('!2 +,#,53+117#4+/1+'09,01).*&*1+<1%*/0*,7)%$"%,")$-$1(-2.!.#"6/(*02*&.,)52'$3&*&-4$,%-2/#;(-"3(7)+8+(.1812-!)?2*8-,3"04'++9-1%),#*0')*2,&%4#91+620-;)%#%6+$/5.--424"&,2'-)8:64*4%0,'(29+'2! $634,2#0:*<3,+)5C-#%47'*(,/5 *1572(3(1/4ՀԀ׀ڀӀՀՀԀՀŀ׀Ԁ؀׀ـԀЀրӀՀՀրրԁ$7>33(&+)+'1-)2-5<&-:#-)+&1-=:,*3)5).3/6,7&-6+-(:45$ 4:;-/?7(=5(/$1"-.-,+%:"&"13"7.,3445)1*3'"6/0'.8";;-%-%35*+(+4**'$2&*+'/"!3,!2$/'!/',+16,*5,,+%+"")-1#01)3-/)01,(,/(+6-#/!.$/.7*-*.1$/".#0+2-('&(9-*6A%":))0 #5')B(*/'12/).,08*.1.&$-'*#7.58)-$?*0%2%+>-./(0*.21-(1,/832%!/6+& !,-?-#'+112)%)/>+7'.6*.1)$9#+,*+/-59-+'/3)0$-,'39.*',/7*+1,.'%)528!'/&)1/-;!!9/4')&6&"( ./*(-/(5#/5%%+-(58()2$'7*(3*82,%1*)-*+32%.25D!.%((:0'8,$$",02%&33&!1./ '"&1$.-$/3,;$)(!,!.;5$B!%<7+00+3,3167.(-"!/-21);33,14<)5%,&+,րր؀ـ؀ӀڀҀԀɀـ؀ـڀրՀԀӀӀ؀؀Ҁԁ30)61I,9)-1;,<206$/4-<,0-$).1)2/21-%7$-/#-(-+)( 28(9-#*1.-,*)@#1*#9.,&**7 ) **1/-09""5(.4/),'30&)1")&..+1+'*%3' -/1,1+')&1;1#.8(15%62)*/')%)%)+)$.1)-%(.*3'',+61&;,)4'1*>(-&6/(.9$,2/'88$<00.*.6*,+)'-40,1$+'/46-6,0,$$)%3-10.5-)%%2*0+.76B0&,($7:'&2'#)&).-.0*". +9)1+7117'%2-&'.&3&)+$0(;%&'/)6$-) $361+89-&0$4.-32'%2/3!'81140/&)-+8!/(&*)2)2 '6!$%&)+0"6=,*6732,+*%,15'1+7(&)*069-2'3,)42*'5,/'(2>-.7)1"2%5.2$)/23'+-/3*- 30''34--/65(@&'//*0/*'-$5)&4!-0,)-0/3&3).#1;4.#./'-24700,$-/--!*9#?.1&47*/%0ـҀӀрҀԀҀ׀ՀɀӀԀҀՀ׀؀ـրրԀـՀ܀ف/+82%2;5%.)-I/8$(//2/:/4)#1):"51)/4,$,&9+&.*2,)3541/'$+")1$,:/;7#"+&*//3**5-&/),2%3',/' +1+'%46*39')61;%+121,#+&/55526',+/6+5.=10%93#%+%25+./<7,-$))"%#2 ,>"2%+!11%7%303.-/13")%2-%7!'+52-/$?(,)%,)-013)):&"8450=#-7'),**/)280,/%16!'055&3#("(2,(@*61$4%.).'%0&%!(2/&55*//3"3(43,8#8!$/-,+%5643+,<$&'%6-2+*";,8'+6!1($%/1716.*(4%**$)&94)2/- ,%,)./2(4!$/43%-;'.14'0;22%(*-/31*-(**.&7.11/+/:&*%/53"-31%0#,3&&1&& .'),%!)6+ ,-.8(/./,53'--++3 #5**-%$24346+)4&#(5%.7.6'#;7.'+42#(B5.*9&%90'%#<31)66-3)/+5'!ՀـڀԀҀـˀـԀˀՀ܀Հ׀ـրԀրـրԀ؀Հԁ545/-'(5/,,9051=.=&*)1/*373.+<-*&+3-+*391%+":'/)$$"6+80,7&-2+'):0'44/!4+)13/*3#"4+-)--=/-,7#2/8!)0/1/5%2!.)*')5,1./8$),45%>%$4/+1-+6/'+-/'2+),=(51#3/13'')"2+&.#,!#1,5.5#+(<+41- .)913*/0-2)4,-(74*2),-'@5%)'330<%#.2&<*&3(!3&/.3%?*47;+/0 0:&')$$+/77(--2)&%=&/357'%'*"'-!''/#,<*2.3&*"%, 0&21"0"/.;.051",-7+)+02",/#"*4+*.1*/4(*$/2--*//-)(//((/*,).,-('1/1&1.+/4*&-%00+,=!8)4--165-./3,.0,33+!(0!*((,//+,76%5, 4.*+*%&)&05;*)--!-.730(($41%7%0/.,)-4+++%9++,+%.:/*-51.*9'!$), -'+4@7,//))+-,51$#:?9/393,/ڀӀـ؀׀ԀހπـɀրրۀـԀۀۀ؀ӀۀՀՀՀ؁-05:+&)6+9/(3$5%5/,'%)A)00 5'+*#2-6'!"0(. /(2&:3+$#9))6*;()*-*.)+/03+2-A/%+!4*)<"-,*-&.*8/!.(7(/&* .,3+-545.".,3:*)(/+-'7&412*-2<105/3&)(4-$7,./02-: +/53-1%::5#+,%3.,2(87*-,1$*+8:*/&/1&*)24'+'/0@-+'(;..!C()7,)%2./):!4"-&--8+0*'10)':4228914-1/'+-:+:7)/*A0.9/0&'+!071.%-30)-%17#-+&+05.(&/+,,+-#+5)-6&7+2. .&*.3"-/-87&#3*21'431%52,$//.#),3+02.(#0(,5*/ #-77-(60120;%.,/103(1(*/-#.59/=)0.,+(-,60/(&03$! -&.!-*.',&%53.&6':%*1640-*)34!1)+,3!;'474',)--(<'5/ *2&4%&.'-3,!8*&(2,=46""4,;--7>*@537&.8,܀Ԁրـ׀݀Ҁ׀ҀԀրԀЀـՀ؀؀ـՀҀ׀Ԁց3./((,%0(.6)>),;%&%41=)'K5""/6'5/7)));&10(1"&/((+35%3.:)--<+/)3)1)$,0'-'.'*+&-*1*1),))1"-1+$--#0(+(/<.443,)+*0'&!$36)%.5&:,<4+*58/542239$%(0-#"7($$$'5/)21.7)%2//9%,1,,0&.1(3'(%+C)()!)')-,,*'"%%1/'3)72++,'7'#:/.-%-1..'3,*554'-,0+1.31:$".-&0.2+*)3'.* +-:*%*&,3(+'("4(/.-)@32 %1-++/8-/+*$;1(%'(*7./,-.+,+'&3%-+&2)2)!))+0,+'-(<)')4*,,5'/,23'6&9+4/+*;*33+2440&:)#845A.3'.)')**1)573#$6+2-+$8.36-0 )-+)*4#*/2"46!;&.%(5/1++&!0.#/(**37%-%0.4860&)5012"3))++,516$5@63-+5&1:3-1+5&=293/,3+/41$63=(;&'-'8,-2%#4ӀՀրԀـ׀׀ـрǀڀڀ؀Հ׀րԀր׀րπԀՀց"4<52=0+&-40,/%4,' 7&#+-$*%'3/+,2>('8$1,'>:30*21*-/3240--6.-0 50=(@4-4. ,00.1/2$36!'1121-'%!.5<'+5*!/5+/+/&-.'7(,58*,)-,*63'1#12$0&.11!1)'* %&&,0,5,6+/+)* 9.&7,&:)-'-+0;$./,+!2' 0).4'+416/4.%@$/%%-1'4)1/-+70--59,-63#1,23+)4/.,(2&!2-@/-9)1-''() %;(2")2)&0$0'5.-5$1).'))-3*1%=1#8./+-6%#9#624$/+,*&,0+6'4+(6//211).5$+?*,")5&//(+4+%:'(22:.3.7%7$#/5: 3-+21,(1-+/+/*$/,/9)/3A923*6-1+/*9+'/B!/1+4++)2:'"/*0),'%4+"*3/!,--7*)1%)"4,29/"40%&",++4+)/68+/1-1$4:7/5.)30(&&-:7/33=+7+/4++()3.'1.6.#8*/630D327(ԀـڀրՀ׀ՀڀĀ׀ՀՀـՀՀր׀ՀҀۀ׀ـׁ(#;3:73-?,101/<,',..53'#B)!*)32474'8& ,-*6$)5('.86"-/*)7'9$--+15-*50&+()"#&-,)44+-.4/4((05/#562).3))$'(.*<,%3-18)5)6(-'+-*9(63)-0#"5-42+,.4$/'/0(7-)-'(',B6+0+63%/(!/'(+&*$'.>(-0/6,!+)),)1/(+-'.+128,0&3"% %-5(,;/(,5#(%&+)#*3//%*27+)&/*0*/)',%72(9($4//#6'*,)'(1'/ 9&2-%528.)0->*589(-.37)4%0$-#3.443(,-( 3&-3<#3%/87%)17'2'*,"*!./+()#4%..'%+$+*$!.711)!'()-).'%+)7;-'6(+,-&%'7-.$*/-60.&)-5114$0*4-$5('(-)37**18/.*%/-,+#(1#=+.#40 ""*+.1,8088(/,/$4/,-*(75/3.$,!,,'33#0,/$''03-&&+*./%)0'+%/-*)/(14݀ۀՀр܀؀րۀӀŀрӀԀЀՀԀـ׀؀ـԀ׀րف7+%:%)3./(+67)%0!-3(+3,7'&+*0()...3<2/7//1--'&&3&''/,,%7(#)=070/-#-/+'')1+7=*'*C*//+* :/3&)&/1'+/.,+3>+(=-,,20)/.0401$,+.&.3$/7.3/#',0/#1340>8%)//!%.&)3&%-**4#0/&2!7)% 4/.#%)*(87043>*84==1-2,60177*36..*'-+3+15(/%*//,#+5#83*9,)8.%)(5>#*52$1 /.+*/6,4(+,+,1$+!*$4'-)(,33*$-(,-+5: ++./(0).,1,!20*/'0)4*"33-*!* 1463%*/+-B8+4,,%$-3%(9,7.4..3%#%$A-/&4')!1#+'-)(*/6*#5061*,-&.+3/6&7)*)31/6&+?--+*28*,(3'*7/$/+'):2,;#/#+,38'+&642&9(522%#4/#7'.)1$%,%/-,-''")-43+9:-60+<)09/1#&9&='<%=.5410/>:$"$42.5:<0(1)5׀Ԁ؀ԀӀ׀ԀրҀŀ׀ՀՀӀـր؀ՀӀڀ؀Հրҁ:-*3-*6,.(5%14+7+01.8+1*C,0,%''+4*(,,/.)0'-'<,('#.#,%27"798/57#9 2",-2'$73,/161951)+*(3/06)'*3,2."60&'10/90.)1/1)3*6'5+.40*& 63-/333,&312"7+3432.%.1(,/(4(4%:+:*8:)5*+(0+*8+-2,)9.3-*,)/7%' /+,--7 (4*:.21,?1.,(69F+"-,$+:.;8&;;2"+,):2(7'2+/."3%3;39)I-!%-/!'*'#-/$<2&+."*-!+B&.4-,(#)(-)3'.413+(3//1',/"'((/6*.03 %0-,*10%1"'+/*+)%*.5*53/172/5(B-(,%039,3',91:/-4 8'3,37)$10;%3024."",-2,5)'/)/1+'=.&+++- /2%*##6%''+*+(7'%)(43&=+1+,20-11*90)02'3-/),),+-&; +9$,(377*+(82---2*=1/.4/*-/'9!7+/0/&02B"&2+,<׀ۀՀՀҀ܀р؀ЀŀӀ݀ՀՀՀـڀۀڀր؀ԀԀ݁,/&7$0+.24+:./7/144&)44/6-30.++116%-)(+$;).0*(/+'#'-75'+-(""!02/&,6&+$1/1,,/1+/10-+.+3313>B+,/';.0**#&-2#, 46.-5(/8$<-4$+.8*6&1)+*+2)-/3?1'6&1,=/!'+*2/+))2,/5%1:(')(2'33,3/*0,,&!00+-,8+/4'5,$%6+4+%!)8/ &+*4.7#*+)$*%3)/0//$3%,32/##)04+"'02&)<:(;4(.'546(+4)5*-%'*7)5)!$(2(4"5*8","+23*//.-5*4%+$:+622/#,&&-&21"'%+-:".*&'),/#1(8' -0).8*-1&//5,0 3'-44)7/- '#-:,(',/*/,&5')3'*!6,%-,/&)7*+0( "/&3.")4*<1#2/.&(-6&(/'/&.@4%180,&'-!#-(52*)8%+.-."10;).#46//)/-1*4*4-16*(''-0510(7&/(),.7.-0=%+,+644$<;-4ր׀׀ـӀրۀـӀĀրۀ؀Հր׀׀؀ـ׀ڀՀـՁ8++-()>&7+:,29;1-80,56.#257146-29'/<-+#2..2,-/#1+-)-18()5/-5+30(%.#*)(>*4#)(1.:>/4)-7+)"/2$#3,#-(5//62*'5$'.*%((/+(3?)+$-7*/.0.B-+':58'02.140*3/(+'$-&,)./.80)1*09#%30-$1#='.$63%1#**-.)-/184+#(%77##/)1+0))--,30)9)24&"5'83&*1-'/;/)$+(*&+(*9&+0,3,8-(.8//!8)+,833/1)%7/-2084);,$0<-''/3((.+(.4-+*)2+'/"'-+*,E*%2*8*%$!5#*C45'%+&'')2$)49(43(0-5$1'+-(3!(4!,/325'''-.#*+;(3:5-)"/3-%7!)22')*"$+&8<2/,"0151324''6,,3-'() +2*1700.-++5;--+3-"&,9*(3;, +*01%50-&2!50.03+<"+.-*32&0+5!/*)/,+06:%/$,*2)3%-):<3-8)1%.,ր׀؀ր׀؀ԀԀҀĀՀր؀׀Ҁ׀ـ؀Ԁ؀؀Հ؀ف*7005',5-00*/33'5(*+,+60112)0,561*.()7$/6.305L%43-1!6)7*,+)77#2(-2."2-.(/-(5-"-'3#)$5:59-,##*3*<6".13/./1,2).*#$+.,43/'/' "+"0!1')!!(%%)5+1,..0(),0'/(<,2/""/!!16*0,20,7325'.+-+&'5$,2725"31-,)3(5 12%-/,/6%032$.#36&:#-3+*.),)/C?'$.(%,/6105>%/(52"#.16'+A-3+/$.(0-$7-+'//(&&">/$)?*/*.'!',5'.3/**.(&.',-7(,5127028&(%+'364/,*#&&52&7$11*0+.1%%2!0+/,&27*.(.5!'6-&,);*(,-(7,5"(&(.,.--750'5786)2%3A+%. ()-6&$-#'0/9'.);3++/1$*,;4!),')*01;/)%;.23)7!+-,&(%/+30)#0'(%!624*- :*%>%5.(!-(15(+2A-8:*/&).7)=(,+:H,5.3--3*028ҀՀӀՀــ܀ڀҀÀӀۀ؀ۀӀՀր؀׀ـ؀ӀЀځ//4,,.5$52(;9/,''0&),,)+&//#0,*=6,(96116%'++%'/*/,),+)//15))5 +%0$054$.)%()=#/.,-&/$(1,;%+(<05&*4*-+?&*+5/*$0+7'*,4*&,=51%./)*21.)///#,3)-(#2'3,!#23*1/.+0.!+1$5+%60;2-+'*&-3,3+%(0.*6.))&0'#&*+14-'&$460%+0*/$/)0)1#,&)02""6(*!4-)--@6.2$%5+(.1+*()#,-!'C$440/./(2C+(!1,+,*6)0';-.06,.)(+6.002"*141?)./-1.%0(%56,-6 $),73F3%*12/0-451#,072)-6''*+//%3-3%*%(243/&";)4/-(%*03.))"-..0#"//+4(+6)/,$-%1&/+6.-).+'')(*4.6+'*+'=%,%(2(+1/(%0+1.(/#+01%50)/ 533$)&/--)-*0.*187&)401>%-3+A-9=1'.%1--4/1+'-.6+921'34+րπ׀׀ڀՀҀـր̀րՀӀ܀΀րՀՀԀڀԀՀ׀с#;='94%1*05'+-/*121#942/-./1*+0+6"&./-*.&*+"6$('/%138'47%'&#/"563(*38*+7((;.*$)'-%+2/6?,/(37'9%;06!8'#3+'0,.%/#&."/(*),,,')123.*0),+,,%&',2%**1+./.#3,0,9?/0'1?"8(/&'+2+(+*.:)9/&,72(3=6+*1!*.+%#'/'*!0:0)&*!0"-"&+.+=4.*+1)/'4+,+;)..,!2=#%%$4+&//+##14!(:4.+3'.""3!#(.,).-(*#1=01&&104&5/-510%2+527+7*.-8!(*+#*$.#+2/0:?95 ,<2%,4/1"('6;8.)++ ,9--+C.(/$843+= ,/*7/)71."+*(-3+5,*0)1,&'*!.'&%, 7:/.8)%%60(&06-20/,)(*2-&/0.(5&-61&)7+(".43=/9&3)<).)36/&((!7+..+$661/%!/1D$('3&9.+$/5*6)*-4)*(23)#72.%;,+170;ԀրՀ׀ۀրـӀ؀ǀـڀހՀ׀؀׀ЀՀҀڀՀրց !'1*27"/.5-2"!*7!+''0/A,*(1)/;;2/#/.5C2(//323,3<21 (.,33(/(--7'/'.*5*&2(0./.+*3)"D1(.$:-147)!+1,)&/"'"+$&4*43""(%=+9115"+1/+010%-1(#2-4B4+'44*#")&.$1;.03 -..!4.3%))(8().3/$88'#36%()5"!-&*3)5/*0& )'&2(%*!.05"$';5 )-/'(B5,-/2''2!/00!.,*/+.,3903&.'-, "+-2,-/*"*./&**+*.- .0:-0++4+&<1)2'#'/+4046,2#$$%/0*063,$0+$06)-()/.. (/-5-(9953(=12F&&6.+/3+1132+5$%.)(!,;/-+(!06"!-5-,-$!.1()&2*)2*')1/!#*5+4/*(2*/7:3*2*$*('*$()"'5*)-0'-9/+3#0-'4%01:3!"*03.,(&)#%.5*50(/,!9/"7-.53+68.(,/'0/.&=&-61,21>;:1*1("/0ր׀܀ۀՀ؀ـҀɀրԀԀ؀Հـ؀ՀӀրՀڀՀԁ:-.60+537+//++2/&176.(/6@74*.73+01,93#/81',*-4-&)1*<'*#'5)-3,2--&7,0)2-91(2554/.8+0$)*, :4)-)#2+0.(7+4"070/+/)""%.,&,,++1360&"'0'0,1(.*271#479(/1,$,/14-"59/'1-.'0%0#/+,,46)4*4#)$-1%*#+ -/.))*+&1(-&,&*+,>('.8.&2*#+/%+,12&$),0*),)+-)#<-$',%7!%,0A#;42)-*:#,5)8.+$2/3-+),60?).3-,-014-.,*",.&&-)'4$,.*$1+,''?/7/,(+1,5-(6,+.#611(4+01:+,0//&#!'#!/,5)..0&.").;03)91./)/3))%(%//+*,34+1.+8,49'/*--,,...',#95:/-7($3'%)/+(0-&'70*-61;.*/3%'5.)3(-620).728(*'.=-1845($"(/)-3>4:+(25')/*)/;95/7-7=1)+;-&4*;'1؀րրڀ΀Ҁ׀ӀՀ̀׀ڀ׀׀׀ՀҀԀ׀ҀրՀـہ5-).+#2-0-0'6%*!/7(+')!'0.3;3.-6$2--)*/'/-2%/ )0(<67-.--+74/-+0)1*A017>7.+"'(1)1#41110"0+3;,*-$.@;,=+3%9%9&2110:$-'-+1$& +30#$+-1-// 3"7$+#-3&-:"+;.0')1%"'4.2+)0;5",*.05#*">(".0'2-'+3"3*-"7'/1.#+13)-2!+)*3!',%&*:/>3%&,*,%'0/(+-0;*1'***%&/53)+/24.74+&-$&+-5)1&!-0'-=1=4*58-26*!)<'-;,0/*4+28-'(6&27/,)/1*$2$&%)#/)/'+/-)&+614&0-/,/7"( 4+'&+#-/0-%,/!)--;63:**,#8<(&.?--9-%6-,5*.!(#2'-12/2,*6-1,91.09&2# 2%&'/2+#$.)$81$1%** )(>(!)4)34 &5'$5('6.'')--2%+ )4*/'%'()37)+'")1'7%6A.)#%1 7* *-*5()/1#2&!04*'Հ׀ـӀ׀րՀҀՀʀԀրӀ܀Ҁ׀׀ՀрՀҀրـ܁26-12-%/'=7+'8$)49*'%+3,,279.+"/5"712$*0?100&/'7$423=25'35$94+ 7-)%:--59>%/571#-$.*:/.!$.+/)30&4.)(#37'0'17&10%3+&0&2-00&&'0+;%/6*//2#+&%"+-#*'1/$;#25$/0!/1,./17%#,("@%.+62(,#//+-:3',.-6+'*71)' 008'&-2;)7%#33+#.7)0)*5#$( /*#'(-5%C*+:.&-5/85$.$,3)98&*!)+'8&-$42&*2--**"/20-//-*1*:,$0*,+,''--28%#-$0,&2&70,()-/)3+--'81&.#.&&B6-','-$69,+&(&/-).2A!#"1(/).#-0*$&7,(&+4("))(*06-'&0:)!13'*8--+*/89,425#&0/%).,+22.**%"&,#)("641&1+56(,#!%+-(;)/:,$:'&2/2//',*-2.46++6&&.-*7,%%%';0876.-.6$2*-160'=*/7,ۀ׀׀؀ڀՀՀӀ؀ՀЀӀԀҀր׀րـ׀ـրրہ/4B74467//(=%74:9=*67.'/A'+'063,'(55+(.A2"=4&B+%5/(2,.(/(474 -0-3/>330'+0,*&+,/,9,5<%.0.-'1/"01,'025*95+=6+$%?3 232*-0++/$542,.+,@*$.5&29,'5),& ,-1+0-%+0)*.,-6)%)"/)+.% '$$"2)260);$*-)3' .)3$) +"(523"#3+;-)/'$2+-:4%*)+"#/-)040"2:&15#$&$$./03/!))5(/.+=)1,A&/,1'$7*:*//120#.4$(*/$*&5+.26-6-7+--90(/)//%%*5+)&2(+2*6*6*//%&*'++%-37(()+1.+2*%+$*-&-+1'3(:$60(0.6$3$8-.619<,+))//");)*"81#)$1)+-$'*+-2-)/0'2<+./39;0":+( +*),9)+(1'()*+/*21+--&#*-0 ,7(,.2(<&0.05+%+,,-B/,193),,.(4$+()1&22,)25?..8.,,7*,*00!2"0-ҀӀրӀ׀׀րրـɀ׀ۀԀՀ׀ҀՀЀҀՀҀӀ؀ԁ+#.2%(9/71+&-/0,(-0678&44/%4,:$&//1&=47#313100,.78+-$1/!0,<*)"21)+(&4-)49)9(5*(+%+/<-+902020$:.11+/)!)*!,$-%2+/$.2?/35'#55+&40*&7+-.'15/55';)()41)+#,%?+)30*+,)-(3/%08,"'3/+/$%'$'#(/"--6!/1,%:0*+&*--0*$"*&15-46+!./)/0 !./,3/(0:0,2.&'0232'/"4171+*-21/:2/10-/,%354.8/.8!)4*(&(2"2+.?8!*1/+46/%#1100).+&*+741144&%4,,*.E+(./-)*,%/4:%.%6%1/.'-26,)5!-%1'-.+*0('5/+-()&*03>+;)('3,(*+#)/'&-.210,&.1-3/'5/#"*%*7&$/;$5,+,*.//)8(2$'()1<#/,07(00&&-4//.)3,44-55 . *)6>(94/$-%)4,,(21.:,(7+5D2+-49/8+.7*.,)5րҀր׀ـрـӀՀŀրӀ؀ԀӀ݀؀рրӀӀ؀Հց4412;&225<'1/3/'!73./1-,&(1=:,.06(;!-;/&.4-&)74#*+'&1,1*':)&#&")(405:''4)--5)50)(0=%/+0,*"1?11)<)90)%-)69//36+1*-0*'=,11,:. /+((&1)+8*,8.33(&/'!,$$3+8,#,,#(*'&&--2*4)/*$/5'-172/=(,,&-$#)7&/+/+5-#&($&)..2"=0+/42+3%)4++2)/&3## %*E1;-.1+7 (*'88)-'4<-.3&/4#48:)'=4,//3+*3-)-/1"0-,(.)$"6'32(#13)3'-,'-$43*"2&,(-,51&7%-74/&$1.)2/:+,&+1 5%7((,/3,(/(!/2*%),4,0151).$/4 7>&'2.3($%/!0-1)"1*,# "2+1( './&-27/-3$C+461#-),3"'6(-,.(930.&+ 0/-6%5%-50('73(,-*/B-,.!230/3)-)5*3,52.1./5.*(.,4.0627,5/'*/591969ՀۀـՀـЀӀՀ׀ۀЀ؀؀Ҁ؀Ӏ׀Հ؀ՀӀրс,51;371'.2.+5+,+2/%'32?(6''*D/)/)--5&0<404+674,0-0*%3",(-0--!-11(.2#0)2#>/$/3)+32/-!1-@*"%/-,!**'')&#*3(,+0&,+ *-(/3& ,56)/1529'#+'1&-&.'+./3+(1"()1'24%/%5(+4,63./&*:(."0#7%75%/')'.,-),&$9)(*/%).(+,1G+%'+(/#)5+,76.+.*())"0,7.21,00-+',,2-)&34#1.,)4.+/$-"(4-,+/*547.,%%+=1/,'+191/#,*.(,/*8'//*1&*5)%*++$;: 00/(/''#'*8,32!'-1/";)6* +/0/'!3,/7-'+0-.)+*.4.1#&0$&5*+&6%1+80/5+1'#41-1-*'.-!-*0&/$/,85().+6,(&*'2$5)"5$&--./"-'-&+.,$=/*410.,1&,()',32"%+729$&002(307&:41-) -203A=9/0/+/>)+).:5591*:D/,314(/9ԀӀрՀӀӀՀӀ׀ŀӀ؀π؀Հրـ؀ـҀрـրׁ2-5,1"15"&4!=6,16)$);0,21061A0/.(/;4$90/.' 4+#3702,2.+4>3+1+)(%**$4/**(',)#*-&2-2-*+-;++0'<.*/30-+,&+70 *.#(#88-&4,+&&-&(@*0-*66. 7*1,#)3./,;/-#1*&!#(*0%)#3/1,48/2.47-9#'4.3-2/-/+86-#$(4$!6+2%1.+(#$(*(43-*(%50%1081,+3&+*3))2','3(9&9;2)'++%4/.)-'(-"&1)4*"$3$0,42+82,7'(5$,';*4&)-()'07,*/&.6)*.+ 3%7+&62(75/,.+/1*+9;"+)&-< )+95%:%$+/.#-21)!)11/09&++.+*:< 74/,'3+&$4#.2(0)/0."''&)"(/2/))//*-$)7 4,4%-)44/2!/3+'0(*/1+,./0-/&8"7)*-,)0.285(,885A)%6743404&,,';/'&05*5:7/0/32270$/*9)--.,20/;+5/98,60ڀـՀπՀ؀ڀ׀Ӏƀ׀Հ׀ـ׀Ӏр׀ڀـҀրՀف+2;<(1.6+/4$1#45$=-7*,.'.045.!&%#,/")+'3,02;2)//,/8/3B3&*:2,45&52/782.(%5)7(/0"'"-729+&4$@.1;4'&"/.1'0&)6'$9%'328+4<%%%21952$-'01$++0(+*2$A6"+3""4(/%/+$)/.$932*+,:!)68"'%74*)4*+$ %'.-29&(((*$!70427)'2.3("//$:94(9)16.&&/-'25=(#*/5*1/+,-5/#5& #+,,8271,*-".*!+/$*&4;,'1(62&/01'/4&)(7 8/)'300!/+9 -53/60$' !,)+*)%2-))!"%-+1<2(24-%5,+(50;1,932-0#"/3#,/-4-,".2"+-3&+9*-,0!51&6,'*//.#(7(/-)024/"*&/-3)*/!38:36()A)!*2$#/+#''0%,&0+&,*&.$'./$*)5<(5-0* -//'8$72(.,/!+5/75) +3//)#.+-,;.5)%*/,34/'',0+1#A'/*22ӀۀڀՀրրـ׀׀ȀҀՀրԀԀԀҀրــրЀՀց>./7.+7&'">0+>,&-0,/897541)$:"%,+<1"1+.:))+/:$)$+,4*-(3.#*,(%**46&.++ ,*2-'1%*#/3"/1&3$8+4&(.03)8*(&(,-:,*$*-#18,)62(",6$>14&!(0'3472/*'2/)'%+#!$%983(-*11'801"*3;%!<'.+0&& 54+#:-.56/1403!2%4:8%5"/#+(+('2&00(,',*,841:4-&80/ 1,+& 2:/$$''*2+(-//2"#36&(22&21/3-#5+(*-=,-+&0*/+/,25&;$;%,-(-#4%24-2/( ',+-511-0"-$*,1*((/34/995$-,,.7',#1, (%-5)+%6?(5*)0)0.-).2)/&:2*--"'*,#/!6+)+15,%(=$)43'2,.'(?).54/'(.9))?/102./50+)9))$30"'7*4.' (8*-+-#*2*/)+"0&.04"58'))-.--3)5#?$ ,5!435%+/#$/;%5'%:#360/,-6C%&ـ׀׀ՀՀЀՀԀ׀ȀڀـԀր؀ـՀ؀р؀ـ׀Ѐց-(10777./9%49:105*=7459-+%3&(1+&42&D2(2 5!.44)-#/'++,-11+26=+*3)2/))/4*+2+;+$;.2)$1,795-%+2'*6'/3 #47-9%*)!(#3+--$82++*6-$#$$4*5'0.'+3+604,0/(+&)$2++*#)/'(/ - -52+)2()4#+.,/(/,1,(+3'/+.+44/&+3")&26"++*'$'49#;/$-'*0+/*!+45./#(#)+854,-4%4-$(; *#)-..:".2+!-/53%5(,-6(6''-.1%+3+.'/&"&*'.)7-#%/62$/22'(210*--3(5+ /,.-+18%4(!=3+- ''*/-/.$3)-&*/3%&"0)'740/5541-$4:-4-33#,044@;.'./1-%',;+/2%%+ +$0!.4-($&.2& ,:03!*.12$&="#,),)7&-484550)4.%<#19)#,#-&+39-+(+6*(5/*3*3-*'6)!+)&'B2<4,/-9+ 613?)4+13׀ـ׀ҀՀӀڀրրȀ׀ՀԀ׀؀ڀҀـ؀߀׀ـҀҁ<%5*/*)9,(6;)(47'>0)0'!1)'$)5-21.+-"//*<2.+33*'.80):352+/)<)-/7(.*@(0040%5*,7(,/4/**/1&&( 3/&&0.,+)1*2'*).(%16&1+/!&.!95#'/'+*-+0-(-'/.$7%'0*7--12:/$#-2+ /(2/-2+(?0/9*)++")3.0*+)9)4.&'6/0-5014&6*-).84"02&*19;+%2--,12&/)(,+9$:).-/*,(.&-3/.7),52*,$9*./5,),!(-,*(933*6.%722$'-4,,'+9:(0+,):7/-!/,"(.#,,71++.68.&&.8/ ,7%2,015(*$-7>62&6+/-/$28*')4)%1+'4)<3**/.3!5/&,*&*'-3'1'4*,+5;&)$%2)+&.-%/4-(/!#'$(-,.,52,@1)-.592'.-031#+%010:(+----+.*/&"'/,,*=+'$/G1'/4-)6+(24+-6-+-93*.45)490(*5+:,33.7Ӏ׀ҀӀԀۀ׀ӀۀȀՀҀԀր׀ԀրӀӀԀՀـ׀ځ75*.="%1#.&,%--64+-B,0:-+30+'(#-=%4(2/127,145'89-349**%1/&)*/.5)34/%/.-+&7(.,+,11#%-17+*,3%+ $$=--4$%)'A,5240%*)2/:%$$/',''((+-,!"4()9-%*/52+/*+/)0,$+('1//09+*9'(-12.*041(-+9)++&'//1*51%8+;.!71%1/)9,'1(,(11#&/!/+#;/+/18%2/-,'+$-%)4*0?,)*1/+%5)",#'-7.<.,+#<$0&)-.(*.47&/05*8$$+/& 8/350/*6(1/#-)02342(/-/#,,4',%,+',&.-64-&0/#.:!#(-4((,/)%)1/,>*'!&/)2*.8)(-,3-(8/&-;50=1(,/.(**/1%3/'7-23#5,2*.,A7042'*#28256)1&&(37/47%3-%/*)'&$,%*,)94(&&,"&$06,*-62#5'#(,-2(+051/ /*2,,30(.301//(/+-)!243,/ -"'/$*)$.3%,6-0'(<$( 65%**'/(,,8-%)1#).2()1/+8"&)7*;0&$1//3.$."*01+:-01 #2532/%4>0;2/0/1(;!1*$--)'.,/('4$%(&.+"(0-,-'+$/5,17)'22.#0*3=&6#4:(':+2/*+4'++!&.'..,&-7-1.,,$*7-&..% +/(3*).50%53A%,;00/30%,/*$3/2#,/2-3#*3$//#8&+!,5(.(.+)&31"+*!/+,#*4%#)/45/'-*#,4*8( +1"3').870/*+,'/-11//1*9)2!+'(-"+637./)րрՀ׀ԀڀـԀـ؀ԀۀԀـ؀ـ؀ՀҀրրԀׁ:71&@'8/77*+,$1#31)7-4(=,3/86.+'(-&4,2%%$4+&.(&%6/)0?*?,%4*A9-2#05)7#:"(2193/1!/1.'1217 6)*05-./16>..($-1:(/:1381*(2$10, 3-(74 42="'!;&+#2*# 8*1/9&!$"*(:(*8)-&5 $=++()!4,-<+)2"%-/#-/5!'"-201(26(3,/).1+$-$2*521./56-.2)-"(:).(,)'(*+%%83/!=24-3+')1#'.*:)41/$)'$")/*%: 1&/-'1(-,3)0#&/)0+',-(),51(.1+&,0+5$8* 3.0,!$%1-$3)#''#5"'".,8)3255+>.-%"73,*465+)+'!0$0)-+&(*'0+<,(/0$!($&!)'94'/47.+&-)00-(+18%//.,/(00,/"677,,%;.(.)1$*)+2*-+),2,90%+8.+71'*31,3(70%,8)1)3'-,1,)0?8-04/-01/'045+(/3.42)&@ԀӀـԀπۀӀۀӀĀӀրـՀՀ׀׀؀ӀҀԀ׀Հց,/513%1(%0+"326-*6*'(2)1-)2*(.46#+*-,.-#00#$=2/+4)4(%.-=)-+..*-0*(1-4/76,07/+3*5"+/$ $.>*%6+4%(1)2+''/!".=".2!%1/6&<3'3+624'3&&+)"1&'1*'%-412,6257=3&65)$//#()2,&-<*$+.1"*-/'"*74%,2+;(&-//.*(0/+(8%&(.=*@'//,*;,-.26"1**)(% !7/!+2!..#15)2%=*0-,&)(!3)+*''1*& 09/215&%2-1)#5'/4*'+7-3/1:,&+@3+/32110*'/+)45$(,-++'/0"6!,4#.-#@131");8/-429)/,&&-!6.%//>0(/0%6.('4(-1#860(&&*$4*!%%&"/01.(+,(&A$6#.(($"7+1,0'4'2:34/-1+)6**--1/.+&)09'+*/--('43/5,11%&&,+1->7(3!!.-/2/#).1%#@'-)?.,,.+)/-(!-)+06,+ 87,2'Հ؀׀рՀπր׀ԀƀҀҀӀ׀ր׀ր׀؀Հ׀р׀ف741.*//!+!:/"%(+"&7*1+47)0,(&4&'93,71*-2*6.825/<!$2*&*3<+8*/,-;&!&B(*-5 +(91*25$+'/+*333 /B 31',/(8+"23#/(+4*+1),0)/)3C0**/&<&/2!15#5&6-$46*&//.&&/*&(3/)(+1+8(-&&4$,&=*8!3)2+;0=:7$./+-50+..1,.,0 )2.+D'1&05)31/67,/-,#,/0--11)*2$<)4$3$73'2').5.((4%)-&.-=1,=(*;,2"'; 1/3 85*5/64'':0,-'+ /1'!$#9-/&7*2(1.%+-+.'0';)8")13"*3((4"4(//(+&512/1.51.$(#<%+4$%$)7.=2&"(*.3%*/7)&#.5(9#)9*/&$."7*0*&.*,(6++*248"5/)),-'%-0(,.((6 #0+52:3-)0*+*'037"*)'*-&'.)'35,5+3-/(4%)/!!$0/10&J,2,399 !)/<53-0,*9*-(.4(+*$)2/+рրՀԀҀۀ؀؀րǀЀԀրԀ׀ـ܀ԀԀڀրՀ؀ց33%<+/21')2.8'6"1+-*1334++-1.%>+&/+3#8, !,#+')%-2(91***!5"%*-/,'3*-0(,-,2'.*9;/"/-5'')(82;,,)-;-'9/'&'$+34'33)'.607(-1+,7%/(,1 *#&"()%'4%!-0.*'-),3'+2."!043,$,-%:!*,+)2*-,%9(H $$1,..-'/)8.'22%*3.*2'(4/.9$&7!1='7('11&+#27"9-(.-36://+:"5.(6,)+2*2#-!#1&35,',$(#++++!2,5*,'31!., %*<,<.,33)+1-*)$-$3'-:(,8./%+1%&(3,%+8-2'%#-0)4#&.*/13"'*,*#0622$#.-'39080)/&(3:29&/-943)9$/(*$248.'(.1))*2%'5:6.31./122.&+3(!6)720%2567./).,);,.-/+&0-%.-95310,1,<#8-33")2/(&+1)8*)'*245,,/6"%(++09%2,2.+;9$30+(7*+(112560 0׀׀ր׀րրԀـՀʀրրրՀ؀ـՀҀـӀ׀ӀـӁ0-%.!31,,61+28+/%+3'&+0:+11>*4$)@-217'503'<'0620/"6,4/3(,./6/./5*,&234(?+3(.'(4-.0(9/+#3+!-0')3')21$8-)'+.-31*'7/137(02/32/&/&11#%,"/5$'47#3*7)3++*++6,+&3%22;,+9(3($5"1+#+' -',-(-+#,))%-*'+(,2-/1- +'!216"'()61$&-8-8)-/0:20*+"-'1.&!4/=" 2 +'9..#01&/53-+.(7**/-1-'2.1!4.*5*52?21'-/7;-'**,21*0*$'&-1/-1+/('#0(%&/%/(-(7,2(>.+.=%/+)1'3+2665:*-&2-.!*0(*#!+'("+ )-//$%*)/572%3'2/4,'+#1"'()--(:+-$**33'(4*)!3'240)5(2.#."?-'"/!7%$..9').1(&),)03(,('1*6!2215$932.>."%%7,7,0)-9/1(33.03352(''*,7//)3),/*59%0րՀԀـ܀ՀـڀӀƀ؀ՀڀԀ׀ҀڀԀ܀րӀӀՀہ/160/277%&84,,1''5&.0.+$..,--(.4).((4,//=*+(4+*(G1,+09A3!'+5,.(5-."'0(,:)/60555 5-0.1%6/5)*)40'.'%+/:0#-1;*!+=-2-+/4,'.,(/25'(/4&&&##;4%*8'3%5(&,!(1809&-$.*./)**30#916>00("3//-4,/3"A1,."-25"4/&1+&,9)&!.29/'%* +'.,#6+2%)-2/4;)$/%-5+0*3./(-*."&'07+3)""".*$*'($0(#-&+ -%57*,.+5,36%/6(/30639452'5.)=&+ 3+6/8&!)5471:24&6,-%1/+#,1*/31*---.%*)(')*31*2.713.22.*',-(,,#1%-':6(%-4$0#8.&--0<**+-.*-1($3/,()#'"+$30/-3'*847'+#-/&7#7-0))1',3+"0,-*1.$&/-0-*'((*&++-8%,+%--;/+#/-,/EC/.2-6-1(&50/8-;1#/<+7;.1<Հ׀րڀ׀ՀҀـ׀ʀՀـрր؀Ҁ܀܀րҀ׀׀րρ.28'548/#=*>,"".23))/)4/9#(*#1+-1/0.-':*(18*4'I'.#.70!)83$/1%2-50)-*2*9.,,36"1/.:):#*#*+134-*1-2))-B3!3*5+@35 ./672"0:(+*+06.&&%26+*2-,--.),-&7+%6+.03,+-/?'9/(1-0-+19"4+.5..(5(>$,:$;%&10/&*8%&8)81'*-1$8=(41&-$.%$265--()&-2%%)*'1-4$*(#,,&5%*1.' +)90,.()&D,(0+%+'5%&,+$:'1&/,9'+&$.*4)@3.>#0("-/6.&()-1!0,!&%-09$"1,$&20.3/&.=1'<+/.32((3!<40/")*86/)0+)+1."/!00))'-*)/1>**./%$-9**(94),127; 403!2?0/#7!0&4!)-.)-3/ $3&+2,2)-,7++'''*9''$!-;%&.1/&'*&.":2*2-3+31-, 2*." 1-5$/4 #7$(/2/*")7(&+/+3,/0&3+/+1'0+.-+..-&8&2+.,;'0)5*/#1D7,+.36-)0003΀рـڀ؀ۀ׀րҀĀҀրՀҀր݀ՀԀـ؀ՀـԀ؁7. -.,,(7(6-&$>-55:9?/.')002+!2 5149%+.*>/).;#0,#*.(252'+,621&*/+%2./#$(@0+&-3+'#).516#4,",--).6.:+9%0**',33*17#&'1 ,+#"1)3'")+9+3/0)* #2,*41.%/45,)2.(1;--:$9&)-2/%*20$&()3(/''*0;6-/3&/#(*(!04?/($+#5+1!/+?3,+2857*40.:'',:,)4%,,(//-((+*4*#6$)-9;54+%-70/1*7)''$-0&+,-' ' (//$.6./6/&+;8(2";*$/5)",$1%/'3,9%8*#1()0(;5%+2,!'2/*/&14*)3#%-$)8")32+5&*.*+"*32"05+*370(3)!-4,7'((2#')4)-(,)!3,,+70+%-6),+?1(./7.+2:$$*.-8#&/+(2)51-72&/(6:360%-:$$!--. 0(*-11'*1,(!+'3835"38;),2%1;(682-5ۀπԀـ؀Ԁ׀݀ՀŀՀрր׀Ҁ؀؀ـր؀ԀҀҀځ"+(02(44/-.5-/#/9*),.:/,',/7,')-/6* 5/)(##-&7.2 *$4,)B-:&6--%0/.+1$,-%6:=7200.$!0--#/-2,#6- ",10"**#**6.4/).."@:+)5.-*+/*,-*23)#84..#&+'-**)*$)7(/75="2'91%=51/++')/! +3.)!-$.,#(41+1,)5 94.71:"+*!+$/2#(*%&'$"."54('0.55/!/,*!$41+8*-%++-,2 )0+9,5"0*'.+','#-%26$%%<,?*.$6&1$/$.)$+1)-2(/1&,+($%+3.(+./-&1/96?62!52'.6(&90*3 -,;)#.2'-&&*4%/ +03;+*,+,'/;39)233'/.#&7")#-%,&6&07(/&+/6/-='.,),(5$4#)/+(*0<1%$8%%-+# (-")+.2*+.:- &5(7)5)*,,.-153250+%&//3/1/.9:68*A;,()'3,.,*+5+)#9$ '/3#'/5(/*&4*,&>'55-1+7&$+-ӀҀ׀ՀԀ΀ـӀـ̀؀ڀӀрӀ׀؀؀؀ՀڀҀڀӁ /*110*10*'18'+)&.3*20;71&83+)*!'.5,50+808$$.'.30,C/%0)17-B 5+#94+-.6-*)4.%,./5(112'(0#=2('+28$#//%)!/!&$2.,(43&+$(/,121"1*,35'&%*/,!6=&3!++.(15(/%(*6$(;,6'!*,/.$41.)2(.30+4%',2*14/,-+03&75-1!5+*$,6-2,$.)8&),/0+/)%*2-+10+101'1)6&'4*7)71'2*11'++.1+3$<+$,,.*3(217%#31.-/+!0-+(/'$2:$./01',!.(+8 . *0.-7.$,.'#/.0)0)',$%/!-7 /**+&!809'-""$-1/:6.@-0))'/,*5$.27(.)A#/1)2+.21;4.3 ,)%/27(/+,/8(/6*=%2&#)4,1.2$5;=)/-'(+.-;))-/)0*$;./271#9/3.&*-/%,5+/(.);/-'%-.%04(+#(0/1$)1#.39(8%7+3"8,3078+/605*'--)*',.38-:8+'؀րۀ׀׀ր؀ـڀƀր׀Հ؀؀׀ր܀؀ՀӀ׀ۀځ5.+13-..=%,2%"867/)(5-2%'*151";,1-3+69!(('%.13*-8&,5/"'-/0,32.8)(01(/99)..$1(%-!($0');0$ -)%.C1'0.&1,&/-;4?27)-3),%:(2);$+0.5.-#+!+3 /-%/''/$+6$&5)F+$'+31*#0,0*&-$'*.'(0**2224.,'-)732.--%36%: !%,/&5(*&&'(*')47-"1"1 4(1!010.#/9(+#-(=+($1'&64&-,5<&%'1/3%*27*/12+7&%*A20'0#)")0+%*6572(<4))<)/%55)*.'60.#'481,(,3/B'=.A.5$,)*"-2&0. "--/<&&&$,#51-&.'+-&()5+,)&-("&,/1)/'- 3.+./%(41($>;4%&77.--/(6&(5?'(+7*56+#6(4*/-2*+6081571.0G$0-74(+*.56,'3%*$%.%/0***)!679$)0#4*/)1(&*2/0*4)/%':#+#,+08/0142<27A3#Ҁ׀݀׀րՀրڀ׀€ڀ׀րրԀՀ؀ՀԀ׀ԀӀ܀ց03./+10*4/637'.-0$>/'+1+%%4)25842'1-0,)72-6-;/#%,.9'5$()*4)':7,1*"0-4.)((947$.0)/-%-"#53,.-6-8'.('063/1*01=/1'+).-$#).1+-*+/%*/%-*+2)'(1"(#$.+"%0-,7'#8'6)-!,)%1"'.-35-.(/(!7)&+$+-2#;&.-'..--''-*1).,3/$=/4(5**+)6% 9)=.,(-20.++(2 (+-,6+&(+*, ,+%0-/'1-2#3/#,+',./#53*('-3.'#82)+/-,-'3(354-(+2($.,).,$36.720,/*1.$//6&'03$67&/(425(!86 %5,80-?$*.-21-)2%9+4.2*,#1,*/-(+(/3"&/+4372"+7,0&."(3)*&5.&5-(%%*:.0?#(195-'*/6+*:2-;B#-7*:5-2.%9*):/+7;)7735,,*5'."53+-&*/217.;6+1-9"4(9׀Ԁ׀Ԁــ؀Հـƀـ׀׀׀Ԁڀр؀ڀրـՀԀׁ+$:(>&/3/)90/62%+;,'8)%--'/1+6,$%) 3-4'&9&(90.2=1--'6+>0('+ &-1:71('1&1.+,8,-'-7%2!'-/;,$**.+.0)+'-63()-2&//&023(#*4"A2&5(-13):545#&+!"%2*0"-43.2#2+&!'%+#,))6*+'4,*5%8.'*/%0A1-+,5+5.(9$2-,.,26/'/%&8'4.)%(,-*<3=-2(.'*("+3417',($2-/9'/#++,)+,)1#4,%16*,1)*5/%*#%$2*)2'1)%#1%1'-9.)-(0/2/(),('2$*2/)*/+36)2E0.'/.'%,A':0:%8($#.%-.'1+%(7>1/!+41%2 ).5$)$5)*4+&+(3*,,%B%%/'0*7%4;@3)6$&,**!,4,8+-.#+*533)!-6,2(&.%5%"13$!).616+'1-$"11'');00.)1-(>1-*+,!+7+26*/1)42+),!3((&$5!("3+*.(:14+'&/6,3'<.'--*44*1+)'//(5*44).1--//3/ %/1-163-)(49)$$4/.+3,8+7+5+*11/6'-0+**)4*$85-(,%29:,..0*,.,5$1))2+2"%$32(9&0/6,2'5'+*(('*3*/2#5,'5%22'3+*<03+%1117;/+34.!322 &).7/!2)"!&<"),8%*2:2('/&/72 %)/')*#*+'$5.%%)( 1-(()&5'0&,5)+1-7)."($26'6,)(5$/#,0'/1''-/()892(-*!!(0,,-33$+)/)<0,950-+"/Ԁр؀؀ҀՀրӀӀĀҀ׀р׀Ԁ׀ـր؀؀ՀӀ܀ہ)7+';.6,/+4862%010-/'0)K10401/0*)'%54+)3')66*4,/36-+)*$,)(-*'1(.16/-0**+/5%$61%(&-2485**>65'7+,2'.,920..%&'&'/08/8,/*0524-+(),&/%+%,-/$=3$4'&/2/1(14$(38+0-/)0%503%'-1/33- 50'%,/&(-4(.2."-''%#/))+++-!7+--7)4*33!(+2(#0*5(&"8 %)/)%4/0.1 3&---/&,$*)62/'(!5&,,(22*9-+--;.'1*0"--767//0 $2)72-2(&9+0!-4(!(3-)),..0.4**-'&'/+'0.-)#6+.12(*++&/-3$)/#*$7 *-7+:!/$3(.3.&+(' 71//331/230$/*(4,318!3/&)?2$$/5/4/'17*51>%*>#%06/3,++/++;-&+)90*1:%*+**,+7,!2,8*,+%1*.%#865%,,++<ڀԀ׀׀׀Ҁր׀ӀƀπڀӀՀԀրڀрӀڀـԀЀف'*'"2.!>+'&25/*/20141&11)/:366'3,/*6(&;7235,17#81+)'31-$0,+/2/:.#/.#311;#-06-)6*2$('45-+*-0%-#8%19-%1/4-+-+31:+/5+'$,)0-.365)#)#12%<)-8-0,'8.)*"7#+ ./'9'*,/%3,/)%0+)&0"3-*+4,:/#+2/':((:.&*%/ (+'210=8+8./,35$!&(/'+2&-8#(-/4#44//0()4 :/;(&%/*1@*7%63/"/*4/5--#--&5%/,4*/-+.5/6%-#+,+"6+)3'021))&"1'-($,+6&62,$5/5&14&(%#,1.5*4+-*:- 7*%(+%+/,4#-#3)/01+,011$,$/&0/!-2-")&'.*'.,,#$(.'20321/#")., 7**,"'/?*.87++.%"454'32!/%%("14-9+/2,5-1-3-)).5/(*-(54>,17.$(:*16--+2((2=+/))+-?4&83-*..0)6!/123(045)/9./);.6'؀؀ڀՀـڀրրրÀ׀ՀҀҀۀ׀ڀԀրـۀҀԀҁ811*.*)/,3'3.25)608-3*1138,(*.(359'5#+.$1/8 /"0@1*')7+0=+0-#.,*-&%.1B3.((+(96--7*2.+3/%%%8#&)4%5.76%+.:1%6721-8-'0/,)4#039/43/:823+))9.%/160#%#1+715)$3 *4-2(.*/+,%:#' (0).&1,!)'()/4(12-.6'*6(,--76/85;#'--%/1*(,0,-0,/2&"/50$.<'*01,%-4'#-%1,+2-*2(5.(.!05-!3 7*56+-(-'$+''01$,)2-2'2/192+1%,41-**4+5)+$)1*(&-)-)-0.(*=(37/06")8*$4+)5$%4!00,0:&5./3+/1,%(,37+37(22:*+,'#1A1(0-7/4051(4B#1++-8-?'#,#()7276(+@'.=*.--/3#? %=.-5 /*-="4.&6--*9*$'&.)>0-3(0000+7+"&,8&(3/2$2$(-6'#=)00:%%42*L5/*5(3-*+/.0&$2067;)52Հ؀ـՀ׀ӀۀрրŀҀ׀ـ؀ـҀ׀ڀ׀؀Ӏـ݀ہ/-'(/5++4"622,)08,01(* /+:/(%2/+-;6.((1'4(93-7+<*703/51481)/81:/<-*$)(.&/(5$/(%)*+.>52/,%/(9-)&/.!8% $$/6,('+4/1/+*-21&*9&.22 )=%+%/,-",(.0-#46(:-$-8% :53*&)'.*2+"$,*'1++%'-0*/#5$/-1-'%+28*-)13./13%&*3/!5++60-$+:%)'56-:/.)%*++5)/7*)0"*.( (1-!0!(2$*%=*3/&,!+)-&7, +.(.(-/-4,,*:212*22*&+0(0B!(,),)0'8110)&6$43 1:1*7(9.;4&,+%*1/0+".(.%-",0)*20-')0)#*-*+-/%''1+3'*5(-)&'',(:62+" ((-;/$)1.'0/$##8/,+02*)H+&9352/+%$.4.01(( 2.76*1.*17(3/$.()+/6*%7%&/)&54+6364!)+5.-)-+%=".(852-$&!-):6+.*/#61"5&,+*7$/&)(%/Ԁ׀ـրҀـՀԀ؀ĀۀրрրՀ΀ӀԀրրՀـԀՁ4%3#162.<60:8-1+'*42,+$61!/5,:22)64(14',#*/0!5906- #;,7.)3/0023-(+015+(6//1 "/0/,57(589-*;%0.!'+2=-+,'.+&#-/& #9)))&4,8)5&,$>/.(/1&+&#/5-#(7(-)/@01;-*+-+24-(,#$&( 14.*/'&%#/2')0"/(-/(2+6-1 -74-0,,60.3&'@129/(0-(*-**0&!#3+3$;*(4-'!&!,8-*0- *0.+30-,2/1);5071(;) +:88,41!350(-($&*$0'8/(3$$*1+06/,"''/7-5(%'%0/(4//-/)%+*3+&%1*/-1/2+.43(%-'1 +/.-'#8194+-+,+-04723)!'7'!))9/$=-+4&"*2542/*-(*/81.'6"&/(85/)79-3++65797/0=46)1=0/,:+./6//6')8'1'&/+*50!)-'/#+/2%//+'"!#:03/-028-487&7#''5*)*/=/2',7..5/Ӏ׀րՀЀ؀ҀՀр׀рــԀՀՀՀ؀ڀԀـ؀ہ-:)-8;$*-7,,*2+1,09!)702;1.'0,&/5#+6-8+1+&/7(A.5."$./%/*.4)&0+&(-&2%6051$0##'9.22+88+#*70&7' 2-#,./:+-31.-/#$!32/9-1*94*,%06,'-#*2)&/2.+00((( 7'*+-5(.'%:.-#.$33!)%&$%2%*5)!3,06(&.)&+03,-,.; /4'*-(+4+,)3/$.882'0:0(/)*'!+5+30$59'550$*-&*&.1+-5+8+7%56%./,+#.*$)4(5%#*(:$,/-%0)/ 5&42*(;;,81.&(-1 &),0($&,509*@)),1/'3$)45/24++16:/66.<&0*1*,'/,-5.,1*/$1.!,81#7)1(5,13%%&-/4(-6&2",38*0#/#-+,+4*%&-+10/.70'6(88"*1)5'.+05=<2=4352,E/)+*1@7$),5(-,+#+&/512-!.4-1-/ '&,-4)#&%2#0++3659(3&.0/,6'*-4"')-+&C5?-3ۀـрҀҀՀ׀؀ӀŀـрӀ׀Հ׀рــՀӀՀӁ(../89&197192227.1-.23;&3$.')1'22$)-/10$*5(,$*#-6'--1(,+ 6>'90/('1*0$670%/.060/#,(2&*2-,3659 &/#B"()-,#(00%0&3,/)4/421))1$)59,+323++)2*.%%*91/5(+-3)+/$46'',0.*&-"20056-/,/***.90-3-67*&,1%+'53+-%&/$2))1%$./)/7-#'45,*)+#:,"-7#.1+,*&)9-.(5$47+,.**+:&/$-/0#'(,1'9)#%.%''94.11,2+#,3<2:#!73425465+$2(*%#413"C0) #;2+$)50%+0"<5/-22,/=;)-06/482.6/-)*'/83-34B/+0.C04741954*<ӀԀӀԀۀրрրԀˀӀր؀Ԁр؀Ԁր׀؀πՀԀہ9&-,,),7+',+*3)+6 $2;*"(.4!)4"%;(-+ )/(5*!'+'&#.-'%/#2"0(01(7# 5.$(6,#03.&!)=2*//4+(*.(-#1+<"0'&124).4'1&(451/+&-;8-31'%0').,"'*,-&$,3)//)20(+7&-(.(3($22,+/1%4-,%+3/'33(+:/+<,"23#6>8.&:)-;''*(9/0/-432+4&'+*-'**1#(,(-.,.&7(+,,&%1512*3#-57,+5$61)-?-.-.'0%4,3*7,/&%/*%#);-#24.6/4$1%*&,-'+/58&%6,"77*8-+"'(=$79#'7,4'#'#/(18'2&1,'$(2<. ,/5&:+3%#-1%-"&.23(#..) #152!01:C.0(,+2,,"9*7#0-)""*(..3(14,*0'+8.'(,0/.17(/1'7'C0C953670:*:?83:531-32&-'3%&#/-2)%$0718*).-9-567/9+/(+-3 481*&+1&&0,%60.-0.0!+,׀؀׀׀ր׀Հ׀ӀÀ׀Ԁ׀ՀրՀ׀ۀـڀڀրՀ΁@(./,$-')<%84,338'@3:--15.4,/& ,+32:!87-6>1-@727'34)))&(/+((*$1.%0+,)+)/5$)(1#2.1%&,21&/05/11+4<3*028,3/= ?*)2.,;-2()*."-1+,03*.+2/04),/-+)*52+)"5+?-/+(F#/20154$,*%-5-$4.*8.%$$ 78/-6.--6*0).#%53&);9%+/%+0*@$52,'$#+()-);"*4*3%6'+-053172'*11..'37*/1,&%:*.%5++)/*A7!!%3(20+#*(67,/!//.252)2475*,32)&4+3-0+#(0 $.-+,+&.)$+#''0.6;7-&3 (+:&,1$;-+(%%&*11,'//-11+,(':%'36)'.5 &65*= '*.,(2(34,2+)0'1B!1/-%3*/32.7.0-7)+)3$6.;*5E9-A;B2J=661D4:4<'%+,+-6)+"+-++2%*0;+;,4.3286'#''7*(.&+%/5,(+3,)2/=2,&&A(15'4$--ӀڀـӀЀӀԀ׀ԀÀ׀؀ӀՀҀրՀӀ׀؀ــڀՁ/3-333>,5(69+6.*(6,-/,5!9,,5#+120--#+49)-"/2,$,:/'&-/(;1246.%0)/.*((..&3/$.4(33.4)#0+'0$(-508" 838'#..*/5*$'1)+##-:31(2<(/)'*4<4*/+2+@#(%)($ #(054/1+*'-6%5+-17."+'+'-%*+$/4#.*+#'+%$1)&5$)7+%C*0',%'#+ *$3(6+0'-.#++(22*&)+"5*%1*50-&4+,/+#)('*$.7+-/7*.*&,'/;@"A)'8'+/,?1-:',2,/174*#/+43$'05"'/-%'.+/+,,0101>.$,01?)(&%( 42-4$/-,&&+;//5,30E&,-/.15"6"+#/,/%1"/%206-4!,2*-*./%-+1-&(5$*%;00/'62.'*'+30+34(*(.'*1/325A:59LEPC8J::<=/7061:2!1397/&&'17+(:-35+9,)?6,*7(.>'-.%**.3,121.(6$'0-0./,*+3);+'70-րԀӀр׀ӀՀԀ؀ˀ׀ҀـҀՀ׀Ѐـ׀؀Հ׀܀ف;&,4++3.,).7"0,/:+4+/,/-7(#*<0 :(739,605+)+-+5("/2*+. '4'7+*0--2/)(029;!64(7/>+8(*--);6'*"2-/*#-(84.8(!02$4*',+'%09+$ 0/:"//-1*/#3.--3)8+5/* 510'/5,8%!*(65*#5)6-33)$22,"(3,D#81"#41.:)200-42-&1/(3-B8137+.%#)/2%.'/.5'"!)2#&'72.,(&4 )/3;5-*"+(+.-,4,)4-).0'+02:+*//5/+/.,+,&-406.-*0&'*,/-+/2.!/#!$*(&(/,9+2-+)&&. 1*-*-3;,'/(.)!( =+1>$98*+71<:.',64';/--('.'4/'.5.&)!78).03)!11%.!%5)&2+<7<%5.$%7//(0=-$2-=8"/',B9'7436E."'1-5% 3$+/$+,-&-"-#$#'*12(()9'+',6))/+0'0#'0(70-32!,/0*!.6/141--.%++&6$4)!9&+'89%*)/-6/!,$*$8#/*5-2/*1-:%*7&5./7#,/,://(#%&'4-0/7)+(%2+-5/-)/#/-#1/6%+."&/49&&/(+,-(")(2#0462,&-/!/*.%+1*:/47+,*2:#36%2-*1)4,0)(*/"7/.#2"=*-+%1.+2&4&+/'8##'/"B)621+*0-) 67*.)6**-)3&$-.+$6111,"(,.04,-/4#(/#0"-012##'/- "-2#,,*"("%/,&,:)4&6/(3)8410*0&-54/%4*-)*)#2#*,)$0*"()/.,+5*,2$2'#%2"5&/)//$")-*-&;$5*-,*1%(9,-+-*/3.20/)'(+%+*)'+&(3)!*..4&$1!,$4.! 5$#(6%%&-(%3+3''4%-4.$042-220 &//"(*&#("724"%!3)20+')69,5' '/5(2'%/3+9*(/"%7843B50.-0)#+1&22(4/&&//$/55+*/A&/)5&--(%.*4!.,&4(-0.7/2&66/*,+'.2.27+0..6$"&"5+3%%//%)$+/0$<8F+1,+ 1:*.4.%'#&&--%*(3%/12,710('+",1,7%..1:2'$%A)7,+('-!4(-+'4672."//,,./+=(872!2.5+- &7798/#12%0+/'(=(/$) +/(6/'*'*+!6"@%3/1*+1.-)(1".2")9!5##&0.0 '+;%+2/$0#///+603$4*-+"".2:;=(,.0/3&'-/0'1/)00)0-+*/93%'1,<2.!1/9,3.)-3#C#31-11,45,+9$&2(4#*5)+ 5-,#$".,.:7@0750-=FLOpr|sQTB674108-3",/,8!!('044-'*47*(6'43(052/6*+/0;/(058.*,(24/C$$;1-(@2.+Ԁ؀ՀۀԀ׀ҀـրĀڀ؀ڀрՀՀԀրրӀۀրՀԁ..492(-)22'$50-/&-*,*26)'9.3;&73.;5': 0010*3/3+-/85*-)#.'/1&6))/%2/"0!'3-0,41%0.4,43%9-/+891(+4)*:0.:'+*0-6 %,7?2,'.42,20-)/)10251+;.(26$,&'//".2/-483/8/).#=7*0%(%)'1#$.*"+4(,5,)0-.2/:--'#$07+0+/# ./7+:7")4'39$7)1#5)/-*1//'0!6$8(1"'%>&;921+* 2)73$(3 #'.-5@23+$$4,1/*#2+*,6 ,5)';2$/&-$-%*-(+&1.<6& *3,(-//*0 .'345,:*5(,4-2/)-2"'&)9))3&+%/0)!602- ,*90(83+>*$8'!4(*2(0:5-!+),,!$'.(.'$226/*/.0!9#%#(5()'1.)+08@7H;BjSlq[JPD?E:.42$*2/25*'->-31'*+,)-/00)943?/-4/-2/61)-()0(045.$8$27#?,64,+5,рրҀԀӀ׀ҀπրˀӀ׀րҀՀ؀րڀԀրՀԀՀҁ2%,2$0'#95:*0.&1/($@6/)060-2,/.*>0/$01'%*7?.'(&-,.!0+//%... (#5(15581+31'0/*3/-%2."',-+*(*",0(%2C#"#.+,.%5%181 '('+'%)3) (&%5&1+/'//!/A!71)=(.#6(&#(,*2/..87,'0#33=-)*1'24A*$34/(/!7.6"2+79(1$7'+#."5%3D,++1-28',+(3/))+-)-#4/2#5-,),%"2002)+0 /+%)1)"*+:3522*6-'.%#!007*:'-/3'569,5$$7!915%+.1019'&&*1%)1.&/%-#0*.'-/<&(--571*-)/*7+'1.')6,"'4,?)&+%2)"*1%2$;,(7(+/*7 (%2'%"--*)('(1%7/3'* (&$')/.*483-1"-383357&>/496JEWa{ā΁uVLOON7C/)3&-&(5'6$6-2:%))&7%=0.),1)10%)375!,&+(1432(-&.1/(2,24%02(//ՀڀҀҀՀրԀԀՀǀҀՀ׀Հ׀Հրۀ؀рڀڀрف*+0)"-2),*,3,;A'0)0%1 %/1-+5 .0(3,42)41-)0.*/08!0,. ;-9:.10,0%.5.1#=.)'*+*23+ -&%*@#,*+8*'%%'2-:#)4+",/37*9/*)2$(.%'-&''3,-.'4-'16'53%)-)-#5)((,/$()2.#!".-52:'37,*#"8,+3+'(/- )'?)26%&#+,'83* (&';&24,)+0(-4!"0-'43--';!/)%"(#(.+*#$3).)5+(+22-3'7',62,-3)+&*!%03#*-/,/*.4$*%#.5)&"%-,7(,%03)6*9!)2,//:.'5!*91-/*&$)6+4-',%.35;%/57@,>4C<%,/(-((#3)- 3-09&(-1#@/*41-B.200/3)*8*5',+86.&1(0.0*1$#*6+5'4&(7"($ +,3(*1(6DQTE^_Áˁˁt]RK:.9;0+E/:2.,D1%;6**(43.)=&*/50-()5203%#<1(66$0/5A9+7'5'8-7+/$#7)'=%׀׀ۀ؀ՀԀՀ؀ۀ؀ӀҀр׀؀ӀՀՀ׀Ѐ׀Ӏف2%21*#9,(66(*/@88///+41'#0-*$&"(%261'-1+*4150+5%8(51'/ 23<1%+#,$/.5)2,?)/!64*43!-+,1%$0&3,)+00/0*%1B5*0,5-5*)0".%-+//.%-"!/*#20!+&(A%,",)#'9,-./.";3-3//*#-4#(/1$$<%7.*.-6$2'*/*')418.(6(4,%*&+1046-0*031+)0)!(5&76$)<&6)0.0,":!16100+/2#5,..&&3/#-&.'*-43+.'/+.093548-,/'14"&16%-'&,59(<+%/'*6-("'-$3(+2',/27*2+2'%,2.1'"/+1.'-:7%.2:4%@2B,'**2.2/#&5*62A&%.-*01&.(*-%0/*/!3!%)#-!-#/53-/.-5!$=%6132.4-10&263()+729;WRi~ԁՁցʁjH@E8?3<54A6/76+-2/4*2$--2%'*5&;*-4+/.5*9<*-08)!=6)6*691'349+40")(28*؀ۀڀ׀рր׀ڀ؀ɀӀ؀׀؀ـՀӀՀـ؀ۀـր܁!80/7%2%24&2$!-)$8/.5-$56565,+1*+0+2)(+0&+80<0-+2*/91'/61630'),%+64,1,%$+,)+.6%0/&4&*/)*070+,')"%85)&*.445-7/&4%8') 99+;*.4&.<#21(-+1*3,(.+$-$,5*846(3,8,*%('(',#%* ));2-$4+1/92)010 5%,:%/*)*.*13+./*&%4"2)41)+/)% ;/2463/-+/+#1.<)(03/,*)#0+.)-%1(5)')3*,)#63.3:*//0.)5, 0+72#'0*02*:#3/(2(!'*/.0#-3(-(*/A')-,+6,*)*%%+50.#//.)4<'*,7%,.<'))+0&,/'0&?2&*-&3%2630*(#-1(,+44#7.1**/&''''+&/0(0$')<-'015$48&$/',#.0A&/D8K=NMqy΁߁΁ρ́hx[aD69G+=48C),1+).915%%!:0@+("5*8139"4)*+./84*%,#/)!#%52&0:12<$(0+/3':׀Հр׀րԀ׀ـрˀԀ΀ՀԀӀր׀р׀ԀրހӀՁ0@.5)38;')09*'$$&*$&/00-7-)(' 32..''.'0)+2=!=6#:)12-47&+2-3/7.<*.:-/--')/("(0++)(6.'32.$#: +.#!.&/3#50*/1,.#7/0 (5*184&$(0#-1/3%/.<& **#8$700./466')03'.02..3'*20,***3-,&-#11'+4.&3%:8$*3"?0',,3,,24%2'*+*3&+'19,2/:%++4*&4!9'A0=%./26(18"/)8. +8)331(3)"*311;#&@).+ =(',-+&+)/)1&)*:)9!55(3/ 1/35-$---'")4%/'+4%-8/#2)0- ")"418.&,(+/2*5-:9648.30$#(+1/8*-+-*+/7(%8! 6-**5'/5:&5,)&:5,. 9*4 2(-+52-5/1'133ADCN?^mŁ܁dUP;IL8/642--5'2-7/0<+1$8&45-&6;&0-)/)1,*-8339*92.1//5(-864'/<%2'):?("׀׀րـڀԀـ׀ۀȀ΀ـπـ؀؀ۀ؀׀؀׀Հ܀с2-=30-+9%;"2/**0)(5(*7.$#/,/02**:$3-75&'<1,*::-2&&/12"8'-263/3%1$&8+7.-41.3$,.*(A,/$,51.,>%.*1-2/)94/'."$(,9+/!(-+55/&44(#2,%:'25:+-)* 5-*+(:#18*('1/*%21+,''-5'+4$' #74,(-(13,<17-+?-)1.0*&!)&+2-13),8%23$2(%/!-.&")**(&%&&(7'6"41,+-++&/0.7+0*+"+7.)1--$13'.0*."+"6+1&,0-2+0*(,0#+,)+%702"-"*(%*+/*54050$851/#,$.D(,?&'7'$'1/8,'.1.! )%*+4 2$;"A0-112)- +$4*/'@&%15&!4)1-6,,1,1-+/3%8972(!)27)6=:2'++/+2'+<%,=31+>(#&$6>80E3EGR]zpāD2MxdHD@DFI7-649"44%,7/04!9754-,9:-*2.)*1/:.2-/6')&'-*0&.1/!,,51*5302/%؀ــ؀Հـ׀րӀǀррՀրրրր؀Ԁ؀ր׀ՀӁ4)4+0#@2,<,'*40.-.$.30#!21+/.'%//<,###&&0/8$,4!3'%--6)/.60-G0=$+*174-21)0//3%)8;16+,,4+3%/5%4(.01.:$)/)...-4/*(95"1 '(248/.)1-*1/4-:.&%'//'//%3$',66105)&'?%#+! E/.#.2&1#%(4-&(+1/"$:/&0'.)7+++)11.*,/-00,1,'!2;1+,.-&")(/)0$(2'&*&'0()*8)+%*,-26,$,(&&#+,/*3*A2/3)/.(+"*8/&/+0,<0',21371'':14%--(7))+)&-,.+0+72/./*4--/'-3.':&%,.//#'22&(/)"0-$$75/34$1*+3")1")6#0*)/6'0'%)7&".',*2%.-2+)5(&&'5?"!->1-8;#7E-30&0260;*>DEXWdu߂FE􁒁{jUXS:B3186-+*+ -1)&()&.3+'5''*-'-1*&+(,72(+.6<$+0-2,9(4,/2,3.+?) ))&)׀ҀـڀրۀՀӀ؀€؀ـӀڀՀԀҀҀӀրрӀ؀ׁ).4+(-9'/7<2/..&.)/.0#*2 */<,5-'*+39-,*>$:)-20#210.&*02*(&73)';.)((3<%400,6+,,-2'**5))!$5-/,77"8)#-,6-32'/'%.4313')0(;*(")1(2,"#"$**%)$'&,5"0+)#('70-%/9,*4-04<+/*/",0$& 7+"&7%5%5$13."*/**?>6*&."1E),981,751 ,03/$9%#*3#+&,/'*',''&&;;(+'&,&3/1-)#)($63-B4'.;(#%$!+/-.+'''-1% 0;>%1$ ''*8)'2,:0+..+()>./%)1/+6*%"(6#&7/!8//3)*)2,,+/,%+4(1':(9/5*%-%)&6''(/-5/ 2&%./-)-'$9/(5*;-115.444.-.1#9,-05D930/13$-.64*0.6/,)20B9/3EWTmZOJ`&k偡jJD>7?;1;<+@:*4()3+'#!-0+-%!59)47(*2$.003/-+$,94912(%%(:0!,0;2251/'%ۀՀ׀׀׀׀ڀـҀĀـրـ؀ր׀ЀԀ؀ЀՀր׀ց-<-(.9$,4-9#1.0-:"'&9,822,9%&3/&7&!;,%%(!4:#6.-8(?25/8>13)*/,'&0)0$(!*7'11*"+,"-+9*+12.3"+'3)8)/#'$0$)2/3&5&)'145-%):+,3-+)9#/.93%;5''<436'&824..#8$2.,+)1%/$0*:.*./-14'*2'#*+836/*(-*-.'2)/'0!*)/"+'&-+,)/)-+350:,12'5-5;)/##""."#1--#*'"+"42*7,3&"0$$,/*3*$+"%) 02(.%*02"#2-.,(10';(&0$%;+-5'%+)%3+,)41,-4'11!)5* +!&)/-#97)$'--7///--5($++/-(#*+#/+/5&'3(+%)1-5,.2-492/(&!+,+%&.))5//,-$)0?0+"+.'-15,')-/.,4:2,9*6-3?IFRPYǂ_KڄQn߂eaSCSA7-3:6//3(+0('9$//3'2(/&)"/&,5(**,-'&4#&6/(>0/74#30/)60- -41(*؀Ӏ݀ԀҀ؀؀Ӏ׀̀΀ـҀրـҀ؀؀րڀԀ׀πہ(.);(/+.51(1;+D2+&1.%A3.*0",)410-$#*,(03;--."-;,,/%&$.22--92-*#-:&,8/*5)!"5+)-#*0,28+7+,$$561!' )#+'//2$+-,%/,52&)/4+,D--(0-8,.)6/3'*"2 ",4--(2.0-*1%;)%1,+8#'0493-1-%.$)/5%(%-&$)1/-0'0)162-')-+%30(()!#,&-/%.*-&!(+&34%,1).*+'&)/0'5)++5'""*-%93*&('/+4&,0!(,/-()'4'$$5/)%+1+2/+74$$+,'.;66+5&/ 0)*,)"9$5*(0#")%'/7*3+'>+513;2,9,-'-3'7:2+6<.2,8+&*/0) 0#*0(,.1&)--*+".*4!0&!)<$+5(09/'49==# *88;*3B4-1/%+)/39.4>H=Fd|:6ۄzS [;ցpeWI;JA.7;3.!,+',$@2-1+.))30>-7/5)5'2,0)2/)2"-1%J1+.'--"4*1"3+(4233-ր΀ҀҀـՀրҀ΀ɀ΀ҀӀـ؀ـԀҀӀӀ׀րڀց+/5%1'532*,(/7$/'+40 -110'>.!4(847-0/':05I%)))9/4)6+:5+&&/0(*&//'0'%!%?3!)36091A"3+:-<",-'&)#'..,8"$.0'81,=2%)%. 3*/%-'408'0..) .3)+B52%%0190(65*50/2.')/%#.5%(02+'(").24!&(,.5&&5/-<4,8&!,*,><20'9+-6*)1.258302.$.9-)1/(,%).)(.",56(,;)0.+1(#.1+ )56)3%*'*'491%%2$//#-+0//&$('.'%&28**).0/"(%&('*7)-%3,2#&+,%)-/7-5+-315/$(**$*--1,:19((%.)0)/.*#;05'+0+=!/%7;5(,7')-/+:',(1/(';.3514(&/"/.-(02.&8('(-4-4$/1:,'/*4'1.-285-L:RctƂ_%mWbdMG/E'71)8:'?-7(316-/).%92/#*7*0&)8=93$,)$*812(3-0$)&+/09)$)'%-)-Հڀрр׀׀Ҁ؀݀ÀԀ׀Ѐրڀ׀׀׀Ҁـ؀Հ؀ځ'"35,'=/1-&7685-713)0.2?)'0*)035-5%('.*/9/--'#5&+*/0(-%-531='9+)'%3$!!/&03&$(,)/(+$)*31)/03:*.+,8''-2?%+4$,0$7+-'31#*9.)-'+/8*F"4%(1/-'6"2)2)20$.2(4/&(*(2+ <+*',+*)2'503$8,+(99-%231&5/61+/)5+)+9&14,0/%)6.2#/4%23).-,0,)09+.*+#.4+;.,%%++-1+5$*% +4")-/1$/'5,'(=3 %71:2;+#/0$*)+1-'/$.5/'/)&3*$&,4%,52*0%+'-47505"/)09,<013/1/68'04/%6-1%,3/8)/#'-"(':'%*, 0'70+5...#/6!2, %)/-(+*'/<#6%#%/1&051<,-1-&%.+,(3@64027.62EEe^vׂ,=턴['v}iWSQJB30E;.31*!->400'/%,++))"0-"*'(0*7'4&)0./;+./5/6,*0+.=.076)/4/րրӀԀӀ׀Հրրƀـ׀ڀր؀Ԁ܀ր׀πրڀԀ؁01*16+"'%536-2466;7+<+242'+(/@'&)3!53;710/1,(5):012$0#+($="9'/,-%-1:#"#+&&&*'@%,. .'>) $5,+24'46*,($.%)2,.)1#43+*&)6 -4/++#'66"'"(367)30-,-/*385)0*>8-/%;+)?!+&/*),3*5('*"/$(1)27-/4&#'940'.,*10$&8!.6*-'1&*%..1/.24+#0%2+036$3.-&%&3-4'#"-:+63%3,"51++#%1&+&)0='-.,)/%#(8(47%1++2/4=52'61"/$-5-4%9+9()**-82- 1-:6,+.)0-.()*3#--4..,00(/(4'7,/**4!/@-.9+*'('*1)46*3*+%7&',,-))+*-/')-''4/1)+421+%6+'9-,9=24,K4MNKZ\vzE`q[JJ#3:*(64:733$5%B%4'.87!+(+9(<7+7*$'4.02&(,0+$/-+9-66.%*&4-:-$/(,62/-*,+"$/.-,0%38 53%'+711),'8--!..)0#)2-3*&2)'28&&+7*+/%'//6,0($+-%*,'&(3*'#5$/!+9&'-+),--&1,*+?+G2/935'3(",'-6'(<+,+%+%42*"+%52//%.957%)*00& 7;.<7/('9#,("<$,2"-*5.#33"1*:$'='"!0/2%231(.,46.510/76/-*70&0#3:;;*""#%3&--'1+(0*45.+-341-;000)/*$(,/1/4 4%.0/.+-4,85,+*/%9%?$"0)--2,1/-%;16/;:<36(8P7OR]w0emnj=S끹weeG89G10C71?0671;)@#(-(67518+2-(35'.%5,*-9#3;/,*/.@7160'%9=8A'/"-ӀԀـ׀րԀـӀӀÀրՀ؀׀ԀӀـԀրӀ׀ՀԀف++*-./50&'1--*+6,(+16)12!1+$)13/-26'7085),.+1!%&00'702E1+.5+,09-3'2'2!0&8'3!/)$- 7$6)50+!)01'&*22:/'02/,+3)'%1(1'*$/./1++$/(<%,:1/).%'* 43-,*0$;.-+=/3,',4*&&*!(**16*/*),73#&5/&0%%9.((+2' >)7/)..'&1(5-193>,%/,,5 "'+"*+'*';*573&+//2(-+" /4&+2,8-0.5574)6#63$'!425=2/4!5240'$)++,&'2- 2),)(="-/&5/!4--!*:7-*77H,/*+4'.6/1(.5,*""050,*31/!'3,!7&(++1-*#3 46).(+9,-*723/7$00"&:/'*;8<-8/$+5)1.-%,$&1)%/&)- ,3.66<&AONn|2O /Rq1ˁdNH4B5=-92%->*3/=5+;5--6-2'6+/1311+4&/+-)36&(,30-/9(/.*(:-2$$01(,ՀրԀۀրՀրҀԀƀۀԀـՀՀ׀߀׀݀ՀЀ׀Ѐ؁3*)0'049+-9-9)/,-+))*6&:;323-03)&2,'))%+!6019%3:,1'(:.6*"+/>/006*/++-2/220/2+((<#*(,,- &)-(/3)+294/%%-+)95+$!6;)'#/1,..1/=)*/-#%&1#+(8261/')7.-*,#/*4:*8' #(&/4-4(18/+*!-/#,-."%:-+::512(!0,2%/,2& ,)0/>*(93)%5&(/*(/1.2!&5%$19+!,.-71')-*0&+3/(%4&17.-*"#4/+/('.44:7:%-#0//42/3-)+/(%,-.+*+6&$2/6.4-46@4'5(2-.=:E,6;323530+)*%3).7,/).:/2&/1E)372+-",317()'&'6)++0)&,-+#3./*")3%&51'0174)+"B()0*/0*&,.!-;2'4%+$3,>49C@7JvkҁF))B?h pcGM@NA7/.&:*+./25/5"3*&,-/&500(/&/.6:(9*6%*+.-,*<,!@83;&46:6.8&/(5)ـր׀ՀԀӀـԀҀŀԀրՀրҀҀ׀Հր׀Ԁ׀ڀ؁/ 537)55#18<0B-/60:/,2-2437(*9)*51/7'(".(3?*%($(.3+./2:*.:.5!.!**4+4"')',-&54+1-*2%)/-203/)-05*( +-(/.#$#+433',+&-,5*#-)#-<,9&+4(5)(1(//2,#0+/6"4)'/3),./.-(4#,1).%!#:52()+.972'&-%*,%-3,1"$4&@-/" //2+,1/5.3-/'.-3%&+(64++6319.'2//&9*,+ -D&+)(:,4"B/"&'+*=>',(53)'2'54.'&+(2/)4?�*&66(.!22)1)+1857(2"1,8-*&7.2A;CQ?869+*@,/#8/(:4...,%:0%)2#98+1*1",8(/*6(/.'',)'&-1/)*.-*&/).605/*2&0-&06+/*$7-:-404+57)0)7)+/'4'21/*?F.[Pbʁ+MrcځǁzjPK:@;249:-C.0)385-5?,.21*:*%*1/3/'1++/29%-2+/'2*4-566(?83(811!'׀ـҀ׀׀ڀ؀؀ՀŀЀӀ؀ҀڀԀԀ׀π݀ր؀ڀׁ12+#:7,'9*53,72-$2+77-56,A))496%$35*'8*;50037#-5/-"8.%.255*)+..)+'96 0-"-, 0-'+%0"308./%-1"06(,/81@;33-'./!3*4/,+4))$.,*2#4/0(*2.'-8&3,-1-&-'%(/0'&/14(<2'6+52)6))(" :-&8$,3%4('+)'.5-# *.)#3%!"20-('.+3,=$00-,&)3&,4217+)))+2/(.,D$$& %$;%.'5:33(++$6)*=89/*#4)#!-0=.'#,(0+//(**//502+.(,+*45.51#0<0&-+"(-3.012:7DE1>=/91-23-%(*7.54,$8(/-/4)+!+.!072&++0224?+*/+*554/.1*!/0''%/'*)%-&00#+7/-+'.',-+,-23)4..48,00$7<35LAIWiāڂ;Ձpr^QED:74>6.7.;)-.*'-&5):,/2#-=0/(//7',#%12#,3+#8$9.7-/)!1!-4%.:.120րրՀۀЀ؀ۀրـȀրр؀ԀӀԀӀ׀рـր׀Հׁ3#5046."$9(263'(0:9#),6.)10=20.0𖑾/"-0,9.(8.-;/4(=6552%-'+'61)/*/%1+1%3,:15/48*:(/1+71+1-.1%+-(5+)$./4%14++36!) <*,%2592, 4/',32"0%,#5,-(%/22/1!"/'/-/6'!:/( -,6#'6*8.,7%(*@"-,%?+#&1*&0-261= 2+)$-* 3+.('+()4+'+.0-/":"'.2)$'0/0",-)2)=(%#(*2(($'!-!95+%!7-#-2 -1!;+/*'9(A'0'/24;1)&+4*%('1'+,-*:-'%-'*2(7'8@?32><3E2112)*/)*6(<-;!0,3/0.62'3&<306/+""#'42))53(7<(5/7$3)+''+C2+'3+-&%6:58+,:@,-)(2236%)*0;/>://90,:L7!@+40C%5'7.>.5-16//;<-*(%5653*.*21+629(%1.)520')5)$-()-6'+"1+..'6'#54/415*2*'(6.>.&2+/4#/+);&!099704%".2-24%3#232)85#3.5+63/#/(-*.%*/'+%3*49(.*#-011-**-/!##8+!00*'%,<3# -..&/1)*#*)6*6-8(:1-,.:+8'"2*1'5'1: &+14,*--0--!C%+/5/8'-)7,1.&'),-'0/ #-(+')A";.3'+*//1.377.**7(&'401-0-8#)*3>3,-%*,%*.0.6,1+*567+.893//>14#9$05!+,-,)%D5,.9+60,.+1=-21'5%.(//+&*,*+0>+,1%27629"97-'-*.*'!++01**&%4.+1%12:@?&5'97,174+).7A:BD_\{p}f[J?AF.=*=31$E376/31&9#*<2*0+7#&*5'71,/&5/%43"'/)+--#&*4))/86&21/252ր׀ӀӀ׀ՀӀ؀ڀƀ׀ՀՀՀրԀڀՀ׀րـԀ׀ف5,27(401)45-(6)9665%+':-A9%+=12*'-"&')-+,5.5'&4+,162&+1$%3,$4,50%+833'1,1.1)/,.3%71$&*28-2.06/'0#),5$:,'*-/>0+-.-7>*)$+%(3'& 0/()4)#,+#7&,$,'='6&+&/'/5-!'3.28/0957)#++))(-:12/.6&211%(/(1+!-'-6*.#53!*-/() %///7'#',),%2-1'57'-1 3.4*2(-0!(/-0*#((',*/..45&() )7+.+08)65*'.6/3&2.%/,0),%*7+3('104<5/+#!..,% 0/4 4/34+8,2>61)A$=0&--*9),3.5.//%.+%5-#4((//(//53-*!/5.&!&',5%,-$(,7(58'%('(5%&4*-2%+'8()?7/309.14($,040/68<25P2@)..4,(+1'$'//)+7/)1/!?0+*ՀԀ׀΀Ԁ؀؀րրǀ׀ҀـրҀــԀԀԀՀҀրց-33"4'.408.2(.*.:11+480./+;46=?QCC_V\Y\]`knTYL?9QB'/6>*9%-.1,'6)%9148+..9>77*)62$'(';"6&+D20*1'88,1$)037%7-+*)12,3րۀ׀׀܀ڀۀԀ׀ÀԀ׀݀Ԁ؀׀ՀӀπۀրրـց%/*,5;.%4"&)+//61(2-:*(%/3+B%6;A0%9001-,&0 1/2/"2 .(1(-36,5.0%0,A.0*%&1/"7&<3281&*-,/*,+*%'413)7/(6"36/'!1/2,:))-/,/50".)-1.-/01/((1%%/",-12(0#,-(25)'3'8-!+5+-$0.'&,31/.,014 #+$-%37/&0.!,*1&32%$%72-.*0/13%'*1))()2*')0.+951%!6!;4!*#!6277;%94*-'+8)-1+6/- 4,').(-4(=>16#&7:#33(+/++,++1+1-1&*)/*/5#%((3E>/.(2.)9"+*21-+ .*,#---%95 03 .+/4)++$0+-87+"4,*%2*-72/;,6).9'#0.2.''5+',(%.+//'*+-+1,'8(1%1-*)4+-.!1/.+&=%!0/?1=I:RQLC?IZ_Y\R\MJ<9>)K?@2904*2+9+3205(,527<;/+(071-78-&)'02!25.-#4*2770(2@&0:'5+4'2+,0.ـڀՀր׀Հ׀؀܀ƀ؀Ӏ׀ــ܀рր؀ҀҀۀԀԁ:1/1('"2)5D3&%0*/62/68&5+-<*&&6*4.73+1:%+*-/6.,,*; 35%*473'+5)'0''0---0#/'93)///%60$2.)3%0'/0/+,+-*2,'*.%*(07*%1%-'62/2*-* ;..(/+,$+4!*)+/3',/!%+'B&,#93'--..!54$0.2/,)1(.23,#89*!6+2/./(2&+)%,3/,:+,13''B'+,2,+60"<-5'745+/-'"5+'@'.& *4,5-4.)>8+*70//'='!+!21!3-+./9..+-&$()'5#'#*=5*-+.&'93!$*)% (,(;*3-4;-.*%/-';@,)#-%+3.8)$( )0)2+2%3/*)=(%040+/.0(&*+.&.*.***;/9+/.'=&)*,0627*5!&+-31#2*+%6,#*"/#*/1"&4/0#314403+0.34<5=*C/2I>DCS=:31-+-/5!)67/'0=*7*7,1C7*28,,+/#/؀Ӏ؀ڀԀԀԀҀՀǀ؀߀Ҁ׀׀ҀӀ׀؀ՀЀրՀ؁@/1.'+&3&)-%0'5=*'1-/-/&,?8/(18+(6>-/'/5/#,5)5'*..7*1-30+/<55#3'*612295)2*-)++*3&/++*$7-);)'@&$61)*0&/(,*+-(,$-44-330/-$.+-'9$6<-&2+0"+$+,)(/=2+; ((+#0+)723#'-/*&9.(*)&+!&#*(/6#,19=(%4"#,"+%&'&/.&'.(%2!%0-+;*9'=373.2)-(+.+5(!1599)72;)&46%+"4+56*8#5A+)$+6,9/6/(0.:3$-/56-#**1*9./#)&'!)52,.+%/25:/71%(-%.!#*0$++.6+,9$?*&,*>)4+C**+,*,-,0)19+3-&$' . *'3#3*3*&5),#0-'/6.5"05+-#2%14%!$4(1&-9%-)+%(./ ''03)'*+1".)/=5,%(0934EDEABT:>MB=:E/BA04A.9$!6*+)A.*40+)1-6'1$604-77$0+20,&'& @-.11.43'11(*.*068210/170׀րրԀـӀՀ؀׀ƀπր׀Ԁ؀ҀՀ׀ӀԀՀـՀҁ)/.&-,;7%--.0119$40'?,2);.0'+&63(201%05;-44%//"&4%-4:)7,<'065&504;8/,'6*4/((0#'+3+/)*6!2//8(*/5+.2)2)(5,00--0-)*14/3,3+*+853&00*%-())4((!2'1;&,( 7/:4:3!2'./00,0!5&'++A%%+715"%!%,%')/+"0.7#*4//-(<2A2"3.((206011"/340-.-%7-''(!7*/1;)/1('242# 8/*4./#0#/$)1*"2&)95&@30+./+%)/%4&'#',)3 $.-<$=0)2*&')9#*&(%'15+2/,4-%3#-.)'-6,",,&)')81-,'+%4$5+16*,47@#',7) 2*/,)*(0)4*+)1A,0+8,"$8,@?>3;=E%*B=3762.'<+,7336*9>+*-/@#14!=,025!//#2-%4)1-)*)'*181'-<1&43)2+6A.0'1׀׀؀׀ӀۀҀ؀ڀĀҀ׀Հ؀рڀۀ؀݀ր؀׀׀Ӂ;%4,E0&/<1(=261<4"/64+(,;-+31<4/2-!5"565,.(*)/!.5%-$+3(*-))'.+/--<&.-(.%#-5/*#'2/%+,70"/$6 1)31.)2/1>*2+/8250#%2*(=,'-2 '")1/6 (&21+2//.54+/52'-,#.&3+%-60"7(-8./'0*5;-..480//$&0 3/5''(0,%!1-/=10)1'8(#(#-4)<')3%-1)4) !2*1/)*5)+2-23'/45+;(-42)/%4&%(1)-."&9.96'.),"4(C83('+1/6()*****,7831-+3),'800'$5-//1(-).%+*3,'8%3:))$7#1)$062)#(7%+,)62..9$10/('82*/0/1---)?20/9''(,.;*-$2#'1!('&;/(/$2().93/32,3./-)1'6*.,55:/'-013529?<3;:191J6+;?>?320;$+*-),,;-)1.130+0C/2))',=&9+/*711(-72./,,1'3,8./31,).*."-)'.),''ڀ׀؀ҀҀԀՀ؀ԀŀԀ׀ՀӀ܀Հրڀۀ΀ՀԀـہ543++/(83+&3)55297/:2$5-&-*1+38)%& 566-+283<#02'.-#+7')-,.4,95)'--)(,.'2&,&<-.<087'%*&21<(3&'(-;3-56***E)&+855.'1/+/.$.0212+1-48---;2(@/.90'8((/))--&,#<=+22*=10.'05(+3/97(.:1#+3#-+(/)*&&-,(%-4./%"&1/!5-'*0@:,.:)++"6(*/9--,5(02!++4+/,-'+ .#.&'B(-3*010'../(**4*'*/,5)'&>2.-!' *#-0/38/+(.&,21'%-(4+-2/,:7*--(4+/3;08'%0+(" *).7>$+.1.$%2><./#0/4*6+=,-1-(8%,-(&$83/!..5)()3+7,%2*)%+&7.'307,,0%(, ;+"0--& .)-2 :+20;B33205;2++#?)8E5@9?772)5=2;/($(64(++&=1+*&27583!287894%5'3!34>)4.4&**/+.)9+/6(5?)39>0',32#'24ـՀ؀րڀ܀րрӀǀՀڀӀـڀ؀Ӏ؀րـր݀Ԁс=-A-,1***+.-+'*3)2&*567-&&..46+/98=+<+8.+,>)*71.&*@733090.#/..<#5,9023?.+7'),76/0/#.#*-46--!15497+2)91+)-(3/)*-3/4*7!/'+'7-4*++$60(--**0+3":=)&$05*,;**:9'.3#/-//3-',6&&!#32.12!.1//*3%1-'4+/""4-1!'979'/1.14*3(.;40&!/"-)("81*/"*)%-1!120&<&.%<')%+*'40/=.&053*(4),-46)2)8#03'-15"-!13$,6552:&.$.4+"01*)**5#+.);,'30736-3.5;#')26(7-,!+*0$)+(!%5&0,10,*/6$.+ - 87'+"/+3+'17-.0/4$=*$.<1++#6$;3=8 +7(&1.;+:/8(=#),(/ ,5B6:&3<3.!/6::/>.:944:":0614,0",420,435/!)+;18%)6= .6%(0105(#-(.%9(70100#:)5%0-;=10+>1'Ԁ؀ԀӀڀ؀ՀՀրĀրӀԀ׀ـڀրـڀՀ؀Ӏۀځ/(;.3182-22<1$,'6/)-*/9/+34.3,!)-=9%(,7:'%3A) ,3,,')),((=.,8.)8-7%4./8'+,(/$(,/4+-4/,")(/1$'&(/#67+)5/3/2:$0/7;*:24/0$+0.+(("3;'421, 156(+7*./'2'-*81,*0"5'&+5)/+/!#.%%+-3)-,*),-;*&&)''9/$$!&5)(8+1&#)(&/',,*/4%+'80./%0*(41911(#$(5+,.-).9"2-&)#4/'1/+.03.1 3$+/;"30@@/'+2.:"(5.-5,16/%2+8/60/'-'14.,-0!9,(),:#/245"/*3)$/.(44639#-#(&: .#8-:6&%7.&23'67*30)&/.%,1,=-.$,"22),:#$3+'/2%6*,/2-/%.*-;--# '-/&+1.2/0734!<2$0,*%$?+,6;)(=54.9;D/+95/84/'32,/,60?-$9(#..716"(>.5)%95:!""2(&4<++*,/+7'$2)/<1!0+$//.9414(:+ــ؀ԀՀ؀܀ӀڀÀ؀ՀـӀـ׀ՀԀۀՀԀ׀րׁ2"0%3#6+0-+/,,-3-5%514.%.-+3)<%94-/*;/2//*.2#;&21'8/"4$*-4/742&!2%+9/#0+*$(&))+!#0 )047316//-50,#1278//(%11&.$$// ((,4%+",51&2083+*)..;-!$&$&/)#11-(',4#'.02*4(,,&/$1&*/3/$3',-':7(*2&90,+/8.+-+33%&3(.7-/"51,).*1$/+0,.,0+:-0*3&6?//C,5,!/%1'3/3'&)916'+7+*$335:P6,(/:3!')/*4#.+.+$-#-./7++-#$',:'53.0!6*(33; '5+-*9,,(()#/ 4101/H/"0(/'+$2/50519*92):/)&-&::5%-)%)$5$15-92+'0852-D4A6,+9+2-)+6*).#%+7)012&+.++'.#+7&6962/5-30)51B5.1445 *5/)45.1,B:18/243%"',,4?9*401.1#7:-& *+,'(&*4380+,3'9''+"5.02(:$-6D+F1*.5?׀ҀՀ׀Ԁ׀׀Ӏ׀ʀԀӀԀ؀׀ӀԀր׀р؀Ҁրԁ&(&-+.',2,!//'".3.'1.B''%(0'-370,/*,.00-0)51*3>;*$+)7-6&$3+(6430%$? &.'&+(5/#7'%.0+)5,16/-,'/6,33.(,/%*-9,,!",,.0'315*2-,-2.-$ 1) 1+<1$)1+2&31'+, 1,/-/.0$;&45!+ 316,1'%#.%4+#/)%2"%. (.9&&5)0.7!.&&*32/09-+,2.*03#./+3+:$1),7#;0-70)-6#.36 )8.1*(?4(+,@6')%631$ 5&%)/+/*5)A:)$#3&&)-*#(/2&73$2'3;59/0'/+'-% '2$+(,75%3=-( 51/2)+*4$-/&!7-2+1 1.--!/*0.-0&*+0+328!/(+14*' )1%5&+6 .<-7+'225-&/+/61",-51/,'&+8 --19+!/,5?50'7/-.'45,&-+!6/4,0-128/15$++2(/#-('5--0?)/+.7(3/"302&*&:.*'7/)-3<:/'-&0+2&-")3/7#0,5׀ՀԀ׀Ԁـ׀݀݀ƀ܀ՀـԀҀՀ܀׀Հ؀؀ՀԀځ#4,0%)*30.'/-0/11'57+0+0C'()&27)=4/4:,=)++')6<1(+(7+1)+0+- ',"/)(),36+-3-0;*68132#1)4.+1&,:;03''*,$'6'$)/0&"',0*-.%*)=-+*%(1(-05*)+;*&9 -(!?,!6/)#2.307/5*<!'*5%+-"(),+<+)/-1!,-3)9)'425*#4-68*'(;0- 1#+5(%+516(01.1,)1*D %*$32:1+/*)+-%@-!7)05-12'.034,(%0+6<-%)&)21+&#%%/&90)-1&=110$1,1?7#*C6,:*8D,("9%).'1.)-'-0%)-06/9)%504%#+8;2)%1*%30.3"310,3$5+5"#0;,0*;--'(*+%/1(-9/633.)4*/1/2#&$?+/01.)+.*/4#33313++:=$0,2;'65B*7901$/-4191-0%:>111H134.7&07/8*0),:-,9//*4&)/''+.1.& %)*--3-#,(3-*/6,0(!1+8'؀ԀӀӀՀր׀׀ـĀԀҀ׀րӀԀڀրҀԀԀԀހҁ4$4$+4.',5)43+;+/*,'(<('%:$9--9-1:<6#'1&3152(,&3)0)09219/2(20),8(21& 6'#+&.,3),,+272*79-!()3C7: ,-+,(045-5,,7,4-#4/8 /:3'+35;//1')44-$/1$*2%.5' *150+.'.>+))7/!,2.6*$'.'/!<333'&6..5.'!/8 47"+30+.279**)"*(173:1/-13,93'(&.(1+.+2)*1/'**''-!-;)01$;=+=1'')#:2-%..+ ----,-&"%)'1) 1)$(&%,6+< /5 -4%$-+/&2*->+#4((,#).21/',%6 /*0"$/'14!;()3.*'9(($6-1:267-4-%-/53&005"+4(5(#2.5(0#)!1.4/',3+");'-+/()0$3210"**"2)#&./0&+/+,(*17,<&4+7+!(--7;;/.+:'(';.01'74/19183+//60!3$,6!1,*1,#80;>0750.2%279!-711@(-4,5ԀπՀـڀՀــހɀՀԀԀր׀րՀӀՀԀ؀рրց9(421($'A/$(12 &;=/7-2&8 :'@2.)8/,'/!//73$C8*/.%,0791"*.+)+**'-/+4%6522*-,1)&12"207.1:'75.)&-)(*,%<17*-.**//3 +!0>&)/3/()-/$%'0)09020(##1/06#2)*:))-+(+#/)3+5(2@0(&+"*)9-&%'#2&&+*/; -#4./5!301*&1-#!1+"/%!#<2*%$%.7-:5(-1'2.&0)03)6/%%;&/889%'839+1;)1+ 038/2/54+.%352A((&+))2-$4#)5#/-'20 ).0)//1*(,:.++7*,++0)+#3#7 ,.*$-)/+$-0$,'(*)/%0*0+*$1B4%4#4(*53/(//%,)'27&%.5///2(/+#(3+6+ 2)#);-51'&.3'&/-+2"+.6=).>"24)-,8..2 )268$?5%+,-2,%1-$3-$*,"4)(+50"*4*-%>"+))28)(2%1-5*02,3/'./3/3/8-.+(51(7)4-(1/15+3'*-+1ՀՀ؀րր׀؀ـڀƀԀ؀ԀԀрӀـۀր׀؀ހ׀ԁ$5%4267+(40-+8',#,12&-.'888&..3+*2%//,/++5+-,2&108(.(1)'/%$*+)),&+(&1012+*74%/%.#*3.70#&$6/64)-$?B;78=1$##<=6%$)1+*)//164-6.-1&+412')*)'+,2)2$!*&4D!/;&%./+=05;,:3/7+8---1&(#$/+,#( +%#7)&3/$(7'/,(0*.$'*10004 #, 56-)5-7+2#*17&*531'-?'&+,.52.#)2+,5-35'+$%$@ .4 -3.1),/.5(0/+*8'19)*..)-55&/ 4$(3,%%-774,(%5)/63 06+625(5(3/#9.9,1.$.%"*80<1!3,&!,+1/-<21,)21-*+!#/-($((%-1&0.-++5'.,,$'%0-7!1-)-&6$.*,00)%5(0!%04-.*25&$41*!+31 3.11:**&)-+;,.-,'*/'*6$-10/-$,"=/)4)+7.*-8,)?8%8-*+)1*#=3, 06)B502*/5,7:2#19%2-؀рր؀ԀԀ׀؀ԀŀЀՀԀԀӀ׀Հ؀ڀڀՀ׀؀ہ/*2)0+3&+(8-+'/*+/80'>5&)(7-61+30/,:-6$'?3%/(,0"+-"=.0-3'$*% -->%++-."%#+B,42/1,/0 2/4&81,#-# ('/'3&!4)*1'!&.!9#@2/*%--#&/6/.F':,%.-/3'5:354+-":---#&5/*=,$/")".),,20-1532+&=-).2+%.,&2+080(23(0$,, /3-*%+%8.//5*4,*1$=*+Հр׀ـԀՀՀ؀݀ɀـՀ׀ԀրӀӀۀ׀ԀՀ̀πԁ7*,7,+(,-!&,2.*(0'00:;$&.7#3/4!,6,,.2+)/C%/4*8++-#-(/3 +.,)9511*0+8(,46(+0+-3.#3#6//!)$80+.6%'=>,%24+,-.,.+001*3)%/#:&'.$--6$""7'5=.&+#/421(!&&30(,72,4,8/0/5(.../-2$%397(-.#'%*3'.+--2$/,8(7-(%'.,+",",008&.<-&4#7='-,+(0+.5.104(3$)3;0%**#,-33/(+?2340-4+51&5,201%/-9'+1&( ++7&%!%8#0,3(*+0&)'.*-:.40*)%)5;6932(%+5)1!//"&2*"6 5$3=-.6 +0&8?7./#3$-/.A6.)0-52.'4/./*.-,1++*/#,+;'*+1;3++'%-/)&.".1/3D.2'&4,57''*)*.28/)/-9.-7-/=-+6#.%6:53.#166-2+76;23/:1701.%4&%$//>/0-,(/'-+:7'8&32&-)6,. (+2)#1/+$#*,-0(08)$*0+,5)&136312523/+$&B5,-2)-*$1(3%*+3..(1/'+#/-%./$%2#!-/+5.1!21'()40)./%+9'$-7;&110/-/7-=&(+012/11 )7*#0.*./'-4,&(+2##/-,#2,-&02//-**25$-4;2<,944)-30"'-&)6$%'5+*162 5#&2203-2,+(*00..+,,''!9.81/15+)%*.9$0/,'/.1/&1'",,=$,-:-1(2/&1$2-,$/'1.3'3/)&.**..5%2%: /(%87+%+!&'/('#"+2,-4'%/140)4.$*/(1/*$+@"+0#1)1+#1<'.,--+%&'!2/*,>-3/0/'22&/):&5-!4$)4*/5360'.,%2//%1$63,/+2(4,.0.-72+#,,,2+-'2//=-,.$>,"*15,%2*5?=%!250;3*05<41!!π׀ՀڀրրՀڀӀĀԀԀԀрՀ߀րҀրՀۀ؀ـӁ3/(('30$)-<>/B$6/.42'//221194,5!07$5*/,00@695/'/./7.,(+."50(%<%2+./+.*,/5* )#00;%5.&5.3629;,*2*'-/(.(22 )0*14*+1)"*8880+/%)*/B.(-%!.<2-$1&5-.73%"3)++)30$/&(,/% 3-2;*))*%7%5.+*5%'0&3)%6(-,+6""/,,&".+-+2)22-2/#37$.'.-/;()415'":;-#0.&%0%)*+8/*$+!1"*'--+)172,'0+--,8-+'+').,,:.(+2)/+/(-97339+()+2!&46&3,! +11+)1-&1-$,9)2/2%263/'%9+59.*50-!15!/7'+'75/),1"2!-#-61'4)%-/+*12.,'/!-/'-'(0559-55&%(%*38/),-2--0)/>-.+18' 678(-9(+(.7%1')/+-:!#02&'176-",*1"3+0D,.';+$-48(&/'$**"1C,3 "3,),90(/3.98+05/1-89'))426(**̀ۀրـՀр׀׀Ҁŀրր׀׀؀ӀԀր׀܀΀ڀ؀Ӂ/=!>*B(5"))2'4)!49'+(28.106*80'&3(82/&9'3- %2*137))/%!5,<43)/3'*7/.;1082#2.757241"7/4(&1$1*+.1#4,%)%+%0*$75)(&,'3&>)6-:-0+(7',5#.3*"6&/,/3"4".+*+-'':8/1!5380/)/*6-(*'.4".$#4'$+&)60"7>(:0-/*,8'*"7+%/"*)$0#!/4(4/62%2.*:'#./*++.1"90/-)/5&<*4-),$-7'2'15#;+;%%/2%*.5+9-#+42#.6. 00,(%1/$*54-'**/0%/)0).)-(+!(%'55/11+,'),2,./#//-$!#.0*(+--#429$/($"8,>)01)04+.='-/'1+.-3%'#1/*6 '/&*:)$9):85/)'(42,'2.213.+!.'$(&:10622# * 25.42:2*6"3,'./4 0&/+53**&-/,%/2-50-/1'*,/-)2,.4+2&#,@+-:'0!% (1A%).5*-(7#52*,)$׀ր؀׀݀׀ڀՀ׀ˀԀـԀـڀրրր׀րڀԀ؀ׁ+,4'3,&54$7.43'4/.)&-+-8((8,+)-)/"('0,2;:,4=#//$.+*22$$1!8(-=+$ -(3;157<.)6&4)+++572*.'.)5.1>2%0/)82336*34'4617)02.//5+,8#'3)').%,1+7' ",40)..(9*4F*15,+)*#54#,&%*769*%7%6+/!2'<1&$3/2+)*;6$-/636!+**&6-'-<6$+*#"0&2*-'-;'&5+=(030&0>'0(2+-3/46&64+#(/1(*6<"3."/4731,%02$8/36$3+3'+.#/2-3/!$&/%1/+:0.;.?*(<4*,+2'%#'/$/,/,*10*-9(().'%4;%93>.946&$./5,<,.6%#73.,6-'),/&*1*2(1.3*/?*),+,&,*4.7- -%/4-)*/++:)69&$,7/4#+.$($6(,07'0,+,/&>5&7;-0&2+*-<%71.9&'-,#092.&(.-,93/2,&15;35/,.-16.2(,/"C8+-+&3(3/(*/$+-.734*&1Ҁրـ؀ՀՀՀҀրĀ̀рۀ؀Հ؀׀؀Հ׀ր܀րہ1##/9/6(!.++@97,5044843;&7/.2=%3*6-58#;0/0)*/%-+-.,43(*#/%%-/,2D/%28;05**0#4$>%90$,=31+,%*/@*1,.*2*;.66,)%/-33).1820-'53*(0. /7'(46,/1+5-)&6&;2-!4;3%+&')#*(&'2>3:6%+*:()5!3,,1--0-%20:$.(0)'*#5'4%'.%.,41"3@!9&1$/%+841,)33)4+*4&$.+8/1-)9&2$%<++5'!$//6+20+!-*/ +(7',&Ol-&/%(*,52B-3(/3%(&-/7(58.($+#) /-E)**+'76"/54*"2%/ 7=*,A+-72>#'../7+52 2*'/,'9$##%1)%16,)$(+942)3),4.()$-&9;2)/.$24,1%)1/!/4/'%+52;22>3 *.%#&2;,/2,"-7&%,&3-A!E-.?2+258=3*%+/-8-,;*/*&(1+1..1&#"*-+,.03,/:%3%3.$$7);7C0',!050;".153؀׀׀Ӏ؀؀ԀՀրɀ؀܀ӀԀՀӀՀЀՀڀـڀـہ3"21)25)*/5&/+>;(/%;),%))-,3,/$601*31+))..,158*/+5):+4C21$=42)*1-.,+2/16+9*/&+/'-)444*3)9520=M(,(+)()4&*2'0.'+24&(4'//+%%,+#94+82-ՀҀр܀؀ۀրրـʀ؀ԀӀ݀؀ۀӀԀۀՀԀՀЀс*90.<(,6+$1,/440!-35096(173558H+30##3=5<1486&*,-)(#/9"$*/*3,6.3((:36)6*403(),)045-/-#*6)"/*)%.!+/3-(,'/(5,/32+%,*//*%"+-/(/<,96)5(54+'.81&,.#3:6.*;4&$,06;.17&0*& 505&;%--+)-2$42#2.052/-)86#)=7.7--:$791'5!".,+):'24;),1+#7/73-'/*3630/-8#&3&-1'!0*1*&':1.&.5-&3/+$+0<*#()/!'..35+3&8,2.&1&9+"#2');*')#.2)")(092/5/3+=4$*(034-79 &42/#'-1%+.*-."+/'+""2412*/!/; 4.6)*)7&,-9+* 7+)3768805<5)#/+7/"1/+*17!//51/7%26$103*2!*)-*,)'(,+60,+>/836'#+)(71,#'72(6)#/4$252.2-%94)/,%/0%/.4!72(6+-)5",$+&)4/-#%3/(+=)ҀԀ׀׀ـ؀Հ׀ՀŀրԀҀ؀؀؀πրՀۀۀӀ׀ҁ.#4%6*6',)8*(?(+-/&;0)6'4$3!1:/1.:43)*;30490929:13/4&7005?$0.;&$("$8,1,-2/.56&'%1'1)#//) (-,3,+(5,6-/--<))3#,(0'1%#3%02')"/.)-3$A%'-#--&%)%)6$)'%/'45.'283:4"564)&/67/33'*:0"3--,(*-7%//'.2,%-.+-2+4).0.+.%,5'/45*4.*'#7,(/<0.&5*/58#%,'3,(( $5*4,1'*;)%6+))7/.!.%25/0<-0(B-$/2'(06:)-&1,+".-&'>11').-.&1%0&/<#62,'&55()--+3*5)(:.-#>)2/-48/,7(361*2&*.53'4+--0515'+0'78.*+*2%-%++6(;+5-"1;+%&)(%0:.*&*34.2.-,/,,%,.%9,'112.8$-13,?(55(3*(7+"%0-11/521--04&,!>%/3,*0)5.52+%*+/*(3)3+.$(*0%%%0*0'0$%(-9.0.9/6F%0130׀ԀЀրҀՀӀՀрրՀԀҀ׀ڀ؀րՀрۀـ؀ہ%%,5!,-+"24%6324,2':$*")+?2/,+,.1(//('0&+-.2&7>9,3*%*>++!/,-902#2(-,0--"53.86./-+3*0/&4156+173/(#17%&//'*24/)3')5&+'+?)'90%1(=F1,&1/(8+. 1431'098,+0=852&&0 %+)//-.=(+.%2&55?,557:2'&5.320($5$62)-,'.!&634%1&&-#0*6.2*13/)62+,*1-&4'.-'*2&%8''/,0(-50-51+0<%%5$#,%$$./2&$./*!$4%&0)-.///+61#5+(*#+531-80.-72 +'-$12*2*,-194)+*7*2&3.,*20+3(/',#/&7/0(-,%-,6,14')83)/#*'%01/;+:0((*;(.,5-&3..459'5!15.%4&0I)10,.#7 %5-'4/'(*.#(+4/95.'&3*-*6+)1(+1*. 2/.@+13+;(7(,*45:*)+/361'@0&3)4#*0936/(3193!#&38,,'2* /&;:#׀܀ـ׀׀րӀӀӀǀڀԀՀԀ׀ـՀՀـ׀׀ЀՀՁ,43(=0-/&)-/7*1/650.'+0-*)+;'0102/918(.):&61'.43#5-+,""&<34/(/&*$'<)/"3//./5+++4'++,3$*6*8+$7+%+;/,:77#//+%A'.,$4)/(*,03&.8)(.(.'+".&B) /;/1#-+*!/)*,+,-.*.'&1-2>&-36,-1)6.$46)-,60).*,3444286%&,.0(()7.(,5,/C,2+158/+( 2%22'#2 +1#,+".)(11/*40+.)//,)4?<..8.--8.14.$,1"$3,(-222-#781$'',3'45*-="+826#41,- 180&#)$*!1%3+1$6/8303/%$-+(>%/%91#.0+,6*0:.&/'%*&8303)./80//07.'-(6(,91#,5+*3)0.&29$.#97,86)%3$-*,2*,2,!7*+&'+/-3'/,5>1';<;(4/*41/(#,72#*2:3004/34;*0/4..(0+@+4A+&2625)05)/7)6(.(1:515)1 6)6,97"21$0,7,/ӀՀ׀ԀՀـррՀȀڀـڀр׀ՀҀӀՀ׀ـӀـ؁630-53-(02-1".+-56(.0)1:$)!'2,+10-*.6/.20.:2/5:,55.,<.="+/.'(-0#220):"-/9#4$-/(9+11,6&$!+ ;5*9='5*/A)!+1;%2**44(+3+8(9&5!032#-.1,)24*61-%7*-,$2%)4-+$22$'2.(/%416C. 9#-1233/*&?''(7%;A#*'*)**';$**.9)427B65,(+#:3'9/A24(5/,)/.)/2534+%&$?56($0)6+2&*+/2)4'1-36(&)0/,''./')5-5-!741;%/%(4*).'-/9!02+417253/&410+!'28&$3+1/!1'4=-1/*9037#*&43*/:(0' +3"3*'#*.0140#/%B1(/%13#2*42,%)/+#7,+$*%*(/)-(+.9%4'9 .*/&&+;++3)''>#1,4-/(5#;)(-2&.41=(%+4#;7*/-,2-2*+$'$*)&.0**0+-) 315+0;3:5:+6+.)%*:4.63)(,7!2:5,-&7-0.1?,!%%7ՀӀ׀р׀ـԀـրŀրԀԀڀӀՀ؀׀рـԀ׀؀ԁ.$/8?1=/+1,4*$.567'*+$,1117:../745*00*)6<'.0(<-,&1/'-2+8'$#0&63#*)1.0/#+4C+&3-3=*2.40+!0-/9+24-4/6&"/:8(*>(/,/,+('/*,(1)%')$*'3' -ᜌ$*"#+*4'!7321.20$$(04,)*&,09)+*6$!5"*>5 20(3-7-*&(.-(45;-" 1(2,2+/%!($=&-(6*4/16+&30*1./+2$-3"3%.&'4$*3,,2*,+15(*+.;2$/.'(-.,.4/3#//*4+*1.330(+.0,(505->=3,&)!0"&&'73)6'),-/2=(0,9*,;**-*1+(6/32;0*2'1-23+*60/+%37.. A,*).!.(2''9.('#3,+$32++61-3)&))08/63/=8%%41%)2*50,(2.+(75,252%25)-7//94.(-.$6&/.,+*@0.%0*'53*3 :)&$18!61)+52/.429,./$8!7()4-4/++6*3.+.%+&2-)=5=3,+,:()6%-&)5322)+3$6,0)1.3'*!13+#*9+4*'+&**'7 96%.*'-(,3 &54-*1')1,.)+09&2,-'2&/,8-);'1%//'1!$+>6%010,1"6,2/*%//0#'>('-*./).:-!),--%*)80.+3(*&17,,0'9+2.-BրـԀڀԀӀހՀԀȀ׀Ҁ؀Ҁ׀рڀԀրԀՀրـ؁)0,,66+*%.*9)65/38002(,+*34"7)3)5'0,/12&*4/.5=('42/6721,%%/()(7:=1*/.2 /.35*&'%+%,3/-'1*4,+15-38!4,2/73&5/%&+2/.!+;/08.*+32)''#-48,6/+/2+>,%;.+'*+4'=10,,8+'5%!=17'81++.,=*+3;5-$"+51-4$689-)$(+4,'0*/=/--+3(,-'1..;.,28&)19'",7*-=,(4+1/+!,&.)'(2**:3481+*,%/,&0"1$&%)3#3'7 ,8/,+2)'5+5024"%(9%691//%))7(.%-9)54*)5%#3/%!1**616:#253//).""16.;)1/#+!*/1,/).1;4+0*-1'5+-:,'1++4&/*+A4.4)?4'3!5-)",)+/72,5..0&!.,6%'*'17 "'6,/1)3'9'1?&0.$*'*)15,)+%) %3( 5'+"-9101*..11;+7,0/+5%2903.&4-0*(8(11+9&73*.65&%+3.K233-,ҀԀՀՀԀ׀׀Հ؀ĀՀڀـԀ܀ՀԀ׀Ӏ܀ҀրڀԁA7%42)*+30+-(B9:-35-71-606%//$%8))//.,/<0+7$&**;;--3')$9'0/)%'1)'/(#+(++34/.-*%,.63*-!+2.034;5-;((!&'-*/10#/5,,%;%2-822',,/.+$1*;'!% /.*7%2-+' +:332//+/+*)-**,2%<'11/# 5/&1)1',21%'3,0+106(2,61##+.(+.$+311(9$+,6)/96.$2((!,2"6 1++00(1(/4-)*)6//$2.6/0+.+,*+,2(,6(-2,*+(%7+%*4')*/6)H#2925012564"(3%,63/8&50$,&;*%3&(4=1/3&7>34*=.2(>/ ,"/:)"3)-$--D).6",'% %.'!:--)+*;+*$)+=4*0+> $8&%07+/;$'3%-*--/,-*5"--3*1(5<13'5&$/3:5'',6+2-:0$//'6/-#((,/*8##%%/2&")012,$1/2/*+&.*!5'%1"-( *%,5?*<7110+73:5?.-6)%(K.7ۀր؀ӀԀ׀ӀՀՀÀրπҀ؀׀Ҁ׀Ԁ؀׀Ӏ؀Ҁց39+)/3+;+'$+97,4*31.86(:)*8/,9*207(';:7++''3(./3,#3"*&(74':+1,123,/(46;=-!+2*-$5./)45223-/?'2+81+1)142-)-*')&25,$6))-'-%+,71//-%+#-7-!.'// (7)!46-2./(4&-2/7+,9)!/6(1(-53-<#"0-(*%)$)%+$,0;&7/#6/+1ϫ'22*';#/+5-99"03.(2)"()0'/02-')+79&401901)7#8)*3**1)"09&5*'.%5,+&1,1+/'60)3+1/)$2*(/38(3,8/"02:*$*31-)+1-*> -+9&,)9*,-516!0)2,-4!+'+-526-21/7/1-52$0-39.-/5'2/:.&*&7*72 11 02)'7B-$6-?5//,30032E-=+$,*':*/*2/%2+,4.37/4-#++-%06&+6611=9")*/-132$&%2+&+/-140;#1/8,0#%40#733./,2*2,+60(75)(/,&1/2#7.$(,%:7)+47&&-340:61//46*-2(>#+$/"0"*3*3)3#%&1+7//(&'#1+050(,--%--(/*+0*'')1(3@%/-%",$/4;3&/2,(88+D,=2.&'/$0*-)/%---+#(+0=%( 5%5?2*.%#52*2,582-:0/'6267; 441#-6/451-$76;222-+#/)'51'0--)2*/*80152)8+*%) 1,7&6--,/,%$=$).63,"?.-- +1.2-$(1*129/%941/0/45-.,)5+&$1$2&)3$4;-7&11,)#2'./'&''12-/1'",5,7-/#'-*%3.##05ۀۀրրڀрՀڀ׀ǀ؀׀րրҀՀԀۀՀԀπӀ؀ԁ3&$50:3*,*56455,-4*/(9)%+<1/'+=14=42()&8&/0.1575'**/-0295113+-)63*-*/0'20)'->.02&4$%3-/6$#70-9,0&,%312*1+/),(,+&0+/#+/*++"020./'/+7.&.0',7-52"( '51#3;-:/1')21)9)*'?#7'+&/:(+4)/+)'91!,#*.)5+,).2$*+022:>)46'$%"-25/3+.%/7*<3&&(*09*3-5-/+&7,1-+"0/,-3%".+/-,*=)/$-*0(./%9+'*(8'*(*2)5'.&+;#2+*/:'(+%068')05@/<1$/->7#+-%- ,$.,2"/3!.-7$)0.&4.&3(E3;+(%--@7/-)9=75+058.+2).*-)-26 /2/-'-0)7+*,7,-)'460+,22.09+-'81/+B&3/53.//: +&+(-6/5(&!80.&(6%-40-%؀ҀրҀՀрـҀ؀ȀՀԀҀՀ܀؀րր׀ՀրՀՀׁ;)2+8&/)&).25%4)5-0(A ,)<*6:$&0<39'7*1+0$,2.76,/1/""0 51&'5,23,!0-*.0>3))3#,+''-,9)+$-/&'-)/19*2-)+9,4,/&!15*!56"%)8-*//#/-'**93*-'$/@-52.-+:0$/+2++,(-).-)0-)5./#+/4-#(35&01*-;;3")(0*5/;)('8%/70/$8*&.-6%%'1436!%-7*/-/&9)2,*26'+1=#///'&.&1*)C#**$+2-,%%0)+/')#31(- 0-12%1'4+.$4)4%-5(2731&*<,768,4482.,)1,7&3/ 1)(.#$21.9,3,1025& 7*#5+:-*(652+#*2+*+(,'"**5-E2 /%0:--/6-($ **$//,(.,71',&04)1*4* -7:-%5,)!-0+340/950'7-,':26'20.0+$-%$-,#<+/"1%1*?";/,"%.1/5#/%*%,3* ('4,+0(+4)&8/)/,5/)./.2+, 7=%*17;;!+/@1$/736D3-(%$/+&*1&;)&8,(&68()6&&9++#*'43#)+-+0%*#77*1.$#!<'/)-7>00:351*%950--B+8.8;"6655$3A($:.133'0%*/-10.//0/0%21,-1:.8/.%*,.4$*%@9$(( $5./24$/.-)/?/5!/3'",+2-++1.--!3/*-/,)*6,/1/*3//4%*1;)#*/-3$1%.8$66*-$1115$242*+*(-$6."6,*&4 3/1):"'26,7#9,?!.$+*&0%1($.'2"(((=";3.48(8:/8*$7'?30$05,;1<1+-0')#'2/%.,(32-%"'505./0""1++4302'0*04*4,:%*7*!3/%."/(222+(1)-$+. -+)0+.+$6-63('2/0$(40),#&.%*$,*++$*93*4,93/.)0';502*76(*!3-5;/"++/+&>615#016)$((//)-/: /255)8>6րـՀր׀ՀـڀـǀՀՀЀۀ؀πҀ؀؀րՀӀр܁$-@$''*/**3,%+647:++5-,8-++2>+**488,')*,+!78'$*65)&--+/<)*'(,1&&"677:5100830,*35437315)(70+7)1+3+&;17+%$58-*//&425'/9*5;20,%2/60<%&+0+08%*;1-'/08-*3107:3(7):.)#++1&*5'/;&:,%,7(131.%$!--8%2=(,0",0,*.3./+''726-0%+6/!,#+)"(&.4/(+4.=3* $2'/>3',$2&/*--#92457/75*(.)(3.+$1&7/9+>7/-?-.)'93)068/;739")445,*/(!5++//"*(4(2-+ 2"73.50/,.)+&57''&,.+4-/""-&$$2,&-'-'%71#&7,6+/(0".55"(60,*"5$5*18,(+3#!/)"1%7+$.)-.1,.(-&,/2@022*0-$#/%#<.(-3(/#5<*12-+7-0*(;56&#+&&30.,70*:+-<02*1,/.20(+/.;10)',(602*,&7024,1(*-50+/9ـ׀ڀ؀ـՀ؀؀рȀրՀրՀՀՀـӀ׀ӀրՀЀՁ8.,+23@;*44/55*:2,?1*69)2?21+609-:;4<&.+5*--50)583')+'2&&5<6.(6.,9.+%)6$&&10.:,'+02.&?+1>3)/)1>)4(/1/%+)*!$'$++ /-->6.9//)/048;033*!+,!$"+):12,'0.22.$.+1$)+/&(6+4,8/*(2!+0)3+(*+55.62+'+*5#--&06,/$1*-)-1+'./4-1%-27A)3&-&#$(/59*3&;(+';/+3.?5*$/)+(-+1/1+,5%-$)47,+>:5:48*(,<01!.($.6)+.3)36&4/+4=/31&)?,3%*/#-11)8-%'03/..-))1-4+%5'51.134*-9*/0%@/107(11),)))9-,#-$+%07.*/1:+8/2(/+"./,7*0.!>+(;(.4!2)+&-+&0/5(/,'1 /,>#-)/57:)('(2--7%+./)((+6$'3'))+,4"6$(0*78?5.4'>&'##2.*0/+0)&рـрՀ׀ՀՀЀ׀ȀԀ׀Ѐ܀րррـрڀ׀׀ـՁ,,1&.&@' ''.$,3(+,)&4&)7ϒ-1(74//1+31-$ -96.*+*,-231/*,*-33.!,*+)46'5* )2&*/5-+44.(2./+32/19.4($):)/ /"'#/%-.4!*-)"%(+0,/:02,'%2/2(-1.3*/4)3-1#-0&0#2.,,.5+-,:1-$-'0/'7//297,3)*7'#,3(+&.)36#'86+=5%-0/,!50,))%09,75,,19+6$,5$'/"77<.=.%2,*.+80,3,502).-,I*+'*/&-./*%&8(!)7-270032(.1-4#:,5+,3//!0='11$/#&%%&'*!4-/'.8)/0-3'.+3-.6)2-($(./7#*)'5)/1+- %"646'./8-*.#,+-,.1;#7-$/6=,245."5'221/,"/,%=./#&?,4($217&(4'345'/'+ =*-7-).&8.1*@3+1"-/%(-*..+16--&+7*%'2696>7/%/)0-=,'61./!-*+<909,-/2'/!('43&719-//85$ـրրр׀׀܀׀ӀʀӀ׀Ӏڀ܀рԀڀӀҀڀ׀׀ځ(6,.3-63*#502-764.(24408,(1*586"&/0)#489,"(?@3+&;#**/5A;4(&49')5$,%89-579+,2./*06&)6- &&4./'2C*1(-(2*)&41 (1"#0-;7),,(90+/,.>'02+192'7!1*'!--,+#E.26,,0*09=.&)%$')2+-91*21$/10$-:2/$7'922 .(37+219%72/'31-/7+#*.*.4/'",+'711' 81,-7:5'140 +*.9#./ /162.2)1+(' 05-*++1&0:1!)1)3*039/.&%3*'*).12,&+22.54'/;-3/!(54-24*02.'=+5%/:93*+32, +*6,(/<)+=.+-.-'4.-(3/3,*7985-73+-3!#-:,93)/12/58!/)1),3-70#(00*39"(-$)))(%+-6.( (2*400+/06*)323+'+)%*(?%('))/:)9+08210./212'3,#6,/2)28<$24)1$73481,3',4<(0-.18#13+.($/!/8(.!3"%3+7-$77-4/+.)11#4 (.*)(!.'3'0#97<7-+;)50'%.&4B*4ր׀ـ؀րـрـπĀӀـ؀ӀӀۀՀ؀Հ׀ӀՀր؁5/!.(6#,31(*'7'02B':6+0:2+'//&'>0%:3473$12,'>2+'6/,+/815'3'.,30**++!11)E;00&90)9&=/%2#73*2+;91/#)5/"*+:%/,,C1**=%;)./,"8,2$,61&/3../)%-7 -.6/;*1&<&3&)'*+!/, 8&$.%%+)(7'15&6;20+7(*61,'57(&,50B&1*2"%8*&.,/-1.+,7/4.-%3%2$+2/ /(9*05:36)71.-7$(3(!)6'0''&"1(41(#5($)+1241;&+1316)#-/-*%&$-0:462%6/*(/6%#-(8102($445%&53462/1--/9*17%*."*2&/(-7&/&,!1/+**6-4,6#-/(2,/-'%/:#*597(-.:7%-/3B1031*J 1'02--0*/>(-+7*/0-+,%<*=+(7)&*5-.483--86952003)#42./-(,).13/:5(,$2,3,')B5. /-/++-7,.6+4-1153533@#<';<,64"7)%'',Հ׀ր׀ՀҀۀπ׀̀΀ـՀڀۀ܀ӀۀՀՀ؀؀ۀс-)7#&/'43$*35)/13./,&--4!*21.;/(21&.4:1+-#&7!!32$58%G6+3;//(14+'+++'(1+-2$).-+9+"?50*-' !+'B2*#57++8:%(+&24-57%2,.-77/08/.*.5%5,'40'5&-52-2.1-$0%+/.&1.-*/7!&2*2"**&$/-)&&.%)0!+"4(&%3).1/0+,),/-(8/$/+0).(/#.)4*'-,:081'9-,,-?)0&!'7-8?0-(%/3462'++(<(-'*-.>5!'%>5=0/()()(+9'!6./.0/0518.$?,+7'9/*+067/('0131+$/,,08'2,1,%-;4407-*9-)..(//0&-2.(/+)?/*2*%.)<,.*/6+-*&09*.,110.-98-))//+)%/2)7'1.*6*2/3.4660/2&%/4/184&94(-4,-0'4.'&*..',1.145--$&.&*2%?-4$/6*/ ?/ -"-(5,9.$-,1(+93*-203--)3,4-2,7(/<&++269&1)2)B3+׀ـڀڀـӀՀՀ؀πӀӀ׀րۀրҀр܀؀؀ӀԀӁ9,,#)40*7),'2.01&7+14:**+-,'*.10)#&7'3)025-)$)!633/%93%*:313(./*(,653%3011*-3(*'.//5/%3((/-/'&./!'+)2702:,'C-2:3 91:22;+20-./53*3!6..)'93?+ 50.2'23)(+0*,/(0!% <,4):031+"-<6('1'5-6,"42?67.1--) 5*/3*.61%$-+10@7((0151-"3/,)"&0()%"-,4:9/-377(9,/>2)676)+&&0#/*$)/28(2)4..*1,)0-1-1/3*1'/)<3E,%23%1-&-&55!'(O&=870(518((.,+-,04)''׀ԀՀ׀ـڀ׀րπÀـՀ׀ـ׀׀ـڀրҀ׀Հ؀Ӂ6)/+7/)70)4.+)'2+!+0)&+'52!,*1)4'5+3)/'51/1+/*3/!(/160@"13;./-*0!/1(*#1=:/01E://'./-*->'/"4(4+3-.)/*)9*$?.8#875*"'-,,-#8%9<1&?2#'&/-#&'0.('$)/7;/4*%-*"28-4#02110+94/5454+9(+'71.:5435&5/;8#/*02/#99'(595,92/%"/' /1,2.)-3#%)<31>>345&&;%!0)&4('#7,&5&'1*22+-!$%(2&)//(0-,5),)6!/="(,%%,1-;9?%*:4'3-,$50.+&'/./@2&%/."54%',4.$4,92$#;%.0)*;5(.7',1+*23:$5,!&,,-6#/B+,--,()%/+,,+(6*39"'6',%1/*04''=$+4'&D36:$72*2*+20'.56%/72%+8.815;"30#51*)8,(.,*;702*.,,");&000#,)5(##-2),((',>1%5+2+>0&*2*/&1-$&2)1-"#/+%10(50ـــӀՀ؀ՀԀҀڀـ׀ـ݀ڀ׀Ԁ׀րڀ؀܀Ӂ(/-7%$38(.:%,883+-*/54.(6-)04&-<9 -14)26.+/+B(45#7'3&/'(9&'1!8.8.)4025)<%05".,3026%5+103/),0+)),327&+-3-,)320.*(3851=/.'&,5$',.0/:!3,5-+-/++.9+- (/*3*3-=-4310..2/916)%77&+"-+,.,-*7'$&'.;./,&(20)771+;#0 --+.#-7'(%(+2%'9#+)(;@1. 3'6)5')()#C3,85,7$-(#.17=*-<,-*+)(602",,1&-9;/0*.C4(.36@,$',+35#<+*9,%!4*8864(+#)-00.6.7*/.90/419(/131(#0.50/+5./6+-+*-/&)0773&'4 4/6';,+7-*>),/4+1-*:90/77%6'+61)+04+,1%533+"+<4$%,/1 )19,-6*261471''-)34+$-+)+/&/4+,39797-",-<2?381*+5(+)./&A)53(/+&/27/1+(44'9.$/9&ՀրҀр׀Հ؀ۀ׀̀׀Հۀ݀ԀـՀڀـҀـ܀؀ہ./9,;%/2'3020F)%9)/:7)5'3*#;2(1A.;!*&#*7;1-,%2%*$('/**.-2:06+:#07*38:6&6+-(644(/).N*440" 53 02/3(*4005,/21))2'*6--./,:-/)1.%(*.+)#-+0A#)5.&$(773)01&*-0:*>)'+6&.(4&%)/B9+5-72>9@AD4-'2>5/*('2&/-)!/)<<'760;#0:3/6/2/*:'221)@-212%#7<--/0C950-#,7&6'5.%&94#.)./+7-2.5(<-7/"+-""4-22.%*5631)'7A''!97'4!(34:)%5+(.0*&7/$(/"+,!/-$$)/(12/1(.,&/12-.&10;71.:,4$$8*4-;--1',,<46$21,9 75*,/278))8*3C,%0)+"&*)3+/(*7;%9961&5/5%8+4$-%15+ 160!"&1,-#-.15=;4/,-+.( *3+*9951,)5'1/&35+3/--/5+1&0108(./* 2<).60420' )625(+%14;($.(1-,5/2;/2#1+'!0%%35,9+(23$/*/)-5)+0)1(224/1D+7-8&1,(4',6.C,&'.93,,/Ԁ׀ـր؀րրڀԀƀ؀ՀրӀրЀ׀ր؀ԀۀԀրՁ20/72*47,>/.(().'.'2685,,/2)++$./3+9".*5+)<(4854/-'/0-.3'09,1204:22E%3.)0)2%5:**40)5*'-2+)34+#0"(/1/B5398=/81,9?&)+!<%*"8"50*(4*2,50)(!(:1;/',%*($ 1/%'*-/$,2'2*(0/-.0+!"2$/:)/1.1'(?"/(.9)6'*4=.1*2**5/)%/!60<1(%,+5+0*48+#.-/5!4%#1-%**6 )*.)4<054#+,)01)3-*+3&4)-9-/311'1/&)(,1&4*,!=4.7%4')378*1*4!-.>(2/%*'*:0.+050(-,*,6)&/-):79-(7'"//60".;6) .6-?45/5.1)21(?.0''#2&%*0/-69/&//*$/44(298)*! 9+&1&'9-,+.6,7,1+1=8/+. $&6$/8+*92D$-(*')0/30/&%**)6*)+3144<30),&7%($1(,-$1;106=9&7:5(4!,,/*4*(!*93'7/'(/;80/׀ՀҀــՀ׀ҀրǀԀՀـр׀рՀڀ׀Ԁڀ΀܀ہ6/%2252&++5/<&),:3*4;3A/(6<$+3#7/-5-5195+.5&1/40%!2,/.%3*2014,&4"43"+1,%#8$).7/423+#;4'!46.;+)"&2#0855B@@L039<33-*$?/+,);8'8+17!263A$*-*23/4/61,)<*"1%053#3335?.#B-''#/9$./.'-'(-0+&-0 =+616*1/7 '&+!)")43>75.9*3//*'/18&40.(-%*&1:$7*'.-!)D/"+)-/1.&)4360.+0,:=-9'(.+67//3$%47*ՀӀրπՀڀڀӀԀɀۀրـҀ܀πՀҀــрրԀӁ64.2/)44 $8%-1)2.9%7%//6548:,'3),.-1*.&'*3-+222/+. +/%,4'/*9%,2.(@'%)2+.7-2'/%4 3)"/8.),//+)&+!-+)/1/8FX:EAE5217)<87/-&.)/4'956%)+2%.2.3/0%*':&-#"/807'-*%7&+5)4D.61&<)(:3.91)./*5192;-.1/13,9+<'2%<068-.6#*(-6;2*58.(//)+1/-2/#34&03?0-/5(/6/9*%?49.00)++/'*)2,,=4';612-2/214'43%?0',1//.((6)! +&5"*86+-&6++1"+*/%)(&#+#.(31)-+6-1-( 70>&*$8+(>")55/6/,(*1/#5&/=5,#*"(8+)0#.')5651:"&&/;1+"$&)+ 9*/1/&*+690,/9*:)#3"*:)$;8)-.63/14%.--%?,!4$.-7.)-(,3:+,9"'.+*1540&20.<)&0$7%..5=+8,&1/1"4,-#3-1,.5/:<*,)/5(3;2)Հ؀׀ڀрՀ׀ڀـǀ܀׀׀ـ׀ՀـЀ׀׀ՀπЀӁ4.-6,;64.D/ ,3'>68>5.40*94A()&)62.73,5&+49520"/=3,7(3!2*''3'"7147-5,&%2%7@(-10 ,'4,0,9*-/(%!+.,7+(..?2;M;=DAE@5=+10.3;&/-.-.!(3+1-#+%-)B*.11:%0+*$281,6'(++ +-)+*.+1.31+=3/:'+$-D2-1;(>:1B'2'.;,*+1/'$044.1)(43.--(+./('+&,"54:?(8$+/4&-//*5/(639&/*,---3276+39';/)(,%4,2,:/<2%('.+)'/+6-3% %)$048&93-6(5)1%(41/+4.%/43+*-,(6)3*(,36+'00+/&.<)*''+)"##/%*,3&(3(26.+/..&+.3+25)('*73/(6+3'1'&-162*/8+#%865#2/$62&33&2&!3'%'-+5+%'+2&-0?--',-7*5>A,2,461+/-&/;5&)/,;(#.(,;7630,-879(8//003",0((+/@,0*3)%/2-!9.34+6/620#)ԀԀ׀Ԁ׀ՀրۀՀƀրԀՀր׀׀ҀـԀӀ؀ԀҀЁ(22;7'3&:/6<,(8!&7/=:2300$&(++.,7,<.4*5+-(430?6/3:,5$=65&3--,/&+&+:2$65*!!4D+-.$%038!+I*)/%$'0'2/(00GF=4@ML>JE:.<7:+1&.03%-'A"8&,0423.#7-.)8'%-67)+4%0,/2'.//*+.12/-B1%6*(/).7*#(/$'&*&6!-:/0"0-+*8";.7&2"+(0*51++2+,&=.(1(9/4;.+.(%.18!0(1,'-+,*(/5(3;).50+5','456../&97,;5"%,1?/4(/+/3+6A.334'+#-*;.,7+2)&(5*-%-3>2,(/8-,*8B*-)1-$,204/%:8(8-04&9+3*)87+"6*6/),&+/5.1--4/.#;%6&0910%%*/$2'/35"'42,3!&,07/4'*--&('51/:;440*7:--)/6-4$-2-9&$8(";)((#1(-&)/270/>+6353'8?12/2-*'/0'1+'D/%:6301++*5-49:*/&*14+4"*/+<(C؀ҀڀӀڀ׀ۀՀ׀ÀـӀҀ׀Հ׀ԀրրހՀՀԀԁ03774:4,3+30.$)E335/-7?".+-*(26!$08/#.()*466).$9:3/#B(A58=751'3./#0)25-/151)2,,!2213(/33&2'/'+13"#%69.1;NBHH=C=?:..#.*00>,324%9$**+5/0(85.*+&,$8&)0);(>,54*;0--&7,&5,10.%/-*+)2+3)*75,!-/*+31+!84)'41#10/1408&3*,7,>-':=(-#%04*07'+%42.7/43+10;.1#)8)156+/E&.-'9-,';3)/>$/557.,)/,!/4-0"471,-0,/=81--*+()/%3,2. 7/''938,"5K/0)*)+3(55/142)-'(''-3.0@*/,-7%4/.')'+270(*/&52020)*2+,0,,/$-+0/<,3)(/*.<065(!-0=23362/$.-4.#47/232+%.#$2,360(*03?24-),(".<*+2+9(,:7,&+04.,6*0.&4/5B'/.*3(7321,.4+750$,#:)8(%/3Հ؀ۀր׀؀׀ӀՀŀрـԀ׀Հڀـڀ׀ր׀̀ՀՁ10/---<02)%6:&7**++%'73.*9/,9)10+*3-"@;=)/'7)3-#2=-7+'5-)7+27-'6+-*'-%+!0)%9'7(8$$%80085.0'8,+*16A>05>D//1+.,'.=)$-4%+%.4-*19'<(5.)5,/91-.9,/4/13<5-7.06017035?5/6./--3'+)-7632 2(-&9/7!<66&5.@=B=?II@C5@*8:&:;'*3 ;.:)#542,703-6'/-6**0830'53/2&(%9(),.(@/)-#D'+0+0/5%15867%,.+'0#*'#'C9(:4/>&-*).1.1,4-7)416-55)5*#8&.0*/%0'35,9(*3,902<%(/,23>+&-/4+-:/:*07#81575/6*,0+2)$,!%>2<2$030+**?-.00/**),6(*7%.&.5/.,*.0--580">"+2%,801#@/"159(-0//-34$,,5,C,0'&/#331+'2-)8307*%-)-&55-?$4>*4)5*+6+'4/%#333*/3#%36./,3/7&.$1'/5+/"+-+&,4*2!5/463"!.-%+/5/,*2402)609+-;89/:.'1-;2!435%0 /(1,3-80-+"32)'9%2'6+ՀҀ׀Ҁ܀ڀՀ׀րĀ؀؀ՀԀۀՀڀ׀ۀрրր΀Ձ&0/ 47.077)-4A/D:+/)281.(.2(,2&.$1318%+$312:5/(941%4'0+9+?5-64)**+,--6/>0&&.,(-"7/.*8 67-*:+(&F..6$%06,5#.,5//&1,%7+32/,;83!:2.#/'8,$/)75.5883>1/-283ԀЀ׀ЀԀԀԀ؀րˀӀրՀ׀׀Ԁۀրــ݀Ҁڀ؁32)!0@#44@0*'35301/.*))-,-/&.44/<5+12?(9+&+0@2,24.:&2%&132%.(1).60*9.:/A)#*'*+.,5//.8/16.206+&&'+?)=5,E%2H2+/:2492%,6.+>5/;(*09,:0*0#5()-6161%,0.'21%.-55+(-16'-$*8%3447%+;)-0(2,(+ ,F7-.+#1<:-7)8(0$7#/29+?.63*).*@%7&2435!16/$-$*,3-.C84..*+%83;:,.%/8/878.**%:&,&,,%:&;0&./$&00$61$48()*0+4,14*$!#(0+0+5$8&/62**1)(=%.<0!%7-(4-6.6657)"!,23$5*''0=44;#-A"=,'/57027620/)5%3!!*.116,1;2;%%1',/03/2/,,&1--'05'.-$ +.+6()7:126<#:/&;'76%-'.47;@1$*-4:4-4/:5.$B&2?1"3/!=".-,5'+$<.7./@&18"*+!+/32+>/+<<9<31*,=61#'&344'<؀րԀڀՀ׀Ӏ؀ր̀׀׀ڀ؀Հр׀Ҁ؀ӀрԀՀځ8&.+477#0()+$1+3'1%/6)-3,/1!#;/%8315'4.-+27)9.110-)2)**B3+5.8(.4*-&+4"'+0-5+)/(/):.,+/$2(0'3:)/14162 3,@,+&)3-6*<-$*)9 /6'9023%,#(+5-"46'(-9/).85+7@:..2(#%0/2;*)E0,7)6&*')/+%3!(..&!$=8.4+*!+/0<79))0+*11,4-(+)4./33'#7.%1%-/-+0((&61;7=76-).+'5$3+-55+**$73-#$+/38/'3-&29!)7*B128)4%2).%#0+,)0*')$490469064?2=3*)0)%4.*5.2+-*1$)61-6&3*04.%52+9%()&**519+2759/C0)4*5"-"7()(0'2$.65/7#-'45,$!05%(-*+/96A//4/3'4,-=3(50%-,11*08,1-/=>%41*"428)20.*&5,+/*+6-/.27..67(*,21-11(7.%2("81*#@38+/)4.'-1.76')0+/.',(90&/4,'/)**#214 2..3*$)4:24%#%?,$,6*-44.7*(-!'6.0./0/+4)---36;/+74*$.6212/)4/&",)$$$3.3*0'06//,%+.&;-6--'),%98/30#(4*/7'54,/(%$*#%(5"-$0-(-'+79'22%)/(3-,;0(1-0"5-'//38&,#1+351(45-7-;.6$3: 3)3)'0#3@3'30(0'/ <>/3))).$6* )5)-15.1=+*'.%3)-73,((2'4/-9!,)0.#>85'01*/,)22/0.6*--99(#(4-5-,!2'/9'%7(3,/2",-))!.7,/66-6-*''-1&+(0)/-*:%+<0735.-$.*(1&0;3;8?//'&1!4&ــۀӀ׀ӀՀ؀рƀ؀؀րրـڀ؀Հր؀ҀՀـ߁,0(*716(1//#,",3-2?:&(-1,,3%'(6-/D0'@)5$3*,#'+)/,+(!*'31)#7--*0/'/+3/1;4,8/5/4'*..(122.#05'6-&+%/+<43!:49753%//9/-160827".+,/(/<2272)+<)35+,0(17)(02&/%4716%.)14-6.3.#51/+/,-9+09'250%,38&;4*-0#63*1.4$ 8,>+(3(/. ;*1758/+4,&-23,/'-#331$(;1/:/$-&"51=0&".'36.3*:0/./,6,(*7532/7454.#)/2-%).-*/ 4;'3,,2-#1*8&. !+.-22(-00,)0;6,504)21$./),1--60,1*../2/>((5*5/1'&034064,.#++/0$379+4,038-9/7.264)002+2'.1.14E-+,2"5**75?+,!+0)..+3003)10';16,+6+ 18+#1@4.6:6"(31()%+542,+&9,(/3$/73/-+:-"5/:/,.2./%,010//:6#-*74*ــրӀӀրրՀрŀԀҀրрՀـՀڀՀՀ׀ـՀց=6+-)4:A9/0/%&12)07;9#,7;:,3+'*%-+68+75-0801+&+45&.)%8%0<8,)/%0/3.9<3/0.++8/(@/.40'0-21;'5')68<2/0./1/ (=91*5;5)3/3506'0,'6%:('8-27,+9*;&+08(38/1&/,+45-4+'%#25>'5,/0=#$.('/2?4":4!059#(6%8.3.+:)**/$,11)+%5*$4.$/,),4&,$(+01-%,24.'3+154,-+(3."/0*::242&!)(+2379)0-'/+-+,0%.7(,://.1*.34&..:,7/0(/%=%G8#)'38+)0*(=&./#+)5-1*+(+31+7&"70-*'A,54874+/8')<$#6$',/<53'-,36$02)+;0=/23'8%$:29461)34)6>%/'*&-327,.!/'6,!3'7$&/:3+0/1>2)&'))1;5B12,0-6.-/0+1$(%-((6$14)+635/7.#+&#I-,,,.<:)@9")0%-(+$9"#4*&+C&12+<2.'+=<.4(рԀ׀րӀـ׀܀׀Ȁـ׀ӀԀ׀ԀڀڀӀӀրрـЁ5/(($// .,-%1-10'-77.3).72645"%*)(0-101<1/14*.(>,)#.*.5/'*:+5-+')502<;1+.<7*3)6201!().2:.++)/-5.6107-'15 44-4E%5'-&$,(1883,-((#+@/92%A'&"4./>!#/+).<)+-4*'+? 51'24311%-6(*0#0(;-+31 )*)!673:'523**-/35/*0($'$%.450))%/9/310">-(.212>(/)4943,%"42.3*)('1+3),,&19+!&!."/,-7 ) <+2#+"%>/%&&+.6(:7/(+C3+%1/2:+61"5(+=8.-,-,,0/24($3-#24 -./2).008/,4,?3/5-2)&0,'( )8767/)#-&,-5$:28.;'"0+$)(**&)3')0:(-'%&*('/1(483)$5<+2!/+/.A1'5&41/4*+)C68,<$-.-*0,'+113% +31,%./+3.,2/6B2/1)-: 161.('A,2++11709#:/,>"/D9236,./.3.07'܀р؀րԀ؀؀׀׀ÀՀ׀րـՀހ؀׀ڀ׀Հр׀ҁ,0468053-135(%/;-+)0%-/1#*714312:,)52<.4'600-+!=:)68+1,,7).$/5;-'812$073*/1+)/146;A.43+-/;&-46+02.08'&%+3,.-.18#+/2&56%*8.-4(..)515.1++.10-,:-,$733/=&213(-.34;(5*G6+5'!?/05/,C#)(*&*9$)20723(0+2/').):/ +67"..-1/")1/!/' -/%-3,,/-+:/*8=-.9=7+*#:&/5(8+*&3/2F+('69-7".,?0-20+3.%(60293.*(1(.31,-%6>"<8+))'17//;/3"3*5'/54.54/,H62(&)+45+-245- 1+5''3!",*?2./+*'0,<20;$*&47+."$0",504%+7+/6066/17"--A#.,2--2)6+&6)/-)+A.4>$.6(-4;22''-5)(0#.%%&.,--*?@/?34-&2539<3",'5/1<#:/7.',(1,8915-B42)59$3/+7&$"--(233(/=%1-/րԀҀ܀׀ڀـ׀ԀŀՀـԀԀـ݀րӀրԀЀրрց*<>*'213258/71#5498/86--,2+62),;020--.1)'))5!9+).*""+<463#.0$B&64/),*;48',(2&+191-//9-.("2(".0-/#"36+:41@/+478:0/(.+/26,67& (4,2(/44',$9*4:4-:4#17.:.67+.6+33#&+;51'-+/33+07%/B77.22$9+(514,0ۀ؀րԀ؀Ԁۀ؀ӀŀހԀـڀԀր؀Հր؀ڀ؀րׁ20.&"/2&5&%1)).6%"(,/75 31)1;("%++--'1+11')*0)*"(-/+->.0#&.+-"*55-%4'+&41 ?/$!01+)4';'1%-/%)'1&)(5)/&,'('*#. ',")2+/17=+95"2.*/ +0$-.!/6$3,..(%/,-+%5'7-.,)+5(41),/$&1(,'3*$0''-)&%$')03-:'8"4%%22+!)'"/&+,*2"1'/)"-.%<& /* .C+3/,&-1/+'0(!'3+/),&/<.&<.(/,%)(8-+,;0.5%/2"7/2#06'!!6'.=128)'B)!'"-0&2>+$,,!(+!#%:!!(/ *-& 4&'(*'$.)-3.%+0+1&*'3 /-("-&,61%-;6/0(+.,071)!$3$/2&-3.5;-'(/)-),*4/86+&#,'#'0*22((+5--35.$/!$B*(-613/%$$33)-*0,(."*11)0(+/(3."()./(1$9%-7+*.4,3#)7'()'%().58+"8(#$,(41#*.+6./5"-!3πЀӀրՀՀ؀؀؀Ȁ׀ՀՀ݀ՀҀҀԀՀ׀ҀـՀրހӀـڀڀــڀۀڀ݀܀ڀۀ܀׀ــڀ߀ـڀـۀـր݀ڀӀ׀ـ؀Ӏ؀׀׀؀ۀ܀ހ׀Հրۀڀۀــրڀ׀ـۀ܀ڀԀـր؀Հހ؀ــ؀ۀ؀؀׀܀ـӀ؀ـــ܀׀؀ӀԀ݀ڀـۀڀۀڀ܀׀ـــՀӀӀ׀ڀӀ׀ր׀ۀ׀ڀ݀؀ۀۀڀր׀Հ܀Ԁـڀ׀ԀڀӀۀۀ݀׀ـրڀـڀ׀ڀՀ׀׀݀׀׀؀׀ـڀ؀׀ڀ݀ր׀݀܀ــ׀րـ܀߀ۀ܀݀ۀրրހۀ݀׀؀׀ހـڀր׀׀Ӏ܀ـۀ؀݀݀ڀ܀݀؀ڀ׀ڀ݀܀Ӏ܀Ѐـ؀܀ــՀրۀۀ߀րۀۀ؀؀ހ׀׀րՀۀ׀؀ڀڀހ؀րրրڀـ݀؀׀ڀ܀؀Ѐۀրـ؀ۀ׀݀׀܀؀ڀ߀ր܀ۀԀڀրڀ؀ـ؀Հ݀Ӏۀ׀܀؀ـԀՀۀՀ؀ـր܀ՀՀڀڀ؀׀ـ߀׀ڀ܀݀ڀـ؀ــ߀ۀڀـ؀ـՀۀـ߀܀܀݀܀ـ݀݀ހــ׀ۀЀ׀ր׀ހ܀ۀ؀ր܀ـۀ׀ր׀ڀ߀ڀـ؀ۀڀ؀Ѐ݀ۀۀۀՀۀހـ݀؀ۀӀ׀׀߀Ҁրڀ؀րـ߀ހր؀ՀՀՀۀ׀ހՀـԀՀր݀׀ـրۀـــ׀ـрۀ߀ـހր׀Ѐۀ߀ـ݀րۀԀڀۀـՀـրۀۀۀӀۀ݀ڀـ؀ڀـ܀ـπـހڀ؀ڀـҀ܀׀ڀ܀ڀӀրـۀԀ݀؀؀ۀ߀ڀӀۀۀ׀׀܀րրՀ؀ڀ݀؀Ԁـ݀׀ـր׀ۀـր܀ۀ݀܀Հڀ׀ԀـڀҀـڀ܀ڀڀրۀ܀ــր؀րۀ׀߀ހ߀݀ـ܀܀ހՀ߀ـՀՀӀ؀Ҁр׀ҀƀԀրՀـӀـЀրـ؀րրՀ؀Ӏۀ݀؀܀ۀــ׀ڀ܀Հրۀۀ݀Հ݀݀ڀ׀ڀ׀Հހ؀ր؀ـހڀ؀݀݀ހڀ؀ـڀ܀ԀՀــ߀Հ܀ڀۀڀր܀ـ؀׀׀ـ݀݀ۀۀڀ׀؀܀Ӏ݀܀րڀڀ؀Հۀــ؀ڀ׀Հــۀڀ׀܀Ԁ׀ۀրӀـހ܀ӀـՀڀ܀ۀՀ؀ۀۀԀڀ܀߀ڀӀ݀ڀՀԀ؀ڀ؀րӀր߀րۀ؀րـ܀ڀۀــڀۀҀۀۀ׀׀ـ؀݀܀Հ܀ڀ؀ۀـۀ׀Ԁ܀׀Ӏ׀؀ـڀހ݀ـր߀Ӏـ܀؀ՀހـՀۀ؀؀ـ؀׀ހڀۀ؀Ԁրۀրـ׀ـՀۀԀ܀܀ۀ׀Հڀۀ߀׀ր؀Ԁހ׀܀ՀրҀڀހ݀݀߀ހҀۀڀۀ܀ۀۀрۀހ׀ۀڀ؀ހۀ؀׀ڀـڀ؀ۀՀ׀݀׀؀݀݀׀܀ڀڀրڀҀۀڀ׀܀Հрހـۀۀـۀـրހ׀ڀ׀ހ߀ڀՀڀ׀ڀ؀ހ׀؀ـ׀րـۀՀҀ׀ـҀڀـۀڀ݀ۀ܀ـۀــڀՀ܀ڀڀ׀ـۀր׀׀ـڀ܀ڀՀ݀ۀ؀݀р׀׀݀ڀӀ܀Հۀ؀؀׀؀ڀـӀրـڀӀހ܀ހ׀܀Հــ؀ԀـՀ؀ۀ݀؀ڀ׀րހ׀ڀۀـڀހۀ߀րـ׀ԀڀԀـ܀ـ݀ۀր؀؀׀؀Ӏ؀׀׀݀ۀــրրـ׀؀؀׀ހـ܀ڀۀ׀ڀڀـ܀Հހـրۀրۀڀۀրրڀր؀؀ۀހ׀݀ր؀׀؀ՀՀـ؀ڀڀڀ؀Ԁۀڀ׀ۀــــ߀ۀـ؀րۀـӀӀ׀ڀ߀ڀՀ܀րԀۀ݀ڀۀـۀۀ؀Հ؀ڀ؀ۀ؀؀׀ـՀ݀؀րۀ܀؀Հ܀Ӏ؀Հـ݀݀݀ۀրڀـրҀ׀Ӏ؀ՀÀՀԀڀրՀۀԀՀ׀ـԀӀր׀׀ــ׀܀܀ր؀р؀؀ӀԀҀ׀׀߀ۀՀـՀـހ݀؀݀׀׀ـ׀׀؀ހۀ߀׀׀ڀր؀ۀ׀߀Հ׀׀ڀ݀݀߀ڀڀրՀ؀ՀԀҀ׀ـۀՀԀՀ݀׀ڀ܀؀ۀۀ׀݀ր݀ۀրހڀր؀؀݀ۀ׀ڀր߀׀Հـڀڀ؀ۀڀۀր܀؀րՀۀրڀՀր؀؀ڀԀ؀ڀڀրۀ܀ր݀ڀ߀؀݀ـ܀؀ۀڀڀ݀܀ހՀۀـڀۀـԀ؀ڀԀ؀܀ـՀڀӀ׀ــۀ؀ۀۀڀڀۀ݀؀ڀրր؀ڀ݀܀׀րڀހրԀ׀ـހۀՀ؀ـۀ݀݀ր܀׀րӀۀ؀ڀۀڀ܀ۀ݀ڀ׀ր܀րՀڀ׀Ѐ߀߀ڀӀۀ׀܀ڀــ݀ҀـրހՀ݀ۀۀ܀ՀՀڀـ׀ـ܀ԀԀ׀ր؀ۀԀրӀր׀ـހـրՀــ؀܀؀݀ـ؀׀؀ڀڀ׀؀؀ـրր׀؀߀ڀ׀݀ڀـހ݀؀ՀۀՀۀ܀܀ހ؀ۀր׀߀؀ۀڀ؀Ԁـ׀ՀڀӀڀڀۀ܀ۀ׀ۀ݀݀܀؀׀ր܀ۀՀۀڀր؀׀րԀԀ߀ـ؀ـ؀ր݀ـ݀ۀ׀܀ـۀ؀ڀۀԀۀ݀ـڀۀ݀ڀՀ߀ڀۀ܀ۀ݀Ӏ܀րԀԀ׀ـހ׀׀րԀ׀ԀڀـրՀ݀߀܀ҀրӀ׀րڀҀڀۀ݀ـ؀ڀڀ݀ڀҀـ؀؀݀ڀۀ׀րـ׀׀ۀ؀ـۀڀ׀؀ڀՀۀ؀ــڀڀ׀Ԁ؀ڀ݀ـ׀؀؀݀؀ۀ܀݀ۀ؀݀ۀڀр׀ـՀ܀؀ـ܀݀Ԁրހ׀ԀӀـ׀ۀ׀ڀۀۀـՀ܀րր܀׀ր׀߀ـրۀـ׀؀ـր߀рڀրـڀ܀րՀۀـڀـ؀׀܀؀Ӏ؀րـ׀ՀڀڀՀՀЀ߀ҀʀԀրҀ׀Ӏ؀рӀԀЀӀ؀؀Ԁ׀Ҁ׀Ԁڀ׀܀݀݀Հ݀݀܀ۀڀր݀׀Հڀ؀ۀڀ؀݀ހ݀׀؀ր܀ڀ׀ۀրր؀րހրҀ؀׀݀܀Հ܀ڀ׀݀ր߀ڀ׀׀ــۀۀۀրـۀۀڀހ؀ڀڀـՀ׀݀ڀЀـހـڀ܀ـۀԀۀۀހ݀Հ׀߀ހ׀݀݀؀ڀրـ܀׀׀ހ؀܀Հ܀ۀۀ׀؀݀ڀۀӀۀրـӀրՀՀۀ߀܀߀ـ܀ڀ؀ۀ݀׀ۀԀڀրՀ؀ـ׀܀؀ۀ׀ހ؀؀׀ـ׀ڀ׀ـҀۀ؀ۀۀ݀ـՀ݀ـ؀ހ܀߀ۀۀ߀ڀڀ׀ۀ܀ހــڀڀ׀ր؀ــ݀݀ڀ؀ڀրڀՀ܀׀Հ݀Ҁ׀݀ــԀՀ׀ـ׀ր׀ڀـՀ݀ۀ؀ـրր߀ڀՀހӀ׀ۀـ؀ـ݀׀ۀ܀׀ՀՀҀԀ؀ڀՀ؀րــڀ׀Հ݀ڀ؀ــ݀ڀ؀Ԁ׀րـӀڀۀӀ؀ڀـ܀Ҁ܀Հـ݀؀ۀԀڀـۀ؀րۀڀـڀՀۀހ؀߀ڀր׀׀ۀ܀߀ЀڀڀրԀӀـހــրـӀــҀــ߀ހ؀ۀրۀ׀؀݀؀ހ׀Հۀ܀ހ܀ـۀ׀܀ԀԀ߀րԀڀ׀ڀ݀ۀۀ׀݀׀ۀـހՀۀ؀Ԁր݀рڀր׀݀݀ۀԀހـ׀߀݀؀Հۀ׀ۀـڀրҀրրՀՀӀۀՀӀ؀ـۀ݀׀ր݀ۀڀ؀ՀڀրڀӀ׀׀ـ܀׀Ӏ؀ۀـ݀؀׀ـڀـրԀـҀ܀׀ހ׀Ԁۀ׀րـڀـހ؀݀րր׀ڀ؀ۀڀۀ܀݀ڀ؀ր݀ހ׀ڀրـڀۀ׀ۀր݀؀ڀ׀ۀ׀݀؀׀݀׀րـ܀ր܀ۀ؀܀ԀӀـ݀ހԀـ݀݀ڀހրڀ׀ڀ׀ހՀӀЀԀԀ؀րրՀȀҀՀـр؀׀ՀԀ؀рڀӀՀ׀܀׀ۀ؀؀ԀۀրԀ؀ހـ݀ـۀۀڀ؀ــހ׀Ҁ׀ހӀ؀ڀـۀрՀҀـՀڀـրـڀրـ܀؀ր߀Հـր؀߀ـۀՀـ؀܀Ԁ߀؀ހӀۀր׀݀݀ڀـ؀ـ܀ڀۀ܀׀ۀۀӀ׀Ҁڀ߀܀ـ׀ۀڀـ܀ـڀ׀ހــۀڀڀ܀ـހӀހ߀ــӀ؀׀Ӏۀހ݀րۀۀ߀ــــ܀׀ހր׀ۀۀ׀݀ހۀԀــ܀؀؀րր׀݀ـՀՀـ׀ր؀߀ڀ܀ڀ݀ڀۀـ݀׀րӀ׀݀ۀـ؀ڀ݀ހҀՀـ݀ۀۀހ܀ڀۀـۀۀ׀ڀـڀրՀۀ݀ӀڀԀՀ׀؀܀ـրՀހրހրހڀـڀ׀܀݀؀ڀހـ؀ـڀ߀ــ߀ۀ׀݀݀ـڀ؀ۀ݀ڀրЀۀ׀܀׀܀܀׀ՀӀ݀܀ـ؀؀Ӏ܀ڀ݀ـڀ׀ۀ؀ــԀ؀߀܀ՀԀԀՀ؀ހ݀׀؀րրӀ׀ـ׀߀؀؀Հ؀׀Ѐހހր܀׀܀؀ր׀ڀր׀߀ڀۀڀրڀՀ׀܀Ԁր׀Հــ׀ـ܀׀ڀЀۀـ܀؀׀؀ڀ܀ՀۀӀ؀܀ـۀڀ܀׀ـ׀ڀՀހՀՀۀ׀׀ր܀ۀӀڀހۀڀ؀ڀ؀ڀ݀׀݀ـۀ݀ڀـրـ׀׀؀Ԁ݀܀׀ـԀڀڀۀۀـ؀݀Ӏۀ܀ԀۀӀ؀Ԁڀـ؀ۀՀ؀؀׀րր׀ڀ߀ր؀݀׀ӀՀ܀؀ــҀڀՀրӀڀـــۀڀڀ܀Ҁـڀڀ׀݀ڀԀ׀ۀՀـڀ׀܀ـڀ؀ـ؀ــۀ܀ڀՀـԀڀ׀ۀ݀݀ՀՀ܀ـ׀ــ݀݀Ԁ߀׀ހ׀Ӏۀـ؀׀݀ڀՀ܀ـ݀ۀۀ݀ހހ؀ـۀڀۀր߀ՀڀڀՀـԀрՀӀÀր׀׀؀րр׀ҀՀӀҀـրԀ؀ӀــހրـՀ߀݀܀ۀ؀܀߀߀ހ݀ڀڀ܀Հڀ؀܀Հ؀؀ր؀܀ۀրڀՀހր׀ր݀րـՀԀԀ؀ۀڀԀӀڀր؀րр܀ۀՀـ߀؀ڀ؀ۀـЀԀ؀ۀրڀۀԀրրրЀڀրـրـՀڀ׀Ԁ؀܀ր܀ڀԀ؀ـހڀր܀׀ۀ؀݀ۀۀڀڀۀڀրрڀڀԀ܀؀Ӏـۀ܀ـ܀ـր߀ՀـՀۀڀـ݀ۀހڀـڀ׀ۀڀۀրۀ؀׀րـ܀܀րրڀڀ؀߀܀Հހ؀ـހـ߀׀؀ۀԀ܀ـ߀؀Հـ݀ۀրڀր׀׀ۀ؀׀Ԁڀ׀׀؀ـրހڀ׀؀Հ׀ހـ׀ــҀڀ܀ր܀Հ׀ۀڀۀڀ׀ڀԀۀԀހ؀ـ׀؀ۀԀۀӀۀӀՀՀۀ؀܀݀ـ؀ۀ߀ހڀ߀րڀ׀ـ؀ހـڀـ׀܀ـԀۀ׀Ԁڀـ݀րڀ؀Ӏڀۀـ߀܀ڀ׀ڀـڀ׀Ԁۀ׀׀Ԁ؀ۀրڀۀր܀ހހԀր܀܀ր݀׀߀׀րـۀـ׀׀ڀۀ׀ۀـ׀Ӏ߀ր؀Հ؀Հހڀۀ؀܀܀ۀ׀؀ڀـ؀ۀ׀ۀ׀݀߀Ҁ߀ۀـ܀׀܀Ԁ݀ۀۀ؀րـۀۀۀր؀؀Ӏ݀ހԀـڀۀր݀րـ߀ڀ݀Հހـր݀ڀրހҀ܀ۀ؀׀րրՀ߀ڀ܀ۀ݀ڀՀـڀ؀؀؀ՀրӀ؀܀ՀӀ؀րՀ߀؀ۀԀ؀؀܀րՀր׀ۀՀ׀ր݀ۀՀ؀߀؀؀ـ׀Հրـ׀؀ڀـ܀Հ׀ۀ׀؀݀ހրހހ܀܀Հ׀ۀڀۀՀӀۀ׀ހր؀׀ڀ׀Հـڀр܀ۀր؀ڀـ׀ـ؀րـ݀րՀڀۀ݀ՀՀـր݀րրрՀ׀׀׀ԀπҀԀʀԀۀԀӀ؀׀ҀԀ׀ՀҀ׀րԀۀۀ݀ڀ׀Ӏ܀ހ݀܀ڀ܀րԀـڀڀڀ݀ڀ݀׀݀؀ڀހր׀׀ـۀЀڀ݀׀؀ۀՀ߀ـԀۀـۀڀ؀؀ր؀ۀـրڀ݀Հހ܀րۀ݀ۀ׀ـ؀ۀـۀԀـڀ׀ڀ׀؀ՀڀۀԀ܀ր܀׀ڀ߀ــրۀ׀ڀ׀Հրڀۀրրր؀ـۀ׀܀؀ڀـހրՀۀ߀ۀ߀ۀڀ׀ۀ݀Ӏ݀ր؀Հހ݀ــ؀܀׀Ӏڀڀ׀Ҁ܀ՀڀՀـــր؀Ԁۀ؀܀܀ڀ׀ހ܀؀؀րۀ؀ڀ؀ۀ׀Ӏڀ؀ۀ؀؀ـۀـ܀ڀڀ݀ր݀׀Հڀ؀؀ۀڀ؀ۀԀր؀Հۀڀ݀ۀ׀׀׀׀؀؀؀׀ڀ܀܀ԀՀۀӀۀ؀Ӏ݀߀݀ۀ׀ڀ׀׀ՀԀڀـրـۀـۀــ܀ۀۀր߀؀ۀڀ؀ـ׀؀ـۀրՀ׀߀؀ހۀ׀ڀ؀ڀڀـ؀؀Ԁــ؀ـ׀ՀԀԀڀۀ܀ހ؀׀Ԁڀ܀ۀ݀ӀــԀ׀݀؀ӀӀ݀ڀ߀׀Հـ؀ހրՀ׀ҀـՀ׀݀Հـ؀ՀՀ܀Ԁۀրڀڀ׀׀Հ׀ـۀԀۀՀ܀ۀ؀݀݀݀܀݀ـۀ׀ۀ܀܀ــ܀ۀ؀Հ؀؀ـۀрՀ݀׀ۀրՀ׀׀܀؀؀߀րڀـڀۀրـ׀ۀ؀׀Հۀ܀ۀڀ׀Ԁ؀؀܀ۀԀۀހ؀܀ۀ؀ـ܀рڀۀۀـ׀ހր܀׀؀ڀ߀׀׀ԀڀӀ׀ր܀Ԁۀڀ؀Ԁ؀ۀ݀ҀӀՀԀ؀րۀր܀؀׀݀րڀـ׀؀؀׀ڀӀڀހ׀ـ܀؀ހπـހ؀܀Ԁ׀ڀڀــ؀ր܀؀؀րԀ݀܀ڀ׀ڀڀــ׀Ԁր܀Հրۀր׀Հ؀ۀ׀ڀڀ݀ހۀ؀ހրـրՀ׀ڀր׀؀ـԀÀӀ܀׀րҀـրՀـӀԀ܀Ԁր܀ڀрր׀ڀрۀ݀ـՀހـ܀Հր݀݀؀؀ԀۀۀـրۀրրހՀ؀ր܀ۀ݀ـހـ݀܀݀րڀրۀԀۀۀހ؀ր܀ր׀Հـ݀ـՀր׀߀݀׀ۀՀ؀ۀـހր߀ـ؀׀ۀ؀ڀހ؀׀ր؀ۀրۀހրրۀ܀ӀހԀրـ׀׀ӀԀ׀ڀր߀׀܀݀ڀրڀ݀ـ׀׀ڀ؀ԀԀۀ׀݀܀рـ؀Ԁڀ؀ۀހۀ׀܀׀ۀۀրڀ؀؀ԀۀۀԀր؀׀ۀڀـ؀ۀ܀Ҁ܀ۀ߀Հހ߀րــԀрۀր܀ՀՀԀـڀހ߀րҀڀــــހՀ߀ԀҀ݀ـրۀՀ܀Ӏۀրڀ܀׀ՀۀـӀހ܀ۀՀ܀ۀ؀ހـހ׀ՀހՀ݀؀ހ݀܀ـڀ׀؀րـՀ݀׀ڀ׀ـހ܀܀؀րր܀܀܀؀ڀ؀܀؀׀Հـ݀Ԁـڀۀڀڀ؀݀ր׀րڀՀڀրڀ׀׀ՀՀۀ׀܀ڀ؀؀׀ـ׀݀Ԁ׀ր݀׀րۀـڀۀ܀܀؀؀׀ڀ܀ـڀ߀ԀրހӀ݀ـӀـڀր݀ـ؀րــۀۀӀ؀ڀ؀ڀۀ׀ـ݀؀ۀ݀ـ؀ڀـڀۀ׀ހӀـ߀ۀ؀ۀՀ܀ހۀӀ܀܀؀܀ۀ׀Հۀـۀ؀ۀـހր߀׀܀Ҁۀ܀ހڀڀրրۀՀ؀݀Ҁڀ܀ۀۀրـ߀ۀ܀ـր܀׀߀ـ܀ۀր؀ڀҀ؀ڀۀـՀۀՀڀ߀ۀ׀ՀԀۀ׀ۀۀ؀Հրڀڀ݀Հ׀؀ڀ׀܀ۀրـڀՀ׀рԀۀ؀܀ـ݀ڀـڀ؀ՀՀ׀ڀ׀݀рހ؀׀ـ׀܀ՀڀۀՀՀۀրԀԀڀ݀ڀ׀ۀހ؀ڀڀۀۀӀ׀ԀڀՀր؀Ӏހڀ׀؀րۀՀՀ؀рӀڀҀ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/ccdproc/tests/data/expected_ifc_file_properties.csv0000644000076600000240000000071300000000000026334 0ustar00mattcraigstaff00000000000000file,simple,bitpix,naxis,naxis1,extend,bscale,bzero,imagetyp,filter,exposure filter_no_object_light.fit,True,16,1,100,True,1,32768,LIGHT,R,1.0 filter_object_light.fit,True,16,1,100,True,1,32768,LIGHT,R,1.0 filter_object_light.fit.gz,True,16,1,100,True,1,32768,LIGHT,R,1.0 no_filter_no_object_bias.fit,True,16,1,100,True,1,32768,BIAS,,0.0 no_filter_no_object_light.fit,True,16,1,100,True,1,32768,LIGHT,,1.0 test.fits.fz,True,16,1,100,True,1,32768,LIGHT,R,15.0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/ccdproc/tests/data/flat-mef.fits0000644000076600000240000004730000000000000022307 0ustar00mattcraigstaff00000000000000SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T END XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' FILTER = 'B ' IMAGETYP= 'FLAT ' DATE-OBS= '1928-07-23T21:03:27' END ????????????????????????????????????????????????????????????????????????????????????????????????????XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' FILTER = 'B ' IMAGETYP= 'FLAT ' DATE-OBS= '1928-07-23T21:03:27' END ????????????????????????????????????????????????????????????????????????????????????????????????????XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' FILTER = 'B ' IMAGETYP= 'FLAT ' DATE-OBS= '1928-07-23T21:03:27' END ????????????????????????????????????????????????????????????????????????????????????????????????????././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/ccdproc/tests/data/science-mef.fits0000644000076600000240000004730000000000000022772 0ustar00mattcraigstaff00000000000000SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T END XTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' OBJECT = 'clouds ' EXPTIME = 30.0 DATE-OBS= '1928-07-23T21:03:27' FILTER = 'B ' IMAGETYP= 'LIGHT ' END B`BŞ8B݌B_BƏB:BɸBƺB BÃBLBBBBTBBϘBȔBʤoBBǘ?Bư%BcB\BʢBBYB]BǢ@B1B4NBȬB\BBɺ9B}B?BZBɮB,B'BBBٝB~B4BǹB ]BɂBn8BɮBB21BTBMBǵBȵBBB!BaB 1BBȝBǕ@BNB2BBŶBl|BhBmBBhB B%BBt4BűBCB;BB{BʤhBN&BT=B#BɈBGBȊBȲB(B38BBԠBɢB BO{B|BܮBXTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' OBJECT = 'clouds ' EXPTIME = 30.0 DATE-OBS= '1928-07-23T21:03:27' FILTER = 'B ' IMAGETYP= 'LIGHT ' END BȕB"BBȒBBʺBpB`BrBpB>BĔB5BBǗBǢB\B\BB)BǀBüBƢ]B]BĘBɔB BfBcbB[uBzBMnBɡB}BG~BqBXBIBćBkBɘIB=BgBLBZBBfBBB̻FBBɗB MBBʷ1BcBBU?B:BȫBBƃ)B\B*BGBsBAB_|BBBBBBŅBGBǍB?UB>B._BؒBôLBNBȺ%BrBʋwBBśBB+B%Bk6BqB B4B5nB# BǭB@BȓBXTENSION= 'IMAGE ' / Image extension BITPIX = -32 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 10 NAXIS2 = 10 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups UNIT = 'electron' OBJECT = 'clouds ' EXPTIME = 30.0 DATE-OBS= '1928-07-23T21:03:27' FILTER = 'B ' IMAGETYP= 'LIGHT ' END B\bBBPbBjBBjBdB$BOBɓpBB BșBǯBƄBŁ4BB)B͇BtBVDBrB?BɹBB7EBNB](BbBq|\2~di|}x{hjsbn~$sju{|~}ydvc}jnzxe{uzolmlqhzZyywlFbm~mz}n{n[yrkv}|rpvzhlw\|{yjzmu~p`^Wyie6{kmd\Vuclzmjoiu}rxX9ZRqrlbpu+uii|]zo{v[rgqz{V{{d]fryvsH(5\vpwpowzSvqsZÌVV|_pkwe{Mqgtv_hߌerfSumqyndLomrl^az^aeX]yznbd}S~zx}Q{rv}xrmvefhm~szja~As`ZU~\|gcutvqs}oizivis}{sgO}dceqz|frtscxfrsxnk}hPsqLnwU`qn~i.bh]|{qlu{zrZk{[qpoaYpx|uh}vHax~dzyk!glvyduexqrtr~npxsr||yps]jntmu\z/|kj|cj}cj^chxjOvpzrsay}}t_|.w،t~w}qlpa%l~e}qyelhkolo*}mtoy{&iyzknpps]fczl}cea{܌vhR8t0bpklu#`lezgobdr`vnuqozwofy{u}nfvnkvgzqxɌci4mOx~nts ~xiYtwpnXvxvhgb%Iivy{z|||r[n{j}|wtmlx]ŒTv̌mjV~orw{splAz{~pkkwt^gg}ozuis_tz@/kpYz}ZxpqRi|qklnkbxbzxdmltitr`}xYpVjbsfOiorx_?4ypsq{tvdlr~omNjli]nj]cyjzhuΌhstwesyroxjrX~{r`{d]|zhloh{obknxw p}~y)}{soyol]vUzrntnbTmh}xx~~le|qvvq{DDrxqsiHh_nmtu}􌑌mhek\|Yzj\itS@lGtfrj}#tsjgk}mwpwkpIwvs}pz{fxmyl͌pwiijf^rswpx팎NXpZsus|oogt~x^Yrv0rohsLxLz\L~xtytrsfU~sqyubʌrzUˌ|r-uu_u^WZp}`r}utoŒpyu}ns|vzspg{ob_q}hmzrsu~|vt\zYlUaV}ly}T|RhsxinbVdyf_qNquZy}|_eigyvQx$xxf_tIuq^l~~k\Rx|r\kSvwyl.^snzuvTut}qyqvtGsxge:wLn|r[TWosdf-wr{mspOeyxq~vЌrx}cSnq}vv~ljvqf/oGpzbz}thnj{Owxomoym}jm|~Mz@vvgworur|uJ(g~tviwvxXt^zj9zsx^hqgsw_l@eflydss{pXvs`xyyH././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/ccdproc/tests/make_mef.py0000644000076600000240000000415400000000000021132 0ustar00mattcraigstaff00000000000000import numpy as np from astropy.utils.misc import NumpyRNGContext from astropy.io import fits from astropy.nddata import CCDData from ccdproc import flat_correct def make_sample_mef(science_name, flat_name, size=10, dtype='float32'): """ Make a multi-extension FITS image with random data and a MEF flat. Parameters ---------- science_name : str Name of the science image created by this function. flat_name : str Name of the flat image created by this function. size : int, optional Size of each dimension of the image; images created are square. dtype : str or numpy dtype, optional dtype of the generated images. """ with NumpyRNGContext(1234): number_of_image_extensions = 3 science_image = [fits.PrimaryHDU()] flat_image = [fits.PrimaryHDU()] for _ in range(number_of_image_extensions): # Simulate a cloudy night, average pixel # value of 100 with a read_noise of 1 electron. data = np.random.normal(100., 1.0, [size, size]).astype(dtype) hdu = fits.ImageHDU(data=data) # Make a header that is at least somewhat realistic hdu.header['unit'] = 'electron' hdu.header['object'] = 'clouds' hdu.header['exptime'] = 30.0 hdu.header['date-obs'] = '1928-07-23T21:03:27' hdu.header['filter'] = 'B' hdu.header['imagetyp'] = 'LIGHT' science_image.append(hdu) # Make a perfect flat flat = np.ones_like(data, dtype=dtype) flat_hdu = fits.ImageHDU(data=flat) flat_hdu.header['unit'] = 'electron' flat_hdu.header['filter'] = 'B' flat_hdu.header['imagetyp'] = 'FLAT' flat_hdu.header['date-obs'] = '1928-07-23T21:03:27' flat_image.append(flat_hdu) science_image = fits.HDUList(science_image) science_image.writeto(science_name) flat_image = fits.HDUList(flat_image) flat_image.writeto(flat_name) if __name__ == '__main__': make_sample_mef('data/science-mef.fits', 'data/flat-mef.fits') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1637332708.0 ccdproc-2.3.0/ccdproc/tests/pytest_fixtures.py0000644000076600000240000000451000000000000022643 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import gzip from tempfile import mkdtemp import os from shutil import rmtree import numpy as np import pytest from astropy import units as u from astropy.utils import NumpyRNGContext from astropy.io import fits from astropy.nddata import CCDData from ..utils.sample_directory import directory_for_testing # If additional pytest markers are defined the key in the dictionary below # should be the name of the marker. DEFAULTS = { 'seed': 123, 'data_size': 100, 'data_scale': 1.0, 'data_mean': 0.0 } DEFAULT_SEED = 123 DEFAULT_DATA_SIZE = 100 DEFAULT_DATA_SCALE = 1.0 DEFAULT_DATA_MEAN = 0.0 def value_from_markers(key, request): m = request.node.get_closest_marker(key) if m is not None: return m.args[0] else: return DEFAULTS[key] def ccd_data(data_size=DEFAULT_DATA_SIZE, data_scale=DEFAULT_DATA_SCALE, data_mean=DEFAULT_DATA_MEAN, rng_seed=DEFAULT_SEED): """ Return a CCDData object with units of ADU. The size of the data array is 100x100 but can be changed using the marker @pytest.mark.data_size(N) on the test function, where N should be the desired dimension. Data values are initialized to random numbers drawn from a normal distribution with mean of 0 and scale 1. The scale can be changed with the marker @pytest.marker.scale(s) on the test function, where s is the desired scale. The mean can be changed with the marker @pytest.marker.scale(m) on the test function, where m is the desired mean. """ size = data_size scale = data_scale mean = data_mean with NumpyRNGContext(rng_seed): data = np.random.normal(loc=mean, size=[size, size], scale=scale) fake_meta = {'my_key': 42, 'your_key': 'not 42'} ccd = CCDData(data, unit=u.adu) ccd.header = fake_meta return ccd @pytest.fixture def triage_setup(request): n_test, test_dir = directory_for_testing() def teardown(): try: rmtree(test_dir) except OSError: # If we cannot clean up just keep going. pass request.addfinalizer(teardown) class Result: def __init__(self, n, directory): self.n_test = n self.test_dir = directory return Result(n_test, test_dir) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1611757433.0 ccdproc-2.3.0/ccdproc/tests/run_for_memory_profile.py0000644000076600000240000001551600000000000024154 0ustar00mattcraigstaff00000000000000from argparse import ArgumentParser from tempfile import TemporaryDirectory from pathlib import Path import sys import gc import psutil from memory_profiler import memory_usage import numpy as np from astropy.io import fits from astropy.stats import median_absolute_deviation from astropy.nddata import CCDData # This bit of hackery ensures that we can see ccdproc from within # the test suite sys.path.append(str(Path().cwd())) from ccdproc import combine, ImageFileCollection try: from ccdproc.combiner import _calculate_size_of_image except ImportError: def _calculate_size_of_image(ccd, combine_uncertainty_function): # If uncertainty_func is given for combine this will create an uncertainty # even if the originals did not have one. In that case we need to create # an empty placeholder. if ccd.uncertainty is None and combine_uncertainty_function is not None: ccd.uncertainty = StdDevUncertainty(np.zeros(ccd.data.shape)) size_of_an_img = ccd.data.nbytes try: size_of_an_img += ccd.uncertainty.array.nbytes # In case uncertainty is None it has no "array" and in case the "array" is # not a numpy array: except AttributeError: pass # Mask is enforced to be a numpy.array across astropy versions if ccd.mask is not None: size_of_an_img += ccd.mask.nbytes # flags is not necessarily a numpy array so do not fail with an # AttributeError in case something was set! # TODO: Flags are not taken into account in Combiner. This number is added # nevertheless for future compatibility. try: size_of_an_img += ccd.flags.nbytes except AttributeError: pass return size_of_an_img # Do not combine these into one statement. When all references are lost # to a TemporaryDirectory the directory is automatically deleted. _TMPDIR # creates a reference that will stick around. _TMPDIR = TemporaryDirectory() TMPPATH = Path(_TMPDIR.name) def generate_fits_files(n_images, size=None, seed=1523): if size is None: use_size = (2024, 2031) else: use_size = (size, size) np.random.seed(seed) base_name = 'test-combine-{num:03d}.fits' for num in range(n_images): data = np.random.normal(size=use_size) # Now add some outlying pixels so there is something to clip n_bad = 50000 bad_x = np.random.randint(0, high=use_size[0] - 1, size=n_bad) bad_y = np.random.randint(0, high=use_size[1] - 1, size=n_bad) data[bad_x, bad_y] = (np.random.choice([-1, 1], size=n_bad) * (10 + np.random.rand(n_bad))) hdu = fits.PrimaryHDU(data=np.asarray(data, dtype='float32')) hdu.header['for_prof'] = 'yes' hdu.header['bunit'] = 'adu' path = TMPPATH.resolve() / base_name.format(num=num) hdu.writeto(path, overwrite=True) def run_memory_profile(n_files, sampling_interval, size=None, sigma_clip=False, combine_method=None, memory_limit=None): """ Try opening a bunch of files with a relatively low limit on the number of open files. Parameters ---------- n_files : int Number of files to combine. sampling_interval : float Time, in seconds, between memory samples. size : int, optional Size of one side of the image (the image is always square). sigma_clip : bool, optional If true, sigma clip the data before combining. combine_method : str, optional Should be one of the combine methods accepted by ccdproc.combine memory_limit : int, optional Cap on memory use during image combination. """ # Do a little input validation if n_files <= 0: raise ValueError("Argument 'n' must be a positive integer") proc = psutil.Process() print('Process ID is: ', proc.pid, flush=True) ic = ImageFileCollection(str(TMPPATH)) files = ic.files_filtered(for_prof='yes', include_path=True) kwargs = {'method': combine_method} if sigma_clip: kwargs.update( {'sigma_clip': True, 'sigma_clip_low_thresh': 5, 'sigma_clip_high_thresh': 5, 'sigma_clip_func': np.ma.median, 'sigma_clip_dev_func': median_absolute_deviation} ) ccd = CCDData.read(files[0]) expected_img_size = _calculate_size_of_image(ccd, None) if memory_limit: kwargs['mem_limit'] = memory_limit pre_mem_use = memory_usage(-1, interval=sampling_interval, timeout=1) baseline = np.mean(pre_mem_use) print('Subtracting baseline memory before profile: {}'.format(baseline)) mem_use = memory_usage((combine, (files,), kwargs), interval=sampling_interval, timeout=None) mem_use = [m - baseline for m in mem_use] return mem_use, expected_img_size if __name__ == '__main__': parser = ArgumentParser() parser.add_argument('number', type=int, help='Number of files to combine.') parser.add_argument('--size', type=int, action='store', help='Size of one side of image to create. ' 'All images are square, so only give ' 'a single number for the size.') parser.add_argument('--combine-method', '-c', choices=('average', 'median'), help='Method to use to combine images.') parser.add_argument('--memory-limit', type=int, help='Limit combination to this amount of memory') parser.add_argument('--sigma-clip', action='store_true', help='If set, sigma-clip before combining. Clipping ' 'will be done with high/low limit of 5. ' 'The central function is the median, the ' 'deviation is the median_absolute_deviation.') parser.add_argument('--sampling-freq', type=float, default=0.05, help='Time, in seconds, between memory samples.') parser.add_argument('--frequent-gc', action='store_true', help='If set, perform garbage collection ' 'much more frequently than the default.') args = parser.parse_args() if args.frequent_gc: gc.set_threshold(10, 10, 10) print("Garbage collection thresholds: ", gc.get_threshold()) mem_use = run_with_limit(args.number, args.sampling_freq, size=args.size, sigma_clip=args.sigma_clip, combine_method=args.combine_method, memory_limit=args.memory_limit) print('Max memory usage (MB): ', np.max(mem_use)) print('Baseline memory usage (MB): ', mem_use[0]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/ccdproc/tests/run_profile.ipynb0000644000076600000240000001712300000000000022403 0ustar00mattcraigstaff00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import gc\n", "from copy import deepcopy\n", "\n", "%matplotlib inline \n", "from matplotlib import pyplot as plt\n", "import numpy as np\n", "\n", "try:\n", " from run_for_memory_profile import run_memory_profile, generate_fits_files\n", "except ImportError:\n", " raise ImportError('Please install memory_profiler before running this notebook.')\n", "\n", "from ccdproc.version import get_git_devstr\n", "from astropy import __version__ as apy_version" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('Astropy version: ', apy_version)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image_size = 4000 # Square image, so 4000 x 4000\n", "num_files = 10\n", "sampling_interval = 0.01 # sec\n", "memory_limit = 1000000000 # bytes, roughly 1GB\n", "\n", "commit = get_git_devstr(sha=True)[:7]\n", "print(commit)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "generate_fits_files(num_files, size=image_size)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "runs = {\n", " 'average': {\n", " 'times': [],\n", " 'memory': [],\n", " 'image_size': 0.\n", " },\n", " 'median': {\n", " 'times': [],\n", " 'memory': [],\n", " 'image_size': 0.\n", " },\n", " 'sum': {\n", " 'times': [],\n", " 'memory': [],\n", " 'image_size': 0.\n", " }\n", "}\n", "runs_clip = deepcopy(runs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Seem to need to do one run before the profiling\n", "\n", "Every time the first run looks different than the rest, so we run one and throw it out." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_, _ = run_memory_profile(num_files, sampling_interval, size=image_size, \n", " memory_limit=memory_limit, combine_method='average')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Memory profile without sigma clipping" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_repetitions = 4" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def run_them(runs, clipping=False):\n", " for combine_method in runs.keys():\n", " for _ in range(n_repetitions):\n", " mem_use, img_size = run_memory_profile(num_files, sampling_interval, size=image_size, \n", " memory_limit=memory_limit, combine_method=combine_method,\n", " sigma_clip=clipping)\n", " gc.collect()\n", " runs[combine_method]['times'].append(np.arange(len(mem_use)) * sampling_interval)\n", " runs[combine_method]['memory'].append(mem_use)\n", " runs[combine_method]['image_size'] = img_size\n", " runs[combine_method]['memory_limit'] = memory_limit\n", " runs[combine_method]['clipping'] = clipping" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "run_them(runs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "styles = ['solid', 'dashed', 'dotted']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(20, 10))\n", "\n", "for idx, method in enumerate(runs.keys()):\n", " style = styles[idx % len(styles)]\n", " for i, data in enumerate(zip(runs[method]['times'], runs[method]['memory'])):\n", " time, mem_use = data \n", " if i == 0:\n", " label = 'Memory use in {} combine (repeated runs same style)'.format(method)\n", " alpha = 1.0\n", " else:\n", " label = ''\n", " alpha = 0.4\n", " plt.plot(time, mem_use, linestyle=style, label=label, alpha=alpha)\n", "\n", "plt.vlines(-40 * sampling_interval, mem_use[0], mem_use[0] + memory_limit/1e6, colors='red', label='Memory use limit')\n", "plt.vlines(-20 * sampling_interval, mem_use[0], mem_use[0] + runs[method]['image_size']/1e6, label='size of one image')\n", "\n", "plt.grid()\n", "clipped = 'ON' if runs[method]['clipping'] else 'OFF'\n", "\n", "plt.title('ccdproc commit {}; {} repetitions per method; sigma_clip {}'.format(commit, n_repetitions, clipped),\n", " fontsize=20)\n", "plt.xlabel('Time (sec)', fontsize=20)\n", "plt.ylabel('Memory use (MB)', fontsize=20)\n", "\n", "plt.legend(fontsize=20)\n", "plt.savefig('commit_{}_reps_{}_clip_{}_memlim_{}GB.png'.format(commit, n_repetitions, clipped, memory_limit/1e9))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Memory profile with sigma clipping" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "run_them(runs_clip, clipping=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plt.figure(figsize=(20, 10))\n", "\n", "for idx, method in enumerate(runs_clip.keys()):\n", " style = styles[idx % len(styles)]\n", " for i, data in enumerate(zip(runs_clip[method]['times'], runs_clip[method]['memory'])):\n", " time, mem_use = data \n", " if i == 0:\n", " label = 'Memory use in {} combine (repeated runs same style)'.format(method)\n", " alpha = 1.0\n", " else:\n", " label = ''\n", " alpha = 0.4\n", " plt.plot(time, mem_use, linestyle=style, label=label, alpha=alpha)\n", "\n", "plt.vlines(-40 * sampling_interval, mem_use[0], mem_use[0] + memory_limit/1e6, colors='red', label='Memory use limit')\n", "plt.vlines(-20 * sampling_interval, mem_use[0], mem_use[0] + runs_clip[method]['image_size']/1e6, label='size of one image')\n", "\n", "plt.grid()\n", "clipped = 'ON' if runs_clip[method]['clipping'] else 'OFF'\n", "\n", "plt.title('ccdproc commit {}; {} repetitions per method; sigma_clip {}'.format(commit, n_repetitions, clipped),\n", " fontsize=20)\n", "plt.xlabel('Time (sec)', fontsize=20)\n", "plt.ylabel('Memory use (MB)', fontsize=20)\n", "\n", "plt.legend(fontsize=20)\n", "plt.savefig('commit_{}_reps_{}_clip_{}_memlim_{}GB.png'.format(commit, n_repetitions, clipped, memory_limit/1e9))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.7" } }, "nbformat": 4, "nbformat_minor": 2 } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/run_with_file_number_limit.py0000644000076600000240000002214200000000000024767 0ustar00mattcraigstaff00000000000000from argparse import ArgumentParser from tempfile import TemporaryDirectory from pathlib import Path import mmap import sys import gc import psutil import numpy as np from astropy.io import fits # This bit of hackery ensures that we can see ccdproc from within # the test suite sys.path.append(str(Path().cwd())) from ccdproc import combine # Do not combine these into one statement. When all references are lost # to a TemporaryDirectory the directory is automatically deleted. _TMPDIR # creates a reference that will stick around. _TMPDIR = TemporaryDirectory() TMPPATH = Path(_TMPDIR.name) ALLOWED_EXTENSIONS = { 'fits': 'fits', 'plain': 'txt' } def generate_fits_files(number, size=None): if size is None: use_size = [250, 250] else: int_size = int(size) use_size = [int_size, int_size] base_name = 'test-combine-{num:03d}.' + ALLOWED_EXTENSIONS['fits'] for num in range(number): data = np.zeros(shape=use_size) hdu = fits.PrimaryHDU(data=data) hdu.header['bunit'] = 'adu' name = base_name.format(num=num) path = TMPPATH / name hdu.writeto(path, overwrite=True) def generate_plain_files(number): for i in range(number): file = TMPPATH / ("{i:03d}.".format(i=i) + ALLOWED_EXTENSIONS['plain']) file.write_bytes(np.random.random(100)) def open_files_with_open(kind): """ Open files with plain open. """ # Ensure the file references persist until end of script. Not really # necessary, but convenient while debugging the script. global fds fds = [] paths = TMPPATH.glob('**/*.' + ALLOWED_EXTENSIONS[kind]) for p in paths: fds.append(p.open()) def open_files_as_mmap(kind): """ Open files as mmaps. """ # Ensure the file references persist until end of script. Not really # necessary, but convenient while debugging the script. global fds fds = [] paths = TMPPATH.glob('**/*.' + ALLOWED_EXTENSIONS[kind]) for p in paths: with p.open() as f: fds.append(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_COPY)) def open_files_ccdproc_combine_chunk(kind): """ Open files indirectly as part of ccdproc.combine, ensuring that the task is broken into chunks. """ global combo paths = sorted(list(TMPPATH.glob('**/*.' + ALLOWED_EXTENSIONS[kind]))) # We want to force combine to break the task into chunks even # if the task really would fit in memory; it is in that case that # we end up with too many open files. We'll open one file, determine # the size of the data in bytes, and set the memory limit to that. # That will mean lots of chunks (however many files there are plus one), # but lots of chunks is fine. with fits.open(paths[0]) as hdulist: array_size = hdulist[0].data.nbytes combo = combine(paths, mem_limit=array_size) def open_files_ccdproc_combine_nochunk(kind): """ Open files indirectly as part of ccdproc.combine, ensuring that the task is not broken into chunks. """ global combo paths = sorted(list(TMPPATH.glob('**/*.' + ALLOWED_EXTENSIONS[kind]))) # We ensure there are no chunks by setting a memory limit large # enough to hold everything. with fits.open(paths[0]) as hdulist: array_size = hdulist[0].data.nbytes # Why 2x the number of files? To make absolutely sure we don't # end up chunking the job. array_size *= 2 * len(paths) combo = combine(paths) ALLOWED_OPENERS = { 'open': open_files_with_open, 'mmap': open_files_as_mmap, 'combine-chunk': open_files_ccdproc_combine_chunk, 'combine-nochunk': open_files_ccdproc_combine_nochunk } def run_with_limit(n, kind='fits', size=None, overhead=6, open_method='mmap'): """ Try opening a bunch of files with a relatively low limit on the number of open files. Parameters ---------- n : int Limit on number of open files in this function. The number of files to create is calculated from this to be just below the maximum number of files controlled by this function that can be opened. kind : one of 'fits', 'plain', optional The type of file to generate. The plain files are intended mainly for testing this script, while the FITS files are for testing ccdproc.combine. size : int, optional Size of file to create. If the kind is 'plain; this is the size of the file, in bytes. If the kind is 'fits', this is the size of one side of the image (the image is always square). overhead : int, optional Number of open files to assume the OS is using for this process. The default value is chosen so that this succeeds on MacOS or Linux. Setting it to a value lower than default should cause a SystemExit exception to be raised because of too many open files. This is meant for testing that this script is actually testing something. Notes ----- .. warning:: You should run this in a subprocess. Running as part of a larger python process will lower the limit on the number of open files for that **entire python process** which will almost certainly lead to nasty side effects. """ # Keep the resource import here so that it is skipped on windows import resource # Do a little input validation if n <= 0: raise ValueError("Argument 'n' must be a positive integer") if kind not in ALLOWED_EXTENSIONS.keys(): raise ValueError("Argument 'kind' must be one of " "{}".format(ALLOWED_EXTENSIONS.keys())) # Set the limit on the number of open files to n. The try/except # is the catch the case where this change would *increase*, rather than # decrease, the limit. That apparently can only be done by a superuser. try: resource.setrlimit(resource.RLIMIT_NOFILE, (n, n)) except ValueError as e: if 'not allowed to raise maximum limit' not in str(e): raise max_n_this_process = resource.getrlimit(resource.RLIMIT_NOFILE) raise ValueError('Maximum number of open ' 'files is {}'.format(max_n_this_process)) # The "-1" is to leave a little wiggle room. overhead is based on the # the number of open files that a process running on linux has open. # These typically include stdin and stout, and apparently others. n_files = n - 1 - overhead proc = psutil.Process() print('Process ID is: ', proc.pid, flush=True) print("Making {} files".format(n_files)) if kind == 'plain': generate_plain_files(n_files) elif kind == 'fits': generate_fits_files(n_files, size=size) # Print number of open files before we try opening anything for debugging # purposes. print("Before opening, files open is {}".format(len(proc.open_files())), flush=True) print(" Note well: this number is different than what lsof reports.") try: ALLOWED_OPENERS[open_method](kind) # fds.append(p.open()) except OSError as e: # Capture the error and re-raise as a SystemExit because this is # run in a subprocess. This ensures that the original error message # is reported back to the calling process; we add on the number of # open files. raise SystemExit(str(e) + '; number of open files: ' + '{}, with target {}'.format(len(proc.open_files()), n_files)) else: print('Opens succeeded, files currently open:', len(proc.open_files()), flush=True) if __name__ == '__main__': parser = ArgumentParser() parser.add_argument('number', type=int, help='Limit on number of open files.') parser.add_argument('--kind', action='store', default='plain', choices=ALLOWED_EXTENSIONS.keys(), help='Kind of file to generate for test; ' 'default is plain') parser.add_argument('--overhead', type=int, action='store', help='Number of files to assume the OS is using.', default=6) parser.add_argument('--open-by', action='store', default='mmap', choices=ALLOWED_OPENERS.keys(), help='How to open the files. Default is mmap') parser.add_argument('--size', type=int, action='store', help='Size of one side of image to create. ' 'All images are square, so only give ' 'a single number for the size.') parser.add_argument('--frequent-gc', action='store_true', help='If set, perform garbage collection ' 'much more frequently than the default.') args = parser.parse_args() if args.frequent_gc: gc.set_threshold(10, 10, 10) print("Garbage collection thresholds: ", gc.get_threshold()) run_with_limit(args.number, kind=args.kind, overhead=args.overhead, open_method=args.open_by, size=args.size) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/test_bitfield.py0000644000076600000240000000442700000000000022212 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np import pytest from astropy.tests.helper import catch_warnings from ccdproc.core import bitfield_to_boolean_mask def test_bitfield_not_integer(): with pytest.raises(TypeError): bitfield_to_boolean_mask(np.random.random((10, 10))) def test_bitfield_negative_flags(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, [-1]) def test_bitfield_non_poweroftwo_flags(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, [3]) def test_bitfield_flipbits_when_no_bits(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(TypeError): bitfield_to_boolean_mask(bm, None, flip_bits=1) def test_bitfield_flipbits_when_stringbits(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(TypeError): bitfield_to_boolean_mask(bm, '3', flip_bits=1) def test_bitfield_string_flag_flip_not_start_of_string(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, '1, ~4') def test_bitfield_string_flag_unbalanced_parens(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, '(1, 4))') def test_bitfield_string_flag_wrong_positioned_parens(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, '((1, )4)') def test_bitfield_string_flag_empty(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(ValueError): bitfield_to_boolean_mask(bm, '~') def test_bitfield_flag_non_integer(): bm = np.random.randint(0, 10, (10, 10)) with pytest.raises(TypeError): bitfield_to_boolean_mask(bm, [1.3]) def test_bitfield_duplicate_flag_throws_warning(): bm = np.random.randint(0, 10, (10, 10)) with catch_warnings(UserWarning) as w: bitfield_to_boolean_mask(bm, [1, 1]) assert len(w) def test_bitfield_none_identical_to_strNone(): bm = np.random.randint(0, 10, (10, 10)) m1 = bitfield_to_boolean_mask(bm, None) m2 = bitfield_to_boolean_mask(bm, 'None') np.testing.assert_array_equal(m1, m2) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/ccdproc/tests/test_ccdmask.py0000644000076600000240000002521500000000000022033 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from numpy.testing import assert_array_equal import numpy as np import pytest from ccdproc.core import ccdmask from astropy.nddata import CCDData def test_ccdmask_no_ccddata(): # Fails when a simple list is given. with pytest.raises(ValueError): ccdmask([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) def test_ccdmask_not_2d(): # Fails when a CCDData has less than 2 dimensions with pytest.raises(ValueError): ccdmask(CCDData(np.ones(3), unit='adu')) # Fails when scalar with pytest.raises(ValueError): ccdmask(CCDData(np.array(10), unit='adu')) # Fails when more than 2d with pytest.raises(ValueError): ccdmask(CCDData(np.ones((3, 3, 3)), unit='adu')) def test_ccdmask_pixels(): flat1 = CCDData(np.array([[ 20044, 19829, 19936, 20162, 19948, 19965, 19919, 20004, 19951, 20002, 19926, 20151, 19886, 20014, 19928, 20025, 19921, 19996, 19912, 20017, 19969, 20103, 20161, 20110, 19977, 19922, 20004, 19802, 20079, 19981, 20083, 19871], [20068, 20204, 20085, 20027, 20103, 19866, 20089, 19914, 20160, 19884, 19956, 20095, 20004, 20075, 19899, 20016, 19995, 20178, 19963, 20030, 20055, 20005, 20073, 19969, 19958, 20040, 19979, 19938, 19986, 19957, 20172, 20054], [20099, 20180, 19912, 20050, 19930, 19930, 20036, 20006, 19833, 19984, 19879, 19815, 20105, 20011, 19949, 20062, 19837, 20070, 20047, 19855, 19956, 19928, 19878, 20102, 19940, 20001, 20082, 20080, 20019, 19991, 19919, 20121], [20014, 20262, 19953, 20077, 19928, 20271, 19962, 20048, 20011, 20054, 20112, 19931, 20125, 19899, 19993, 19939, 19916, 19998, 19921, 19949, 20246, 20160, 19881, 19863, 19874, 19979, 19989, 19901, 19850, 19931, 20001, 20167], [20131, 19991, 20073, 19945, 19980, 20021, 19938, 19964, 20002, 20177, 19888, 19901, 19919, 19977, 20280, 20035, 20045, 19849, 20169, 20074, 20113, 19993, 19965, 20026, 20018, 19966, 20023, 19965, 19962, 20082, 20027, 20145], [20106, 20025, 19846, 19865, 19913, 20046, 19998, 20037, 19986, 20048, 20005, 19790, 20011, 19985, 19959, 19882, 20085, 19978, 19881, 19960, 20111, 19936, 19983, 19863, 19819, 19896, 19968, 20134, 19824, 19990, 20146, 19886], [20162, 19997, 19966, 20110, 19822, 19923, 20029, 20129, 19936, 19882, 20077, 20112, 20040, 20051, 20177, 19763, 20097, 19898, 19832, 20061, 19919, 20056, 20010, 19929, 20010, 19995, 20124, 19965, 19922, 19860, 20021, 19989], [20088, 20104, 19956, 19959, 20018, 19948, 19836, 20107, 19920, 20117, 19882, 20039, 20206, 20067, 19784, 20087, 20117, 19990, 20242, 19861, 19923, 19779, 20024, 20024, 19981, 19915, 20017, 20053, 19932, 20179, 20062, 19908], [19993, 20047, 20008, 20172, 19977, 20054, 19980, 19952, 20138, 19940, 19995, 20029, 19888, 20191, 19958, 20007, 19938, 19959, 19933, 20139, 20069, 19905, 20101, 20086, 19904, 19807, 20131, 20048, 19927, 19905, 19939, 20030], [20040, 20051, 19997, 20013, 19942, 20130, 19983, 19603, 19934, 19944, 19961, 19979, 20164, 19855, 20157, 20010, 20020, 19902, 20134, 19971, 20228, 19967, 19879, 20022, 19915, 20063, 19768, 19976, 19860, 20041, 19955, 19984], [19807, 20066, 19986, 19999, 19975, 20115, 19998, 20056, 20059, 20016, 19970, 19964, 20053, 19975, 19985, 19973, 20041, 19918, 19875, 19997, 19954, 19777, 20117, 20248, 20034, 20019, 20018, 20058, 20027, 20121, 19909, 20094], [19890, 20018, 20032, 20058, 19909, 19906, 19812, 20206, 19908, 19767, 20127, 20015, 19959, 20026, 20021, 19964, 19824, 19934, 20147, 19984, 20026, 20168, 19992, 20175, 20040, 20208, 20077, 19897, 20037, 19996, 19998, 20019], [19966, 19897, 20062, 19914, 19780, 20004, 20029, 20140, 20057, 20134, 20125, 19973, 19894, 19929, 19876, 20135, 19981, 20057, 20015, 20113, 20107, 20115, 19924, 19987, 19926, 19885, 20013, 20058, 19950, 20155, 19825, 20092], [19889, 20046, 20113, 19991, 19829, 20180, 19949, 20011, 20014, 20123, 19980, 19770, 20086, 20041, 19957, 19949, 20026, 19918, 19777, 20062, 19862, 20085, 20090, 20122, 19692, 19937, 19897, 20018, 19935, 20037, 19946, 19998], [20001, 19940, 19994, 19835, 19959, 19895, 20017, 20002, 20007, 19851, 19900, 20044, 20354, 19814, 19869, 20148, 20001, 20143, 19778, 20146, 19975, 19859, 20008, 20041, 19937, 20072, 20203, 19778, 20027, 20075, 19877, 19999], [19753, 19866, 20037, 20149, 20020, 20071, 19955, 20164, 19837, 19967, 19959, 20163, 20003, 20127, 20065, 20118, 20104, 19839, 20124, 20057, 19943, 20023, 20138, 19996, 19910, 20048, 20070, 19833, 19913, 20012, 19897, 19983]]), unit='adu') flat2 = CCDData(np.array([[ 20129, 20027, 19945, 20085, 19951, 20015, 20102, 19957, 20100, 19865, 19878, 20111, 20047, 19882, 19929, 20079, 19937, 19999, 20109, 19929, 19985, 19970, 19941, 19868, 20191, 20142, 19948, 20079, 19975, 19949, 19972, 20053], [20075, 19980, 20035, 20014, 19865, 20058, 20091, 20030, 19931, 19806, 19990, 19902, 19895, 19789, 20079, 20048, 20040, 19968, 20049, 19946, 19982, 19865, 19766, 19903, 20025, 19916, 19904, 20128, 19865, 20103, 19864, 19832], [20008, 19989, 20032, 19891, 20063, 20061, 20179, 19920, 19960, 19655, 19897, 19943, 20015, 20123, 20009, 19940, 19876, 19964, 20097, 19814, 20086, 20096, 20030, 20140, 19903, 19858, 19978, 19817, 20107, 19893, 19988, 19956], [20105, 19873, 20003, 19671, 19993, 19981, 20234, 19976, 20079, 19882, 19982, 19959, 19882, 20103, 20008, 19960, 20084, 20025, 19864, 19969, 19945, 19979, 19937, 19965, 19981, 19957, 19906, 19959, 19839, 19679, 19988, 20154], [20053, 20152, 19858, 20134, 19867, 20027, 20024, 19884, 20015, 19904, 19992, 20137, 19981, 20147, 19814, 20035, 19992, 19921, 20007, 20103, 19920, 19889, 20182, 19964, 19859, 20016, 20011, 20203, 19761, 19954, 20151, 19973], [20029, 19863, 20217, 19819, 19984, 19950, 19914, 20028, 19980, 20033, 20016, 19796, 19901, 20027, 20078, 20136, 19995, 19915, 20014, 19920, 19996, 20216, 19939, 19967, 19949, 20023, 20024, 19949, 19949, 19902, 19980, 19895], [19962, 19872, 19926, 20047, 20136, 19944, 20151, 19956, 19958, 20054, 19942, 20010, 19972, 19936, 20062, 20259, 20230, 19927, 20004, 19963, 20095, 19866, 19942, 19958, 20149, 19956, 20000, 19979, 19949, 19892, 20249, 20050], [20019, 19999, 19954, 20095, 20045, 20002, 19761, 20187, 20113, 20048, 20117, 20002, 19938, 19968, 19993, 19995, 20094, 19913, 19963, 19813, 20040, 19950, 19992, 19958, 20043, 19925, 20036, 19930, 20057, 20055, 20040, 19937], [19958, 19984, 19842, 19990, 19985, 19958, 20070, 19850, 20026, 20047, 20081, 20094, 20048, 20048, 19917, 19893, 19766, 19765, 20109, 20067, 19905, 19870, 19832, 20019, 19868, 20075, 20132, 19916, 19944, 19840, 20140, 20117], [19995, 20122, 19998, 20039, 20125, 19879, 19911, 20010, 19944, 19994, 19903, 20057, 20021, 20139, 19972, 20026, 19922, 20132, 19976, 20025, 19948, 20038, 19807, 19809, 20145, 20003, 20090, 19848, 19884, 19936, 19997, 19944], [19839, 19990, 20005, 19826, 20070, 19987, 20015, 19835, 20083, 19908, 19910, 20218, 19960, 19937, 19987, 19808, 19893, 19929, 20004, 20055, 19973, 19794, 20242, 20082, 20110, 20058, 19876, 20042, 20064, 19966, 20041, 20015], [20048, 20203, 19855, 20011, 19888, 19926, 19973, 19893, 19986, 20152, 20030, 19880, 20012, 19848, 19959, 20002, 20027, 19935, 19975, 19905, 19932, 20190, 20188, 19903, 20012, 19943, 19954, 19891, 19947, 19939, 19974, 19808], [20102, 20041, 20013, 20097, 20101, 19859, 20011, 20144, 19920, 19880, 20134, 19963, 19980, 20090, 20027, 19822, 20051, 19903, 19784, 19845, 20014, 19974, 20043, 20141, 19968, 20055, 20066, 20045, 20182, 20104, 20008, 19999], [19932, 20023, 20042, 19894, 20070, 20015, 20172, 20024, 19988, 20181, 20180, 20023, 19978, 19989, 19976, 19870, 20152, 20003, 19984, 19903, 19904, 19940, 19990, 19922, 19911, 19976, 19841, 19946, 20273, 20085, 20142, 20122], [19959, 20071, 20020, 20037, 20024, 19967, 20044, 20009, 19997, 20045, 19995, 19831, 20035, 19976, 20049, 19958, 20021, 19887, 19961, 19928, 19805, 20173, 19928, 19939, 19826, 20096, 20078, 20100, 19935, 19942, 19969, 19941], [19876, 20056, 20071, 19886, 19979, 20174, 19978, 20037, 19933, 20184, 19948, 20034, 19896, 19905, 20138, 19870, 19936, 20085, 19971, 20063, 19936, 19941, 19928, 19937, 19970, 19931, 20036, 19965, 19855, 19949, 19965, 19821]]), unit='adu') target_mask = np.zeros(flat1.shape, dtype=bool) # No bad pixels in this scenario ratio = flat1.divide(flat2) mask = ccdmask(ratio, ncsig=9, nlsig=11) assert mask.shape == ratio.shape assert_array_equal(mask, target_mask) # Check again with different ncsig and nlsig ratio = flat1.divide(flat2) mask = ccdmask(ratio, ncsig=11, nlsig=15) assert mask.shape == ratio.shape assert_array_equal(mask, target_mask) # Add single bad pixel flat1.data[14][3] = 65535 flat2.data[14][3] = 1 ratio = flat1.divide(flat2) mask = ccdmask(ratio, ncsig=11, nlsig=15) target_mask[14][3] = True assert_array_equal(mask, target_mask) # Add single bad column flat1.data[:, 7] = 65535 flat2.data[:, 7] = 1 ratio = flat1.divide(flat2) target_mask[:, 7] = True mask = ccdmask(ratio, ncsig=11, nlsig=15) assert_array_equal(mask, target_mask) mask = ccdmask(ratio, ncsig=11, nlsig=15, byblocks=True) assert_array_equal(mask, target_mask) mask = ccdmask(ratio, ncsig=11, nlsig=15, findbadcolumns=True) assert_array_equal(mask, target_mask) mask = ccdmask(ratio, ncsig=11, nlsig=15, findbadcolumns=True, byblocks=True) assert_array_equal(mask, target_mask) # Add bad column with gaps flat1.data[0:8, 2] = 65535 flat1.data[11:, 2] = 65535 flat2.data[0:8, 2] = 1 flat2.data[11:, 2] = 1 ratio = flat1.divide(flat2) mask = ccdmask(ratio, ncsig=11, nlsig=15, findbadcolumns=False) target_mask[0:8, 2] = True target_mask[11:, 2] = True assert_array_equal(mask, target_mask) mask = ccdmask(ratio, ncsig=11, nlsig=15, findbadcolumns=True) target_mask[:, 2] = True assert_array_equal(mask, target_mask) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/ccdproc/tests/test_ccdproc.py0000644000076600000240000012351000000000000022040 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np from astropy.io import fits from astropy.modeling import models from astropy.units.quantity import Quantity import astropy.units as u from astropy.wcs import WCS from astropy.tests.helper import catch_warnings from astropy.utils.exceptions import AstropyUserWarning from astropy.nddata import StdDevUncertainty, CCDData import astropy from numpy.testing import assert_array_equal import pytest import skimage from ccdproc.core import ( ccd_process, cosmicray_median, cosmicray_lacosmic, create_deviation, flat_correct, gain_correct, subtract_bias, subtract_dark, subtract_overscan, transform_image, trim_image, wcs_project, Keyword) from ccdproc.core import _blkavg from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func try: from ..core import block_reduce, block_average, block_replicate HAS_BLOCK_X_FUNCS = True except ImportError: HAS_BLOCK_X_FUNCS = False # Test creating deviation # Success expected if u_image * u_gain = u_readnoise @pytest.mark.parametrize('u_image,u_gain,u_readnoise,expect_success', [ (u.electron, None, u.electron, True), (u.electron, u.electron, u.electron, False), (u.adu, u.electron / u.adu, u.electron, True), (u.electron, None, u.dimensionless_unscaled, False), (u.electron, u.dimensionless_unscaled, u.electron, True), (u.adu, u.dimensionless_unscaled, u.electron, False), (u.adu, u.photon / u.adu, u.electron, False), ]) def test_create_deviation(u_image, u_gain, u_readnoise, expect_success): ccd_data = ccd_data_func(data_size=10, data_mean=100) ccd_data.unit = u_image if u_gain is not None: gain = 2.0 * u_gain else: gain = None readnoise = 5 * u_readnoise if expect_success: ccd_var = create_deviation(ccd_data, gain=gain, readnoise=readnoise) assert ccd_var.uncertainty.array.shape == (10, 10) assert ccd_var.uncertainty.array.size == 100 assert ccd_var.uncertainty.array.dtype == np.dtype(float) if gain is not None: expected_var = np.sqrt(2 * ccd_data.data + 5 ** 2) / 2 else: expected_var = np.sqrt(ccd_data.data + 5 ** 2) np.testing.assert_array_equal(ccd_var.uncertainty.array, expected_var) assert ccd_var.unit == ccd_data.unit # Uncertainty should *not* have any units -- does it? with pytest.raises(AttributeError): ccd_var.uncertainty.array.unit else: with pytest.raises(u.UnitsError): ccd_var = create_deviation(ccd_data, gain=gain, readnoise=readnoise) def test_create_deviation_from_negative(): ccd_data = ccd_data_func(data_mean=0, data_scale=10) ccd_data.unit = u.electron readnoise = 5 * u.electron ccd_var = create_deviation(ccd_data, gain=None, readnoise=readnoise, disregard_nan=False) np.testing.assert_array_equal(ccd_data.data < 0, np.isnan(ccd_var.uncertainty.array)) def test_create_deviation_from_negative(): ccd_data = ccd_data_func(data_mean=0, data_scale=10) ccd_data.unit = u.electron readnoise = 5 * u.electron ccd_var = create_deviation(ccd_data, gain=None, readnoise=readnoise, disregard_nan=True) mask = (ccd_data.data < 0) ccd_data.data[mask] = 0 expected_var = np.sqrt(ccd_data.data + readnoise.value**2) np.testing.assert_array_equal(ccd_var.uncertainty.array, expected_var) def test_create_deviation_keywords_must_have_unit(): ccd_data = ccd_data_func() # Gain must have units if provided with pytest.raises(TypeError): create_deviation(ccd_data, gain=3) # Readnoise must have units with pytest.raises(TypeError): create_deviation(ccd_data, readnoise=5) # Readnoise must be provided with pytest.raises(ValueError): create_deviation(ccd_data) # Tests for overscan @pytest.mark.parametrize('data_rectangle', [False, True]) @pytest.mark.parametrize('median,transpose', [ (False, False), (False, True), (True, False), ]) def test_subtract_overscan(median, transpose, data_rectangle): ccd_data = ccd_data_func() # Make data non-square if desired if data_rectangle: ccd_data.data = ccd_data.data[:, :-30] # Create the overscan region oscan = 300. oscan_region = (slice(None), slice(0, 10)) # Indices 0 through 9 fits_section = '[1:10, :]' science_region = (slice(None), slice(10, None)) overscan_axis = 1 if transpose: # Put overscan in first axis, not second, a test for #70 oscan_region = oscan_region[::-1] fits_section = '[:, 1:10]' science_region = science_region[::-1] overscan_axis = 0 ccd_data.data[oscan_region] = oscan # Add a fake sky background so the "science" part of the image has a # different average than the "overscan" part. sky = 10. original_mean = ccd_data.data[science_region].mean() ccd_data.data[science_region] += oscan + sky # Test once using the overscan argument to specify the overscan region ccd_data_overscan = subtract_overscan(ccd_data, overscan=ccd_data[oscan_region], overscan_axis=overscan_axis, median=median, model=None) # Is the mean of the "science" region the sum of sky and the mean the # "science" section had before backgrounds were added? np.testing.assert_almost_equal( ccd_data_overscan.data[science_region].mean(), sky + original_mean) # Is the overscan region zero? assert (ccd_data_overscan.data[oscan_region] == 0).all() # Now do what should be the same subtraction, with the overscan specified # with the fits_section ccd_data_fits_section = subtract_overscan(ccd_data, overscan_axis=overscan_axis, fits_section=fits_section, median=median, model=None) # Is the mean of the "science" region the sum of sky and the mean the # "science" section had before backgrounds were added? np.testing.assert_almost_equal( ccd_data_fits_section.data[science_region].mean(), sky + original_mean) # Is the overscan region zero? assert (ccd_data_fits_section.data[oscan_region] == 0).all() # Do both ways of subtracting overscan give exactly the same result? np.testing.assert_array_equal(ccd_data_overscan[science_region], ccd_data_fits_section[science_region]) # Set overscan_axis to None, and let the routine figure out the axis. # This should lead to the same results as before. ccd_data_overscan_auto = subtract_overscan( ccd_data, overscan_axis=None, overscan=ccd_data[oscan_region], median=median, model=None) np.testing.assert_almost_equal( ccd_data_overscan_auto.data[science_region].mean(), sky + original_mean) # Use overscan_axis=None with a FITS section ccd_data_fits_section_overscan_auto = subtract_overscan( ccd_data, overscan_axis=None, fits_section=fits_section, median=median, model=None) np.testing.assert_almost_equal( ccd_data_fits_section_overscan_auto.data[science_region].mean(), sky + original_mean) # Overscan_axis should be 1 for a square overscan region # This test only works for a non-square data region, but the # default has the wrong axis. if data_rectangle: ccd_data.data = ccd_data.data.T oscan_region = (slice(None), slice(0, -30)) science_region = (slice(None), slice(-30, None)) ccd_data_square_overscan_auto = subtract_overscan( ccd_data, overscan_axis=None, overscan=ccd_data[oscan_region], median=median, model=None) ccd_data_square = subtract_overscan( ccd_data, overscan_axis=1, overscan=ccd_data[oscan_region], median=median, model=None) np.testing.assert_allclose(ccd_data_square_overscan_auto, ccd_data_square) # A more substantial test of overscan modeling @pytest.mark.parametrize('transpose', [ True, False]) def test_subtract_overscan_model(transpose): ccd_data = ccd_data_func() # Create the overscan region size = ccd_data.shape[0] oscan_region = (slice(None), slice(0, 10)) science_region = (slice(None), slice(10, None)) yscan, xscan = np.mgrid[0:size, 0:size] / 10.0 + 300.0 if transpose: oscan_region = oscan_region[::-1] science_region = science_region[::-1] scan = xscan overscan_axis = 0 else: overscan_axis = 1 scan = yscan original_mean = ccd_data.data[science_region].mean() ccd_data.data[oscan_region] = 0. # Only want overscan in that region ccd_data.data = ccd_data.data + scan ccd_data = subtract_overscan(ccd_data, overscan=ccd_data[oscan_region], overscan_axis=overscan_axis, median=False, model=models.Polynomial1D(2)) np.testing.assert_almost_equal(ccd_data.data[science_region].mean(), original_mean) # Set the overscan_axis explicitly to None, and let the routine # figure it out. ccd_data = subtract_overscan(ccd_data, overscan=ccd_data[oscan_region], overscan_axis=None, median=False, model=models.Polynomial1D(2)) np.testing.assert_almost_equal(ccd_data.data[science_region].mean(), original_mean) def test_subtract_overscan_fails(): ccd_data = ccd_data_func() # Do we get an error if the *image* is neither CCDData nor an array? with pytest.raises(TypeError): subtract_overscan(3, np.zeros((5, 5))) # Do we get an error if the *overscan* is not an image or an array? with pytest.raises(TypeError): subtract_overscan(np.zeros((10, 10)), 3, median=False, model=None) # Do we get an error if we specify both overscan and fits_section? with pytest.raises(TypeError): subtract_overscan(ccd_data, overscan=ccd_data[0:10], fits_section='[1:10]') # Do we raise an error if we specify neither overscan nor fits_section? with pytest.raises(TypeError): subtract_overscan(ccd_data) # Does a fits_section which is not a string raise an error? with pytest.raises(TypeError): subtract_overscan(ccd_data, fits_section=5) def test_trim_image_fits_section_requires_string(): ccd_data = ccd_data_func() with pytest.raises(TypeError): trim_image(ccd_data, fits_section=5) @pytest.mark.parametrize('mask_data, uncertainty', [ (False, False), (True, True)]) def test_trim_image_fits_section(mask_data, uncertainty): ccd_data = ccd_data_func(data_size=50) if mask_data: ccd_data.mask = np.zeros_like(ccd_data) if uncertainty: err = np.random.normal(size=ccd_data.shape) ccd_data.uncertainty = StdDevUncertainty(err) trimmed = trim_image(ccd_data, fits_section='[20:40,:]') # FITS reverse order, bounds are inclusive and starting index is 1-based assert trimmed.shape == (50, 21) np.testing.assert_array_equal(trimmed.data, ccd_data[:, 19:40]) if mask_data: assert trimmed.shape == trimmed.mask.shape if uncertainty: assert trimmed.shape == trimmed.uncertainty.array.shape def test_trim_image_no_section(): ccd_data = ccd_data_func(data_size=50) trimmed = trim_image(ccd_data[:, 19:40]) assert trimmed.shape == (50, 21) np.testing.assert_array_equal(trimmed.data, ccd_data[:, 19:40]) def test_trim_with_wcs_alters_wcs(): ccd_data = ccd_data_func() # WCS construction example pulled form astropy.wcs docs wcs = WCS(naxis=2) wcs.wcs.crpix = np.array(ccd_data.shape)/2 wcs.wcs.cdelt = np.array([-0.066667, 0.066667]) wcs.wcs.crval = [0, -90] wcs.wcs.ctype = ["RA---AIR", "DEC--AIR"] wcs.wcs.set_pv([(2, 1, 45.0)]) ccd_wcs = CCDData(ccd_data, wcs=wcs) # The trim below should subtract 10 from the 2nd element of crpix. # (Second element because the FITS convention for index ordering is # opposite that of python) trimmed = trim_image(ccd_wcs[10:, :]) assert trimmed.wcs.wcs.crpix[1] == wcs.wcs.crpix[1] - 10 def test_subtract_bias(): ccd_data = ccd_data_func() data_avg = ccd_data.data.mean() bias_level = 5.0 ccd_data.data = ccd_data.data + bias_level ccd_data.header['key'] = 'value' master_bias_array = np.zeros_like(ccd_data.data) + bias_level master_bias = CCDData(master_bias_array, unit=ccd_data.unit) no_bias = subtract_bias(ccd_data, master_bias, add_keyword=None) # Does the data we are left with have the correct average? np.testing.assert_almost_equal(no_bias.data.mean(), data_avg) # With logging turned off, metadata should not change assert no_bias.header == ccd_data.header del no_bias.header['key'] assert 'key' in ccd_data.header assert no_bias.header is not ccd_data.header def test_subtract_bias_fails(): ccd_data = ccd_data_func(data_size=50) # Should fail if shapes don't match bias = CCDData(np.array([200, 200]), unit=u.adu) with pytest.raises(ValueError): subtract_bias(ccd_data, bias) # Should fail because units don't match bias = CCDData(np.zeros_like(ccd_data), unit=u.meter) with pytest.raises(u.UnitsError): subtract_bias(ccd_data, bias) @pytest.mark.parametrize('exposure_keyword', [True, False]) @pytest.mark.parametrize('explicit_times', [True, False]) @pytest.mark.parametrize('scale', [True, False]) def test_subtract_dark(explicit_times, scale, exposure_keyword): ccd_data = ccd_data_func() exptime = 30.0 exptime_key = 'exposure' exposure_unit = u.second dark_level = 1.7 master_dark_data = np.zeros_like(ccd_data.data) + dark_level master_dark = CCDData(master_dark_data, unit=u.adu) master_dark.header[exptime_key] = 2 * exptime dark_exptime = master_dark.header[exptime_key] ccd_data.header[exptime_key] = exptime dark_exposure_unit = exposure_unit if explicit_times: # Test case when units of dark and data exposures are different dark_exposure_unit = u.minute dark_sub = subtract_dark(ccd_data, master_dark, dark_exposure=dark_exptime * dark_exposure_unit, data_exposure=exptime * exposure_unit, scale=scale, add_keyword=None) elif exposure_keyword: key = Keyword(exptime_key, unit=u.second) dark_sub = subtract_dark(ccd_data, master_dark, exposure_time=key, scale=scale, add_keyword=None) else: dark_sub = subtract_dark(ccd_data, master_dark, exposure_time=exptime_key, exposure_unit=u.second, scale=scale, add_keyword=None) dark_scale = 1.0 if scale: dark_scale = float((exptime / dark_exptime) * (exposure_unit / dark_exposure_unit)) np.testing.assert_array_equal(ccd_data.data - dark_scale * dark_level, dark_sub.data) # Headers should have the same content...do they? assert dark_sub.header == ccd_data.header # But the headers should not be the same object -- a copy was made assert dark_sub.header is not ccd_data.header def test_subtract_dark_fails(): ccd_data = ccd_data_func() # None of these tests check a result so the content of the master # can be anything. ccd_data.header['exptime'] = 30.0 master = ccd_data.copy() # Do we fail if we give one of dark_exposure, data_exposure but not both? with pytest.raises(TypeError): subtract_dark(ccd_data, master, dark_exposure=30 * u.second) with pytest.raises(TypeError): subtract_dark(ccd_data, master, data_exposure=30 * u.second) # Do we fail if we supply dark_exposure and data_exposure and exposure_time with pytest.raises(TypeError): subtract_dark(ccd_data, master, dark_exposure=10 * u.second, data_exposure=10 * u.second, exposure_time='exptime') # Fail if we supply none of the exposure-related arguments? with pytest.raises(TypeError): subtract_dark(ccd_data, master) # Fail if we supply exposure time but not a unit? with pytest.raises(TypeError): subtract_dark(ccd_data, master, exposure_time='exptime') # Fail if ccd_data or master are not CCDData objects? with pytest.raises(TypeError): subtract_dark(ccd_data.data, master, exposure_time='exptime') with pytest.raises(TypeError): subtract_dark(ccd_data, master.data, exposure_time='exptime') # Fail if units do not match... # ...when there is no scaling? master = CCDData(ccd_data) master.unit = u.meter with pytest.raises(u.UnitsError) as e: subtract_dark(ccd_data, master, exposure_time='exptime', exposure_unit=u.second) assert "uncalibrated image" in str(e.value) # Fail when the arrays are not the same size with pytest.raises(ValueError): small_master = CCDData(ccd_data) small_master.data = np.zeros((1, 1)) subtract_dark(ccd_data, small_master) def test_unit_mismatch_behaves_as_expected(): ccd_data = ccd_data_func() """ Test to alert us to any changes in how errors are raised in astropy when units do not match. """ bad_unit = ccd_data.copy() bad_unit.unit = u.meter if astropy.__version__.startswith('1.0'): expected_error = ValueError expected_message = 'operand units' else: expected_error = u.UnitConversionError # Make this an empty string, which always matches. In this case # we are really only checking by the type of error raised. expected_message = '' # Did we raise the right error? with pytest.raises(expected_error) as e: ccd_data.subtract(bad_unit) # Was the error message as expected? assert expected_message in str(e.value) # Test for flat correction def test_flat_correct(): ccd_data = ccd_data_func(data_scale=10) # Add metadata to header for a test below... ccd_data.header['my_key'] = 42 size = ccd_data.shape[0] # create the flat, with some scatter data = 2 * np.random.normal(loc=1.0, scale=0.05, size=(size, size)) flat = CCDData(data, meta=fits.header.Header(), unit=ccd_data.unit) flat_data = flat_correct(ccd_data, flat, add_keyword=None) # Check that the flat was normalized # Should be the case that flat * flat_data = ccd_data * flat.data.mean # if the normalization was done correctly. np.testing.assert_almost_equal((flat_data.data * flat.data).mean(), ccd_data.data.mean() * flat.data.mean()) np.testing.assert_allclose(ccd_data.data / flat_data.data, flat.data / flat.data.mean()) # Check that metadata is unchanged (since logging is turned off) assert flat_data.header == ccd_data.header # Test for flat correction with min_value def test_flat_correct_min_value(data_scale=1, data_mean=5): ccd_data = ccd_data_func() size = ccd_data.shape[0] # Create the flat data = 2 * np.random.normal(loc=1.0, scale=0.05, size=(size, size)) flat = CCDData(data, meta=fits.header.Header(), unit=ccd_data.unit) flat_orig_data = flat.data.copy() min_value = 2.1 # Should replace some, but not all, values flat_corrected_data = flat_correct(ccd_data, flat, min_value=min_value) flat_with_min = flat.copy() flat_with_min.data[flat_with_min.data < min_value] = min_value # Check that the flat was normalized. The asserts below, which look a # little odd, are correctly testing that # flat_corrected_data = ccd_data / (flat_with_min / mean(flat_with_min)) np.testing.assert_almost_equal( (flat_corrected_data.data * flat_with_min.data).mean(), (ccd_data.data * flat_with_min.data.mean()).mean() ) np.testing.assert_allclose(ccd_data.data / flat_corrected_data.data, flat_with_min.data / flat_with_min.data.mean()) # Test that flat is not modified. assert (flat_orig_data == flat.data).all() assert flat_orig_data is not flat.data def test_flat_correct_norm_value(): ccd_data = ccd_data_func(data_scale=10) # Test flat correction with mean value that is different than # the mean of the flat frame. # Create the flat, with some scatter # Note that mean value of flat is set below and is different than # the mean of the flat data. flat_mean = 5.0 data = np.random.normal(loc=1.0, scale=0.05, size=ccd_data.shape) flat = CCDData(data, meta=fits.Header(), unit=ccd_data.unit) flat_data = flat_correct(ccd_data, flat, add_keyword=None, norm_value=flat_mean) # Check that the flat was normalized # Should be the case that flat * flat_data = ccd_data * flat_mean # if the normalization was done correctly. np.testing.assert_almost_equal((flat_data.data * flat.data).mean(), ccd_data.data.mean() * flat_mean) np.testing.assert_allclose(ccd_data.data / flat_data.data, flat.data / flat_mean) def test_flat_correct_norm_value_bad_value(): ccd_data = ccd_data_func() # Test that flat_correct raises the appropriate error if # it is given a bad norm_value. Bad means <=0. # Create the flat, with some scatter data = np.random.normal(loc=1.0, scale=0.05, size=ccd_data.shape) flat = CCDData(data, meta=fits.Header(), unit=ccd_data.unit) with pytest.raises(ValueError) as e: flat_correct(ccd_data, flat, add_keyword=None, norm_value=-7) assert "norm_value must be" in str(e.value) # Test for deviation and for flat correction def test_flat_correct_deviation(): ccd_data = ccd_data_func(data_scale=10, data_mean=300) size = ccd_data.shape[0] ccd_data.unit = u.electron ccd_data = create_deviation(ccd_data, readnoise=5 * u.electron) # Create the flat data = 2 * np.ones((size, size)) flat = CCDData(data, meta=fits.header.Header(), unit=ccd_data.unit) flat = create_deviation(flat, readnoise=0.5 * u.electron) ccd_data = flat_correct(ccd_data, flat) # Test the uncertainty on the data after flat correction def test_flat_correct_data_uncertainty(): # Regression test for #345 dat = CCDData(np.ones([100, 100]), unit='adu', uncertainty=np.ones([100, 100])) # Note flat is set to 10, error, if present, is set to one. flat = CCDData(10 * np.ones([100, 100]), unit='adu') res = flat_correct(dat, flat) assert (res.data == dat.data).all() assert (res.uncertainty.array == dat.uncertainty.array).all() # Tests for gain correction def test_gain_correct(): ccd_data = ccd_data_func() init_data = ccd_data.data gain_data = gain_correct(ccd_data, gain=3, add_keyword=None) assert_array_equal(gain_data.data, 3 * init_data) assert ccd_data.meta == gain_data.meta def test_gain_correct_quantity(): ccd_data = ccd_data_func() init_data = ccd_data.data g = Quantity(3, u.electron / u.adu) ccd_data = gain_correct(ccd_data, gain=g) assert_array_equal(ccd_data.data, 3 * init_data) assert ccd_data.unit == u.electron # Test transform is ccd def test_transform_isccd(): with pytest.raises(TypeError): transform_image(1, 1) # Test function is callable def test_transform_isfunc(): ccd_data = ccd_data_func() with pytest.raises(TypeError): transform_image(ccd_data, 1) # Test warning is issue if WCS information is available def test_catch_transform_wcs_warning(): ccd_data = ccd_data_func() def tran(arr): return 10 * arr with catch_warnings() as w: tran = transform_image(ccd_data, tran) @pytest.mark.parametrize('mask_data, uncertainty', [ (False, False), (True, True)]) def test_transform_image(mask_data, uncertainty): ccd_data = ccd_data_func(data_size=50) if mask_data: ccd_data.mask = np.zeros_like(ccd_data) ccd_data.mask[10, 10] = 1 if uncertainty: err = np.random.normal(size=ccd_data.shape) ccd_data.uncertainty = StdDevUncertainty(err) def tran(arr): return 10 * arr tran = transform_image(ccd_data, tran) assert_array_equal(10 * ccd_data.data, tran.data) if mask_data: assert tran.shape == tran.mask.shape assert_array_equal(ccd_data.mask, tran.mask) if uncertainty: assert tran.shape == tran.uncertainty.array.shape assert_array_equal(10 * ccd_data.uncertainty.array, tran.uncertainty.array) # Test block_reduce and block_replicate wrapper @pytest.mark.skipif(not HAS_BLOCK_X_FUNCS, reason="needs astropy >= 1.1.x") @pytest.mark.skipif((skimage.__version__ < '0.14.2') and ('dev' in np.__version__), reason="Incompatibility between scikit-image " "and numpy 1.16") def test_block_reduce(): ccd = CCDData(np.ones((4, 4)), unit='adu', meta={'testkw': 1}, mask=np.zeros((4, 4), dtype=bool), uncertainty=StdDevUncertainty(np.ones((4, 4))) ) with catch_warnings(AstropyUserWarning) as w: ccd_summed = block_reduce(ccd, (2, 2)) assert len(w) == 1 assert 'following attributes were set' in str(w[0].message) assert isinstance(ccd_summed, CCDData) assert np.all(ccd_summed.data == 4) assert ccd_summed.data.shape == (2, 2) assert ccd_summed.unit == u.adu # Other attributes are set to None. In case the function is modified to # work on these attributes correctly those tests need to be updated! assert ccd_summed.meta == {'testkw': 1} assert ccd_summed.mask is None assert ccd_summed.uncertainty is None # Make sure meta is copied ccd_summed.meta['testkw2'] = 10 assert 'testkw2' not in ccd.meta @pytest.mark.skipif(not HAS_BLOCK_X_FUNCS, reason="needs astropy >= 1.1.x") @pytest.mark.skipif((skimage.__version__ < '0.14.2') and ('dev' in np.__version__), reason="Incompatibility between scikit-image " "and numpy 1.16") def test_block_average(): ccd = CCDData(np.ones((4, 4)), unit='adu', meta={'testkw': 1}, mask=np.zeros((4, 4), dtype=bool), uncertainty=StdDevUncertainty(np.ones((4, 4)))) ccd.data[::2, ::2] = 2 with catch_warnings(AstropyUserWarning) as w: ccd_avgd = block_average(ccd, (2, 2)) assert len(w) == 1 assert 'following attributes were set' in str(w[0].message) assert isinstance(ccd_avgd, CCDData) assert np.all(ccd_avgd.data == 1.25) assert ccd_avgd.data.shape == (2, 2) assert ccd_avgd.unit == u.adu # Other attributes are set to None. In case the function is modified to # work on these attributes correctly those tests need to be updated! assert ccd_avgd.meta == {'testkw': 1} assert ccd_avgd.mask is None assert ccd_avgd.wcs is None assert ccd_avgd.uncertainty is None # Make sure meta is copied ccd_avgd.meta['testkw2'] = 10 assert 'testkw2' not in ccd.meta @pytest.mark.skipif(not HAS_BLOCK_X_FUNCS, reason="needs astropy >= 1.1.x") def test_block_replicate(): ccd = CCDData(np.ones((4, 4)), unit='adu', meta={'testkw': 1}, mask=np.zeros((4, 4), dtype=bool), uncertainty=StdDevUncertainty(np.ones((4, 4)))) with catch_warnings(AstropyUserWarning) as w: ccd_repl = block_replicate(ccd, (2, 2)) assert len(w) == 1 assert 'following attributes were set' in str(w[0].message) assert isinstance(ccd_repl, CCDData) assert np.all(ccd_repl.data == 0.25) assert ccd_repl.data.shape == (8, 8) assert ccd_repl.unit == u.adu # Other attributes are set to None. In case the function is modified to # work on these attributes correctly those tests need to be updated! assert ccd_repl.meta == {'testkw': 1} assert ccd_repl.mask is None assert ccd_repl.wcs is None assert ccd_repl.uncertainty is None # Make sure meta is copied ccd_repl.meta['testkw2'] = 10 assert 'testkw2' not in ccd.meta # Test blockaveraging ndarray def test__blkavg_ndarray(): with pytest.raises(TypeError): _blkavg(1, (5, 5)) # Test rebinning dimensions def test__blkavg_dimensions(): ccd_data = ccd_data_func(data_size=10) with pytest.raises(ValueError): _blkavg(ccd_data.data, (5,)) # Test blkavg works def test__blkavg_larger(): ccd_data = ccd_data_func(data_size=20) a = ccd_data.data b = _blkavg(a, (10, 10)) assert b.shape == (10, 10) np.testing.assert_almost_equal(b.sum(), 0.25 * a.sum()) # Test overscan changes def test__overscan_schange(): ccd_data = ccd_data_func() old_data = ccd_data.copy() new_data = subtract_overscan(ccd_data, overscan=ccd_data[:, 1], overscan_axis=0) assert not np.allclose(old_data.data, new_data.data) np.testing.assert_array_equal(old_data.data, ccd_data.data) def test_create_deviation_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() ccd = create_deviation(ccd_data, gain=5 * u.electron / u.adu, readnoise=10 * u.electron) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_cosmicray_median_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() error = np.zeros_like(ccd_data) with np.errstate(invalid="ignore", divide="ignore"): _ = cosmicray_median(ccd_data, error_image=error, thresh=5, mbox=11, gbox=0, rbox=0) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_cosmicray_lacosmic_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() error = np.zeros_like(ccd_data) ccd = cosmicray_lacosmic(ccd_data) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_flat_correct_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() flat = CCDData(np.zeros_like(ccd_data), unit=ccd_data.unit) with np.errstate(invalid="ignore"): _ = flat_correct(ccd_data, flat=flat) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_gain_correct_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() ccd = gain_correct(ccd_data, gain=1, gain_unit=ccd_data.unit) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_subtract_bias_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() master_frame = CCDData(np.zeros_like(ccd_data), unit=ccd_data.unit) ccd = subtract_bias(ccd_data, master=master_frame) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_trim_image_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() ccd = trim_image(ccd_data, fits_section=None) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_transform_image_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() with np.errstate(invalid="ignore"): _ = transform_image(ccd_data, np.sqrt) np.testing.assert_array_equal(original.data, ccd_data) assert original.unit == ccd_data.unit def wcs_for_testing(shape): # Set up a simple WCS, details are cut/pasted from astropy WCS docs, # mostly. CRPIX is set to the center of shape, rounded down. # Create a new WCS object. The number of axes must be set # from the start w = WCS(naxis=2) # Set up an "Airy's zenithal" projection # Vector properties may be set with Python lists, or Numpy arrays w.wcs.crpix = [shape[0] // 2, shape[1] // 2] w.wcs.cdelt = np.array([-0.066667, 0.066667]) w.wcs.crval = [0, -90] w.wcs.ctype = ["RA---AIR", "DEC--AIR"] w.wcs.set_pv([(2, 1, 45.0)]) return w def test_wcs_project_onto_same_wcs(): ccd_data = ccd_data_func() # The trivial case, same WCS, no mask. target_wcs = wcs_for_testing(ccd_data.shape) ccd_data.wcs = wcs_for_testing(ccd_data.shape) new_ccd = wcs_project(ccd_data, target_wcs) # Make sure new image has correct WCS. assert new_ccd.wcs.wcs.compare(target_wcs.wcs) # Make sure data matches within some reasonable tolerance. np.testing.assert_allclose(ccd_data.data, new_ccd.data, rtol=1e-5) def test_wcs_project_onto_same_wcs_remove_headers(): ccd_data = ccd_data_func() # Remove an example WCS keyword from the header target_wcs = wcs_for_testing(ccd_data.shape) ccd_data.wcs = wcs_for_testing(ccd_data.shape) print(ccd_data.header) ccd_data.header = ccd_data.wcs.to_header() new_ccd = wcs_project(ccd_data, target_wcs) for k in ccd_data.wcs.to_header(): assert k not in new_ccd.header def test_wcs_project_onto_shifted_wcs(): ccd_data = ccd_data_func() # Just make the target WCS the same as the initial with the center # pixel shifted by 1 in x and y. ccd_data.wcs = wcs_for_testing(ccd_data.shape) target_wcs = wcs_for_testing(ccd_data.shape) target_wcs.wcs.crpix += [1, 1] ccd_data.mask = np.random.choice([0, 1], size=ccd_data.shape) new_ccd = wcs_project(ccd_data, target_wcs) # Make sure new image has correct WCS. assert new_ccd.wcs.wcs.compare(target_wcs.wcs) # Make sure data matches within some reasonable tolerance, keeping in mind # that the pixels should all be shifted. masked_input = np.ma.array(ccd_data.data, mask=ccd_data.mask) masked_output = np.ma.array(new_ccd.data, mask=new_ccd.mask) np.testing.assert_allclose(masked_input[:-1, :-1], masked_output[1:, 1:], rtol=1e-5) # The masks should all be shifted too. np.testing.assert_array_equal(ccd_data.mask[:-1, :-1], new_ccd.mask[1:, 1:]) # We should have more values that are masked in the output array # than on input because some on output were not in the footprint # of the original array. # In the case of a shift, one row and one column should be nan, and they # will share one common nan where they intersect, so we know how many nan # there should be. assert np.isnan(new_ccd.data).sum() == np.sum(new_ccd.shape) - 1 # Use an odd number of pixels to make a well-defined center pixel def test_wcs_project_onto_scale_wcs(): # Make the target WCS with half the pixel scale and number of pixels # and the values should drop by a factor of 4. ccd_data = ccd_data_func(data_size=31) ccd_data.wcs = wcs_for_testing(ccd_data.shape) # Make sure wcs is centered at the center of the center pixel. ccd_data.wcs.wcs.crpix += 0.5 # Use uniform input data value for simplicity. ccd_data.data = np.ones_like(ccd_data.data) # Make mask zero... ccd_data.mask = np.zeros_like(ccd_data.data) # ...except the center pixel, which is one. ccd_data.mask[int(ccd_data.wcs.wcs.crpix[0]), int(ccd_data.wcs.wcs.crpix[1])] = 1 target_wcs = wcs_for_testing(ccd_data.shape) target_wcs.wcs.cdelt /= 2 # Choice below ensures we are really at the center pixel of an odd range. target_shape = 2 * np.array(ccd_data.shape) + 1 target_wcs.wcs.crpix = 2 * target_wcs.wcs.crpix + 1 + 0.5 # Explicitly set the interpolation method so we know what to # expect for the mass. new_ccd = wcs_project(ccd_data, target_wcs, target_shape=target_shape, order='nearest-neighbor') # Make sure new image has correct WCS. assert new_ccd.wcs.wcs.compare(target_wcs.wcs) # Define a cutout from the new array that should match the old. new_lower_bound = (np.array(new_ccd.shape) - np.array(ccd_data.shape)) // 2 new_upper_bound = (np.array(new_ccd.shape) + np.array(ccd_data.shape)) // 2 data_cutout = new_ccd.data[new_lower_bound[0]:new_upper_bound[0], new_lower_bound[1]:new_upper_bound[1]] # Make sure data matches within some reasonable tolerance, keeping in mind # that the pixels have been scaled. np.testing.assert_allclose(ccd_data.data / 4, data_cutout, rtol=1e-5) # Mask should be true for four pixels (all nearest neighbors) # of the single pixel we masked initially. new_center = np.array(new_ccd.wcs.wcs.crpix, dtype=int, copy=False) assert np.all(new_ccd.mask[new_center[0]:new_center[0]+2, new_center[1]:new_center[1]+2]) # Those four, and any that reproject made nan because they draw on # pixels outside the footprint of the original image, are the only # pixels that should be masked. assert new_ccd.mask.sum() == 4 + np.isnan(new_ccd.data).sum() def test_ccd_process_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() ccd = ccd_process(ccd_data, gain=5 * u.electron / u.adu, readnoise=10 * u.electron) np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit def test_ccd_process_parameters_are_appropriate(): ccd_data = ccd_data_func() # oscan check with pytest.raises(TypeError): ccd_process(ccd_data, oscan=True) # Trim section check with pytest.raises(TypeError): ccd_process(ccd_data, trim=True) # Error frame check # gain and readnoise must be specified with pytest.raises(ValueError): ccd_process(ccd_data, error=True) # gain must be specified with pytest.raises(ValueError): ccd_process(ccd_data, error=True, gain=None, readnoise=5) # Mask check with pytest.raises(TypeError): ccd_process(ccd_data, bad_pixel_mask=3) # master bias check with pytest.raises(TypeError): ccd_process(ccd_data, master_bias=3) # Master flat check with pytest.raises(TypeError): ccd_process(ccd_data, master_flat=3) def test_ccd_process(): # Test the through ccd_process ccd_data = CCDData(10.0 * np.ones((100, 100)), unit=u.adu) ccd_data.data[:, -10:] = 2 ccd_data.meta['testkw'] = 100 mask = np.zeros((100, 90)) masterbias = CCDData(2.0 * np.ones((100, 90)), unit=u.electron) masterbias.uncertainty = StdDevUncertainty(np.zeros((100, 90))) dark_frame = CCDData(0.0 * np.ones((100, 90)), unit=u.electron) dark_frame.uncertainty = StdDevUncertainty(np.zeros((100, 90))) masterflat = CCDData(10.0 * np.ones((100, 90)), unit=u.electron) masterflat.uncertainty = StdDevUncertainty(np.zeros((100, 90))) occd = ccd_process(ccd_data, oscan=ccd_data[:, -10:], trim='[1:90,1:100]', error=True, master_bias=masterbias, master_flat=masterflat, dark_frame=dark_frame, bad_pixel_mask=mask, gain=0.5 * u.electron/u.adu, readnoise=5**0.5 * u.electron, oscan_median=True, dark_scale=False, dark_exposure=1.*u.s, data_exposure=1.*u.s) # Final results should be (10 - 2) / 2.0 - 2 = 2 # Error should be (4 + 5)**0.5 / 0.5 = 3.0 np.testing.assert_array_equal(2.0 * np.ones((100, 90)), occd.data) np.testing.assert_almost_equal(3.0 * np.ones((100, 90)), occd.uncertainty.array) np.testing.assert_array_equal(mask, occd.mask) assert(occd.unit == u.electron) # Make sure the original keyword is still present. Regression test for #401 assert occd.meta['testkw'] == 100 def test_ccd_process_gain_corrected(): # Test the through ccd_process with gain_corrected as False ccd_data = CCDData(10.0 * np.ones((100, 100)), unit=u.adu) ccd_data.data[:, -10:] = 2 ccd_data.meta['testkw'] = 100 mask = np.zeros((100, 90)) masterbias = CCDData(4.0 * np.ones((100, 90)), unit=u.adu) masterbias.uncertainty = StdDevUncertainty(np.zeros((100, 90))) dark_frame = CCDData(0.0 * np.ones((100, 90)), unit=u.adu) dark_frame.uncertainty = StdDevUncertainty(np.zeros((100, 90))) masterflat = CCDData(5.0 * np.ones((100, 90)), unit=u.adu) masterflat.uncertainty = StdDevUncertainty(np.zeros((100, 90))) occd = ccd_process(ccd_data, oscan=ccd_data[:, -10:], trim='[1:90,1:100]', error=True, master_bias=masterbias, master_flat=masterflat, dark_frame=dark_frame, bad_pixel_mask=mask, gain=0.5 * u.electron/u.adu, readnoise=5**0.5 * u.electron, oscan_median=True, dark_scale=False, dark_exposure=1.*u.s, data_exposure=1.*u.s, gain_corrected=False) # Final results should be (10 - 2) / 2.0 - 2 = 2 # Error should be (4 + 5)**0.5 / 0.5 = 3.0 np.testing.assert_array_equal(2.0 * np.ones((100, 90)), occd.data) np.testing.assert_almost_equal(3.0 * np.ones((100, 90)), occd.uncertainty.array) np.testing.assert_array_equal(mask, occd.mask) assert(occd.unit == u.electron) # Make sure the original keyword is still present. Regression test for #401 assert occd.meta['testkw'] == 100 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/ccdproc/tests/test_ccdproc_logging.py0000644000076600000240000001015200000000000023543 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np from astropy.nddata import CCDData import pytest from ccdproc import subtract_bias, create_deviation, Keyword, trim_image from ccdproc.core import _short_names from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func @pytest.mark.parametrize('key', [ 'short', 'toolongforfits']) def test_log_string(key): ccd_data = ccd_data_func() add_key = key new = create_deviation(ccd_data, readnoise=3 * ccd_data.unit, add_keyword=add_key) # Keys should be added to new but not to ccd_data and should have # no value. assert add_key in new.meta assert add_key not in ccd_data.meta # Long keyword names should be accessible with just the keyword name # without HIERARCH -- is it? assert new.meta[add_key] is None def test_log_keyword(): ccd_data = ccd_data_func() key = 'filter' key_val = 'V' kwd = Keyword(key, value=key_val) new = create_deviation(ccd_data, readnoise=3 * ccd_data.unit, add_keyword=kwd) # Was the Keyword added with the correct value? assert kwd.name in new.meta assert kwd.name not in ccd_data.meta assert new.meta[kwd.name] == key_val def test_log_dict(): ccd_data = ccd_data_func() keys_to_add = { 'process': 'Added deviation', 'n_images_input': 1, 'current_temp': 42.9 } new = create_deviation(ccd_data, readnoise=3 * ccd_data.unit, add_keyword=keys_to_add) for k, v in keys_to_add.items(): # Were all dictionary items added? assert k in new.meta assert k not in ccd_data.meta assert new.meta[k] == v def test_log_bad_type_fails(): ccd_data = ccd_data_func() add_key = 15 # anything not string and not dict-like will work here # Do we fail with non-string, non-Keyword, non-dict-like value? with pytest.raises(AttributeError): create_deviation(ccd_data, readnoise=3 * ccd_data.unit, add_keyword=add_key) def test_log_set_to_None_does_not_change_header(): ccd_data = ccd_data_func() new = create_deviation(ccd_data, readnoise=3 * ccd_data.unit, add_keyword=None) assert new.meta.keys() == ccd_data.header.keys() def test_implicit_logging(): ccd_data = ccd_data_func() # If nothing is supplied for the add_keyword argument then the following # should happen: # + A key named func.__name__ is created, with # + value that is the list of arguments the function was called with. bias = CCDData(np.zeros_like(ccd_data.data), unit="adu") result = subtract_bias(ccd_data, bias) assert "subtract_bias" in result.header assert result.header['subtract_bias'] == ( 'subbias', 'Shortened name for ccdproc command') assert result.header['subbias'] == "ccd=, master=" result = create_deviation(ccd_data, readnoise=3 * ccd_data.unit) assert result.header['create_deviation'] == ( 'creatvar', 'Shortened name for ccdproc command') assert ("readnoise=" + str(3 * ccd_data.unit) in result.header['creatvar']) def test_loggin_without_keyword_args(): # Regression test for the first failure in #704, which fails because # there is no "fits_section" keyword in the call to trim_image. ccd = CCDData(data=np.arange(1000).reshape(20, 50), header=None, unit='adu') section = "[10:20, 10:20]" trim_1 = trim_image(ccd, "[10:20, 10:20]") assert section in trim_1.header[_short_names['trim_image']] def test_logging_with_really_long_parameter_value(): # Another regression test for the trim_3 case in #704 ccd = CCDData(data=np.arange(1000).reshape(20, 50), header=None, unit='adu') section = ("[10:2000000000000000000000000000000000000000000000000000000, " "10:2000000000000000000000000000000]") trim_3 = trim_image(ccd, fits_section=section) assert section in trim_3.header[_short_names['trim_image']] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1552077140.0 ccdproc-2.3.0/ccdproc/tests/test_combine_open_files.py0000644000076600000240000000407500000000000024246 0ustar00mattcraigstaff00000000000000from pathlib import Path import subprocess import sys import os import pytest run_dir = Path(__file__).parent # Why? So that we get up to the file above ccdproc, so that in the # subprocess we can add that direction to sys.path. subprocess_dir = run_dir.parent.parent OVERHEAD = '4' NUM_FILE_LIMIT = '20' common_args = [sys.executable, str(run_dir / 'run_with_file_number_limit.py'), '--kind', 'fits', '--overhead', OVERHEAD] # Regression test for #629 @pytest.mark.skipif(os.environ.get('APPVEYOR') or os.sys.platform == 'win32', reason='Test relies on linux/osx features of psutil') @pytest.mark.skipif(sys.version_info < (3, 5), reason='Test requires subprocess.run, introduced in 3.5') def test_open_files_combine_no_chunks(): """ Test that we are not opening (much) more than the number of files we are processing. """ # Make a copy args = list(common_args) args.extend(['--open-by', 'combine-nochunk', NUM_FILE_LIMIT]) p = subprocess.run(args=args, stderr=subprocess.PIPE, cwd=str(subprocess_dir)) # If we have succeeded the test passes. We are only checking that # we don't have too many files open. assert p.returncode == 0 # Regression test for #629 @pytest.mark.skipif(os.environ.get('APPVEYOR') or os.sys.platform == 'win32', reason='Test relies on linux/osx features of psutil') @pytest.mark.skipif(sys.version_info < (3, 5), reason='Test requires subprocess.run, introduced in 3.5') def test_open_files_combine_chunks(): """ Test that we are not opening (much) more than the number of files we are processing when combination is broken into chunks. """ # Make a copy args = list(common_args) args.extend(['--open-by', 'combine-chunk', NUM_FILE_LIMIT]) p = subprocess.run(args=args, stderr=subprocess.PIPE, cwd=str(subprocess_dir)) # If we have succeeded the test passes. We are only checking that # we don't have too many files open. assert p.returncode == 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/tests/test_combiner.py0000644000076600000240000010473000000000000022224 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from packaging.version import Version, parse import numpy as np import astropy.units as u from astropy.stats import median_absolute_deviation as mad import astropy import pytest from astropy.utils.data import get_pkg_data_filename from astropy.nddata import CCDData from ccdproc.combiner import (Combiner, combine, _calculate_step_sizes, _default_std, sigma_func) from ccdproc.image_collection import ImageFileCollection from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func SUPER_OLD_ASTROPY = parse(astropy.__version__) < Version('4.3.0') # Several tests have many more NaNs in them than real data. numpy generates # lots of warnings in those cases and it makes more sense to suppress them # than to generate them. pytestmark = pytest.mark.filterwarnings( 'ignore:All-NaN slice encountered:RuntimeWarning' ) # test that the Combiner raises error if empty def test_combiner_empty(): with pytest.raises(TypeError): Combiner() # empty initializer should fail # test that the Combiner raises error if empty if ccd_list is None def test_combiner_init_with_none(): with pytest.raises(TypeError): Combiner(None) # empty initializer should fail # test that Combiner throws an error if input # objects are not ccddata objects def test_ccddata_combiner_objects(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, None] with pytest.raises(TypeError): Combiner(ccd_list) # different objects should fail # test that Combiner throws an error if input # objects do not have the same size def test_ccddata_combiner_size(): ccd_data = ccd_data_func() ccd_large = CCDData(np.zeros((200, 100)), unit=u.adu) ccd_list = [ccd_data, ccd_data, ccd_large] with pytest.raises(TypeError): Combiner(ccd_list) # arrays of different sizes should fail # test that Combiner throws an error if input # objects do not have the same units def test_ccddata_combiner_units(): ccd_data = ccd_data_func() ccd_large = CCDData(np.zeros((100, 100)), unit=u.second) ccd_list = [ccd_data, ccd_data, ccd_large] with pytest.raises(TypeError): Combiner(ccd_list) # test if mask and data array are created def test_combiner_create(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) assert c.data_arr.shape == (3, 100, 100) assert c.data_arr.mask.shape == (3, 100, 100) # test if dtype matches the value that is passed def test_combiner_dtype(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list, dtype=np.float32) assert c.data_arr.dtype == np.float32 avg = c.average_combine() # dtype of average should match input dtype assert avg.dtype == c.dtype med = c.median_combine() # dtype of median should match dtype of input assert med.dtype == c.dtype result_sum = c.sum_combine() # dtype of sum should match dtype of input assert result_sum.dtype == c.dtype # test mask is created from ccd.data def test_combiner_mask(): data = np.zeros((10, 10)) data[5, 5] = 1 mask = (data == 0) ccd = CCDData(data, unit=u.adu, mask=mask) ccd_list = [ccd, ccd, ccd] c = Combiner(ccd_list) assert c.data_arr.shape == (3, 10, 10) assert c.data_arr.mask.shape == (3, 10, 10) assert not c.data_arr.mask[0, 5, 5] def test_weights(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) with pytest.raises(TypeError): c.weights = 1 def test_weights_shape(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) with pytest.raises(ValueError): c.weights = ccd_data.data def test_1Dweights(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) c.weights = np.array([1, 5, 10]) ccd = c.average_combine() np.testing.assert_almost_equal(ccd.data, 312.5) def test_pixelwise_weights(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) c.weights = np.ones_like(c.data_arr) c.weights[:, 5, 5] = [1, 5, 10] ccd = c.average_combine() np.testing.assert_almost_equal(ccd.data[5, 5], 312.5) np.testing.assert_almost_equal(ccd.data[0, 0], 0) # test the min-max rejection def test_combiner_minmax(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) c.minmax_clipping(min_clip=-500, max_clip=500) ccd = c.median_combine() assert ccd.data.mean() == 0 def test_combiner_minmax_max(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) c.minmax_clipping(min_clip=None, max_clip=500) assert c.data_arr[2].mask.all() def test_combiner_minmax_min(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) c.minmax_clipping(min_clip=-500, max_clip=None) assert c.data_arr[1].mask.all() def test_combiner_sigmaclip_high(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 1000, unit=u.adu)] c = Combiner(ccd_list) # using mad for more robust statistics vs. std c.sigma_clipping(high_thresh=3, low_thresh=None, func=np.ma.median, dev_func=mad) assert c.data_arr[5].mask.all() def test_combiner_sigmaclip_single_pix(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu)] c = Combiner(ccd_list) # add a single pixel in another array to check that # that one gets rejected c.data_arr[0, 5, 5] = 0 c.data_arr[1, 5, 5] = -5 c.data_arr[2, 5, 5] = 5 c.data_arr[3, 5, 5] = -5 c.data_arr[4, 5, 5] = 25 c.sigma_clipping(high_thresh=3, low_thresh=None, func=np.ma.median, dev_func=mad) assert c.data_arr.mask[4, 5, 5] def test_combiner_sigmaclip_low(): ccd_list = [CCDData(np.zeros((10, 10)), unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) - 10, unit=u.adu), CCDData(np.zeros((10, 10)) + 10, unit=u.adu), CCDData(np.zeros((10, 10)) - 1000, unit=u.adu)] c = Combiner(ccd_list) # using mad for more robust statistics vs. std c.sigma_clipping(high_thresh=None, low_thresh=3, func=np.ma.median, dev_func=mad) assert c.data_arr[5].mask.all() @pytest.mark.parametrize('threshold', [1, 10]) def test_combiner_sigma_clip_use_astropy_same_result(threshold): # If we turn on use_astropy and make no other changes we should get exactly # the same result as if we use ccdproc sigma_clipping ccd_list = [ccd_data_func(rng_seed=seed + 1) for seed in range(10)] c_ccdp = Combiner(ccd_list) c_apy = Combiner(ccd_list) c_ccdp.sigma_clipping(low_thresh=threshold, high_thresh=threshold) c_apy.sigma_clipping(low_thresh=threshold, high_thresh=threshold, use_astropy=True) np.testing.assert_allclose(c_ccdp.data_arr.mask, c_apy.data_arr.mask) # test that the median combination works and returns a ccddata object def test_combiner_median(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.median_combine() assert isinstance(ccd, CCDData) assert ccd.shape == (100, 100) assert ccd.unit == u.adu assert ccd.meta['NCOMBINE'] == len(ccd_list) # test that the average combination works and returns a ccddata object def test_combiner_average(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.average_combine() assert isinstance(ccd, CCDData) assert ccd.shape == (100, 100) assert ccd.unit == u.adu assert ccd.meta['NCOMBINE'] == len(ccd_list) # test that the sum combination works and returns a ccddata object def test_combiner_sum(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.sum_combine() assert isinstance(ccd, CCDData) assert ccd.shape == (100, 100) assert ccd.unit == u.adu assert ccd.meta['NCOMBINE'] == len(ccd_list) # test weighted sum def test_combiner_sum_weighted(): ccd_data = CCDData(data=[[0, 1], [2, 3]], unit='adu') ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) c.weights = np.array([1, 2, 3]) ccd = c.sum_combine() expected_result = sum(w * d.data for w, d in zip(c.weights, ccd_list)) np.testing.assert_almost_equal(ccd, expected_result) # test weighted sum def test_combiner_sum_weighted_by_pixel(): ccd_data = CCDData(data=[[1, 2], [4, 8]], unit='adu') ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) # Weights below are chosen so that every entry in weights_pixel = [ [8, 4], [2, 1] ] c.weights = np.array([weights_pixel] * 3) ccd = c.sum_combine() expected_result = [ [24, 24], [24, 24] ] np.testing.assert_almost_equal(ccd, expected_result) # This warning is generated by numpy and is expected when # many pixels are masked. @pytest.mark.filterwarnings( 'ignore:Mean of empty slice:RuntimeWarning', 'ignore:Degrees of freedom <= 0:RuntimeWarning' ) def test_combiner_mask_average(): # test data combined with mask is created correctly data = np.zeros((10, 10)) data[5, 5] = 1 mask = (data == 0) ccd = CCDData(data, unit=u.adu, mask=mask) ccd_list = [ccd, ccd, ccd] c = Combiner(ccd_list) ccd = c.average_combine() # How can we assert anything about the data if all values # are masked?! # assert ccd.data[0, 0] == 0 assert ccd.data[5, 5] == 1 assert ccd.mask[0, 0] assert not ccd.mask[5, 5] def test_combiner_with_scaling(): ccd_data = ccd_data_func() # The factors below are not particularly important; just avoid anything # whose average is 1. ccd_data_lower = ccd_data.multiply(3) ccd_data_higher = ccd_data.multiply(0.9) combiner = Combiner([ccd_data, ccd_data_higher, ccd_data_lower]) # scale each array to the mean of the first image scale_by_mean = lambda x: ccd_data.data.mean() / np.ma.average(x) combiner.scaling = scale_by_mean avg_ccd = combiner.average_combine() # Does the mean of the scaled arrays match the value to which it was # scaled? np.testing.assert_almost_equal(avg_ccd.data.mean(), ccd_data.data.mean()) assert avg_ccd.shape == ccd_data.shape median_ccd = combiner.median_combine() # Does median also scale to the correct value? np.testing.assert_almost_equal(np.median(median_ccd.data), np.median(ccd_data.data)) # Set the scaling manually... combiner.scaling = [scale_by_mean(combiner.data_arr[i]) for i in range(3)] avg_ccd = combiner.average_combine() np.testing.assert_almost_equal(avg_ccd.data.mean(), ccd_data.data.mean()) assert avg_ccd.shape == ccd_data.shape def test_combiner_scaling_fails(): ccd_data = ccd_data_func() combiner = Combiner([ccd_data, ccd_data.copy()]) # Should fail unless scaling is set to a function or list-like with pytest.raises(TypeError): combiner.scaling = 5 # test data combined with mask is created correctly def test_combiner_mask_median(): data = np.zeros((10, 10)) data[5, 5] = 1 mask = (data == 0) ccd = CCDData(data, unit=u.adu, mask=mask) ccd_list = [ccd, ccd, ccd] c = Combiner(ccd_list) ccd = c.median_combine() # We should not check the data value for masked entries. # Instead, just check that entries are masked appropriately. assert ccd.mask[0, 0] assert ccd.data[5, 5] == 1 assert not ccd.mask[5, 5] # Ignore warnings generated because most values are masked @pytest.mark.filterwarnings( 'ignore:Degrees of freedom <= 0:RuntimeWarning' ) def test_combiner_mask_sum(): # test data combined with mask is created correctly data = np.zeros((10, 10)) data[5, 5] = 1 mask = (data == 0) ccd = CCDData(data, unit=u.adu, mask=mask) ccd_list = [ccd, ccd, ccd] c = Combiner(ccd_list) ccd = c.sum_combine() assert ccd.data[0, 0] == 0 assert ccd.data[5, 5] == 3 assert ccd.mask[0, 0] assert not ccd.mask[5, 5] # test combiner convenience function reads fits file and combine as expected def test_combine_average_fitsimages(): fitsfile = get_pkg_data_filename('data/a8280271.fits', package='ccdproc.tests') ccd = CCDData.read(fitsfile, unit=u.adu) ccd_list = [ccd] * 3 c = Combiner(ccd_list) ccd_by_combiner = c.average_combine() fitsfilename_list = [fitsfile] * 3 avgccd = combine(fitsfilename_list, output_file=None, method='average', unit=u.adu) # averaging same fits images should give back same fits image np.testing.assert_array_almost_equal(avgccd.data, ccd_by_combiner.data) def test_combine_numpyndarray(): """ Test of numpy ndarray implementation: #493 Test the average combine using ``Combiner`` and ``combine`` with input ``img_list`` in the format of ``numpy.ndarray``. """ fitsfile = get_pkg_data_filename('data/a8280271.fits') ccd = CCDData.read(fitsfile, unit=u.adu) ccd_list = [ccd] * 3 c = Combiner(ccd_list) ccd_by_combiner = c.average_combine() fitsfilename_list = np.array([fitsfile] * 3) avgccd = combine(fitsfilename_list, output_file=None, method='average', unit=u.adu) # averaging same fits images should give back same fits image np.testing.assert_array_almost_equal(avgccd.data, ccd_by_combiner.data) def test_combiner_result_dtype(): """Regression test: #391 The result should have the appropriate dtype not the dtype of the first input.""" ccd = CCDData(np.ones((3, 3), dtype=np.uint16), unit='adu') res = combine([ccd, ccd.multiply(2)]) # The default dtype of Combiner is float64 assert res.data.dtype == np.float64 ref = np.ones((3, 3)) * 1.5 np.testing.assert_array_almost_equal(res.data, ref) res = combine([ccd, ccd.multiply(2), ccd.multiply(3)], dtype=int) # The result dtype should be integer: assert res.data.dtype == np.int_ ref = np.ones((3, 3)) * 2 np.testing.assert_array_almost_equal(res.data, ref) def test_combiner_image_file_collection_input(tmp_path): # Regression check for #754 ccd = ccd_data_func() for i in range(3): ccd.write(tmp_path / f'ccd-{i}.fits') ifc = ImageFileCollection(tmp_path) comb = Combiner(ifc.ccds()) np.testing.assert_array_almost_equal(ccd.data, comb.average_combine().data) def test_combine_image_file_collection_input(tmp_path): # Another regression check for #754 but this time with the # combine function instead of Combiner ccd = ccd_data_func() for i in range(3): ccd.write(tmp_path / f'ccd-{i}.fits') ifc = ImageFileCollection(tmp_path) comb_files = combine(ifc.files_filtered(include_path=True), method='average') comb_ccds = combine(ifc.ccds(), method='average') np.testing.assert_array_almost_equal(ccd.data, comb_files.data) np.testing.assert_array_almost_equal(ccd.data, comb_ccds.data) with pytest.raises(FileNotFoundError): # This should fail because the test is not running in the # folder where the images are. _ = combine(ifc.files_filtered()) # test combiner convenience function works with list of ccddata objects def test_combine_average_ccddata(): fitsfile = get_pkg_data_filename('data/a8280271.fits') ccd = CCDData.read(fitsfile, unit=u.adu) ccd_list = [ccd] * 3 c = Combiner(ccd_list) ccd_by_combiner = c.average_combine() avgccd = combine(ccd_list, output_file=None, method='average', unit=u.adu) # averaging same ccdData should give back same images np.testing.assert_array_almost_equal(avgccd.data, ccd_by_combiner.data) # test combiner convenience function reads fits file and # and combine as expected when asked to run in limited memory def test_combine_limitedmem_fitsimages(): fitsfile = get_pkg_data_filename('data/a8280271.fits') ccd = CCDData.read(fitsfile, unit=u.adu) ccd_list = [ccd] * 5 c = Combiner(ccd_list) ccd_by_combiner = c.average_combine() fitsfilename_list = [fitsfile] * 5 avgccd = combine(fitsfilename_list, output_file=None, method='average', mem_limit=1e6, unit=u.adu) # averaging same ccdData should give back same images np.testing.assert_array_almost_equal(avgccd.data, ccd_by_combiner.data) # test combiner convenience function reads fits file and # and combine as expected when asked to run in limited memory with scaling def test_combine_limitedmem_scale_fitsimages(): fitsfile = get_pkg_data_filename('data/a8280271.fits') ccd = CCDData.read(fitsfile, unit=u.adu) ccd_list = [ccd] * 5 c = Combiner(ccd_list) # scale each array to the mean of the first image scale_by_mean = lambda x: ccd.data.mean() / np.ma.average(x) c.scaling = scale_by_mean ccd_by_combiner = c.average_combine() fitsfilename_list = [fitsfile] * 5 avgccd = combine(fitsfilename_list, output_file=None, method='average', mem_limit=1e6, scale=scale_by_mean, unit=u.adu) np.testing.assert_array_almost_equal( avgccd.data, ccd_by_combiner.data, decimal=4) # test the optional uncertainty function in average_combine def test_average_combine_uncertainty(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.average_combine(uncertainty_func=np.sum) uncert_ref = np.sum(c.data_arr, 0) / np.sqrt(3) np.testing.assert_array_equal(ccd.uncertainty.array, uncert_ref) # Compare this also to the "combine" call ccd2 = combine(ccd_list, method='average', combine_uncertainty_function=np.sum) np.testing.assert_array_equal(ccd.data, ccd2.data) np.testing.assert_array_equal( ccd.uncertainty.array, ccd2.uncertainty.array) # test the optional uncertainty function in median_combine def test_median_combine_uncertainty(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.median_combine(uncertainty_func=np.sum) uncert_ref = np.sum(c.data_arr, 0) / np.sqrt(3) np.testing.assert_array_equal(ccd.uncertainty.array, uncert_ref) # Compare this also to the "combine" call ccd2 = combine(ccd_list, method='median', combine_uncertainty_function=np.sum) np.testing.assert_array_equal(ccd.data, ccd2.data) np.testing.assert_array_equal( ccd.uncertainty.array, ccd2.uncertainty.array) # test the optional uncertainty function in sum_combine def test_sum_combine_uncertainty(): ccd_data = ccd_data_func() ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) ccd = c.sum_combine(uncertainty_func=np.sum) uncert_ref = np.sum(c.data_arr, 0) * np.sqrt(3) np.testing.assert_almost_equal(ccd.uncertainty.array, uncert_ref) # Compare this also to the "combine" call ccd2 = combine(ccd_list, method='sum', combine_uncertainty_function=np.sum) np.testing.assert_array_equal(ccd.data, ccd2.data) np.testing.assert_array_equal( ccd.uncertainty.array, ccd2.uncertainty.array) # Ignore warnings generated because most values are masked @pytest.mark.filterwarnings( 'ignore:Mean of empty slice:RuntimeWarning', 'ignore:Degrees of freedom <= 0:RuntimeWarning' ) @pytest.mark.parametrize('mask_point', [True, False]) @pytest.mark.parametrize('comb_func', ['average_combine', 'median_combine', 'sum_combine']) def test_combine_result_uncertainty_and_mask(comb_func, mask_point): # Regression test for #774 # Turns out combine does not return an uncertainty or mask if the input # CCDData has no uncertainty or mask, which makes very little sense. ccd_data = ccd_data_func() # Make sure the initial ccd_data has no uncertainty, which was the condition that # led to no uncertainty being returned. assert ccd_data.uncertainty is None if mask_point: # Make one pixel really negative so we can clip it and guarantee a resulting # pixel is masked. ccd_data.data[0, 0] = -1000 ccd_list = [ccd_data, ccd_data, ccd_data] c = Combiner(ccd_list) c.minmax_clipping(min_clip=-100) expected_result = getattr(c, comb_func)() # Just need the first part of the name for the combine function combine_method_name = comb_func.split('_')[0] ccd_comb = combine(ccd_list, method=combine_method_name, minmax_clip=True, minmax_clip_min=-100) np.testing.assert_array_almost_equal(ccd_comb.uncertainty.array, expected_result.uncertainty.array) # Check that the right point is masked, and only one point is # masked assert expected_result.mask[0, 0] == mask_point assert expected_result.mask.sum() == mask_point assert ccd_comb.mask[0, 0] == mask_point assert ccd_comb.mask.sum() == mask_point # test resulting uncertainty is corrected for the number of images def test_combiner_uncertainty_average(): ccd_list = [CCDData(np.ones((10, 10)), unit=u.adu), CCDData(np.ones((10, 10)) * 2, unit=u.adu)] c = Combiner(ccd_list) ccd = c.average_combine() # Just the standard deviation of ccd data. ref_uncertainty = np.ones((10, 10)) / 2 # Correction because we combined two images. ref_uncertainty /= np.sqrt(2) np.testing.assert_array_almost_equal(ccd.uncertainty.array, ref_uncertainty) # test resulting uncertainty is corrected for the number of images (with mask) def test_combiner_uncertainty_average_mask(): mask = np.zeros((10, 10), dtype=np.bool_) mask[5, 5] = True ccd_with_mask = CCDData(np.ones((10, 10)), unit=u.adu, mask=mask) ccd_list = [ccd_with_mask, CCDData(np.ones((10, 10)) * 2, unit=u.adu), CCDData(np.ones((10, 10)) * 3, unit=u.adu)] c = Combiner(ccd_list) ccd = c.average_combine() # Just the standard deviation of ccd data. ref_uncertainty = np.ones((10, 10)) * np.std([1, 2, 3]) # Correction because we combined two images. ref_uncertainty /= np.sqrt(3) ref_uncertainty[5, 5] = np.std([2, 3]) / np.sqrt(2) np.testing.assert_array_almost_equal(ccd.uncertainty.array, ref_uncertainty) # test resulting uncertainty is corrected for the number of images (with mask) def test_combiner_uncertainty_median_mask(): mad_to_sigma = 1.482602218505602 mask = np.zeros((10, 10), dtype=np.bool_) mask[5, 5] = True ccd_with_mask = CCDData(np.ones((10, 10)), unit=u.adu, mask=mask) ccd_list = [ccd_with_mask, CCDData(np.ones((10, 10)) * 2, unit=u.adu), CCDData(np.ones((10, 10)) * 3, unit=u.adu)] c = Combiner(ccd_list) ccd = c.median_combine() # Just the standard deviation of ccd data. ref_uncertainty = np.ones((10, 10)) * mad_to_sigma * mad([1, 2, 3]) # Correction because we combined two images. ref_uncertainty /= np.sqrt(3) # 0.855980789955 ref_uncertainty[5, 5] = mad_to_sigma * \ mad([2, 3]) / np.sqrt(2) # 0.524179041254 np.testing.assert_array_almost_equal(ccd.uncertainty.array, ref_uncertainty) # test resulting uncertainty is corrected for the number of images (with mask) def test_combiner_uncertainty_sum_mask(): mask = np.zeros((10, 10), dtype=np.bool_) mask[5, 5] = True ccd_with_mask = CCDData(np.ones((10, 10)), unit=u.adu, mask=mask) ccd_list = [ccd_with_mask, CCDData(np.ones((10, 10)) * 2, unit=u.adu), CCDData(np.ones((10, 10)) * 3, unit=u.adu)] c = Combiner(ccd_list) ccd = c.sum_combine() # Just the standard deviation of ccd data. ref_uncertainty = np.ones((10, 10)) * np.std([1, 2, 3]) ref_uncertainty *= np.sqrt(3) ref_uncertainty[5, 5] = np.std([2, 3]) * np.sqrt(2) np.testing.assert_array_almost_equal(ccd.uncertainty.array, ref_uncertainty) def test_combiner_3d(): data1 = CCDData(3 * np.ones((5, 5, 5)), unit=u.adu) data2 = CCDData(2 * np.ones((5, 5, 5)), unit=u.adu) data3 = CCDData(4 * np.ones((5, 5, 5)), unit=u.adu) ccd_list = [data1, data2, data3] c = Combiner(ccd_list) assert c.data_arr.shape == (3, 5, 5, 5) assert c.data_arr.mask.shape == (3, 5, 5, 5) ccd = c.average_combine() assert ccd.shape == (5, 5, 5) np.testing.assert_array_almost_equal(ccd.data, data1, decimal=4) def test_3d_combiner_with_scaling(): ccd_data = ccd_data_func() # The factors below are not particularly important; just avoid anything # whose average is 1. ccd_data = CCDData(np.ones((5, 5, 5)), unit=u.adu) ccd_data_lower = CCDData(3 * np.ones((5, 5, 5)), unit=u.adu) ccd_data_higher = CCDData(0.9 * np.ones((5, 5, 5)), unit=u.adu) combiner = Combiner([ccd_data, ccd_data_higher, ccd_data_lower]) # scale each array to the mean of the first image scale_by_mean = lambda x: ccd_data.data.mean() / np.ma.average(x) combiner.scaling = scale_by_mean avg_ccd = combiner.average_combine() # Does the mean of the scaled arrays match the value to which it was # scaled? np.testing.assert_almost_equal(avg_ccd.data.mean(), ccd_data.data.mean()) assert avg_ccd.shape == ccd_data.shape median_ccd = combiner.median_combine() # Does median also scale to the correct value? np.testing.assert_almost_equal(np.median(median_ccd.data), np.median(ccd_data.data)) # Set the scaling manually... combiner.scaling = [scale_by_mean(combiner.data_arr[i]) for i in range(3)] avg_ccd = combiner.average_combine() np.testing.assert_almost_equal(avg_ccd.data.mean(), ccd_data.data.mean()) assert avg_ccd.shape == ccd_data.shape def test_clip_extrema_3d(): ccdlist = [CCDData(np.ones((3, 3, 3)) * 90., unit="adu"), CCDData(np.ones((3, 3, 3)) * 20., unit="adu"), CCDData(np.ones((3, 3, 3)) * 10., unit="adu"), CCDData(np.ones((3, 3, 3)) * 40., unit="adu"), CCDData(np.ones((3, 3, 3)) * 25., unit="adu"), CCDData(np.ones((3, 3, 3)) * 35., unit="adu"), ] c = Combiner(ccdlist) c.clip_extrema(nlow=1, nhigh=1) result = c.average_combine() expected = CCDData(np.ones((3, 3, 3)) * 30, unit="adu") np.testing.assert_array_equal(result, expected) @pytest.mark.parametrize('comb_func', ['average_combine', 'median_combine', 'sum_combine']) def test_writeable_after_combine(tmpdir, comb_func): ccd_data = ccd_data_func() tmp_file = tmpdir.join('tmp.fits') from ..combiner import Combiner combined = Combiner([ccd_data for _ in range(3)]) ccd2 = getattr(combined, comb_func)() # This should not fail because the resulting uncertainty has a mask ccd2.write(tmp_file.strpath) def test_clip_extrema(): ccdlist = [CCDData(np.ones((3, 5)) * 90., unit="adu"), CCDData(np.ones((3, 5)) * 20., unit="adu"), CCDData(np.ones((3, 5)) * 10., unit="adu"), CCDData(np.ones((3, 5)) * 40., unit="adu"), CCDData(np.ones((3, 5)) * 25., unit="adu"), CCDData(np.ones((3, 5)) * 35., unit="adu"), ] ccdlist[0].data[0, 1] = 3.1 ccdlist[1].data[1, 2] = 100.1 ccdlist[1].data[2, 0] = 100.1 c = Combiner(ccdlist) c.clip_extrema(nlow=1, nhigh=1) result = c.average_combine() expected = [[30.0, 22.5, 30.0, 30.0, 30.0], [30.0, 30.0, 47.5, 30.0, 30.0], [47.5, 30.0, 30.0, 30.0, 30.0]] np.testing.assert_array_equal(result, expected) def test_clip_extrema_via_combine(): ccdlist = [CCDData(np.ones((3, 5)) * 90., unit="adu"), CCDData(np.ones((3, 5)) * 20., unit="adu"), CCDData(np.ones((3, 5)) * 10., unit="adu"), CCDData(np.ones((3, 5)) * 40., unit="adu"), CCDData(np.ones((3, 5)) * 25., unit="adu"), CCDData(np.ones((3, 5)) * 35., unit="adu"), ] ccdlist[0].data[0, 1] = 3.1 ccdlist[1].data[1, 2] = 100.1 ccdlist[1].data[2, 0] = 100.1 result = combine(ccdlist, clip_extrema=True, nlow=1, nhigh=1,) expected = [[30.0, 22.5, 30.0, 30.0, 30.0], [30.0, 30.0, 47.5, 30.0, 30.0], [47.5, 30.0, 30.0, 30.0, 30.0]] np.testing.assert_array_equal(result, expected) def test_clip_extrema_with_other_rejection(): ccdlist = [CCDData(np.ones((3, 5)) * 90., unit="adu"), CCDData(np.ones((3, 5)) * 20., unit="adu"), CCDData(np.ones((3, 5)) * 10., unit="adu"), CCDData(np.ones((3, 5)) * 40., unit="adu"), CCDData(np.ones((3, 5)) * 25., unit="adu"), CCDData(np.ones((3, 5)) * 35., unit="adu"), ] ccdlist[0].data[0, 1] = 3.1 ccdlist[1].data[1, 2] = 100.1 ccdlist[1].data[2, 0] = 100.1 c = Combiner(ccdlist) # Reject ccdlist[1].data[1,2] by other means c.data_arr.mask[1, 1, 2] = True # Reject ccdlist[1].data[1,2] by other means c.data_arr.mask[3, 0, 0] = True c.clip_extrema(nlow=1, nhigh=1) result = c.average_combine() expected = [[80. / 3., 22.5, 30., 30., 30.], [30., 30., 47.5, 30., 30.], [47.5, 30., 30., 30., 30.]] np.testing.assert_array_equal(result, expected) # The expected values below assume an image that is 2000x2000 @pytest.mark.parametrize('num_chunks, expected', [(53, (37, 2000)), (1500, (1, 2000)), (2001, (1, 1000)), (2999, (1, 1000)), (10000, (1, 333))] ) def test_ystep_calculation(num_chunks, expected): # Regression test for # https://github.com/astropy/ccdproc/issues/639 # See that issue for the motivation for the choice of # image size and number of chunks in the test below. xstep, ystep = _calculate_step_sizes(2000, 2000, num_chunks) assert xstep == expected[0] and ystep == expected[1] def test_combiner_gen(): ccd_data = ccd_data_func() def create_gen(): yield ccd_data yield ccd_data yield ccd_data c = Combiner(create_gen()) assert c.data_arr.shape == (3, 100, 100) assert c.data_arr.mask.shape == (3, 100, 100) @pytest.mark.parametrize('comb_func', ['average_combine', 'median_combine', 'sum_combine']) def test_combiner_with_scaling_uncertainty(comb_func): # A regression test for #719, in which it was pointed out that the # uncertainty was not properly calculated from scaled data in # median_combine ccd_data = ccd_data_func() # The factors below are not particularly important; just avoid anything # whose average is 1. ccd_data_lower = ccd_data.multiply(3) ccd_data_higher = ccd_data.multiply(0.9) combiner = Combiner([ccd_data, ccd_data_higher, ccd_data_lower]) # scale each array to the mean of the first image scale_by_mean = lambda x: ccd_data.data.mean() / np.ma.average(x) combiner.scaling = scale_by_mean scaled_ccds = np.array([ccd_data.data * scale_by_mean(ccd_data.data), ccd_data_lower.data * scale_by_mean(ccd_data_lower.data), ccd_data_higher.data * scale_by_mean(ccd_data_higher.data) ]) avg_ccd = getattr(combiner, comb_func)() if comb_func != 'median_combine': uncertainty_func = _default_std() else: uncertainty_func = sigma_func expected_unc = uncertainty_func(scaled_ccds, axis=0) np.testing.assert_almost_equal(avg_ccd.uncertainty.array, expected_unc) @pytest.mark.parametrize('comb_func', ['average_combine', 'median_combine', 'sum_combine']) def test_user_supplied_combine_func_that_relies_on_masks(comb_func): # Test to make sure that setting some values to NaN internally # does not affect results when the user supplies a function that # uses masks to screen out bad data. data = np.ones((10, 10)) data[5, 5] = 2 mask = (data == 2) ccd = CCDData(data, unit=u.adu, mask=mask) # Same, but no mask ccd2 = CCDData(data, unit=u.adu) ccd_list = [ccd, ccd, ccd2] c = Combiner(ccd_list) if comb_func == 'sum_combine': expected_result = 3 * data actual_result = c.sum_combine(sum_func=np.ma.sum) elif comb_func == 'average_combine': expected_result = data actual_result = c.average_combine(scale_func=np.ma.mean) elif comb_func == 'median_combine': expected_result = data actual_result = c.median_combine(median_func=np.ma.median) # Two of the three values are masked, so no matter what the combination # method is the result in this pixel should be 2. expected_result[5, 5] = 2 np.testing.assert_almost_equal(expected_result, actual_result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/tests/test_cosmicray.py0000644000076600000240000003557700000000000022433 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import packaging import numpy as np from numpy.testing import assert_allclose import pytest from astropy.utils import NumpyRNGContext from astropy.utils.exceptions import AstropyDeprecationWarning from astropy.nddata import StdDevUncertainty from astropy import units as u try: from astroscrappy import __version__ as asy_version except Exception as e: print("Oh no, no working astroscrappy") pytest.skip("skipping astroscrappy tests", allow_module_level=True) from ccdproc.core import (cosmicray_lacosmic, cosmicray_median, background_deviation_box, background_deviation_filter) from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func DATA_SCALE = 5.3 NCRAYS = 30 OLD_ASTROSCRAPPY = (packaging.version.parse(asy_version) < packaging.version.parse('1.1.0')) def add_cosmicrays(data, scale, threshold, ncrays=NCRAYS): size = data.shape[0] with NumpyRNGContext(125): crrays = np.random.randint(0, size, size=(ncrays, 2)) # use (threshold + 15) below to make sure cosmic ray is well above the # threshold no matter what the random number generator returns crflux = (10 * scale * np.random.random(NCRAYS) + (threshold + 15) * scale) for i in range(ncrays): y, x = crrays[i] data.data[y, x] = crflux[i] def test_cosmicray_lacosmic(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 10 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) data, crarr = cosmicray_lacosmic(ccd_data.data, sigclip=5.9) # check the number of cosmic rays detected # Note that to get this to succeed reliably meant tuning # both sigclip and the threshold assert crarr.sum() == NCRAYS def test_cosmicray_lacosmic_ccddata(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise nccd_data = cosmicray_lacosmic(ccd_data, sigclip=5.9) # check the number of cosmic rays detected # Note that to get this to succeed reliably meant tuning # both sigclip and the threshold assert nccd_data.mask.sum() == NCRAYS def test_cosmicray_lacosmic_check_data(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) with pytest.raises(TypeError): noise = DATA_SCALE * np.ones_like(ccd_data.data) cosmicray_lacosmic(10, noise) @pytest.mark.parametrize('array_input', [True, False]) @pytest.mark.parametrize('gain_correct_data', [True, False]) def test_cosmicray_gain_correct(array_input, gain_correct_data): # Add regression check for #705 and for the new gain_correct # argument. # The issue is that cosmicray_lacosmic gain-corrects the # data and returns that gain corrected data. That is not the # intent... ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise # No units here on purpose. gain = 2.0 # Don't really need to set this (6.5 is the default value) but want to # make lack of units explicit. readnoise = 6.5 if array_input: new_data, cr_mask = cosmicray_lacosmic(ccd_data.data, gain=gain, gain_apply=gain_correct_data) else: new_ccd = cosmicray_lacosmic(ccd_data, gain=gain, gain_apply=gain_correct_data) new_data = new_ccd.data cr_mask = new_ccd.mask # Fill masked locations with 0 since there is no simple relationship # between the original value and the corrected value. orig_data = np.ma.array(ccd_data.data, mask=cr_mask).filled(0) new_data = np.ma.array(new_data.data, mask=cr_mask).filled(0) if gain_correct_data: gain_for_test = gain else: gain_for_test = 1.0 np.testing.assert_allclose(gain_for_test * orig_data, new_data) def test_cosmicray_lacosmic_accepts_quantity_gain(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise # The units below are the point of the test gain = 2.0 * u.electron / u.adu # Since gain and ccd_data have units, the readnoise should too. readnoise = 6.5 * u.electron new_ccd = cosmicray_lacosmic(ccd_data, gain=gain, gain_apply=True) def test_cosmicray_lacosmic_accepts_quantity_readnoise(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise gain = 2.0 * u.electron / u.adu # The units below are the point of this test readnoise = 6.5 * u.electron new_ccd = cosmicray_lacosmic(ccd_data, gain=gain, gain_apply=True, readnoise=readnoise) def test_cosmicray_lacosmic_detects_inconsistent_units(): # This is intended to detect cases like a ccd with units # of adu, a readnoise in electrons and a gain in adu / electron. # That is not internally inconsistent. ccd_data = ccd_data_func(data_scale=DATA_SCALE) ccd_data.unit = 'adu' threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise readnoise = 6.5 * u.electron # The units below are deliberately incorrect. gain = 2.0 * u.adu / u.electron with pytest.raises(ValueError) as e: cosmicray_lacosmic(ccd_data, gain=gain, gain_apply=True, readnoise=readnoise) assert 'Inconsistent units' in str(e.value) def test_cosmicray_lacosmic_warns_on_ccd_in_electrons(recwarn): # Check that an input ccd in electrons raises a warning. ccd_data = ccd_data_func(data_scale=DATA_SCALE) # The unit below is important for the test; this unit on # input is supposed to raise an error. ccd_data.unit = u.electron threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise # No units here on purpose. gain = 2.0 # Don't really need to set this (6.5 is the default value) but want to # make lack of units explicit. readnoise = 6.5 new_ccd = cosmicray_lacosmic(ccd_data, gain=gain, gain_apply=True, readnoise=readnoise) assert "Image unit is electron" in str(recwarn.pop()) # The skip can be removed when the oldest supported astroscrappy # is 1.1.0 or higher @pytest.mark.skipif(OLD_ASTROSCRAPPY, reason='astroscrappy < 1.1.0 does not support ' 'this functionality') # The values for inbkg and invar are DELIBERATELY BAD. They are supposed to be # arrays, so if detect_cosmics is called with these bad values a ValueError # will be raised, which we can check for. @pytest.mark.parametrize('new_args', [dict(inbkg=5), dict(invar=5), dict(inbkg=5, invar=5)]) def test_cosmicray_lacosmic_invar_inbkg(new_args): # This IS NOT TESTING FUNCTIONALITY it is simply testing # that calling with the new keyword arguments to astroscrappy # 1.1.0 raises no error. ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise with pytest.raises(TypeError): nccd_data = cosmicray_lacosmic(ccd_data, sigclip=5.9, **new_args) def test_cosmicray_median_check_data(): with pytest.raises(TypeError): ndata, crarr = cosmicray_median(10, thresh=5, mbox=11, error_image=DATA_SCALE) def test_cosmicray_median(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) ndata, crarr = cosmicray_median(ccd_data.data, thresh=5, mbox=11, error_image=DATA_SCALE) # check the number of cosmic rays detected assert crarr.sum() == NCRAYS def test_cosmicray_median_ccddata(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) ccd_data.uncertainty = ccd_data.data*0.0+DATA_SCALE nccd = cosmicray_median(ccd_data, thresh=5, mbox=11, error_image=None) # check the number of cosmic rays detected assert nccd.mask.sum() == NCRAYS def test_cosmicray_median_masked(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) data = np.ma.masked_array(ccd_data.data, (ccd_data.data > -1e6)) ndata, crarr = cosmicray_median(data, thresh=5, mbox=11, error_image=DATA_SCALE) # check the number of cosmic rays detected assert crarr.sum() == NCRAYS def test_cosmicray_median_background_None(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) data, crarr = cosmicray_median(ccd_data.data, thresh=5, mbox=11, error_image=None) # check the number of cosmic rays detected assert crarr.sum() == NCRAYS def test_cosmicray_median_gbox(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) scale = DATA_SCALE # yuck. Maybe use pytest.parametrize? threshold = 5 add_cosmicrays(ccd_data, scale, threshold, ncrays=NCRAYS) error = ccd_data.data*0.0+DATA_SCALE data, crarr = cosmicray_median(ccd_data.data, error_image=error, thresh=5, mbox=11, rbox=0, gbox=5) data = np.ma.masked_array(data, crarr) assert crarr.sum() > NCRAYS assert abs(data.std() - scale) < 0.1 def test_cosmicray_median_rbox(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) scale = DATA_SCALE # yuck. Maybe use pytest.parametrize? threshold = 5 add_cosmicrays(ccd_data, scale, threshold, ncrays=NCRAYS) error = ccd_data.data*0.0+DATA_SCALE data, crarr = cosmicray_median(ccd_data.data, error_image=error, thresh=5, mbox=11, rbox=21, gbox=5) assert data[crarr].mean() < ccd_data.data[crarr].mean() assert crarr.sum() > NCRAYS def test_cosmicray_median_background_deviation(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) with pytest.raises(TypeError): cosmicray_median(ccd_data.data, thresh=5, mbox=11, error_image='blank') def test_background_deviation_box(): with NumpyRNGContext(123): scale = 5.3 cd = np.random.normal(loc=0, size=(100, 100), scale=scale) bd = background_deviation_box(cd, 25) assert abs(bd.mean() - scale) < 0.10 def test_background_deviation_box_fail(): with NumpyRNGContext(123): scale = 5.3 cd = np.random.normal(loc=0, size=(100, 100), scale=scale) with pytest.raises(ValueError): background_deviation_box(cd, 0.5) def test_background_deviation_filter(): with NumpyRNGContext(123): scale = 5.3 cd = np.random.normal(loc=0, size=(100, 100), scale=scale) bd = background_deviation_filter(cd, 25) assert abs(bd.mean() - scale) < 0.10 def test_background_deviation_filter_fail(): with NumpyRNGContext(123): scale = 5.3 cd = np.random.normal(loc=0, size=(100, 100), scale=scale) with pytest.raises(ValueError): background_deviation_filter(cd, 0.5) # This test can be removed in ccdproc 3.0 when support for old # astroscrappy is removed. def test_cosmicray_lacosmic_pssl_deprecation_warning(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) with pytest.warns(AstropyDeprecationWarning): cosmicray_lacosmic(ccd_data, pssl=1.0) # Remaining tests can be removed when the oldest supported version # of astroscrappy is 1.1.0 or higher. @pytest.mark.skipif(not OLD_ASTROSCRAPPY, reason='Should succeed for new astroscrappy') @pytest.mark.parametrize('bad_args', [dict(inbkg=5), dict(invar=5), dict(inbkg=5, invar=5)]) def test_error_raised_lacosmic_old_interface_new_args(bad_args): ccd_data = ccd_data_func(data_scale=DATA_SCALE) with pytest.raises(TypeError) as err: cosmicray_lacosmic(ccd_data, **bad_args) check_message = [k in str(err) for k in bad_args.keys()] assert all(check_message) @pytest.mark.skipif(OLD_ASTROSCRAPPY, reason='Test is of new interface compatibility layer') def test_cosmicray_lacosmic_pssl_and_inbkg_fails(): ccd_data = ccd_data_func(data_scale=DATA_SCALE) with pytest.raises(ValueError) as err: # An error should be raised if both pssl and inbkg are provided with pytest.warns(AstropyDeprecationWarning): # The deprecation warning is expected and should be captured cosmicray_lacosmic(ccd_data, pssl=3, inbkg=ccd_data.data) assert 'pssl and inbkg' in str(err) @pytest.mark.skipif(OLD_ASTROSCRAPPY, reason='Test is of new interface compatibility layer') def test_cosmicray_lacosmic_pssl_does_not_fail(): # This test is a copy/paste of test_cosmicray_lacosmic_ccddata # except with pssl=0.0001 as an argument. Subtracting nearly zero from # the background should have no effect. The test is really # to make sure that passing in pssl does not lead to an error # since the new interface does not include pssl. ccd_data = ccd_data_func(data_scale=DATA_SCALE) threshold = 5 add_cosmicrays(ccd_data, DATA_SCALE, threshold, ncrays=NCRAYS) noise = DATA_SCALE * np.ones_like(ccd_data.data) ccd_data.uncertainty = noise with pytest.warns(AstropyDeprecationWarning): # The deprecation warning is expected and should be captured nccd_data = cosmicray_lacosmic(ccd_data, sigclip=5.9, pssl=0.0001) # check the number of cosmic rays detected # Note that to get this to succeed reliably meant tuning # both sigclip and the threshold assert nccd_data.mask.sum() == NCRAYS ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/test_gain.py0000644000076600000240000000412400000000000021340 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np import pytest import astropy.units as u from ccdproc.core import create_deviation, gain_correct, Keyword from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func # tests for gain @pytest.mark.parametrize('gain', [ 3.0, 3.0 * u.photon / u.adu, 3.0 * u.electron / u.adu, Keyword('gainval', unit=u.electron / u.adu)]) def test_linear_gain_correct(gain): ccd_data = ccd_data_func() # The data values should be positive, so the poisson noise calculation # works without throwing warnings ccd_data.data = np.absolute(ccd_data.data) ccd_data = create_deviation(ccd_data, readnoise=1.0 * u.adu) ccd_data.meta['gainval'] = 3.0 orig_data = ccd_data.data ccd = gain_correct(ccd_data, gain) if isinstance(gain, Keyword): gain = gain.value # convert to Quantity... try: gain_value = gain.value except AttributeError: gain_value = gain np.testing.assert_array_almost_equal_nulp(ccd.data, gain_value * orig_data) np.testing.assert_array_almost_equal_nulp( ccd.uncertainty.array, gain_value * ccd_data.uncertainty.array) if isinstance(gain, u.Quantity): assert ccd.unit == ccd_data.unit * gain.unit else: assert ccd.unit == ccd_data.unit # test gain with gain_unit def test_linear_gain_unit_keyword(): ccd_data = ccd_data_func() # The data values should be positive, so the poisson noise calculation # works without throwing warnings ccd_data.data = np.absolute(ccd_data.data) ccd_data = create_deviation(ccd_data, readnoise=1.0 * u.adu) orig_data = ccd_data.data gain = 3.0 gain_unit = u.electron / u.adu ccd = gain_correct(ccd_data, gain, gain_unit=gain_unit) np.testing.assert_array_almost_equal_nulp(ccd.data, gain * orig_data) np.testing.assert_array_almost_equal_nulp( ccd.uncertainty.array, gain * ccd_data.uncertainty.array) assert ccd.unit == ccd_data.unit * gain_unit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/ccdproc/tests/test_image_collection.py0000644000076600000240000013523000000000000023722 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import os from shutil import rmtree from tempfile import mkdtemp, TemporaryDirectory, NamedTemporaryFile from glob import iglob from pathlib import Path import logging import pytest import astropy.io.fits as fits from astropy.table import Table import numpy as np from astropy.tests.helper import catch_warnings from astropy.utils.data import get_pkg_data_filename from astropy.utils import minversion from astropy.utils.exceptions import AstropyUserWarning from astropy.io.fits.verify import VerifyWarning from astropy.nddata import CCDData from ccdproc.image_collection import ImageFileCollection _filters = [] _original_dir = '' _ASTROPY_LT_1_3 = not minversion("astropy", "1.3") def test_fits_summary(triage_setup): keywords = ['imagetyp', 'filter'] ic = ImageFileCollection(triage_setup.test_dir, keywords=keywords) summary = ic._fits_summary(header_keywords=keywords) assert len(summary['file']) == triage_setup.n_test['files'] for keyword in keywords: assert len(summary[keyword]) == triage_setup.n_test['files'] # Explicit conversion to array is needed to avoid astropy Table bug in # 0.2.4 no_filter_no_object_row = np.array(summary['file'] == 'no_filter_no_object_bias.fit') # There should be no filter keyword in the bias file assert summary['filter'][no_filter_no_object_row].mask class TestImageFileCollectionRepresentation: def test_repr_location(self, triage_setup): ic = ImageFileCollection(location=triage_setup.test_dir) assert repr(ic) == "ImageFileCollection(location={0!r})".format( triage_setup.test_dir) def test_repr_keywords(self, triage_setup): ic = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp']) ref = ("ImageFileCollection(location={0!r}, keywords=['imagetyp'])" .format(triage_setup.test_dir)) assert repr(ic) == ref def test_repr_globs(self, triage_setup): ic = ImageFileCollection( location=triage_setup.test_dir, glob_exclude="*no_filter*", glob_include="*object_light*") ref = ("ImageFileCollection(location={0!r}, " "glob_include='*object_light*', " "glob_exclude='*no_filter*')" .format(triage_setup.test_dir)) assert repr(ic) == ref def test_repr_files(self, triage_setup): ic = ImageFileCollection( location=triage_setup.test_dir, filenames=['no_filter_no_object_light.fit', 'no_filter_no_object_bias.fit']) ref = ("ImageFileCollection(location={0!r}, " "filenames=['no_filter_no_object_light.fit', " "'no_filter_no_object_bias.fit'])" .format(triage_setup.test_dir)) assert repr(ic) == ref def test_repr_ext(self, triage_setup): hdul = fits.HDUList([fits.PrimaryHDU(np.ones((10, 10))), fits.ImageHDU(np.ones((10, 10)))]) hdul.writeto(os.path.join(triage_setup.test_dir, 'mef.fits')) ic = ImageFileCollection( location=triage_setup.test_dir, filenames=['mef.fits'], ext=1) ref = ("ImageFileCollection(location={0!r}, " "filenames=['mef.fits'], " "ext=1)" .format(triage_setup.test_dir)) assert repr(ic) == ref # This should work mark all test methods as using the triage_setup # fixture, but it doesn't, so the fixture is given explicitly as an # argument to each method. # @pytest.mark.usefixtures("triage_setup") class TestImageFileCollection: def _setup_logger(self, path, level=logging.WARN): """ Set up file logger at the path. """ logger = logging.getLogger() logger.setLevel(level) logger.addHandler(logging.FileHandler(path)) return logger def test_filter_files(self, triage_setup): img_collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp', 'filter']) assert len(img_collection.files_filtered( imagetyp='bias')) == triage_setup.n_test['bias'] assert len(img_collection.files) == triage_setup.n_test['files'] assert ('filter' in img_collection.keywords) assert ('flying monkeys' not in img_collection.keywords) assert len(img_collection.values('imagetyp', unique=True)) == 2 def test_filter_files_whitespace_keys(self, triage_setup): hdr = fits.Header([('HIERARCH a b', 2)]) hdul = fits.HDUList([fits.PrimaryHDU(np.ones((10, 10)), header=hdr)]) hdul.writeto(os.path.join(triage_setup.test_dir, 'hdr_with_whitespace.fits')) ic = ImageFileCollection(location=triage_setup.test_dir) # Using a dictionary and unpacking it should work filtered = ic.files_filtered(**{'a b': 2}) assert len(filtered) == 1 assert 'hdr_with_whitespace.fits' in filtered # Also check it's working with generators: for _, filename in ic.data(a_b=2, replace_='_', return_fname=True): assert filename == 'hdr_with_whitespace.fits' def test_filter_files_with_str_on_nonstr_column(self, triage_setup): ic = ImageFileCollection(location=triage_setup.test_dir) # Filtering an integer column with a string filtered = ic.files_filtered(naxis='2') assert len(filtered) == 0 def test_filter_fz_files(self, triage_setup): fn = 'test.fits.fz' ic = ImageFileCollection(location=triage_setup.test_dir, filenames=fn) # Get a subset of files with a specific header value filtered = ic.files_filtered(exposure=15.0) assert len(filtered) == 1 def test_filtered_files_have_proper_path(self, triage_setup): ic = ImageFileCollection(location=triage_setup.test_dir, keywords='*') # Get a subset of the files. plain_biases = ic.files_filtered(imagetyp='bias') # Force a copy... plain_biases = list(plain_biases) # Same subset, but with full path. path_biases = ic.files_filtered(imagetyp='bias', include_path=True) for path_b, plain_b in zip(path_biases, plain_biases): # If the path munging has been done properly, this will succeed. assert os.path.basename(path_b) == plain_b def test_filenames_are_set_properly(self, triage_setup): fn = ['filter_no_object_bias.fit', 'filter_object_light_foo.fit'] img_collection = ImageFileCollection( location=triage_setup.test_dir, filenames=fn, keywords=['filter']) assert img_collection.files == fn img_collection.refresh() assert img_collection.files == fn fn = 'filter_no_object_bias.fit' img_collection = ImageFileCollection( location=triage_setup.test_dir, filenames=fn, keywords=['filter']) assert img_collection.files == [fn] def test_keywords_deleter(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords='*') assert ic.keywords != [] del ic.keywords assert ic.keywords == [] def test_files_with_compressed(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir) assert len(collection._fits_files_in_directory( compressed=True)) == triage_setup.n_test['files'] def test_files_with_no_compressed(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir) n_files_found = len( collection._fits_files_in_directory(compressed=False)) n_uncompressed = (triage_setup.n_test['files'] - triage_setup.n_test['compressed']) assert n_files_found == n_uncompressed def test_generator_full_path(self, triage_setup): collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp']) for path, file_name in zip(collection._paths(), collection.files): assert path == os.path.join(triage_setup.test_dir, file_name) def test_hdus(self, triage_setup): collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp']) n_hdus = 0 for hdu in collection.hdus(): assert isinstance(hdu, fits.PrimaryHDU) data = hdu.data # Must access the data to force scaling # pre-astropy 1.1 unsigned data was changed to float32 and BZERO # removed. In 1.1 and later, BZERO stays but the data type is # unsigned int. assert (('BZERO' not in hdu.header) or (data.dtype is np.dtype(np.uint16))) n_hdus += 1 assert n_hdus == triage_setup.n_test['files'] def test_hdus_masking(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp', 'exposure']) old_data = np.array(collection.summary) for hdu in collection.hdus(imagetyp='bias'): pass new_data = np.array(collection.summary) assert (new_data == old_data).all() @pytest.mark.parametrize('extension', ['TESTEXT', 1, ('TESTEXT', 1)]) def test_multiple_extensions(self, triage_setup, extension): ext1 = fits.PrimaryHDU() ext1.data = np.arange(1, 5) # It is important than the name used for this test extension # NOT be MASK or UNCERT because both are treated in a special # way by the FITS reader. test_ext_name = 'TESTEXT' ext2 = fits.ImageHDU(name=test_ext_name) ext2.data = np.arange(6, 10) hdulist = fits.hdu.hdulist.HDUList([ext1, ext2]) hdulist.writeto(os.path.join(triage_setup.test_dir, 'multi-extension.fits')) ic2 = ImageFileCollection( triage_setup.test_dir, keywords='*', filenames=['multi-extension.fits'], ext=extension) ic1 = ImageFileCollection( triage_setup.test_dir, keywords='*', filenames=['multi-extension.fits'], ext=0) assert ic1.ext == 0 assert ic2.ext == extension column2 = ic2.summary.colnames column1 = ic1.summary.colnames assert column1 != column2 list1 = [key.lower() for key in ext2.header] list2 = ic2.summary.colnames[1:] assert list1 == list2 ccd_kwargs = {'unit': 'adu'} for data, hdr, hdu, ccd in zip(ic2.data(), ic2.headers(), ic2.hdus(), ic2.ccds(ccd_kwargs)): np.testing.assert_array_equal(data, ext2.data) assert hdr == ext2.header # Now compare that the generators each give the same stuff np.testing.assert_array_equal(data, ccd.data) np.testing.assert_array_equal(data, hdu.data) assert hdr == hdu.header assert hdr == ccd.meta def test_headers(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) n_headers = 0 for header in collection.headers(): assert isinstance(header, fits.Header) assert ('bzero' in header) n_headers += 1 assert n_headers == triage_setup.n_test['files'] def test_headers_save_location(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) destination = mkdtemp() for header in collection.headers(save_location=destination): pass new_collection = ImageFileCollection(location=destination, keywords=['imagetyp']) basenames = lambda paths: set( [os.path.basename(file) for file in paths]) assert (len(basenames(collection._paths()) - basenames(new_collection._paths())) == 0) rmtree(destination) def test_headers_with_filter(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) cnt = 0 for header in collection.headers(imagetyp='light'): assert header['imagetyp'].lower() == 'light' cnt += 1 assert cnt == triage_setup.n_test['light'] def test_headers_with_multiple_filters(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) cnt = 0 for header in collection.headers(imagetyp='light', filter='R'): assert header['imagetyp'].lower() == 'light' assert header['filter'].lower() == 'r' cnt += 1 assert cnt == (triage_setup.n_test['light'] - triage_setup.n_test['missing_filter_value']) def test_headers_with_filter_wildcard(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) cnt = 0 for header in collection.headers(imagetyp='*'): cnt += 1 assert cnt == triage_setup.n_test['files'] def test_headers_with_filter_missing_keyword(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) for header in collection.headers(imagetyp='light', object=''): assert header['imagetyp'].lower() == 'light' with pytest.raises(KeyError): header['object'] def test_generator_headers_save_with_name(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) for header in collection.headers(save_with_name='_new'): assert isinstance(header, fits.Header) new_collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) assert (len(new_collection._paths()) == 2 * (triage_setup.n_test['files']) - triage_setup.n_test['compressed']) [os.remove(fil) for fil in iglob(triage_setup.test_dir + '/*_new*')] def test_generator_data(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) for img in collection.data(): assert isinstance(img, np.ndarray) def test_generator_ccds_without_unit(self, triage_setup): collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp']) with pytest.raises(ValueError): ccd = next(collection.ccds()) def test_generator_ccds(self, triage_setup): collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp']) ccd_kwargs = {'unit': 'adu'} for ccd in collection.ccds(ccd_kwargs=ccd_kwargs): assert isinstance(ccd, CCDData) def test_consecutive_fiilters(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp', 'filter', 'object']) no_files_match = collection.files_filtered(object='fdsafs') assert(len(no_files_match) == 0) some_files_should_match = collection.files_filtered(object=None, imagetyp='light') assert(len(some_files_should_match) == triage_setup.n_test['light']) def test_filter_does_not_not_permanently_change_file_mask(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) # Ensure all files are originally unmasked assert not collection.summary['file'].mask.any() # Generate list that will match NO files collection.files_filtered(imagetyp='foisajfoisaj') # If the code works, this should have no permanent effect assert not collection.summary['file'].mask.any() @pytest.mark.parametrize("new_keywords,collection_keys", [ (['imagetyp', 'object'], ['imagetyp', 'filter']), (['imagetyp'], ['imagetyp', 'filter'])]) def test_keyword_setting(self, new_keywords, collection_keys, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=collection_keys) tbl_orig = collection.summary collection.keywords = new_keywords tbl_new = collection.summary if set(new_keywords).issubset(collection_keys): # Should just delete columns without rebuilding table assert(tbl_orig is tbl_new) else: # We need new keywords so must rebuild assert(tbl_orig is not tbl_new) for key in new_keywords: assert(key in tbl_new.keys()) assert (tbl_orig['file'] == tbl_new['file']).all() assert (tbl_orig['imagetyp'] == tbl_new['imagetyp']).all() assert 'filter' not in tbl_new.keys() assert 'object' not in tbl_orig.keys() def test_keyword_setting_to_empty_list(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir) ic.keywords = [] assert ['file'] == ic.keywords def test_header_and_filename(self, triage_setup): collection = ImageFileCollection(location=triage_setup.test_dir, keywords=['imagetyp']) for header, fname in collection.headers(return_fname=True): assert (fname in collection.summary['file']) assert (isinstance(header, fits.Header)) def test_dir_with_no_fits_files(self, tmpdir): empty_dir = tmpdir.mkdtemp() some_file = empty_dir.join('some_file.txt') some_file.dump('words') with catch_warnings() as w: collection = ImageFileCollection(location=empty_dir.strpath, keywords=['imagetyp']) assert len(w) == 1 assert str(w[0].message) == "no FITS files in the collection." assert collection.summary is None for hdr in collection.headers(): # This statement should not be reached if there are no FITS files assert 0 def test_dir_with_no_keys(self, tmpdir): # This test should fail if the FITS files in the directory # are actually read. bad_dir = tmpdir.mkdtemp() not_really_fits = bad_dir.join('not_fits.fit') not_really_fits.dump('I am not really a FITS file') # Make sure an error will be generated if the FITS file is read with pytest.raises(IOError): fits.getheader(not_really_fits.strpath) log = tmpdir.join('tmp.log') self._setup_logger(log.strpath) _ = ImageFileCollection(location=bad_dir.strpath, keywords=[]) with open(log.strpath) as f: warnings = f.read() # ImageFileCollection will suppress the IOError but log a warning # so check that the log has no warnings in it. assert (len(warnings) == 0) def test_fits_summary_when_keywords_are_not_subset(self, triage_setup): """ Catch case when there is overlap between keyword list passed to the ImageFileCollection and to files_filtered but the latter is not a subset of the former. """ ic = ImageFileCollection(triage_setup.test_dir, keywords=['imagetyp', 'exposure']) n_files = len(ic.files) files_missing_this_key = ic.files_filtered(imagetyp='*', monkeys=None) assert(n_files > 0) assert(n_files == len(files_missing_this_key)) def test_duplicate_keywords_in_setting(self, triage_setup): keywords_in = ['imagetyp', 'a', 'a'] ic = ImageFileCollection(triage_setup.test_dir, keywords=keywords_in) for key in set(keywords_in): assert (key in ic.keywords) # One keyword gets added: file assert len(ic.keywords) < len(keywords_in) + 1 def test_keyword_includes_file(self, triage_setup): keywords_in = ['file', 'imagetyp'] ic = ImageFileCollection(triage_setup.test_dir, keywords=keywords_in) assert 'file' in ic.keywords file_keywords = [key for key in ic.keywords if key == 'file'] assert len(file_keywords) == 1 def test_setting_keywords_to_none(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords=['imagetyp']) ic.keywords = None assert ic.summary == [] def test_getting_value_for_keyword(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords=['imagetyp']) # Does it fail if the keyword is not in the summary? with pytest.raises(ValueError): ic.values('filter') # If I ask for unique values do I get them? values = ic.values('imagetyp', unique=True) assert values == list(set(ic.summary['imagetyp'])) assert len(values) < len(ic.summary['imagetyp']) # Does the list of non-unique values match the raw column? values = ic.values('imagetyp', unique=False) assert values == list(ic.summary['imagetyp']) # Does unique actually default to false? values2 = ic.values('imagetyp') assert values == values2 def test_collection_when_one_file_not_fits(self, triage_setup): not_fits = 'foo.fit' path_bad = os.path.join(triage_setup.test_dir, not_fits) # Create an empty file... with open(path_bad, 'w'): pass ic = ImageFileCollection(triage_setup.test_dir, keywords=['imagetyp']) assert not_fits not in ic.summary['file'] os.remove(path_bad) def test_data_type_mismatch_in_fits_keyword_values(self, triage_setup): # If one keyword has an unexpected type, do we notice? img = np.uint16(np.arange(100)) bad_filter = fits.PrimaryHDU(img) bad_filter.header['imagetyp'] = 'LIGHT' bad_filter.header['filter'] = 15.0 path_bad = os.path.join(triage_setup.test_dir, 'bad_filter.fit') bad_filter.writeto(path_bad) ic = ImageFileCollection(triage_setup.test_dir, keywords=['filter']) # dtype is object when there is a mix of types assert ic.summary['filter'].dtype == np.dtype('O') os.remove(path_bad) def test_filter_by_numerical_value(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords=['naxis']) should_be_zero = ic.files_filtered(naxis=2) assert len(should_be_zero) == 0 should_not_be_zero = ic.files_filtered(naxis=1) assert len(should_not_be_zero) == triage_setup.n_test['files'] def test_files_filtered_with_full_path(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords=['naxis']) files = ic.files_filtered(naxis=1, include_path=True) for f in files: assert f.startswith(triage_setup.test_dir) def test_unknown_generator_type_raises_error(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords=['naxis']) with pytest.raises(ValueError): for foo in ic._generator('not a real generator'): pass def test_setting_write_location_to_bad_dest_raises_error(self, tmpdir, triage_setup): new_tmp = tmpdir.mkdtemp() bad_directory = new_tmp.join('foo') ic = ImageFileCollection(triage_setup.test_dir, keywords=['naxis']) with pytest.raises(IOError): for hdr in ic.headers(save_location=bad_directory.strpath): pass def test_no_fits_files_in_collection(self): with catch_warnings(AstropyUserWarning) as warning_lines: assert "no FITS files in the collection." def test_initialization_with_no_keywords(self, triage_setup): # This test is primarily historical -- the old default for # keywords was an empty list (it is now the wildcard '*'). ic = ImageFileCollection(location=triage_setup.test_dir, keywords=[]) # Iteration below failed before bugfix... execs = 0 for h in ic.headers(): execs += 1 assert not execs def check_all_keywords_in_collection(self, image_collection): lower_case_columns = [c.lower() for c in image_collection.summary.colnames] for h in image_collection.headers(): for k in h: assert k.lower() in lower_case_columns def test_tabulate_all_keywords(self, triage_setup): ic = ImageFileCollection(location=triage_setup.test_dir, keywords='*') self.check_all_keywords_in_collection(ic) def test_summary_table_is_always_masked(self, triage_setup): # First, try grabbing all of the keywords ic = ImageFileCollection(location=triage_setup.test_dir, keywords='*') assert ic.summary.masked # Now, try keywords that every file will have ic.keywords = ['bitpix'] assert ic.summary.masked # What about keywords that include some that will surely be missing? ic.keywords = ['bitpix', 'dsafui'] assert ic.summary.masked def test_case_of_keywords_respected(self, triage_setup): keywords_in = ['BitPix', 'instrume', 'NAXIS'] ic = ImageFileCollection(location=triage_setup.test_dir, keywords=keywords_in) for key in keywords_in: assert key in ic.summary.colnames def test_grabbing_all_keywords_and_specific_keywords(self, triage_setup): keyword_not_in_headers = 'OIdn89!@' ic = ImageFileCollection(triage_setup.test_dir, keywords=['*', keyword_not_in_headers]) assert keyword_not_in_headers in ic.summary.colnames self.check_all_keywords_in_collection(ic) def test_grabbing_all_keywords_excludes_empty_key(self, triage_setup): # This test needs a file with a blank keyword in it to ensure # that case is handled correctly. blank_keyword = fits.PrimaryHDU() blank_keyword.data = np.zeros((100, 100)) blank_keyword.header[''] = 'blank' blank_keyword.writeto(os.path.join(triage_setup.test_dir, 'blank.fits')) ic = ImageFileCollection(triage_setup.test_dir, keywords='*') assert 'col0' not in ic.summary.colnames @pytest.mark.skipif("os.environ.get('APPVEYOR') or os.sys.platform == 'win32'", reason="fails on Windows because of file permissions.") def test_header_with_long_history_roundtrips_to_disk(self, triage_setup): # I tried combing several history comments into one table entry with # '\n'.join(history), which resulted in a table that couldn't # round trip to disk because on read the newline character was # interpreted as...a new line! This test is a check against future # foolishness. from astropy.table import Table img = np.uint16(np.arange(100)) long_history = fits.PrimaryHDU(img) long_history.header['imagetyp'] = 'BIAS' long_history.header['history'] = 'Something happened' long_history.header['history'] = 'Then something else happened' long_history.header['history'] = 'And then something odd happened' path_history = os.path.join(triage_setup.test_dir, 'long_history.fit') long_history.writeto(path_history) ic = ImageFileCollection(triage_setup.test_dir, keywords='*') with NamedTemporaryFile() as test_table: ic.summary.write(test_table.name, format='ascii.csv', overwrite=True) table_disk = Table.read(test_table.name, format='ascii.csv') assert len(table_disk) == len(ic.summary) @pytest.mark.skipif("os.environ.get('APPVEYOR') or os.sys.platform == 'win32'", reason="fails on Windows because file " "overwriting fails") def test_refresh_method_sees_added_keywords(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords='*') # Add a keyword I know isn't already in the header to each file. not_in_header = 'BARKARK' for h in ic.headers(overwrite=True): h[not_in_header] = True assert not_in_header not in ic.summary.colnames ic.refresh() # After refreshing the odd keyword should be present. assert not_in_header.lower() in ic.summary.colnames def test_refresh_method_sees_added_files(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir, keywords='*') # Compressed files don't get copied. Not sure why... original_len = len(ic.summary) - triage_setup.n_test['compressed'] # Generate additional files in this directory for h in ic.headers(save_with_name="_foo"): pass ic.refresh() new_len = len(ic.summary) - triage_setup.n_test['compressed'] assert new_len == 2 * original_len def test_keyword_order_is_preserved(self, triage_setup): keywords = ['imagetyp', 'exposure', 'filter'] ic = ImageFileCollection(triage_setup.test_dir, keywords=keywords) assert ic.keywords == ['file'] + keywords def test_sorting(self, triage_setup): collection = ImageFileCollection( location=triage_setup.test_dir, keywords=['imagetyp', 'filter', 'object']) all_elements = [] for hdu, fname in collection.hdus(return_fname=True): all_elements.append((str(hdu.header), fname)) # Now sort collection.sort(keys=['imagetyp', 'object']) # and check it's all still right for hdu, fname in collection.hdus(return_fname=True): assert((str(hdu.header), fname) in all_elements) for i in range(len(collection.summary)): assert(collection.summary['file'][i] == collection.files[i]) @pytest.mark.skipif( _ASTROPY_LT_1_3, reason="It seems to fail with a TypeError there but because of " "different reasons (something to do with NumPy).") def test_sorting_without_key_fails(self, triage_setup): ic = ImageFileCollection(location=triage_setup.test_dir) with pytest.raises(ValueError): ic.sort(keys=None) def test_duplicate_keywords(self, triage_setup): # Make sure duplicated keywords don't make the imagefilecollection # fail. hdu = fits.PrimaryHDU() hdu.data = np.zeros((5, 5)) hdu.header['stupid'] = 'fun' hdu.header.append(('stupid', 'nofun')) hdu.writeto(os.path.join(triage_setup.test_dir, 'duplicated.fits')) with catch_warnings(UserWarning) as w: ic = ImageFileCollection(triage_setup.test_dir, keywords='*') assert len(w) == 1 assert 'stupid' in str(w[0].message) assert 'stupid' in ic.summary.colnames assert 'fun' in ic.summary['stupid'] assert 'nofun' not in ic.summary['stupid'] def test_ccds_generator_in_different_directory(self, triage_setup, tmpdir): """ Regression test for https://github.com/astropy/ccdproc/issues/421 in which the ccds generator fails if the current working directory is not the location of the ImageFileCollection. """ coll = ImageFileCollection(triage_setup.test_dir) # The temporary directory below should be different that the collection # location. os.chdir(tmpdir.strpath) # Let's make sure it is. assert not os.path.samefile(os.getcwd(), coll.location) # This generated an IOError before the issue was fixed. for _ in coll.ccds(ccd_kwargs={'unit': 'adu'}): pass def test_ccds_generator_does_not_support_overwrite(self, triage_setup): """ CCDData objects have several attributes that make it hard to reliably support overwriting. For example in what extension should mask, uncertainty be written? Also CCDData doesn't explicitly support in-place operations so it's to easy to create a new CCDData object inadvertantly and all modifications might be lost. """ ic = ImageFileCollection(triage_setup.test_dir) with pytest.raises(NotImplementedError): ic.ccds(overwrite=True) with pytest.raises(NotImplementedError): ic.ccds(clobber=True) def test_glob_matching(self, triage_setup): # We'll create two files with strange names to test glob # includes / excludes one = fits.PrimaryHDU() one.data = np.zeros((5, 5)) one.header[''] = 'whatever' one.writeto(os.path.join(triage_setup.test_dir, 'SPAM_stuff.fits')) one.writeto(os.path.join(triage_setup.test_dir, 'SPAM_other_stuff.fits')) coll = ImageFileCollection(triage_setup.test_dir, glob_include='SPAM*') assert len(coll.files) == 2 coll = ImageFileCollection(triage_setup.test_dir, glob_include='SPAM*', glob_exclude='*other*') assert len(coll.files) == 1 # The glob attributes are readonly, so setting them raises an Exception. with pytest.raises(AttributeError): coll.glob_exclude = '*stuff*' with pytest.raises(AttributeError): coll.glob_include = '*stuff*' def test_that_test_files_have_expected_properties(self, triage_setup): expected_name = \ get_pkg_data_filename('data/expected_ifc_file_properties.csv') expected = Table.read(expected_name) # Make the comparison more reliable by sorting expected.sort('file') ic = ImageFileCollection(triage_setup.test_dir) actual = ic.summary # Write the actual IFC summary out to disk to turn bool into strings of # "True" and "False", and any other non-essential differences between # the tables. tmp_file = 'actual.csv' actual.write(tmp_file) actual = Table.read(tmp_file) # Make the comparison more reliable by sorting actual.sort('file') assert len(ic.summary) == len(expected) for column in expected.colnames: assert np.all(actual[column] == expected[column]) def test_image_collection_with_no_location(self, triage_setup): # Test for a feature requested in # # https://github.com/astropy/ccdproc/issues/374 # # and fix the bug reported in # # https://github.com/astropy/ccdproc/issues/662 # # Create a collection from a list of file names (which can include # path as needed) source_path = Path(triage_setup.test_dir) # Put the first three files in source_path into temp_path below # then create the image collection out of the three in temp_path and # the rest in source_path. source_files = [p for p in source_path.iterdir()] move_to_temp = source_files[:3] keep_in_source = source_files[3:] with TemporaryDirectory() as td: temp_dir = Path(td) file_paths = [] for source in move_to_temp: temp_path = temp_dir / source.name temp_path.write_bytes(source.read_bytes()) file_paths.append(str(temp_path)) file_paths.extend(str(p) for p in keep_in_source) # Move up a level to make sure we are not accidentally # pulling in files from the current working directory, # which includes everythin in source. os.chdir('..') ic = ImageFileCollection(filenames=file_paths) assert len(ic.summary) == len(file_paths) expected_name = \ get_pkg_data_filename('data/expected_ifc_file_properties.csv') expected = Table.read(expected_name) # Make the comparison more reliable by sorting expected.sort('file') actual = ic.summary # Write the actual IFC summary out to disk to turn bool into strings # of"True" and "False", and any other non-essential differences # between the tables. tmp_file = 'actual.csv' actual.write(tmp_file) actual = Table.read(tmp_file) # Make the comparison more reliable by sorting...but the actual # in this case includes paths, so we really want to sort by the # base name of the file. bases = np.array([Path(f).name for f in actual['file']]) sort_order = np.argsort(bases) actual = actual[sort_order] bases = bases[sort_order] assert all(Path(f).exists() for f in actual['file']) for column in expected.colnames: if column == 'file': assert np.all(bases == expected[column]) else: assert np.all(actual[column] == expected[column]) # Set comparisons don't care about order :) # Check several of the ways we can get file names from the # collection and ensure all of them include the path. assert set(file_paths) == set(ic.summary['file']) assert set(file_paths) == set(ic.files) assert set(file_paths) == set(ic.files_filtered(include_path=True)) # Spot check a couple of dtypes as a test for # https://github.com/astropy/ccdproc/issues/662 assert ic.summary['extend'].dtype == 'bool' # Of course, default dtypes on Windows are different. So instead # of comparing to something sensible like int64, compare to the # default int dtype. assert ic.summary['naxis1'].dtype == np.array([5]).dtype # and the default float dtype assert ic.summary['exposure'].dtype == np.array([5.0]).dtype expected_heads = (actual['imagetyp'] == 'LIGHT').sum() n_heads = 0 # Try one of the generators for h in ic.headers(imagetyp='light'): assert h['imagetyp'].lower() == 'light' n_heads += 1 assert n_heads == expected_heads def test_force_detect_fits_files_finds_fits_files(self, triage_setup): # Tests for new feature # # https://github.com/astropy/ccdproc/issues/620 # # which supports adding all of the FITS files in a location based on # their *contents* instead of their *extension*. # Grab files from the default collection and make a copy with a new name # (and with no fits-like extension) # # Making a copy of *every* file means we can just double the expected # number of files as part of the tests. path = Path(triage_setup.test_dir) for idx, p in enumerate(path.iterdir()): new_name = 'no_extension{}'.format(idx) new_path = path / new_name new_path.write_bytes(p.read_bytes()) ic = ImageFileCollection(location=str(path), find_fits_by_reading=True) # Compressed files won't be automagically detected by reading the # first few bytes. expected_number = (2 * triage_setup.n_test['files'] - triage_setup.n_test['compressed']) assert len(ic.summary) == expected_number n_bias = (ic.summary['imagetyp'] == 'BIAS').sum() assert n_bias == 2 * triage_setup.n_test['bias'] # Only one file in the original set of test files has exposure time # 15, so there should be two now. assert len(ic.files_filtered(exposure=15.0)) == 2 # Try one of the generators expected_heads = (2 * triage_setup.n_test['light'] - triage_setup.n_test['compressed']) n_heads = 0 for h in ic.headers(imagetyp='light'): assert h['imagetyp'].lower() == 'light' n_heads += 1 assert n_heads == expected_heads def test_less_strict_verify_option(self, triage_setup): # Tests for feature request # # https://github.com/astropy/ccdproc/issues/607 # # which would allow reading of otherwise invalid FITS headers. bad_header = """ NAXIS1 = 10 / length of data axis 1 NAXIS2 = 10 / length of data axis 2 TESTVERI= '2017/02/13-16:51:38 / Test VerifyWarning """ with catch_warnings(VerifyWarning) as warning_lines: testh = fits.Header.fromstring(bad_header) print(testh) testfits = fits.PrimaryHDU(data=np.ones((10, 10)), header=testh) path = Path(triage_setup.test_dir) bad_fits_name = 'test_warnA.fits' testfits.writeto(path / bad_fits_name, output_verify='warn', overwrite=True) ic = ImageFileCollection(location=str(path)) print(ic.summary.colnames) assert bad_fits_name in ic.files # Turns out this sample header is so messed up that TESTVERI does not # end up as a keyword. assert 'TESTVERI' not in ic.summary.colnames # This does end up as a key some how *shrug*. assert '17/02/13' in ic.summary.colnames # Try making the summary as in the original bug report ic = ImageFileCollection(location=str(path), glob_include='*warnA*') def test_type_of_empty_collection(self, triage_setup): # Test for implementation of the suggestion in # # https://github.com/astropy/ccdproc/issues/601 # # in which an empty collection with no keys has but with files # returns a summary table with one column, but an empty collection # with no keys and no files returns None. # Make a dummy keyword that we then delete. with catch_warnings(AstropyUserWarning) as warning_lines: ic = ImageFileCollection(triage_setup.test_dir, keywords=['fafa']) assert "no FITS files in the collection." ic.keywords = [] assert set(ic.summary.colnames) == set(['file']) # Remove all of the fits files path = Path(triage_setup.test_dir) for p in path.iterdir(): p.unlink() # Now the summary should be none ic = ImageFileCollection(triage_setup.test_dir) assert ic.summary is None assert ic.keywords == [] def test_regex_match_for_search(self, triage_setup): # Test regex matching in searches ic = ImageFileCollection(triage_setup.test_dir) files = ic.files_filtered(regex_match=True, imagetyp='b.*s') assert len(files) == triage_setup.n_test['bias'] # This should return all of the files in the test set all_files = ic.files_filtered(regex_match=True, imagetyp='bias|light') assert len(all_files) == triage_setup.n_test['files'] # Add a column with more interesting content and see whether we # match that. ic.summary['match_me'] = [ 'hello', 'goodbye', 'bye', 'byte', 'good bye hello', 'dog' ] hello_anywhere = ic.files_filtered(regex_match=True, match_me='hello') assert len(hello_anywhere) == 2 hello_start = ic.files_filtered(regex_match=True, match_me='^hello') assert len(hello_start) == 1 # Is it really a case-insensitive match? hello_start = ic.files_filtered(regex_match=True, match_me='^HeLlo') assert len(hello_start) == 1 any_bye = ic.files_filtered(regex_match=True, match_me='by.*e') assert len(any_bye) == 4 def test_generator_with_regex(self, triage_setup): ic = ImageFileCollection(triage_setup.test_dir) n_light = 0 for h in ic.headers(regex_match=True, imagetyp='li.*t'): assert h['imagetyp'].lower() == 'light' n_light += 1 assert n_light == triage_setup.n_test['light'] def test_make_collection_by_filtering(self, triage_setup): # Test for implementation of feature at # # https://github.com/astropy/ccdproc/issues/596 # # which adds the ability to create a new collection by filtering # an existing ImageFileCollection. ic = ImageFileCollection(location=triage_setup.test_dir) new_ic = ic.filter(imagetyp='light') assert len(new_ic.summary) == triage_setup.n_test['light'] for header in new_ic.headers(): assert header['imagetyp'].lower() == 'light' def test_filtered_collection_with_no_files(self, triage_setup): ifc = ImageFileCollection(triage_setup.test_dir) with pytest.warns(AstropyUserWarning, match="no FITS files"): _ = ifc.filter(object='really fake object') def test_filter_on_regex_escape_characters(self, triage_setup): # Test for implementation of bugfix at # # https://github.com/astropy/ccdproc/issues/770 # # which escapes regex special characters in keyword values used for # filtering a collection, when option `regex_match=False`. # For a few different special characters, make test files with FILTER # keyword containing these special_kwds = ['CO+', 'GG420 (1)', 'V|R|I', 'O[III]', 'NaD^2'] for i,kw in enumerate(special_kwds,1): hdu = fits.PrimaryHDU() hdu.data = np.zeros((5, 5)) hdu.header['REGEX_FL'] = kw hdu.writeto(os.path.join(triage_setup.test_dir, 'regex_special_{:d}.fits'.format(i))) ic = ImageFileCollection(triage_setup.test_dir) for kw in special_kwds: new_ic = ic.filter(regex_fl=kw) assert len(new_ic.files) == 1 assert kw in new_ic.summary['regex_fl'] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/test_keyword.py0000644000076600000240000000417200000000000022111 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import pytest from astropy import units as u from astropy.io import fits from ccdproc.core import Keyword def test_keyword_init(): key_name = 'some_key' key = Keyword(key_name, unit=u.second) assert key.name == key_name assert key.unit == u.second def test_keyword_properties_read_only(): key = Keyword('observer') with pytest.raises(AttributeError): key.name = 'error' with pytest.raises(AttributeError): key.unit = u.hour unit = u.second numerical_value = 30 # The variable "expected" below is # True if the expected result is key.value == numerical_value * key.unit # Name of an error if an error is expected # A string if the expected value is a string @pytest.mark.parametrize('value,unit,expected', [ (numerical_value, unit, True), (numerical_value, None, ValueError), (numerical_value * unit, None, True), (numerical_value * unit, unit, True), (numerical_value * unit, u.km, True), ('some string', None, 'some string'), ('no strings with unit', unit, ValueError) ]) def test_value_setting(value, unit, expected): name = 'exposure' # Setting at initialization time with try: expected_is_error = issubclass(expected, Exception) except TypeError: expected_is_error = False if expected_is_error: with pytest.raises(expected): key = Keyword(name, unit=unit, value=value) else: key = Keyword(name, unit=unit, value=value) if isinstance(expected, str): assert key.value == expected else: assert key.value == numerical_value * key.unit def test_keyword_value_from_header(): name = 'exposure' numerical_value = 30 unit = u.second h = fits.Header() h[name] = numerical_value key = Keyword(name, unit=unit) assert key.value_from(h) == numerical_value * unit assert key.value == numerical_value * unit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1637332708.0 ccdproc-2.3.0/ccdproc/tests/test_memory_use.py0000644000076600000240000000525300000000000022612 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from sys import platform import numpy as np import pytest try: from ccdproc.tests.run_for_memory_profile import run_memory_profile, generate_fits_files, TMPPATH except ImportError: memory_profile_present = False else: memory_profile_present = True image_size = 2000 # Square image, so 4000 x 4000 num_files = 10 def setup_module(): if memory_profile_present: generate_fits_files(num_files, size=image_size) def teardown_module(): if memory_profile_present: for fil in TMPPATH.glob('*.fit'): fil.unlink() @pytest.mark.skipif(not platform.startswith('linux'), reason='memory tests only work on linux') @pytest.mark.skipif(not memory_profile_present, reason='memory_profiler not installed') @pytest.mark.parametrize('combine_method', ['average', 'sum', 'median']) def test_memory_use_in_combine(combine_method): # This is essentially a regression test for # https://github.com/astropy/ccdproc/issues/638 # sampling_interval = 0.01 # sec memory_limit = 500000000 # bytes, roughly 0.5GB mem_use, _ = run_memory_profile(num_files, sampling_interval, size=image_size, memory_limit=memory_limit, combine_method=combine_method) mem_use = np.array(mem_use) # We do not expect memory use to be strictly less than memory_limit # throughout the combination. The factor below allows for that. # It may need to be raised in the future...that is fine, there is a # separate test for average memory use. overhead_allowance = 1.75 # memory_profile reports in MB (no, this is not the correct conversion) memory_limit_mb = memory_limit / 1e6 # Checks for TOO MUCH MEMORY USED # Check peak memory use assert np.max(mem_use) <= overhead_allowance * memory_limit_mb # Also check average, which gets no allowance assert np.mean(mem_use) < memory_limit_mb # Checks for NOT ENOUGH MEMORY USED; if these fail it means that # memory_factor in the combine function should perhaps be modified # DROPPED THESE TESTS -- it isn't clear they were actually useful and # in any event the important thing to guarantee is that we don't # exceed the memory limit. # If the peak is coming in under the limit something need to be fixed # assert np.max(mem_use) >= 0.95 * memory_limit_mb # If the average is really low perhaps we should look at reducing peak # usage. Nothing special, really, about the factor 0.4 below. # assert np.mean(mem_use[mem_use > 0]) > 0.4 * memory_limit_mb ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/test_rebin.py0000644000076600000240000000514300000000000021523 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np import pytest from astropy.nddata import StdDevUncertainty from astropy.tests.helper import catch_warnings from astropy.utils.exceptions import AstropyDeprecationWarning from ccdproc.core import rebin from ccdproc.tests.pytest_fixtures import ccd_data as ccd_data_func # test rebinning ndarray def test_rebin_ndarray(): with pytest.raises(TypeError), catch_warnings(AstropyDeprecationWarning): rebin(1, (5, 5)) # test rebinning dimensions def test_rebin_dimensions(): ccd_data = ccd_data_func(data_size=10) with pytest.raises(ValueError), catch_warnings(AstropyDeprecationWarning): rebin(ccd_data.data, (5,)) # test rebinning dimensions def test_rebin_ccddata_dimensions(): ccd_data = ccd_data_func(data_size=10) with pytest.raises(ValueError), catch_warnings(AstropyDeprecationWarning): rebin(ccd_data, (5,)) # test rebinning works def test_rebin_larger(): ccd_data = ccd_data_func(data_size=10) a = ccd_data.data with catch_warnings(AstropyDeprecationWarning) as w: b = rebin(a, (20, 20)) assert len(w) >= 1 assert b.shape == (20, 20) np.testing.assert_almost_equal(b.sum(), 4 * a.sum()) # test rebinning is invariant def test_rebin_smaller(): ccd_data = ccd_data_func(data_size=10) a = ccd_data.data with catch_warnings(AstropyDeprecationWarning) as w: b = rebin(a, (20, 20)) c = rebin(b, (10, 10)) assert len(w) >= 1 assert c.shape == (10, 10) assert (c - a).sum() == 0 # test rebinning with ccddata object @pytest.mark.parametrize('mask_data, uncertainty', [ (False, False), (True, True)]) def test_rebin_ccddata(mask_data, uncertainty): ccd_data = ccd_data_func(data_size=10) if mask_data: ccd_data.mask = np.zeros_like(ccd_data) if uncertainty: err = np.random.normal(size=ccd_data.shape) ccd_data.uncertainty = StdDevUncertainty(err) with catch_warnings(AstropyDeprecationWarning) as w: b = rebin(ccd_data, (20, 20)) assert len(w) >= 1 assert b.shape == (20, 20) if mask_data: assert b.mask.shape == (20, 20) if uncertainty: assert b.uncertainty.array.shape == (20, 20) def test_rebin_does_not_change_input(): ccd_data = ccd_data_func() original = ccd_data.copy() with catch_warnings(AstropyDeprecationWarning) as w: _ = rebin(ccd_data, (20, 20)) assert len(w) >= 1 np.testing.assert_array_equal(original.data, ccd_data.data) assert original.unit == ccd_data.unit ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1615821771.0 ccdproc-2.3.0/ccdproc/tests/test_wrapped_external_funcs.py0000644000076600000240000000502500000000000025165 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np from astropy.nddata import StdDevUncertainty, CCDData from scipy import ndimage from ccdproc import core def test_medianfilter_correct(): ccd = CCDData([[2, 6, 6, 1, 7, 2, 4, 5, 9, 1], [10, 10, 9, 0, 2, 10, 8, 3, 9, 7], [2, 4, 0, 4, 4, 10, 0, 5, 6, 5], [7, 10, 8, 7, 7, 0, 5, 3, 5, 9], [9, 6, 3, 8, 6, 9, 2, 8, 10, 10], [6, 5, 1, 7, 8, 0, 8, 2, 9, 3], [0, 6, 0, 6, 3, 10, 8, 9, 7, 8], [5, 8, 3, 2, 3, 0, 2, 0, 3, 5], [9, 6, 3, 7, 1, 0, 5, 4, 8, 3], [5, 6, 9, 9, 0, 4, 9, 1, 7, 8]], unit='adu') result = core.median_filter(ccd, 3) assert isinstance(result, CCDData) assert np.all(result.data == [[6, 6, 6, 6, 2, 4, 4, 5, 5, 7], [4, 6, 4, 4, 4, 4, 5, 5, 5, 6], [7, 8, 7, 4, 4, 5, 5, 5, 5, 7], [7, 6, 6, 6, 7, 5, 5, 5, 6, 9], [7, 6, 7, 7, 7, 6, 3, 5, 8, 9], [6, 5, 6, 6, 7, 8, 8, 8, 8, 8], [5, 5, 5, 3, 3, 3, 2, 7, 5, 5], [6, 5, 6, 3, 3, 3, 4, 5, 5, 5], [6, 6, 6, 3, 2, 2, 2, 4, 4, 5], [6, 6, 7, 7, 4, 4, 4, 7, 7, 8]]) assert result.unit == 'adu' assert all(getattr(result, attr) is None for attr in ['mask', 'uncertainty', 'wcs', 'flags']) # The following test could be deleted if log_to_metadata is also applied. assert not result.meta def test_medianfilter_unusued(): ccd = CCDData(np.ones((3, 3)), unit='adu', mask=np.ones((3, 3)), uncertainty=StdDevUncertainty(np.ones((3, 3))), flags=np.ones((3, 3))) result = core.median_filter(ccd, 3) assert isinstance(result, CCDData) assert result.unit == 'adu' assert all(getattr(result, attr) is None for attr in ['mask', 'uncertainty', 'wcs', 'flags']) # The following test could be deleted if log_to_metadata is also applied. assert not result.meta def test_medianfilter_ndarray(): arr = np.random.random((5, 5)) result = core.median_filter(arr, 3) reference = ndimage.median_filter(arr, 3) # It's a wrapped function so we can use the equal comparison. np.testing.assert_array_equal(result, reference) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1352677 ccdproc-2.3.0/ccdproc/utils/0000755000076600000240000000000000000000000017006 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/ccdproc/utils/__init__.py0000644000076600000240000000030700000000000021117 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # This sub-module is destined for common non-package specific utility # functions that will ultimately be merged into `astropy.utils` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567734309.0 ccdproc-2.3.0/ccdproc/utils/sample_directory.py0000644000076600000240000000556400000000000022737 0ustar00mattcraigstaff00000000000000import gzip from tempfile import mkdtemp import os import numpy as np from astropy.io import fits def _make_file_for_testing(file_name='', **kwd): img = np.uint16(np.arange(100)) hdu = fits.PrimaryHDU(img) for k, v in kwd.items(): hdu.header[k] = v hdu.writeto(file_name) def directory_for_testing(): """ Set up directory with these contents: One file with imagetyp BIAS. It has an the keyword EXPOSURE in the header, but no others beyond IMAGETYP and the bare minimum created with the FITS file. File name(s) ------------ no_filter_no_object_bias.fit Five (5) files with imagetyp LIGHT, including two compressed files. + One file for each compression type, currently .gz and .fz. + ALL of the files will have the keyword EXPOSURE in the header. + Only ONE of them will have the value EXPOSURE=15.0. + All of the files EXCEPT ONE will have the keyword FILTER with the value 'R'. + NONE of the files have the keyword OBJECT File names ---------- test.fits.fz filter_no_object_light.fit filter_object_light.fit.gz filter_object_light.fit no_filter_no_object_light.fit <---- this one has no filter """ n_test = { 'files': 6, 'missing_filter_value': 1, 'bias': 1, 'compressed': 2, 'light': 5 } test_dir = mkdtemp() # Directory is reset on teardown. original_dir = os.getcwd() os.chdir(test_dir) _make_file_for_testing(file_name='no_filter_no_object_bias.fit', imagetyp='BIAS', EXPOSURE=0.0) _make_file_for_testing(file_name='no_filter_no_object_light.fit', imagetyp='LIGHT', EXPOSURE=1.0) _make_file_for_testing(file_name='filter_no_object_light.fit', imagetyp='LIGHT', EXPOSURE=1.0, filter='R') _make_file_for_testing(file_name='filter_object_light.fit', imagetyp='LIGHT', EXPOSURE=1.0, filter='R') with open('filter_object_light.fit', 'rb') as f_in: with gzip.open('filter_object_light.fit.gz', 'wb') as f_out: f_out.write(f_in.read()) # filter_object.writeto('filter_object_RA_keyword_light.fit') _make_file_for_testing(file_name='test.fits.fz', imagetyp='LIGHT', EXPOSURE=15.0, filter='R') os.chdir(original_dir) return n_test, test_dir def sample_directory_with_files(): """ Returns the path to the small sample directory used in the tests of ``ImageFileCollection``. Primarily intended for use in the doctests. """ n_test, tmpdir = directory_for_testing() return tmpdir ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/ccdproc/utils/slices.py0000644000076600000240000001011200000000000020635 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Define utility functions and classes for ccdproc """ __all__ = ["slice_from_string"] def slice_from_string(string, fits_convention=False): """ Convert a string to a tuple of slices. Parameters ---------- string : str A string that can be converted to a slice. fits_convention : bool, optional If True, assume the input string follows the FITS convention for indexing: the indexing is one-based (not zero-based) and the first axis is that which changes most rapidly as the index increases. Returns ------- slice_tuple : tuple of slice objects A tuple able to be used to index a numpy.array Notes ----- The ``string`` argument can be anything that would work as a valid way to slice an array in Numpy. It must be enclosed in matching brackets; all spaces are stripped from the string before processing. Examples -------- >>> import numpy as np >>> arr1d = np.arange(5) >>> a_slice = slice_from_string('[2:5]') >>> arr1d[a_slice] array([2, 3, 4]) >>> a_slice = slice_from_string('[ : : -2] ') >>> arr1d[a_slice] array([4, 2, 0]) >>> arr2d = np.array([arr1d, arr1d + 5, arr1d + 10]) >>> arr2d array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) >>> a_slice = slice_from_string('[1:-1, 0:4:2]') >>> arr2d[a_slice] array([[5, 7]]) >>> a_slice = slice_from_string('[0:2,0:3]') >>> arr2d[a_slice] array([[0, 1, 2], [5, 6, 7]]) """ no_space = string.replace(' ', '') if not no_space: return () if not (no_space.startswith('[') and no_space.endswith(']')): raise ValueError('Slice string must be enclosed in square brackets.') no_space = no_space.strip('[]') if fits_convention: # Special cases first # Flip dimension, with step no_space = no_space.replace('-*:', '::-') # Flip dimension no_space = no_space.replace('-*', '::-1') # Normal wildcard no_space = no_space.replace('*', ':') string_slices = no_space.split(',') slices = [] for string_slice in string_slices: slice_args = [int(arg) if arg else None for arg in string_slice.split(':')] a_slice = slice(*slice_args) slices.append(a_slice) if fits_convention: slices = _defitsify_slice(slices) return tuple(slices) def _defitsify_slice(slices): """ Convert a FITS-style slice specification into a python slice. This means two things: + Subtract 1 from starting index because in the FITS specification arrays are one-based. + Do **not** subtract 1 from the ending index because the python convention for a slice is for the last value to be one less than the stop value. In other words, this subtraction is already built into python. + Reverse the order of the slices, because the FITS specification dictates that the first axis is the one along which the index varies most rapidly (aka FORTRAN order). """ python_slice = [] for a_slice in slices[::-1]: new_start = a_slice.start - 1 if a_slice.start is not None else None if new_start is not None and new_start < 0: raise ValueError("Smallest permissible FITS index is 1") if a_slice.stop is not None and a_slice.stop < 0: raise ValueError("Negative final index not allowed for FITS slice") new_slice = slice(new_start, a_slice.stop, a_slice.step) if (a_slice.start is not None and a_slice.stop is not None and a_slice.start > a_slice.stop): # FITS use a positive step index when dimension are inverted new_step = -1 if a_slice.step is None else -a_slice.step # Special case to prevent -1 as slice stop value new_stop = None if a_slice.stop == 1 else a_slice.stop-2 new_slice = slice(new_start, new_stop, new_step) python_slice.append(new_slice) return python_slice ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1360037 ccdproc-2.3.0/ccdproc/utils/tests/0000755000076600000240000000000000000000000020150 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/ccdproc/utils/tests/__init__.py0000644000076600000240000000000000000000000022247 0ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/ccdproc/utils/tests/test_slices.py0000644000076600000240000001036400000000000023047 0ustar00mattcraigstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import numpy as np import pytest from ..slices import slice_from_string # none of these are properly enclosed in brackets; is an error raised? @pytest.mark.parametrize('arg', ['1:2', '[1:2', '1:2]']) def test_slice_from_string_needs_enclosing_brackets(arg): with pytest.raises(ValueError): slice_from_string(arg) @pytest.mark.parametrize('start,stop,step', [ (None, None, -1), (5, 10, None), (None, 25, None), (2, 30, 3), (30, None, -2), (None, None, None) ]) def test_slice_from_string_1d(start, stop, step): an_array = np.zeros([100]) stringify = lambda n: str(n) if n else '' start_str = stringify(start) stop_str = stringify(stop) step_str = stringify(step) if step_str: slice_str = ':'.join([start_str, stop_str, step_str]) else: slice_str = ':'.join([start_str, stop_str]) sli = slice_from_string('[' + slice_str + ']') expected = an_array[slice(start, stop, step)] np.testing.assert_array_equal(expected, an_array[sli]) @pytest.mark.parametrize('arg', [' [ 1: 45]', '[ 1 :4 5]', ' [1:45] ']) def test_slice_from_string_spaces(arg): an_array = np.zeros([100]) np.testing.assert_array_equal(an_array[1:45], an_array[slice_from_string(arg)]) def test_slice_from_string_2d(): an_array = np.zeros([100, 200]) # manually writing a few cases here rather than parametrizing because the # latter seems not worth the trouble. sli = slice_from_string('[:-1:2, :]') np.testing.assert_array_equal(an_array[:-1:2, :], an_array[sli]) sli = slice_from_string('[:, 15:90]') np.testing.assert_array_equal(an_array[:, 15:90], an_array[sli]) sli = slice_from_string('[10:80:5, 15:90:-1]') np.testing.assert_array_equal(an_array[10:80:5, 15:90:-1], an_array[sli]) def test_slice_from_string_fits_style(): sli = slice_from_string('[1:5, :]', fits_convention=True) # order is reversed, so is the *first* slice one that includes everything? assert (sli[0].start is None and sli[0].stop is None and sli[0].step is None) # In the second slice, has the first index been reduced by 1 and the # second index left unchanged? assert (sli[1].start == 0 and sli[1].stop == 5) sli = slice_from_string('[1:10:2, 4:5:2]', fits_convention=True) assert sli[0] == slice(3, 5, 2) assert sli[1] == slice(0, 10, 2) def test_slice_from_string_fits_inverted(): sli = slice_from_string('[20:10:2, 10:5, 5:4]', fits_convention=True) assert sli[0] == slice(4, 2, -1) assert sli[1] == slice(9, 3, -1) assert sli[2] == slice(19, 8, -2) # Handle a bunch of special cases for inverted slices, when the # stop index is 1 or 2 sli = slice_from_string('[20:1:4, 21:1:4, 22:2:4, 2:1]', fits_convention=True) assert sli[0] == slice(1, None, -1) assert sli[1] == slice(21, 0, -4) assert sli[2] == slice(20, None, -4) assert sli[3] == slice(19, None, -4) def test_slice_from_string_empty(): assert len(slice_from_string('')) == 0 def test_slice_from_string_bad_fits_slice(): with pytest.raises(ValueError): # Do I error because 0 is an illegal lower bound? slice_from_string('[0:10, 1:5]', fits_convention=True) with pytest.raises(ValueError): # Same as above, but switched order slice_from_string('[1:5, 0:10]', fits_convention=True) with pytest.raises(ValueError): # Do I error if an ending index is negative? slice_from_string('[1:10, 10:-1]', fits_convention=True) def test_slice_from_string_fits_wildcard(): sli = slice_from_string('[*,-*]', fits_convention=True) assert sli[0] == slice(None, None, -1) assert sli[1] == slice(None, None, None) sli = slice_from_string('[*:2,-*:2]', fits_convention=True) assert sli[0] == slice(None, None, -2) assert sli[1] == slice(None, None, 2) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111993.0 ccdproc-2.3.0/ccdproc/version.py0000644000076600000240000000021600000000000017704 0ustar00mattcraigstaff00000000000000# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '2.3.0' version_tuple = (2, 3, 0) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1094036 ccdproc-2.3.0/ccdproc.egg-info/0000755000076600000240000000000000000000000017340 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111993.0 ccdproc-2.3.0/ccdproc.egg-info/PKG-INFO0000644000076600000240000000067100000000000020441 0ustar00mattcraigstaff00000000000000Metadata-Version: 2.1 Name: ccdproc Version: 2.3.0 Summary: Astropy affiliated package Home-page: http://ccdproc.readthedocs.io/ Author: Steve Crawford, Matt Craig, and Michael Seifert Author-email: ccdproc@gmail.com License: BSD Platform: UNKNOWN Requires-Python: >=3.7 Provides-Extra: test Provides-Extra: docs License-File: LICENSE.rst License-File: AUTHORS.rst This is a package for reducing optical/IR CCD data that relies on astropy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111994.0 ccdproc-2.3.0/ccdproc.egg-info/SOURCES.txt0000644000076600000240000000502000000000000021221 0ustar00mattcraigstaff00000000000000.gitignore .gitmodules .mailmap .readthedocs.yml AUTHORS.rst CHANGES.rst CITATION.rst CODE_OF_CONDUCT.rst LICENSE.rst MANIFEST.in README.rst pyproject.toml setup.cfg setup.py tox.ini .github/CONTRIBUTING.md .github/ISSUE_TEMPLATE.md .github/PULL_REQUEST_TEMPLATE.md .github/workflows/ci_tests.yml benchmarks/__init__.py benchmarks/benchmarks.py ccdproc/__init__.py ccdproc/_astropy_init.py ccdproc/ccddata.py ccdproc/combiner.py ccdproc/conftest.py ccdproc/core.py ccdproc/image_collection.py ccdproc/log_meta.py ccdproc/version.py ccdproc.egg-info/PKG-INFO ccdproc.egg-info/SOURCES.txt ccdproc.egg-info/dependency_links.txt ccdproc.egg-info/not-zip-safe ccdproc.egg-info/requires.txt ccdproc.egg-info/top_level.txt ccdproc/extern/__init__.py ccdproc/extern/bitfield.py ccdproc/tests/__init__.py ccdproc/tests/make_mef.py ccdproc/tests/pytest_fixtures.py ccdproc/tests/run_for_memory_profile.py ccdproc/tests/run_profile.ipynb ccdproc/tests/run_with_file_number_limit.py ccdproc/tests/test_bitfield.py ccdproc/tests/test_ccdmask.py ccdproc/tests/test_ccdproc.py ccdproc/tests/test_ccdproc_logging.py ccdproc/tests/test_combine_open_files.py ccdproc/tests/test_combiner.py ccdproc/tests/test_cosmicray.py ccdproc/tests/test_gain.py ccdproc/tests/test_image_collection.py ccdproc/tests/test_keyword.py ccdproc/tests/test_memory_use.py ccdproc/tests/test_rebin.py ccdproc/tests/test_wrapped_external_funcs.py ccdproc/tests/data/README.rst ccdproc/tests/data/a8280271.fits ccdproc/tests/data/expected_ifc_file_properties.csv ccdproc/tests/data/flat-mef.fits ccdproc/tests/data/science-mef.fits ccdproc/tests/data/sip-wcs.fit ccdproc/utils/__init__.py ccdproc/utils/sample_directory.py ccdproc/utils/slices.py ccdproc/utils/tests/__init__.py ccdproc/utils/tests/test_slices.py docs/.DS_Store docs/Makefile docs/api.rst docs/authors_for_sphinx.rst docs/ccddata.rst docs/changelog.rst docs/citation.rst docs/conduct.rst docs/conf.py docs/contributing.rst docs/default_config.rst docs/getting_started.rst docs/image_combination.rst docs/image_management.rst docs/index.rst docs/install.rst docs/license.rst docs/make.bat docs/overview.rst docs/reduction_examples.rst docs/reduction_toolbox.rst docs/rtd-pip-requirements docs/_static/ccd_proc.ico docs/_static/ccd_proc.png docs/_static/ccdproc.css docs/_static/ccdproc.svg docs/_static/ccdproc_banner.pdf docs/_static/ccdproc_banner.png docs/_static/ccdproc_banner.svg docs/_templates/autosummary/base.rst docs/_templates/autosummary/class.rst docs/_templates/autosummary/module.rst licenses/LICENSE_STSCI_TOOLS.txt licenses/README.rst././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111993.0 ccdproc-2.3.0/ccdproc.egg-info/dependency_links.txt0000644000076600000240000000000100000000000023406 0ustar00mattcraigstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640104261.0 ccdproc-2.3.0/ccdproc.egg-info/not-zip-safe0000644000076600000240000000000100000000000021566 0ustar00mattcraigstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111993.0 ccdproc-2.3.0/ccdproc.egg-info/requires.txt0000644000076600000240000000023200000000000021735 0ustar00mattcraigstaff00000000000000numpy>=1.18 astropy>=4.0.6 scipy astroscrappy>=1.0.8 reproject>=0.7 scikit-image [docs] sphinx-astropy matplotlib [test] pytest-astropy memory_profiler ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640111993.0 ccdproc-2.3.0/ccdproc.egg-info/top_level.txt0000644000076600000240000000002300000000000022065 0ustar00mattcraigstaff00000000000000benchmarks ccdproc ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1477447 ccdproc-2.3.0/docs/0000755000076600000240000000000000000000000015161 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1618857895.0 ccdproc-2.3.0/docs/.DS_Store0000644000076600000240000001400400000000000016643 0ustar00mattcraigstaff00000000000000Bud1 ticbwspbl_staticbwspblobbplist00 ]ShowStatusBar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar _{{129, 17}, {1990, 762}} %1=I`myz{|}~_staticvSrnlong  @ @ @ @ E DSDB ` @ @ @././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/Makefile0000644000076600000240000001116400000000000016624 0ustar00mattcraigstaff00000000000000# 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." ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1539881 ccdproc-2.3.0/docs/_static/0000755000076600000240000000000000000000000016607 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_static/ccd_proc.ico0000644000076600000240000132262600000000000021073 0ustar00mattcraigstaff00000000000000 hf 00 %v@@ (B; (F} ( n(  @ddd/!!"!(!("dddC)/!("66y&,!(""!(!(") ."/ +5Dy&3"/ +dddA19)2 +) . .),1!6ccc/,1!6ccc/ccc/dddCdddCccc/,11,96LydddCddd/96Ly6Ly99==9ddd/!A=9E>TdddSdddCE>T>TE6Ly>P1MEdddC)L!HEL1VF^wdddAL>XFYw K5ZyT`{c>XLdddA1Q&NLSU!Vccc/S6ZydddCccc/S6Zy6ZySccc/dddC6ZyS`a!addd/`aa``6by6by`ddd/dddC6by`e!e#e!f6by>b>b6by6by-e&eedddC1f&eej i ij5myTg{cTg{c5my5my&j"h fdddA)i ijtrrtt6my6myttr!qccc/ccc/!qrt~~~~~!|ddd/ddd/!|~!!!!1y6x!!#!  11 ( @ !!0000!!dddWddd1PP00!!!!0000!!!!0000!!$$$$ddd1GG$$$$$$$$$$02$&;=$&$&;=$&02ddd{ddd]VVy$&$&;=$&0266**@@$&$&;=$&0202$&;=$&$&;=$&02$.$.ddd1GK$.ddd1GK$.$.$.$.$.ddd1GK$.ddd1GK$.$.$.0>$6;F$6$6NSFKOR6E*=@K$6$6NSFKORdddyddd]``iFKFKVXyFKOR0>$6;F$6$6;F$60>, $8 GPddd1, $8 GPddd1ddd1ddd]ddd]ddd1, $8 $8 ,!6,0@,,PVddd1dddW!6,0@,,PVddd1dddWdddWddd1ddd{ddd1ddd1ddd{ddd1dddW!6,0@,,0@,!6!A97Lddd1ddd1ddd{ddd1dddW!A97Lddd1ddd17L9!A!A90I990I9!AdddWddd1PY990I9!A90*Fddd]ddd190*F*F0990$B00$B09ddd1GU00$B090K$EASddd]ddd]dddddd]ddd{0K$EASddd]ddd]AS$E0K6L*FAR*F*F=P$E0Kddd{ddd]V]y$E$E;P$E0KF@*Oddd]ddd1F@*O*O@Fddd1ddd]*O@Fddd1GY@@$M@FF@*Oddd]ddd1F@*O*O@Fddd1ddd]*O@Fddd1GY@@$M@F0W$T>/Hccc{dddIcccIccc{dddycccIdddIddd{0I>>/Hddd{dddIcccIddd{/H>>0I0I>>/H0H>>0H/H>>0Iddd{dddIdddIdddy0H>>0H/H>>0I>00>cccGcccGdddEdddI>00>dddGdddG>00>>00>>00>>00>dddIcccE>00>>00>>//>dddGcccGdddEcccI>//>dddGdddG>//>>0/>>//>>/0>cccIcccE>//>>/0>/H>>.HdddwcccEdddEdddwcccucccEcccEcccw/H>>.HdddwcccEdddEdddw.H>>/H/H>>.H/H>>/H.H>>/HdddwdddEcccEdddu/H>>/H.H>>/H/QII/PdddydddGdddGdddydddwdddGdddGdddy/QII/PdddydddGdddGdddy/PII/QdddydddGdddGdddwdddydddGdddGdddy/PII/QdddydddGdddGdddw/QII/Q/PII/QJ@@IcccGcccGdddEcccIJ@@IdddGdddGI@@JcccIdddEcccGdddGI@@JcccIdddEI@@II@@JJ@@IcccGdddGdddEcccIJ@@IdddGdddGI@@JcccIdddEdddGcccGI@@JcccIdddEI@@II@@J/QII.PcccwdddEcccEdddwcccucccEcccEdddy/QII.PdddwdddEdddEdddw.PII/QdddydddEcccEddducccwcccEcccEcccw.PII/QdddydddEcccEdddu/QII/Q.PII/Q/YUU.Y/YUU/YddducccEcccEdddw/YUU.YdddwdddEdddEdddwdddudddEcccEdddw/YUU.YdddwcccEdddEdddw.YUU/YdddwcccEdddEdddudddwcccEdddEdddw.YUU/YUOPUUPPUdddEdddIUPPUdddGdddGdddEdddIUPOUdddGdddGUOPUcccIcccEdddGdddGUOPUUPPUUPPUdddEcccIUPPUdddGdddGdddEdddIUPPUdddGdddGUPPUdddIdddEdddGdddGUPPU0YUU/Y/YUU/YcccwdddIdddIddd{0YUU/YdddycccIdddIdddycccwdddIdddIccc{0YUU/YcccydddIdddIcccy/YUU0Yddd{dddIdddIdddwcccydddIdddIcccy/YUU0Y0aaa/a0aaa0adddycccIdddIccc{0aaa/a0aaa0a/aaa0a0aaa/addd{dddIdddIddd{/aaa0accc{cccIdddIdddyddd{dddIdddIddd{/aaa0aa__aa__adddEdddIa__aa__aa__aa__adddGdddGa__adddIcccEdddGdddGa__aa``aa``adddEdddIa``aa``aa``aa``adddGdddGa``acccIcccEdddGdddGa``a/aaa.a/aaa/acccudddEcccEdddw/aaa.a/aaa/a.aaa/a/aaa.adddwcccEcccEcccw.aaa/adddwdddEdddEcccudddwcccEcccEcccw.aaa/a/fff/f/fff/f/fff/fcccydddGcccGcccwcccycccGcccGcccycccwcccGdddGdddycccycccGcccGcccw/fff/f/fff/fcccycccGcccGcccw/fff/f/fff/ffggffhhffhhfdddIcccEdddGdddGcccEdddIcccIdddEfggffghfcccIdddEfggffghffghffghffhhfdddIcccEdddGdddGcccEdddIcccIcccEfggffghfcccIcccEfggffghf/fff.f/fff/f.fff/fdddycccEdddEcccudddwdddEcccEdddwcccudddEcccEdddycccydddEdddEdddu/fff/f.fff/fcccydddEdddEdddu/fff/f.fff/f/nrr.n/nrr/n.nrr/n/nrr.ncccwcccEcccEcccw.nrr/n/nrr.n/nrr/ncccucccEcccEcccwcccwcccEcccEcccu/nrr/n.nrr/nrwxrrxwrrwxrrxxrdddGdddGrxxrrxwrrwwrcccEcccIcccIcccErwwrrwxrrxxrrxxrrxxrrxxrdddGdddGrxxrrxxrrxxrcccEdddIdddIcccErxxrrxxr0nrr/n/nrr/n/nrr0n0nrr/ndddydddIdddIdddy/nrr0n0nrr/n/nrr/ndddwdddIdddIddd{ddd{dddIdddIdddw/nrr/n/nrr0n0v}}/v0v}}0v/v}}0v0v}}/v0v}}0v/v}}0v0v}}/v0v}}0vdddydddIcccIddd{ddd{cccIdddIdddy0v}}0v/v}}0v}~~~~}}~~~~}}~~~dddEdddIdddIcccE~~~}}~~~~}}~~~~}}~~~dddEdddIdddIdddE~~~}/v~~.w/w~~/w.w~~/v/v~~.w/w~~/w.w~~/v/v~~.w/w~~/wcccudddEcccEdddwdddwdddEdddEcccu/w~~/w.w~~/v//////////////cccydddGdddGcccy////////cccGdddGdddGdddG/.//.//.//.//.cccwdddEcccEcccw.//.//.//.//.//.//.//.//.//.//./0////00////00////00////0(@ B<<%%%%%%((QQ%%%%%%%%QQ((%%%%%%<;;-;w;$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$ 'o2]v~nuir[]OߔAOgggĝn`:G{VxUU%ψz|rZnCHUUiZ_m:ǍUUnZw7F|vcՉn#Z"nwe)9KPYe)9rFO/5eͳ4wקq $ʝS[D^=}8V 3;ΤG;ms=OFđӅnӱٙxxLB:;wPB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ P@ꋕ/>d έ-잹g_#ƧosVXYlx=Yn񺡝;<"~omsMD2Ca);|T0:XYo|V83W9;al2NbӱٙxQ֌W,cu;$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$ Vۭ""ZpkюKGшNk AcƭUTE;ڃhǰ\.t|vJyD|48@I:v>J"beL01?eeSZ1ͳVDt)e| (tH@@_=Mf`PcڱiĮZT VmRRtZąΨƞ)ePk|h7<"F,e|i_B;?~l?W@z>{,^xx_C HT履 7vP);;$^xv|n7<"/oszޟ#kc~&zOD|s  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  tT8,|4v~n4W9 j7ePmV#@{bȁ߼үxmgYPݰuvUYxDm:e)9KPYe9r:qǼ;rjJ9Rr,%g J9"rvQ('g J9Rr,%g J9"r r_A<~,vǛ76m}['HT@|\ݹipOy:<}rD3#{5w"wۜz#5,ӋGuع|:%_v>;m~g3 5D?bs?dr>V'ơ֩{cx݅~~m3Q{&6z;wOΧ~n@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ hu*"b!h󌜫꾫?ZZn,׸{dꏣ>r{m@Eg|/4g+GgFī$xD|9'IT"bجΧSunYע 7jNV{z/j: 튈gߌk:Ͼ{?Ҹ"v:);y޹[@'"t-8rjJ9Rr,%g J9"rv"[M؂!筦,%g J9Rr,"[衸}RY6uWȦՇ; $bHݺD[z~|:vq:@zXf<.8Qc ;tq&y("~Q3);1CթBmjyrQѪ}~뭎|:v~:3W9;1S89g3uvsv޲)߹[B:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PBZnhEXm85hǥ\xD0bXqkщEy=.yΧ3i;b6kXjlV#b5"V UoWD,^y]#fNDο;]IUDϯAcP@θ?ܲZWLsҹZ;v% (tH@@_=ËMf`jrkǦB5]ZꭴbU;sQ1e類3&Rty@B{gT}FLjv>;5ўj,Ը gw|gK?Ccuo=_8z?GJyDy&zGij5oM*}ϝ)j Q8A=M׼eSXs  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  tqbaY6sW\{irk/Mz^Qndq|P#@{bȁ s;I:1hmq s;n`:G,A)gYJr,A)gYDN N\1NRβ%(,KYRβ(6Br#YRβ%(,KYRβn@A<̲x+ߨ{띈6ǹ'#H"⿶9"b#v>ڝ?ٟb{ñꌏ#@I߫J ĩҁxMyh;k_^o,ԸOpxLB~6Ǚ=cnә'nZ9@zo?Vo~a/t;B;`哹<:vc>;wPB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ P@V =AyFu]xD݈xPo^y>o#d#SDZ??B{bD ę@D|k;Jsw+n,Ըw'^6+wn,Mf\woGe糵<6b |vŽ tnr:ǦClVSYe)9KPYj:9o5e)9KPYe9rF-}q)&l]냏Խۈms=$",ط~`S?z=.i N_>T,߶cì?x@z/~HwP%<"F ę䡈Χ~B q©B݄~C\[ssu|fәg<;amgqks;ﯝXvnYs  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  nъ_6aڝ?W^FJ#튈+<"$w{\^Ov;hWBxbTcRvީ0g݉AkvUD0u"bv%J"bb,ƵyYv>[inAr=C܈yn@ P=k̲Վ A5Jg!r6.?5//pv>wÈ8@I#b_|%AÐ~LΗ.Eg3Gi] 5ľMGO}??4ޱ,^p[O9D^3;N;~&zODkzhLgkޚΧ3:Ξj Qn@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ \}1hľMf=w-.UsKzmgYtJyD,z';u<4a~`r0Χ3:)ֵn`:G,A)gYJr,A)gYDN N\1NRβ%(,KYRβ(6Br#YRβ%(,KYRβn@&lpviwy76q $s?o QWp/GD8|="7>씼s)q\[g~PNܽvdr{ڥ?q{uS?Snӱ)y>ۧSr:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$  (tH@@ PB:$йbea!=XY68kvWHqmN3B^vguC;N;>wvUYxDm:e)9KPYe9r:ǦClVSYe)9KPYj:9o5e)9KPYe9rFO/]ϽRY6讻׏;v>[w>:*"VItv ȹy֊;q#v>;ٲٱ/-wHu!L]IENDB`././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_static/ccdproc.css0000644000076600000240000000032000000000000020731 0ustar00mattcraigstaff00000000000000 @import url("bootstrap-astropy.css"); div.topbar a.brand { background: transparent url("ccd_proc.png") no-repeat 10px 4px; background-image: url("ccdproc.svg"), none; background-size: 32px 32px; } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_static/ccdproc.svg0000755000076600000240000014447100000000000020763 0ustar00mattcraigstaff00000000000000 image/svg+xml ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/_static/ccdproc_banner.pdf0000644000076600000240000002217600000000000022254 0ustar00mattcraigstaff00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xZMO1 ϯq'3sT{CвU Cj+cHXqlh= ڏN^kuѽ:~^>XDV_˫gnPg9߼= ƫ}37`o{u.k3ZD)-'?ym7Z+i $-:K8GFp+Ef,DB% 򉶵~V&"6Ѷlhw SYmV[>e%Bnت `;Dyol`ru]bI~élrZm9m)YmmjmEM@YE#1+?/z=q͋`D(UiXkbETY`"&C K%ˠ0$1Y3/ouMS rg?vJZyWX?ZV;gɤ|hH,򼛃\n uv.;%8&('BPKr9F"¦I~0`|S:Jj|C:WcGpeE/2ZbZS)a%VA,2`@p$ 6#R΢+qTqKM)dK=P4ʠaU-uceЂR^{%*XF&<*ѩS@iSK2:)L(U9vP*H&@)$OPrMg;g4r5}Qms:_鮶wۣd'S\,nj#"h3'WψO+<|S_uƷ{o~_Sz3|C^@ endstream endobj 4 0 obj 1126 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-0-0 5 0 R /f-1-0 6 0 R /f-2-0 7 0 R >> >> endobj 8 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 2150.199951 525.412415 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode /Length1 1596 >> stream xTAlE3q[DZ؉!`w$%$MƔ&BH#BUUU@UUU|@ժqBUjE7$@ c EPqiFo A?Pr _Lf;3c ~;s,NԿQw,6Iv=޹SWLf m[88} |;)KdM&'Cl8̆">4?O͹|۩IkzD>&? %r!rg9jD^}xgxT[¾qK']JxaޖJeǃu# l K%$E5${li̺hgc9qtHp3wb(QѱTqȌMa +}me7NXdrpl,VT@W6_t!L%aZ!Z"=TGj@g0iXڣ(ΎA_=Kq&n⏇&1fapl)MY 'fiheǟ\DZܶ-)^AQ(Js| m\gU5+oK8xO['͉CIOɤ& H{JLj(c3Wc>p C%9H^#otF=JZe&Y/6?|'F09G!a+eld;1Aθjov/3n;85ݸ0ͧT)^Us;cn/bfXJ-ލ8 endstream endobj 10 0 obj 942 endobj 11 0 obj << /Length 12 0 R /Filter /FlateDecode >> stream x]Pn -/ ۱B,.<'aqb@8].:>D #hcUA"̸K[ӱE\GG*n1p(7c8}]46BCEW"">*M> endobj 5 0 obj << /Type /Font /Subtype /TrueType /BaseFont /GKIIBP+SourceSansPro-Semibold /FirstChar 32 /LastChar 116 /FontDescriptor 13 0 R /Encoding /WinAnsiEncoding /Widths [ 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 558 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 462 564 0 0 0 0 0 0 0 0 0 0 549 0 0 373 431 361 ] /ToUnicode 11 0 R >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Length1 1444 >> stream xTKhQwf2I~fd3զmҙZ(iتZM ~6ՅT(~" Eg!(U A;4~Нq};4}C̳ { [v ^.ڱptn(%m .u$L!Jf_xKS<(B(z򀢄ZT"[Ģw\"m4^o{$3f;%ϝ W3}YqX'*FU5Z+|((+VUb v $e mRz*lլ:Kڞ2wC&EVƫt-V/hϑ VUUV{p+lҸCZcُ r]̢*?%łƭP^U^Swήa]wYsJUSzUS Xzx/ύ4?&?3'gjǰ|ri[ }k'O[kgƛ {L_I=@yQa$Hb1Oa܆),i(U_<* endstream endobj 15 0 obj 801 endobj 16 0 obj << /Length 17 0 R /Filter /FlateDecode >> stream x]PAn =&⨭"YCڪnaqj@.R0Z~_zgՀ u:רFcU֕[20Na[ν3-.)n{~=5F&}]4!.`] ]ex3/Cinv s M5 F&dt 7{ѨoYBP!T~4S!|99K%_1R9k >dU9lx endstream endobj 17 0 obj 245 endobj 18 0 obj << /Type /FontDescriptor /FontName /IZLWJH+SourceSansPro-Light /FontFamily (Source Sans Pro Light) /Flags 32 /FontBBox [ -453 -281 2160 945 ] /ItalicAngle 0 /Ascent 984 /Descent -273 /CapHeight 945 /StemV 80 /StemH 80 /FontFile2 14 0 R >> endobj 6 0 obj << /Type /Font /Subtype /TrueType /BaseFont /IZLWJH+SourceSansPro-Light /FirstChar 32 /LastChar 121 /FontDescriptor 18 0 R /Encoding /WinAnsiEncoding /Widths [ 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 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 0 449 0 0 0 0 0 0 0 0 0 0 0 535 544 0 317 0 0 0 0 0 0 436 ] /ToUnicode 16 0 R >> endobj 19 0 obj << /Length 20 0 R /Filter /FlateDecode /Length1 2464 >> stream xVklU>v–t-L;n>Bv6HHx%@(1|F~Hb"hPL4~5DH@ 2mY c_{s;@@KBxzuGt a: MZvXh -_16VB=6.1jWn/`7]P'H`0Ht" K0oe"aurM(_vgٶ] M{3[(o+~r6AvzF$ kQ2U1'FG㍍=h2\!⒬R($-fgiuܪc. rM]es%^C75&T*Bz:IOQAZ(ٞME}[˷j W7]5+P\ BTP&!eP`` 9H*1?jfLXogō4'10cc_biu6.Sz|PTY9"Jd}qEbzBg>bPxɭR2٤ +XC8fs;3wI)gzSEѱQZ\QIep__dcxhhy]..uj;-U" Ag Q.Xd: 9#`0 \FcZZN \~!YX$ϊ9ĺt},>ZKe|""}t;9>VTZ DXٝ0a҄U'<[i{Z7-dzumh~F)9R!Οqwͱ[ Jݩ>؜{b.M ]'JD1.,zQ:E)R FcƸD=F`%֔# q=9/ЧH@_)KTա/pϰ9Ҝf%%K"K_յz>{Mj,>2'"5dR)lww9||0̨86NW=1mȡ/6(Vp@`q}&[}iE iU|JМ3͑uH3Lwl7QY~gdA}J;J["mx;-=P^5MLVQ $G8WF=szb%Pg{?jA:8|y8efmdL+e@JS̰9e96tpJ^Po=hh(Ƒ $Ed+Iޥ s5>wsxRw$8cnވ?c Gȇc,s)nCT>> stream x]Qj0+Il Pҋ}P`KTPBV!Έ}w6B5`c.נ&X'UʷG/ 2qYP|Praݓ^&|P SE(EׁFC߽ul6n{)>7Pw%EGat,;tLF}AcEҲ dS.3@# k:a62n>1>n)dxִI?$5m4yxLO['մ 5_^\[ux߭_|r 2Z endstream endobj 22 0 obj 298 endobj 23 0 obj << /Type /FontDescriptor /FontName /VSYGSY+SourceSansPro-Regular /FontFamily (Source Sans Pro) /Flags 32 /FontBBox [ -454 -293 2159 968 ] /ItalicAngle 0 /Ascent 984 /Descent -273 /CapHeight 968 /StemV 80 /StemH 80 /FontFile2 19 0 R >> endobj 7 0 obj << /Type /Font /Subtype /TrueType /BaseFont /VSYGSY+SourceSansPro-Regular /FirstChar 32 /LastChar 117 /FontDescriptor 23 0 R /Encoding /WinAnsiEncoding /Widths [ 200 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 544 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 504 0 456 555 496 0 504 0 246 0 495 0 829 547 542 555 0 347 0 338 544 ] /ToUnicode 21 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 8 0 R ] /Count 1 >> endobj 24 0 obj << /Creator (cairo 1.14.8 (http://cairographics.org)) /Producer (cairo 1.14.8 (http://cairographics.org)) >> endobj 25 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 26 0000000000 65535 f 0000008490 00000 n 0000001241 00000 n 0000000015 00000 n 0000001218 00000 n 0000003327 00000 n 0000005292 00000 n 0000008061 00000 n 0000001388 00000 n 0000001617 00000 n 0000002654 00000 n 0000002677 00000 n 0000003011 00000 n 0000003034 00000 n 0000003737 00000 n 0000004634 00000 n 0000004657 00000 n 0000004981 00000 n 0000005004 00000 n 0000005705 00000 n 0000007353 00000 n 0000007377 00000 n 0000007754 00000 n 0000007777 00000 n 0000008555 00000 n 0000008683 00000 n trailer << /Size 26 /Root 25 0 R /Info 24 0 R >> startxref 8736 %%EOF ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/_static/ccdproc_banner.png0000644000076600000240000027153300000000000022272 0ustar00mattcraigstaff00000000000000PNG  IHDR 3bKGD IDATx{s?f*Kv1"ݭEDQsMCDNR,u^rXhlbv-{ywfgrX3x\3}}|y]Kssss쮥23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23P23PDZmV:@{sɌ3JhSuuuhJ:LyȐ!#t(s9甎Ѧot k@ ѽtְ=)c:a^sXGmb3ZC,3ukLKJX.usѶXGmb3ZCI(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(B(Kssss-a_׵1u]L~:ڞu-Q[tu 4`4hP91T߾}s^:Akhh:ju-Q[tu@kZ:9uy{JGކSf~KGކ23PDZ?}Ɣ\n}n{}9h{Q[:;oD+[om0hsa:dkczu[R:re:j˛.zj4+?Q::5t-蜔""""""""""""""""""""""""""""""""""""""Xaݺu+vL֫WhǺ4777 u]Sei4˯{^ru=-Q[tuukD+! 6Q1hРi⦛n 'P:F۷on1:K'h MҐkC-Q[YA=0)eodV+vk_3y-,Qf`et2SO=U:@ӻwhǺ@e23+C ^:@k{G1c,[gnfu?gm:juԖNN8ъ[sm:::UYYuUӥK477@;!u]ӫ1K]7u--Q[::ҫW6Lsb窃t%zʂ JGZ:jV:23+Eսtڟ444dɒ%innN]]]ҽ̄ZaŠYpaMӧg3gNϟgYti.]$I=^[uuuYeUkd5ȚkZ+kfwgϞWzXQ~{I555e1cƫog̘iӦYfeYdɫ׿}ճgW\ZZ{ӧO'믿~6`mrh?Shyg3nܸ7.?|&Mɓ'gΜ9kv6`'lA6llb-һwV{ܶ Rf͛ &d„ yg_}ĉoZv\QL\hQf͚\g5o7r-;dv{v5Y ЁL>=Ǐe]UVY%ү_|cFmT:"ا)m9>`nq>}zHj֬Ys7wk|&;StR:23'&&'Tǒɒѵkҭ.=뙬f'Z23@ [hQ ><#FɓKG)K,ɽޛ{7gyf~߿[o$7g&Nn!7xcoϐ!C2dȐlF9CrAeW/I:NfMMfzo&f& ů}ԔtvX5vuv:5K(yGd?˂'F'/O^:׬=$oleɦ;%[|:@cC2sj5|pβE(YڐԭY{~^y$lTe=J(aqͿ3砥In^Kѳwzuvw&ܸ:ݰЎ)3Ԙ;СCtvc̘13fL<9^{t4:t٧u]+r}t1iҤuY9ryefhfLNLzq1nuՅ8`dU7Y܅v(3Ԁ ><7|s,Ytv)FʨQrg泟l9䐬ю٧->ey̛7/W^ye^zt`\r%qDfel2vDQUqiT47%SU7,xU÷l[3lqi2=ZbP#_z6y>ޥKMb&tPf()GM7ݔ/7o^HҌ3r /#8";cXi۰OwSN_!Cd…+2tp9ꨣ"efQ3&'RcUSڛeŌTjU#Ɏ{WŌn~mf/$/ʤj_TVN>yLგJ'cyM^OfL)h47%SW[}yl~ձNˠ,YX],1vDu<=*YkEss5GIkI޷g5jWmlٹksgԩt6lX ~9#ߥcQ2SL . ]w]. kҥKsWs;yY}ֻ23ԐVoItQ*=>u}rɴy3ͯLpN}?WJ3r~|ΤaqDk7WGRMC$[lj<(3'x"7ߜ%Kө=:Gvm}k?^:5>iK/w]466ӡ碋._Nn-?P :.qy5qSӴkIvBU0x;$i[wѾoԘmpr/ƔN^ ::G(3׆UypK'*cKǪH>VSUy?'O&wV/Oyu5wo]:23@+jll_ <8>`8{,̶nc=6[H`6c;wn .,wG͜93vZe]Z+3b.Vuk疲W?r_,}i˒/v47/-cJ|:[t:ڃU˓~kKO.?%4tDwzrdrǠdԖE˦6w햼o5.'Jk^L|t4crr/cݒVMmvm@,Z(_~y.䒼˥yirǶxd/iؘ*o2gN xKǏ/'pB]e)3bc˖7ݩ㕙'>rnK Jm 蛪cw$P2ۨt2:X3,dIZvNy͛QMŽWo-M÷WH>ؼᖥA5acCDëcw${Mw=U)3Wʑ3sqxz~mr 'd6+V`oi7jԨ?3:HW|.Y:Ԟ&T_2cJ4i5?Inudo%kW:23@ PX{7tPbS+)>Xgƌ932tQyN-ܒO?=}]ᯥLZ̍ _ iU`55& >rj=K#k~\zRh^4Kd~StMչvɳNO/>tMoݻJZNɈ+[%~#1t%ǿ?Q-m޼yoۦ1S '|;o{h235=Lf;"9a'/?W: MHEUkj,n ^1ˑזNtMW:EЭq'Z2iW &Gmw'-9ɔ'K_=|{&O.I-,Y$ҿ|ۥQN9qyJ!)>-cԨQo2tQh7|s<<留I\f~TeqK͟ :&n{+&3%'~NMddJ49ɏI&-;1['^]: 'gz;tHÒӓ['X: 23{׿u-2M9rd>O+(SOy3ihjj/|_΋/X:-hҤI9CrUWUf&͟Y:;*mrKڼo_sSU=}L 199S/&Ӿ͟YG{U[xxqҗd++d̙8p`~t>ee٧->k¹*t:Ic?\00Y4tZ˂դ6;tj23Щ577 .?L0t:c̰an׹SZ}566n~_4ά1?O8Iam]fU!3Ɔ}ԭ.Fֻwn"YodnK'l{ߞkҳPz2Ǔ̘Ҷ[딙=WUW5Y}Ǫ$m>IZFɆ[Uu]{k#Ye{~[Z_=o͙V=Ϙ\_y';EL<}r5UZӢyy(:u6H6ff ٻ**w_aqu;z~B2u\uyq\٭0kj=ON9s^ kx&:qcs=W: A3䬳J޽Ki7Sڒ}-Z>:ɦ$\_bgQdƥ$W'U-d[M[Y]VS,{V6\sSUXˎ^ %y9wIv_: h:]_a'rlٯ}ɯ6o{!O6`4+'{Q_J*j\jn ]%9tjM ^Y'%yu[y}dxE?Y $w\lӯlZ԰$=WNuO#]>SMnzN>ou mЭ蛪Z5vDrҮnK߬tګ/&?7`$okB~Vӎkaβ f'/Or~ܘSsS2{.m @7mڴ 0 O=T(tbJ~_W^$Z`.3gΜq@R'W0Y5![dUhw%<:K=wzd1i}p4Ԃ֘L{t7{_O>!jkUkQ'#.r%ܐ΍K(3s=|+2eJ(tr'|rj1j}JO+ .WՌWGVDKGi̵4:'Z[R͓COK8)_$/?W:k55&B5MnPڒ̼xA򣽓)mx1h.ɇH>sBm+kwNP[ML~?4t׷G.ҵtkWݝxN^kKuE=1fXsJ'yc#qNE:=/^w71;;'']W=?u:1c$>>W:FMOi>G}tƌZ2sSc2Rd~~\8)9WdWܿ|dK'zƥY'>\: -jMUQهZkm?kWz]kܵ[5Hvt*2vO{c'LzZ:k-YV]ֿ6=VMJǒSݻ u6HɠOzV:2s%?xr:=ܓ?Vا5jT(-k ֘L~eMַlSnN~xת"/ݒ~&x(9ҪQ+ON7>tJj:z[p]hk6I^kOWk_gme?TqD\W+ ^$Բ+~失tֽP㓯Jn[:QNuI>vx\XKΐ3J'3@˹馛rQGeѢE#կj54͢Fا ܜ;KGhyow2sssry7wHu2zsO[^b򻧒NJV)2$ NB)[h2Vom={'G*Ѻ+ɾɢy,Շ3IN* 9䗏$ߧt׺ɥ'NA-hKj o.XODk>1'g_+.nr.tH@rOҥKKGtIzKǨ9)>{Kh ,/H~䂣ۨݴ;9j-2Tey:-0ѿVP[N7镴oȅ'9qHrƽV]:Mw䔡ɏ^#ԊLR:~V:kWr>N~lcu9v )萔[n)f?zh5>ا;#Kh=ɂ9o|k[[?ӛyG䛗%g"n{fU!|EI C&t JXӞO~~P2y^%]|fti =0`uiL]1ݩ{笣YGm)QW6Nbì\u 4`x wtU'9H: Fivѫ P3#*cu>QdžQtbAHC( Ig !$ggFKB{;MCU4u]P^^&M5k))9Yx?T% GJ~N?DGb EKvn">4vSIKqI_Nw$Qu#)wn(L:ofJ}fY9%o[l^[o uTX'r}kX'hر_5^œlٲE_~9afέ.fdҏ#RK3"ժG5Η;1c'vP^&$Lȉ\mn99U:.iUf^n|3["sjt-҉ľ!귔n&mn<')9 pi=[(]>Qju`m;GhF=$Nf-uYf骫 č#Gp]SSx s*EQ]}Zj*`MgK?"sa=Hcن邱ucKvl[/R7FUg<-9!] ,2EEfK-;KcJ'cٴMQϗ >o5 c7l.X uo'qҁ=d m۶u]e "UIR>}4h 5<901G֬Y\{o?J%GUW:ztK[~paGKwJopv+i"Iǜ+]VÍmkm}nINu#lrƾ=9BX]&^)k!m7I?n!n(]Ա&X5i'Ib&kc/6O 2ŋ]W`Ѳet饗j׮]M[ou]SSx s+gq]bWieO鑐(LtTi}HTQ txRNna\R;*K$I4 *)zv&JGaI)m=gHw0o4iE[Ė>)es$9Ot4li{}.k<+u_[ne]BUIRrrƌLU<901W֨Q\>xVz|4PwMt8^s鎏=?7lҊP#[T\z|iuh^ ^TO,s[-ku9+Kw&EKu$K)^Pt;)#䙼?>! |+:tVX #F9iSxs*EQ1B۷ɀ ޗ^WZHtƟ1KmGj֖tqnϓlس?'39]z4F s$Ԩc7t=]+OwtkiVwj֑ny;HlRFV^+tӫS亀 }_<5*卝kwDr:U~,77xCSNuXu}dԨQ5k=Xs9kx s aNc&L>u 6]+^*]?ExwOK:taGq%Of޹UzIҸ7y{Mx'ns?J85ӫR3/Iw9}F$Nް]Fm^-Û$isON-DmnO 2srBT ŮkTJrBqv:aM%''cA: |: u^O^p]Q&Mt))9;wƏwkTimIײ)ҍH̰];AK0]Hޕ5K>MvBt҉׺G%FK Yf[?O_'-]=b'3X0$vDfz<3f讻r]QrrƌLu?)9ٱchK|*^&ص]?_5%m׃,\zs\d>~6s?i$S~oK/Nn.<Ӟ~WT'B#vH/.Pt+} 23zsϹ999ܹ: ]vIKKSZZ233Un=;Zb͛ٳgkZbEu/$;Vjr]3S}cN2~x\!ФIyڵ:v&MTMMMUjj$:uTڵKK.Ղ 4|-X@-R4VqQKt䙮. {әo{^&i94[oҾoڹM nnJz{ͅY]H_hrxT7كNM6i&|kig172p IDAT[iXfIk֬M7qBIiii֭znݺEֲԬY35kL'p$iŊ6mM>̓P6LkxsjsӘoV>O>jΉС:t38Cc}g3gfΜ{r@kMYp`iŗҿ\4e¼g/+?M |k7395ܱ\DiݧKeo,JcI)լN8`Fl܅ &eחn_<_,3h4aÆi۶mNZZzSZZJ?5x`^ZSLє)Sj*$Izy纆g00UǜƔ/JKK]W LtI:3ժU+u$I5jQG:JqF͘1Cf̘" KHN.}[T;=Ζ'GJ]K{0W'3o^zIzׂ74 ҕI®H/ F ❱'S\r7i5?cXf9ƍgY%qtYq5jO7_!Chz'5{lg}6lѣG;"4aN2i$}駮k^FtEOVzz:)SN9Ev5uT{*)h 2sk lKJ}Fcr =*{<듙yo1ҹwVu];NemmQgυ}..=n'c1^']b?@( S>=CkBJJs=Wp:U޽{wޚ7oƎ3gZD4vXeeeY24~`Ni&s=kZF4d|JJߏOտ_7o֔)S/jҥ֦4n&pq~^b/bK= ?_\vSܦҰ9;c[z_ n&:ni2id^"=@Jϴ=sg$.S\\nIeeeZjj9;;| kz㏫SNrZy^ǜs_Ok֭kRFF_]"ծ][k{LGqJ?4;%֯}^̏\-W"Ijխ;ݦҐ v3^BtӱlڰRzrL]NTkj}46Z,3s]÷" zK7x /4u{NƍS6mfS67i0ǜd׿庆/{챚:u.2cDkׯ\W/FlqRR6'JrM-s{2J ?5Jjwx_UCzb󥑓8>Rҥ_r}s`LS7HkY7O Xf /#<⺆oqzu7^zXѫW/M+N;E4/\i/e;Mq0礅k>vs 'y2K'GCu Onqtw/9N5J;vp]78M2En>1c[oUZZ~ 4Нwɂۯ0974~ѨƎ뺆/iF/v꺊gխ[WweeeSHG QR\;Y_NPYGA-`ZIuӤ/Hi5e?j֑=oIыW˃ m2/> >}O/$$$誫ĉ㺎/ 8P/+G"{?ǜVs{嗵d5jMƌC>}(55u:9;򚧤d>?Rҥ7*)޵|z"KN.lxFhHtţRbN-9UQ)zS7HE;чI_}`/V4 m۶iܸqkx^V*??u@8ScvڿםwiœVs_Twq{Q]z饗ԣGUKN}V:O HDmj'G;N!5lL[œhOQj^mw鄫m^#M^^ؔIOyRv}WXf`?mݺu Oرz)կxK.׿֭cD"{8 2S3ʙ={͛纆gScƌa92335~x]xᅮU]_=sA*~ ɂ{K]lxNVNG}԰Wvl&o<(_a/?^%XqF=ӮkxZ.]+;;u@jҤ}YCW^yt⺒0ƜŜĉ]W:HcǎeـD 6LwuRSS]_\@PsTi'nV̗fl/oCR$^#%]lۤE;WtbU=\𬂂=Cp]%j֬|P\r*ĜV99ݻ ?t]ÓZj &(--u@;cqC\rtv"c/I[ndu,SAo^_*n// ^_ں^ޠۤZo kw]ó:t蠇~X5jp]%"\b̩]q*eggk„ r]%>`=3jҤ*} E*>~|IH.lX vRҥs-Ɗ:M<6l$񠝬0U(|I}.|ef<#ڽ{'*33usw)`ڵz7]𜤤$,Z֢E =sj׮* 9`y'-`ËIMd!N^޿Hw ]7)1b/ؼy^|N:8qj׮ B9;^1ydE|nP׮]]u əe$ؓ! u ذ;di$2NaRv;Y[~ EVR,rϑi/?"5<'--MǏW^^*s)\'Ov]s :u b{iiҀ ] Ͳ UJa' ᐞ)3^以R$V->.m^c'+=S:.;Y 0Xf`ܮ]3ϸIz \ 0YfiʕkxJft-/`'Ԭ-鹡mcv:pu^eUN2r{<s~cq'O֭[];O$toSxɤI\Wd;V5jpc:KJ+yӤSFJ5] Jd~`' ᒐ('{yoN4!NVftv@ 8N{]jk?bN9v]S ۻon(2-b>vdՓ\l(/~NVvgB8~wofK f/Rz<,30?֒%K\:u{U$q]Ĝ s yWU\l6h߾.5ػg5;90Rjat{'~^ ^[kxNVv}v@7F=Ӯ+x߮\51Ŝk^|E<#hlJ)sxKρYY~pZlF:,;YNuRN;YAƃm8znU23c~{kxʠAԳgO51Ŝk-Zu 8ծ];5XF-svn|Tض^9NDn6Og޹U9;YAP[zvrK}.efL2Ehu ]~9%^曖999 Y CNvr`F:R![w-T:9t?Ujz,[)RF;Yd@b1'[:i'n]x sK)hڴi+xƵ^L5. 50[2)d' b3tYyvnڣvr223#>S}wkxiݻsK)hժUZxЦM|ɮkPyrX m^c>VCpe { 'J^d>J5dc?۸Jm;Y],մefFpOԩ#Fs^;︮CUb"?!YqJ5n[/;9.eճ\Z wxT^n'˯>xN*/3sc>~ JKKۖN}Jjr]Sx1;vT޽]`ZfO$I:-`Ky4{WR5MKgTn,h,3>H۶ms]5p@5`Nœ«6oެ/u O 2>ռ΂|NfOrn[%m\m>!Rns\:;Y3_Gk|j's@ ^uONΡ'J5dPHt]@+x_Wi s /3eH$N;u K?sP=}.9 TfZXfYG|PYMK-;Y@~?cεB#0ǚ绮ŋzj5ҥ \9aNu,3K={T] >)ٌ"/A%J^lڰRZ1|H`}39&|m[8ĠV]`neEތ3\WsÜˢѨ 5;]W ~8NηsXfNjnn'稳 RYl΂1ߓ%s:KfxD㣏>r]f͚cq]+9-X@;wt]élqk?[J9sm>Uu62sFRs@h/ fHes*,3ݻw?w]ùA)1j^7g۷8 0X 7=]K u3vlt#Ioi,>|@Fo\KFQVe1þ o_ry74u=~/9u. +2>sTJJN:$5bNS'&Z*J9m ir9!23=eg9^qz.gP 2sPh::aM%''W1îP\GP<(ʺ0ʘ9s {r]+9>s]é\u_B3J5Jg3"RUUekEfs'x [0NΡ'fHJt]sʜ[TϞ=g+P/-,3?Bʨe>'.!>0.6BefVZZ Tխ[75bNS… qs Dn`>efg>s?@uuo>cVisk c  ھ+T߾}9EƜ2E ?zRSSuᇻ9-:XOX]Lc~Сi> ^}+9o>/p]-0Tsʜ~2s.]4io>}l>N#YG9@uEN3ccjL|!gb@}Iq*((p]sʜ~2af5=|%Ry{se>GX}};|FVRN9 XfPm s >1)oÆ ڸqNuBG% IDATu̲q2sni*9RegbRiW/2s#gPc@رC+Wt]épP )s Xp Neff,V K6Nfb3pg\<>`)5lFy%`23jU^^3iiiڵ@Sh":D\uzsXfٲVٌtAf3x$Im,l&V#Ie>嫯r];L)))kbNSCOfԩ ؑ|̀;˾0)l>vGϰ1^ccV]qj,3"ٽ{w}bNSCguh|̀;6)3[{X1|?;z,3%K C=u`SrJIHH`uX|=Lp@&%$nT55}i>3@ Z.]꺂3z>9mܸQEEEk8ӴiSժUu |3ٲ3Zv6[lAk%E=̭9(23*[~ ]psJLU69?^mۺ=6NfCNXZٌZRnS)4<^QV-4Ӭ z6@-Yu:uO)s Xj N '3IךK떛igaymP;tٌ&Rf323jX| NxĜ2Nff"uI$97-723|29 /q*3efUS$Y0)ef!rhp8n NfYYf0^ef,30/^5lPkx=xsʜ|A$QӦM].̛8 K ?kVJJ1ac@ ¼$پ}{JaN6h@IIIk`WV][~0,3"X$Ijo6c lW]b>Y |dV\W*9\k֬q]Ù</3Һf_FT 4 icHnް'F&fH7ov]$)s شi]pYf+`eguOf6oa>)^2sN))l J֯_ﺂS-[t]'9?lٲu7nq231)!w]23`U38A`c)JmZm>ef`$K7v]YfP'3S|'3#l,ژG׶`>ef`d@x/5jP)),3 L;JsXYfnj>0-T T֭[]Wp%Is CSSSXf./v2 d]NfF@^ 2u38X23* Uz\W*9!7dee6%x'#RnE@>e<Nf `d۶m+8P))a ;;u܈$KI)swSȖ>"mLϣl`S34?vTI$ԩP))a^fdf@8kL[la-vpmf_XfP%,IǜPX_Vef@Xf.f2OL ?1=jm d>%Is Cqq ddd;  '3gߠ/30'Nf J\Wp&3/8`N())q]f>c.b/3X2s9X23* ,^/S¼̜DgDKgʢRv̌ (} WʢRe b~FFk8H$O)s `b232sI WL/2K c~۵+Q @0y9--uܱ\JwϨ23rtef@,3oa^Is GShc+l,Mp# $9̻-b`[4ޓX_0 @Hq236[,Yf?23%ɤ$ l9<ȏ'!fc9̀6&Y,Dx?\=@,[YY p$9arj6823J@,c~+- N,]/S<X4NF? Nfc{d>Xf]Wp+s CgU!#6i-$oXf򨢩e4RRa_1uMfMՕc\}yPuax_d2JaNJR4jxUt?;ܜ23&lvvXfrcD:¥;:%09u\ka^*--u]0Ϫ+eNZ[Rمcs ]?'H/S¾̼BI,"<*!uw xs2s~~ @hy+s CgUdf@E9d fboc.]IJ6a 2E\WB#̋Wņ s G$ > Rعq &˲AƵp+ϜPe+8sNJaNHL 芊\Wy +,"@%Ryٌ  qm , oJTK1$%iVb`/a?VNு@()[6W繮Q)o$hjb=~,0ѷgQռ:u??úP\sn](b@ԬYug6o(` S|m\W_MHYsY,,)2زeg3,3Lٴi @0?d۷6k֖XXs -Yfb@ԪUug6nP)) @b>#3|47X ̜kϰqϰ J| \W*9!'36k1 &!Qm6$6A',023efU_׬YP))a^f޾}]> BA^q23A:0,3lçxP))YYY+8i'Bhj Xez$,3W'09X23*[ p+9vڮ+8aoJA_SRʳ([+_M/39X23* +fsʜ|$mܸu쳱lz /5 1}2sZ )9lko23efUk4VRRի-<&9?}yݺu+`U3L/V%ӋIfl30< MK7KeQ?23* $-[u`SPvmZf gdܦ3dy!gðl eҦf ^z+8d}bNSCOf{~9 ]ms67'6'^lYa5n03,3P$tR}bNSCؗ9:6NezaRvaXf6}2d{ 5j亂3/v]lE"5a:6jJi5IV}OlXoqZ'9X23*kذ ,ZHhu `SD뺆3k֬Qiiسz Ne1w̌ y38X23* Zd>1)~hT+VU_5M,h fd-g  `XfPeM6u] sʜ|$-[uaDRY,DXYfni>õLfm ,b@5ouK}bNSCOf˗=+Yf`-ʢf3Ӿ|FNf_=,3/IΙ3u`SРAZt QQڶ|̀(K5A3|yfN0&m\e6XfPe5Rjj,YD[nu]sʜ¾̼pBcWvr귴lwg&lFڸyib@5$&&*//u g'T9eN5r]oV(g$J ۘ[Of,3lm̡W>YV `@nufϞO)s M{n-[u ̳)d>o Tedf ,3mۺ|O)s ]233]p-T kXѮy'~K,3Ͼ|9 ̜ ,a@绮Բe˴rJ5 1)y+85w\0t 9 gس)ٌ5J%f3Sl,37io>+ %KSЬY3bx˾؂Zdˢ*j2̜'Ր>zWn 5jL5>} @Se u Y7[5-,3/|=T[AAO}*--;P!9huf'@8Sl̼#@} 56 ,`@uuNP!9nZHu fq*(]b>E'*yuϙ1cȒLD,e R(dIִ(KjZ e+ՔMiQJhDƙq?O2ùs_aksg]qm `3'3ܿm du?GDDD"EDDDD]u~Y石~5DNIs9K.]êkגnH}2ȩ u:{as"dB%!Ww3mfH2 RX15:~8IIIkTs*lٲ+Xu1 R CkWA:ș2p:&-3K1q*sBpEx """Ѵ,"""""AqWۮ`믿niiN5}ʕ]+Wڮ ""|Xf.^/~ef,O01w^UziYDDDDDk][vZ5DNIs923|ᇶ+-6s ɚ2+H=~H0| sU `Gt?GDDD"EDDDD$($xlW9%ͩCs*^VlY_ٲe"""3")nSEվ_`s*\~W~ơksDDD$biYDDDDD"!!B ٮaݒ%Kطo"'9uhN .L./=vv+,>ғ!rV~FLN(W*VߚgH2M͚ЩiSͩxUW]euk)CDDD~N29"uW8%%#r.6\&Ĺe&NgN^~D,-3Hԩc!`޼yۮ!rRSTRJ+Xyfnjȹ~t?G2xOOk9[GSỏ13_ásDDD$"iYDDDDDk%*Jf̝;v Ҝ:4e+W]-Zdȹt+3#"Yp-~7x Mx.7pA ]~D$RDDDDD&_|\qkxŒ3txͩxU ȑ#ֽdffڮ!""rBs|>ɾ 2xwψUw?J^yY HZf:S_ų4ͩxU\\ʕ]ú]v駟ڮ!""r6~S:+~dߕN~Hv37ϹZ~/;p9"""q,"""""AՠA~Hv}s*q?f9"""q|@ `D0tOL6YA'}[|11ntv2228a\D|s"#WӧOFB]Fرcpwӿ5DEszThw}kX|@BlWӘ>}:'N]è-/]9t}]FE_ ~4z`4݌aZw3Dk\[us&$C焂m09u:€Hr n BI|Y %as846G|aɮ +خ :thѢͩxQ5"33v~?gfdO,2+Ef n`wP:r~%w@|GS!g9"""QlӨQ#<ѣL0v ќ9/:PA#--v Yj٫Lldu?|knq?#eu(as2sٲemWR EenPEѺukj֬i_49/U֭]ú?_.]خ"""5i9f0#"gLu(p1p79p#+g2UoKu?9P9"""1fyӦM+|>n&f̘ag >$ri9=ͩxMZ:u/hD+ IDATҾ}{bccmW9_+ Uw?GD΍5Z¢g l3'9.v^, TԃY y#"""#v O7| }vLb?hNIs*^SR%re'ٳۮ!""5˧ɩ|UHHLΒ䈜SN"?K爈HO8DDDDDe˖.]Sf̘ul4%111TVv xIOO]CϡCXh""ޱ-ٹP9wk;틅SI? `&fk39zs39˦Y\v74N]K㒸,o qc41ʿ Xd K.=tw#\>=-so9Jllْ1cخ~!Cog9S rJ5<W^G;v޽{i&UDDcfrks狂-w ;=nȩ|2t?'o!ErrU8'43ٽ]#"""!,cyv,ڷC||6g/&&oaip82Nhνt+ռys&NHF?4 !?3cƌaذaӓќԯ_Çi'L6-[RP!U"^ff& 믿]ED;ǯɪ Zmͼa4h;br%w,zLV΋BfYK2ϟ ڮ9se2?HiN+ (@JlT&Nh#GdŊkxBۢs#"ue}(\{ŰkF䄲o7zL5-3ڶmk'=OkSќWիm׈h̛7v og;dUifL+wvZ8LNT44Lb(RLfr$6.i&aT3Y"""1n#Gu*#GdŊkuVڶmKRR9v%c49H… SZ55hLwdIdSXkg%^* ' 1ffDDD$hYDDDDD(R͛7]ó~?dժUD$ڶm֭[oVbCʴLszzSo9mѢ sٮvӧo*""嵼\Lּfr$2"""rv,"""""tޝ(}r*{|WDG裏2dD>lyӜڣ9Fg'=:9=ǭG}d}:6UF)NwZ}LDod9aX-eG剈Ho'EDDDDĘK.ov OKOOG:ՠӺuk,Xp?o>&Md=3Ӝ9us 7خI@!Cn:UBޏ?Hٸq*""π׆k5\1bɒ?/3~k./\^2ngYDDD$,"""""F[Azz:z .'cǎlݺ~ܹ_@34g95Cso:t]ѣG+rr˗/C޽v[> vo1upe}3Y"b΅@=C'n=o&K"ɰ{3Y CnfYޞLQKYfkx^FFgΜ97ҦMyLɰaòC4k4Ҝ\ʕmЭ[7vaJHdĉw}ڮ#"}G`cZTfA65Q8, oy#5八j͡XY3Y0~3Y"""6,"""""{f9D233m Ǐg]vl޼9W_uhNFs|3ܹ gt?lJHHII{L>v8𛙬BVfDļevf]@5a8rLVиpAAV̓>3'"""!O"""""b\bh/j̙3ի]%}嗴lْ{Nm}7C iNGsӬ馛8m={p뭷i&Uy|~,"e;7cu>:O$yY24aɳ{3Y^0׬ĝg./{DDD$"""""bE޽ +WUVlذvg w-[9r<ͼOs=ӳ9͞8Z g­'|b;vDzAJJ:""^qLVP,' L8LB9;~LnLV 75kX6\4-3o_?8 ;vSNK3`ڴi4mڔ%K}X+W}z44٣9={:u"::v ;|0={W_]3Zl̙3<%"]͂˻mZEĮ̝¾a%,}L7:˩Yhjλ\+þ_剈H2XӡCʖ-kFH9vFk׮ڵvi֬&M"--͕{tW޷hNOs5sSxq4ibFH5rau9r#GK.l۶vsxyJ7U剈]%*B;{6'׍0oŠ`sy$>/y\^ax< YZfk:t!/[naΜ9dfcHNNK.у_~qď;w2ydW3@sz4'9 Nk̲ŋӺuk֭[gQ@~M2g,"rf{d|Ω"Y:\ydgɒЗg2oM"M>p%֮3剈HH2XuWӴiS5BNXl۶-k֬]Ǫ=zйsgV^m,_4giNOМ_2em)۷ocǎ=:lOdڷo<޽DD= 8u;CJD[>,f.OB[ǯ]z \wH:=f6Al-3u?r]#dmܸN:1h onQ?u|~?ÇS.5Fs9uK=lW9+|,[vWlܸ޽{ӹsg֯_oHh;O*"PWo?˓г;;l] %2z9>L>#"""N"""""b]…ٳ!oѢE4mڔ~;M61d5kҥK.)&''oX7EsS;yN+VH5lI;w_~tؑdubՋ֭[خ#"w˻>}oƘKƵ2%tm>>m5|Qfo,7fH2xwAҥmy~ Ҹqc ڵkmW @ NiѢIII~۵?~<]uМsگ_?Bڷ~KΝOmɶǏxb:uDvmW +o˻0=j.OD2o$t<vn25'PFlGỏfHH2xBLL G&::vYd :t}>|vw^MFƍڵ+V]_<Ș1O2@s\SyN*6lhFꫯ֭-ZW_СC+֖-[8q" 4`Yv%k3lflx]O:9X5\xD: 5| s""","""""QBn5κu:t(uaРA|ۮuZ))),XݻS~}&Mm:$K5\9uԌpAi&FEݺu0`K.%--v-n /@Vh֬ӧOg޽kO6-KdN~\fVpM3sy"m^ 7k6󹻝r8hhLq{<wpEDDD+""""""׫W/V\ƍmW ;,ZEG͚5W\s %Kw_j*̴l1~U\9u}:%KUV,XvΒ%KXd qqqTVիSF ʕ+GTDY/+Wc3Eھ{dxٳݝE.SrnOq: ~L^!FS#0-d5A &\SD-Hi.sJs2Zfɑ#GM6;vv·~ȇ~@꫹+)Wʕ@ٳ 6uKNNîO?1c z4|aS349ӧOE||S\9ʗ/Or(^8ŋpg|Qvmغu+[laڵZ^qñ4%mc Jykm6cPLk: kh.0=<>GFDss Ӹc6S)W ie6qpQhlxEDDDDsʔ)C߾}?~*#%%e˖lٲ\_׭pϟ|/_>∉9vsRRRطo+ +Ѻ禛n"!!vWiNӜOiB֭'O]%쥦Lrr?{9 ȟ??'w5=zGξ}zD< :4λv5Kaf3KU}fHu3e~ =3)*&mfh [o͝ \ U; 9m,"""""twfU"ٿ?֭]%=z#F /خ:ͩ}ӳsڽ{wؾ}*ݻ{ڮ""Yv %+n# :B \ft=]1@NsK@RbLkCjܞ /b>wsżaӑo4+VMg-i`t x y-v#1H"""""Y =gH%&&rSL,ќJ( 9S7p""##;9Yưsuf3E$t] lf Ov +Y_2[9|Y^P{`d8l~jWI'۲{DD(-3կ_=zخ!m)))L4v #4yN|Am -́AW/m7S2feok:ɆA2f33aMZhW,mlnT4 7+Y㋂:_xzgY"wk`w~۳ݝ+ꈈHOIDDDDD^j׮mH͛7uٮaTBUi"El =;7a󶛈aL; zL /1+"-W<brM?߅抻p#+w*= wNPߞzƷ|%""FiYDDDDD}F̚5v #4uN}>O<s]ED$4}w%~v &qx6d%d"^Zmj9SDDx3棩ȹ:Dc`[^x8:Ɔ=[ၚ-Nc30lߐA$""ޠef )-[GkdKjj*F]ͩp֭[suٮ!"ږ=+JF6wvip5vE$ğ@"•2a\[Hh>[d8~N~ap/ I%*ׅu8F3* H2~Ѻuk5D>]ͩp /v жGxz?m8>NUt;ف4 L;$k20'zйl -Ѥ7\#5& fRWY|OGhYDDDDDB҈#mɖQFf1S E: `ҤIDGGۮ""wl{ݹ?~YoC0t+l_;n@JRWY4voq/^9[DD‚EDDDD$͛3fPHUD3l0>4jyN}>$$$خ"">+ᾊv6vƶ9Â\VEpNd.^ND[Ή`=nG$g `\[-I IDAT9`H^qŗ ٦C)ΕR^Ǐ"@zd2%""2ݻwcybˈA'X|11ntv2228?;.W͹jۈd۹;ؽ[x0|pڷooQS 5<[ncǎ:d"!,!!e˖ٮ:ԃ[/~n?~n'{ܸ&2Wg"3sDD䜅Ǐ]ADDD$ 4vB׮]ټy*"g/RfM5ӜJ( 97n/ׯϤI=q VΝINN:Er2SnM{C|^mC=rN@C :Y9-ݣrn4Y@pNnFWnqmc w>m7`;MpUCMDDaAlڴv 0sL.6l`)]|*Tv +4*"aN޽{ywmWִiSƎKt?/!^D\fpΩaRՇ1иW}^wiYD\-xd1<ҏ??·[V}Bٺm&'EHwXdEOn`4-h0x=ߓ򵳸GP4 }DDD v7͛^zUڮ"rRUTa޼y.]vk4u2QQQ= خ"!sΌ7_,3x7Ø/XY{R0^س^P{ $፼ܨfɕ 9m7qmB/ad[̭Nn!^)giKA2KΕ=$l Zd.R""aD"""""K/DVlW[n^z .v4U6L8Zjٮ"!1h JT\dIíDΠTesM`dgbdc*ɹd)p}޶tm"a9 ͹nr/Ẑv|o]%Xvj0tIBsиo3G}+g zCa.aw&G #v'{^s Wno;qxyg.Oۿc`\r;WEvqAX.3ڮ%1S_"&&x\ bbbN82Nhν%"Wr!vJҥ8p G]G"P޼y;v,uֵ]ų4b:u*=z 99v|SOQZ3لBZmgun~W ۜ宜sڹ  +gc'C^dO`Xؾv2I-r@"rO_={5l7/k0Lw?vu#L ]i(W YX7! DEMDD%D뮻9spۮ"bŊ,\0$Js*hNO8xU*AeʔaYZd. a, 2a x QY(K5|6$6{/zo9:Ef ]B=MN`LkY9ۍ8rV̀"o.!<%kuGCڑsMK[˷kw`S F6OV0~EDœEDDDD$oꫯ]E"Dǎ={As*iN-..)SРAUC4hܹs)^x^%)$,>sۤolMm)0|8owIዅp4v ̸^-$gkCI`2vSm4il 1"\w[ۭNh9ڋE!<1\2%-o.X8=FalA`/ k9l1-L2SmW0'OFA&MlW IS1AszzzI>}|%J`ժU.4 eO0w8dnOǯ98TͪMJ{d–հf)|:o̊v)"r€נH)x#ћpd]S{:'hm˾?ỏ+|(k }%1_8Wضv3KVvnApޡ<@Ru95A &""bEDDDD$"EGGӧOjժٵkJFjԨAbb"E]%iNMӬf*T3fخ#ϟcRv~%K b#EEC!PsBOl7:tgxR>(\ *և@:PcsnX̹]Rl7ʺMwJmpۍNX"O;KVJFpymi3` Xnu>yDEn#`q9 ˧nuG8'3 ppup^~ O-lX ksBw=u"DDD 2D*U[o1|p/^l9sү_?n:RNNs*9>)]4>(+!UTa„ _D1XYNYxm`_xRbg9Te(UJW/4omk`[2j&;Z=>(MDDs0!Ƶ-_n5e[2,, %+%T0, ~dH?lC0<>D!&'|9{JH?bQzi%Ktn^,>~e};%99 U2o<͛m)RDjԨYhQHOO1`q.|^,ۆ'{K]qرt8,?f-%DD"IX{2v ';wn9w,Ks>f; ͑Io:yڵkǢEhذ\-3KX -~{@tۍt|>~jcHyOûeX MՀџARE&W< .nC骐svbۈ lȟ??<˖-M6:2UTӧ3{lJu$4Cs=_~9:} <+d޼y1cZkDLL~oW޽.iaaaׯN>mt)o n`-Zknt`iJѴa\i3:}ػdiRRmѕ )ao#]1DFIaK%}Tѕu CY?f,zF]  5|Y,ݻW;vΝ;uqi0afΜ3fhĈF?v:S"**J .… uImذA|.9s,X3g*((e˖-Ft FH?yFkoKI5iHWfQi>lt@w%RڇҞ ֛;.Ri)7K:ލO<^9JvK?oN12=Ծ߹.ih:!#BBBtUW骫?SN/ԗ_~4zedȐ!2ej]}Պ4$t2lmc;; 4H~?~\۷o׎;.jtyӧoٳյkWK` )a%iRGRgRUyFPi-'G_cWH=;f*)PTt'4F߄yRX' PݝOIyҞoGvHVzW@4|4u4e5f D|Ԁtw;jUVV<]Gj&] δwsQ_ IDAT~FW@ رc5vXq ӧO̙3*--UEE˛ϫVj*((Haaa m٭[7[}Q>}ԻwohСOl6]pafwfС޽[faƍ (!!iz#t*66V111Ns95ܧ\ F\@t(ĉP999 PQQrssgٺꫝ3.7.6 Iցc.!YMuV/V:b?y?e9eb9S,l,׵վ}0IvrrFrm\i{*))|+--56{2b %02obi5tQ//Sf9e;g94#--/4hР2dZ/99Ys;n-gϞ8p1IAAN:8[&ډ*++..0&L5GZg.,,TNNKu*--UvvS?^8JKh?Zfc" iyԨQFjjj68qeM4I|A&%%iȑ.4JMMfsjq5Ç;=Nef:*Kp 3;%--Mfamyъdjud{po;5u98=ou7 2 @ G81buvAAAJLLls"T Ipj(߿#FK.Nە5ڇ03Ε[303WSS9ܤI\w$.*8Ǐ 7y8=ڇ03dX.@'nt @3 F j~!pƑl?Z]y4|/W䚍7jӦM_:zrx]gXhiii2m0srr8\_GVTTL}W:~JJJT]]jYVK.+X;VC4#g:afIJHHО={αcTTT+|EeeN2?^uuul U׮] 4H#FP\\"##^Cq7YVZ&I6i?w-_Hpq9-/! VE.!! Vrxw iW^)+:rFsnݺiĈ-ի'N:"hԨQNNڶm[͙3ѕھ}vܩV[n ^\\L}+B3g 7@:rfvEJJn5Bii٣*33S%%%NO{7nƏI&)&&^VUUU]ݻJJJ{n3fVUIIIO0fYϟW~~~SMÆ wsٳg{n9rDYYYpx܈5Jcƌє)S4l0VTJUeRu`#]>RX8E9z"I]vW_mX ;l6i֭ڻwϛL&L&$)//iJgv5gϞ_mݺ}Y~Bڳg֭[`M|XSSS)??~ ̙35k,Se]ee>sڵzɾ.?^ϟWNNo.ɾ]N6M Hqqq.=yJJJݻZ~4~x]s5JHHO?srr*55K5;m۶)00Pwynv ;٬ɓ'U[[0`ƍD?ޡs.]l %%%)--MGQ}}}77~̙35gÓe;urKǀ ti aX{n%%%)==0XIIrssШQ4}t͜9ӧ\\jjjo>:ydݻ+T\\4vw_먟@߷o_%&&z";oq r[_ɤ9܄ <ɛ4iRaf6:\WWիW9ܴie2GiiW}EEEz~zp ZpaniiiNիW/]y啗>h édo6MvҦMtΟ?HF|;9sKѣG{9\c`֭Ӻu\_^{5/v:uJ/S,\0+ԩSNͣ!ڶmnݪ<~۷f͚9st NKGK6J'K'؃ z])! $4v=i{Mmsvۗ۷wx68z"I111 Xxi޽z7ڵ0ڱcvء'SttkTQQzK7ov^vҮ]4j(-ZH#GlwmRVV/B۷oo{ҜSN};Cާ裏>Ҏ;zM[In6MIIIڰa(d IDATC7S9u͛7W^;wn +N>ݡK/)55ZRhh͝;WGvx:W|#+//?bҥVXzX/5qnYݻw;tɓfԩz.99Y{oԡ[l?O?BbhJKKŋ} 4iΝ;^vvJKK}Tz뭷.ky233_]7xn&Z(++Ӿ}@[lQ]QXXw}Wk׮@C q|\3Ɍ_w4rs;(}C{K̎٤viRp4u4RR@su8*v4ltKJ], a]9v***?u߯_\Χ~7|]7\*;;[=~G?OWZ~v/[bZkΝ{4|YUUUzwgmg7|SOvKKKfmذA~n|*** 3o߾]J;\%'vG;SVVkj馛 ?: NzmٲE;vM7ݤKiiiXmh]UU֮]O?-XV*55U'N=Su\l}PTTz?5٬m۶i۶m;vV8m?:*ժ~ s@@|AY/5qny6 tأFrbegg{KTw+--СC[_XX^hΝ#}z%%%)99Y3g=ccoW:;f!t`{7K)ۻcKSnvϴ/5gr3}t3`J(_Ӳki˖-zl7UUU馛nrj܊ \R{HmVUk֬љ3g/L<{VݩV/:|PUVeCPXXUVСCWUU+))Io4h l6<ܚ0XO슊kZ#f֭[[{լY.ߥKwyE~vܩOSLڼT :ٷo߯}{;}⸭%\-mݭn&##C/֜9sh"yl^me|y֫O~M\9A\sK-a23*^b֭[hʔ)ڲeK&%%u0skATZJ^NK.ҥK;Νv?~ .ՒSak7xJ]]֮]M6; 7SMZfГO>ǝ6rgZjΞ=i:fiJOOעEGTP$IzPڵsuZ WKw3JN3D4vϝkk:TKNfYW=^f+P]+Vy;رCz<>۷Ok֬Qvv!PrrdsDcH>f3_իW{즃ŋ5mO9-ӬV׿*991bn`=KIIѪU7}޽{Kef_|Ѱsbӟ԰e4d2W^#[cٴn:߿_<϶u\RXX!?+6M7oVffz!8je}g?k,zn?q9-CrKKKspܕ _JJJ8Q/))5ժW^yE˖-FEK.JƗ}߾}կ_GGG+&&fddvqR_~?D=z嗵xb?~r:l5`vOl6^GCs=K6ФsIZў 2_V O;݈5?jw^dn=ɎcBwJB/rrrnzGz|}v}^dfɒ%ӟ_$6:xV\iȼV/_Cxbիb بNO=>SCo6M=[̒iJzbgX/k^?8p@կ";z}fƍ1_W/t =>QKs[.{-55Uw6o=C#_rrr/:J^Z>;wj:/񍆆=sJMM|}U]]|Im޼RZWՠ4x饗 zbg2#wUU/_ 1dO}#e\ 6hdr( :<ݰ0%&&jm+--mܹsz'|"0tM4R]WAA*rZcv4\].R{-b&iuR@ #w4Zo^L6$/Y S,_ge#JC.~*}t3wJ~ׯwc;9rD_ k]En:G$nZrJ}i /VXyTTTE2wѴiԿW^q)$xMJC.lL&=3`yEw)++Stt$iժU:s-ƺ<3g?/}Qeey=JLL4 >'JE-.t˃Rs5eK??k~8Mz~)f0]h"4/_fn3"[4+222O}34bEEE)44T555***҉'. 9cݺu;w$IfYO?4BCC5rH]qbQii tYkݻ5uTњ=zh$00PÇտ]y啊TxxBBBd2d2ti;vLp3l6__kuTrr>}e BCCu7"##(!!Aw}ۧTmm,Ycǎ4~\\&LQFѣBCCe6UUUsĉ:|MVU=VZ^zT_Gr~ĉ?tBCCeX.1::뉝dc=;ʕaj=#:uT8ũo߾֭vzUWW:~T˾C vՆҥK]XkVDDe2T^^'O*;;[py?K.[]ݸ[kjժUn IҥK3ϸ οZwth؀հa=_q [[03pӦMszSNU@@@+++KGvzRZZǏs(DD}Մ ШFiiiO\2477WR||vfYN7rHEDD9\DDFժ4͞0uưaôh"0[*99#p5\YY'x̍f-[+VhQUfox_l9{JC7>G?veo p_(u{=Nۃ:{bk?wٻ5?_*--UNN|Iݺuw]͞=[ jvͦJ[l͛!BϴpBkٲe_jʔ)7o"m߾]ׯw)tvZ%'?ӝy+88XӦMӌ3]9N$ ڻwz-9UCtޔRTTիW;5ȑ#C+pj8-_\;vի&Z-^k7|\/CCC5w\|͊ivpO>7nnfL&m۶Mk׮u8\ty=s7C#fVXЗ6m&OѣGW^ fSUUΞ=,߿W?}v4vX?^cƌQ߾}ս{wÇ-}[Kt5եK͘1C_Fƍk=##C~a=ôm=S.uynP޽[lVRR֯_RKA]2-[̩q4fM8Q0`"##wQU ({=V*fiOM{zrM3J\,2܅}1PT\QdeX_ras2sg;ͽF<{ w۷qy V޸q7n;#h0ǁo>ÇG6m$he44=Jf&B!(NVVΟ?_n+4~~~LˮFFF*:9%%'O.71(z0j(楩СCtի_eѓgΜ4!SyTjFOfNNN_e>yOښ:L㏲AВyyyy/DU5l̞= .#ؽk R0;൮͢~> dxX1}H|_.@PڒZdm 7n.%%;c0`l[\Y^z߿?/^8cḼ[ƿojժa׿իWȑ#\]|IIInݺa޽嶭R .]^B FPP6mڄ۷syfY&_2\v0i$Q۷oZjaܹ.{ TV F۷sH1cƠjժ9::wҥ ֯_={0w9r:uӜ']HNN.=zR gggo߾ȯUE{5jl'''xxx 00Dnn.N<{">>^LDAVZXT*v튑#GՕ{&MI&pVZUj2٤IPf2xrvL֬Yt:|U*wz̄\C4 :uꄎ;^ǂ h"AsRy\޽{[n&_+>} ''Gr{n˃_YO._UV1o۶- &i tDsR(B!8111LZl ZXVZ1%3kZ=Zo7>+U:u7>Sdff2r̕Vo2M8<*@KeǎڶmMnݺT?Kfggɓ'spy$%%q/xbӧOaÆInݺ~cMx̙/YiU*X`B ÀهYl}t-nc '3GmF/˯" )~PwI& z?_~%ҥ2e nݺUfʕ+cҤI1ydc֭\:tH`/>|8=Zj…;;ZZ'煝$&&Jq mڴ,I߾1ede;v @ևΝO?$ bٲeL'ׯ_888_q}eǤIPzucH\\6n(h^{ &L4FCvЮ];$$$`f{NB-D]0ؼys̟?pVVϟK K F_%WCѳgOt۶m/”^V¢E. ^F?/`^ɪ^zeh9[*GS@!B!dj׺uk}/UXǏsWS`|7&իWSNJ:tp辕(//OP+ggg Ņ|$*rrrpqT*6lIЩS'Ԯ]*UF PX|9/_>}0UӧBCCh6o,hB_R!!!ѣt邰04lj>vdd$weѲO>1cV^;w?5k`̙0`@ꄸvl"Dq|0nq(Ӂk~L@K. @lTjcƌ_AlmmڵksW^"sHH$u#F@vRJ8p`...ǪUеkW$hڕ=ϲ<6o̜-ݚ5k2-ߪj*Ѽrrr|rEJ0|ɫoPGiؽ{+͓'O`t:T*ۘ;wQ<)%KpTZ5gI^lذ!͛ɓ' \GL+55?Pƽy)5kŋKZ5ܹsrٰaCےA%$$pa_TjƯ7s IDATIIIرcdqȁqe˖o1|p o&fΜ}W"::ZTst_~~>͛\ |ʿe9(BcKēB!EyΟ?_n;F#jb 5j{mF T* aÆIZU: ݺuJl}վnݺ۷/ZlYf"ݻw gdd`\#GDHH/cԨQ8s 6l؀7oǎ c 0gJ-f0w2{IS%nc5Jn?4JŊfx;%3 MMJr<;;;L4 }W-CT*ÇKv_#==ݻwq]Y߿?ۇǏB ٳ'Fልe^9::ƍcz*-- OfjRGR5Eٳ'2ݽ{ǿ/cشiRRRں/m֭[7֭[Wn? t:\zgb煢/w(hGձcG4k -ŋ%?>ϪU L6MJM4%K7`ĉ&k b Z|<[[[̘1ssfnx<8::bҥnٲ%M9sTy<6mB֭%=?}U[z5s FO>UT5&:iE4TL!BQk7o;;;Q}VgbLӦM4EÆ z+˒ǠQQQkٲQg N0'xdR10f|w {鳊+bԩTS ըQ~).]N:[f͚ٳ'7nի&&T@~l2iӆRREXd x ѿXl,I=jDb#>zl" ;C|KntǷLҀs.ME$Kd.V^=Q+E }$9/rvvF~{;;;1ꫯnJiKÉ'+j uԑ 4o&5,11;S[GGG̞=[rۥIrPT2eQ<)ܹkJYT?"h=jժDA۹s'۫jL6MDb666:u*v:+W-&!hWY|}}_˒\,00[$m/ưo>*}Q:i- !B!š *61s'O2Y +bΜ9?'%%*((ܺZTQ<((HNc~^ׯ گjժTeqssCDDfΜʕ+CRapww2JAa.kHll,?.* &L [[[AP6l$UXnܸa%Զ5ZI/ (GPß.҅N \'*Q/:[޽h0c kNˆѣo1:wKڵk777+Yٳ^ȗ*KndO>ݻW8yE?%$$`͚5ˬh- !B!3?v*TdƍddVQFd'wܑ1㋍Eff&~7~]Vu]AR"88+V,5)f͚z(ׯ}ѣG{D?]=iʒ$&&w]?FU"6dD5@6i-ȭS2j׮/M :FYd9;;yoܸ![,T*VRP:t^ʗz=]c_Z5ԫWOKӦMvǎs1]tbJ>>>L%$$0/l 5jÍ{Hrr2bbb C^DKn"#G޽{_vt4 Hw6h}||~gaGQB3'N4ZjK?G6߃(ۅ pm+VСCe(Mtt4m۶Eݺue0\ݻWhFRreoKڋP*S'+WLm]]]1sLhQ23!B!D233ߨ2#0095\\\Hg-|s/ <==[V-xxxWn1w߿/a$XzmԪ]uߖm)HZNj:Ɠ'OpI"2S@p?ß)M vOGԶ*Pe TZhF*LoY=!BTKp?}1YAlKtBTϋ*U_:O>|֭m XÇs8p d(ё#GbqLĉ&^<.yQ&M3B2ٻw//6661co%1@cK%dfB!B"0 $\aoof ,9oVt)'s!c$%h?)| ԩSilmmWXA50j(y1Q{yy}EcX D.{ZߺJPpp^.p|HO <@T2m"cW.hPJtOWD^zs[cVrqqajQz{*Ftvv6nݺehLgFFs1&Zu{1"4YYY\/GƈVBQxҀLX<9y(//׮]13t֬Y~+YYS?4AcKedfB!B"V=cVZ1KMMťK$\1o)z^pc)MJLL~OL+<<ƨ}?`$p {E?y<~gۿtE樔AR(B!br<0Xy3OtM$<:NHԢ6m*IB+J"KCd@cQ퍶vF-xTl5b,Jҧ}]r5(((HI,˴߽{ZF=L7'EΞeRTԩw:D.|QxM6废O!;wHd8k.T*4)nn߾-c4g]rJ${///DDD(~] L1- !B!&Ze˖ Y'SSS&-s[KIP =Vffg~GN~ Ǐ"Yܼk3zb4vu(Qr_3=Y¿M㛀|#Tn \Æ71 ST1e%_u:n߾Hu<pڗ%^W2mذ~TRrYTV nnnFΓx}||&#??7odn߬UMEtss{DLƒ(S$bח{PXX(c4a]g>|(TU<<<>xyU>|NLd05KUnݺ́Y7or))9+11dAmԩSm~~>ɻk/ڙ"(- !B!&i ǀݻm/Z )Ih2U zؒxݎ;b\K'??'N':cǎH"z%x&fTN@QrNf\PCOHOH{yEqA 7P,l<b%222pE\xqT^:*Vr^p7ngmgcc///ܻw]jS%)&tM1ս0Q>RMÃJ*(///dfJ\WJ<~RUj84t:͛Ǖ<)tK2E4Q>K[*%3B!BL&##y ,s4@V **Hbb"SnCJff=W_je;wU*&M?iÇm6l۶ M4AѦMh4" b͚2%ClJ~ƶ c'88MھEn+ͥ*ZWy#Ăݼy/_6/RThѢ=Zn۫W"%%UV5hv>>>PՂa]F6%%Æ ܏XZҝΓ">}:v޽{s/H/55UJ^Pa*4N2s_~h<̏ Ti<ѣGqQZM6Lz=ߏ#FKAA8V{׼Bdee,AyR޸zPаISb*9&c$TT%-N̛3s2sFFcɒ%{Q%[@lRPbͭ,l o˕nc o?NVШ}bz=9cو6668p S۬,['o^|ۼy3SڵkKT|-y'ExK"@vvv1ּGGGE||~DEEaڴi]Q%{jO\KTef[JBxLnc?xy[voifx{piO?HvLWWW^z_>|}}K]6֭[K`̘17oSe˖.,,ҥKԾN:hV|Uys4īyR*3?E)9dfޘ,:CKaa!s[)X¬X׮]x111FHHdʚChrX%3B!B.==ytld0dj M1;w͛qq z1޽?'Nj1pZ PM䖲r:l^xxht̑[KnlKq-̙3<u{{{4ohڴeH)$$[FLLLm1w\L4I}Nvv6-ZSN1WTxw$yPf)^b, 'E,ykb\HYt:#!J{s7&%V&ևgu2X|pnnn={6~wܹ?O?pa bYhYe B!B-22!H&-- /^DfL ATTCuoOOODDD`ذaqQ%XjlmmѵkWY"h4*Mvvv {[Rؒz=ph0bc P23!'>>g|#,,b~?3g2'5,^y%gϞ֭[???xxxHKK۷q%A5j?XȠkODff)R2ejjʁ'&FcQ i|ܟIf]R0y;`…7`ɒ%Pr ,}ldL!B1h: IEGGcܸqPADQK\L74upttD>}лwo\xCLL, xb\HKd<'Izß96= h]1]n/2,\ϞRZ!C`Ȑ!F{i*۷GVVVZ0(z(|MܼySXV9sMc+sTFZ):O8991MOO1bxX*+;Ą?dfKf󒕕N^yk/Ioȑ }i[v~ŕ͛7sNrk?Y,yldL!B1HvظqozĦ!..*ZVkdwܺu ަ@Q%###jߗT_%+ky:wޕ(+{'w]wi%?Nx2s֒T*Pa#Ăl۶ 7n`n3fq2F,={J·~k4hӧrʒw܁1σ|%&oH='yՏ"T)kOZ;wƠA ~6n8L4і-[ C|h9K[* #B!Mzz:dDffnk U|IJJ ^j0B箳3իWc̙,pzojԨ!jއARsŒ~owzy6n@bN2fJfnT%xX4oܰ`{鐐`wy[nQ>:O&3 1gj߇>y2FCwѣG͕1~ofn+w!,xj_tRӦM1qR?B߾}dOA>>/0aY_2Yˆ$:Ojsf-:<\$]25k2JDx~gWZ'OOOL>pR 1ϟ?GJat9YRL[L&C: &y {_c>:tȈWga)߃+󊾇Y D*Lj5d|5 m8ɫS~@j$|5k}lo|}}1k,?| >|(xQQQx%Oonn.nܸzIUrrr,|nPKrWe.֨-끞^rpgB^ۿVgVVfΜY5HII_;w$N:PTL/^t z*JyRw̖ue2ŋ!JTn]]跷~2G֭[bn_N!ͥK åWYfٹܶxw`c b:6:DIuX|9.]hǹqt:4կ__p239sƨΝ&uMC1*-j~_Kn 8U!R]t O>ej[n]tM戔s)!3мysDGGc޽t ruuEPP:vh񌣣#<<s>h]0a6/4~رt JgcK%d $!B!D(v*JQ͛7g|8lZ}'sL!((HpT^]R0a߸cW_ƍK;UZ-  Q4ƣhзo_YFϞ= %3ZFPPK1ٳ&L0*8\רQ^^^Fddx{`qc^2q2{`XrP2s>}Ei#Č%%%1]6eF9 jժ2xxx`ȑF8V*uɓ 1BdDI<)ҴiS_O<~E*ggg$Gr4oޜkšwS{]5oޜw:ddcǎqU 1Bʗ'O2Z_YOP曢1l0?~)))MNNƶm0bQ}t4acKqB!BZ- ښz'DGG3 4,*U<+&Q,;%h?ggg}>|P֪]v?td:Hi~Orxgͻ5@T-w^ .>.B,ӧOY[5ѓ'O/sٙ~nΝ2FCΓ"K{Vd\>22*{L WR!((HaF>}DŐ#F@ZDUFF}WjU\|YоO<Ç.hkdkk=z`֭qqtI ؛7o~hۢE ԨQC̐.سg{57b~P-/89b vHa}_ArU*W9 |_n:H`:Ob۶mn݊2FT|"{˗/Ce)c޼y&]ݫ[nزe sUЫWܹ̑ɏbNCNNU&9iHKKcj5k֔9zO\sbvڅǏ3o׮WI|-!x/]{w᫵_:RYd2mA9b0%2=ak GGG#F#\YK#Y̿},=!bhZh2 fNf{gqK/YSN ~XNjOriԨ3AkZ̫WFTTN>ѣGld%zЄbǎE ;v~Ւ%KtRTRE0|I("3[i)KhX^T2fp않y֖̜\:Qr{p# w⸖RBAA+iO2sFF"-- g6uU+VD~}~G*bD '''0'ߺu ~~~2G<666ٳ'~gXz_سgnݺ2FDH>>]6>~Q?z=,YY޿<YYYck\Yx_hNKKҥKE/>^qj5F%fo φ02ejjTr__Çcꫯ!C>;OC3eo’Te\ӿW|ny?W)J&~mw~XU )((E_6l{zߘ8qI{;wĚ5kQxEM/ìcǎe^=Çؼy~7 RyB!bQ>}ʼ\dH&MbŊMLa+jP rSaa!bbb۟={;w#%%֭èQ0w\ݻ+w<={nj3DUBƍH1f|gعs'N8ԩS믿w?ƛoڪ $ǺuVXz >,X6lw}E?ěoKVp-y-/^ X Jy;@=dW2z}Go/&2&M0tXh*Lܼyضm PaUTs 1{l&II&ҥ >iiiOk.+>ʕ+qm{a" :O^zqU~C.\[J7(^|Fj)J[@Qܹs}ݻ$tJio˖-7o233%ѣG/l%97n Z~۶mOsҰaCуk?F\\dqO?~1 ,by_c֬Ykt:,_k׮w\UeKCYO)j֬{nj7 i !B!DEE17oF#sDjرcLcbb0~x#N>\AT* OТE ςj֭[>}%K)((Sp)=jժjժvvvųgCܺu zo!Z$u:Ξ=˔XU& V!\]]1m4̚5 S#)) III3L6 *UoVVlق?C{ )0%椧#"":z% >J7nڵk~'0vXIOAAbbbᅲk.ݛA0'jcƌ~ʵ^ǦMp꿰QQQذauի///Ɉ)S`]|=._\f;///mV 山i˗/cȑԩWeWt:ر۷o/u&!!{A޽hڴ)jiii;w.fϞmrxwqUݴ4̚5 }(Gbݺu+%9::bڴi4GK?Dѷo_Qs7Xz5ܹýo1b}_럒 2ǎc*ZRXXo .Z)s>T}E!BUdd$s[CV322dWVob$rԯ_,ųgTj^ŋK,ZNN{wUS#CF! $HB`DWiTAqM7J[FF/ Bb"B@YՐRU_ke-8INRu~γ0EΝտMz|M;Deegg[$nSԥK͚5K+V0:hȑF!x[)|]JK-m"4=DSG~5}/H7I%X/k1JNNƍ-'##CӧOԩS5l0.]?P~ m )-2XRRkjݺuر`$777fyxxW^^^JyzzjzGNXҜ9sԣG=Zz9yܹsG[aa}]luRpmiV{a͞=[III5jvj/yfVqÇ믿n>ΝOƋL&̙mZ/Jm߾]۷oW\\}*44}KJJW_iƍ\K;w[s|s^^^3X>;;[~ƌcq\c ?t$3O>ԫW/[\ tΝ$3߿L&S۷oirݻWrKm6lؠt#4fϞݠJ?umiÆ *((,쓙~*fׯ>sKOJv4eTĜ IZ{e};>M#?U߻]j*3P1chVU(*((G[JNNVBBjTK*..VNNw^}/j6`j X;v}ulᡠ (,,LQQQj׮㭚E֭5|͛7O%%%Vtwܹbcc~ FիdeeСC,>#qjݺqaNTeddشsEEvޭݻwWWVDD|}}驢"]|Y}}Vs^nZnϖl޽{w^yyy)&&F ?֬E)88خ 7Ԁ4n8{6 +zW]m۶ UXXӧOӡC[Oqq^~e-ZȦ88p^{55g%$$(11Q:uRPPUVVK.);;[ڿua2}2TOh*--j߼<]VԽ{wEGG+44T>>>2***RnnN8G*==j?u=,eee^Sxx:uꤨTPPӧOoÇm^P[1{l,Os{s%S^,|M_}Adfv_X<չsFε=K8ݫr1tQlЄ+O$&&ڜ,Uוܽ{wEDD4J\C)""nӸqo혖رcڧ4i***P5`=#vKK_+5O tcҏu$+%Oov˽қOH?̿ [IfjѣGkӦMڴi @2[&4w\jƌ6!L&f̘swMiiΟ?+33zdRLL4h i!iѢEzmj_]YOOOCzj JJJ͐\'7oΝku*,,Ծ}o>Eiܹ:qݎk u]l1^̟=EPPz)EGGKjeԩ*..և~h1***"I_}:nݺ| ooo5K:t\V||ϟgyƦJ=zTGu@t1au]˙3gt_~v?._kNЃ>hC2 X||c ~o ܌@ӳsN&%%90ǰ&|sڵ}[hX;Fc_={TQQQwAls6i$ }.\[nZlY X QRR.M(xnf)E)eu`,k7@}O˰<<<4m4=8 2,1chot-X@K.ŋ~=suֺ|a4k:i׮z)^5'2L0a,XPU5{l0k\f%W->^<󌂃f͚CA#ЩS' 24777i/4?W3~x{׬YcU\1QSs4j׮]W v -k+&&{q%6/%Iv1 K/ݠ>l6+--5kC%ki.?ѣѡH|ryFRSHd˒o-ҿJ/ZJAz*Uiz۪vC<==ukٲevY"!fF5kh.Uu+V8=ZjnR2$%''k̙NIZPjj͛Wÿ-[tx,رV\oݰD7т yd2Gx-裏jܹT7LJIIъ+Ԯ];ƫLRgT=;S]tʕ+խ[7)qejزe9bU\c ~oiMnnUBIv5}Ν; lQ^^{ڼ|||2`,&&FZyW/j@_ɓպukƎUVk׮N{ذaz'*6bYFwu*f :TW=#www`-#*-/uű}fYZIN^ɣJ%&9/ԩVXЉ|M0AWwΚmH/mfh*))s=;]tK/T'My{{k̘1zW5}tN@lur-sxrD6mOW``u۸Z2TuĠj%Ajek<==5m4׀)Sh4hCӲeϱcTPPmzW4a<\l2ԯ_?Wjj<<<'k_̙33fhٲe}2L6$JZQkb nɌ `׮]mppkWGll-1޳g|AUlE󯻜:udhɓ'm$Р M8QƍӞ={駟5& ) @C՝wiNBBVZ~[[lQIICSIII}tjرڸq>c]th7nK&jX,6{ˤI׫u3$/_Hc|ë5V ҥKm6[p]]IB#+#9sF/֩Slg6u˗/k͚57oCᡔ9R[n͛>QQQ9rnf6f_' P>}GWnnݎ;SÇ!>WUVOo߮ 744Tw}nfKRBtt{1?~\{p=Zկp$jڼy~m]rŮǿtF񀼭5eqڰa>c۵٬>}hĉNz&I#FPrrhÆ v{ ߮1cƸ,\IxxƎwyǢ֯_ &X0a~o,JK3 \\ZZZG816Mz]5<ѣ5f'Gd7jӦM}<8<\ ZyFh {n}TڲeKC}M7rp'hǎ:~xΝ;+!!AIIIr6F:|vܩݻw+//cy{{릛nջwoL*_4Luiڮ̑g鳷m-O׭ҠZ ғ=)Ez|رC[lQVVL&x߿ju3gԻ|֭ջwocKvv/^\{رc5uToǏܹst钊TZZjæ|rxN+++K;w띈dRTT~,Մ4뤼\_~nݪ_*..ѣ5p@5EO:>H;v>l6G2dhiNNlrWi޽ڵk<ؠĮ֭[O>2d:uꗵ),,Ԗ-[m68q¦c+>>zܢ囲r]OVvv;Nyyy*((Е+WT\\,oooWRlllϣW&=Uu:*yW҉'3uA~~~Ԇ14$3@]Jw=.IͱcT^^hs!9rĢ}up4.A1 ~;Ώ@Xo+WNr%%%z-j[o0ƀdfͅe5DICs~<Hڽ;tp4ʕ+u)>\ ԅ14$3TVJ/Z*.K%wOɈ]VV֯_hWYYիW?}PP&L@}@c@23\eknoC2hR,n׿U?M݊j˖-3}to.&jdrzHv)&&ƢזV}7U^^~.++K7|@c@23Է3)Ruo{H4I'Nmee֭[G}Ts`TU.\_~YotI޽|AF\8]zN0kO;=$MW~ԳgO>>6124MHfw \Kޒ6(mңH79.VVpp.\竸ua>|z-y{{Seee*))Q~~.]\UVV6FiӦl67X1+#@ҡO)&J%VRytG!tcݥ9HF:/nNǎtR-YHSRRӧO!׿nf1*4=W#]\ }Y՗5ߔRB\\^xXBFS+--MAAAF\RJ)=~1@=BCC3hӦMZv nݺiҤIڵѡ1Wc4:{HKKGy< r*5|u<<<mJKKUZZzךσp:õpǫj`4hEäoovN.=e~&4|ts/>'??_>cdR=4qDuXhҤI ך5k4A\jKZõԕ ܘ4R8yr@wֺ^Rq臥 O'NԶm駟*;;۩1DFFjȐ!JNNVhhS\A 23@#+3KLRNij#¤7I#`#::uJ_~ image/svg+xml ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.0908043 ccdproc-2.3.0/docs/_templates/0000755000076600000240000000000000000000000017316 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1640111994.1587255 ccdproc-2.3.0/docs/_templates/autosummary/0000755000076600000240000000000000000000000021704 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_templates/autosummary/base.rst0000644000076600000240000000037200000000000023352 0ustar00mattcraigstaff00000000000000{% 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. #}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_templates/autosummary/class.rst0000644000076600000240000000037300000000000023546 0ustar00mattcraigstaff00000000000000{% 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. #}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/_templates/autosummary/module.rst0000644000076600000240000000037400000000000023727 0ustar00mattcraigstaff00000000000000{% 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. #}././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/api.rst0000644000076600000240000000033600000000000016466 0ustar00mattcraigstaff00000000000000API Reference ============= .. automodapi:: ccdproc :skip: CCDData :skip: fits_ccddata_writer :skip: fits_ccddata_reader .. automodapi:: ccdproc.utils.slices .. _GitHub repo: https://github.com/astropy/ccdproc ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/authors_for_sphinx.rst0000644000076600000240000000006700000000000021642 0ustar00mattcraigstaff00000000000000Contributors ************ .. include:: ../AUTHORS.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/ccddata.rst0000644000076600000240000001640100000000000017300 0ustar00mattcraigstaff00000000000000.. _ccddata: Using the ``CCDData`` image class: I/O, properties and arithmetic ================================================================= Input and output ---------------- Getting data in +++++++++++++++ The tools in `ccdproc` accept only `~astropy.nddata.CCDData` objects, a subclass of `~astropy.nddata.NDData`. Creating a `~astropy.nddata.CCDData` object from any array-like data is easy: >>> import numpy as np >>> from astropy.nddata import CCDData >>> import ccdproc >>> ccd = CCDData(np.arange(10), unit="adu") Note that behind the scenes, `~astropy.nddata.NDData` creates references to (not copies of) your data when possible, so modifying the data in ``ccd`` will modify the underlying data. You are **required** to provide a unit for your data. The most frequently used units for these objects are likely to be ``adu``, ``photon`` and ``electron``, which can be set either by providing the string name of the unit (as in the example above) or from unit objects: >>> from astropy import units as u >>> ccd_photon = CCDData([1, 2, 3], unit=u.photon) >>> ccd_electron = CCDData([1, 2, 3], unit="electron") If you prefer *not* to use the unit functionality then use the special unit ``u.dimensionless_unscaled`` when you create your `~astropy.nddata.CCDData` images: >>> ccd_unitless = CCDData(np.zeros((10, 10)), ... unit=u.dimensionless_unscaled) A `~astropy.nddata.CCDData` object can also be initialized from a FITS file: >>> ccd = CCDData.read('my_file.fits', unit="adu") # doctest: +SKIP If there is a unit in the FITS file (in the ``BUNIT`` keyword), that will be used, but a unit explicitly provided in ``read`` will override any unit in the FITS file. There is no restriction at all on what the unit can be -- any unit in `astropy.units` or that you create yourself will work. In addition, the user can specify the extension in a FITS file to use: >>> ccd = CCDData.read('my_file.fits', hdu=1, unit="adu") # doctest: +SKIP If ``hdu`` is not specified, it will assume the data is in the primary extension. If there is no data in the primary extension, the first extension with data will be used. Getting data out ++++++++++++++++ A `~astropy.nddata.CCDData` object behaves like a numpy array (masked if the `~astropy.nddata.CCDData` mask is set) in expressions, and the underlying data (ignoring any mask) is accessed through ``data`` attribute: >>> ccd_masked = CCDData([1, 2, 3], unit="adu", mask=[0, 0, 1]) >>> res = 2 * np.ones(3) * ccd_masked >>> res.mask # one return value will be masked array([False, False, True]...) >>> 2 * np.ones(3) * ccd_masked.data # doctest: +FLOAT_CMP array([ 2., 4., 6.]) You can force conversion to a numpy array with: >>> np.asarray(ccd_masked) array([1, 2, 3]) >>> np.ma.array(ccd_masked.data, mask=ccd_masked.mask) # doctest: +SKIP A method for converting a `~astropy.nddata.CCDData` object to a FITS HDU list is also available. It converts the metadata to a FITS header: >>> hdulist = ccd_masked.to_hdu() You can also write directly to a FITS file: >>> ccd_masked.write('my_image.fits') Essential properties -------------------- Metadata ++++++++ When initializing from a FITS file, the ``header`` property is initialized using the header of the FITS file. Metadata is optional, and can be provided by any dictionary or dict-like object: >>> ccd_simple = CCDData(np.arange(10), unit="adu") >>> my_meta = {'observer': 'Edwin Hubble', 'exposure': 30.0} >>> ccd_simple.header = my_meta # or use ccd_simple.meta = my_meta Whether the metadata is case sensitive or not depends on how it is initialized. A FITS header, for example, is not case sensitive, but a python dictionary is. Masks and flags +++++++++++++++ Although not required when a `~astropy.nddata.CCDData` image is created you can also specify a mask and/or flags. A mask is a boolean array the same size as the data in which a value of ``True`` indicates that a particular pixel should be masked, *i.e.* not be included in arithmetic operations or aggregation. Flags are one or more additional arrays (of any type) whose shape matches the shape of the data. For more details on setting flags see `astropy.nddata.NDData`. WCS +++ The ``wcs`` attribute of `~astropy.nddata.CCDData` object can be set two ways. + If the `~astropy.nddata.CCDData` object is created from a FITS file that has WCS keywords in the header, the ``wcs`` attribute is set to a `astropy.wcs.WCS` object using the information in the FITS header. + The WCS can also be provided when the `~astropy.nddata.CCDData` object is constructed with the ``wcs`` argument. Either way, the ``wcs`` attribute is kept up to date if the `~astropy.nddata.CCDData` image is trimmed. Uncertainty +++++++++++ Pixel-by-pixel uncertainty can be calculated for you: >>> data = np.random.normal(size=(10, 10), loc=1.0, scale=0.1) >>> ccd = CCDData(data, unit="electron") >>> ccd_new = ccdproc.create_deviation(ccd, readnoise=5 * u.electron) See :ref:`create_deviation` for more details. You can also set the uncertainty directly, either by creating a `~astropy.nddata.StdDevUncertainty` object first: >>> from astropy.nddata.nduncertainty import StdDevUncertainty >>> uncertainty = 0.1 * ccd.data # can be any array whose shape matches the data >>> my_uncertainty = StdDevUncertainty(uncertainty) >>> ccd.uncertainty = my_uncertainty or by providing a `~numpy.ndarray` with the same shape as the data: >>> ccd.uncertainty = 0.1 * ccd.data # doctest: +ELLIPSIS INFO: array provided for uncertainty; assuming it is a StdDevUncertainty. [...] In this case the uncertainty is assumed to be `~astropy.nddata.StdDevUncertainty`. Using `~astropy.nddata.StdDevUncertainty` is required to enable error propagation in `~astropy.nddata.CCDData` If you want access to the underlying uncertainty use its ``.array`` attribute: >>> ccd.uncertainty.array # doctest: +ELLIPSIS array(...) Arithmetic with images ---------------------- Methods are provided to perform arithmetic operations with a `~astropy.nddata.CCDData` image and a number, an astropy `~astropy.units.Quantity` (a number with units) or another `~astropy.nddata.CCDData` image. Using these methods propagates errors correctly (if the errors are uncorrelated), take care of any necessary unit conversions, and apply masks appropriately. Note that the metadata of the result is *not* set if the operation is between two `~astropy.nddata.CCDData` objects. >>> result = ccd.multiply(0.2 * u.adu) >>> uncertainty_ratio = result.uncertainty.array[0, 0]/ccd.uncertainty.array[0, 0] >>> round(uncertainty_ratio, 5) # doctest: +FLOAT_CMP 0.2 >>> result.unit Unit("adu electron") .. note:: In most cases you should use the functions described in :ref:`reduction_toolbox` to perform common operations like scaling by gain or doing dark or sky subtraction. Those functions try to construct a sensible header for the result and provide a mechanism for logging the action of the function in the header. The arithmetic operators ``*``, ``/``, ``+`` and ``-`` are *not* overridden. .. note:: If two images have different WCS values, the wcs on the first `~astropy.nddata.CCDData` object will be used for the resultant object. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/changelog.rst0000644000076600000240000000013200000000000017636 0ustar00mattcraigstaff00000000000000.. _changelog: ************** Full Changelog ************** .. include:: ../CHANGES.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/citation.rst0000644000076600000240000000006400000000000017525 0ustar00mattcraigstaff00000000000000.. _ccdproc_citation: .. include:: ../CITATION.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/conduct.rst0000644000076600000240000000006600000000000017354 0ustar00mattcraigstaff00000000000000.. _ccdproc_coc: .. include:: ../CODE_OF_CONDUCT.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/docs/conf.py0000644000076600000240000001544700000000000016473 0ustar00mattcraigstaff00000000000000# -*- 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 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 try: from ConfigParser import ConfigParser except ImportError: 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 ---------------------------------------------------- # By default, highlight as Python 3. highlight_language = 'python3' # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.2' # 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') # 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 = '{0}, {1}'.format( datetime.datetime.now().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] ver = package.__version__ version = '.'.join(ver.split('.'))[:5] release = ver # -- 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 = 'bootstrap-ccdproc' html_theme_options = { 'logotext1': 'ccd', # white, semi-bold 'logotext2': 'proc', # orange, light 'logotext3': ':docs' # white, light } # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = '' # 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 = '' from os.path import join html_favicon = join('_static', 'ccd_proc.ico') # 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 = '{0} v{1}'.format(project, release) # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # Static files to copy after template files html_static_path = ['_static'] html_style = 'ccdproc.css' # -- 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 + u' 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 + u' Documentation', [author], 1)] # -- Options for the edit_on_github extension --------------------------------- if eval(setup_cfg.get('edit_on_github')): extensions += ['sphinx_astropy.ext.edit_on_github'] versionmod = __import__(setup_cfg['name'] + '.version') edit_on_github_project = setup_cfg['github_project'] if versionmod.version.release: edit_on_github_branch = "v" + versionmod.version.version else: edit_on_github_branch = "main" edit_on_github_source_root = "" edit_on_github_doc_root = "docs" # -- Resolving issue number to links in changelog ----------------------------- github_issues_url = 'https://github.com/astropy/ccdproc/issues/' # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- # nitpicky = True # nitpick_ignore = [] # # for line in open('nitpick-exceptions'): # if line.strip() == "" or line.startswith("#"): # continue # dtype, target = line.split(None, 1) # target = target.strip() # nitpick_ignore.append((dtype, six.u(target))) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/contributing.rst0000644000076600000240000000307700000000000020431 0ustar00mattcraigstaff00000000000000Reporting Issues and contributing code ====================================== Reporting Issues ---------------- If you have found a bug in ccdproc please report it by creating a new issue on the `ccdproc GitHub issue tracker `_. That requires creating a `free Github account `_ if you do not have one. Please include an example that demonstrates the issue and will allow the developers to reproduce and fix the problem, if possible. You may be asked to also provide information about your operating system and a full Python stack trace. The developers will walk you through obtaining a stack trace if it is necessary. Contributing code ----------------- Like the `Astropy`_ project, `ccdproc `_ is made both by and for its users. We accept contributions at all levels, spanning the gamut from fixing a typo in the documentation to developing a major new feature. We welcome contributors who will abide by the `Astropy Code of Conduct `_. Ccdproc follows the same workflow and coding guidelines as `Astropy`_. The following pages will help you get started with contributing fixes, code, or documentation (no git or GitHub experience necessary): * `How to make a code contribution `_ * `Coding Guidelines `_ * `Developer Documentation `_ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621385102.0 ccdproc-2.3.0/docs/default_config.rst0000644000076600000240000000043000000000000020661 0ustar00mattcraigstaff00000000000000.. _default_config: ccdproc's Default Configuration File ************************************ To customize this, copy it to your ``$HOME/.astropy/config/ccdproc.cfg``, uncomment the relevant configuration item(s), and insert your desired value(s). .. generate_config:: ccdproc ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/getting_started.rst0000644000076600000240000000737700000000000021120 0ustar00mattcraigstaff00000000000000Getting Started =============== A ``CCDData`` object can be created from a numpy array (masked or not) or from a FITS file: >>> import numpy as np >>> from astropy import units as u >>> from astropy.nddata import CCDData >>> import ccdproc >>> image_1 = CCDData(np.ones((10, 10)), unit="adu") An example of reading from a FITS file is ``image_2 = astropy.nddata.CCDData.read('my_image.fits', unit="electron")`` (the ``electron`` unit is defined as part of ``ccdproc``). The metadata of a ``CCDData`` object may be any dictionary-like object, including a FITS header. When a ``CCDData`` object is initialized from FITS file its metadata is a FITS header. The data is accessible either by indexing directly or through the ``data`` attribute: >>> sub_image = image_1[:, 1:-3] # a CCDData object >>> sub_data = image_1.data[:, 1:-3] # a numpy array See the documentation for `~astropy.nddata.CCDData` for a complete list of attributes. Most operations are performed by functions in `ccdproc`: >>> dark = CCDData(np.random.normal(size=(10, 10)), unit="adu") >>> dark_sub = ccdproc.subtract_dark(image_1, dark, ... dark_exposure=30*u.second, ... data_exposure=15*u.second, ... scale=True) See the documentation for `~ccdproc.subtract_dark` for more compact ways of providing exposure times. Every function returns a *copy* of the data with the operation performed. Every function in `ccdproc` supports logging through the addition of information to the image metadata. Logging can be simple -- add a string to the metadata: >>> dark_sub_gained = ccdproc.gain_correct(dark_sub, 1.5 * u.photon/u.adu, add_keyword='gain_corrected') Logging can be more complicated -- add several keyword/value pairs by passing a dictionary to ``add_keyword``: >>> my_log = {'gain_correct': 'Gain value was 1.5', ... 'calstat': 'G'} >>> dark_sub_gained = ccdproc.gain_correct(dark_sub, ... 1.5 * u.photon/u.adu, ... add_keyword=my_log) You might wonder why there is a `~ccdproc.gain_correct` at all, since the implemented gain correction simple multiplies by a constant. There are two things you get with `~ccdproc.gain_correct` that you do not get with multiplication: + Appropriate scaling of uncertainties. + Units The same advantages apply to operations that are more complex, like flat correction, in which one image is divided by another: >>> flat = CCDData(np.random.normal(1.0, scale=0.1, size=(10, 10)), ... unit='adu') >>> image_1_flat = ccdproc.flat_correct(image_1, flat) In addition to doing the necessary division, `~ccdproc.flat_correct` propagates uncertainties (if they are set). The function `~ccdproc.wcs_project` allows you to reproject an image onto a different WCS. To make applying the same operations to a set of files in a directory easier, use an `~ccdproc.image_collection.ImageFileCollection`. It constructs, given a directory, a `~astropy.table.Table` containing the values of user-selected keywords in the directory. It also provides methods for iterating over the files. The example below was used to find an image in which the sky background was high for use in a talk: >>> from ccdproc import ImageFileCollection >>> import numpy as np >>> from glob import glob >>> dirs = glob('/Users/mcraig/Documents/Data/feder-images/fixed_headers/20*-??-??') >>> for d in dirs: ... print(d) ... ic = ImageFileCollection(d, keywords='*') ... for data, fname in ic.data(imagetyp='LIGHT', return_fname=True): ... if data.mean() > 4000.: ... print(fname) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/docs/image_combination.rst0000644000076600000240000002312700000000000021364 0ustar00mattcraigstaff00000000000000.. _image_combination: Combining images and generating masks from clipping =================================================== .. note:: There are currently two interfaces to image combination. One is through the `~ccdproc.Combiner` class, the other through the `~ccdproc.combine` function. They offer *almost* identical capabilities. The primary difference is that `~ccdproc.combine` allows you to place an upper limit on the amount of memory used. .. note:: Image combination performance is substantially better if you install the `bottleneck`_ package, especially when using a median. .. _bottleneck: https://github.com/pydata/bottleneck The first step in combining a set of images is creating a `~ccdproc.Combiner` instance: >>> from astropy import units as u >>> from astropy.nddata import CCDData >>> from ccdproc import Combiner >>> import numpy as np >>> ccd1 = CCDData(np.random.normal(size=(10,10)), ... unit=u.adu) >>> ccd2 = ccd1.copy() >>> ccd3 = ccd1.copy() >>> combiner = Combiner([ccd1, ccd2, ccd3]) The combiner task really combines two things: generation of masks for individual images via several clipping techniques and combination of images, with optional weighting of images for some of the combination methods. .. _clipping: Image masks and clipping ------------------------ There are currently three methods of clipping. None affect the data directly; instead each constructs a mask that is applied when images are combined. Masking done by clipping operations is combined with the image mask provided when the `~ccdproc.Combiner` is created. Min/max clipping ++++++++++++++++ `~ccdproc.Combiner.minmax_clipping` masks all pixels above or below user-specified levels. For example, to mask all values above the value ``0.1`` and below the value ``-0.3``: >>> combiner.minmax_clipping(min_clip=-0.3, max_clip=0.1) Either ``min_clip`` or ``max_clip`` can be omitted. Sigma clipping ++++++++++++++ For each pixel of an image in the combiner, `~ccdproc.combiner.Combiner.sigma_clipping` masks the pixel if is more than a user-specified number of deviations from the central value of that pixel in the list of images. The `~ccdproc.combiner.Combiner.sigma_clipping` method is very flexible: you can specify both the function for calculating the central value and the function for calculating the deviation. The default is to use the mean (ignoring any masked pixels) for the central value and the standard deviation (again ignoring any masked values) for the deviation. You can mask pixels more than 5 standard deviations above or 2 standard deviations below the median with >>> combiner.sigma_clipping(low_thresh=2, high_thresh=5, func=np.ma.median) .. note:: Numpy masked median can be very slow in exactly the situation typically encountered in reducing ccd data: a cube of data in which one dimension (in the case the number of frames in the combiner) is much smaller than the number of pixels. Extrema clipping ++++++++++++++++ For each pixel position in the input arrays, the algorithm will mask the highest ``nhigh`` and lowest ``nlow`` pixel values. The resulting image will be a combination of ``Nimages-nlow-nhigh`` pixel values instead of the combination of ``Nimages`` worth of pixel values. You can mask the lowest pixel value and the highest two pixel values with: >>> combiner.clip_extrema(nlow=1, nhigh=2) Iterative clipping ++++++++++++++++++ To clip iteratively, continuing the clipping process until no more pixels are rejected, loop in the code calling the clipping method: >>> old_n_masked = 0 # dummy value to make loop execute at least once >>> new_n_masked = combiner.data_arr.mask.sum() >>> while (new_n_masked > old_n_masked): ... combiner.sigma_clipping(func=np.ma.median) ... old_n_masked = new_n_masked ... new_n_masked = combiner.data_arr.mask.sum() Note that the default values for the high and low thresholds for rejection are 3 standard deviations. Image combination ----------------- Image combination is straightforward; to combine by taking the average, excluding any pixels mapped by clipping: >>> combined_average = combiner.average_combine() # doctest: +IGNORE_WARNINGS Performing a median combination is also straightforward, but can be slow: >>> combined_median = combiner.median_combine() # doctest: +IGNORE_WARNINGS Combination with image scaling ++++++++++++++++++++++++++++++ In some circumstances it may be convenient to scale all images to some value before combining them. Do so by setting `~ccdproc.Combiner.scaling`: >>> scaling_func = lambda arr: 1/np.ma.average(arr) >>> combiner.scaling = scaling_func # doctest: +IGNORE_WARNINGS >>> combined_average_scaled = combiner.average_combine() # doctest: +IGNORE_WARNINGS This will normalize each image by its mean before combining (note that the underlying images are *not* scaled; scaling is only done as part of combining using `~ccdproc.Combiner.average_combine` or `~ccdproc.Combiner.median_combine`). Weighting images during image combination +++++++++++++++++++++++++++++++++++++++++ There are times when different images need to have different weights during image combination. For example, different images may have different exposure times. When combining image mosaics, each pixel may need a different weight depending on how much overlap there is between the images that make up the mosaic. Both weighting by image and pixel-wise weighting are done by setting `~ccdproc.Combiner.weights`. Recall that in the example on this page three images, each ``10 x 10`` pixels, are being combined. To weight the three images differently, set `~ccdproc.Combiner.weights` to an array for length three: >>> combiner.weights = np.array([0.5, 1, 2.0]) >>> combine_weighted_by_image = combiner.average_combine() # doctest: +IGNORE_WARNINGS To use pixel-wise weighting set `~ccdproc.Combiner.weights` to an array that matches the number of images and image shape, in this case ``3 x 10 x 10``: >>> combiner.weights = np.random.random_sample([3, 10, 10]) >>> combine_weighted_by_image = combiner.average_combine() # doctest: +IGNORE_WARNINGS .. note:: Weighting does **not** work when using the median to combine images. It works only for combining by average or by summation. .. _combination_with_IFC: Image combination using `~ccdproc.ImageFileCollection` ------------------------------------------------------ There are a couple of ways that image combination can be done if you are using `~ccdproc.ImageFileCollection` to :ref:`manage a folder of images `. For this example, a temporary folder with images in it is created: >>> from tempfile import mkdtemp >>> from pathlib import Path >>> import numpy as np >>> from astropy.nddata import CCDData >>> from ccdproc import ImageFileCollection, Combiner, combine >>> >>> ccd = CCDData(np.ones([5, 5]), unit='adu') >>> >>> # Make a temporary folder as a path object >>> image_folder = Path(mkdtemp()) >>> # Put several copies ccd in the temporary folder >>> _ = [ccd.write(image_folder / f"ccd-{i}.fits") for i in range(3)] >>> ifc = ImageFileCollection(image_folder) To combine images using the `~ccdproc.Combiner` class you can use the ``ccds`` method of the `~ccdproc.ImageFileCollection`: >>> c = Combiner(ifc.ccds()) >>> avg_combined = c.average_combine() There two ways combine images using the `~ccdproc.combine` function. If the images are large enough to combine in memory, then use the file names as the argument to `~ccdproc.combine`, like this: >>> avg_combo_mem_lim = combine(ifc.files_filtered(include_path=True), ... mem_limit=1e9) If memory use is not an issue, then the ``ccds`` method can be used here too: >>> avg_combo = combine(ifc.ccds()) .. _reprojection: Combination with image transformation and alignment --------------------------------------------------- .. note:: **Flux conservation** Whether flux is conserved in performing the reprojection depends on the method you use for reprojecting and the extent to which pixel area varies across the image. `~ccdproc.wcs_project` rescales counts by the ratio of pixel area *of the pixel indicated by the keywords* ``CRPIX`` of the input and output images. The reprojection methods available are described in detail in the documentation for the `reproject project`_; consult those documents for details. You should carefully check whether flux conservation provided in CCDPROC is adequate for your needs. Suggestions for improvement are welcome! Align and then combine images based on World Coordinate System (WCS) information in the image headers in two steps. First, reproject each image onto the same footprint using `~ccdproc.wcs_project`. The example below assumes you have an image with WCS information and another image (or WCS) onto which you want to project your images: .. doctest-skip:: >>> from ccdproc import wcs_project >>> reprojected_image = wcs_project(input_image, target_wcs) Repeat this for each of the images you want to combine, building up a list of reprojected images: .. doctest-skip:: >>> reprojected = [] >>> for img in my_list_of_images: ... new_image = wcs_project(img, target_wcs) ... reprojected.append(new_image) Then, combine the images as described above for any set of images: .. doctest-skip:: >>> combiner = Combiner(reprojected) >>> stacked_image = combiner.average_combine() # doctest: +IGNORE_WARNINGS .. _reproject project: http://reproject.readthedocs.io/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/docs/image_management.rst0000644000076600000240000002027000000000000021172 0ustar00mattcraigstaff00000000000000.. _image_management: Image Management ================ .. _image_collection: Working with a directory of images ---------------------------------- For the sake of argument all of the examples below assume you are working in a directory that contains FITS images. The class :class:`~ccdproc.image_collection.ImageFileCollection` is meant to make working with a directory of FITS images easier by allowing you select the files you act on based on the values of FITS keywords in their headers or based on Unix shell-style filename matching. It is initialized with the name of a directory containing FITS images and a list of FITS keywords you want the :class:`~ccdproc.image_collection.ImageFileCollection` to be aware of. An example initialization looks like:: >>> from ccdproc import ImageFileCollection >>> from ccdproc.utils.sample_directory import sample_directory_with_files >>> keys = ['imagetyp', 'object', 'filter', 'exposure'] >>> dir = sample_directory_with_files() >>> ic1 = ImageFileCollection(dir, keywords=keys) # only keep track of keys You can use the wildcard ``*`` in place of a list to indicate you want the collection to use all keywords in the headers:: >>> ic_all = ImageFileCollection(dir, keywords='*') Normally identification of FITS files is done by looking at the file extension and including all files with the correct extension. If the files are not compressed (e.g. not gzipped) then you can force the image collection to open each file and check from its contents whether it is FITS by using the ``find_fits_by_reading`` argument:: >> ic_from_content = ImageFileCollection(dir, find_fits_by_reading=True) You can indicate filename patterns to include or exclude using Unix shell-style expressions. For example, to include all filenames that begin with ``1d_`` but not ones that include the word ``bad``, you could do:: >>> ic_all = ImageFileCollection(dir, glob_include='1d_*', ... glob_exclude='*bad*') # doctest: +IGNORE_WARNINGS Alternatively, you can create the collection with an explicit list of file names:: >>> ic_names = ImageFileCollection(filenames=['a.fits', '/some/path/b.fits.gz']) Most of the useful interaction with the image collection is via its ``.summary`` property, a :class:`~astropy.table.Table` of the value of each keyword for each file in the collection:: >>> ic1.summary.colnames ['file', 'imagetyp', 'object', 'filter', 'exposure'] >>> ic_all.summary.colnames # doctest: +SKIP # long list of keyword names omitted Note that the name of the file is automatically added to the table as a column named ``file``. Selecting files --------------- Selecting the files that match a set of criteria, for example all images in the I band with exposure time less than 60 seconds you could do:: >>> matches = (ic1.summary['filter'] == 'R') & (ic1.summary['exposure'] < 15) >>> my_files = ic1.summary['file'][matches] The column ``file`` is added automatically when the image collection is created. For more simple selection, when you just want files whose keywords exactly match particular values, say all I band images with exposure time of 30 seconds, there is a convenience method ``.files_filtered``:: >>> my_files = ic1.files_filtered(filter='R', exposure=15) The optional arguments to ``files_filtered`` are used to filter the list of files. Python regular expression patterns can also be used as the value if the ``regex_match`` flag is set. For example, to find all of the images whose object is in the Kelt exoplanet survey, you might do:: >>> my_files = ic1.files_filtered(regex_match=True, object='kelt.*') To get all of the images that have image type ``BIAS`` or ``LIGHT`` you can also use a regular expression pattern:: >>> my_files = ic1.files_filtered(regex_match=True, ... imagetyp='bias|light') Note that regular expression is different, and much more flexible than, file name matching (or "globbing") at the command line. The `Python documentation on the re module `_ is useful for learning about regular expressions. Finally, a new `~ccdproc.ImageFileCollection` can be created with by providing a list of keywords. The example below makes a new collection containing the files whose ``imagetyp`` is ``BIAS`` or ``LIGHT``:: >>> new_ic = ic1.filter(regex_match=True, ... imagetyp='bias|light') Sorting files ------------- Sometimes it is useful to bring the files into a specific order, e.g. if you make a plot for each object you probably want all images of the same object next to each other. To do this, the images in a collection can be sorted with the ``sort`` method using the fits header keys in the same way you would sort a :class:`~astropy.table.Table`:: >>> ic1.sort(['exposure', 'imagetyp']) Iterating over hdus, headers, data, or ccds ------------------------------------------- Four methods are provided for iterating over the images in the collection, optionally filtered by keyword values. For example, to iterate over all of the I band images with exposure of 30 seconds, performing some basic operation on the data (very contrived example):: >>> for hdu in ic1.hdus(imagetyp='LiGhT', filter='R', exposure=15): ... hdu.header['exposure'] ... new_data = hdu.data - hdu.data.mean() 15.0 Note that the names of the arguments to ``hdus`` here are the names of FITS keywords in the collection and the values are the values of those keywords you want to select. Note also that string comparisons are not case sensitive. The other iterators are ``headers``, ``data``, and ``ccds``. All of them have the option to also provide the file name in addition to the hdu (or header or data):: >>> for hdu, fname in ic1.hdus(return_fname=True, ... imagetyp='LiGhT', filter='R', exposure=15): ... hdu.header['meansub'] = True ... hdu.data = hdu.data - hdu.data.mean() ... hdu.writeto(fname + '.new') That last use case, doing something to several files and saving them somewhere afterwards, is common enough that the iterators provide arguments to automate it. Automatic saving from the iterators ----------------------------------- There are three ways of triggering automatic saving. 1. One is with the argument ``save_with_name``; it adds the value of the argument to the file name between the original base name and extension. The example below has (almost) the same effect of the example above, subtracting the mean from each image and saving to a new file:: >>> for hdu in ic1.hdus(save_with_name='_new', ... imagetyp='LiGhT', filter='R', exposure=15): ... hdu.header['meansub'] = True ... hdu.data = hdu.data - hdu.data.mean() It saves, in the ``location`` of the image collection, a new FITS file with the mean subtracted from the data, with ``_new`` added to the name; as an example, if one of the files iterated over was ``intput001.fit`` then a new file, in the same directory, called ``input001_new.fit`` would be created. 2. You can also provide the directory to which you want to save the files with ``save_location``; note that you do not need to actually do anything to the hdu (or header or data) to cause the copy to be made. The example below copies all of the I band images with 30 second exposure from the original location to ``other_dir``:: >>> for hdu in ic1.hdus(save_location='other_dir', ... imagetyp='LiGhT', filter='I', exposure=30): # doctest: +SKIP ... pass This option can be combined with the previous one to also give the files a new name. 3. Finally, if you want to live dangerously, you can overwrite the files in the same location with the ``overwrite`` argument; use it carefully because it preserves no backup. The example below replaces each of the I band images with 30 second exposure with a file that has had the mean subtracted:: >>> for hdu in ic1.hdus(overwrite=True, ... imagetyp='LiGhT', filter='R', exposure=15): # doctest: +SKIP ... hdu.header['meansub'] = True ... hdu.data = hdu.data - hdu.data.mean() .. note:: This functionality is not currently available on Windows. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1621385102.0 ccdproc-2.3.0/docs/index.rst0000644000076600000240000000314500000000000017025 0ustar00mattcraigstaff00000000000000.. the "raw" directive below is used to hide the title in favor of just the logo being visible .. raw:: html ======= ccdproc ======= .. raw:: html .. only:: latex .. image:: _static/ccdproc_banner.pdf **Ccdproc** is is an `Astropy`_ `affiliated package `_ for basic data reductions of CCD images. It provides the essential tools for processing of CCD images in a framework that provides error propagation and bad pixel tracking throughout the reduction process. .. Important:: If you use `ccdproc`_ for a project that leads to a publication, whether directly or as a dependency of another package, please include an :doc:`acknowledgment and/or citation `. Detailed, step-by-step guide ---------------------------- In addition to the documentation here, a detailed guide to the topic of CCD data reduction using ``ccdproc`` and other `astropy`_ tools is available here: https://mwcraig.github.io/ccd-as-book/00-00-Preface Getting started --------------- .. toctree:: :maxdepth: 1 install overview getting_started citation contributing conduct authors_for_sphinx changelog license Using `ccdproc` --------------- .. toctree:: :maxdepth: 2 ccddata image_combination reduction_toolbox image_management reduction_examples default_config .. toctree:: :maxdepth: 1 api ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1616184398.0 ccdproc-2.3.0/docs/install.rst0000644000076600000240000000346500000000000017371 0ustar00mattcraigstaff00000000000000************ Installation ************ Requirements ============ Ccdproc has the following requirements: - `Astropy`_ v2.0 or later - `NumPy `_ - `SciPy `_ - `scikit-image `_ - `astroscrappy `_ - `reproject `_ One easy way to get these dependencies is to install a python distribution like `anaconda`_. Installing ccdproc ================== Using pip ------------- To install ccdproc with `pip `_, simply run:: pip install ccdproc Using conda ------------- To install ccdproc with `anaconda`_, run:: conda install -c conda-forge ccdproc Building from source ==================== Obtaining the source packages ----------------------------- Source packages ^^^^^^^^^^^^^^^ The latest stable source package for ccdproc can be `downloaded here `_. Development repository ^^^^^^^^^^^^^^^^^^^^^^ The latest development version of ccdproc can be cloned from github using this command:: git clone git://github.com/astropy/ccdproc.git Building and Installing ----------------------- To build ccdproc (from the root of the source tree):: python setup.py build To install ccdproc (from the root of the source tree):: pip install . To set up a development install in which changes to the source are immediately reflected in the installed package (from the root of the source tree):: pip install -e . Testing a source code build of ccdproc -------------------------------------- The easiest way to test that your ccdproc built correctly (without installing ccdproc) is to run this from the root of the source tree:: python setup.py test .. _anaconda: https://anaconda.com/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1552064604.0 ccdproc-2.3.0/docs/license.rst0000644000076600000240000000023500000000000017335 0ustar00mattcraigstaff00000000000000.. _license: ******* License ******* Ccdproc License =============== Ccdproc is licensed under a 3-clause BSD style license: .. include:: ../LICENSE.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534209296.0 ccdproc-2.3.0/docs/make.bat0000644000076600000240000001064100000000000016570 0ustar00mattcraigstaff00000000000000@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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/overview.rst0000644000076600000240000000157400000000000017570 0ustar00mattcraigstaff00000000000000Overview ======== .. note:: `ccdproc` works only with astropy version 2.0 or later. The `ccdproc` package provides: + An image class, `~astropy.nddata.CCDData`, that includes an uncertainty for the data, units and methods for performing arithmetic with images including the propagation of uncertainties. + A set of functions performing common CCD data reduction steps (e.g. dark subtraction, flat field correction) with a flexible mechanism for logging reduction steps in the image metadata. + A function for reprojecting an image onto another WCS, useful for stacking science images. The actual reprojection is done by the `reproject package `_. + A class for combining and/or clipping images, `~ccdproc.Combiner`, and associated functions. + A class, `~ccdproc.ImageFileCollection`, for working with a directory of images. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1567445431.0 ccdproc-2.3.0/docs/reduction_examples.rst0000644000076600000240000000156300000000000021612 0ustar00mattcraigstaff00000000000000Reduction examples and tutorial =============================== Here are some examples and different repositories using `ccdproc`. * `Extended guide to image calibration using ccdproc`_ * `ipython notebook`_ * `WHT basic reductions`_ * `pyhrs`_ * `reduceccd`_ * `astrolib`_ * `mont4k_reduction`_ *Processes multi-image-extension FITS files* .. _Extended guide to image calibration using ccdproc: https://mwcraig.github.io/ccd-as-book/00-00-Preface .. _ipython notebook: http://nbviewer.ipython.org/gist/mwcraig/06060d789cc298bbb08e .. _WHT basic reductions: https://github.com/crawfordsm/wht_reduction_scripts/blob/master/wht_basic_reductions.py .. _pyhrs: https://github.com/saltastro/pyhrs .. _reduceccd: https://github.com/rgbIAA/reduceccd .. _astrolib: https://github.com/yucelkilic/astrolib .. _mont4k_reduction: https://github.com/bjweiner/ARTN/tree/master/mont4k_pipeline ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1577217856.0 ccdproc-2.3.0/docs/reduction_toolbox.rst0000644000076600000240000005135400000000000021465 0ustar00mattcraigstaff00000000000000.. _reduction_toolbox: Reduction toolbox ================= .. note:: This is not intended to be an introduction to image reduction. While performing the steps presented here may be the correct way to reduce data in some cases, it is not correct in all cases. A much more detailed guide to CCD data reduction is `available `_ Logging in `ccdproc` -------------------- All logging in `ccdproc` is done in the sense of recording the steps performed in image metadata. if you want to do `logging in the python sense of the word `_ please see those docs. There are basically three logging options: 1. Implicit logging: No setup or keywords needed, each of the functions below adds a note to the metadata when it is performed. 2. Explicit logging: You can specify what information is added to the metadata using the ``add_keyword`` argument for any of the functions below. 3. No logging: If you prefer no logging be done you can "opt-out" by calling each function with ``add_keyword=None``. .. _create_deviation: Gain correct and create deviation image ---------------------------------------- Uncertainty +++++++++++ An uncertainty can be calculated from your data with `~ccdproc.create_deviation`: >>> from astropy import units as u >>> import numpy as np >>> from astropy.nddata import CCDData >>> import ccdproc >>> img = np.random.normal(loc=10, scale=0.5, size=(100, 232)) >>> data = CCDData(img, unit=u.adu) >>> data_with_deviation = ccdproc.create_deviation( ... data, gain=1.5 * u.electron/u.adu, ... readnoise=5 * u.electron) >>> data_with_deviation.header['exposure'] = 30.0 # for dark subtraction The uncertainty, :math:`u_{ij}`, at pixel :math:`(i,~j)` with value :math:`p_{ij}` is calculated as .. math:: u_{ij} = \left(g * p_{ij} + \sigma_{rn}^2\right)^{\frac{1}{2}}, where :math:`\sigma_{rn}` is the read noise. Gain is only necessary when the image units are different than the units of the read noise, and is used only to calculate the uncertainty. The data itself is not scaled by this function. As with all of the functions in `ccdproc`, *the input image is not modified*. In the example above the new image ``data_with_deviation`` has its uncertainty set. Gain ++++ To apply a gain to an image, do: >>> gain_corrected = ccdproc.gain_correct(data_with_deviation, 1.5*u.electron/u.adu) The result ``gain_corrected`` has its data *and uncertainty* scaled by the gain and its unit updated. There are several ways to provide the gain, among them as an `astropy.units.Quantity`, as in the example above, as a `ccdproc.Keyword`. See to documentation for `~ccdproc.gain_correct` for details. Clean image ----------- There are two ways to clean an image of cosmic rays. One is to use clipping to create a mask for a stack of images, as described in :ref:`clipping`. The other is to replace, in a single image, each pixel that is several standard deviations from a central value in a region surrounding that pixel. The methods below describe how to do that. LACosmic ++++++++ The lacosmic technique identifies cosmic rays by identifying pixels based on a variation of the Laplacian edge detection. The algorithm is an implementation of the code describe in van Dokkum (2001) [1]_ as implemented in [astroscrappy](https://github.com/astropy/astroscrappy) [2]_. Use this technique with `~ccdproc.cosmicray_lacosmic`: >>> cr_cleaned = ccdproc.cosmicray_lacosmic(gain_corrected, sigclip=5) .. note:: By default, `~ccdproc.cosmicray_lacosmic` multiplies the image by the gain; prior to version 2.1 it did so without changing the units of the image which could result in incorrect results. There are two ways to correctly invoke `~ccdproc.cosmicray_lacosmic`: + Supply a gain-corrected image, in units of ``electron``, and set ``gain=1.0`` (the default value) in `~ccdproc.cosmicray_lacosmic`. + Supply an image in ``adu`` and set the ``gain`` argument of `~ccdproc.cosmicray_lacosmic` to the appropriate value for your instrument. Ideally, pass in a ``gain`` with units, but if units are omitted the will be assumed to be ``electron/adu``. median ++++++ Another cosmic ray cleaning algorithm available in ccdproc is `~ccdproc.cosmicray_median` that is analogous to iraf.imred.crutil.crmedian. This technique can be used with `ccdproc.cosmicray_median`: >>> cr_cleaned = ccdproc.cosmicray_median(gain_corrected, mbox=11, ... rbox=11, gbox=5) Although `ccdproc` provides functions for identifying outlying pixels and for calculating the deviation of the background you are free to provide your own error image instead. There is one additional argument, ``gbox``, that specifies the size of the box, centered on a outlying pixel, in which pixel should be grown. The argument ``rbox`` specifies the size of the box used to calculate a median value if values for bad pixels should be replaced. Indexing: python and FITS ------------------------- Overscan subtraction and image trimming are done with two separate functions. Both are straightforward to use once you are familiar with python's rules for array indexing; both have arguments that allow you to specify the part of the image you want in the FITS standard way. The difference between python and FITS indexing is that python starts indexes at 0, FITS starts at 1, and the order of the indexes is switched (FITS follows the FORTRAN convention for array ordering, python follows the C convention). The examples below include both python-centric versions and FITS-centric versions to help illustrate the differences between the two. Consider an image from a FITS file in which ``NAXIS1=232`` and ``NAXIS2=100``, in which the last 32 columns along ``NAXIS1`` are overscan. In FITS parlance, the overscan is described by the region ``[201:232, 1:100]``. If that image has been read into a python array ``img`` by `astropy.io.fits` then the overscan is ``img[0:100, 200:232]`` (or, more compactly ``img[:, 200:])``, the starting value of the first index implicitly being zero, and the ending value for both indices implicitly the last index). One aspect of python indexing may particularly surprising to newcomers: indexing goes up to *but not including* the end value. In ``img[0:100, 200:232]`` the end value of the first index is 99 and the second index is 231, both what you would expect given that python indexing starts at zero, not one. Those transitioning from IRAF to ccdproc do not need to worry about this too much because the functions for overscan subtraction and image trimming both allow you to use the familiar ``BIASSEC`` and ``TRIMSEC`` conventions for specifying the overscan and region to be retained in a trim. Subtract overscan and trim images --------------------------------- .. note:: + Images reduced with `ccdproc` do **NOT** have to come from FITS files. The discussion below is intended to ease the transition from the indexing conventions used in FITS and IRAF to python indexing. + No bounds checking is done when trimming arrays, so indexes that are too large are silently set to the upper bound of the array. This is because `numpy`, which provides the infrastructure for the arrays in `ccdproc` has this behavior. Overscan subtraction ++++++++++++++++++++ To subtract the overscan in our image from a FITS file in which ``NAXIS1=232`` and ``NAXIS2=100``, in which the last 32 columns along ``NAXIS1`` are overscan, use `~ccdproc.subtract_overscan`: >>> # python-style indexing first >>> oscan_subtracted = ccdproc.subtract_overscan(cr_cleaned, ... overscan=cr_cleaned[:, 200:], ... overscan_axis=1) >>> # FITS/IRAF-style indexing to accomplish the same thing >>> oscan_subtracted = ccdproc.subtract_overscan(cr_cleaned, ... fits_section='[201:232,1:100]', ... overscan_axis=1) **Note well** that the argument ``overscan_axis`` *always* follows the python convention for axis ordering. Since the order of the indexes in the ``fits_section`` get switched in the (internal) conversion to a python index, the overscan axis ends up being the *second* axis, which is numbered 1 in python zero-based numbering. With the arguments in this example the overscan is averaged over the overscan columns (i.e. 200 through 231) and then subtracted row-by-row from the image. The ``median`` argument can be used to median combine instead. This example is not very realistic: typically one wants to fit a low-order polynomial to the overscan region and subtract that fit: >>> from astropy.modeling import models >>> poly_model = models.Polynomial1D(1) # one-term, i.e. constant >>> oscan_subtracted = ccdproc.subtract_overscan(cr_cleaned, ... overscan=cr_cleaned[:, 200:], ... overscan_axis=1, ... model=poly_model) See the documentation for `astropy.modeling.polynomial` for more examples of the available models and for a description of creating your own model. Trim an image +++++++++++++ The overscan-subtracted image constructed above still contains the overscan portion. We are assuming came from a FITS file in which ``NAXIS1=232`` and ``NAXIS2=100``, in which the last 32 columns along ``NAXIS1`` are overscan. Trim it using `~ccdproc.trim_image`,shown below in both python- style and FITS-style indexing: >>> # FITS-style: >>> trimmed = ccdproc.trim_image(oscan_subtracted, ... fits_section='[1:200, 1:100]') >>> # python-style: >>> trimmed = ccdproc.trim_image(oscan_subtracted[:, :200]) Note again that in python the order of indices is opposite that assumed in FITS format, that the last value in an index means "up to, but not including", and that a missing value implies either first or last value. Those familiar with python may wonder what the point of `~ccdproc.trim_image` is; it looks like simply indexing ``oscan_subtracted`` would accomplish the same thing. The only additional thing `~ccdproc.trim_image` does is to make a copy of the image before trimming it. .. note:: By default, python automatically reduces array indices that extend beyond the actual length of the array to the actual length. In practice, this means you can supply an invalid shape for, e.g. trimming, and an error will not be raised. To make this concrete, ``ccdproc.trim_image(oscan_subtracted[:, :200000000])`` will be treated as if you had put in the correct upper bound, ``200``. Subtract bias and dark ---------------------- Both of the functions below propagate the uncertainties in the science and calibration images if either or both is defined. Assume in this section that you have created a master bias image called ``master_bias`` and a master dark image called ``master_dark`` that *has been bias-subtracted* so that it can be scaled by exposure time if necessary. Subtract the bias with `~ccdproc.subtract_bias`: >>> fake_bias_data = np.random.normal(size=trimmed.shape) # just for illustration >>> master_bias = CCDData(fake_bias_data, unit=u.electron, ... mask=np.zeros(trimmed.shape)) >>> bias_subtracted = ccdproc.subtract_bias(trimmed, master_bias) There are several ways you can specify the exposure times of the dark and science images; see `~ccdproc.subtract_dark` for a full description. In the example below we assume there is a keyword ``exposure`` in the metadata of the trimmed image and the master dark and that the units of the exposure are seconds (note that you can instead explicitly provide these times). To perform the dark subtraction use `~ccdproc.subtract_dark`: >>> master_dark = master_bias.multiply(0.1) # just for illustration >>> master_dark.header['exposure'] = 15.0 >>> dark_subtracted = ccdproc.subtract_dark(bias_subtracted, master_dark, ... exposure_time='exposure', ... exposure_unit=u.second, ... scale=True) Note that scaling of the dark is not done by default; use ``scale=True`` to scale. Correct flat ------------ Given a flat frame called ``master_flat``, use `~ccdproc.flat_correct` to perform this calibration: >>> fake_flat_data = np.random.normal(loc=1.0, scale=0.05, size=trimmed.shape) >>> master_flat = CCDData(fake_flat_data, unit=u.electron) >>> reduced_image = ccdproc.flat_correct(dark_subtracted, master_flat) As with the additive calibrations, uncertainty is propagated in the division. The flat is scaled by the mean of ``master_flat`` before dividing. If desired, you can specify a minimum value the flat can have (e.g. to prevent division by zero). Any pixels in the flat whose value is less than ``min_value`` are replaced with ``min_value``): >>> reduced_image = ccdproc.flat_correct(dark_subtracted, master_flat, ... min_value=0.9) Basic Processing with a single command -------------------------------------- All of the basic processing steps can be accomplished in a single step using `~ccdproc.ccd_process`. This step will call overscan correct, trim, gain correct, add a bad pixel mask, create an uncertainty frame, subtract the master bias, and flat-field the image. The unit of the master calibration frames must match that of the image *after* the gain, if any, is applied. In the example below, ``img`` has unit ``adu``, but the master frames have unit ``electron``. These can be run together as: >>> ccd = CCDData(img, unit=u.adu) >>> ccd.header['exposure'] = 30.0 # for dark subtraction >>> nccd = ccdproc.ccd_process(ccd, oscan='[201:232,1:100]', ... trim='[1:200, 1:100]', ... error=True, ... gain=2.0*u.electron/u.adu, ... readnoise=5*u.electron, ... dark_frame=master_dark, ... exposure_key='exposure', ... exposure_unit=u.second, ... dark_scale=True, ... master_flat=master_flat) Reprojecting onto a different image footprint --------------------------------------------- An image with coordinate information (WCS) can be reprojected onto a different image footprint. The underlying functionality is proved by the `reproject project`_. Please see :ref:`reprojection` for more details. Data Quality Flags (Bitfields and bitmasks) ------------------------------------------- Some FITS files contain data quality flags or bitfield extension, while these are currently not supported as part of `~astropy.nddata.CCDData` these can be loaded manually using `~astropy.io.fits` and converted to regular (`numpy`-like) masks (with `~ccdproc.bitfield_to_boolean_mask`) that are supported by many operations in `ccdproc`. .. code:: import numpy as np from astropy.io import fits from ccdproc import bitfield_to_boolean_mask, CCDData fitsfilename = 'some_fits_file.fits' bitfieldextension = extensionname_or_extensionnumber # Read the data of the fits file as CCDData object ccd = CCDData.read(fitsfilename) # Open the file again (assuming the bitfield is saved in the same FITS file) mask = bitfield_to_boolean_mask(fits.getdata(fitsfilename, bitfieldextension)) # Save the mask as "mask" attribute of the ccd ccd.mask = mask Another method for creating a mask is using the `~ccdproc.ccdmask` task. This task will produced a data aray where good pixels have a value of zero and bad pixels have a value of one. This task follows the same algorithm used in the iraf ccdmask task. >>> ccd.mask = ccdproc.ccdmask(ccd, ncmed=7, nlmed=7, ncsig=15, nlsig=15, ... lsigma=9, hsigma=9, ngood=5) Filter and Convolution ---------------------- There are several convolution and filter functions for `numpy.ndarray` across the scientific python packages: - ``scipy.ndimage.filters``, offers a variety of filters. - ``astropy.convolution``, offers some filters which also handle ``NaN`` values. - ``scikit-image.filters``, offers several filters which can also handle masks but are mostly limited to special data types (mostly unsigned integers). For convenience one of these is also accessible through the ``ccdproc`` package namespace which accepts `~astropy.nddata.CCDData` objects and then also returns one: - `~ccdproc.median_filter` Median Filter +++++++++++++ The median filter is especially useful if the data contains sharp noise peaks which should be removed rather than propagated: .. plot:: :include-source: import ccdproc from astropy.nddata import CCDData import numpy as np import matplotlib.pyplot as plt from astropy.modeling.functional_models import Gaussian2D from astropy.utils.misc import NumpyRNGContext from scipy.ndimage import uniform_filter # Create some source signal source = Gaussian2D(60, 70, 70, 20, 25) data = source(*np.mgrid[0:250, 0:250]) # and another one source = Gaussian2D(70, 150, 180, 15, 15) data += source(*np.mgrid[0:250, 0:250]) # create some random signals with NumpyRNGContext(1234): noise = np.random.exponential(40, (250, 250)) # remove low signal noise[noise < 100] = 0 data += noise # create a CCD object based on the data ccd = CCDData(data, unit='adu') # Create some plots fig, (ax1, ax2, ax3) = plt.subplots(1, 3) ax1.set_title('Unprocessed') ax1.imshow(ccd, origin='lower', interpolation='none', cmap=plt.cm.gray) ax2.set_title('Mean filtered') ax2.imshow(uniform_filter(ccd.data, 5), origin='lower', interpolation='none', cmap=plt.cm.gray) ax3.set_title('Median filtered') ax3.imshow(ccdproc.median_filter(ccd, 5), origin='lower', interpolation='none', cmap=plt.cm.gray) plt.tight_layout() plt.show() Working with multi-extension FITS image files --------------------------------------------- Multi-extension FITS (MEF) image files cannot be processed natively in ``ccdproc``. The example below illustrates how to `~ccdproc.flat_correct` all of the extensions in a MEF and write out the calibrated file as a MEF. Applying other reduction steps would be similar. >>> from astropy.utils.data import get_pkg_data_filename >>> from astropy.io import fits >>> from astropy.nddata import CCDData >>> from ccdproc import flat_correct >>> >>> # Read sample images included in ccdproc >>> science_name = get_pkg_data_filename('data/science-mef.fits', ... package='ccdproc.tests') >>> flat_name = get_pkg_data_filename('data/flat-mef.fits', ... package='ccdproc.tests') >>> science_mef = fits.open(science_name) >>> flat_mef = fits.open(flat_name) >>> >>> new = [] >>> >>> # This assumes the primary header just has metadata >>> new.append(science_mef[0]) >>> >>> # The code below will preserve each image's header >>> for science_hdu, flat_hdu in zip(science_mef[1:], flat_mef[1:]): ... # Make a CCDData from this science image extension ... science = CCDData(data=science_hdu.data, ... header=science_hdu.header, ... unit=science_hdu.header['unit']) ... ... # Make a CCDData from this flat image extension ... flat = CCDData(data=flat_hdu.data, ... header=flat_hdu.header, ... unit=science_hdu.header['unit']) ... ... # Calibrate the science image ... science_cal = flat_correct(science, flat) ... ... # Turn the calibrated image into an image HDU ... as_hdu = fits.ImageHDU(data=science_cal.data, ... header=science_cal.header) ... ... # Add this hdu to the list of calibrated HDUs ... new.append(as_hdu) >>> # Write out the new MEF >>> as_hdulist = fits.HDUList(new) >>> as_hdulist.writeto('science_cal.fits') >>> # Close the input files >>> science_mef.close() >>> flat_mef.close() .. [1] van Dokkum, P; 2001, "Cosmic-Ray Rejection by Laplacian Edge Detection". The Publications of the Astronomical Society of the Pacific, Volume 113, Issue 789, pp. 1420-1427. doi: 10.1086/323894 .. [2] McCully, C., 2014, "Astro-SCRAPPY", https://github.com/astropy/astroscrappy .. _reproject project: http://reproject.readthedocs.io/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1553964972.0 ccdproc-2.3.0/docs/rtd-pip-requirements0000644000076600000240000000010300000000000021176 0ustar00mattcraigstaff00000000000000numpy>=1.9 scipy scikit-image numpydoc astropy>=3.0 sphinx-astropy ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1640111994.159763 ccdproc-2.3.0/licenses/0000755000076600000240000000000000000000000016036 5ustar00mattcraigstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/licenses/LICENSE_STSCI_TOOLS.txt0000644000076600000240000000266700000000000021621 0ustar00mattcraigstaff00000000000000Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA) 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. The name of AURA and its representatives may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY AURA ``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 AURA 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1534554413.0 ccdproc-2.3.0/licenses/README.rst0000644000076600000240000000037300000000000017530 0ustar00mattcraigstaff00000000000000Licenses ======== This directory holds license and credit information for works the ccdproc package is derived from or distributes, and/or datasets. The license file for the ccdproc package itself is placed in the root directory of this repository. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1577217856.0 ccdproc-2.3.0/pyproject.toml0000644000076600000240000000020400000000000017141 0ustar00mattcraigstaff00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel"] build-backend = 'setuptools.build_meta' ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1640111994.16134 ccdproc-2.3.0/setup.cfg0000644000076600000240000000352100000000000016053 0ustar00mattcraigstaff00000000000000[tool:pytest] minversion = 2.2 testpaths = "ccdproc" "docs" norecursedirs = build docs/_build doctest_plus = enabled addopts = --doctest-rst filterwarnings = error ignore:numpy.ndarray size changed:RuntimeWarning ignore:`np.bool` is a deprecated alias for the builtin `bool`:DeprecationWarning markers = data_size(N): set dimension of square data array for ccd_data fixture data_scale(s): set the scale of the normal distribution used to generate data data_mean(m): set the center of the normal distribution used to generate data [metadata] name = ccdproc description = Astropy affiliated package long_description = This is a package for reducing optical/IR CCD data that relies on astropy author = Steve Crawford, Matt Craig, and Michael Seifert author_email = ccdproc@gmail.com license = BSD url = http://ccdproc.readthedocs.io/ edit_on_github = False github_project = astropy/ccdproc [options] packages = find: zip_safe = False setup_requires = setuptools_scm install_requires = numpy>=1.18 astropy>=4.0.6 # Support LTS, but only with bug fixes scipy astroscrappy>=1.0.8 reproject>=0.7 scikit-image python_requires = >=3.7 [options.package_data] * = data/* [options.extras_require] test = pytest-astropy memory_profiler docs = sphinx-astropy matplotlib [pycodestyle] select = E101,E111,E112,E113,E221,E222,E223,E224,E225,E241,E242,E251,E271,E272,E303,E304,E502,E703,E901,E902,W191,W291,W292,W293,W391 exclude = _astropy_init.py,version.py [entry_points] [flake8] max-line-length = 100 [coverage:run] source = ccdproc omit = */ccdproc/__init__* */ccdproc/conftest.py */ccdproc/*setup* */ccdproc/*/tests/* */ccdproc/tests/* [coverage:report] exclude_lines = pragma: no cover except ImportError raise AssertionError raise NotImplementedError def main\(.*\): pragma: py{ignore_python_version} [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1577217856.0 ccdproc-2.3.0/setup.py0000755000076600000240000000311100000000000015742 0ustar00mattcraigstaff00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst import sys from setuptools import setup from pathlib import Path # 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 . 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 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) # NOTE: The configuration for the package, including the name, version, and # other information are set in the setup.cfg file. Here we mainly set up # setup_requires and install_requires since these are determined # programmatically. setup(use_scm_version={'write_to': Path('ccdproc') / 'version.py'}) # If compiled extensions are added to the package, add the argument below # to setup: # ext_modules=get_extensions()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1640102242.0 ccdproc-2.3.0/tox.ini0000644000076600000240000000454100000000000015550 0ustar00mattcraigstaff00000000000000[tox] requires = setuptools >= 30.3.0 pip >= 19.3.1 isolated_build = true [testenv] extras = test # Run the tests in a temporary directory to make sure that we don't # import this package from the source tree changedir = test: .tmp/{envname} description = run tests alldeps: with all optional dependencies devdeps: with the latest developer version of key dependencies oldestdeps: with the oldest supported version of key dependencies cov: and test coverage numpy117: with numpy 1.17.* numpy118: with numpy 1.18.* numpy119: with numpy 1.19.* numpy120: with numpy 1.20.* numpy121: with numpy 1.21.* astropylts: with the latest astropy LTS bottleneck: with bottleneck # The following provides some specific pinnings for key packages deps = cov: coverage numpy117: numpy==1.17.* numpy118: numpy==1.18.* numpy119: numpy==1.19.* numpy120: numpy==1.20.* numpy121: numpy==1.21.* astroscrappy11: astroscrappy==1.1.* astroscrappy10: astroscrappy==1.0.* astropylts: astropy==4.0.* bottleneck: bottleneck>=1.3.2 devdeps: git+https://github.com/astropy/astropy.git#egg=astropy devdeps: git+https://github.com/astropy/astroscrappy.git#egg=astroscrappy # Remember to transfer any changes here to setup.cfg also. Only listing # packages which are constrained in the setup.cfg # NOTE ABOUT NUMPY VERSION: for astroscrappy 1.0.8 have to use at least 1.20 # for the tests to even get to the point of running. oldestdeps: numpy==1.20.* oldestdeps: astropy==4.0.* oldestdeps: reproject==0.7 oldestdeps: astroscrappy==1.0.8 oldestdeps: cython commands = pip freeze !cov-!oldestdeps: pytest --pyargs ccdproc {toxinidir}/docs {posargs} cov: pytest --pyargs ccdproc {toxinidir}/docs --cov ccdproc --cov-config={toxinidir}/setup.cfg {posargs} cov: coverage xml -o {toxinidir}/coverage.xml # Do not care about warnings on the oldest builds oldestdeps: pytest --pyargs ccdproc {toxinidir}/docs -W ignore {posargs} [testenv:build_docs] extras = docs setenv = HOME = {envtmpdir} changedir = docs commands = sphinx-build . _build/html -b html -W {posargs} [testenv:pycodestyle] skip_install = true changedir = . description = check code style with pycodestyle deps = pycodestyle commands = pycodestyle ccdproc --count --show-source --show-pep8